PluginProbe
PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin / 1.8.2
PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin v1.8.2
trunk 1.5 1.6 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 All 71 releases
pdf-print / mpdf / classes / indic.php

indic.php in PDF & Print by BestWebSoft – WordPress Posts and Pages PDF Generator Plugin 1.8.2, at mpdf/classes/indic.php

1,714 lines 66.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 class INDIC {
5
6 /* FROM hb-ot-shape-complex-indic-private.hh */
7 // indic_category
8 const OT_X = 0;
9 const OT_C = 1;
10 const OT_V = 2;
11 const OT_N = 3;
12 const OT_H = 4;
13 const OT_ZWNJ = 5;
14 const OT_ZWJ = 6;
15 const OT_M = 7; /* Matra or Dependent Vowel */
16 const OT_SM = 8;
17 const OT_VD = 9;
18 const OT_A = 10;
19 const OT_NBSP = 11;
20 const OT_DOTTEDCIRCLE = 12; /* Not in the spec, but special in Uniscribe. /Very very/ special! */
21 const OT_RS = 13; /* Register Shifter, used in Khmer OT spec */
22 const OT_Coeng = 14;
23 const OT_Repha = 15;
24 const OT_Ra = 16; /* Not explicitly listed in the OT spec, but used in the grammar. */
25 const OT_CM = 17;
26
27
28 // Based on indic_category used to make string to find syllables
29 // OT_ to string character (using e.g. OT_C from INDIC) hb-ot-shape-complex-indic-private.hh
30 public static $indic_category_char = array(
31 'x',
32 'C',
33 'V',
34 'N',
35 'H',
36 'Z',
37 'J',
38 'M',
39 'S',
40 'v',
41 'A', /* Spec gives Andutta U+0952 as OT_A. However, testing shows that Uniscribe
42 * treats U+0951..U+0952 all as OT_VD - see set_indic_properties */
43 's',
44 'D',
45 'F', /* Register shift Khmer only */
46 'G', /* Khmer only */
47 'r', /* 0D4E (dot reph) only one in Malayalam */
48 'R',
49 'm', /* Consonant medial only used in Indic 0A75 in Gurmukhi (0A00..0A7F) : also in Lao, Myanmar, Tai Tham, Javanese & Cham */
50 );
51
52
53 /* Visual positions in a syllable from left to right. */
54 /* FROM hb-ot-shape-complex-indic-private.hh */
55 // indic_position
56 const POS_START = 0;
57
58 const POS_RA_TO_BECOME_REPH = 1;
59 const POS_PRE_M = 2;
60 const POS_PRE_C = 3;
61
62 const POS_BASE_C = 4;
63 const POS_AFTER_MAIN = 5;
64
65 const POS_ABOVE_C = 6;
66
67 const POS_BEFORE_SUB = 7;
68 const POS_BELOW_C = 8;
69 const POS_AFTER_SUB = 9;
70
71 const POS_BEFORE_POST = 10;
72 const POS_POST_C = 11;
73 const POS_AFTER_POST = 12;
74
75 const POS_FINAL_C = 13;
76 const POS_SMVD = 14;
77
78 const POS_END = 15;
79
80 /*
81 * Basic features.
82 * These features are applied in order, one at a time, after initial_reordering.
83 */
84 /*
85 * Must be in the same order as the indic_features array. Ones starting with _ are F_GLOBAL
86 * Ones without the _ are only applied where the mask says!
87 */
88 const _NUKT = 0;
89 const _AKHN = 1;
90 const RPHF = 2;
91 const _RKRF = 3;
92 const PREF = 4;
93 const BLWF = 5;
94 const HALF = 6;
95 const ABVF = 7;
96 const PSTF = 8;
97 const CFAR = 9; // Khmer only
98 const _VATU = 10;
99 const _CJCT = 11;
100 const INIT = 12;
101
102
103 public static function set_indic_properties(&$info, $scriptblock ) {
104 $u = $info['uni'];
105 $type = self::indic_get_categories($u);
106 $cat = ($type & 0x7F);
107 $pos = ($type >> 8);
108
109 /*
110 * Re-assign category
111 */
112
113 if ($u == 0x17D1) $cat = self::OT_X;
114
115 if ($cat == self::OT_X && self::in_range($u, 0x17CB, 0x17D3)) { /* Khmer Various signs */
116 /* These are like Top Matras. */
117 $cat = self::OT_M;
118 $pos = self::POS_ABOVE_C;
119 }
120
121 if ($u == 0x17C6) $cat = self::OT_N; /* Khmer Bindu doesn't like to be repositioned. */
122
123 if ($u == 0x17D2) $cat = self::OT_Coeng; /* Khmer coeng */
124
125 /* The spec says U+0952 is OT_A. However, testing shows that Uniscribe
126 * treats U+0951..U+0952 all as OT_VD.
127 * TESTS:
128 * U+092E,U+0947,U+0952
129 * U+092E,U+0952,U+0947
130 * U+092E,U+0947,U+0951
131 * U+092E,U+0951,U+0947
132 * */
133 //if ($u == 0x0952) $cat = self::OT_A;
134 if (self::in_range($u, 0x0951, 0x0954))
135 $cat = self::OT_VD;
136
137 if ($u == 0x200C) $cat = self::OT_ZWNJ;
138 else if ($u == 0x200D) $cat = self::OT_ZWJ;
139 else if ($u == 0x25CC) $cat = self::OT_DOTTEDCIRCLE;
140 else if ($u == 0x0A71) $cat = self::OT_SM; /* GURMUKHI ADDAK. More like consonant medial. like 0A75. */
141
142 if ($cat == self::OT_Repha) {
143 /* There are two kinds of characters marked as Repha:
144 * - The ones that are GenCat=Mn are already positioned visually, ie. after base. (eg. Khmer)
145 * - The ones that are GenCat=Lo is encoded logically, ie. beginning of syllable. (eg. Malayalam)
146 *
147 * We recategorize the first kind to look like a Nukta and attached to the base directly.
148 */
149 if ($info['general_category'] == UCDN::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
150 $cat = self::OT_N;
151 }
152
153 /*
154 * Re-assign position.
155 */
156
157 if ((self::FLAG($cat) & (self::FLAG(self::OT_C) | self::FLAG(self::OT_CM) | self::FLAG(self::OT_Ra) | self::FLAG(self::OT_V) | self::FLAG(self::OT_NBSP) | self::FLAG(self::OT_DOTTEDCIRCLE)))) { // = CONSONANT_FLAGS like is_consonant
158 if ($scriptblock == UCDN::SCRIPT_KHMER) $pos = self::POS_BELOW_C; /* Khmer differs from Indic here. */
159 else $pos = self::POS_BASE_C; /* Will recategorize later based on font lookups. */
160
161 if (self::is_ra ($u))
162 $cat = self::OT_Ra;
163 }
164 else if ($cat == self::OT_M) {
165 $pos = self::matra_position($u, $pos);
166 }
167 else if ($cat == self::OT_SM || $cat == self::OT_VD) {
168 $pos = self::POS_SMVD;
169 }
170
171 if ($u == 0x0B01) $pos = self::POS_BEFORE_SUB; /* Oriya Bindu is BeforeSub in the spec. */
172
173 $info['indic_category'] = $cat;
174 $info['indic_position'] = $pos;
175 }
176
177 // syllable_type
178 const CONSONANT_SYLLABLE = 0;
179 const VOWEL_SYLLABLE = 1;
180 const STANDALONE_CLUSTER = 2;
181 const BROKEN_CLUSTER = 3;
182 const NON_INDIC_CLUSTER = 4;
183
184 public static function set_syllables(&$o, $s, &$broken_syllables) {
185 $ptr = 0;
186 $syllable_serial = 1;
187 $broken_syllables = false;
188
189 while($ptr < strlen($s)) {
190 $match = '';
191 $syllable_length = 1;
192 $syllable_type = self::NON_INDIC_CLUSTER ;
193 // CONSONANT_SYLLABLE Consonant syllable
194 // From OT spec:
195 if (preg_match('/^([CR]m*[N]?(H[ZJ]?|[ZJ]H))*[CR]m*[N]?[A]?(H[ZJ]?|[M]*[N]?[H]?)?[S]?[v]{0,2}/', substr($s,$ptr), $ma)) {
196 // From HarfBuzz:
197 //if (preg_match('/^r?([CR]J?(Z?[N]{0,2})?[ZJ]?H(J[N]?)?){0,4}[CR]J?(Z?[N]{0,2})?A?((([ZJ]?H(J[N]?)?)|HZ)|(HJ)?([ZJ]{0,3}M[N]?(H|JHJR)?){0,4})?(S[Z]?)?[v]{0,2}/', substr($s,$ptr), $ma)) {
198 $syllable_length = strlen($ma[0]);
199 $syllable_type = self::CONSONANT_SYLLABLE ;
200 }
201 // VOWEL_SYLLABLE Vowel-based syllable
202 // From OT spec:
203 else if (preg_match('/^(RH|r)?V[N]?([ZJ]?H[CR]m*|J[CR]m*)?([M]*[N]?[H]?)?[S]?[v]{0,2}/', substr($s,$ptr), $ma)) {
204 // From HarfBuzz:
205 //else if (preg_match('/^(RH|r)?V(Z?[N]{0,2})?(J|([ZJ]?H(J[N]?)?[CR]J?(Z?[N]{0,2})?){0,4}((([ZJ]?H(J[N]?)?)|HZ)|(HJ)?([ZJ]{0,3}M[N]?(H|JHJR)?){0,4})?(S[Z]?)?[v]{0,2})/', substr($s,$ptr), $ma)) {
206 $syllable_length = strlen($ma[0]);
207 $syllable_type = self::VOWEL_SYLLABLE ;
208 }
209
210 /* Apply only if it's a word start. */
211 // STANDALONE_CLUSTER Stand Alone syllable at start of word
212 // From OT spec:
213 else if (($ptr==0 ||
214 $o[$ptr - 1]['general_category'] < UCDN::UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER ||
215 $o[$ptr - 1]['general_category'] > UCDN::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK
216 )
217
218 && (preg_match('/^(RH|r)?[sD][N]?([ZJ]?H[CR]m*)?([M]*[N]?[H]?)?[S]?[v]{0,2}/', substr($s,$ptr), $ma))) {
219 // From HarfBuzz:
220 // && (preg_match('/^(RH|r)?[sD](Z?[N]{0,2})?(([ZJ]?H(J[N]?)?)[CR]J?(Z?[N]{0,2})?){0,4}((([ZJ]?H(J[N]?)?)|HZ)|(HJ)?([ZJ]{0,3}M[N]?(H|JHJR)?){0,4})?(S[Z]?)?[v]{0,2}/', substr($s,$ptr), $ma)) {
221 $syllable_length = strlen($ma[0]);
222 $syllable_type = self::STANDALONE_CLUSTER ;
223 }
224
225 // BROKEN_CLUSTER syllable
226 else if (preg_match('/^(RH|r)?[N]?([ZJ]?H[CR])?([M]*[N]?[H]?)?[S]?[v]{0,2}/', substr($s,$ptr), $ma)) {
227 // From HarfBuzz:
228 //else if (preg_match('/^(RH|r)?(Z?[N]{0,2})?(([ZJ]?H(J[N]?)?)[CR]J?(Z?[N]{0,2})?){0,4}((([ZJ]?H(J[N]?)?)|HZ)|(HJ)?([ZJ]{0,3}M[N]?(H|JHJR)?){0,4})(S[Z]?)?[v]{0,2}/', substr($s,$ptr), $ma)) {
229 if (strlen($ma[0])) { // May match blank
230 $syllable_length = strlen($ma[0]);
231 $syllable_type = self::BROKEN_CLUSTER ;
232 $broken_syllables = true;
233 }
234 }
235
236 for ($i = $ptr; $i < $ptr+$syllable_length; $i++) { $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; }
237 $ptr += $syllable_length ;
238 $syllable_serial++;
239 if ($syllable_serial == 16) $syllable_serial = 1;
240 }
241 }
242
243
244 public static function set_syllables_sinhala(&$o, $s, &$broken_syllables) {
245 $ptr = 0;
246 $syllable_serial = 1;
247 $broken_syllables = false;
248
249 while($ptr < strlen($s)) {
250 $match = '';
251 $syllable_length = 1;
252 $syllable_type = self::NON_INDIC_CLUSTER ;
253 // CONSONANT_SYLLABLE Consonant syllable
254 // From OT spec:
255 if (preg_match('/^([CR]HJ|[CR]JH){0,8}[CR][HM]{0,3}[S]{0,1}/', substr($s,$ptr), $ma)) {
256 $syllable_length = strlen($ma[0]);
257 $syllable_type = self::CONSONANT_SYLLABLE ;
258 }
259 // VOWEL_SYLLABLE Vowel-based syllable
260 // From OT spec:
261 else if (preg_match('/^V[S]{0,1}/', substr($s,$ptr), $ma)) {
262 $syllable_length = strlen($ma[0]);
263 $syllable_type = self::VOWEL_SYLLABLE ;
264 }
265
266 for ($i = $ptr; $i < $ptr+$syllable_length; $i++) { $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; }
267 $ptr += $syllable_length ;
268 $syllable_serial++;
269 if ($syllable_serial == 16) $syllable_serial = 1;
270 }
271 }
272
273 public static function set_syllables_khmer(&$o, $s, &$broken_syllables) {
274 $ptr = 0;
275 $syllable_serial = 1;
276 $broken_syllables = false;
277
278 while($ptr < strlen($s)) {
279 $match = '';
280 $syllable_length = 1;
281 $syllable_type = self::NON_INDIC_CLUSTER ;
282 // CONSONANT_SYLLABLE Consonant syllable
283 if (preg_match('/^r?([CR]J?((Z?F)?[N]{0,2})?[ZJ]?G(JN?)?){0,4}[CR]J?((Z?F)?[N]{0,2})?A?((([ZJ]?G(JN?)?)|GZ)|(GJ)?([ZJ]{0,3}MN?(H|JHJR)?){0,4})?(G([CR]J?((Z?F)?[N]{0,2})?|V))?(SZ?)?[v]{0,2}/', substr($s,$ptr), $ma)) {
284 $syllable_length = strlen($ma[0]);
285 $syllable_type = self::CONSONANT_SYLLABLE ;
286 }
287 // VOWEL_SYLLABLE Vowel-based syllable
288 else if (preg_match('/^(RH|r)?V((Z?F)?[N]{0,2})?(J|([ZJ]?G(JN?)?[CR]J?((Z?F)?[N]{0,2})?){0,4}((([ZJ]?G(JN?)?)|GZ)|(GJ)?([ZJ]{0,3}MN?(H|JHJR)?){0,4})?(G([CR]J?((Z?F)?[N]{0,2})?|V))?(SZ?)?[v]{0,2})/', substr($s,$ptr), $ma)) {
289 $syllable_length = strlen($ma[0]);
290 $syllable_type = self::VOWEL_SYLLABLE ;
291 }
292
293
294 // BROKEN_CLUSTER syllable
295 else if (preg_match('/^(RH|r)?((Z?F)?[N]{0,2})?(([ZJ]?G(JN?)?)[CR]J?((Z?F)?[N]{0,2})?){0,4}((([ZJ]?G(JN?)?)|GZ)|(GJ)?([ZJ]{0,3}MN?(H|JHJR)?){0,4})(G([CR]J?((Z?F)?[N]{0,2})?|V))?(SZ?)?[v]{0,2}/', substr($s,$ptr), $ma)) {
296 if (strlen($ma[0])) { // May match blank
297 $syllable_length = strlen($ma[0]);
298 $syllable_type = self::BROKEN_CLUSTER ;
299 $broken_syllables = true;
300 }
301 }
302
303 for ($i = $ptr; $i < $ptr+$syllable_length; $i++) { $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; }
304 $ptr += $syllable_length ;
305 $syllable_serial++;
306 if ($syllable_serial == 16) $syllable_serial = 1;
307 }
308 }
309
310 public static function initial_reordering(&$info, $GSUBdata, $broken_syllables, $indic_config, $scriptblock, $is_old_spec, $dottedcircle) {
311
312 self::update_consonant_positions ($info, $GSUBdata);
313
314 if ($broken_syllables && $dottedcircle) { self::insert_dotted_circles ($info, $dottedcircle); }
315
316 $count = count($info);
317 if (!$count) return;
318 $last = 0;
319 $last_syllable = $info[0]['syllable'];
320 for ($i = 1; $i < $count; $i++) {
321 if ($last_syllable != $info[$i]['syllable']) {
322 self::initial_reordering_syllable ($info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $last, $i);
323 $last = $i;
324 $last_syllable = $info[$last]['syllable'];
325 }
326 }
327 self::initial_reordering_syllable($info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $last, $count);
328 }
329
330 public static function update_consonant_positions(&$info, $GSUBdata) {
331 $count = count($info);
332 for ($i = 0; $i < $count; $i++) {
333 if ($info[$i]['indic_position'] == self::POS_BASE_C) {
334 $c = $info[$i]['uni'];
335 // If would substitute...
336 if (isset($GSUBdata['pref'][$c])) { $info[$i]['indic_position'] = self::POS_POST_C; }
337 else if (isset($GSUBdata['blwf'][$c])) { $info[$i]['indic_position'] = self::POS_BELOW_C; }
338 else if (isset($GSUBdata['pstf'][$c])) { $info[$i]['indic_position'] = self::POS_POST_C; }
339 }
340 }
341 }
342
343 public static function insert_dotted_circles(&$info, $dottedcircle) {
344 $idx = 0;
345 $last_syllable = 0;
346 while ($idx < count($info)) {
347 $syllable = $info[$idx]['syllable'];
348 $syllable_type = ($syllable & 0x0F);
349 if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) {
350 $last_syllable = $syllable;
351
352 $dottedcircle[0]['syllable'] = $info[$idx]['syllable'];
353
354 /* Insert dottedcircle after possible Repha. */
355 while ($idx < count($info) && $last_syllable == $info[$idx]['syllable'] && $info[$idx]['indic_category'] == self::OT_Repha)
356 $idx++;
357 array_splice($info, $idx, 0, $dottedcircle);
358 }
359 else
360 $idx++;
361 }
362 // I am not sue how this code below got in here, since $idx should now be > count($info) and thus invalid.
363 // In case I am missing something(!) I'll leave a warning here for now:
364 if (isset($info[$idx])) { die("This shouldn't happen (in otl.php)"); exit; }
365 // In case of final bloken cluster...
366 //$syllable = $info[$idx]['syllable'];
367 //$syllable_type = ($syllable & 0x0F);
368 //if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) {
369 // $dottedcircle[0]['syllable'] = $info[$idx]['syllable'];
370 // array_splice($info, $idx, 0, $dottedcircle);
371 //}
372 }
373
374
375
376 /* Rules from:
377 * https://www.microsoft.com/typography/otfntdev/devanot/shaping.aspx */
378
379 public static function initial_reordering_syllable (&$info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $start, $end) {
380 /* vowel_syllable: We made the vowels look like consonants. So uses the consonant logic! */
381 /* broken_cluster: We already inserted dotted-circles, so just call the standalone_cluster. */
382 /* standalone_cluster: We treat NBSP/dotted-circle as if they are consonants, so we should just chain. */
383
384 $syllable_type = ($info[$start]['syllable'] & 0x0F);
385 if ($syllable_type==self::NON_INDIC_CLUSTER ) { return; }
386 if ($syllable_type==self::BROKEN_CLUSTER || $syllable_type==self::STANDALONE_CLUSTER ) {
387 //if ($uniscribe_bug_compatible) {
388 /* For dotted-circle, this is what Uniscribe does:
389 * If dotted-circle is the last glyph, it just does nothing.
390 * i.e. It doesn't form Reph. */
391 if ($info[$end - 1]['indic_category'] == self::OT_DOTTEDCIRCLE) {
392 return;
393 }
394 }
395
396 /* 1. Find base consonant:
397 *
398 * The shaping engine finds the base consonant of the syllable, using the
399 * following algorithm: starting from the end of the syllable, move backwards
400 * until a consonant is found that does not have a below-base or post-base
401 * form (post-base forms have to follow below-base forms), or that is not a
402 * pre-base reordering Ra, or arrive at the first consonant. The consonant
403 * stopped at will be the base.
404 *
405 * o If the syllable starts with Ra + Halant (in a script that has Reph)
406 * and has more than one consonant, Ra is excluded from candidates for
407 * base consonants.
408 */
409
410 $base = $end;
411 $has_reph = false;
412 $limit = $start;
413
414 if ($scriptblock != UCDN::SCRIPT_KHMER) {
415 /* -> If the syllable starts with Ra + Halant (in a script that has Reph)
416 * and has more than one consonant, Ra is excluded from candidates for
417 * base consonants. */
418 if (count($GSUBdata['rphf']) /* ?? $indic_plan->mask_array[RPHF] */ && $start + 3 <= $end &&
419 (
420 ($indic_config[4] == self::REPH_MODE_IMPLICIT && !self::is_joiner($info[$start + 2])) ||
421 ($indic_config[4] == self::REPH_MODE_EXPLICIT && $info[$start + 2]['indic_category'] == self::OT_ZWJ)
422 )) {
423 /* See if it matches the 'rphf' feature. */
424 //$glyphs = array($info[$start]['uni'], $info[$start + 1]['uni']);
425 //if ($indic_plan->rphf->would_substitute ($glyphs, count($glyphs), true, face)) {
426 if (isset($GSUBdata['rphf'][$info[$start]['uni']]) && self::is_halant_or_coeng($info[$start + 1]) ) {
427 $limit += 2;
428 while ($limit < $end && self::is_joiner($info[$limit]))
429 $limit++;
430 $base = $start;
431 $has_reph = true;
432 }
433 }
434 else if ($indic_config[4] == self::REPH_MODE_LOG_REPHA && $info[$start]['indic_category'] == self::OT_Repha) {
435 $limit += 1;
436 while ($limit < $end && self::is_joiner($info[$limit]))
437 $limit++;
438 $base = $start;
439 $has_reph = true;
440 }
441 }
442
443 switch ($indic_config[2]) { // base_pos
444 case self::BASE_POS_LAST:
445 /* -> starting from the end of the syllable, move backwards */
446 $i = $end;
447 $seen_below = false;
448 do {
449 $i--;
450 /* -> until a consonant is found */
451 if (self::is_consonant($info[$i])) {
452 /* -> that does not have a below-base or post-base form
453 * (post-base forms have to follow below-base forms), */
454 if ($info[$i]['indic_position'] != self::POS_BELOW_C && ($info[$i]['indic_position'] != self::POS_POST_C || $seen_below)) {
455 $base = $i;
456 break;
457 }
458 if ($info[$i]['indic_position'] == self::POS_BELOW_C)
459 $seen_below = true;
460
461 /* -> or that is not a pre-base reordering Ra,
462 *
463 * IMPLEMENTATION NOTES:
464 *
465 * Our pre-base reordering Ra's are marked POS_POST_C, so will be skipped
466 * by the logic above already.
467 */
468
469 /* -> or arrive at the first consonant. The consonant stopped at will
470 * be the base. */
471 $base = $i;
472 }
473 else {
474 /* A ZWJ after a Halant stops the base search, and requests an explicit
475 * half form.
476 * [A ZWJ before a Halant, requests a subjoined form instead, and hence
477 * search continues. This is particularly important for Bengali
478 * sequence Ra,H,Ya that should form Ya-Phalaa by subjoining Ya] */
479 if ($start < $i && $info[$i]['indic_category'] == self::OT_ZWJ && $info[$i - 1]['indic_category'] == self::OT_H) {
480 if (!defined("OMIT_INDIC_FIX_1") || OMIT_INDIC_FIX_1!=1) { $base = $i; } // INDIC_FIX_1
481 break;
482 }
483 // ZKI8
484 if ($start < $i && $info[$i]['indic_category'] == self::OT_ZWNJ) {
485 break;
486 }
487 }
488 } while ($i > $limit);
489 break;
490
491 case self::BASE_POS_FIRST:
492 /* In scripts without half forms (eg. Khmer), the first consonant is always the base. */
493
494 if (!$has_reph)
495 $base = $limit;
496
497 /* Find the last base consonant that is not blocked by ZWJ. If there is
498 * a ZWJ right before a base consonant, that would request a subjoined form. */
499 for ($i = $limit; $i < $end; $i++) {
500 if (self::is_consonant($info[$i]) && $info[$i]['indic_position'] == self::POS_BASE_C) {
501 if ($limit < $i && $info[$i - 1]['indic_category'] == self::OT_ZWJ)
502 break;
503 else
504 $base = $i;
505 }
506 }
507
508 /* Mark all subsequent consonants as below. */
509 for ($i = $base + 1; $i < $end; $i++) {
510 if (self::is_consonant ($info[$i]) && $info[$i]['indic_position'] == self::POS_BASE_C)
511 $info[$i]['indic_position'] = self::POS_BELOW_C;
512 }
513 break;
514 //default:
515 //assert (false);
516 /* fallthrough */
517 }
518
519 /* -> If the syllable starts with Ra + Halant (in a script that has Reph)
520 * and has more than one consonant, Ra is excluded from candidates for
521 * base consonants.
522 *
523 * Only do this for unforced Reph. (ie. not for Ra,H,ZWJ. */
524 if ($scriptblock != UCDN::SCRIPT_KHMER) {
525 if ($has_reph && $base == $start && $limit - $base <= 2) {
526 /* Have no other consonant, so Reph is not formed and Ra becomes base. */
527 $has_reph = false;
528 }
529 }
530
531 /* 2. Decompose and reorder Matras:
532 *
533 * Each matra and any syllable modifier sign in the cluster are moved to the
534 * appropriate position relative to the consonant(s) in the cluster. The
535 * shaping engine decomposes two- or three-part matras into their constituent
536 * parts before any repositioning. Matra characters are classified by which
537 * consonant in a conjunct they have affinity for and are reordered to the
538 * following positions:
539 *
540 * o Before first half form in the syllable
541 * o After subjoined consonants
542 * o After post-form consonant
543 * o After main consonant (for above marks)
544 *
545 * IMPLEMENTATION NOTES:
546 *
547 * The normalize() routine has already decomposed matras for us, so we don't
548 * need to worry about that.
549 */
550
551
552 /* 3. Reorder marks to canonical order:
553 *
554 * Adjacent nukta and halant or nukta and vedic sign are always repositioned
555 * if necessary, so that the nukta is first.
556 *
557 * IMPLEMENTATION NOTES:
558 *
559 * Use the combining Class from Unicode categories? to bubble_sort.
560 */
561
562 /* Reorder characters */
563
564 for ($i = $start; $i < $base; $i++)
565 $info[$i]['indic_position'] = min(self::POS_PRE_C, $info[$i]['indic_position']);
566
567 if ($base < $end)
568 $info[$base]['indic_position'] = self::POS_BASE_C;
569
570 /* Mark final consonants. A final consonant is one appearing after a matra,
571 * ? only in Khmer. */
572 for ($i = $base + 1; $i < $end; $i++)
573 if ($info[$i]['indic_category'] == self::OT_M) {
574 for ($j = $i + 1; $j < $end; $j++)
575 if (self::is_consonant ($info[$j])) {
576 $info[$j]['indic_position'] = self::POS_FINAL_C;
577 break;
578 }
579 break;
580 }
581
582 /* Handle beginning Ra */
583 if ($scriptblock != UCDN::SCRIPT_KHMER) {
584 if ($has_reph)
585 $info[$start]['indic_position'] = self::POS_RA_TO_BECOME_REPH;
586 }
587
588
589 /* For old-style Indic script tags, move the first post-base Halant after
590 * last consonant. Only do this if there is *not* a Halant after last
591 * consonant. Otherwise it becomes messy. */
592 if ($is_old_spec) {
593 for ($i = $base + 1; $i < $end; $i++) {
594 if ($info[$i]['indic_category'] == self::OT_H) {
595 for ($j = $end - 1; $j > $i; $j--) {
596 if (self::is_consonant($info[$j]) || $info[$j]['indic_category'] == self::OT_H) { break; }
597 }
598 if ($info[$j]['indic_category'] != self::OT_H && $j > $i) {
599 /* Move Halant to after last consonant. */
600 self::_move_info_pos($info, $i, $j+1);
601 }
602 break;
603 }
604 }
605 }
606
607 /* Attach misc marks to previous char to move with them. */
608 $last_pos = self::POS_START;
609 for ($i = $start; $i < $end; $i++) {
610 if ((self::FLAG($info[$i]['indic_category']) & (self::FLAG(self::OT_ZWJ)| self::FLAG(self::OT_ZWNJ) | self::FLAG(self::OT_N) | self::FLAG (self::OT_RS) | self::FLAG (self::OT_H) | self::FLAG (self::OT_Coeng) ))) {
611 $info[$i]['indic_position'] = $last_pos;
612 if ($info[$i]['indic_category'] == self::OT_H && $info[$i]['indic_position'] == self::POS_PRE_M) {
613 /*
614 * Uniscribe doesn't move the Halant with Left Matra.
615 * TEST: U+092B,U+093F,U+094DE
616 * We follow. This is important for the Sinhala
617 * U+0DDA split matra since it decomposes to U+0DD9,U+0DCA
618 * where U+0DD9 is a left matra and U+0DCA is the virama.
619 * We don't want to move the virama with the left matra.
620 * TEST: U+0D9A,U+0DDA
621 */
622 for ($j = $i; $j > $start; $j--)
623 if ($info[$j - 1]['indic_position'] != self::POS_PRE_M) {
624 $info[$i]['indic_position'] = $info[$j - 1]['indic_position'];
625 break;
626 }
627 }
628 }
629 else if ($info[$i]['indic_position'] != self::POS_SMVD) {
630 $last_pos = $info[$i]['indic_position'];
631 }
632 }
633
634 /* Re-attach ZWJ, ZWNJ, and halant to next char, for after-base consonants. */
635 $last_halant = $end;
636 for ($i = $base + 1; $i < $end; $i++) {
637 if (self::is_halant_or_coeng($info[$i]))
638 $last_halant = $i;
639 else if (self::is_consonant($info[$i])) {
640 for ($j = $last_halant; $j < $i; $j++)
641 if ($info[$j]['indic_position'] != self::POS_SMVD)
642 $info[$j]['indic_position'] = $info[$i]['indic_position'];
643 }
644 }
645
646
647 if ($scriptblock == UCDN::SCRIPT_KHMER) {
648 /* KHMER_FIX_2 */
649 /* Move Coeng+RO (Halant,Ra) sequence before base consonant. */
650 for ($i = $base + 1; $i < $end; $i++) {
651 if (self::is_halant_or_coeng($info[$i]) && self::is_ra($info[$i + 1]['uni'])) {
652 $info[$i]['indic_position'] = self::POS_PRE_C;
653 $info[$i + 1]['indic_position'] = self::POS_PRE_C;
654 break;
655 }
656 }
657 }
658
659
660 /*
661 if (!defined("OMIT_INDIC_FIX_2") || OMIT_INDIC_FIX_2 != 1) {
662 // INDIC_FIX_2
663 $ZWNJ_found = false;
664 $POST_ZWNJ_c_found = false;
665 for ($i = $base + 1; $i < $end; $i++) {
666 if ($info[$i]['indic_category'] == self::OT_ZWNJ) { $ZWNJ_found = true; }
667 else if ($ZWNJ_found && $info[$i]['indic_category'] == self::OT_C) { $POST_ZWNJ_c_found = true; }
668 else if ($POST_ZWNJ_c_found && $info[$i]['indic_position'] == self::POS_BEFORE_SUB) { $info[$i]['indic_position'] = self::POS_AFTER_SUB; }
669 }
670 }
671 */
672
673 /* Setup masks now */
674 for ($i = $start; $i < $end; $i++) {
675 $info[$i]['mask'] = 0;
676 }
677
678
679 if ($scriptblock == UCDN::SCRIPT_KHMER) {
680 /* Find a Coeng+RO (Halant,Ra) sequence and mark it for pre-base processing. */
681 $mask = self::FLAG(self::PREF);
682 for ($i = $base; $i < $end-1; $i++) { /* KHMER_FIX_1 From $start (not base) */
683 if (self::is_halant_or_coeng($info[$i]) && self::is_ra($info[$i + 1]['uni']) ) {
684
685 $info[$i]['mask'] |= self::FLAG(self::PREF);
686 $info[$i + 1]['mask'] |= self::FLAG(self::PREF);
687
688 /* Mark the subsequent stuff with 'cfar'. Used in Khmer.
689 * Read the feature spec.
690 * This allows distinguishing the following cases with MS Khmer fonts:
691 * U+1784,U+17D2,U+179A,U+17D2,U+1782 [C+Coeng+RO+Coeng+C] => Should activate CFAR
692 * U+1784,U+17D2,U+1782,U+17D2,U+179A [C+Coeng+C+Coeng+RO] => Should NOT activate CFAR
693 */
694 for ($j=($i+2); $j < $end; $j++)
695 $info[$j]['mask'] |= self::FLAG(self::CFAR);
696
697 break;
698 }
699 }
700 }
701
702
703
704 /* Sit tight, rock 'n roll! */
705 self::bubble_sort ($info, $start, $end - $start);
706
707 /* Find base again */
708 $base = $end;
709 for ($i = $start; $i < $end; $i++) {
710 if ($info[$i]['indic_position'] == self::POS_BASE_C) {
711 $base = $i;
712 break;
713 }
714 }
715
716 if ($scriptblock != UCDN::SCRIPT_KHMER) {
717 /* Reph */
718 for ($i = $start; $i < $end; $i++) {
719 if ($info[$i]['indic_position'] == self::POS_RA_TO_BECOME_REPH) {
720 $info[$i]['mask'] |= self::FLAG(self::RPHF);
721 }
722 }
723
724 /* Pre-base */
725 $mask = self::FLAG(self::HALF);
726 for ($i = $start; $i < $base; $i++) {
727 $info[$i]['mask'] |= $mask;
728 }
729 }
730
731 /* Post-base */
732 $mask = (self::FLAG(self::BLWF) | self::FLAG(self::ABVF) | self::FLAG(self::PSTF));
733 for ($i = $base + 1; $i < $end; $i++) {
734 $info[$i]['mask'] |= $mask;
735 }
736
737
738 if ($scriptblock != UCDN::SCRIPT_KHMER) {
739 if (!defined("OMIT_INDIC_FIX_3") || OMIT_INDIC_FIX_3 != 1) {
740 /* INDIC_FIX_3 */
741 /* Find a (pre-base) Consonant, Halant,Ra sequence and mark Halant|Ra for below-base BLWF processing. */
742 // TEST CASE &#x995;&#x9cd;&#x9b0;&#x9cd;&#x995; in FreeSans versus Vrinda
743 if (($base - $start) >= 3) {
744 for ($i = $start; $i < ($base-2); $i++) {
745 if (self::is_consonant($info[$i])) {
746 if (self::is_halant_or_coeng($info[$i + 1]) && self::is_ra($info[$i + 2]['uni'])) {
747 // If would substitute Halant+Ra...BLWF
748 if (isset($GSUBdata['blwf'][$info[$i+2]['uni']])) {
749 $info[$i + 1]['mask'] |= self::FLAG(self::BLWF);
750 $info[$i + 2]['mask'] |= self::FLAG(self::BLWF);
751 }
752 /* If would not substitute as blwf, mark Ra+Halant for RPHF using following Halant (if present) */
753 else if (self::is_halant_or_coeng($info[$i + 3])) {
754 $info[$i + 2]['mask'] |= self::FLAG(self::RPHF);
755 $info[$i + 3]['mask'] |= self::FLAG(self::RPHF);
756 }
757 break;
758 }
759 }
760 }
761 }
762 }
763 }
764
765
766
767 if ($is_old_spec && $scriptblock == UCDN::SCRIPT_DEVANAGARI) {
768 /* Old-spec eye-lash Ra needs special handling. From the spec:
769 * "The feature 'below-base form' is applied to consonants
770 * having below-base forms and following the base consonant.
771 * The exception is vattu, which may appear below half forms
772 * as well as below the base glyph. The feature 'below-base
773 * form' will be applied to all such occurrences of Ra as well."
774 *
775 * Test case: U+0924,U+094D,U+0930,U+094d,U+0915
776 * with Sanskrit 2003 font.
777 *
778 * However, note that Ra,Halant,ZWJ is the correct way to
779 * request eyelash form of Ra, so we wouldbn't inhibit it
780 * in that sequence.
781 *
782 * Test case: U+0924,U+094D,U+0930,U+094d,U+200D,U+0915
783 */
784 for ($i = $start; ($i + 1) < $base; $i++) {
785 if ($info[$i]['indic_category'] == self::OT_Ra && $info[$i+1]['indic_category'] == self::OT_H &&
786 ($i + 2 == $base || $info[$i+2]['indic_category'] != self::OT_ZWJ)) {
787 $info[$i]['mask'] |= self::FLAG(self::BLWF);
788 $info[$i+1]['mask'] |= self::FLAG(self::BLWF);
789 }
790 }
791 }
792
793 if ($scriptblock != UCDN::SCRIPT_KHMER) {
794 if (count($GSUBdata['pref']) && $base + 2 < $end) {
795 /* Find a Halant,Ra sequence and mark it for pre-base processing. */
796 for ($i = $base + 1; $i + 1 < $end; $i++) {
797 // If old_spec find Ra-Halant...
798 if ((isset($GSUBdata['pref'][$info[$i + 1]['uni']]) && self::is_halant_or_coeng($info[$i]) && self::is_ra($info[$i + 1]['uni']) ) ||
799 ($is_old_spec && isset($GSUBdata['pref'][$info[$i]['uni']]) && self::is_halant_or_coeng($info[$i + 1]) && self::is_ra($info[$i]['uni']) )
800 ) {
801 $info[$i++]['mask'] |= self::FLAG(self::PREF);
802 $info[$i++]['mask'] |= self::FLAG(self::PREF);
803 break;
804 }
805 }
806 }
807 }
808
809
810 /* Apply ZWJ/ZWNJ effects */
811 for ($i = $start + 1; $i < $end; $i++) {
812 if (self::is_joiner ($info[$i])) {
813 $non_joiner = ($info[$i]['indic_category'] == self::OT_ZWNJ);
814 $j = $i;
815 while ($j > $start) {
816 if (defined("OMIT_INDIC_FIX_4") && OMIT_INDIC_FIX_4 == 1) {
817 // INDIC_FIX_4 = do nothing - carry on //
818 // ZWNJ should block H C from forming blwf post-base - need to unmask backwards beyond first consonant arrived at //
819 if (!self::is_consonant($info[$j])) { break; }
820 }
821 $j--;
822
823 /* ZWJ/ZWNJ should disable CJCT. They do that by simply
824 * being there, since we don't skip them for the CJCT
825 * feature (ie. F_MANUAL_ZWJ) */
826
827 /* A ZWNJ disables HALF. */
828 if ($non_joiner) {
829 $info[$j]['mask'] &= ~(self::FLAG(self::HALF) | self::FLAG(self::BLWF));
830 }
831
832 }
833 }
834 }
835 }
836
837 public static function final_reordering (&$info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec) {
838 $count = count($info);
839 if (!$count) return;
840 $last = 0;
841 $last_syllable = $info[0]['syllable'];
842 for ($i = 1; $i < $count; $i++) {
843 if ($last_syllable != $info[$i]['syllable']) {
844 self::final_reordering_syllable ($info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $last, $i);
845 $last = $i;
846 $last_syllable = $info[$last]['syllable'];
847 }
848 }
849 self::final_reordering_syllable ($info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $last, $count);
850
851 }
852
853 public static function final_reordering_syllable (&$info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $start, $end) {
854
855 /* 4. Final reordering:
856 *
857 * After the localized forms and basic shaping forms GSUB features have been
858 * applied (see below), the shaping engine performs some final glyph
859 * reordering before applying all the remaining font features to the entire
860 * cluster.
861 */
862
863 /* Find base again */
864 for ($base = $start; $base < $end; $base++)
865 if ($info[$base]['indic_position'] >= self::POS_BASE_C) {
866 if ($start < $base && $info[$base]['indic_position'] > self::POS_BASE_C)
867 $base--;
868 break;
869 }
870 if ($base == $end && $start < $base && $info[$base - 1]['indic_category'] != self::OT_ZWJ)
871 $base--;
872 while ($start < $base && isset($info[$base]) && ($info[$base]['indic_category'] == self::OT_H || $info[$base]['indic_category'] == self::OT_N))
873 $base--;
874
875
876 /* o Reorder matras:
877 *
878 * If a pre-base matra character had been reordered before applying basic
879 * features, the glyph can be moved closer to the main consonant based on
880 * whether half-forms had been formed. Actual position for the matra is
881 * defined as "after last standalone halant glyph, after initial matra
882 * position and before the main consonant". If ZWJ or ZWNJ follow this
883 * halant, position is moved after it.
884 */
885
886
887 if ($start + 1 < $end && $start < $base) { /* Otherwise there can't be any pre-base matra characters. */
888 /* If we lost track of base, alas, position before last thingy. */
889 $new_pos = ($base == $end) ? $base - 2 : $base - 1;
890
891 /* Malayalam / Tamil do not have "half" forms or explicit virama forms.
892 * The glyphs formed by 'half' are Chillus or ligated explicit viramas.
893 * We want to position matra after them.
894 */
895 if ($scriptblock != UCDN::SCRIPT_MALAYALAM && $scriptblock != UCDN::SCRIPT_TAMIL) {
896 while ($new_pos > $start && !(self::is_one_of ($info[$new_pos], (self::FLAG(self::OT_M) | self::FLAG(self::OT_H) | self::FLAG(self::OT_Coeng)))))
897 $new_pos--;
898
899 /* If we found no Halant we are done.
900 * Otherwise only proceed if the Halant does
901 * not belong to the Matra itself! */
902 if (self::is_halant_or_coeng($info[$new_pos]) && $info[$new_pos]['indic_position'] != self::POS_PRE_M) {
903 /* -> If ZWJ or ZWNJ follow this halant, position is moved after it. */
904 if ($new_pos + 1 < $end && self::is_joiner($info[$new_pos + 1]))
905 $new_pos++;
906 }
907 else
908 $new_pos = $start; /* No move. */
909 }
910
911 if ($start < $new_pos && $info[$new_pos]['indic_position'] != self::POS_PRE_M) {
912 /* Now go see if there's actually any matras... */
913 for ($i = $new_pos; $i > $start; $i--)
914 if ($info[$i - 1]['indic_position'] == self::POS_PRE_M) {
915 $old_pos = $i - 1;
916 //memmove (&info[$old_pos], &info[$old_pos + 1], ($new_pos - $old_pos) * sizeof ($info[0]));
917 self::_move_info_pos($info, $old_pos, $new_pos+1);
918
919 if ($old_pos < $base && $base <= $new_pos) /* Shouldn't actually happen. */
920 $base--;
921 $new_pos--;
922 }
923 }
924 }
925
926
927 /* o Reorder reph:
928 *
929 * Reph's original position is always at the beginning of the syllable,
930 * (i.e. it is not reordered at the character reordering stage). However,
931 * it will be reordered according to the basic-forms shaping results.
932 * Possible positions for reph, depending on the script, are; after main,
933 * before post-base consonant forms, and after post-base consonant forms.
934 */
935
936 /* If there's anything after the Ra that has the REPH pos, it ought to be halant.
937 * Which means that the font has failed to ligate the Reph. In which case, we
938 * shouldn't move. */
939 if ($start + 1 < $end &&
940 $info[$start]['indic_position'] == self::POS_RA_TO_BECOME_REPH && $info[$start + 1]['indic_position'] != self::POS_RA_TO_BECOME_REPH) {
941 $reph_pos = $indic_config[3];
942 $skip_to_reph_step_5 = false;
943 $skip_to_reph_move = false;
944
945 /* 1. If reph should be positioned after post-base consonant forms,
946 * proceed to step 5.
947 */
948 if ($reph_pos == self::REPH_POS_AFTER_POST) {
949 $skip_to_reph_step_5 = true;
950 }
951
952 /* 2. If the reph repositioning class is not after post-base: target
953 * position is after the first explicit halant glyph between the
954 * first post-reph consonant and last main consonant. If ZWJ or ZWNJ
955 * are following this halant, position is moved after it. If such
956 * position is found, this is the target position. Otherwise,
957 * proceed to the next step.
958 *
959 * Note: in old-implementation fonts, where classifications were
960 * fixed in shaping engine, there was no case where reph position
961 * will be found on this step.
962 */
963
964 if (!$skip_to_reph_step_5) {
965
966 $new_reph_pos = $start + 1;
967
968 while ($new_reph_pos < $base && !self::is_halant_or_coeng($info[$new_reph_pos]))
969 $new_reph_pos++;
970
971 if ($new_reph_pos < $base && self::is_halant_or_coeng($info[$new_reph_pos])) {
972 /* ->If ZWJ or ZWNJ are following this halant, position is moved after it. */
973 if ($new_reph_pos + 1 < $base && self::is_joiner ($info[$new_reph_pos + 1]))
974 $new_reph_pos++;
975 $skip_to_reph_move =true;
976 }
977 }
978
979 /* 3. If reph should be repositioned after the main consonant: find the
980 * first consonant not ligated with main, or find the first
981 * consonant that is not a potential pre-base reordering Ra.
982 */
983 if ($reph_pos == self::REPH_POS_AFTER_MAIN && !$skip_to_reph_move && !$skip_to_reph_step_5) {
984 $new_reph_pos = $base;
985 /* XXX Skip potential pre-base reordering Ra. */
986 while ($new_reph_pos + 1 < $end && $info[$new_reph_pos + 1]['indic_position'] <= self::POS_AFTER_MAIN)
987 $new_reph_pos++;
988 if ($new_reph_pos < $end)
989 $skip_to_reph_move =true;
990 }
991
992 /* 4. If reph should be positioned before post-base consonant, find
993 * first post-base classified consonant not ligated with main. If no
994 * consonant is found, the target position should be before the
995 * first matra, syllable modifier sign or vedic sign.
996 */
997 /* This is our take on what step 4 is trying to say (and failing, BADLY). */
998 if ($reph_pos == self::REPH_POS_AFTER_SUB && !$skip_to_reph_move && !$skip_to_reph_step_5) {
999 $new_reph_pos = $base;
1000 while ($new_reph_pos < $end && isset($info[$new_reph_pos + 1]['indic_position']) &&
1001 !( self::FLAG($info[$new_reph_pos + 1]['indic_position']) & (self::FLAG(self::POS_POST_C) | self::FLAG(self::POS_AFTER_POST) | self::FLAG(self::POS_SMVD)))) {
1002 $new_reph_pos++;
1003 }
1004 if ($new_reph_pos < $end) { $skip_to_reph_move =true; }
1005 }
1006
1007 /* 5. If no consonant is found in steps 3 or 4, move reph to a position
1008 * immediately before the first post-base matra, syllable modifier
1009 * sign or vedic sign that has a reordering class after the intended
1010 * reph position. For example, if the reordering position for reph
1011 * is post-main, it will skip above-base matras that also have a
1012 * post-main position.
1013 */
1014 if (!$skip_to_reph_move) {
1015 /* Copied from step 2. */
1016 $new_reph_pos = $start + 1;
1017 while ($new_reph_pos < $base && !self::is_halant_or_coeng($info[$new_reph_pos]))
1018 $new_reph_pos++;
1019
1020 if ($new_reph_pos < $base && self::is_halant_or_coeng($info[$new_reph_pos])) {
1021 /* ->If ZWJ or ZWNJ are following this halant, position is moved after it. */
1022 if ($new_reph_pos + 1 < $base && self::is_joiner($info[$new_reph_pos + 1]))
1023 $new_reph_pos++;
1024 $skip_to_reph_move =true;
1025 }
1026 }
1027
1028
1029 /* 6. Otherwise, reorder reph to the end of the syllable.
1030 */
1031 if (!$skip_to_reph_move) {
1032 $new_reph_pos = $end - 1;
1033 while ($new_reph_pos > $start && $info[$new_reph_pos]['indic_position'] == self::POS_SMVD)
1034 $new_reph_pos--;
1035
1036 /*
1037 * If the Reph is to be ending up after a Matra,Halant sequence,
1038 * position it before that Halant so it can interact with the Matra.
1039 * However, if it's a plain Consonant,Halant we shouldn't do that.
1040 * Uniscribe doesn't do this.
1041 * TEST: U+0930,U+094D,U+0915,U+094B,U+094D
1042 */
1043 //if (!$hb_options.uniscribe_bug_compatible && self::is_halant_or_coeng($info[$new_reph_pos])) {
1044 if (self::is_halant_or_coeng($info[$new_reph_pos])) {
1045 for ($i = $base + 1; $i < $new_reph_pos; $i++)
1046 if ($info[$i]['indic_category'] == self::OT_M) {
1047 /* Ok, got it. */
1048 $new_reph_pos--;
1049 }
1050 }
1051 }
1052
1053
1054 /* Move */
1055 self::_move_info_pos($info, $start, $new_reph_pos+1);
1056
1057 if ($start < $base && $base <= $new_reph_pos) {
1058 $base--;
1059 }
1060 }
1061
1062
1063 /* o Reorder pre-base reordering consonants:
1064 *
1065 * If a pre-base reordering consonant is found, reorder it according to
1066 * the following rules:
1067 */
1068
1069
1070 if (count($GSUBdata['pref']) && $base + 1 < $end) { /* Otherwise there can't be any pre-base reordering Ra. */
1071 for ($i = $base + 1; $i < $end; $i++) {
1072 if ($info[$i]['mask'] & self::FLAG(self::PREF)) {
1073 /* 1. Only reorder a glyph produced by substitution during application
1074 * of the <pref> feature. (Note that a font may shape a Ra consonant with
1075 * the feature generally but block it in certain contexts.)
1076 */
1077 // ??? Need to TEST if actual substitution has occurred
1078 if ($i + 1 == $end || ($info[$i + 1]['mask'] & self::FLAG(self::PREF)) == 0) {
1079 /*
1080 * 2. Try to find a target position the same way as for pre-base matra.
1081 * If it is found, reorder pre-base consonant glyph.
1082 *
1083 * 3. If position is not found, reorder immediately before main
1084 * consonant.
1085 */
1086 $new_pos = $base;
1087 /* Malayalam / Tamil do not have "half" forms or explicit virama forms.
1088 * The glyphs formed by 'half' are Chillus or ligated explicit viramas.
1089 * We want to position matra after them.
1090 */
1091 if ($scriptblock != UCDN::SCRIPT_MALAYALAM && $scriptblock != UCDN::SCRIPT_TAMIL) {
1092 while ($new_pos > $start &&
1093 !(self::is_one_of($info[$new_pos - 1], self::FLAG(self::OT_M) | self::FLAG(self::OT_H) | self::FLAG(self::OT_Coeng))))
1094 $new_pos--;
1095
1096 /* In Khmer coeng model, a V,Ra can go *after* matras. If it goes after a
1097 * split matra, it should be reordered to *before* the left part of such matra. */
1098 if ($new_pos > $start && $info[$new_pos - 1]['indic_category'] == self::OT_M) {
1099 $old_pos = i;
1100 for ($i = $base + 1; $i < $old_pos; $i++)
1101 if ($info[$i]['indic_category'] == self::OT_M) {
1102 $new_pos--;
1103 break;
1104 }
1105 }
1106 }
1107
1108 if ($new_pos > $start && self::is_halant_or_coeng($info[$new_pos - 1])) {
1109 /* -> If ZWJ or ZWNJ follow this halant, position is moved after it. */
1110 if ($new_pos < $end && self::is_joiner($info[$new_pos]))
1111 $new_pos++;
1112 }
1113
1114 $old_pos = $i;
1115 self::_move_info_pos($info, $old_pos, $new_pos);
1116
1117 if ($new_pos <= $base && $base < $old_pos)
1118 $base++;
1119 }
1120
1121 break;
1122 }
1123 }
1124 }
1125
1126
1127 /* Apply 'init' to the Left Matra if it's a word start. */
1128 if ($info[$start]['indic_position'] == self::POS_PRE_M &&
1129 ($start==0 ||
1130 ($info[$start - 1]['general_category'] < UCDN::UNICODE_GENERAL_CATEGORY_FORMAT || $info[$start - 1]['general_category'] > UCDN::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
1131 )) {
1132 $info[$start]['mask'] |= self::FLAG(self::INIT);
1133 }
1134
1135
1136 /*
1137 * Finish off and go home!
1138 */
1139
1140 }
1141
1142 function _move_info_pos(&$info, $from, $to) {
1143 $t = array();
1144 $t[0] = $info[$from];
1145 if ($from > $to) {
1146 array_splice($info, $from, 1);
1147 array_splice($info, $to, 0, $t);
1148 }
1149 else {
1150 array_splice($info, $to, 0, $t);
1151 array_splice($info, $from, 1);
1152 }
1153 }
1154
1155
1156 public static $ra_chars = array(
1157 0x0930 => 1, /* Devanagari */
1158 0x09B0 => 1, /* Bengali */
1159 0x09F0 => 1, /* Bengali (Assamese) */
1160 0x0A30 => 1, /* Gurmukhi */ /* No Reph */
1161 0x0AB0 => 1, /* Gujarati */
1162 0x0B30 => 1, /* Oriya */
1163 0x0BB0 => 1, /* Tamil */ /* No Reph */
1164 0x0C30 => 1, /* Telugu */ /* Reph formed only with ZWJ */
1165 0x0CB0 => 1, /* Kannada */
1166 0x0D30 => 1, /* Malayalam */ /* No Reph, Logical Repha */
1167
1168 0x0DBB => 1, /* Sinhala */ /* Reph formed only with ZWJ */
1169 0x179A => 1, /* Khmer */ /* No Reph, Visual Repha */
1170 );
1171
1172 public static function is_ra ($u) {
1173 if (isset(self::$ra_chars[$u])) return true;
1174 return false;
1175 }
1176
1177 public static function is_one_of ($info, $flags) {
1178 if (isset($info['is_ligature']) && $info['is_ligature']) return false; /* If it ligated, all bets are off. */
1179 return !!(self::FLAG($info['indic_category']) & $flags);
1180 }
1181
1182 public static function is_joiner($info) {
1183 return self::is_one_of ($info, (self::FLAG(self::OT_ZWJ) | self::FLAG(self::OT_ZWNJ)));
1184 }
1185
1186
1187 /* Vowels and placeholders treated as if they were consonants. */
1188 public static function is_consonant($info) {
1189 return self::is_one_of($info, (self::FLAG(self::OT_C) | self::FLAG(self::OT_CM) | self::FLAG(self::OT_Ra) | self::FLAG(self::OT_V) | self::FLAG(self::OT_NBSP) | self::FLAG(self::OT_DOTTEDCIRCLE)));
1190 }
1191
1192
1193 public static function is_halant_or_coeng($info) {
1194 return self::is_one_of($info, (self::FLAG(self::OT_H) | self::FLAG(self::OT_Coeng)));
1195 }
1196
1197
1198
1199 // From hb-private.hh
1200 public static function in_range ($u, $lo, $hi) {
1201 if ( (($lo^$hi) & $lo) == 0 && (($lo^$hi) & $hi) == ($lo^$hi) && (($lo^$hi) & (($lo^$hi) + 1)) == 0 )
1202 return ($u & ~($lo^$hi)) == $lo;
1203 else
1204 return $lo <= $u && $u <= $hi;
1205 }
1206 // From hb-private.hh
1207 public static function FLAG($x) { return (1<<($x)); }
1208
1209
1210 // BELOW from hb-ot-shape-complex-indic.cc
1211
1212 /*
1213 * Indic configurations.
1214 */
1215
1216 // base_position
1217 const BASE_POS_FIRST = 0;
1218 const BASE_POS_LAST = 1;
1219
1220 // reph_position
1221 const REPH_POS_DEFAULT = 10; // POS_BEFORE_POST,
1222
1223 const REPH_POS_AFTER_MAIN = 5; // POS_AFTER_MAIN,
1224 const REPH_POS_BEFORE_SUB = 7; // POS_BEFORE_SUB,
1225 const REPH_POS_AFTER_SUB = 9; // POS_AFTER_SUB,
1226 const REPH_POS_BEFORE_POST = 10; // POS_BEFORE_POST,
1227 const REPH_POS_AFTER_POST = 12; // POS_AFTER_POST
1228
1229 // reph_mode
1230 const REPH_MODE_IMPLICIT = 0; /* Reph formed out of initial Ra,H sequence. */
1231 const REPH_MODE_EXPLICIT = 1; /* Reph formed out of initial Ra,H,ZWJ sequence. */
1232 const REPH_MODE_VIS_REPHA = 2; /* Encoded Repha character, no reordering needed. */
1233 const REPH_MODE_LOG_REPHA = 3; /* Encoded Repha character, needs reordering. */
1234
1235
1236
1237 /*
1238 struct of indic_configs{
1239 KEY - script;
1240 0 - has_old_spec;
1241 1 - virama;
1242 2 - base_pos;
1243 3 - reph_pos;
1244 4 - reph_mode;
1245 };
1246 */
1247
1248 public static $indic_configs = array( /* index is SCRIPT_number from UCDN */
1249 9 => array(true, 0x094D, 1, 10, 0),
1250 10 => array(true, 0x09CD, 1, 9, 0),
1251 11 => array(true, 0x0A4D, 1, 7, 0),
1252 12 => array(true, 0x0ACD, 1, 10, 0),
1253 13 => array(true, 0x0B4D, 1, 5, 0),
1254 14 => array(true, 0x0BCD, 1, 12, 0),
1255 15 => array(true, 0x0C4D, 1, 12, 1),
1256 16 => array(true, 0x0CCD, 1, 12, 0),
1257 17 => array(true, 0x0D4D, 1, 5, 3),
1258 18 => array(false, 0x0DCA, 0, 5, 1), /* Sinhala */
1259 30 => array(false, 0x17D2, 0, 10, 2), /* Khmer */
1260 84 => array(false, 0xA9C0, 1, 10, 0), /* Javanese */
1261
1262 );
1263
1264
1265
1266 /*
1267
1268 // from "hb-ot-shape-complex-indic-table.cc"
1269
1270
1271 const ISC_A = 0; // INDIC_SYLLABIC_CATEGORY_AVAGRAHA Avagraha
1272 const ISC_Bi = 8; // INDIC_SYLLABIC_CATEGORY_BINDU Bindu
1273 const ISC_C = 1; // INDIC_SYLLABIC_CATEGORY_CONSONANT Consonant
1274 const ISC_CD = 1; // INDIC_SYLLABIC_CATEGORY_CONSONANT_DEAD Consonant_Dead
1275 const ISC_CF = 17; // INDIC_SYLLABIC_CATEGORY_CONSONANT_FINAL Consonant_Final
1276 const ISC_CHL = 1; // INDIC_SYLLABIC_CATEGORY_CONSONANT_HEAD_LETTER Consonant_Head_Letter
1277 const ISC_CM = 17; // INDIC_SYLLABIC_CATEGORY_CONSONANT_MEDIAL Consonant_Medial
1278 const ISC_CP = 11; // INDIC_SYLLABIC_CATEGORY_CONSONANT_PLACEHOLDER Consonant_Placeholder
1279 const ISC_CR = 15; // INDIC_SYLLABIC_CATEGORY_CONSONANT_REPHA Consonant_Repha
1280 const ISC_CS = 1; // INDIC_SYLLABIC_CATEGORY_CONSONANT_SUBJOINED Consonant_Subjoined
1281 const ISC_ML = 0; // INDIC_SYLLABIC_CATEGORY_MODIFYING_LETTER Modifying_Letter
1282 const ISC_N = 3; // INDIC_SYLLABIC_CATEGORY_NUKTA Nukta
1283 const ISC_x = 0; // INDIC_SYLLABIC_CATEGORY_OTHER Other
1284 const ISC_RS = 13; // INDIC_SYLLABIC_CATEGORY_REGISTER_SHIFTER Register_Shifter
1285 const ISC_TL = 0; // INDIC_SYLLABIC_CATEGORY_TONE_LETTER Tone_Letter
1286 const ISC_TM = 3; // INDIC_SYLLABIC_CATEGORY_TONE_MARK Tone_Mark
1287 const ISC_V = 4; // INDIC_SYLLABIC_CATEGORY_VIRAMA Virama
1288 const ISC_Vs = 8; // INDIC_SYLLABIC_CATEGORY_VISARGA Visarga
1289 const ISC_Vo = 2; // INDIC_SYLLABIC_CATEGORY_VOWEL Vowel
1290 const ISC_M = 7; // INDIC_SYLLABIC_CATEGORY_VOWEL_DEPENDENT Vowel_Dependent
1291 const ISC_VI = 2; // INDIC_SYLLABIC_CATEGORY_VOWEL_INDEPENDENT Vowel_Independent
1292
1293 const IMC_B = 8; // INDIC_MATRA_CATEGORY_BOTTOM Bottom
1294 const IMC_BR = 11; // INDIC_MATRA_CATEGORY_BOTTOM_AND_RIGHT Bottom_And_Right
1295 const IMC_I = 15; // INDIC_MATRA_CATEGORY_INVISIBLE Invisible
1296 const IMC_L = 3; // INDIC_MATRA_CATEGORY_LEFT Left
1297 const IMC_LR = 11; // INDIC_MATRA_CATEGORY_LEFT_AND_RIGHT Left_And_Right
1298 const IMC_x = 15; // INDIC_MATRA_CATEGORY_NOT_APPLICABLE Not_Applicable
1299 const IMC_O = 5; // INDIC_MATRA_CATEGORY_OVERSTRUCK Overstruck
1300 const IMC_R = 11; // INDIC_MATRA_CATEGORY_RIGHT Right
1301 const IMC_T = 6; // INDIC_MATRA_CATEGORY_TOP Top
1302 const IMC_TB = 8; // INDIC_MATRA_CATEGORY_TOP_AND_BOTTOM Top_And_Bottom
1303 const IMC_TBR = 11; // INDIC_MATRA_CATEGORY_TOP_AND_BOTTOM_AND_RIGHT Top_And_Bottom_And_Right
1304 const IMC_TL = 6; // INDIC_MATRA_CATEGORY_TOP_AND_LEFT Top_And_Left
1305 const IMC_TLR = 11; // INDIC_MATRA_CATEGORY_TOP_AND_LEFT_AND_RIGHT Top_And_Left_And_Right
1306 const IMC_TR = 11; // INDIC_MATRA_CATEGORY_TOP_AND_RIGHT Top_And_Right
1307 const IMC_VOL = 2; // INDIC_MATRA_CATEGORY_VISUAL_ORDER_LEFT Visual_Order_Left
1308
1309 If in original table = _(C,x), that = ISC_C,IMC_x
1310 Value is IMC_x << 8 (or IMC_x * 256) = 3840
1311 plus ISC_C = 1, so = 3841
1312
1313 */
1314
1315
1316
1317 public static $indic_table = array(
1318
1319 /* Devanagari (0900..097F) */
1320
1321 /* 0900 */ 3848,3848,3848,3848,3842,3842,3842,3842,
1322 /* 0908 */ 3842,3842,3842,3842,3842,3842,3842,3842,
1323 /* 0910 */ 3842,3842,3842,3842,3842, 3841, 3841, 3841,
1324 /* 0918 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1325 /* 0920 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1326 /* 0928 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1327 /* 0930 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1328 /* 0938 */ 3841, 3841, 1543, 2823, 3843, 3840, 2823, 775,
1329 /* 0940 */ 2823, 2055, 2055, 2055, 2055, 1543, 1543, 1543,
1330 /* 0948 */ 1543, 2823, 2823, 2823, 2823, 2052, 775, 2823,
1331 /* 0950 */ 3840, 3840, 3840, 3840, 3840, 1543, 2055, 2055,
1332 /* 0958 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1333 /* 0960 */ 3842,3842, 2055, 2055, 3840, 3840, 3840, 3840,
1334 /* 0968 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1335 /* 0970 */ 3840, 3840,3842,3842,3842,3842,3842,3842,
1336 /* 0978 */ 3840, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1337
1338 /* Bengali (0980..09FF) */
1339
1340 /* 0980 */ 3840,3848,3848,3848, 3840,3842,3842,3842,
1341 /* 0988 */ 3842,3842,3842,3842,3842, 3840, 3840,3842,
1342 /* 0990 */ 3842, 3840, 3840,3842,3842, 3841, 3841, 3841,
1343 /* 0998 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1344 /* 09A0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1345 /* 09A8 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841,
1346 /* 09B0 */ 3841, 3840, 3841, 3840, 3840, 3840, 3841, 3841,
1347 /* 09B8 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 775,
1348 /* 09C0 */ 2823, 2055, 2055, 2055, 2055, 3840, 3840, 775,
1349 /* 09C8 */ 775, 3840, 3840,2823,2823, 2052,3841, 3840,
1350 /* 09D0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 2823,
1351 /* 09D8 */ 3840, 3840, 3840, 3840, 3841, 3841, 3840, 3841,
1352 /* 09E0 */ 3842,3842, 2055, 2055, 3840, 3840, 3840, 3840,
1353 /* 09E8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1354 /* 09F0 */ 3841, 3841, 3840, 3840, 3840, 3840, 3840, 3840,
1355 /* 09F8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1356
1357 /* Gurmukhi (0A00..0A7F) */
1358
1359 /* 0A00 */ 3840,3848,3848,3848, 3840,3842,3842,3842,
1360 /* 0A08 */ 3842,3842,3842, 3840, 3840, 3840, 3840,3842,
1361 /* 0A10 */ 3842, 3840, 3840,3842,3842, 3841, 3841, 3841,
1362 /* 0A18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1363 /* 0A20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1364 /* 0A28 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841,
1365 /* 0A30 */ 3841, 3840, 3841, 3841, 3840, 3841, 3841, 3840,
1366 /* 0A38 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 775,
1367 /* 0A40 */ 2823, 2055, 2055, 3840, 3840, 3840, 3840, 1543,
1368 /* 0A48 */ 1543, 3840, 3840, 1543, 1543, 2052, 3840, 3840,
1369 /* 0A50 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1370 /* 0A58 */ 3840, 3841, 3841, 3841, 3841, 3840, 3841, 3840,
1371 /* 0A60 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1372 /* 0A68 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1373 /* 0A70 */ 3848, 3840,13841,13841, 3840, 3857, 3840, 3840,
1374 /* 0A78 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1375
1376 /* Gujarati (0A80..0AFF) */
1377
1378 /* 0A80 */ 3840,3848,3848,3848, 3840,3842,3842,3842,
1379 /* 0A88 */ 3842,3842,3842,3842,3842,3842, 3840,3842,
1380 /* 0A90 */ 3842,3842, 3840,3842,3842, 3841, 3841, 3841,
1381 /* 0A98 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1382 /* 0AA0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1383 /* 0AA8 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841,
1384 /* 0AB0 */ 3841, 3840, 3841, 3841, 3840, 3841, 3841, 3841,
1385 /* 0AB8 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 775,
1386 /* 0AC0 */ 2823, 2055, 2055, 2055, 2055, 1543, 3840, 1543,
1387 /* 0AC8 */ 1543,2823, 3840, 2823, 2823, 2052, 3840, 3840,
1388 /* 0AD0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1389 /* 0AD8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1390 /* 0AE0 */ 3842,3842, 2055, 2055, 3840, 3840, 3840, 3840,
1391 /* 0AE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1392 /* 0AF0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1393 /* 0AF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1394
1395 /* Oriya (0B00..0B7F) */
1396
1397 /* 0B00 */ 3840,3848,3848,3848, 3840,3842,3842,3842,
1398 /* 0B08 */ 3842,3842,3842,3842,3842, 3840, 3840,3842,
1399 /* 0B10 */ 3842, 3840, 3840,3842,3842, 3841, 3841, 3841,
1400 /* 0B18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1401 /* 0B20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1402 /* 0B28 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841,
1403 /* 0B30 */ 3841, 3840, 3841, 3841, 3840, 3841, 3841, 3841,
1404 /* 0B38 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 1543,
1405 /* 0B40 */ 2823, 2055, 2055, 2055, 2055, 3840, 3840, 775,
1406 /* 0B48 */ 1543, 3840, 3840,2823,2823,2052, 3840, 3840,
1407 /* 0B50 */ 3840, 3840, 3840, 3840, 3840, 3840, 1543,2823,
1408 /* 0B58 */ 3840, 3840, 3840, 3840, 3841, 3841, 3840, 3841,
1409 /* 0B60 */ 3842,3842, 2055, 2055, 3840, 3840, 3840, 3840,
1410 /* 0B68 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1411 /* 0B70 */ 3840, 3841, 3840, 3840, 3840, 3840, 3840, 3840,
1412 /* 0B78 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1413
1414 /* Tamil (0B80..0BFF) */
1415
1416 /* 0B80 */ 3840, 3840, 3848, 3840, 3840, 3842, 3842, 3842,
1417 /* 0B88 */ 3842, 3842, 3842, 3840, 3840, 3840, 3842,3842,
1418 /* 0B90 */ 3842, 3840, 3842, 3842, 3842, 3841, 3840, 3840,
1419 /* 0B98 */ 3840, 3841, 3841, 3840, 3841, 3840, 3841, 3841,
1420 /* 0BA0 */ 3840, 3840, 3840, 3841, 3841, 3840, 3840, 3840,
1421 /* 0BA8 */ 3841, 3841, 3841, 3840, 3840, 3840, 3841, 3841,
1422 /* 0BB0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1423 /* 0BB8 */ 3841, 3841, 3840, 3840, 3840, 3840, 2823, 2823,
1424 /* 0BC0 */ 1543, 2055, 2055, 3840, 3840, 3840, 775, 775,
1425 /* 0BC8 */ 775, 3840, 2823, 2823, 2823, 1540, 3840, 3840,
1426 /* 0BD0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 2823,
1427 /* 0BD8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1428 /* 0BE0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1429 /* 0BE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1430 /* 0BF0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1431 /* 0BF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1432
1433 /* Telugu (0C00..0C7F) */
1434
1435 /* 0C00 */ 3840,3848,3848,3848, 3840,3842,3842,3842,
1436 /* 0C08 */ 3842,3842,3842,3842,3842, 3840,3842,3842,
1437 /* 0C10 */ 3842, 3840,3842,3842,3842, 3841, 3841, 3841,
1438 /* 0C18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1439 /* 0C20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1440 /* 0C28 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841,
1441 /* 0C30 */ 3841, 3841, 3841, 3841, 3840, 3841, 3841, 3841,
1442 /* 0C38 */ 3841, 3841, 3840, 3840, 3840, 3840, 1543, 1543,
1443 /* 0C40 */ 1543, 2823, 2823, 2823, 2823, 3840, 1543, 1543,
1444 /* 0C48 */ 2055, 3840, 1543, 1543, 1543, 1540, 3840, 3840,
1445 /* 0C50 */ 3840, 3840, 3840, 3840, 3840, 1543, 2055, 3840,
1446 /* 0C58 */ 3841, 3841, 3840, 3840, 3840, 3840, 3840, 3840,
1447 /* 0C60 */ 3842,3842, 2055, 2055, 3840, 3840, 3840, 3840,
1448 /* 0C68 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1449 /* 0C70 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1450 /* 0C78 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1451
1452 /* Kannada (0C80..0CFF) */
1453
1454 /* 0C80 */ 3840, 3840,3848,3848, 3840,3842,3842,3842,
1455 /* 0C88 */ 3842,3842,3842,3842,3842, 3840,3842,3842,
1456 /* 0C90 */ 3842, 3840,3842,3842,3842, 3841, 3841, 3841,
1457 /* 0C98 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1458 /* 0CA0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1459 /* 0CA8 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841,
1460 /* 0CB0 */ 3841, 3841, 3841, 3841, 3840, 3841, 3841, 3841,
1461 /* 0CB8 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 1543,
1462 /* 0CC0 */ 2823, 2823, 2823, 2823, 2823, 3840, 1543,2823,
1463 /* 0CC8 */ 2823, 3840,2823,2823, 1543, 1540, 3840, 3840,
1464 /* 0CD0 */ 3840, 3840, 3840, 3840, 3840, 2823, 2823, 3840,
1465 /* 0CD8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3841, 3840,
1466 /* 0CE0 */ 3842,3842, 2055, 2055, 3840, 3840, 3840, 3840,
1467 /* 0CE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1468 /* 0CF0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1469 /* 0CF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1470
1471 /* Malayalam (0D00..0D7F) */
1472
1473 /* 0D00 */ 3840, 3840,3848,3848, 3840,3842,3842,3842,
1474 /* 0D08 */ 3842,3842,3842,3842,3842, 3840,3842,3842,
1475 /* 0D10 */ 3842, 3840,3842,3842,3842, 3841, 3841, 3841,
1476 /* 0D18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1477 /* 0D20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1478 /* 0D28 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1479 /* 0D30 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1480 /* 0D38 */ 3841, 3841, 3841, 3840, 3840, 3840, 2823, 2823,
1481 /* 0D40 */ 2823, 2823, 2823, 2055, 2055, 3840, 775, 775,
1482 /* 0D48 */ 775, 3840,2823,2823,2823, 1540, 3855, 3840,
1483 /* 0D50 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 2823,
1484 /* 0D58 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1485 /* 0D60 */ 3842,3842, 2055, 2055, 3840, 3840, 3840, 3840,
1486 /* 0D68 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1487 /* 0D70 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1488 /* 0D78 */ 3840, 3840,3841,3841,3841,3841,3841,3841,
1489
1490 /* Sinhala (0D80..0DFF) */
1491
1492 /* 0D80 */ 3840, 3840, 3848, 3848, 3840, 3842, 3842, 3842,
1493 /* 0D88 */ 3842, 3842, 3842, 3842, 3842, 3842, 3842, 3842,
1494 /* 0D90 */ 3842, 3842, 3842, 3842, 3842, 3842, 3842, 3840,
1495 /* 0D98 */ 3840, 3840, 3841, 3841, 3841, 3841, 3841, 3841,
1496 /* 0DA0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1497 /* 0DA8 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1498 /* 0DB0 */ 3841, 3841, 3840, 3841, 3841, 3841, 3841, 3841,
1499 /* 0DB8 */ 3841, 3841, 3841, 3841, 3840, 3841, 3840, 3840,
1500 /* 0DC0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3840,
1501 /* 0DC8 */ 3840, 3840, 1540, 3840, 3840, 3840, 3840, 2823,
1502 /* 0DD0 */ 2823, 2823, 1543, 1543, 2055, 3840, 2055, 3840,
1503 /* 0DD8 */ 2823, 775, 1543, 775, 2823, 2823, 2823, 2823,
1504 /* 0DE0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1505 /* 0DE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1506 /* 0DF0 */ 3840, 3840, 2823, 2823, 3840, 3840, 3840, 3840,
1507 /* 0DF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1508
1509
1510 /* Vedic Extensions (1CD0..1CFF) */
1511
1512 /* 1CD0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1513 /* 1CD8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1514 /* 1CE0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1515 /* 1CE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1516 /* 1CF0 */ 3840, 3840,3848,3848, 3840, 3840, 3840, 3840,
1517 /* 1CF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1518
1519
1520 );
1521
1522 public static $khmer_table = array(
1523
1524 /* Khmer (1780..17FF) */
1525
1526 /* 1780 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1527 /* 1788 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1528 /* 1790 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1529 /* 1798 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841,
1530 /* 17A0 */ 3841, 3841, 3841, 3842, 3842, 3842, 3842, 3842,
1531 /* 17A8 */ 3842, 3842, 3842, 3842, 3842, 3842, 3842, 3842,
1532 /* 17B0 */ 3842, 3842, 3842, 3842, 3840, 3840, 2823, 1543,
1533 /* 17B8 */ 1543, 1543, 1543, 2055, 2055, 2055, 1543,2823,
1534 /* 17C0 */ 2823, 775, 775, 775, 2823, 2823, 3848, 3848,
1535 /* 17C8 */ 2823, 3853, 3853, 3840, 3855, 3840, 3840, 3840,
1536 /* 17D0 */ 3840, 1540, 3844, 3840, 3840, 3840, 3840, 3840,
1537 /* 17D8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1538 /* 17E0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1539 /* 17E8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1540 /* 17F0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1541 /* 17F8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840,
1542
1543
1544 );
1545
1546
1547
1548 // from "hb-ot-shape-complex-indic-table.cc"
1549 public static function indic_get_categories ($u) {
1550 if (0x0900 <= $u && $u <= 0x0DFF) return self::$indic_table[$u - 0x0900 + 0]; // offset 0 for Most "indic"
1551 if (0x1CD0 <= $u && $u <= 0x1D00) return self::$indic_table[$u - 0x1CD0 + 1152]; // offset for Vedic extensions
1552 if (0x1780 <= $u && $u <= 0x17FF) return self::$khmer_table[$u - 0x1780]; // Khmer
1553 if ($u == 0x00A0) return 3851; // (ISC_CP | (IMC_x << 8))
1554 if ($u == 0x25CC) return 3851; // (ISC_CP | (IMC_x << 8))
1555 return 3840; // (ISC_x | (IMC_x << 8))
1556 }
1557
1558 // BELOW from hb-ot-shape-complex-indic.cc
1559 /*
1560 * Indic shaper.
1561 */
1562
1563 public static function IN_HALF_BLOCK($u, $Base) { return (($u & ~0x7F) == $Base); }
1564
1565 public static function IS_DEVA($u) { return self::IN_HALF_BLOCK ($u, 0x0900); }
1566 public static function IS_BENG($u) { return self::IN_HALF_BLOCK ($u, 0x0980); }
1567 public static function IS_GURU($u) { return self::IN_HALF_BLOCK ($u, 0x0A00); }
1568 public static function IS_GUJR($u) { return self::IN_HALF_BLOCK ($u, 0x0A80); }
1569 public static function IS_ORYA($u) { return self::IN_HALF_BLOCK ($u, 0x0B00); }
1570 public static function IS_TAML($u) { return self::IN_HALF_BLOCK ($u, 0x0B80); }
1571 public static function IS_TELU($u) { return self::IN_HALF_BLOCK ($u, 0x0C00); }
1572 public static function IS_KNDA($u) { return self::IN_HALF_BLOCK ($u, 0x0C80); }
1573 public static function IS_MLYM($u) { return self::IN_HALF_BLOCK ($u, 0x0D00); }
1574 public static function IS_SINH($u) { return self::IN_HALF_BLOCK ($u, 0x0D80); }
1575 public static function IS_KHMR($u) { return self::IN_HALF_BLOCK ($u, 0x1780); }
1576
1577
1578 public static function MATRA_POS_LEFT($u) { return self::POS_PRE_M; }
1579 public static function MATRA_POS_RIGHT($u) { return
1580 (self::IS_DEVA($u) ? self::POS_AFTER_SUB :
1581 (self::IS_BENG($u) ? self::POS_AFTER_POST :
1582 (self::IS_GURU($u) ? self::POS_AFTER_POST :
1583 (self::IS_GUJR($u) ? self::POS_AFTER_POST :
1584 (self::IS_ORYA($u) ? self::POS_AFTER_POST :
1585 (self::IS_TAML($u) ? self::POS_AFTER_POST :
1586 (self::IS_TELU($u) ? ($u <= 0x0C42 ? self::POS_BEFORE_SUB : self::POS_AFTER_SUB) :
1587 (self::IS_KNDA($u) ? ($u < 0x0CC3 || $u > 0xCD6 ? self::POS_BEFORE_SUB : self::POS_AFTER_SUB) :
1588 (self::IS_MLYM($u) ? self::POS_AFTER_POST :
1589 (self::IS_SINH($u) ? self::POS_AFTER_SUB :
1590 (self::IS_KHMR($u) ? self::POS_AFTER_POST :
1591 self::POS_AFTER_SUB))))))))))); /*default*/
1592 }
1593 public static function MATRA_POS_TOP($u) { return /* BENG and MLYM don't have top matras. */
1594 (self::IS_DEVA($u) ? self::POS_AFTER_SUB :
1595 (self::IS_GURU($u) ? self::POS_AFTER_POST : /* Deviate from spec */
1596 (self::IS_GUJR($u) ? self::POS_AFTER_SUB :
1597 (self::IS_ORYA($u) ? self::POS_AFTER_MAIN :
1598 (self::IS_TAML($u) ? self::POS_AFTER_SUB :
1599 (self::IS_TELU($u) ? self::POS_BEFORE_SUB :
1600 (self::IS_KNDA($u) ? self::POS_BEFORE_SUB :
1601 (self::IS_SINH($u) ? self::POS_AFTER_SUB :
1602 (self::IS_KHMR($u) ? self::POS_AFTER_POST :
1603 self::POS_AFTER_SUB))))))))); /*default*/
1604 }
1605 public static function MATRA_POS_BOTTOM($u) { return
1606 (self::IS_DEVA($u) ? self::POS_AFTER_SUB :
1607 (self::IS_BENG($u) ? self::POS_AFTER_SUB :
1608 (self::IS_GURU($u) ? self::POS_AFTER_POST :
1609 (self::IS_GUJR($u) ? self::POS_AFTER_POST :
1610 (self::IS_ORYA($u) ? self::POS_AFTER_SUB :
1611 (self::IS_TAML($u) ? self::POS_AFTER_POST :
1612 (self::IS_TELU($u) ? self::POS_BEFORE_SUB :
1613 (self::IS_KNDA($u) ? self::POS_BEFORE_SUB :
1614 (self::IS_MLYM($u) ? self::POS_AFTER_POST :
1615 (self::IS_SINH($u) ? self::POS_AFTER_SUB :
1616 (self::IS_KHMR($u) ? self::POS_AFTER_POST :
1617 self::POS_AFTER_SUB))))))))))); /*default*/
1618 }
1619
1620 public static function matra_position ($u, $side) {
1621 switch ($side) {
1622 case self::POS_PRE_C: return self::MATRA_POS_LEFT($u);
1623 case self::POS_POST_C: return self::MATRA_POS_RIGHT($u);
1624 case self::POS_ABOVE_C: return self::MATRA_POS_TOP($u);
1625 case self::POS_BELOW_C: return self::MATRA_POS_BOTTOM($u);
1626 }
1627 return $side;
1628 }
1629
1630 // vowel matras that have to be split into two parts.
1631 // From Harfbuzz (old)
1632 // New HarfBuzz uses /src/hb-ucdn/ucdn.c and unicodedata_db.h for full method of decomposition for all characters
1633 // Should always fully decompose and then recompose back, but we will just do the split matras
1634 public static function decompose_indic($ab) {
1635 $sub = array();
1636 switch ($ab) {
1637 /*
1638 * Decompose split matras.
1639 */
1640 /* bengali */
1641 case 0x9cb : $sub[0] = 0x9c7; $sub[1]= 0x9be; return $sub;
1642 case 0x9cc : $sub[0] = 0x9c7; $sub[1]= 0x9d7; return $sub;
1643 /* oriya */
1644 case 0xb48 : $sub[0] = 0xb47; $sub[1]= 0xb56; return $sub;
1645 case 0xb4b : $sub[0] = 0xb47; $sub[1]= 0xb3e; return $sub;
1646 case 0xb4c : $sub[0] = 0xb47; $sub[1]= 0xb57; return $sub;
1647 /* tamil */
1648 case 0xbca : $sub[0] = 0xbc6; $sub[1]= 0xbbe; return $sub;
1649 case 0xbcb : $sub[0] = 0xbc7; $sub[1]= 0xbbe; return $sub;
1650 case 0xbcc : $sub[0] = 0xbc6; $sub[1]= 0xbd7; return $sub;
1651 /* telugu */
1652 case 0xc48 : $sub[0] = 0xc46; $sub[1]= 0xc56; return $sub;
1653 /* kannada */
1654 case 0xcc0 : $sub[0] = 0xcbf; $sub[1]= 0xcd5; return $sub;
1655 case 0xcc7 : $sub[0] = 0xcc6; $sub[1]= 0xcd5; return $sub;
1656 case 0xcc8 : $sub[0] = 0xcc6; $sub[1]= 0xcd6; return $sub;
1657 case 0xcca : $sub[0] = 0xcc6; $sub[1]= 0xcc2; return $sub;
1658 case 0xccb : $sub[0] = 0xcc6; $sub[1]= 0xcc2; $sub[2]= 0xcd5; return $sub;
1659 /* malayalam */
1660 case 0xd4a : $sub[0] = 0xd46; $sub[1]= 0xd3e; return $sub;
1661 case 0xd4b : $sub[0] = 0xd47; $sub[1]= 0xd3e; return $sub;
1662 case 0xd4c : $sub[0] = 0xd46; $sub[1]= 0xd57; return $sub;
1663 /* sinhala */
1664 // NB Some fonts break with these Sinhala decomps (although this is Uniscribe spec)
1665 // Can check if character would be substituted by pstf and only decompose if true
1666 // e.g. if (isset($GSUBdata['pstf'][$ab])) - would need to pass $GSUBdata as parameter to this function
1667 case 0xdda : $sub[0] = 0xdd9; $sub[1]= 0xdca; return $sub;
1668 case 0xddc : $sub[0] = 0xdd9; $sub[1]= 0xdcf; return $sub;
1669 case 0xddd : $sub[0] = 0xdd9; $sub[1]= 0xdcf; $sub[2]= 0xdca; return $sub;
1670 case 0xdde : $sub[0] = 0xdd9; $sub[1]= 0xddf; return $sub;
1671 /* khmer */
1672 case 0x17be : $sub[0] = 0x17c1; $sub[1]= 0x17be; return $sub;
1673 case 0x17bf : $sub[0] = 0x17c1; $sub[1]= 0x17bf; return $sub;
1674 case 0x17c0 : $sub[0] = 0x17c1; $sub[1]= 0x17c0; return $sub;
1675
1676 case 0x17c4 : $sub[0] = 0x17c1; $sub[1]= 0x17c4; return $sub;
1677 case 0x17c5 : $sub[0] = 0x17c1; $sub[1]= 0x17c5; return $sub;
1678 /* tibetan - included here although does not use Inidc shaper in other ways */
1679 case 0xf73 : $sub[0] = 0xf71; $sub[1]= 0xf72; return $sub;
1680 case 0xf75 : $sub[0] = 0xf71; $sub[1]= 0xf74; return $sub;
1681 case 0xf76 : $sub[0] = 0xfb2; $sub[1]= 0xf80; return $sub;
1682 case 0xf77 : $sub[0] = 0xfb2; $sub[1]= 0xf81; return $sub;
1683 case 0xf78 : $sub[0] = 0xfb3; $sub[1]= 0xf80; return $sub;
1684 case 0xf79 : $sub[0] = 0xfb3; $sub[1]= 0xf71; $sub[2]= 0xf80; return $sub;
1685 case 0xf81 : $sub[0] = 0xf71; $sub[1]= 0xf80; return $sub;
1686 }
1687 return false;
1688 }
1689
1690
1691
1692
1693
1694 public static function bubble_sort(&$arr, $start, $len) {
1695 if ($len<2) { return;}
1696 $k = $start+$len-2;
1697 while ($k >= $start) {
1698 for ($j=$start; $j<=$k; $j++) {
1699 if ($arr[$j]['indic_position'] > $arr[$j + 1]['indic_position']) {
1700 $t = $arr[$j];
1701 $arr[$j] = $arr[$j + 1];
1702 $arr[$j + 1] = $t;
1703 }
1704 }
1705 $k--;
1706 }
1707 }
1708
1709
1710
1711
1712 } // end Class
1713
1714 ?>