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