1<?php 2 3namespace Mpdf\Shaper; 4 5class Sea 6{ 7 // South East Asian shaper 8 // sea_category 9 const OT_X = 0; 10 11 const OT_C = 1; 12 13 const OT_IV = 2; # Independent Vowel 14 15 const OT_T = 3; # Tone Marks 16 17 const OT_H = 4; # Halant 18 19 const OT_A = 10; # Anusvara 20 21 const OT_GB = 12; # Generic Base (OT_DOTTEDCIRCLE in Indic) 22 23 const OT_CM = 17; # Consonant Medial 24 25 const OT_MR = 22; # Medial Ra 26 27 const OT_VABV = 26; 28 29 const OT_VBLW = 27; 30 31 const OT_VPRE = 28; 32 33 const OT_VPST = 29; 34 35 // ? From Indic categories 36 const OT_ZWNJ = 5; 37 38 const OT_ZWJ = 6; 39 40 const OT_M = 7; 41 42 const OT_SM = 8; 43 44 const OT_VD = 9; 45 46 const OT_NBSP = 11; 47 48 const OT_RS = 13; 49 50 const OT_COENG = 14; 51 52 const OT_REPHA = 15; 53 54 const OT_RA = 16; 55 56 /* Visual positions in a syllable from left to right. */ 57 // sea_position 58 const POS_START = 0; 59 60 const POS_RA_TO_BECOME_REPH = 1; 61 62 const POS_PRE_M = 2; 63 64 const POS_PRE_C = 3; 65 66 const POS_BASE_C = 4; 67 68 const POS_AFTER_MAIN = 5; 69 70 const POS_ABOVE_C = 6; 71 72 const POS_BEFORE_SUB = 7; 73 74 const POS_BELOW_C = 8; 75 76 const POS_AFTER_SUB = 9; 77 78 const POS_BEFORE_POST = 10; 79 80 const POS_POST_C = 11; 81 82 const POS_AFTER_POST = 12; 83 84 const POS_FINAL_C = 13; 85 86 const POS_SMVD = 14; 87 88 const POS_END = 15; 89 90 // Based on sea_category used to make string to find syllables 91 // OT_ to string character (using e.g. OT_C from INDIC) hb-ot-shape-complex-sea-private.hh 92 public static $sea_category_char = [ 93 'x', 94 'C', 95 'V', 96 'T', 97 'H', 98 'x', 99 'x', 100 'x', 101 'x', 102 'x', 103 'A', 104 'x', 105 'G', 106 'x', 107 'x', 108 'x', 109 'x', 110 'M', 111 'x', 112 'x', 113 'x', 114 'x', 115 'R', 116 'x', 117 'x', 118 'x', 119 'a', 120 'b', 121 'p', 122 't', 123 ]; 124 125 public static function set_sea_properties(&$info, $scriptblock) 126 { 127 $u = $info['uni']; 128 $type = self::sea_get_categories($u); 129 $cat = ($type & 0x7F); 130 $pos = ($type >> 8); 131 132 /* 133 * Re-assign category 134 */ 135 // Medial Ra 136 if ($u == 0x1A55 || $u == 0xAA34) { 137 $cat = self::OT_MR; 138 } 139 140 /* 141 * Re-assign position. 142 */ 143 if ($cat == self::OT_M) { // definitely "OT_M" in HarfBuzz - although this does not seem to have been defined ? should be OT_MR 144 switch ($pos) { 145 case self::POS_PRE_C: 146 $cat = self::OT_VPRE; 147 break; 148 case self::POS_ABOVE_C: 149 $cat = self::OT_VABV; 150 break; 151 case self::POS_BELOW_C: 152 $cat = self::OT_VBLW; 153 break; 154 case self::POS_POST_C: 155 $cat = self::OT_VPST; 156 break; 157 } 158 } 159 160 $info['sea_category'] = $cat; 161 $info['sea_position'] = $pos; 162 } 163 164 // syllable_type 165 const CONSONANT_SYLLABLE = 0; 166 167 const BROKEN_CLUSTER = 1; 168 169 const NON_SEA_CLUSTER = 2; 170 171 public static function set_syllables(&$o, $s, &$broken_syllables) 172 { 173 $ptr = 0; 174 $syllable_serial = 1; 175 $broken_syllables = false; 176 while ($ptr < strlen($s)) { 177 $match = ''; 178 $syllable_length = 1; 179 $syllable_type = self::NON_SEA_CLUSTER; 180 181 // CONSONANT_SYLLABLE Consonant syllable 182 if (preg_match('/^(C|V|G)(p|a|b|t|HC|M|R|T|A)*/', substr($s, $ptr), $ma)) { 183 $syllable_length = strlen($ma[0]); 184 $syllable_type = self::CONSONANT_SYLLABLE; 185 } // BROKEN_CLUSTER syllable 186 elseif (preg_match('/^(p|a|b|t|HC|M|R|T|A)+/', substr($s, $ptr), $ma)) { 187 $syllable_length = strlen($ma[0]); 188 $syllable_type = self::BROKEN_CLUSTER; 189 $broken_syllables = true; 190 } 191 192 for ($i = $ptr; $i < $ptr + $syllable_length; $i++) { 193 $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; 194 } 195 $ptr += $syllable_length; 196 $syllable_serial++; 197 if ($syllable_serial == 16) { 198 $syllable_serial = 1; 199 } 200 } 201 } 202 203 public static function initial_reordering(&$info, $GSUBdata, $broken_syllables, $scriptblock, $dottedcircle) 204 { 205 206 if ($broken_syllables && $dottedcircle) { 207 self::insert_dotted_circles($info, $dottedcircle); 208 } 209 210 $count = count($info); 211 if (!$count) { 212 return; 213 } 214 $last = 0; 215 $last_syllable = $info[0]['syllable']; 216 for ($i = 1; $i < $count; $i++) { 217 if ($last_syllable != $info[$i]['syllable']) { 218 self::initial_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $i); 219 $last = $i; 220 $last_syllable = $info[$last]['syllable']; 221 } 222 } 223 self::initial_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $count); 224 } 225 226 public static function insert_dotted_circles(&$info, $dottedcircle) 227 { 228 $idx = 0; 229 $last_syllable = 0; 230 while ($idx < count($info)) { 231 $syllable = $info[$idx]['syllable']; 232 $syllable_type = ($syllable & 0x0F); 233 if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) { 234 $last_syllable = $syllable; 235 $dottedcircle[0]['syllable'] = $info[$idx]['syllable']; 236 array_splice($info, $idx, 0, $dottedcircle); 237 } else { 238 $idx++; 239 } 240 } 241 } 242 243 public static function initial_reordering_syllable(&$info, $GSUBdata, $scriptblock, $start, $end) 244 { 245 /* broken_cluster: We already inserted dotted-circles, so just call the standalone_cluster. */ 246 247 $syllable_type = ($info[$start]['syllable'] & 0x0F); 248 if ($syllable_type == self::NON_SEA_CLUSTER) { 249 return; 250 } 251 if ($syllable_type == self::BROKEN_CLUSTER) { 252 /* For dotted-circle, this is what Uniscribe does: 253 * If dotted-circle is the last glyph, it just does nothing. */ 254 if ($info[$end - 1]['sea_category'] == self::OT_GB) { 255 return; 256 } 257 } 258 259 $base = $start; 260 $i = $start; 261 for (; $i < $base; $i++) { 262 $info[$i]['sea_position'] = self::POS_PRE_C; 263 } 264 if ($i < $end) { 265 $info[$i]['sea_position'] = self::POS_BASE_C; 266 $i++; 267 } 268 for (; $i < $end; $i++) { 269 if (isset($info[$i]['sea_category']) && $info[$i]['sea_category'] == self::OT_MR) { /* Pre-base reordering */ 270 $info[$i]['sea_position'] = self::POS_PRE_C; 271 continue; 272 } 273 if (isset($info[$i]['sea_category']) && $info[$i]['sea_category'] == self::OT_VPRE) { /* Left matra */ 274 $info[$i]['sea_position'] = self::POS_PRE_M; 275 continue; 276 } 277 $info[$i]['sea_position'] = self::POS_AFTER_MAIN; 278 } 279 280 /* Sit tight, rock 'n roll! */ 281 self::bubble_sort($info, $start, $end - $start); 282 } 283 284 public static function final_reordering(&$info, $GSUBdata, $scriptblock) 285 { 286 $count = count($info); 287 if (!$count) { 288 return; 289 } 290 $last = 0; 291 $last_syllable = $info[0]['syllable']; 292 for ($i = 1; $i < $count; $i++) { 293 if ($last_syllable != $info[$i]['syllable']) { 294 self::final_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $i); 295 $last = $i; 296 $last_syllable = $info[$last]['syllable']; 297 } 298 } 299 self::final_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $count); 300 } 301 302 public static function final_reordering_syllable(&$info, $GSUBdata, $scriptblock, $start, $end) 303 { 304 /* 305 * Nothing to do here at present! 306 */ 307 } 308 309 public static $sea_table = [ 310 /* New Tai Lue (1980..19DF) */ 311 312 /* 1980 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 313 /* 1988 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 314 /* 1990 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 315 /* 1998 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 316 /* 19A0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 317 /* 19A8 */ 3841, 3841, 3841, 3841, 3840, 3840, 3840, 3840, 318 /* 19B0 */ 2823, 2823, 2823, 2823, 2823, 775, 775, 775, 319 /* 19B8 */ 2823, 2823, 775, 2823, 2823, 2823, 2823, 2823, 320 /* 19C0 */ 2823, 3857, 3857, 3857, 3857, 3857, 3857, 3857, 321 /* 19C8 */ 3843, 3843, 3840, 3840, 3840, 3840, 3840, 3840, 322 /* 19D0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 323 /* 19D8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 324 /* Tai Tham (1A20..1AAF) */ 325 326 /* 1A20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 327 /* 1A28 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 328 /* 1A30 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 329 /* 1A38 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 330 /* 1A40 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 331 /* 1A48 */ 3841, 3841, 3841, 3841, 3841, 3842, 3842, 3842, 332 /* 1A50 */ 3842, 3842, 3842, 3841, 3841, 3857, 3857, 3857, 333 /* 1A58 */ 3857, 3857, 3857, 3857, 3857, 3857, 3857, 3840, 334 /* 1A60 */ 3844, 2823, 1543, 2823, 2823, 1543, 1543, 1543, 335 /* 1A68 */ 1543, 2055, 2055, 1543, 2055, 2823, 775, 775, 336 /* 1A70 */ 775, 775, 775, 1543, 1543, 3843, 3843, 3843, 337 /* 1A78 */ 3843, 3843, 3840, 3840, 3840, 3840, 3840, 3840, 338 /* 1A80 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 339 /* 1A88 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 340 /* 1A90 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 341 /* 1A98 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 342 /* 1AA0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 343 /* 1AA8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 344 /* Cham (AA00..AA5F) */ 345 346 /* AA00 */ 3842, 3842, 3842, 3842, 3842, 3842, 3841, 3841, 347 /* AA08 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 348 /* AA10 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 349 /* AA18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 350 /* AA20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, 351 /* AA28 */ 3841, 1543, 1543, 1543, 1543, 2055, 1543, 775, 352 /* AA30 */ 775, 1543, 2055, 3857, 3857, 3857, 3857, 3840, 353 /* AA38 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 354 /* AA40 */ 3857, 3857, 3857, 3857, 3857, 3857, 3857, 3857, 355 /* AA48 */ 3857, 3857, 3857, 3857, 3857, 3857, 3840, 3840, 356 /* AA50 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 357 /* AA58 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, 358 ]; 359 360 public static function sea_get_categories($u) 361 { 362 if (0x1980 <= $u && $u <= 0x19DF) { 363 return self::$sea_table[$u - 0x1980]; // offset 0 for New Tai Lue 364 } 365 if (0x1A20 <= $u && $u <= 0x1AAF) { 366 return self::$sea_table[$u - 0x1A20 + 96]; // offset for Tai Tham 367 } 368 if (0xAA00 <= $u && $u <= 0xAA5F) { 369 return self::$sea_table[$u - 0xAA00 + 96 + 144]; // Cham 370 } 371 if ($u == 0x00A0) { 372 return 3851; // (ISC_CP | (IMC_x << 8)) 373 } 374 if ($u == 0x25CC) { 375 return 3851; // (ISC_CP | (IMC_x << 8)) 376 } 377 return 3840; // (ISC_x | (IMC_x << 8)) 378 } 379 380 public static function bubble_sort(&$arr, $start, $len) 381 { 382 if ($len < 2) { 383 return; 384 } 385 $k = $start + $len - 2; 386 while ($k >= $start) { 387 for ($j = $start; $j <= $k; $j++) { 388 if ($arr[$j]['sea_position'] > $arr[$j + 1]['sea_position']) { 389 $t = $arr[$j]; 390 $arr[$j] = $arr[$j + 1]; 391 $arr[$j + 1] = $t; 392 } 393 } 394 $k--; 395 } 396 } 397} 398