Initial commit

This commit is contained in:
2026-08-30 22:02:02 +00:00
commit b6bd2277f5
2334 changed files with 646393 additions and 0 deletions
@@ -0,0 +1,187 @@
<?php
/**
* DevanagariTest.php
*
* @since 2026-04-30
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-unicode
*
* This file is part of tc-lib-unicode software library.
*/
namespace Test\Substitution;
use Com\Tecnick\Unicode\Substitution\Devanagari;
use PHPUnit\Framework\Attributes\DataProvider;
use Test\TestUtil;
/**
* Devanagari substitution test
*
* @since 2026-04-30
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-unicode
*/
class DevanagariTest extends TestUtil
{
/**
* @param array<int, int> $input
* @param array<int, int> $expected
*/
#[DataProvider('devanagariDataProvider')]
public function testGetOrdarr(array $input, array $expected): void
{
$obj = new Devanagari($input);
$this->assertSame($expected, $obj->getOrdarr());
}
/**
* @return array<string, array{0: array<int, int>, 1: array<int, int>}>
*/
public static function devanagariDataProvider(): array
{
// Codepoint reference:
// U+0915 KA U+0916 KHA U+0937 SHA U+0939 HA
// U+094D VIRAMA U+093F VOWEL SIGN I (left matra)
// U+0958 QA (first extended) U+095F YYA (last extended)
return [
// Empty input returns empty output
'empty' => [
[],
[],
],
// Pure ASCII: no Devanagari codepoints, pass through unchanged
'ascii_only' => [
[0x41, 0x42, 0x43],
[0x41, 0x42, 0x43],
],
// Single consonant: no matra follows, pass through unchanged
// U+0915 KA
'consonant_only' => [
[0x0915],
[0x0915],
],
// Last standard consonant: U+0939 HA
'last_standard_consonant' => [
[0x0939],
[0x0939],
],
// First extended consonant: U+0958 QA, unchanged (no matra)
'first_extended_consonant' => [
[0x0958],
[0x0958],
],
// Last extended consonant: U+095F YYA, unchanged (no matra)
'last_extended_consonant' => [
[0x095F],
[0x095F],
],
// Orphaned left matra at start (no preceding consonant): unchanged
// U+093F alone
'orphaned_left_matra' => [
[0x093F],
[0x093F],
],
// Left matra followed by consonant (orphaned leading matra):
// the matra is not recognised as following a cluster, unchanged
// U+093F, U+0915
'matra_then_consonant_no_reorder' => [
[0x093F, 0x0915],
[0x093F, 0x0915],
],
// Simple reposition: U+0915 KA + U+093F → U+093F, U+0915
'ka_with_i_vowel' => [
[0x0915, 0x093F],
[0x093F, 0x0915],
],
// U+094E PRISHTHAMATRA E is the second vowel sign of Indic
// Positional Category Left: U+0915 KA + U+094E
'ka_with_prishthamatra_e' => [
[0x0915, 0x094E],
[0x094E, 0x0915],
],
// Last standard consonant + left matra: U+0939 HA + U+093F
'ha_with_i_vowel' => [
[0x0939, 0x093F],
[0x093F, 0x0939],
],
// Extended consonant + left matra: U+0958 QA + U+093F
'extended_consonant_with_matra' => [
[0x0958, 0x093F],
[0x093F, 0x0958],
],
// Conjunct cluster: U+0915 KA + U+094D VIRAMA + U+0916 KHA + U+093F
// → U+093F, U+0915, U+094D, U+0916
'conjunct_with_matra' => [
[0x0915, 0x094D, 0x0916, 0x093F],
[0x093F, 0x0915, 0x094D, 0x0916],
],
// Longer conjunct: KA + VIRAMA + SHA + VIRAMA + HA + VOWEL SIGN I
// → U+093F, KA, VIRAMA, SHA, VIRAMA, HA
'three_consonant_conjunct_with_matra' => [
[0x0915, 0x094D, 0x0937, 0x094D, 0x0939, 0x093F],
[0x093F, 0x0915, 0x094D, 0x0937, 0x094D, 0x0939],
],
// Conjunct where virama is not followed by a consonant: cluster
// ends at the virama; the matra after virama is NOT moved
// U+0915, U+094D, U+093F → U+0915, U+094D, U+093F
// (U+094D followed by non-consonant ends the cluster at KA only,
// but then U+094D is the next codepoint: not a left matra)
'virama_then_matra_no_reorder' => [
[0x0915, 0x094D, 0x093F],
[0x0915, 0x094D, 0x093F],
],
// Consonant followed by non-matra: pass through unchanged
// U+0915, 0x41 (ASCII A)
'consonant_then_ascii' => [
[0x0915, 0x41],
[0x0915, 0x41],
],
// Two separate simple clusters
// KA+I, KHA+I → I+KA, I+KHA
'two_simple_clusters' => [
[0x0915, 0x093F, 0x0916, 0x093F],
[0x093F, 0x0915, 0x093F, 0x0916],
],
// Mixed: ASCII + Devanagari cluster + ASCII
// 0x41, U+0915, U+093F, 0x42 → 0x41, U+093F, U+0915, 0x42
'mixed_ascii_devanagari' => [
[0x41, 0x0915, 0x093F, 0x42],
[0x41, 0x093F, 0x0915, 0x42],
],
];
}
public function testNormalizesSparseIndexes(): void
{
$obj = new Devanagari([5 => 0x0915, 9 => 0x093F]);
$this->assertSame([0x093F, 0x0915], $obj->getOrdarr());
}
}
@@ -0,0 +1,230 @@
<?php
/**
* HangulTest.php
*
* @since 2026-04-30
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-unicode
*
* This file is part of tc-lib-unicode software library.
*/
namespace Test\Substitution;
use Com\Tecnick\Unicode\Substitution\Hangul;
use PHPUnit\Framework\Attributes\DataProvider;
use Test\TestUtil;
/**
* Hangul Jamo composition test
*
* @since 2026-04-30
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-unicode
*/
class HangulTest extends TestUtil
{
/**
* @param array<int, int> $input
* @param array<int, int> $expected
*/
#[DataProvider('hangulDataProvider')]
public function testGetOrdarr(array $input, array $expected): void
{
$obj = new Hangul($input);
$this->assertSame($expected, $obj->getOrdarr());
}
/**
* @return array<string, array{0: array<int, int>, 1: array<int, int>}>
*
* Expected values verified against Unicode conformance data (NormalizationTest.txt,
* Hangul section) and the algorithmic formula in Unicode Standard 15.1 §3.12:
* S = SBase(AC00) + (L1100)×NCount(588) + (V1161)×TCount(28) + (T11A7)
*
* Spot-checks:
* 가 (U+AC00) = AC00 + (11001100)×588 + (11611161)×28 + 0 = AC00
* 나 (U+B098) = AC00 + (11021100)×588 + (11611161)×28 = AC00 + 2×588 = AC00+1176 = B1D8? No wait:
* L=U+1102 (NIEUN), V=U+1161 (A)
* S = AC00 + (1102-1100)*588 + (1161-1161)*28 = AC00 + 2*588 = AC00 + 1176 = 0xB1D8 -- that's 나 but wait
* Actually 나 = U+B098: B098 - AC00 = 1176 - hmm, 0xB098 - 0xAC00 = 0x498 = 1176. Yes.
* (1102-1100)*588 = 2*588 = 1176. Correct.
* 닭 (U+B2ED) = AC00 + (1103-1100)*588 + (1161-1161)*28 + (11AF-11A7) -- wait닭 has T=U+11BC?
* 닭: L=U+1103 (TIKEUT), V=U+1161 (A), T=U+11BC (IEUNG)? No.
* Let's just use simple known values:
* 가 U+AC00: L=U+1100, V=U+1161 → AC00 + 0 + 0 = AC00 ✓
* 각 U+AC01: L=U+1100, V=U+1161, T=U+11A8 → AC00 + 0 + (11A8-11A7)=1 = AC01 ✓
* 갈 U+AC08: L=U+1100, V=U+1161, T=U+11AF → AC00 + 7 = AC07? 11AF-11A7=8, so AC00+8=AC08 ✓
* 나 U+B098: L=U+1102, V=U+1161 → AC00+2*588=AC00+0x498=B098 -- 0xAC00+0x498=0xB098?
* 0xAC00=44032, 2*588=1176, 44032+1176=45208=0xB098. ✓
*/
public static function hangulDataProvider(): array
{
return [
// Empty input returns empty output
'empty' => [
[],
[],
],
// Rule 2 with a precomposed LV syllable already in the input:
// U+AC00 (GA) + U+11A8 (KIYEOK) -> U+AC01 (GAG)
'precomposed_lv_plus_t' => [
[0xAC00, 0x11A8],
[0xAC01],
],
// A syllable that already has a trailing consonant is not composed again
'lvt_plus_t' => [
[0xAC01, 0x11A8],
[0xAC01, 0x11A8],
],
// A precomposed LV syllable followed by anything else is left alone
'precomposed_lv_plus_ascii' => [
[0xAC00, 0x41],
[0xAC00, 0x41],
],
// Pure ASCII: no Hangul, pass through unchanged
'ascii_only' => [
[0x41, 0x42, 0x43],
[0x41, 0x42, 0x43],
],
// Leading consonant at end of array (no following vowel): unchanged
// U+1100 KIYEOK alone
'lone_leading_consonant' => [
[0x1100],
[0x1100],
],
// First leading consonant, last leading consonant: boundary check
// U+1100, U+1112: no vowels follow; both pass through
'leading_consonant_boundaries' => [
[0x1100, 0x1112],
[0x1100, 0x1112],
],
// Codepoint just above leading consonant range (U+1113): not L, pass through
'above_leading_consonant_range' => [
[0x1113],
[0x1113],
],
// Vowel alone: not a leading consonant, pass through
// U+1161 JUNGSEONG A
'lone_vowel' => [
[0x1161],
[0x1161],
],
// Trailing consonant alone: not a leading consonant, pass through
// U+11A8 JONGSEONG KIYEOK
'lone_trailing_consonant' => [
[0x11A8],
[0x11A8],
],
// L + V → LV syllable (no trailing consonant)
// U+1100 + U+1161 → U+AC00 가 (GA)
'l_plus_v_ga' => [
[0x1100, 0x1161],
[0xAC00],
],
// L + V boundary: last L (U+1112) + last V (U+1175) → syllable
// S = AC00 + 18*588 + 20*28 = AC00 + 10584 + 560 = AC00 + 11144 = D7A4 - 28 = D784?
// 0xAC00 + 18*588 + 20*28 = 44032 + 10584 + 560 = 55176 = 0xD788
'l_plus_v_boundary' => [
[0x1112, 0x1175],
[0xD788],
],
// L + V + T → LVT syllable
// U+1100 + U+1161 + U+11A8 → U+AC01 각 (GAK)
// LV = AC00, T = 11A8 11A7 = 1 → AC00 + 1 = AC01
'l_plus_v_plus_t_gak' => [
[0x1100, 0x1161, 0x11A8],
[0xAC01],
],
// L + V + T with T = last valid trailing consonant (U+11C2)
// U+1100 + U+1161 + U+11C2 → AC00 + (11C2 11A7) = AC00 + 27 = AC1B
'l_plus_v_plus_t_last_trailing' => [
[0x1100, 0x1161, 0x11C2],
[0xAC1B],
],
// L + V + TBase (U+11A7): TBase itself is NOT a valid trailing
// consonant; treated as next non-T codepoint. LV emitted, then
// U+11A7 passed through unchanged.
'l_plus_v_plus_tbase_not_trailing' => [
[0x1100, 0x1161, 0x11A7],
[0xAC00, 0x11A7],
],
// L + V + codepoint above T range (U+11C3): not a trailing consonant,
// LV emitted then U+11C3 passed through
'l_plus_v_then_above_t_range' => [
[0x1100, 0x1161, 0x11C3],
[0xAC00, 0x11C3],
],
// L followed by non-vowel (ASCII): L emitted unchanged, then ASCII
'leading_consonant_then_ascii' => [
[0x1100, 0x41],
[0x1100, 0x41],
],
// Two separate LV syllables in sequence
// U+1100+U+1161, U+1102+U+1161 → U+AC00, U+B098
// B098: AC00 + 2*588 = AC00 + 1176 = 0xB098
'two_lv_syllables' => [
[0x1100, 0x1161, 0x1102, 0x1161],
[0xAC00, 0xB098],
],
// Mixed: ASCII + Jamo cluster + ASCII
// 0x41, U+1100, U+1161, 0x42 → 0x41, U+AC00, 0x42
'mixed_ascii_hangul' => [
[0x41, 0x1100, 0x1161, 0x42],
[0x41, 0xAC00, 0x42],
],
// L + V + T + next L + V: two clusters in series
// U+1100, U+1161, U+11A8, U+1102, U+1161 → U+AC01, U+B098
'two_clusters_with_trailing' => [
[0x1100, 0x1161, 0x11A8, 0x1102, 0x1161],
[0xAC01, 0xB098],
],
// L + first vowel out-of-range: U+1160 is just below VBASE: not a vowel
'leading_consonant_then_below_vbase' => [
[0x1100, 0x1160],
[0x1100, 0x1160],
],
// L + first codepoint above vowel range: U+1176: not a vowel
'leading_consonant_then_above_vrange' => [
[0x1100, 0x1176],
[0x1100, 0x1176],
],
];
}
public function testNormalizesSparseIndexes(): void
{
$obj = new Hangul([10 => 0x1100, 20 => 0x1161]);
$this->assertSame([0xAC00], $obj->getOrdarr());
}
}
@@ -0,0 +1,157 @@
<?php
/**
* ThaiTest.php
*
* @since 2026-04-30
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-unicode
*
* This file is part of tc-lib-unicode software library.
*/
namespace Test\Substitution;
use Com\Tecnick\Unicode\Substitution\Thai;
use PHPUnit\Framework\Attributes\DataProvider;
use Test\TestUtil;
/**
* Thai substitution test
*
* @since 2026-04-30
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-unicode
*/
class ThaiTest extends TestUtil
{
/**
* @param array<int, int> $input
* @param array<int, int> $expected
*/
#[DataProvider('thaiDataProvider')]
public function testGetOrdarr(array $input, array $expected): void
{
$obj = new Thai($input);
$this->assertSame($expected, $obj->getOrdarr());
}
/**
* @return array<string, array{0: array<int, int>, 1: array<int, int>}>
*/
public static function thaiDataProvider(): array
{
return [
// Empty input returns empty output
'empty' => [
[],
[],
],
// Pure ASCII: no Thai codepoints, pass through unchanged
'ascii_only' => [
[0x41, 0x42, 0x43],
[0x41, 0x42, 0x43],
],
// Thai consonant only (no leading vowel): unchanged
// U+0E01 THAI CHARACTER KO KAI
'consonant_only' => [
[0x0E01],
[0x0E01],
],
// Tone mark only: not a leading vowel, unchanged
// U+0E48 THAI CHARACTER MAI EK
'tone_mark_only' => [
[0x0E48],
[0x0E48],
],
// Single leading vowel at end of array (orphaned): leave unchanged
// U+0E40 THAI CHARACTER SARA E
'orphaned_leading_vowel_end' => [
[0x0E40],
[0x0E40],
],
// Leading vowel followed by a tone mark (not a base consonant):
// leave unchanged: U+0E40, U+0E48
'leading_vowel_then_tone_mark' => [
[0x0E40, 0x0E48],
[0x0E40, 0x0E48],
],
// Leading vowel followed by an ASCII character (not a base
// consonant): leave unchanged: U+0E40, 0x41
'leading_vowel_then_ascii' => [
[0x0E40, 0x41],
[0x0E40, 0x41],
],
// Preposed vowel: stored and displayed before the consonant
// U+0E40 (SARA E) + U+0E01 (KO KAI)
'sara_e_before_ko_kai' => [
[0x0E40, 0x0E01],
[0x0E40, 0x0E01],
],
// U+0E41 (SARA AE) + U+0E02 (KHO KHAI)
'sara_ae_before_kho_khai' => [
[0x0E41, 0x0E02],
[0x0E41, 0x0E02],
],
// U+0E44 (SARA AI MAIMALAI) + last base consonant U+0E2E
'sara_ai_before_ho_nokhuk' => [
[0x0E44, 0x0E2E],
[0x0E44, 0x0E2E],
],
// Multiple consecutive preposed vowels before one consonant
'two_leading_vowels_then_consonant' => [
[0x0E40, 0x0E41, 0x0E01],
[0x0E40, 0x0E41, 0x0E01],
],
// Preposed vowel + consonant + tone mark
'vowel_consonant_tone' => [
[0x0E40, 0x0E01, 0x0E48],
[0x0E40, 0x0E01, 0x0E48],
],
// Mixed: ASCII + Thai cluster + ASCII
'mixed_ascii_thai' => [
[0x41, 0x0E40, 0x0E01, 0x0E48, 0x42],
[0x41, 0x0E40, 0x0E01, 0x0E48, 0x42],
],
// Two separate Thai clusters in one array
'two_clusters' => [
[0x0E40, 0x0E01, 0x0E44, 0x0E2E],
[0x0E40, 0x0E01, 0x0E44, 0x0E2E],
],
// Multiple consecutive leading vowels followed by non-consonant
// (both left unchanged)
'two_leading_vowels_no_consonant' => [
[0x0E40, 0x0E41],
[0x0E40, 0x0E41],
],
];
}
public function testNormalizesSparseIndexes(): void
{
$obj = new Thai([3 => 0x0E40, 7 => 0x0E01]);
$this->assertSame([0x0E40, 0x0E01], $obj->getOrdarr());
}
}