pdfnative 1.0.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -88,12 +88,12 @@ function shapeThaiText(str, fontData) {
88
88
  function getAdv(gid) {
89
89
  return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
90
90
  }
91
- function getBaseAnchor(baseGid, markClass) {
91
+ function getBaseAnchor2(baseGid, markClass) {
92
92
  const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
93
93
  if (!base) return null;
94
94
  return base[markClass] ?? null;
95
95
  }
96
- function getMarkAnchor(markGid) {
96
+ function getMarkAnchor2(markGid) {
97
97
  const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
98
98
  if (!mark) return null;
99
99
  return { classIdx: mark[0], x: mark[1], y: mark[2] };
@@ -130,7 +130,7 @@ function shapeThaiText(str, fontData) {
130
130
  for (let ai = 0; ai < cluster.aboves.length; ai++) {
131
131
  const abvCp = cluster.aboves[ai];
132
132
  const markGid = resolveMarkGid(abvCp, isTallBase);
133
- const markAnchor = getMarkAnchor(markGid);
133
+ const markAnchor = getMarkAnchor2(markGid);
134
134
  let dx = 0;
135
135
  let dy = 0;
136
136
  if (ai > 0 && prevAboveMarkGid !== null) {
@@ -139,7 +139,7 @@ function shapeThaiText(str, fontData) {
139
139
  dx = prevAboveMarkDx + m2mOffset.dx;
140
140
  dy = prevAboveMarkDy + m2mOffset.dy;
141
141
  } else if (markAnchor) {
142
- const baseAnchor = getBaseAnchor(baseGid, markAnchor.classIdx);
142
+ const baseAnchor = getBaseAnchor2(baseGid, markAnchor.classIdx);
143
143
  if (baseAnchor) {
144
144
  dx = baseAnchor[0] - markAnchor.x - baseAdv;
145
145
  dy = baseAnchor[1] - markAnchor.y;
@@ -147,7 +147,7 @@ function shapeThaiText(str, fontData) {
147
147
  }
148
148
  } else {
149
149
  if (markAnchor) {
150
- const baseAnchor = getBaseAnchor(baseGid, markAnchor.classIdx);
150
+ const baseAnchor = getBaseAnchor2(baseGid, markAnchor.classIdx);
151
151
  if (baseAnchor) {
152
152
  dx = baseAnchor[0] - markAnchor.x - baseAdv;
153
153
  dy = baseAnchor[1] - markAnchor.y;
@@ -161,11 +161,11 @@ function shapeThaiText(str, fontData) {
161
161
  }
162
162
  for (const blwCp of cluster.belows) {
163
163
  const markGid = cmap[blwCp] || 0;
164
- const markAnchor = getMarkAnchor(markGid);
164
+ const markAnchor = getMarkAnchor2(markGid);
165
165
  let dx = 0;
166
166
  let dy = 0;
167
167
  if (markAnchor) {
168
- const baseAnchor = getBaseAnchor(baseGid, markAnchor.classIdx);
168
+ const baseAnchor = getBaseAnchor2(baseGid, markAnchor.classIdx);
169
169
  if (baseAnchor) {
170
170
  dx = baseAnchor[0] - markAnchor.x - baseAdv;
171
171
  dy = baseAnchor[1] - markAnchor.y;
@@ -177,148 +177,6 @@ function shapeThaiText(str, fontData) {
177
177
  return shaped;
178
178
  }
179
179
 
180
- // src/shaping/script-detect.ts
181
- function detectCharLang(cp) {
182
- if (cp >= 880 && cp <= 1023 || cp >= 7936 && cp <= 8191) return "el";
183
- if (cp >= 2304 && cp <= 2431 || cp >= 43232 && cp <= 43263) return "hi";
184
- if (cp >= 3584 && cp <= 3711) return "th";
185
- if (cp >= 12352 && cp <= 12543) return "ja";
186
- if (cp >= 44032 && cp <= 55215 || cp >= 4352 && cp <= 4607 || cp >= 12592 && cp <= 12687) return "ko";
187
- if (cp >= 19968 && cp <= 40959 || cp >= 13312 && cp <= 19903 || cp >= 63744 && cp <= 64255) return "zh";
188
- if (cp >= 7680 && cp <= 7935 || cp === 8363 || cp === 272 || cp === 273 || cp === 259 || cp === 258) return "vi";
189
- if (cp === 260 || cp === 261 || cp === 262 || cp === 263 || cp === 280 || cp === 281 || cp === 321 || cp === 322 || cp === 323 || cp === 324 || cp === 346 || cp === 347 || cp === 377 || cp === 378 || cp === 379 || cp === 380) return "pl";
190
- if (cp >= 256 && cp <= 383 || cp === 8378) return "tr";
191
- if (cp >= 1424 && cp <= 1535) return "he";
192
- if (cp >= 1536 && cp <= 1791 || cp >= 1872 && cp <= 1919 || cp >= 64336 && cp <= 65023 || cp >= 65136 && cp <= 65279) return "ar";
193
- if (cp >= 1024 && cp <= 1279 || cp >= 1280 && cp <= 1327) return "ru";
194
- if (cp >= 4256 && cp <= 4351 || cp >= 11520 && cp <= 11567) return "ka";
195
- if (cp >= 1328 && cp <= 1423 || cp >= 64275 && cp <= 64279) return "hy";
196
- return null;
197
- }
198
-
199
- // src/shaping/multi-font.ts
200
- function splitTextByFont(str, fontEntries) {
201
- if (!str || fontEntries.length === 0) return [];
202
- if (fontEntries.length === 1) return [{ text: str, entry: fontEntries[0] }];
203
- const runs = [];
204
- let currentEntry = null;
205
- let currentText = "";
206
- for (let i = 0; i < str.length; ) {
207
- const cp = str.codePointAt(i) ?? 0;
208
- const charLen = cp > 65535 ? 2 : 1;
209
- const normCp = cp === 8239 || cp === 160 ? 32 : cp;
210
- const char = str.substring(i, i + charLen);
211
- if (currentEntry && currentEntry.fontData.cmap[normCp]) {
212
- currentText += char;
213
- i += charLen;
214
- continue;
215
- }
216
- let newEntry = null;
217
- const charLang = detectCharLang(normCp);
218
- if (charLang) {
219
- for (const fe of fontEntries) {
220
- if (fe.lang === charLang && fe.fontData.cmap[normCp]) {
221
- newEntry = fe;
222
- break;
223
- }
224
- }
225
- }
226
- if (!newEntry) {
227
- for (const fe of fontEntries) {
228
- if (fe.fontData.cmap[normCp]) {
229
- newEntry = fe;
230
- break;
231
- }
232
- }
233
- }
234
- if (!newEntry) newEntry = fontEntries[0];
235
- if (newEntry !== currentEntry) {
236
- if (currentText && currentEntry) runs.push({ text: currentText, entry: currentEntry });
237
- currentEntry = newEntry;
238
- currentText = char;
239
- } else {
240
- currentText += char;
241
- }
242
- i += charLen;
243
- }
244
- if (currentText && currentEntry) runs.push({ text: currentText, entry: currentEntry });
245
- return runs;
246
- }
247
-
248
- // src/fonts/encoding.ts
249
- function toWinAnsi(str) {
250
- if (!str) return "";
251
- let r = "";
252
- for (let i = 0; i < str.length; i++) {
253
- const c = str.charCodeAt(i);
254
- if (c >= 32 && c <= 126) r += str[i];
255
- else if (c >= 160 && c <= 255) r += str[i];
256
- else if (c === 8364) r += "\x80";
257
- else if (c === 8218) r += "\x82";
258
- else if (c === 402) r += "\x83";
259
- else if (c === 8222) r += "\x84";
260
- else if (c === 8230) r += "\x85";
261
- else if (c === 8224) r += "\x86";
262
- else if (c === 8225) r += "\x87";
263
- else if (c === 710) r += "\x88";
264
- else if (c === 8240) r += "\x89";
265
- else if (c === 352) r += "\x8A";
266
- else if (c === 8249) r += "\x8B";
267
- else if (c === 338) r += "\x8C";
268
- else if (c === 381) r += "\x8E";
269
- else if (c === 8216) r += "\x91";
270
- else if (c === 8217) r += "\x92";
271
- else if (c === 8220) r += "\x93";
272
- else if (c === 8221) r += "\x94";
273
- else if (c === 8226) r += "\x95";
274
- else if (c === 8211) r += "\x96";
275
- else if (c === 8212) r += "\x97";
276
- else if (c === 732) r += "\x98";
277
- else if (c === 8482) r += "\x99";
278
- else if (c === 353) r += "\x9A";
279
- else if (c === 8250) r += "\x9B";
280
- else if (c === 339) r += "\x9C";
281
- else if (c === 382) r += "\x9E";
282
- else if (c === 376) r += "\x9F";
283
- else if (c === 160 || c === 8239) r += " ";
284
- else if (c === 9 || c === 10 || c === 13) r += " ";
285
- else if (c < 32) ; else r += "?";
286
- }
287
- return r;
288
- }
289
- function pdfString(str) {
290
- const s = toWinAnsi(str);
291
- return "(" + s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)") + ")";
292
- }
293
- function truncate(str, max) {
294
- if (!str || str.length <= max) return str || "";
295
- if (max <= 2) return "..";
296
- return str.slice(0, max - 2) + "..";
297
- }
298
- function helveticaWidth(str, sz) {
299
- let w = 0;
300
- for (let i = 0; i < str.length; i++) {
301
- const cp = str.codePointAt(i) ?? 0;
302
- if (cp > 65535) i++;
303
- if (cp >= 48 && cp <= 57) w += 556;
304
- else if (cp >= 65 && cp <= 90) w += 680;
305
- else if (cp >= 97 && cp <= 122) w += 500;
306
- else if (cp === 32) w += 278;
307
- else if (cp === 46 || cp === 44) w += 278;
308
- else if (cp === 43) w += 584;
309
- else if (cp === 45) w += 333;
310
- else if (cp === 47 || cp === 58) w += 278;
311
- else if (cp === 8212) w += 1e3;
312
- else if (cp === 8211) w += 556;
313
- else if (cp === 8230) w += 1e3;
314
- else if (cp === 8216 || cp === 8217) w += 222;
315
- else if (cp === 8220 || cp === 8221) w += 333;
316
- else if (cp === 8364) w += 556;
317
- else w += 556;
318
- }
319
- return w * sz / 1e3;
320
- }
321
-
322
180
  // src/shaping/script-registry.ts
323
181
  var ARABIC_START = 1536;
324
182
  var ARABIC_END = 1791;
@@ -340,6 +198,36 @@ var BENGALI_START = 2432;
340
198
  var BENGALI_END = 2559;
341
199
  var TAMIL_START = 2944;
342
200
  var TAMIL_END = 3071;
201
+ var EMOJI_RANGES = [
202
+ [127744, 128511],
203
+ // Miscellaneous Symbols and Pictographs
204
+ [128512, 128591],
205
+ // Emoticons
206
+ [128640, 128767],
207
+ // Transport and Map Symbols
208
+ [128768, 128895],
209
+ // Alchemical Symbols (partial)
210
+ [128896, 129023],
211
+ // Geometric Shapes Extended
212
+ [129024, 129279],
213
+ // Supplemental Arrows-C
214
+ [129280, 129535],
215
+ // Supplemental Symbols and Pictographs
216
+ [129536, 129647],
217
+ // Chess Symbols
218
+ [129648, 129791],
219
+ // Symbols and Pictographs Extended-A
220
+ [9728, 9983],
221
+ // Miscellaneous Symbols
222
+ [9984, 10175],
223
+ // Dingbats
224
+ [126976, 127023],
225
+ // Mahjong Tiles
226
+ [127136, 127231]
227
+ // Playing Cards
228
+ ];
229
+ var FITZPATRICK_START = 127995;
230
+ var FITZPATRICK_END = 127999;
343
231
  function isArabicCodepoint(cp) {
344
232
  return cp >= ARABIC_START && cp <= ARABIC_END || cp >= ARABIC_SUPPLEMENT_START && cp <= ARABIC_SUPPLEMENT_END || cp >= ARABIC_EXTENDED_A_START && cp <= ARABIC_EXTENDED_A_END || cp >= ARABIC_PRES_A_START && cp <= ARABIC_PRES_A_END || cp >= ARABIC_PRES_B_START && cp <= ARABIC_PRES_B_END;
345
233
  }
@@ -355,6 +243,13 @@ function isTamilCodepoint(cp) {
355
243
  function isDevanagariCodepoint(cp) {
356
244
  return cp >= DEVANAGARI_START && cp <= DEVANAGARI_END || cp >= DEVANAGARI_EXT_START && cp <= DEVANAGARI_EXT_END;
357
245
  }
246
+ function isEmojiCodepoint(cp) {
247
+ if (cp >= FITZPATRICK_START && cp <= FITZPATRICK_END) return true;
248
+ for (const [lo, hi] of EMOJI_RANGES) {
249
+ if (cp >= lo && cp <= hi) return true;
250
+ }
251
+ return false;
252
+ }
358
253
  function containsArabic(text) {
359
254
  for (let i = 0; i < text.length; ) {
360
255
  const cp = text.codePointAt(i) ?? 0;
@@ -388,442 +283,829 @@ function containsDevanagari(str) {
388
283
  return false;
389
284
  }
390
285
 
391
- // src/shaping/bengali-shaper.ts
392
- var HALANT = 2509;
393
- var NUKTA = 2492;
394
- var RA = 2480;
395
- function bengaliCharType(cp) {
396
- if (cp === HALANT) return 7;
397
- if (cp === NUKTA) return 8;
398
- if (cp >= 2433 && cp <= 2435) return 6;
399
- if (cp >= 2437 && cp <= 2452) return 1;
400
- if (cp >= 2453 && cp <= 2489) return 0;
401
- if (cp === 2495) return 4;
402
- if (cp === 2503) return 4;
403
- if (cp === 2504) return 4;
404
- if (cp === 2497 || cp === 2498 || cp === 2499 || cp === 2500) return 3;
405
- if (cp === 2494) return 5;
406
- if (cp === 2496) return 5;
407
- if (cp === 2507) return 4;
408
- if (cp === 2508) return 4;
409
- if (cp >= 2494 && cp <= 2508) return 2;
410
- if (cp === 2519) return 5;
411
- if (cp >= 2534 && cp <= 2543) return 9;
412
- if (cp === 2527) return 0;
413
- if (cp === 2493) return 1;
414
- if (cp === 2510) return 0;
415
- return -1;
416
- }
417
- function isConsonant(cp) {
418
- return bengaliCharType(cp) === 0;
286
+ // src/shaping/script-detect.ts
287
+ function detectCharLang(cp) {
288
+ if (cp >= 880 && cp <= 1023 || cp >= 7936 && cp <= 8191) return "el";
289
+ if (cp >= 2304 && cp <= 2431 || cp >= 43232 && cp <= 43263) return "hi";
290
+ if (cp >= 3584 && cp <= 3711) return "th";
291
+ if (cp >= 12352 && cp <= 12543) return "ja";
292
+ if (cp >= 44032 && cp <= 55215 || cp >= 4352 && cp <= 4607 || cp >= 12592 && cp <= 12687) return "ko";
293
+ if (cp >= 19968 && cp <= 40959 || cp >= 13312 && cp <= 19903 || cp >= 63744 && cp <= 64255) return "zh";
294
+ if (cp >= 7680 && cp <= 7935 || cp === 8363 || cp === 272 || cp === 273 || cp === 259 || cp === 258) return "vi";
295
+ if (cp === 260 || cp === 261 || cp === 262 || cp === 263 || cp === 280 || cp === 281 || cp === 321 || cp === 322 || cp === 323 || cp === 324 || cp === 346 || cp === 347 || cp === 377 || cp === 378 || cp === 379 || cp === 380) return "pl";
296
+ if (cp >= 256 && cp <= 383 || cp === 8378) return "tr";
297
+ if (cp >= 1424 && cp <= 1535) return "he";
298
+ if (cp >= 1536 && cp <= 1791 || cp >= 1872 && cp <= 1919 || cp >= 64336 && cp <= 65023 || cp >= 65136 && cp <= 65279) return "ar";
299
+ if (cp >= 1024 && cp <= 1279 || cp >= 1280 && cp <= 1327) return "ru";
300
+ if (cp >= 4256 && cp <= 4351 || cp >= 11520 && cp <= 11567) return "ka";
301
+ if (cp >= 1328 && cp <= 1423 || cp >= 64275 && cp <= 64279) return "hy";
302
+ if (isEmojiCodepoint(cp)) return "emoji";
303
+ return null;
419
304
  }
420
- function buildBengaliClusters(str) {
421
- const clusters = [];
422
- const cps = [];
423
- for (let i2 = 0; i2 < str.length; ) {
424
- const cp = str.codePointAt(i2) ?? 0;
425
- cps.push(cp);
426
- i2 += cp > 65535 ? 2 : 1;
427
- }
428
- let i = 0;
429
- while (i < cps.length) {
430
- const cp = cps[i];
431
- const type = bengaliCharType(cp);
432
- if (type < 0 || cp < BENGALI_START || cp > BENGALI_END) {
433
- clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
434
- i++;
305
+
306
+ // src/shaping/multi-font.ts
307
+ function splitTextByFont(str, fontEntries) {
308
+ if (!str || fontEntries.length === 0) return [];
309
+ if (fontEntries.length === 1) return [{ text: str, entry: fontEntries[0] }];
310
+ const runs = [];
311
+ let currentEntry = null;
312
+ let currentText = "";
313
+ for (let i = 0; i < str.length; ) {
314
+ const cp = str.codePointAt(i) ?? 0;
315
+ const charLen = cp > 65535 ? 2 : 1;
316
+ const normCp = cp === 8239 || cp === 160 ? 32 : cp;
317
+ const char = str.substring(i, i + charLen);
318
+ if (currentEntry && currentEntry.fontData.cmap[normCp]) {
319
+ currentText += char;
320
+ i += charLen;
435
321
  continue;
436
322
  }
437
- const syllable = [];
438
- let baseIdx = 0;
439
- let hasReph = false;
440
- const preMatras = [];
441
- if (isConsonant(cp) && cp === RA && i + 2 < cps.length && cps[i + 1] === HALANT && isConsonant(cps[i + 2])) {
442
- hasReph = true;
443
- syllable.push(cp, cps[i + 1]);
444
- i += 2;
323
+ let newEntry = null;
324
+ const charLang = detectCharLang(normCp);
325
+ if (charLang) {
326
+ for (const fe of fontEntries) {
327
+ if (fe.lang === charLang && fe.fontData.cmap[normCp]) {
328
+ newEntry = fe;
329
+ break;
330
+ }
331
+ }
445
332
  }
446
- let lastConsonantIdx = -1;
447
- while (i < cps.length) {
448
- const cc = cps[i];
449
- const ct = bengaliCharType(cc);
450
- if (ct === 0) {
451
- lastConsonantIdx = syllable.length;
452
- syllable.push(cc);
453
- i++;
454
- if (i < cps.length && cps[i] === NUKTA) {
455
- syllable.push(cps[i]);
456
- i++;
333
+ if (!newEntry) {
334
+ for (const fe of fontEntries) {
335
+ if (fe.fontData.cmap[normCp]) {
336
+ newEntry = fe;
337
+ break;
457
338
  }
458
- if (i < cps.length && cps[i] === HALANT) {
459
- if (i + 1 < cps.length && isConsonant(cps[i + 1])) {
460
- syllable.push(cps[i]);
461
- i++;
462
- continue;
463
- } else {
464
- syllable.push(cps[i]);
465
- i++;
466
- break;
467
- }
468
- }
469
- break;
470
- } else {
471
- break;
472
339
  }
473
340
  }
474
- baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
475
- while (i < cps.length) {
476
- const ct = bengaliCharType(cps[i]);
477
- if (ct >= 2 && ct <= 5) {
478
- if (ct === 4) {
479
- preMatras.push(syllable.length);
480
- }
481
- syllable.push(cps[i]);
482
- i++;
483
- } else {
484
- break;
485
- }
341
+ if (!newEntry) newEntry = fontEntries[0];
342
+ if (newEntry !== currentEntry) {
343
+ if (currentText && currentEntry) runs.push({ text: currentText, entry: currentEntry });
344
+ currentEntry = newEntry;
345
+ currentText = char;
346
+ } else {
347
+ currentText += char;
486
348
  }
487
- while (i < cps.length && bengaliCharType(cps[i]) === 6) {
488
- syllable.push(cps[i]);
489
- i++;
349
+ i += charLen;
350
+ }
351
+ if (currentText && currentEntry) runs.push({ text: currentText, entry: currentEntry });
352
+ return runs;
353
+ }
354
+
355
+ // src/shaping/bidi.ts
356
+ function classifyBidiType(cp) {
357
+ if (cp >= 768 && cp <= 879 || // Combining Diacritical Marks
358
+ cp >= 1425 && cp <= 1469 || // Hebrew marks
359
+ cp >= 1471 && cp <= 1471 || cp >= 1473 && cp <= 1474 || cp >= 1476 && cp <= 1477 || cp >= 1479 && cp <= 1479 || cp >= 1552 && cp <= 1562 || // Arabic marks
360
+ cp >= 1611 && cp <= 1631 || // Arabic harakat
361
+ cp >= 1648 && cp <= 1648 || // Arabic superscript alef
362
+ cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 65056 && cp <= 65071) return "NSM";
363
+ if (cp === 8203 || cp === 8204 || cp === 8205 || cp === 8206 || cp === 8207 || cp === 65279 || cp === 8294 || cp === 8295 || cp === 8296 || cp === 8297) return "BN";
364
+ if (cp >= 1632 && cp <= 1641) return "AN";
365
+ if (cp >= 1776 && cp <= 1785) return "AN";
366
+ if (cp >= 48 && cp <= 57) return "EN";
367
+ if (cp === 43 || cp === 45) return "ES";
368
+ if (cp === 35 || cp === 36 || cp === 37 || cp === 162 || cp === 163 || cp === 164 || cp === 165 || cp === 8364 || cp === 8377 || cp === 8378) return "ET";
369
+ if (cp === 44 || cp === 46 || cp === 47 || cp === 58 || cp === 160) return "CS";
370
+ if (cp === 32 || cp === 9 || cp === 10 || cp === 13 || cp === 12 || cp === 8192 || cp === 8202 || cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287 || cp === 12288) return "WS";
371
+ if (cp >= 1536 && cp <= 1791) return "AL";
372
+ if (cp >= 1872 && cp <= 1919) return "AL";
373
+ if (cp >= 2208 && cp <= 2303) return "AL";
374
+ if (cp >= 64336 && cp <= 65023) return "AL";
375
+ if (cp >= 65136 && cp <= 65278) return "AL";
376
+ if (cp >= 1488 && cp <= 1514) return "R";
377
+ if (cp >= 1520 && cp <= 1524) return "R";
378
+ if (cp >= 1792 && cp <= 1871) return "R";
379
+ if (cp >= 1920 && cp <= 1983) return "R";
380
+ if (cp >= 64285 && cp <= 64335) return "R";
381
+ if (cp >= 65 && cp <= 90) return "L";
382
+ if (cp >= 97 && cp <= 122) return "L";
383
+ if (cp >= 192 && cp <= 591) return "L";
384
+ if (cp >= 880 && cp <= 1023) return "L";
385
+ if (cp >= 1024 && cp <= 1279) return "L";
386
+ if (cp >= 3584 && cp <= 3711) return "L";
387
+ if (cp >= 2304 && cp <= 2431) return "L";
388
+ if (cp >= 12352 && cp <= 12543) return "L";
389
+ if (cp >= 19968 && cp <= 40959) return "L";
390
+ if (cp >= 44032 && cp <= 55215) return "L";
391
+ if (cp >= 33 && cp <= 47) return "ON";
392
+ if (cp >= 58 && cp <= 64) return "ON";
393
+ if (cp >= 91 && cp <= 96) return "ON";
394
+ if (cp >= 123 && cp <= 126) return "ON";
395
+ if (cp >= 161 && cp <= 191) return "ON";
396
+ if (cp >= 8208 && cp <= 8231) return "ON";
397
+ if (cp >= 8240 && cp <= 8286) return "ON";
398
+ return "L";
399
+ }
400
+ function detectParagraphLevel(types) {
401
+ for (const t of types) {
402
+ if (t === "L") return 0;
403
+ if (t === "R" || t === "AL") return 1;
404
+ }
405
+ return 0;
406
+ }
407
+ function resolveWeakTypes(types, paraLevel) {
408
+ const len = types.length;
409
+ let prevType = paraLevel === 0 ? "L" : "R";
410
+ for (let i = 0; i < len; i++) {
411
+ if (types[i] === "BN") continue;
412
+ if (types[i] === "NSM") {
413
+ types[i] = prevType;
490
414
  }
491
- if (syllable.length === 0) {
492
- syllable.push(cps[i] ?? 32);
493
- i++;
415
+ prevType = types[i];
416
+ }
417
+ let lastStrong = paraLevel === 0 ? "L" : "R";
418
+ for (let i = 0; i < len; i++) {
419
+ if (types[i] === "BN") continue;
420
+ if (types[i] === "R" || types[i] === "L" || types[i] === "AL") {
421
+ lastStrong = types[i];
422
+ } else if (types[i] === "EN" && lastStrong === "AL") {
423
+ types[i] = "AN";
494
424
  }
495
- clusters.push({
496
- codepoints: syllable,
497
- baseIndex: hasReph ? baseIdx + 2 : baseIdx,
498
- // Adjust for reph prefix
499
- hasReph,
500
- preBaseMatras: preMatras
501
- });
502
425
  }
503
- return clusters;
504
- }
505
- function shapeBengaliText(str, fontData) {
506
- const { cmap, gsub, ligatures, markAnchors, widths, defaultWidth } = fontData;
507
- const shaped = [];
508
- function resolveGid(cp) {
509
- const normCp = cp === 8239 || cp === 160 ? 32 : cp;
510
- return cmap[normCp] || 0;
426
+ for (let i = 0; i < len; i++) {
427
+ if (types[i] === "AL") types[i] = "R";
511
428
  }
512
- function resolveGidGsub(cp) {
513
- const gid = resolveGid(cp);
514
- if (gsub[gid] !== void 0) return gsub[gid];
515
- return gid;
429
+ for (let i = 1; i < len - 1; i++) {
430
+ if (types[i] === "BN") continue;
431
+ if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") {
432
+ types[i] = "EN";
433
+ } else if (types[i] === "CS") {
434
+ if (types[i - 1] === "EN" && types[i + 1] === "EN") types[i] = "EN";
435
+ else if (types[i - 1] === "AN" && types[i + 1] === "AN") types[i] = "AN";
436
+ }
516
437
  }
517
- function tryLigature(gids) {
518
- if (!ligatures || gids.length < 2) return null;
519
- const firstGid = gids[0];
520
- const entries = ligatures[firstGid];
521
- if (!entries) return null;
522
- for (const entry of entries) {
523
- const compCount = entry.length - 1;
524
- if (compCount > gids.length - 1) continue;
525
- let match = true;
526
- for (let ci = 0; ci < compCount; ci++) {
527
- if (gids[1 + ci] !== entry[1 + ci]) {
528
- match = false;
438
+ for (let i = 0; i < len; i++) {
439
+ if (types[i] === "ET") {
440
+ let found = false;
441
+ for (let j = i - 1; j >= 0; j--) {
442
+ if (types[j] === "EN") {
443
+ found = true;
529
444
  break;
530
445
  }
446
+ if (types[j] !== "ET" && types[j] !== "BN") break;
447
+ }
448
+ if (!found) {
449
+ for (let j = i + 1; j < len; j++) {
450
+ if (types[j] === "EN") {
451
+ found = true;
452
+ break;
453
+ }
454
+ if (types[j] !== "ET" && types[j] !== "BN") break;
455
+ }
531
456
  }
532
- if (match) return { resultGid: entry[0], consumed: compCount + 1 };
457
+ if (found) types[i] = "EN";
533
458
  }
534
- return null;
535
- }
536
- function getAdv(gid) {
537
- return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
538
- }
539
- function getBaseAnchor(baseGid, markClass) {
540
- const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
541
- if (!base) return null;
542
- return base[markClass] ?? null;
543
459
  }
544
- function getMarkAnchor(markGid) {
545
- const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
546
- if (!mark) return null;
547
- return { classIdx: mark[0], x: mark[1], y: mark[2] };
460
+ for (let i = 0; i < len; i++) {
461
+ if (types[i] === "ES" || types[i] === "ET" || types[i] === "CS") {
462
+ types[i] = "ON";
463
+ }
548
464
  }
549
- function emitGlyph(gid, isZero, baseGid) {
550
- if (isZero && baseGid !== void 0) {
551
- const markAnchor = getMarkAnchor(gid);
552
- if (markAnchor) {
553
- const baseAnchorPt = getBaseAnchor(baseGid, markAnchor.classIdx);
554
- if (baseAnchorPt) {
555
- const baseAdv = getAdv(baseGid);
556
- shaped.push({
557
- gid,
558
- dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
559
- dy: baseAnchorPt[1] - markAnchor.y,
560
- isZeroAdvance: true
561
- });
562
- return;
563
- }
564
- }
565
- shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
566
- } else {
567
- shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
465
+ lastStrong = paraLevel === 0 ? "L" : "R";
466
+ for (let i = 0; i < len; i++) {
467
+ if (types[i] === "BN") continue;
468
+ if (types[i] === "L" || types[i] === "R") {
469
+ lastStrong = types[i];
470
+ } else if (types[i] === "EN" && lastStrong === "L") {
471
+ types[i] = "L";
568
472
  }
569
473
  }
570
- const clusters = buildBengaliClusters(str);
571
- for (const cluster of clusters) {
572
- const { codepoints, hasReph, preBaseMatras } = cluster;
573
- const baseStart = hasReph ? 2 : 0;
574
- let baseGid = 0;
575
- for (let ci = baseStart; ci < codepoints.length; ci++) {
576
- const ct = bengaliCharType(codepoints[ci]);
577
- if (ct === 0) {
578
- baseGid = resolveGid(codepoints[ci]);
579
- } else if (ct >= 2) {
474
+ }
475
+ function resolveNeutralTypes(types, paraLevel) {
476
+ const len = types.length;
477
+ const paraDir = paraLevel === 0 ? "L" : "R";
478
+ for (let i = 0; i < len; i++) {
479
+ if (types[i] !== "ON" && types[i] !== "WS" && types[i] !== "BN") continue;
480
+ const start = i;
481
+ while (i < len && (types[i] === "ON" || types[i] === "WS" || types[i] === "BN")) i++;
482
+ const end = i;
483
+ let prevStrong = paraDir;
484
+ for (let j = start - 1; j >= 0; j--) {
485
+ if (types[j] === "L" || types[j] === "R" || types[j] === "EN" || types[j] === "AN") {
486
+ prevStrong = types[j] === "EN" || types[j] === "AN" ? "R" : types[j];
580
487
  break;
581
488
  }
582
489
  }
583
- const splitPostComponents = [];
584
- for (const mIdx of preBaseMatras) {
585
- if (mIdx < codepoints.length) {
586
- const mCp = codepoints[mIdx];
587
- if (mCp === 2507) {
588
- emitGlyph(resolveGid(2503), false);
589
- splitPostComponents.push(2494);
590
- } else if (mCp === 2508) {
591
- emitGlyph(resolveGid(2503), false);
592
- splitPostComponents.push(2519);
593
- } else {
594
- emitGlyph(resolveGid(mCp), false);
595
- }
596
- }
597
- }
598
- const clusterGids = [];
599
- const clusterEndIdx = [];
600
- let matraStart = codepoints.length;
601
- for (let ci = baseStart; ci < codepoints.length; ci++) {
602
- const ct = bengaliCharType(codepoints[ci]);
603
- if (ct === 0 || ct === 7 || ct === 8) {
604
- clusterGids.push(resolveGid(codepoints[ci]));
605
- clusterEndIdx.push(ci);
606
- } else if (ct < 0 || ct === 1 || ct === 9) {
607
- emitGlyph(resolveGid(codepoints[ci]), false);
608
- } else {
609
- matraStart = ci;
490
+ let nextStrong = paraDir;
491
+ for (let j = end; j < len; j++) {
492
+ if (types[j] === "L" || types[j] === "R" || types[j] === "EN" || types[j] === "AN") {
493
+ nextStrong = types[j] === "EN" || types[j] === "AN" ? "R" : types[j];
610
494
  break;
611
495
  }
612
496
  }
613
- let ligConsumed = 0;
614
- const ligResult = tryLigature(clusterGids);
615
- if (ligResult) {
616
- emitGlyph(ligResult.resultGid, false);
617
- baseGid = ligResult.resultGid;
618
- ligConsumed = ligResult.consumed;
619
- let gi = ligConsumed;
620
- while (gi < clusterGids.length) {
621
- const subSeq = clusterGids.slice(gi);
622
- const subLig = tryLigature(subSeq);
623
- if (subLig) {
624
- emitGlyph(subLig.resultGid, false);
625
- gi += subLig.consumed;
626
- } else {
627
- const origCi = clusterEndIdx[gi];
628
- const ct = bengaliCharType(codepoints[origCi]);
629
- if (ct === 7) {
630
- emitGlyph(clusterGids[gi], true, baseGid);
631
- } else {
632
- emitGlyph(clusterGids[gi], false);
633
- }
634
- gi++;
635
- }
636
- }
497
+ const resolved = prevStrong === nextStrong ? prevStrong : paraDir;
498
+ for (let j = start; j < end; j++) {
499
+ types[j] = resolved;
500
+ }
501
+ }
502
+ }
503
+ function assignLevels(types, paraLevel) {
504
+ const levels = [];
505
+ for (const t of types) {
506
+ if (paraLevel === 0) {
507
+ levels.push(t === "R" || t === "AN" ? 1 : 0);
637
508
  } else {
638
- for (let ci = baseStart; ci < matraStart; ci++) {
639
- const cp = codepoints[ci];
640
- const ct = bengaliCharType(cp);
641
- if (ct === 0) {
642
- emitGlyph(resolveGid(cp), false);
643
- } else if (ct === 7) {
644
- emitGlyph(resolveGid(cp), true, baseGid);
645
- } else if (ct === 8) {
646
- emitGlyph(resolveGid(cp), true, baseGid);
647
- }
509
+ levels.push(t === "L" ? 2 : 1);
510
+ }
511
+ }
512
+ return levels;
513
+ }
514
+ var SENTENCE_PUNCT = /* @__PURE__ */ new Set([
515
+ 46,
516
+ // .
517
+ 44,
518
+ // ,
519
+ 59,
520
+ // ;
521
+ 58,
522
+ // :
523
+ 33,
524
+ // !
525
+ 63
526
+ // ?
527
+ ]);
528
+ function fixPunctuationAffinity(types, codePoints, len) {
529
+ for (let i = 1; i < len; i++) {
530
+ if (types[i] === "R" && SENTENCE_PUNCT.has(codePoints[i])) {
531
+ let prevIdx = i - 1;
532
+ while (prevIdx >= 0 && (types[prevIdx] === "WS" || types[prevIdx] === "BN")) prevIdx--;
533
+ if (prevIdx >= 0 && types[prevIdx] === "L") {
534
+ types[i] = "L";
648
535
  }
649
536
  }
650
- for (let ci = matraStart; ci < codepoints.length; ci++) {
651
- const cp = codepoints[ci];
652
- const ct = bengaliCharType(cp);
653
- if (ct === 4) continue;
654
- if (ct === 2 || ct === 3) {
655
- emitGlyph(resolveGid(cp), true, baseGid);
656
- } else if (ct === 5) {
657
- emitGlyph(resolveGid(cp), false);
658
- } else if (ct === 6) {
659
- emitGlyph(resolveGid(cp), true, baseGid);
660
- } else if (ct === 9) {
661
- emitGlyph(resolveGid(cp), false);
662
- } else {
663
- emitGlyph(resolveGid(cp), false);
537
+ }
538
+ }
539
+ function fixBracketPairing(types, codePoints, len) {
540
+ const OPEN_BRACKETS = {
541
+ 40: 41,
542
+ // ( )
543
+ 91: 93,
544
+ // [ → ]
545
+ 123: 125
546
+ // { → }
547
+ };
548
+ for (let i = 0; i < len; i++) {
549
+ const closer = OPEN_BRACKETS[codePoints[i]];
550
+ if (closer === void 0) continue;
551
+ let depth = 1;
552
+ let closeIdx = -1;
553
+ for (let j = i + 1; j < len; j++) {
554
+ if (codePoints[j] === codePoints[i]) depth++;
555
+ else if (codePoints[j] === closer) {
556
+ depth--;
557
+ if (depth === 0) {
558
+ closeIdx = j;
559
+ break;
560
+ }
664
561
  }
665
562
  }
666
- for (const postCp of splitPostComponents) {
667
- emitGlyph(resolveGid(postCp), false);
563
+ if (closeIdx === -1) continue;
564
+ let hasL = false;
565
+ for (let j = i + 1; j < closeIdx; j++) {
566
+ if (types[j] === "L") {
567
+ hasL = true;
568
+ break;
569
+ }
668
570
  }
669
- if (hasReph) {
670
- const raGid = resolveGidGsub(RA);
671
- const halantGid = resolveGid(HALANT);
672
- emitGlyph(raGid, true, baseGid);
673
- emitGlyph(halantGid, true, baseGid);
571
+ if (hasL) {
572
+ types[i] = "L";
573
+ types[closeIdx] = "L";
674
574
  }
675
575
  }
676
- return shaped;
677
- }
678
-
679
- // src/shaping/tamil-shaper.ts
680
- var PULLI = 3021;
681
- function tamilCharType(cp) {
682
- if (cp === PULLI) return 7;
683
- if (cp === 2946 || cp === 2947) return 6;
684
- if (cp >= 2949 && cp <= 2964) return 1;
685
- if (cp >= 2965 && cp <= 3001) return 0;
686
- if (cp === 3007) return 4;
687
- if (cp === 3014) return 4;
688
- if (cp === 3015) return 4;
689
- if (cp === 3016) return 4;
690
- if (cp === 3018) return 4;
691
- if (cp === 3019) return 4;
692
- if (cp === 3020) return 4;
693
- if (cp === 3006) return 5;
694
- if (cp === 3008) return 5;
695
- if (cp === 3009 || cp === 3010) return 2;
696
- if (cp === 3031) return 5;
697
- if (cp >= 3006 && cp <= 3020) return 2;
698
- if (cp >= 3046 && cp <= 3055) return 9;
699
- if (cp >= 3056 && cp <= 3066) return 9;
700
- if (cp === 3024) return 6;
701
- return -1;
702
576
  }
703
- function isConsonant2(cp) {
704
- return tamilCharType(cp) === 0;
705
- }
706
- function buildTamilClusters(str) {
707
- const clusters = [];
708
- const cps = [];
709
- for (let i2 = 0; i2 < str.length; ) {
710
- const cp = str.codePointAt(i2) ?? 0;
711
- cps.push(cp);
712
- i2 += cp > 65535 ? 2 : 1;
713
- }
577
+ function findOutermostIsolatePairs(codePoints) {
578
+ const pairs = [];
714
579
  let i = 0;
715
- while (i < cps.length) {
716
- const cp = cps[i];
717
- const type = tamilCharType(cp);
718
- if (type < 0 || cp < TAMIL_START || cp > TAMIL_END) {
719
- clusters.push({ codepoints: [cp], baseIndex: 0, preBaseMatras: [] });
720
- i++;
721
- continue;
722
- }
723
- const syllable = [];
724
- let lastConsonantIdx = -1;
725
- const preMatras = [];
726
- while (i < cps.length) {
727
- const cc = cps[i];
728
- const ct = tamilCharType(cc);
729
- if (ct === 0) {
730
- lastConsonantIdx = syllable.length;
731
- syllable.push(cc);
732
- i++;
733
- if (i < cps.length && cps[i] === PULLI) {
734
- if (i + 1 < cps.length && isConsonant2(cps[i + 1])) {
735
- syllable.push(cps[i]);
736
- i++;
737
- continue;
738
- } else {
739
- syllable.push(cps[i]);
740
- i++;
580
+ while (i < codePoints.length) {
581
+ const cp = codePoints[i];
582
+ if (cp === 8294 || cp === 8295 || cp === 8296) {
583
+ let depth = 1;
584
+ let close = -1;
585
+ for (let j = i + 1; j < codePoints.length; j++) {
586
+ const cj = codePoints[j];
587
+ if (cj === 8294 || cj === 8295 || cj === 8296) depth++;
588
+ else if (cj === 8297) {
589
+ depth--;
590
+ if (depth === 0) {
591
+ close = j;
741
592
  break;
742
593
  }
743
594
  }
744
- break;
745
- } else {
746
- break;
747
595
  }
748
- }
749
- const baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
750
- while (i < cps.length) {
751
- const ct = tamilCharType(cps[i]);
752
- if (ct >= 2 && ct <= 5) {
753
- if (ct === 4) {
754
- preMatras.push(syllable.length);
755
- }
756
- syllable.push(cps[i]);
596
+ if (close === -1) {
757
597
  i++;
758
- } else {
759
- break;
598
+ continue;
760
599
  }
761
- }
762
- while (i < cps.length && tamilCharType(cps[i]) === 6) {
763
- syllable.push(cps[i]);
764
- i++;
765
- }
766
- if (syllable.length === 0) {
767
- syllable.push(cps[i] ?? 32);
600
+ const kind = cp === 8294 ? "LRI" : cp === 8295 ? "RLI" : "FSI";
601
+ pairs.push({ open: i, close, kind });
602
+ i = close + 1;
603
+ } else {
768
604
  i++;
769
605
  }
770
- clusters.push({
771
- codepoints: syllable,
772
- baseIndex: baseIdx,
773
- preBaseMatras: preMatras
774
- });
775
606
  }
776
- return clusters;
607
+ return pairs;
777
608
  }
778
- function shapeTamilText(str, fontData) {
779
- const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
780
- const shaped = [];
781
- function resolveGid(cp) {
782
- const normCp = cp === 8239 || cp === 160 ? 32 : cp;
783
- return cmap[normCp] || 0;
609
+ function normalizeBidiEmbeddings(text) {
610
+ const LRE = 8234, RLE = 8235, PDF_CP = 8236, LRO = 8237, RLO = 8238;
611
+ const LRI = 8294, RLI = 8295, PDI = 8297;
612
+ let hasEmbed = false;
613
+ for (let i = 0; i < text.length; i++) {
614
+ const c = text.charCodeAt(i);
615
+ if (c === LRE || c === RLE || c === PDF_CP || c === LRO || c === RLO) {
616
+ hasEmbed = true;
617
+ break;
618
+ }
784
619
  }
785
- function tryLigature(gids) {
786
- if (!ligatures || gids.length < 2) return null;
787
- const firstGid = gids[0];
788
- const entries = ligatures[firstGid];
789
- if (!entries) return null;
790
- for (const entry of entries) {
791
- const compCount = entry.length - 1;
792
- if (compCount > gids.length - 1) continue;
793
- let match = true;
794
- for (let ci = 0; ci < compCount; ci++) {
795
- if (gids[1 + ci] !== entry[1 + ci]) {
796
- match = false;
797
- break;
798
- }
799
- }
800
- if (match) return { resultGid: entry[0], consumed: compCount + 1 };
620
+ if (!hasEmbed) return text;
621
+ const stack = [];
622
+ const out = [];
623
+ const MAX_DEPTH = 125;
624
+ for (let i = 0; i < text.length; ) {
625
+ const cp = text.codePointAt(i) ?? 0;
626
+ const cpLen = cp > 65535 ? 2 : 1;
627
+ if (cp === LRE || cp === RLO || cp === LRO || cp === RLE) {
628
+ if (stack.length >= MAX_DEPTH) {
629
+ i += cpLen;
630
+ continue;
631
+ }
632
+ stack.push(1);
633
+ out.push(cp === LRE || cp === LRO ? LRI : RLI);
634
+ i += cpLen;
635
+ } else if (cp === PDF_CP) {
636
+ if (stack.pop()) {
637
+ out.push(PDI);
638
+ }
639
+ i += cpLen;
640
+ } else {
641
+ out.push(cp);
642
+ i += cpLen;
801
643
  }
802
- return null;
803
644
  }
804
- function getAdv(gid) {
805
- return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
645
+ let result = "";
646
+ for (let i = 0; i < out.length; i++) result += String.fromCodePoint(out[i]);
647
+ return result;
648
+ }
649
+ function resolveBidiRuns(text) {
650
+ if (!text) return [];
651
+ const normalized = normalizeBidiEmbeddings(text);
652
+ if (normalized !== text) {
653
+ return resolveBidiRuns(normalized);
806
654
  }
807
- function getBaseAnchor(baseGid, markClass) {
808
- const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
809
- if (!base) return null;
810
- return base[markClass] ?? null;
655
+ const codePoints = [];
656
+ const cpToStr = [];
657
+ for (let i = 0; i < text.length; ) {
658
+ cpToStr.push(i);
659
+ const cp = text.codePointAt(i) ?? 0;
660
+ codePoints.push(cp);
661
+ i += cp > 65535 ? 2 : 1;
811
662
  }
812
- function getMarkAnchor(markGid) {
813
- const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
814
- if (!mark) return null;
815
- return { classIdx: mark[0], x: mark[1], y: mark[2] };
663
+ cpToStr.push(text.length);
664
+ const isolates = findOutermostIsolatePairs(codePoints);
665
+ if (isolates.length === 0) {
666
+ return resolveBidiCore(text, codePoints, cpToStr);
667
+ }
668
+ const insideIsolate = new Array(codePoints.length).fill(false);
669
+ for (const p of isolates) {
670
+ for (let k = p.open; k <= p.close; k++) insideIsolate[k] = true;
671
+ }
672
+ const outerTypes = codePoints.map((cp, idx) => insideIsolate[idx] ? "BN" : classifyBidiType(cp));
673
+ const parentLevel = detectParagraphLevel(outerTypes);
674
+ const out = [];
675
+ const emitSegment = (cpStart, cpEnd, forced) => {
676
+ if (cpStart >= cpEnd) return;
677
+ const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
678
+ const segCps = codePoints.slice(cpStart, cpEnd);
679
+ const baseStrIdx = cpToStr[cpStart];
680
+ const segCpToStr = cpToStr.slice(cpStart, cpEnd + 1).map((x) => x - baseStrIdx);
681
+ const segRuns = forced === void 0 ? resolveBidiRuns(segText) : resolveBidiCore(segText, segCps, segCpToStr, forced);
682
+ for (const r of segRuns) {
683
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
684
+ }
685
+ };
686
+ let cursor = 0;
687
+ for (const pair of isolates) {
688
+ emitSegment(cursor, pair.open, parentLevel);
689
+ const innerStart = pair.open + 1;
690
+ const innerEnd = pair.close;
691
+ let innerLevel;
692
+ if (pair.kind === "LRI") innerLevel = 0;
693
+ else if (pair.kind === "RLI") innerLevel = 1;
694
+ else {
695
+ const innerTypes = codePoints.slice(innerStart, innerEnd).map(classifyBidiType);
696
+ innerLevel = detectParagraphLevel(innerTypes);
697
+ }
698
+ if (innerStart < innerEnd) {
699
+ const innerText = text.substring(cpToStr[innerStart], cpToStr[innerEnd]);
700
+ const innerRuns = resolveBidiRunsForced(innerText, innerLevel);
701
+ const baseStrIdx = cpToStr[innerStart];
702
+ for (const r of innerRuns) {
703
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
704
+ }
705
+ }
706
+ cursor = pair.close + 1;
707
+ }
708
+ emitSegment(cursor, codePoints.length, parentLevel);
709
+ return out;
710
+ }
711
+ function resolveBidiRunsForced(text, forcedLevel) {
712
+ if (!text) return [];
713
+ const codePoints = [];
714
+ const cpToStr = [];
715
+ for (let i = 0; i < text.length; ) {
716
+ cpToStr.push(i);
717
+ const cp = text.codePointAt(i) ?? 0;
718
+ codePoints.push(cp);
719
+ i += cp > 65535 ? 2 : 1;
816
720
  }
817
- function emitGlyph(gid, isZero, baseGid) {
818
- if (isZero && baseGid !== void 0) {
819
- const markAnchor = getMarkAnchor(gid);
820
- if (markAnchor) {
821
- const baseAnchorPt = getBaseAnchor(baseGid, markAnchor.classIdx);
822
- if (baseAnchorPt) {
823
- const baseAdv = getAdv(baseGid);
824
- shaped.push({
825
- gid,
826
- dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
721
+ cpToStr.push(text.length);
722
+ const isolates = findOutermostIsolatePairs(codePoints);
723
+ if (isolates.length === 0) {
724
+ return resolveBidiCore(text, codePoints, cpToStr, forcedLevel);
725
+ }
726
+ const out = [];
727
+ const emit = (cpStart, cpEnd, forced) => {
728
+ if (cpStart >= cpEnd) return;
729
+ const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
730
+ const segCps = codePoints.slice(cpStart, cpEnd);
731
+ const baseStrIdx = cpToStr[cpStart];
732
+ const segCpToStr = cpToStr.slice(cpStart, cpEnd + 1).map((x) => x - baseStrIdx);
733
+ const segRuns = resolveBidiCore(segText, segCps, segCpToStr, forced);
734
+ for (const r of segRuns) {
735
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
736
+ }
737
+ };
738
+ let cursor = 0;
739
+ for (const pair of isolates) {
740
+ emit(cursor, pair.open, forcedLevel);
741
+ const innerStart = pair.open + 1;
742
+ const innerEnd = pair.close;
743
+ let innerLevel;
744
+ if (pair.kind === "LRI") innerLevel = 0;
745
+ else if (pair.kind === "RLI") innerLevel = 1;
746
+ else {
747
+ const innerTypes = codePoints.slice(innerStart, innerEnd).map(classifyBidiType);
748
+ innerLevel = detectParagraphLevel(innerTypes);
749
+ }
750
+ if (innerStart < innerEnd) {
751
+ const innerText = text.substring(cpToStr[innerStart], cpToStr[innerEnd]);
752
+ const innerRuns = resolveBidiRunsForced(innerText, innerLevel);
753
+ const baseStrIdx = cpToStr[innerStart];
754
+ for (const r of innerRuns) {
755
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
756
+ }
757
+ }
758
+ cursor = pair.close + 1;
759
+ }
760
+ emit(cursor, codePoints.length, forcedLevel);
761
+ return out;
762
+ }
763
+ function resolveBidiCore(text, codePoints, cpToStr, forcedLevel) {
764
+ const len = codePoints.length;
765
+ if (len === 0) return [];
766
+ const types = codePoints.map(classifyBidiType);
767
+ const paraLevel = forcedLevel !== void 0 ? forcedLevel : detectParagraphLevel(types);
768
+ resolveWeakTypes(types, paraLevel);
769
+ resolveNeutralTypes(types, paraLevel);
770
+ if (paraLevel === 1) {
771
+ fixPunctuationAffinity(types, codePoints, len);
772
+ fixBracketPairing(types, codePoints, len);
773
+ }
774
+ const levels = assignLevels(types, paraLevel);
775
+ const runs = [];
776
+ let runStart = 0;
777
+ let runLevel = levels[0];
778
+ for (let i = 1; i <= len; i++) {
779
+ if (i === len || levels[i] !== runLevel) {
780
+ const start = cpToStr[runStart];
781
+ const end = cpToStr[i];
782
+ let runText = text.substring(start, end);
783
+ if (runLevel % 2 === 1) {
784
+ runText = reverseString(runText);
785
+ }
786
+ runs.push({ text: runText, level: runLevel, start });
787
+ if (i < len) {
788
+ runStart = i;
789
+ runLevel = levels[i];
790
+ }
791
+ }
792
+ }
793
+ if (paraLevel === 1 && runs.length > 1) {
794
+ runs.reverse();
795
+ }
796
+ return runs;
797
+ }
798
+ function containsRTL(text) {
799
+ for (let i = 0; i < text.length; ) {
800
+ const cp = text.codePointAt(i) ?? 0;
801
+ const t = classifyBidiType(cp);
802
+ if (t === "R" || t === "AL") return true;
803
+ i += cp > 65535 ? 2 : 1;
804
+ }
805
+ return false;
806
+ }
807
+ function stripBidiControls(text) {
808
+ if (!text) return text;
809
+ let needs = false;
810
+ for (let i = 0; i < text.length; i++) {
811
+ const c = text.charCodeAt(i);
812
+ if (c === 8206 || c === 8207 || c >= 8234 && c <= 8238 || c >= 8294 && c <= 8297) {
813
+ needs = true;
814
+ break;
815
+ }
816
+ }
817
+ if (!needs) return text;
818
+ let out = "";
819
+ for (let i = 0; i < text.length; i++) {
820
+ const c = text.charCodeAt(i);
821
+ if (c === 8206 || c === 8207 || c >= 8234 && c <= 8238 || c >= 8294 && c <= 8297) continue;
822
+ out += text[i];
823
+ }
824
+ return out;
825
+ }
826
+ function reverseString(str) {
827
+ const cps = [];
828
+ for (let i = 0; i < str.length; ) {
829
+ const cp = str.codePointAt(i) ?? 0;
830
+ cps.push(cp);
831
+ i += cp > 65535 ? 2 : 1;
832
+ }
833
+ cps.reverse();
834
+ return String.fromCodePoint(...cps);
835
+ }
836
+
837
+ // src/fonts/encoding.ts
838
+ function toWinAnsi(str) {
839
+ if (!str) return "";
840
+ let r = "";
841
+ for (let i = 0; i < str.length; i++) {
842
+ const c = str.charCodeAt(i);
843
+ if (c >= 32 && c <= 126) r += str[i];
844
+ else if (c >= 160 && c <= 255) r += str[i];
845
+ else if (c === 8364) r += "\x80";
846
+ else if (c === 8218) r += "\x82";
847
+ else if (c === 402) r += "\x83";
848
+ else if (c === 8222) r += "\x84";
849
+ else if (c === 8230) r += "\x85";
850
+ else if (c === 8224) r += "\x86";
851
+ else if (c === 8225) r += "\x87";
852
+ else if (c === 710) r += "\x88";
853
+ else if (c === 8240) r += "\x89";
854
+ else if (c === 352) r += "\x8A";
855
+ else if (c === 8249) r += "\x8B";
856
+ else if (c === 338) r += "\x8C";
857
+ else if (c === 381) r += "\x8E";
858
+ else if (c === 8216) r += "\x91";
859
+ else if (c === 8217) r += "\x92";
860
+ else if (c === 8220) r += "\x93";
861
+ else if (c === 8221) r += "\x94";
862
+ else if (c === 8226) r += "\x95";
863
+ else if (c === 8211) r += "\x96";
864
+ else if (c === 8212) r += "\x97";
865
+ else if (c === 732) r += "\x98";
866
+ else if (c === 8482) r += "\x99";
867
+ else if (c === 353) r += "\x9A";
868
+ else if (c === 8250) r += "\x9B";
869
+ else if (c === 339) r += "\x9C";
870
+ else if (c === 382) r += "\x9E";
871
+ else if (c === 376) r += "\x9F";
872
+ else if (c === 160 || c === 8239) r += " ";
873
+ else if (c === 9 || c === 10 || c === 13) r += " ";
874
+ else if (c < 32) ; else r += "?";
875
+ }
876
+ return r;
877
+ }
878
+ function pdfString(str) {
879
+ const s = toWinAnsi(stripBidiControls(str));
880
+ return "(" + s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)") + ")";
881
+ }
882
+ function truncate(str, max) {
883
+ if (!str || str.length <= max) return str || "";
884
+ if (max <= 1) return "\u2026";
885
+ return str.slice(0, max - 1) + "\u2026";
886
+ }
887
+ function helveticaWidth(str, sz) {
888
+ str = stripBidiControls(str);
889
+ let w = 0;
890
+ for (let i = 0; i < str.length; i++) {
891
+ const cp = str.codePointAt(i) ?? 0;
892
+ if (cp > 65535) i++;
893
+ if (cp >= 48 && cp <= 57) w += 556;
894
+ else if (cp >= 65 && cp <= 90) w += 680;
895
+ else if (cp >= 97 && cp <= 122) w += 500;
896
+ else if (cp === 32) w += 278;
897
+ else if (cp === 46 || cp === 44) w += 278;
898
+ else if (cp === 43) w += 584;
899
+ else if (cp === 45) w += 333;
900
+ else if (cp === 47 || cp === 58) w += 278;
901
+ else if (cp === 8212) w += 1e3;
902
+ else if (cp === 8211) w += 556;
903
+ else if (cp === 8230) w += 1e3;
904
+ else if (cp === 8216 || cp === 8217) w += 222;
905
+ else if (cp === 8220 || cp === 8221) w += 333;
906
+ else if (cp === 8364) w += 556;
907
+ else w += 556;
908
+ }
909
+ return w * sz / 1e3;
910
+ }
911
+ function helveticaBoldWidth(str, sz) {
912
+ str = stripBidiControls(str);
913
+ let w = 0;
914
+ for (let i = 0; i < str.length; i++) {
915
+ const cp = str.codePointAt(i) ?? 0;
916
+ if (cp > 65535) i++;
917
+ if (cp >= 48 && cp <= 57) w += 556;
918
+ else if (cp >= 65 && cp <= 90) w += 722;
919
+ else if (cp >= 97 && cp <= 122) w += 611;
920
+ else if (cp === 32) w += 278;
921
+ else if (cp === 46 || cp === 44) w += 278;
922
+ else if (cp === 43) w += 584;
923
+ else if (cp === 45) w += 333;
924
+ else if (cp === 47 || cp === 58) w += 278;
925
+ else if (cp === 8212) w += 1e3;
926
+ else if (cp === 8211) w += 556;
927
+ else if (cp === 8230) w += 1e3;
928
+ else if (cp === 8216 || cp === 8217) w += 278;
929
+ else if (cp === 8220 || cp === 8221) w += 500;
930
+ else if (cp === 8364) w += 556;
931
+ else w += 611;
932
+ }
933
+ return w * sz / 1e3;
934
+ }
935
+
936
+ // src/shaping/gsub-driver.ts
937
+ function tryLigature(gids, ligatures) {
938
+ if (!ligatures || gids.length < 2) return null;
939
+ const firstGid = gids[0];
940
+ const entries = ligatures[firstGid];
941
+ if (!entries) return null;
942
+ for (const entry of entries) {
943
+ const compCount = entry.length - 1;
944
+ if (compCount > gids.length - 1) continue;
945
+ let match = true;
946
+ for (let ci = 0; ci < compCount; ci++) {
947
+ if (gids[1 + ci] !== entry[1 + ci]) {
948
+ match = false;
949
+ break;
950
+ }
951
+ }
952
+ if (match) return { resultGid: entry[0], consumed: compCount + 1 };
953
+ }
954
+ return null;
955
+ }
956
+
957
+ // src/shaping/bengali-shaper.ts
958
+ var HALANT = 2509;
959
+ var NUKTA = 2492;
960
+ var RA = 2480;
961
+ function bengaliCharType(cp) {
962
+ if (cp === HALANT) return 7;
963
+ if (cp === NUKTA) return 8;
964
+ if (cp >= 2433 && cp <= 2435) return 6;
965
+ if (cp >= 2437 && cp <= 2452) return 1;
966
+ if (cp >= 2453 && cp <= 2489) return 0;
967
+ if (cp === 2495) return 4;
968
+ if (cp === 2503) return 4;
969
+ if (cp === 2504) return 4;
970
+ if (cp === 2497 || cp === 2498 || cp === 2499 || cp === 2500) return 3;
971
+ if (cp === 2494) return 5;
972
+ if (cp === 2496) return 5;
973
+ if (cp === 2507) return 4;
974
+ if (cp === 2508) return 4;
975
+ if (cp >= 2494 && cp <= 2508) return 2;
976
+ if (cp === 2519) return 5;
977
+ if (cp >= 2534 && cp <= 2543) return 9;
978
+ if (cp === 2527) return 0;
979
+ if (cp === 2493) return 1;
980
+ if (cp === 2510) return 0;
981
+ return -1;
982
+ }
983
+ function isConsonant(cp) {
984
+ return bengaliCharType(cp) === 0;
985
+ }
986
+ function buildBengaliClusters(str) {
987
+ const clusters = [];
988
+ const cps = [];
989
+ for (let i2 = 0; i2 < str.length; ) {
990
+ const cp = str.codePointAt(i2) ?? 0;
991
+ cps.push(cp);
992
+ i2 += cp > 65535 ? 2 : 1;
993
+ }
994
+ let i = 0;
995
+ while (i < cps.length) {
996
+ const cp = cps[i];
997
+ const type = bengaliCharType(cp);
998
+ if (type < 0 || cp < BENGALI_START || cp > BENGALI_END) {
999
+ clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
1000
+ i++;
1001
+ continue;
1002
+ }
1003
+ const syllable = [];
1004
+ let baseIdx = 0;
1005
+ let hasReph = false;
1006
+ const preMatras = [];
1007
+ if (isConsonant(cp) && cp === RA && i + 2 < cps.length && cps[i + 1] === HALANT && isConsonant(cps[i + 2])) {
1008
+ hasReph = true;
1009
+ syllable.push(cp, cps[i + 1]);
1010
+ i += 2;
1011
+ }
1012
+ let lastConsonantIdx = -1;
1013
+ while (i < cps.length) {
1014
+ const cc = cps[i];
1015
+ const ct = bengaliCharType(cc);
1016
+ if (ct === 0) {
1017
+ lastConsonantIdx = syllable.length;
1018
+ syllable.push(cc);
1019
+ i++;
1020
+ if (i < cps.length && cps[i] === NUKTA) {
1021
+ syllable.push(cps[i]);
1022
+ i++;
1023
+ }
1024
+ if (i < cps.length && cps[i] === HALANT) {
1025
+ if (i + 1 < cps.length && isConsonant(cps[i + 1])) {
1026
+ syllable.push(cps[i]);
1027
+ i++;
1028
+ continue;
1029
+ } else {
1030
+ syllable.push(cps[i]);
1031
+ i++;
1032
+ break;
1033
+ }
1034
+ }
1035
+ break;
1036
+ } else {
1037
+ break;
1038
+ }
1039
+ }
1040
+ baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
1041
+ while (i < cps.length) {
1042
+ const ct = bengaliCharType(cps[i]);
1043
+ if (ct >= 2 && ct <= 5) {
1044
+ if (ct === 4) {
1045
+ preMatras.push(syllable.length);
1046
+ }
1047
+ syllable.push(cps[i]);
1048
+ i++;
1049
+ } else {
1050
+ break;
1051
+ }
1052
+ }
1053
+ while (i < cps.length && bengaliCharType(cps[i]) === 6) {
1054
+ syllable.push(cps[i]);
1055
+ i++;
1056
+ }
1057
+ if (syllable.length === 0) {
1058
+ syllable.push(cps[i] ?? 32);
1059
+ i++;
1060
+ }
1061
+ clusters.push({
1062
+ codepoints: syllable,
1063
+ baseIndex: hasReph ? baseIdx + 2 : baseIdx,
1064
+ // Adjust for reph prefix
1065
+ hasReph,
1066
+ preBaseMatras: preMatras
1067
+ });
1068
+ }
1069
+ return clusters;
1070
+ }
1071
+ function shapeBengaliText(str, fontData) {
1072
+ const { cmap, gsub, ligatures, markAnchors, widths, defaultWidth } = fontData;
1073
+ const shaped = [];
1074
+ function resolveGid(cp) {
1075
+ const normCp = cp === 8239 || cp === 160 ? 32 : cp;
1076
+ return cmap[normCp] || 0;
1077
+ }
1078
+ function resolveGidGsub(cp) {
1079
+ const gid = resolveGid(cp);
1080
+ if (gsub[gid] !== void 0) return gsub[gid];
1081
+ return gid;
1082
+ }
1083
+ function tryLig(gids) {
1084
+ return tryLigature(gids, ligatures);
1085
+ }
1086
+ function getAdv(gid) {
1087
+ return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
1088
+ }
1089
+ function getBaseAnchor2(baseGid, markClass) {
1090
+ const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
1091
+ if (!base) return null;
1092
+ return base[markClass] ?? null;
1093
+ }
1094
+ function getMarkAnchor2(markGid) {
1095
+ const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
1096
+ if (!mark) return null;
1097
+ return { classIdx: mark[0], x: mark[1], y: mark[2] };
1098
+ }
1099
+ function emitGlyph(gid, isZero, baseGid) {
1100
+ if (isZero && baseGid !== void 0) {
1101
+ const markAnchor = getMarkAnchor2(gid);
1102
+ if (markAnchor) {
1103
+ const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
1104
+ if (baseAnchorPt) {
1105
+ const baseAdv = getAdv(baseGid);
1106
+ shaped.push({
1107
+ gid,
1108
+ dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
827
1109
  dy: baseAnchorPt[1] - markAnchor.y,
828
1110
  isZeroAdvance: true
829
1111
  });
@@ -835,12 +1117,13 @@ function shapeTamilText(str, fontData) {
835
1117
  shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
836
1118
  }
837
1119
  }
838
- const clusters = buildTamilClusters(str);
1120
+ const clusters = buildBengaliClusters(str);
839
1121
  for (const cluster of clusters) {
840
- const { codepoints, preBaseMatras } = cluster;
1122
+ const { codepoints, hasReph, preBaseMatras } = cluster;
1123
+ const baseStart = hasReph ? 2 : 0;
841
1124
  let baseGid = 0;
842
- for (let ci = 0; ci < codepoints.length; ci++) {
843
- const ct = tamilCharType(codepoints[ci]);
1125
+ for (let ci = baseStart; ci < codepoints.length; ci++) {
1126
+ const ct = bengaliCharType(codepoints[ci]);
844
1127
  if (ct === 0) {
845
1128
  baseGid = resolveGid(codepoints[ci]);
846
1129
  } else if (ct >= 2) {
@@ -851,15 +1134,12 @@ function shapeTamilText(str, fontData) {
851
1134
  for (const mIdx of preBaseMatras) {
852
1135
  if (mIdx < codepoints.length) {
853
1136
  const mCp = codepoints[mIdx];
854
- if (mCp === 3018) {
855
- emitGlyph(resolveGid(3014), false);
856
- splitPostComponents.push(3006);
857
- } else if (mCp === 3019) {
858
- emitGlyph(resolveGid(3015), false);
859
- splitPostComponents.push(3006);
860
- } else if (mCp === 3020) {
861
- emitGlyph(resolveGid(3014), false);
862
- splitPostComponents.push(3031);
1137
+ if (mCp === 2507) {
1138
+ emitGlyph(resolveGid(2503), false);
1139
+ splitPostComponents.push(2494);
1140
+ } else if (mCp === 2508) {
1141
+ emitGlyph(resolveGid(2503), false);
1142
+ splitPostComponents.push(2519);
863
1143
  } else {
864
1144
  emitGlyph(resolveGid(mCp), false);
865
1145
  }
@@ -868,22 +1148,20 @@ function shapeTamilText(str, fontData) {
868
1148
  const clusterGids = [];
869
1149
  const clusterEndIdx = [];
870
1150
  let matraStart = codepoints.length;
871
- for (let ci = 0; ci < codepoints.length; ci++) {
872
- const ct = tamilCharType(codepoints[ci]);
873
- if (ct === 0 || ct === 7) {
1151
+ for (let ci = baseStart; ci < codepoints.length; ci++) {
1152
+ const ct = bengaliCharType(codepoints[ci]);
1153
+ if (ct === 0 || ct === 7 || ct === 8) {
874
1154
  clusterGids.push(resolveGid(codepoints[ci]));
875
1155
  clusterEndIdx.push(ci);
876
- } else if (ct >= 2) {
1156
+ } else if (ct < 0 || ct === 1 || ct === 9) {
1157
+ emitGlyph(resolveGid(codepoints[ci]), false);
1158
+ } else {
877
1159
  matraStart = ci;
878
1160
  break;
879
- } else if (ct < 0) {
880
- emitGlyph(resolveGid(codepoints[ci]), false);
881
- } else if (ct === 1) {
882
- emitGlyph(resolveGid(codepoints[ci]), false);
883
1161
  }
884
1162
  }
885
1163
  let ligConsumed = 0;
886
- const ligResult = tryLigature(clusterGids);
1164
+ const ligResult = tryLig(clusterGids);
887
1165
  if (ligResult) {
888
1166
  emitGlyph(ligResult.resultGid, false);
889
1167
  baseGid = ligResult.resultGid;
@@ -891,13 +1169,13 @@ function shapeTamilText(str, fontData) {
891
1169
  let gi = ligConsumed;
892
1170
  while (gi < clusterGids.length) {
893
1171
  const subSeq = clusterGids.slice(gi);
894
- const subLig = tryLigature(subSeq);
1172
+ const subLig = tryLig(subSeq);
895
1173
  if (subLig) {
896
1174
  emitGlyph(subLig.resultGid, false);
897
1175
  gi += subLig.consumed;
898
1176
  } else {
899
1177
  const origCi = clusterEndIdx[gi];
900
- const ct = tamilCharType(codepoints[origCi]);
1178
+ const ct = bengaliCharType(codepoints[origCi]);
901
1179
  if (ct === 7) {
902
1180
  emitGlyph(clusterGids[gi], true, baseGid);
903
1181
  } else {
@@ -907,19 +1185,21 @@ function shapeTamilText(str, fontData) {
907
1185
  }
908
1186
  }
909
1187
  } else {
910
- for (let ci = 0; ci < matraStart; ci++) {
1188
+ for (let ci = baseStart; ci < matraStart; ci++) {
911
1189
  const cp = codepoints[ci];
912
- const ct = tamilCharType(cp);
1190
+ const ct = bengaliCharType(cp);
913
1191
  if (ct === 0) {
914
1192
  emitGlyph(resolveGid(cp), false);
915
1193
  } else if (ct === 7) {
916
1194
  emitGlyph(resolveGid(cp), true, baseGid);
1195
+ } else if (ct === 8) {
1196
+ emitGlyph(resolveGid(cp), true, baseGid);
917
1197
  }
918
1198
  }
919
1199
  }
920
1200
  for (let ci = matraStart; ci < codepoints.length; ci++) {
921
1201
  const cp = codepoints[ci];
922
- const ct = tamilCharType(cp);
1202
+ const ct = bengaliCharType(cp);
923
1203
  if (ct === 4) continue;
924
1204
  if (ct === 2 || ct === 3) {
925
1205
  emitGlyph(resolveGid(cp), true, baseGid);
@@ -936,36 +1216,44 @@ function shapeTamilText(str, fontData) {
936
1216
  for (const postCp of splitPostComponents) {
937
1217
  emitGlyph(resolveGid(postCp), false);
938
1218
  }
1219
+ if (hasReph) {
1220
+ const raGid = resolveGidGsub(RA);
1221
+ const halantGid = resolveGid(HALANT);
1222
+ emitGlyph(raGid, true, baseGid);
1223
+ emitGlyph(halantGid, true, baseGid);
1224
+ }
939
1225
  }
940
1226
  return shaped;
941
1227
  }
942
1228
 
943
- // src/shaping/devanagari-shaper.ts
944
- var HALANT2 = 2381;
945
- var NUKTA2 = 2364;
946
- var RA2 = 2352;
947
- function devanagariCharType(cp) {
948
- if (cp === HALANT2) return 7;
949
- if (cp === NUKTA2) return 8;
950
- if (cp >= 2305 && cp <= 2307) return 6;
951
- if (cp >= 2308 && cp <= 2324) return 1;
952
- if (cp >= 2325 && cp <= 2361) return 0;
953
- if (cp === 2367) return 4;
954
- if (cp >= 2369 && cp <= 2372) return 3;
955
- if (cp === 2366 || cp === 2368) return 5;
956
- if (cp === 2375 || cp === 2376) return 2;
957
- if (cp === 2379 || cp === 2380) return 4;
958
- if (cp >= 2373 && cp <= 2380) return 2;
959
- if (cp >= 2406 && cp <= 2415) return 9;
960
- if (cp >= 2392 && cp <= 2399) return 0;
961
- if (cp === 2365) return 1;
962
- if (cp === 2384) return 1;
1229
+ // src/shaping/tamil-shaper.ts
1230
+ var PULLI = 3021;
1231
+ function tamilCharType(cp) {
1232
+ if (cp === PULLI) return 7;
1233
+ if (cp === 2946 || cp === 2947) return 6;
1234
+ if (cp >= 2949 && cp <= 2964) return 1;
1235
+ if (cp >= 2965 && cp <= 3001) return 0;
1236
+ if (cp === 3007) return 4;
1237
+ if (cp === 3014) return 4;
1238
+ if (cp === 3015) return 4;
1239
+ if (cp === 3016) return 4;
1240
+ if (cp === 3018) return 4;
1241
+ if (cp === 3019) return 4;
1242
+ if (cp === 3020) return 4;
1243
+ if (cp === 3006) return 5;
1244
+ if (cp === 3008) return 5;
1245
+ if (cp === 3009 || cp === 3010) return 2;
1246
+ if (cp === 3031) return 5;
1247
+ if (cp >= 3006 && cp <= 3020) return 2;
1248
+ if (cp >= 3046 && cp <= 3055) return 9;
1249
+ if (cp >= 3056 && cp <= 3066) return 9;
1250
+ if (cp === 3024) return 6;
963
1251
  return -1;
964
1252
  }
965
- function isConsonant3(cp) {
966
- return devanagariCharType(cp) === 0;
1253
+ function isConsonant2(cp) {
1254
+ return tamilCharType(cp) === 0;
967
1255
  }
968
- function buildDevanagariClusters(str) {
1256
+ function buildTamilClusters(str) {
969
1257
  const clusters = [];
970
1258
  const cps = [];
971
1259
  for (let i2 = 0; i2 < str.length; ) {
@@ -976,34 +1264,24 @@ function buildDevanagariClusters(str) {
976
1264
  let i = 0;
977
1265
  while (i < cps.length) {
978
1266
  const cp = cps[i];
979
- const type = devanagariCharType(cp);
980
- if (type < 0 || cp < DEVANAGARI_START || cp > DEVANAGARI_END) {
981
- clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
1267
+ const type = tamilCharType(cp);
1268
+ if (type < 0 || cp < TAMIL_START || cp > TAMIL_END) {
1269
+ clusters.push({ codepoints: [cp], baseIndex: 0, preBaseMatras: [] });
982
1270
  i++;
983
1271
  continue;
984
1272
  }
985
1273
  const syllable = [];
986
- let hasReph = false;
987
- const preMatras = [];
988
- if (isConsonant3(cp) && cp === RA2 && i + 2 < cps.length && cps[i + 1] === HALANT2 && isConsonant3(cps[i + 2])) {
989
- hasReph = true;
990
- syllable.push(cp, cps[i + 1]);
991
- i += 2;
992
- }
993
1274
  let lastConsonantIdx = -1;
1275
+ const preMatras = [];
994
1276
  while (i < cps.length) {
995
1277
  const cc = cps[i];
996
- const ct = devanagariCharType(cc);
1278
+ const ct = tamilCharType(cc);
997
1279
  if (ct === 0) {
998
1280
  lastConsonantIdx = syllable.length;
999
1281
  syllable.push(cc);
1000
1282
  i++;
1001
- if (i < cps.length && cps[i] === NUKTA2) {
1002
- syllable.push(cps[i]);
1003
- i++;
1004
- }
1005
- if (i < cps.length && cps[i] === HALANT2) {
1006
- if (i + 1 < cps.length && isConsonant3(cps[i + 1])) {
1283
+ if (i < cps.length && cps[i] === PULLI) {
1284
+ if (i + 1 < cps.length && isConsonant2(cps[i + 1])) {
1007
1285
  syllable.push(cps[i]);
1008
1286
  i++;
1009
1287
  continue;
@@ -1020,7 +1298,7 @@ function buildDevanagariClusters(str) {
1020
1298
  }
1021
1299
  const baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
1022
1300
  while (i < cps.length) {
1023
- const ct = devanagariCharType(cps[i]);
1301
+ const ct = tamilCharType(cps[i]);
1024
1302
  if (ct >= 2 && ct <= 5) {
1025
1303
  if (ct === 4) {
1026
1304
  preMatras.push(syllable.length);
@@ -1031,7 +1309,7 @@ function buildDevanagariClusters(str) {
1031
1309
  break;
1032
1310
  }
1033
1311
  }
1034
- while (i < cps.length && devanagariCharType(cps[i]) === 6) {
1312
+ while (i < cps.length && tamilCharType(cps[i]) === 6) {
1035
1313
  syllable.push(cps[i]);
1036
1314
  i++;
1037
1315
  }
@@ -1041,57 +1319,40 @@ function buildDevanagariClusters(str) {
1041
1319
  }
1042
1320
  clusters.push({
1043
1321
  codepoints: syllable,
1044
- baseIndex: hasReph ? baseIdx + 2 : baseIdx,
1045
- hasReph,
1322
+ baseIndex: baseIdx,
1046
1323
  preBaseMatras: preMatras
1047
1324
  });
1048
1325
  }
1049
1326
  return clusters;
1050
1327
  }
1051
- function shapeDevanagariText(str, fontData) {
1052
- const { cmap, gsub, ligatures, markAnchors, widths, defaultWidth } = fontData;
1328
+ function shapeTamilText(str, fontData) {
1329
+ const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
1053
1330
  const shaped = [];
1054
1331
  function resolveGid(cp) {
1055
1332
  const normCp = cp === 8239 || cp === 160 ? 32 : cp;
1056
1333
  return cmap[normCp] || 0;
1057
1334
  }
1058
- function tryLigature(gids) {
1059
- if (!ligatures || gids.length < 2) return null;
1060
- const firstGid = gids[0];
1061
- const entries = ligatures[firstGid];
1062
- if (!entries) return null;
1063
- for (const entry of entries) {
1064
- const compCount = entry.length - 1;
1065
- if (compCount > gids.length - 1) continue;
1066
- let match = true;
1067
- for (let ci = 0; ci < compCount; ci++) {
1068
- if (gids[1 + ci] !== entry[1 + ci]) {
1069
- match = false;
1070
- break;
1071
- }
1072
- }
1073
- if (match) return { resultGid: entry[0], consumed: compCount + 1 };
1074
- }
1075
- return null;
1335
+ function tryLig(gids) {
1336
+ return tryLigature(gids, ligatures);
1076
1337
  }
1077
1338
  function getAdv(gid) {
1078
1339
  return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
1079
1340
  }
1080
- function getBaseAnchor(baseGid, markClass) {
1341
+ function getBaseAnchor2(baseGid, markClass) {
1081
1342
  const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
1082
1343
  if (!base) return null;
1083
1344
  return base[markClass] ?? null;
1084
1345
  }
1085
- function getMarkAnchor(markGid) {
1346
+ function getMarkAnchor2(markGid) {
1086
1347
  const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
1087
1348
  if (!mark) return null;
1088
1349
  return { classIdx: mark[0], x: mark[1], y: mark[2] };
1089
1350
  }
1090
1351
  function emitGlyph(gid, isZero, baseGid) {
1091
1352
  if (isZero && baseGid !== void 0) {
1092
- const markAnchor = getMarkAnchor(gid);
1353
+ const markAnchor = getMarkAnchor2(gid);
1093
1354
  if (markAnchor) {
1094
- const baseAnchorPt = getBaseAnchor(baseGid, markAnchor.classIdx);
1355
+ const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
1095
1356
  if (baseAnchorPt) {
1096
1357
  const baseAdv = getAdv(baseGid);
1097
1358
  shaped.push({
@@ -1108,566 +1369,560 @@ function shapeDevanagariText(str, fontData) {
1108
1369
  shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
1109
1370
  }
1110
1371
  }
1111
- const clusters = buildDevanagariClusters(str);
1372
+ const clusters = buildTamilClusters(str);
1112
1373
  for (const cluster of clusters) {
1113
- const { codepoints, hasReph, preBaseMatras } = cluster;
1114
- const baseStart = hasReph ? 2 : 0;
1374
+ const { codepoints, preBaseMatras } = cluster;
1115
1375
  let baseGid = 0;
1116
- for (let ci = baseStart; ci < codepoints.length; ci++) {
1117
- const ct = devanagariCharType(codepoints[ci]);
1376
+ for (let ci = 0; ci < codepoints.length; ci++) {
1377
+ const ct = tamilCharType(codepoints[ci]);
1118
1378
  if (ct === 0) {
1119
1379
  baseGid = resolveGid(codepoints[ci]);
1120
1380
  } else if (ct >= 2) {
1121
1381
  break;
1122
1382
  }
1123
1383
  }
1124
- const splitPostComponents = [];
1125
- for (const mIdx of preBaseMatras) {
1126
- if (mIdx < codepoints.length) {
1127
- const mCp = codepoints[mIdx];
1128
- if (mCp === 2379) {
1129
- emitGlyph(resolveGid(2375), false);
1130
- splitPostComponents.push(2366);
1131
- } else if (mCp === 2380) {
1132
- emitGlyph(resolveGid(2375), false);
1133
- splitPostComponents.push(2380);
1134
- } else {
1135
- emitGlyph(resolveGid(mCp), false);
1136
- }
1137
- }
1138
- }
1139
- if (hasReph) {
1140
- const raGid = resolveGid(RA2);
1141
- const halantGid = resolveGid(HALANT2);
1142
- const rephLig = tryLigature([raGid, halantGid]);
1143
- if (rephLig) {
1144
- emitGlyph(rephLig.resultGid, true, baseGid);
1145
- } else {
1146
- const raGsubbed = gsub[raGid] !== void 0 ? gsub[raGid] : raGid;
1147
- emitGlyph(raGsubbed, true, baseGid);
1148
- }
1149
- }
1150
- const clusterGids = [];
1151
- const clusterEndIdx = [];
1152
- let matraStart = codepoints.length;
1153
- for (let ci = baseStart; ci < codepoints.length; ci++) {
1154
- const ct = devanagariCharType(codepoints[ci]);
1155
- if (ct === 0 || ct === 7 || ct === 8) {
1156
- clusterGids.push(resolveGid(codepoints[ci]));
1157
- clusterEndIdx.push(ci);
1158
- } else if (ct < 0 || ct === 1 || ct === 9) {
1159
- emitGlyph(resolveGid(codepoints[ci]), false);
1160
- } else {
1161
- matraStart = ci;
1162
- break;
1163
- }
1164
- }
1165
- const ligResult = tryLigature(clusterGids);
1166
- if (ligResult) {
1167
- emitGlyph(ligResult.resultGid, false);
1168
- baseGid = ligResult.resultGid;
1169
- let gi = ligResult.consumed;
1170
- while (gi < clusterGids.length) {
1171
- const subSeq = clusterGids.slice(gi);
1172
- const subLig = tryLigature(subSeq);
1173
- if (subLig) {
1174
- emitGlyph(subLig.resultGid, false);
1175
- gi += subLig.consumed;
1176
- } else {
1177
- const origCi = clusterEndIdx[gi];
1178
- const ct = devanagariCharType(codepoints[origCi]);
1179
- if (ct === 7) {
1180
- emitGlyph(clusterGids[gi], true, baseGid);
1181
- } else {
1182
- emitGlyph(clusterGids[gi], false);
1183
- }
1184
- gi++;
1185
- }
1186
- }
1187
- } else {
1188
- for (let ci = baseStart; ci < matraStart; ci++) {
1189
- const cp = codepoints[ci];
1190
- const ct = devanagariCharType(cp);
1191
- if (ct === 0) {
1192
- emitGlyph(resolveGid(cp), false);
1193
- } else if (ct === 7) {
1194
- emitGlyph(resolveGid(cp), true, baseGid);
1195
- } else if (ct === 8) {
1196
- emitGlyph(resolveGid(cp), true, baseGid);
1197
- }
1198
- }
1199
- }
1200
- for (let ci = matraStart; ci < codepoints.length; ci++) {
1201
- const cp = codepoints[ci];
1202
- const ct = devanagariCharType(cp);
1203
- if (ct === 4) continue;
1204
- if (ct === 2 || ct === 3) {
1205
- emitGlyph(resolveGid(cp), true, baseGid);
1206
- } else if (ct === 5) {
1207
- emitGlyph(resolveGid(cp), false);
1208
- } else if (ct === 6) {
1209
- emitGlyph(resolveGid(cp), true, baseGid);
1210
- } else if (ct === 9) {
1211
- emitGlyph(resolveGid(cp), false);
1212
- } else {
1213
- emitGlyph(resolveGid(cp), false);
1214
- }
1215
- }
1216
- for (const postCp of splitPostComponents) {
1217
- emitGlyph(resolveGid(postCp), false);
1218
- }
1219
- }
1220
- return shaped;
1221
- }
1222
-
1223
- // src/shaping/arabic-shaper.ts
1224
- function getJoiningType(cp) {
1225
- if (cp >= 1611 && cp <= 1631 || // Harakat (vowel marks)
1226
- cp === 1648 || // Superscript alef
1227
- cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 1552 && cp <= 1562) return "T";
1228
- if (cp === 1600) return "C";
1229
- if (cp >= 1574 && cp <= 1576 || // YEH HAMZA, BA series
1230
- cp >= 1578 && cp <= 1582 || // TA through KHA
1231
- cp >= 1587 && cp <= 1594 || // SEEN through GHAIN
1232
- cp >= 1601 && cp <= 1607 || // FA through HA
1233
- cp === 1609 || // ALEF MAKSURA
1234
- cp === 1610 || // YA
1235
- cp === 1656 || // HIGH HAMZA YEH
1236
- cp >= 1690 && cp <= 1727 || // Extended Arabic
1237
- cp >= 1729 && cp <= 1731 || cp >= 1740 && cp <= 1742 || cp >= 1744 && cp <= 1747 || cp === 1749 || cp === 1786 || cp === 1787 || cp === 1788) return "D";
1238
- if (cp === 1570 || cp === 1571 || cp === 1572 || cp === 1573 || cp === 1575 || // ALEF
1239
- cp === 1577 || // TEH MARBUTA
1240
- cp === 1583 || cp === 1584 || // DAL, THAL
1241
- cp === 1585 || cp === 1586 || // RA, ZAIN
1242
- cp === 1608 || // WAW
1243
- cp >= 1649 && cp <= 1651 || cp === 1653 || cp === 1654 || cp === 1655 || cp >= 1672 && cp <= 1689 || // Extended DAL/RA series
1244
- cp === 1728 || cp >= 1732 && cp <= 1739 || cp === 1743 || cp === 1774 || cp === 1775) return "R";
1245
- if (cp >= ARABIC_START && cp <= ARABIC_END) return "U";
1246
- return "U";
1247
- }
1248
- var ARABIC_PRES_FORMS = /* @__PURE__ */ new Map([
1249
- [1569, { isol: 65152 }],
1250
- [1570, { isol: 65153, fina: 65154 }],
1251
- [1571, { isol: 65155, fina: 65156 }],
1252
- [1572, { isol: 65157, fina: 65158 }],
1253
- [1573, { isol: 65159, fina: 65160 }],
1254
- [1574, { isol: 65161, fina: 65162, init: 65163, medi: 65164 }],
1255
- [1575, { isol: 65165, fina: 65166 }],
1256
- [1576, { isol: 65167, fina: 65168, init: 65169, medi: 65170 }],
1257
- [1577, { isol: 65171, fina: 65172 }],
1258
- [1578, { isol: 65173, fina: 65174, init: 65175, medi: 65176 }],
1259
- [1579, { isol: 65177, fina: 65178, init: 65179, medi: 65180 }],
1260
- [1580, { isol: 65181, fina: 65182, init: 65183, medi: 65184 }],
1261
- [1581, { isol: 65185, fina: 65186, init: 65187, medi: 65188 }],
1262
- [1582, { isol: 65189, fina: 65190, init: 65191, medi: 65192 }],
1263
- [1583, { isol: 65193, fina: 65194 }],
1264
- [1584, { isol: 65195, fina: 65196 }],
1265
- [1585, { isol: 65197, fina: 65198 }],
1266
- [1586, { isol: 65199, fina: 65200 }],
1267
- [1587, { isol: 65201, fina: 65202, init: 65203, medi: 65204 }],
1268
- [1588, { isol: 65205, fina: 65206, init: 65207, medi: 65208 }],
1269
- [1589, { isol: 65209, fina: 65210, init: 65211, medi: 65212 }],
1270
- [1590, { isol: 65213, fina: 65214, init: 65215, medi: 65216 }],
1271
- [1591, { isol: 65217, fina: 65218, init: 65219, medi: 65220 }],
1272
- [1592, { isol: 65221, fina: 65222, init: 65223, medi: 65224 }],
1273
- [1593, { isol: 65225, fina: 65226, init: 65227, medi: 65228 }],
1274
- [1594, { isol: 65229, fina: 65230, init: 65231, medi: 65232 }],
1275
- [1601, { isol: 65233, fina: 65234, init: 65235, medi: 65236 }],
1276
- [1602, { isol: 65237, fina: 65238, init: 65239, medi: 65240 }],
1277
- [1603, { isol: 65241, fina: 65242, init: 65243, medi: 65244 }],
1278
- [1604, { isol: 65245, fina: 65246, init: 65247, medi: 65248 }],
1279
- [1605, { isol: 65249, fina: 65250, init: 65251, medi: 65252 }],
1280
- [1606, { isol: 65253, fina: 65254, init: 65255, medi: 65256 }],
1281
- [1607, { isol: 65257, fina: 65258, init: 65259, medi: 65260 }],
1282
- [1608, { isol: 65261, fina: 65262 }],
1283
- [1609, { isol: 65263, fina: 65264 }],
1284
- [1610, { isol: 65265, fina: 65266, init: 65267, medi: 65268 }]
1285
- ]);
1286
- var LAM_ALEF_PRES = /* @__PURE__ */ new Map([
1287
- [1570, [65269, 65270]],
1288
- // LAM + ALEF WITH MADDA ABOVE
1289
- [1571, [65271, 65272]],
1290
- // LAM + ALEF WITH HAMZA ABOVE
1291
- [1573, [65273, 65274]],
1292
- // LAM + ALEF WITH HAMZA BELOW
1293
- [1575, [65275, 65276]]
1294
- // LAM + ALEF
1295
- ]);
1296
- function resolvePositionalForms(codePoints) {
1297
- const len = codePoints.length;
1298
- const forms = new Array(len).fill("isol");
1299
- const joining = codePoints.map(getJoiningType);
1300
- for (let i = 0; i < len; i++) {
1301
- if (joining[i] === "T" || joining[i] === "U") continue;
1302
- let prevJoin = "U";
1303
- for (let j = i - 1; j >= 0; j--) {
1304
- if (joining[j] !== "T") {
1305
- prevJoin = joining[j];
1306
- break;
1384
+ const splitPostComponents = [];
1385
+ for (const mIdx of preBaseMatras) {
1386
+ if (mIdx < codepoints.length) {
1387
+ const mCp = codepoints[mIdx];
1388
+ if (mCp === 3018) {
1389
+ emitGlyph(resolveGid(3014), false);
1390
+ splitPostComponents.push(3006);
1391
+ } else if (mCp === 3019) {
1392
+ emitGlyph(resolveGid(3015), false);
1393
+ splitPostComponents.push(3006);
1394
+ } else if (mCp === 3020) {
1395
+ emitGlyph(resolveGid(3014), false);
1396
+ splitPostComponents.push(3031);
1397
+ } else {
1398
+ emitGlyph(resolveGid(mCp), false);
1399
+ }
1307
1400
  }
1308
1401
  }
1309
- let nextJoin = "U";
1310
- for (let j = i + 1; j < len; j++) {
1311
- if (joining[j] !== "T") {
1312
- nextJoin = joining[j];
1402
+ const clusterGids = [];
1403
+ const clusterEndIdx = [];
1404
+ let matraStart = codepoints.length;
1405
+ for (let ci = 0; ci < codepoints.length; ci++) {
1406
+ const ct = tamilCharType(codepoints[ci]);
1407
+ if (ct === 0 || ct === 7) {
1408
+ clusterGids.push(resolveGid(codepoints[ci]));
1409
+ clusterEndIdx.push(ci);
1410
+ } else if (ct >= 2) {
1411
+ matraStart = ci;
1313
1412
  break;
1413
+ } else if (ct < 0) {
1414
+ emitGlyph(resolveGid(codepoints[ci]), false);
1415
+ } else if (ct === 1) {
1416
+ emitGlyph(resolveGid(codepoints[ci]), false);
1314
1417
  }
1315
1418
  }
1316
- const joinsToPrev = prevJoin === "D" || prevJoin === "C";
1317
- const joinsToNext = (nextJoin === "D" || nextJoin === "R" || nextJoin === "C") && (joining[i] === "D" || joining[i] === "C");
1318
- if (joinsToPrev && joinsToNext) {
1319
- forms[i] = "medi";
1320
- } else if (joinsToPrev) {
1321
- forms[i] = "fina";
1322
- } else if (joinsToNext) {
1323
- forms[i] = "init";
1419
+ let ligConsumed = 0;
1420
+ const ligResult = tryLig(clusterGids);
1421
+ if (ligResult) {
1422
+ emitGlyph(ligResult.resultGid, false);
1423
+ baseGid = ligResult.resultGid;
1424
+ ligConsumed = ligResult.consumed;
1425
+ let gi = ligConsumed;
1426
+ while (gi < clusterGids.length) {
1427
+ const subSeq = clusterGids.slice(gi);
1428
+ const subLig = tryLig(subSeq);
1429
+ if (subLig) {
1430
+ emitGlyph(subLig.resultGid, false);
1431
+ gi += subLig.consumed;
1432
+ } else {
1433
+ const origCi = clusterEndIdx[gi];
1434
+ const ct = tamilCharType(codepoints[origCi]);
1435
+ if (ct === 7) {
1436
+ emitGlyph(clusterGids[gi], true, baseGid);
1437
+ } else {
1438
+ emitGlyph(clusterGids[gi], false);
1439
+ }
1440
+ gi++;
1441
+ }
1442
+ }
1324
1443
  } else {
1325
- forms[i] = "isol";
1326
- }
1327
- }
1328
- return forms;
1329
- }
1330
- var LAM = 1604;
1331
- var ALEF_VARIANTS = /* @__PURE__ */ new Set([1570, 1571, 1573, 1575]);
1332
- function isLamAlef(cp1, cp2) {
1333
- return cp1 === LAM && ALEF_VARIANTS.has(cp2);
1334
- }
1335
- function shapeArabicText(str, fontData) {
1336
- if (!str) return [];
1337
- const codePoints = [];
1338
- for (let i = 0; i < str.length; ) {
1339
- const cp = str.codePointAt(i) ?? 0;
1340
- codePoints.push(cp);
1341
- i += cp > 65535 ? 2 : 1;
1342
- }
1343
- const forms = resolvePositionalForms(codePoints);
1344
- const glyphs = [];
1345
- const cmap = fontData.cmap;
1346
- for (let i = 0; i < codePoints.length; i++) {
1347
- const cp = codePoints[i];
1348
- if (i < codePoints.length - 1 && isLamAlef(cp, codePoints[i + 1])) {
1349
- const ligForms = LAM_ALEF_PRES.get(codePoints[i + 1]);
1350
- if (ligForms) {
1351
- const isFinal = forms[i] === "medi" || forms[i] === "fina";
1352
- const ligCP = isFinal ? ligForms[1] : ligForms[0];
1353
- const ligGid = cmap[ligCP];
1354
- if (ligGid) {
1355
- glyphs.push({ gid: ligGid, dx: 0, dy: 0, isZeroAdvance: false });
1356
- i++;
1357
- continue;
1444
+ for (let ci = 0; ci < matraStart; ci++) {
1445
+ const cp = codepoints[ci];
1446
+ const ct = tamilCharType(cp);
1447
+ if (ct === 0) {
1448
+ emitGlyph(resolveGid(cp), false);
1449
+ } else if (ct === 7) {
1450
+ emitGlyph(resolveGid(cp), true, baseGid);
1358
1451
  }
1359
1452
  }
1360
1453
  }
1361
- let gid = cmap[cp] ?? 0;
1362
- const presForm = ARABIC_PRES_FORMS.get(cp);
1363
- if (presForm) {
1364
- const form = forms[i];
1365
- let presCP;
1366
- if (form === "init") presCP = presForm.init;
1367
- else if (form === "medi") presCP = presForm.medi;
1368
- else if (form === "fina") presCP = presForm.fina;
1369
- else presCP = presForm.isol;
1370
- if (presCP) {
1371
- const presGid = cmap[presCP];
1372
- if (presGid) gid = presGid;
1454
+ for (let ci = matraStart; ci < codepoints.length; ci++) {
1455
+ const cp = codepoints[ci];
1456
+ const ct = tamilCharType(cp);
1457
+ if (ct === 4) continue;
1458
+ if (ct === 2 || ct === 3) {
1459
+ emitGlyph(resolveGid(cp), true, baseGid);
1460
+ } else if (ct === 5) {
1461
+ emitGlyph(resolveGid(cp), false);
1462
+ } else if (ct === 6) {
1463
+ emitGlyph(resolveGid(cp), true, baseGid);
1464
+ } else if (ct === 9) {
1465
+ emitGlyph(resolveGid(cp), false);
1466
+ } else {
1467
+ emitGlyph(resolveGid(cp), false);
1373
1468
  }
1374
1469
  }
1375
- const joining = getJoiningType(cp);
1376
- const isZeroAdvance = joining === "T";
1377
- glyphs.push({ gid, dx: 0, dy: 0, isZeroAdvance });
1470
+ for (const postCp of splitPostComponents) {
1471
+ emitGlyph(resolveGid(postCp), false);
1472
+ }
1378
1473
  }
1379
- return glyphs;
1474
+ return shaped;
1380
1475
  }
1381
1476
 
1382
- // src/shaping/bidi.ts
1383
- function classifyBidiType(cp) {
1384
- if (cp >= 768 && cp <= 879 || // Combining Diacritical Marks
1385
- cp >= 1425 && cp <= 1469 || // Hebrew marks
1386
- cp >= 1471 && cp <= 1471 || cp >= 1473 && cp <= 1474 || cp >= 1476 && cp <= 1477 || cp >= 1479 && cp <= 1479 || cp >= 1552 && cp <= 1562 || // Arabic marks
1387
- cp >= 1611 && cp <= 1631 || // Arabic harakat
1388
- cp >= 1648 && cp <= 1648 || // Arabic superscript alef
1389
- cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 65056 && cp <= 65071) return "NSM";
1390
- if (cp === 8203 || cp === 8204 || cp === 8205 || cp === 8206 || cp === 8207 || cp === 65279) return "BN";
1391
- if (cp >= 1632 && cp <= 1641) return "AN";
1392
- if (cp >= 1776 && cp <= 1785) return "AN";
1393
- if (cp >= 48 && cp <= 57) return "EN";
1394
- if (cp === 43 || cp === 45) return "ES";
1395
- if (cp === 35 || cp === 36 || cp === 37 || cp === 162 || cp === 163 || cp === 164 || cp === 165 || cp === 8364 || cp === 8377 || cp === 8378) return "ET";
1396
- if (cp === 44 || cp === 46 || cp === 47 || cp === 58 || cp === 160) return "CS";
1397
- if (cp === 32 || cp === 9 || cp === 10 || cp === 13 || cp === 12 || cp === 8192 || cp === 8202 || cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287 || cp === 12288) return "WS";
1398
- if (cp >= 1536 && cp <= 1791) return "AL";
1399
- if (cp >= 1872 && cp <= 1919) return "AL";
1400
- if (cp >= 2208 && cp <= 2303) return "AL";
1401
- if (cp >= 64336 && cp <= 65023) return "AL";
1402
- if (cp >= 65136 && cp <= 65278) return "AL";
1403
- if (cp >= 1488 && cp <= 1514) return "R";
1404
- if (cp >= 1520 && cp <= 1524) return "R";
1405
- if (cp >= 1792 && cp <= 1871) return "R";
1406
- if (cp >= 1920 && cp <= 1983) return "R";
1407
- if (cp >= 64285 && cp <= 64335) return "R";
1408
- if (cp >= 65 && cp <= 90) return "L";
1409
- if (cp >= 97 && cp <= 122) return "L";
1410
- if (cp >= 192 && cp <= 591) return "L";
1411
- if (cp >= 880 && cp <= 1023) return "L";
1412
- if (cp >= 1024 && cp <= 1279) return "L";
1413
- if (cp >= 3584 && cp <= 3711) return "L";
1414
- if (cp >= 2304 && cp <= 2431) return "L";
1415
- if (cp >= 12352 && cp <= 12543) return "L";
1416
- if (cp >= 19968 && cp <= 40959) return "L";
1417
- if (cp >= 44032 && cp <= 55215) return "L";
1418
- if (cp >= 33 && cp <= 47) return "ON";
1419
- if (cp >= 58 && cp <= 64) return "ON";
1420
- if (cp >= 91 && cp <= 96) return "ON";
1421
- if (cp >= 123 && cp <= 126) return "ON";
1422
- if (cp >= 161 && cp <= 191) return "ON";
1423
- if (cp >= 8208 && cp <= 8231) return "ON";
1424
- if (cp >= 8240 && cp <= 8286) return "ON";
1425
- return "L";
1477
+ // src/shaping/gpos-positioner.ts
1478
+ function getBaseAnchor(markAnchors, baseGid, markClass) {
1479
+ if (!markAnchors) return null;
1480
+ const base = markAnchors.bases[baseGid];
1481
+ if (!base) return null;
1482
+ return base[markClass] ?? null;
1483
+ }
1484
+ function getMarkAnchor(markAnchors, markGid) {
1485
+ if (!markAnchors) return null;
1486
+ const mark = markAnchors.marks[markGid];
1487
+ if (!mark) return null;
1488
+ return { classIdx: mark[0], x: mark[1], y: mark[2] };
1489
+ }
1490
+ function positionMarkOnBase(markAnchors, markGid, baseGid, baseAdv) {
1491
+ const mark = getMarkAnchor(markAnchors, markGid);
1492
+ if (!mark) return null;
1493
+ const base = getBaseAnchor(markAnchors, baseGid, mark.classIdx);
1494
+ if (!base) return null;
1495
+ return {
1496
+ dx: base[0] - mark.x - baseAdv,
1497
+ dy: base[1] - mark.y
1498
+ };
1426
1499
  }
1427
- function detectParagraphLevel(types) {
1428
- for (const t of types) {
1429
- if (t === "L") return 0;
1430
- if (t === "R" || t === "AL") return 1;
1431
- }
1432
- return 0;
1500
+
1501
+ // src/shaping/devanagari-shaper.ts
1502
+ var HALANT2 = 2381;
1503
+ var NUKTA2 = 2364;
1504
+ var RA2 = 2352;
1505
+ function devanagariCharType(cp) {
1506
+ if (cp === HALANT2) return 7;
1507
+ if (cp === NUKTA2) return 8;
1508
+ if (cp >= 2305 && cp <= 2307) return 6;
1509
+ if (cp >= 2308 && cp <= 2324) return 1;
1510
+ if (cp >= 2325 && cp <= 2361) return 0;
1511
+ if (cp === 2367) return 4;
1512
+ if (cp >= 2369 && cp <= 2372) return 3;
1513
+ if (cp === 2366 || cp === 2368) return 5;
1514
+ if (cp === 2375 || cp === 2376) return 2;
1515
+ if (cp === 2379 || cp === 2380) return 4;
1516
+ if (cp >= 2373 && cp <= 2380) return 2;
1517
+ if (cp >= 2406 && cp <= 2415) return 9;
1518
+ if (cp >= 2392 && cp <= 2399) return 0;
1519
+ if (cp === 2365) return 1;
1520
+ if (cp === 2384) return 1;
1521
+ return -1;
1433
1522
  }
1434
- function resolveWeakTypes(types, paraLevel) {
1435
- const len = types.length;
1436
- let prevType = paraLevel === 0 ? "L" : "R";
1437
- for (let i = 0; i < len; i++) {
1438
- if (types[i] === "BN") continue;
1439
- if (types[i] === "NSM") {
1440
- types[i] = prevType;
1441
- }
1442
- prevType = types[i];
1523
+ function isConsonant3(cp) {
1524
+ return devanagariCharType(cp) === 0;
1525
+ }
1526
+ function buildDevanagariClusters(str) {
1527
+ const clusters = [];
1528
+ const cps = [];
1529
+ for (let i2 = 0; i2 < str.length; ) {
1530
+ const cp = str.codePointAt(i2) ?? 0;
1531
+ cps.push(cp);
1532
+ i2 += cp > 65535 ? 2 : 1;
1443
1533
  }
1444
- let lastStrong = paraLevel === 0 ? "L" : "R";
1445
- for (let i = 0; i < len; i++) {
1446
- if (types[i] === "BN") continue;
1447
- if (types[i] === "R" || types[i] === "L" || types[i] === "AL") {
1448
- lastStrong = types[i];
1449
- } else if (types[i] === "EN" && lastStrong === "AL") {
1450
- types[i] = "AN";
1534
+ let i = 0;
1535
+ while (i < cps.length) {
1536
+ const cp = cps[i];
1537
+ const type = devanagariCharType(cp);
1538
+ if (type < 0 || cp < DEVANAGARI_START || cp > DEVANAGARI_END) {
1539
+ clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
1540
+ i++;
1541
+ continue;
1451
1542
  }
1452
- }
1453
- for (let i = 0; i < len; i++) {
1454
- if (types[i] === "AL") types[i] = "R";
1455
- }
1456
- for (let i = 1; i < len - 1; i++) {
1457
- if (types[i] === "BN") continue;
1458
- if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") {
1459
- types[i] = "EN";
1460
- } else if (types[i] === "CS") {
1461
- if (types[i - 1] === "EN" && types[i + 1] === "EN") types[i] = "EN";
1462
- else if (types[i - 1] === "AN" && types[i + 1] === "AN") types[i] = "AN";
1543
+ const syllable = [];
1544
+ let hasReph = false;
1545
+ const preMatras = [];
1546
+ if (isConsonant3(cp) && cp === RA2 && i + 2 < cps.length && cps[i + 1] === HALANT2 && isConsonant3(cps[i + 2])) {
1547
+ hasReph = true;
1548
+ syllable.push(cp, cps[i + 1]);
1549
+ i += 2;
1463
1550
  }
1464
- }
1465
- for (let i = 0; i < len; i++) {
1466
- if (types[i] === "ET") {
1467
- let found = false;
1468
- for (let j = i - 1; j >= 0; j--) {
1469
- if (types[j] === "EN") {
1470
- found = true;
1471
- break;
1551
+ let lastConsonantIdx = -1;
1552
+ while (i < cps.length) {
1553
+ const cc = cps[i];
1554
+ const ct = devanagariCharType(cc);
1555
+ if (ct === 0) {
1556
+ lastConsonantIdx = syllable.length;
1557
+ syllable.push(cc);
1558
+ i++;
1559
+ if (i < cps.length && cps[i] === NUKTA2) {
1560
+ syllable.push(cps[i]);
1561
+ i++;
1472
1562
  }
1473
- if (types[j] !== "ET" && types[j] !== "BN") break;
1474
- }
1475
- if (!found) {
1476
- for (let j = i + 1; j < len; j++) {
1477
- if (types[j] === "EN") {
1478
- found = true;
1563
+ if (i < cps.length && cps[i] === HALANT2) {
1564
+ if (i + 1 < cps.length && isConsonant3(cps[i + 1])) {
1565
+ syllable.push(cps[i]);
1566
+ i++;
1567
+ continue;
1568
+ } else {
1569
+ syllable.push(cps[i]);
1570
+ i++;
1479
1571
  break;
1480
1572
  }
1481
- if (types[j] !== "ET" && types[j] !== "BN") break;
1482
1573
  }
1574
+ break;
1575
+ } else {
1576
+ break;
1483
1577
  }
1484
- if (found) types[i] = "EN";
1485
1578
  }
1486
- }
1487
- for (let i = 0; i < len; i++) {
1488
- if (types[i] === "ES" || types[i] === "ET" || types[i] === "CS") {
1489
- types[i] = "ON";
1579
+ const baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
1580
+ while (i < cps.length) {
1581
+ const ct = devanagariCharType(cps[i]);
1582
+ if (ct >= 2 && ct <= 5) {
1583
+ if (ct === 4) {
1584
+ preMatras.push(syllable.length);
1585
+ }
1586
+ syllable.push(cps[i]);
1587
+ i++;
1588
+ } else {
1589
+ break;
1590
+ }
1490
1591
  }
1491
- }
1492
- lastStrong = paraLevel === 0 ? "L" : "R";
1493
- for (let i = 0; i < len; i++) {
1494
- if (types[i] === "BN") continue;
1495
- if (types[i] === "L" || types[i] === "R") {
1496
- lastStrong = types[i];
1497
- } else if (types[i] === "EN" && lastStrong === "L") {
1498
- types[i] = "L";
1592
+ while (i < cps.length && devanagariCharType(cps[i]) === 6) {
1593
+ syllable.push(cps[i]);
1594
+ i++;
1499
1595
  }
1596
+ if (syllable.length === 0) {
1597
+ syllable.push(cps[i] ?? 32);
1598
+ i++;
1599
+ }
1600
+ clusters.push({
1601
+ codepoints: syllable,
1602
+ baseIndex: hasReph ? baseIdx + 2 : baseIdx,
1603
+ hasReph,
1604
+ preBaseMatras: preMatras
1605
+ });
1500
1606
  }
1607
+ return clusters;
1501
1608
  }
1502
- function resolveNeutralTypes(types, paraLevel) {
1503
- const len = types.length;
1504
- const paraDir = paraLevel === 0 ? "L" : "R";
1505
- for (let i = 0; i < len; i++) {
1506
- if (types[i] !== "ON" && types[i] !== "WS" && types[i] !== "BN") continue;
1507
- const start = i;
1508
- while (i < len && (types[i] === "ON" || types[i] === "WS" || types[i] === "BN")) i++;
1509
- const end = i;
1510
- let prevStrong = paraDir;
1511
- for (let j = start - 1; j >= 0; j--) {
1512
- if (types[j] === "L" || types[j] === "R" || types[j] === "EN" || types[j] === "AN") {
1513
- prevStrong = types[j] === "EN" || types[j] === "AN" ? "R" : types[j];
1514
- break;
1609
+ function shapeDevanagariText(str, fontData) {
1610
+ const { cmap, gsub, ligatures, markAnchors, widths, defaultWidth } = fontData;
1611
+ const shaped = [];
1612
+ function resolveGid(cp) {
1613
+ const normCp = cp === 8239 || cp === 160 ? 32 : cp;
1614
+ return cmap[normCp] || 0;
1615
+ }
1616
+ function tryLig(gids) {
1617
+ return tryLigature(gids, ligatures);
1618
+ }
1619
+ function getAdv(gid) {
1620
+ return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
1621
+ }
1622
+ function emitGlyph(gid, isZero, baseGid) {
1623
+ if (isZero && baseGid !== void 0) {
1624
+ const markAnchor = getMarkAnchor(markAnchors, gid);
1625
+ if (markAnchor) {
1626
+ const baseAnchorPt = getBaseAnchor(markAnchors, baseGid, markAnchor.classIdx);
1627
+ if (baseAnchorPt) {
1628
+ const baseAdv = getAdv(baseGid);
1629
+ shaped.push({
1630
+ gid,
1631
+ dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
1632
+ dy: baseAnchorPt[1] - markAnchor.y,
1633
+ isZeroAdvance: true
1634
+ });
1635
+ return;
1636
+ }
1515
1637
  }
1638
+ shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
1639
+ } else {
1640
+ shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
1516
1641
  }
1517
- let nextStrong = paraDir;
1518
- for (let j = end; j < len; j++) {
1519
- if (types[j] === "L" || types[j] === "R" || types[j] === "EN" || types[j] === "AN") {
1520
- nextStrong = types[j] === "EN" || types[j] === "AN" ? "R" : types[j];
1642
+ }
1643
+ const clusters = buildDevanagariClusters(str);
1644
+ for (const cluster of clusters) {
1645
+ const { codepoints, hasReph, preBaseMatras } = cluster;
1646
+ const baseStart = hasReph ? 2 : 0;
1647
+ let baseGid = 0;
1648
+ for (let ci = baseStart; ci < codepoints.length; ci++) {
1649
+ const ct = devanagariCharType(codepoints[ci]);
1650
+ if (ct === 0) {
1651
+ baseGid = resolveGid(codepoints[ci]);
1652
+ } else if (ct >= 2) {
1521
1653
  break;
1522
1654
  }
1523
1655
  }
1524
- const resolved = prevStrong === nextStrong ? prevStrong : paraDir;
1525
- for (let j = start; j < end; j++) {
1526
- types[j] = resolved;
1656
+ const splitPostComponents = [];
1657
+ for (const mIdx of preBaseMatras) {
1658
+ if (mIdx < codepoints.length) {
1659
+ const mCp = codepoints[mIdx];
1660
+ if (mCp === 2379) {
1661
+ emitGlyph(resolveGid(2375), false);
1662
+ splitPostComponents.push(2366);
1663
+ } else if (mCp === 2380) {
1664
+ emitGlyph(resolveGid(2375), false);
1665
+ splitPostComponents.push(2380);
1666
+ } else {
1667
+ emitGlyph(resolveGid(mCp), false);
1668
+ }
1669
+ }
1527
1670
  }
1528
- }
1529
- }
1530
- function assignLevels(types, paraLevel) {
1531
- const levels = [];
1532
- for (const t of types) {
1533
- if (paraLevel === 0) {
1534
- levels.push(t === "R" || t === "AN" ? 1 : 0);
1671
+ if (hasReph) {
1672
+ const raGid = resolveGid(RA2);
1673
+ const halantGid = resolveGid(HALANT2);
1674
+ const rephLig = tryLig([raGid, halantGid]);
1675
+ if (rephLig) {
1676
+ emitGlyph(rephLig.resultGid, true, baseGid);
1677
+ } else {
1678
+ const raGsubbed = gsub[raGid] !== void 0 ? gsub[raGid] : raGid;
1679
+ emitGlyph(raGsubbed, true, baseGid);
1680
+ }
1681
+ }
1682
+ const clusterGids = [];
1683
+ const clusterEndIdx = [];
1684
+ let matraStart = codepoints.length;
1685
+ for (let ci = baseStart; ci < codepoints.length; ci++) {
1686
+ const ct = devanagariCharType(codepoints[ci]);
1687
+ if (ct === 0 || ct === 7 || ct === 8) {
1688
+ clusterGids.push(resolveGid(codepoints[ci]));
1689
+ clusterEndIdx.push(ci);
1690
+ } else if (ct < 0 || ct === 1 || ct === 9) {
1691
+ emitGlyph(resolveGid(codepoints[ci]), false);
1692
+ } else {
1693
+ matraStart = ci;
1694
+ break;
1695
+ }
1696
+ }
1697
+ const ligResult = tryLig(clusterGids);
1698
+ if (ligResult) {
1699
+ emitGlyph(ligResult.resultGid, false);
1700
+ baseGid = ligResult.resultGid;
1701
+ let gi = ligResult.consumed;
1702
+ while (gi < clusterGids.length) {
1703
+ const subSeq = clusterGids.slice(gi);
1704
+ const subLig = tryLig(subSeq);
1705
+ if (subLig) {
1706
+ emitGlyph(subLig.resultGid, false);
1707
+ gi += subLig.consumed;
1708
+ } else {
1709
+ const origCi = clusterEndIdx[gi];
1710
+ const ct = devanagariCharType(codepoints[origCi]);
1711
+ if (ct === 7) {
1712
+ emitGlyph(clusterGids[gi], true, baseGid);
1713
+ } else {
1714
+ emitGlyph(clusterGids[gi], false);
1715
+ }
1716
+ gi++;
1717
+ }
1718
+ }
1535
1719
  } else {
1536
- levels.push(t === "L" ? 2 : 1);
1720
+ for (let ci = baseStart; ci < matraStart; ci++) {
1721
+ const cp = codepoints[ci];
1722
+ const ct = devanagariCharType(cp);
1723
+ if (ct === 0) {
1724
+ emitGlyph(resolveGid(cp), false);
1725
+ } else if (ct === 7) {
1726
+ emitGlyph(resolveGid(cp), true, baseGid);
1727
+ } else if (ct === 8) {
1728
+ emitGlyph(resolveGid(cp), true, baseGid);
1729
+ }
1730
+ }
1537
1731
  }
1538
- }
1539
- return levels;
1540
- }
1541
- var SENTENCE_PUNCT = /* @__PURE__ */ new Set([
1542
- 46,
1543
- // .
1544
- 44,
1545
- // ,
1546
- 59,
1547
- // ;
1548
- 58,
1549
- // :
1550
- 33,
1551
- // !
1552
- 63
1553
- // ?
1554
- ]);
1555
- function fixPunctuationAffinity(types, codePoints, len) {
1556
- for (let i = 1; i < len; i++) {
1557
- if (types[i] === "R" && SENTENCE_PUNCT.has(codePoints[i])) {
1558
- let prevIdx = i - 1;
1559
- while (prevIdx >= 0 && (types[prevIdx] === "WS" || types[prevIdx] === "BN")) prevIdx--;
1560
- if (prevIdx >= 0 && types[prevIdx] === "L") {
1561
- types[i] = "L";
1732
+ for (let ci = matraStart; ci < codepoints.length; ci++) {
1733
+ const cp = codepoints[ci];
1734
+ const ct = devanagariCharType(cp);
1735
+ if (ct === 4) continue;
1736
+ if (ct === 2 || ct === 3) {
1737
+ emitGlyph(resolveGid(cp), true, baseGid);
1738
+ } else if (ct === 5) {
1739
+ emitGlyph(resolveGid(cp), false);
1740
+ } else if (ct === 6) {
1741
+ emitGlyph(resolveGid(cp), true, baseGid);
1742
+ } else if (ct === 9) {
1743
+ emitGlyph(resolveGid(cp), false);
1744
+ } else {
1745
+ emitGlyph(resolveGid(cp), false);
1562
1746
  }
1563
1747
  }
1748
+ for (const postCp of splitPostComponents) {
1749
+ emitGlyph(resolveGid(postCp), false);
1750
+ }
1564
1751
  }
1752
+ return shaped;
1565
1753
  }
1566
- function fixBracketPairing(types, codePoints, len) {
1567
- const OPEN_BRACKETS = {
1568
- 40: 41,
1569
- // ( )
1570
- 91: 93,
1571
- // [ ]
1572
- 123: 125
1573
- // { }
1574
- };
1754
+
1755
+ // src/shaping/arabic-shaper.ts
1756
+ function getJoiningType(cp) {
1757
+ if (cp >= 1611 && cp <= 1631 || // Harakat (vowel marks)
1758
+ cp === 1648 || // Superscript alef
1759
+ cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 1552 && cp <= 1562) return "T";
1760
+ if (cp === 1600) return "C";
1761
+ if (cp >= 1574 && cp <= 1576 || // YEH HAMZA, BA series
1762
+ cp >= 1578 && cp <= 1582 || // TA through KHA
1763
+ cp >= 1587 && cp <= 1594 || // SEEN through GHAIN
1764
+ cp >= 1601 && cp <= 1607 || // FA through HA
1765
+ cp === 1609 || // ALEF MAKSURA
1766
+ cp === 1610 || // YA
1767
+ cp === 1656 || // HIGH HAMZA YEH
1768
+ cp >= 1690 && cp <= 1727 || // Extended Arabic
1769
+ cp >= 1729 && cp <= 1731 || cp >= 1740 && cp <= 1742 || cp >= 1744 && cp <= 1747 || cp === 1749 || cp === 1786 || cp === 1787 || cp === 1788) return "D";
1770
+ if (cp === 1570 || cp === 1571 || cp === 1572 || cp === 1573 || cp === 1575 || // ALEF
1771
+ cp === 1577 || // TEH MARBUTA
1772
+ cp === 1583 || cp === 1584 || // DAL, THAL
1773
+ cp === 1585 || cp === 1586 || // RA, ZAIN
1774
+ cp === 1608 || // WAW
1775
+ cp >= 1649 && cp <= 1651 || cp === 1653 || cp === 1654 || cp === 1655 || cp >= 1672 && cp <= 1689 || // Extended DAL/RA series
1776
+ cp === 1728 || cp >= 1732 && cp <= 1739 || cp === 1743 || cp === 1774 || cp === 1775) return "R";
1777
+ if (cp >= ARABIC_START && cp <= ARABIC_END) return "U";
1778
+ return "U";
1779
+ }
1780
+ var ARABIC_PRES_FORMS = /* @__PURE__ */ new Map([
1781
+ [1569, { isol: 65152 }],
1782
+ [1570, { isol: 65153, fina: 65154 }],
1783
+ [1571, { isol: 65155, fina: 65156 }],
1784
+ [1572, { isol: 65157, fina: 65158 }],
1785
+ [1573, { isol: 65159, fina: 65160 }],
1786
+ [1574, { isol: 65161, fina: 65162, init: 65163, medi: 65164 }],
1787
+ [1575, { isol: 65165, fina: 65166 }],
1788
+ [1576, { isol: 65167, fina: 65168, init: 65169, medi: 65170 }],
1789
+ [1577, { isol: 65171, fina: 65172 }],
1790
+ [1578, { isol: 65173, fina: 65174, init: 65175, medi: 65176 }],
1791
+ [1579, { isol: 65177, fina: 65178, init: 65179, medi: 65180 }],
1792
+ [1580, { isol: 65181, fina: 65182, init: 65183, medi: 65184 }],
1793
+ [1581, { isol: 65185, fina: 65186, init: 65187, medi: 65188 }],
1794
+ [1582, { isol: 65189, fina: 65190, init: 65191, medi: 65192 }],
1795
+ [1583, { isol: 65193, fina: 65194 }],
1796
+ [1584, { isol: 65195, fina: 65196 }],
1797
+ [1585, { isol: 65197, fina: 65198 }],
1798
+ [1586, { isol: 65199, fina: 65200 }],
1799
+ [1587, { isol: 65201, fina: 65202, init: 65203, medi: 65204 }],
1800
+ [1588, { isol: 65205, fina: 65206, init: 65207, medi: 65208 }],
1801
+ [1589, { isol: 65209, fina: 65210, init: 65211, medi: 65212 }],
1802
+ [1590, { isol: 65213, fina: 65214, init: 65215, medi: 65216 }],
1803
+ [1591, { isol: 65217, fina: 65218, init: 65219, medi: 65220 }],
1804
+ [1592, { isol: 65221, fina: 65222, init: 65223, medi: 65224 }],
1805
+ [1593, { isol: 65225, fina: 65226, init: 65227, medi: 65228 }],
1806
+ [1594, { isol: 65229, fina: 65230, init: 65231, medi: 65232 }],
1807
+ [1601, { isol: 65233, fina: 65234, init: 65235, medi: 65236 }],
1808
+ [1602, { isol: 65237, fina: 65238, init: 65239, medi: 65240 }],
1809
+ [1603, { isol: 65241, fina: 65242, init: 65243, medi: 65244 }],
1810
+ [1604, { isol: 65245, fina: 65246, init: 65247, medi: 65248 }],
1811
+ [1605, { isol: 65249, fina: 65250, init: 65251, medi: 65252 }],
1812
+ [1606, { isol: 65253, fina: 65254, init: 65255, medi: 65256 }],
1813
+ [1607, { isol: 65257, fina: 65258, init: 65259, medi: 65260 }],
1814
+ [1608, { isol: 65261, fina: 65262 }],
1815
+ [1609, { isol: 65263, fina: 65264 }],
1816
+ [1610, { isol: 65265, fina: 65266, init: 65267, medi: 65268 }]
1817
+ ]);
1818
+ var LAM_ALEF_PRES = /* @__PURE__ */ new Map([
1819
+ [1570, [65269, 65270]],
1820
+ // LAM + ALEF WITH MADDA ABOVE
1821
+ [1571, [65271, 65272]],
1822
+ // LAM + ALEF WITH HAMZA ABOVE
1823
+ [1573, [65273, 65274]],
1824
+ // LAM + ALEF WITH HAMZA BELOW
1825
+ [1575, [65275, 65276]]
1826
+ // LAM + ALEF
1827
+ ]);
1828
+ function resolvePositionalForms(codePoints) {
1829
+ const len = codePoints.length;
1830
+ const forms = new Array(len).fill("isol");
1831
+ const joining = codePoints.map(getJoiningType);
1575
1832
  for (let i = 0; i < len; i++) {
1576
- const closer = OPEN_BRACKETS[codePoints[i]];
1577
- if (closer === void 0) continue;
1578
- let depth = 1;
1579
- let closeIdx = -1;
1580
- for (let j = i + 1; j < len; j++) {
1581
- if (codePoints[j] === codePoints[i]) depth++;
1582
- else if (codePoints[j] === closer) {
1583
- depth--;
1584
- if (depth === 0) {
1585
- closeIdx = j;
1586
- break;
1587
- }
1833
+ if (joining[i] === "T" || joining[i] === "U") continue;
1834
+ let prevJoin = "U";
1835
+ for (let j = i - 1; j >= 0; j--) {
1836
+ if (joining[j] !== "T") {
1837
+ prevJoin = joining[j];
1838
+ break;
1588
1839
  }
1589
1840
  }
1590
- if (closeIdx === -1) continue;
1591
- let hasL = false;
1592
- for (let j = i + 1; j < closeIdx; j++) {
1593
- if (types[j] === "L") {
1594
- hasL = true;
1841
+ let nextJoin = "U";
1842
+ for (let j = i + 1; j < len; j++) {
1843
+ if (joining[j] !== "T") {
1844
+ nextJoin = joining[j];
1595
1845
  break;
1596
1846
  }
1597
1847
  }
1598
- if (hasL) {
1599
- types[i] = "L";
1600
- types[closeIdx] = "L";
1848
+ const joinsToPrev = prevJoin === "D" || prevJoin === "C";
1849
+ const joinsToNext = (nextJoin === "D" || nextJoin === "R" || nextJoin === "C") && (joining[i] === "D" || joining[i] === "C");
1850
+ if (joinsToPrev && joinsToNext) {
1851
+ forms[i] = "medi";
1852
+ } else if (joinsToPrev) {
1853
+ forms[i] = "fina";
1854
+ } else if (joinsToNext) {
1855
+ forms[i] = "init";
1856
+ } else {
1857
+ forms[i] = "isol";
1601
1858
  }
1602
1859
  }
1860
+ return forms;
1603
1861
  }
1604
- function resolveBidiRuns(text) {
1605
- if (!text) return [];
1862
+ var LAM = 1604;
1863
+ var ALEF_VARIANTS = /* @__PURE__ */ new Set([1570, 1571, 1573, 1575]);
1864
+ function isLamAlef(cp1, cp2) {
1865
+ return cp1 === LAM && ALEF_VARIANTS.has(cp2);
1866
+ }
1867
+ function shapeArabicText(str, fontData) {
1868
+ if (!str) return [];
1606
1869
  const codePoints = [];
1607
- for (let i = 0; i < text.length; ) {
1608
- const cp = text.codePointAt(i) ?? 0;
1870
+ for (let i = 0; i < str.length; ) {
1871
+ const cp = str.codePointAt(i) ?? 0;
1609
1872
  codePoints.push(cp);
1610
1873
  i += cp > 65535 ? 2 : 1;
1611
1874
  }
1612
- const len = codePoints.length;
1613
- if (len === 0) return [];
1614
- const types = codePoints.map(classifyBidiType);
1615
- const paraLevel = detectParagraphLevel(types);
1616
- resolveWeakTypes(types, paraLevel);
1617
- resolveNeutralTypes(types, paraLevel);
1618
- if (paraLevel === 1) {
1619
- fixPunctuationAffinity(types, codePoints, len);
1620
- fixBracketPairing(types, codePoints, len);
1621
- }
1622
- const levels = assignLevels(types, paraLevel);
1623
- const runs = [];
1624
- let runStart = 0;
1625
- let runLevel = levels[0];
1626
- const cpToStr = [];
1627
- let strIdx = 0;
1628
- for (let i = 0; i < len; i++) {
1629
- cpToStr.push(strIdx);
1630
- strIdx += codePoints[i] > 65535 ? 2 : 1;
1631
- }
1632
- cpToStr.push(strIdx);
1633
- for (let i = 1; i <= len; i++) {
1634
- if (i === len || levels[i] !== runLevel) {
1635
- const start = cpToStr[runStart];
1636
- const end = cpToStr[i];
1637
- let runText = text.substring(start, end);
1638
- if (runLevel % 2 === 1) {
1639
- runText = reverseString(runText);
1875
+ const forms = resolvePositionalForms(codePoints);
1876
+ const glyphs = [];
1877
+ const cmap = fontData.cmap;
1878
+ const widths = fontData.widths;
1879
+ const defaultWidth = fontData.defaultWidth;
1880
+ const markAnchors = fontData.markAnchors;
1881
+ let lastBaseGid = 0;
1882
+ for (let i = 0; i < codePoints.length; i++) {
1883
+ const cp = codePoints[i];
1884
+ if (i < codePoints.length - 1 && isLamAlef(cp, codePoints[i + 1])) {
1885
+ const ligForms = LAM_ALEF_PRES.get(codePoints[i + 1]);
1886
+ if (ligForms) {
1887
+ const isFinal = forms[i] === "medi" || forms[i] === "fina";
1888
+ const ligCP = isFinal ? ligForms[1] : ligForms[0];
1889
+ const ligGid = cmap[ligCP];
1890
+ if (ligGid) {
1891
+ glyphs.push({ gid: ligGid, dx: 0, dy: 0, isZeroAdvance: false });
1892
+ lastBaseGid = ligGid;
1893
+ i++;
1894
+ continue;
1895
+ }
1640
1896
  }
1641
- runs.push({ text: runText, level: runLevel, start });
1642
- if (i < len) {
1643
- runStart = i;
1644
- runLevel = levels[i];
1897
+ }
1898
+ let gid = cmap[cp] ?? 0;
1899
+ const presForm = ARABIC_PRES_FORMS.get(cp);
1900
+ if (presForm) {
1901
+ const form = forms[i];
1902
+ let presCP;
1903
+ if (form === "init") presCP = presForm.init;
1904
+ else if (form === "medi") presCP = presForm.medi;
1905
+ else if (form === "fina") presCP = presForm.fina;
1906
+ else presCP = presForm.isol;
1907
+ if (presCP) {
1908
+ const presGid = cmap[presCP];
1909
+ if (presGid) gid = presGid;
1645
1910
  }
1646
1911
  }
1912
+ const joining = getJoiningType(cp);
1913
+ const isZeroAdvance = joining === "T";
1914
+ if (isZeroAdvance && lastBaseGid !== 0) {
1915
+ const baseAdv = widths[lastBaseGid] !== void 0 ? widths[lastBaseGid] : defaultWidth;
1916
+ const offset = positionMarkOnBase(markAnchors, gid, lastBaseGid, baseAdv);
1917
+ if (offset) {
1918
+ glyphs.push({ gid, dx: offset.dx, dy: offset.dy, isZeroAdvance: true });
1919
+ continue;
1920
+ }
1921
+ }
1922
+ glyphs.push({ gid, dx: 0, dy: 0, isZeroAdvance });
1923
+ if (!isZeroAdvance) lastBaseGid = gid;
1647
1924
  }
1648
- if (paraLevel === 1 && runs.length > 1) {
1649
- runs.reverse();
1650
- }
1651
- return runs;
1652
- }
1653
- function containsRTL(text) {
1654
- for (let i = 0; i < text.length; ) {
1655
- const cp = text.codePointAt(i) ?? 0;
1656
- const t = classifyBidiType(cp);
1657
- if (t === "R" || t === "AL") return true;
1658
- i += cp > 65535 ? 2 : 1;
1659
- }
1660
- return false;
1661
- }
1662
- function reverseString(str) {
1663
- const cps = [];
1664
- for (let i = 0; i < str.length; ) {
1665
- const cp = str.codePointAt(i) ?? 0;
1666
- cps.push(cp);
1667
- i += cp > 65535 ? 2 : 1;
1668
- }
1669
- cps.reverse();
1670
- return String.fromCodePoint(...cps);
1925
+ return glyphs;
1671
1926
  }
1672
1927
 
1673
1928
  // src/core/encoding-context.ts
@@ -1713,7 +1968,7 @@ function splitArabicNonArabic(text, fd) {
1713
1968
  if (cur) segments.push({ text: cur, arabic: curArabic });
1714
1969
  return segments;
1715
1970
  }
1716
- function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid) {
1971
+ function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid, pdfA = false) {
1717
1972
  const upm = fd.metrics.unitsPerEm;
1718
1973
  const result = [];
1719
1974
  let mode = null;
@@ -1753,11 +2008,11 @@ function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid) {
1753
2008
  const cp = rawCp === 8239 || rawCp === 160 ? 32 : rawCp;
1754
2009
  const char = text.substring(i, i + charLen);
1755
2010
  const gid = fd.cmap[cp] ?? 0;
1756
- if (gid === 0 && isWinAnsi(cp)) {
2011
+ if (gid === 0 && isWinAnsi(cp) && !pdfA) {
1757
2012
  if (mode === "cid") flushCid();
1758
2013
  mode = "hel";
1759
2014
  helChars += char;
1760
- } else if (mode === "hel" && isWinAnsi(cp)) {
2015
+ } else if (mode === "hel" && isWinAnsi(cp) && !pdfA) {
1761
2016
  helChars += char;
1762
2017
  } else {
1763
2018
  if (mode === "hel") flushHel();
@@ -1774,7 +2029,7 @@ function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid) {
1774
2029
  if (mode === "hel") flushHel();
1775
2030
  return result;
1776
2031
  }
1777
- function createEncodingContext(fontEntries) {
2032
+ function createEncodingContext(fontEntries, pdfA = false) {
1778
2033
  if (!fontEntries || fontEntries.length === 0) {
1779
2034
  return {
1780
2035
  isUnicode: false,
@@ -1803,6 +2058,8 @@ function createEncodingContext(fontEntries) {
1803
2058
  return _usedGids;
1804
2059
  },
1805
2060
  textRuns(str, sz) {
2061
+ if (!str) return [];
2062
+ str = stripBidiControls(str);
1806
2063
  if (!str) return [];
1807
2064
  if (containsRTL(str)) {
1808
2065
  const bidiRuns = resolveBidiRuns(str);
@@ -1830,12 +2087,12 @@ function createEncodingContext(fontEntries) {
1830
2087
  }
1831
2088
  result.push({ text: seg.text, fontRef, fontData: fd, shaped: visual, hexStr: null, widthPt: designW * sz / upm });
1832
2089
  } else {
1833
- const subRuns = buildTextRunsWithFallback(seg.text, fontRef, fd, sz, _trackGid);
2090
+ const subRuns = buildTextRunsWithFallback(seg.text, fontRef, fd, sz, _trackGid, pdfA);
1834
2091
  result.push(...subRuns);
1835
2092
  }
1836
2093
  }
1837
2094
  } else if (isRTL) {
1838
- const subRuns = buildTextRunsWithFallback(fRun.text, fontRef, fd, sz, _trackGid);
2095
+ const subRuns = buildTextRunsWithFallback(fRun.text, fontRef, fd, sz, _trackGid, pdfA);
1839
2096
  result.push(...subRuns);
1840
2097
  } else {
1841
2098
  if (containsThai(fRun.text)) {
@@ -1879,7 +2136,7 @@ function createEncodingContext(fontEntries) {
1879
2136
  }
1880
2137
  result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
1881
2138
  } else {
1882
- const subRuns = buildTextRunsWithFallback(fRun.text, fontRef, fd, sz, _trackGid);
2139
+ const subRuns = buildTextRunsWithFallback(fRun.text, fontRef, fd, sz, _trackGid, pdfA);
1883
2140
  result.push(...subRuns);
1884
2141
  }
1885
2142
  }
@@ -1936,10 +2193,12 @@ function createEncodingContext(fontEntries) {
1936
2193
  }
1937
2194
  return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
1938
2195
  }
1939
- return buildTextRunsWithFallback(run.text, fontRef, fd, sz, _trackGid);
2196
+ return buildTextRunsWithFallback(run.text, fontRef, fd, sz, _trackGid, pdfA);
1940
2197
  });
1941
2198
  },
1942
2199
  ps(str) {
2200
+ if (!str) return "<>";
2201
+ str = stripBidiControls(str);
1943
2202
  if (!str) return "<>";
1944
2203
  const { cmap } = primary.fontData;
1945
2204
  if (containsRTL(str)) {
@@ -2408,7 +2667,7 @@ function buildPdfMetadata(now = /* @__PURE__ */ new Date()) {
2408
2667
  const xmpDate = `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}${tzSign}${tzH}:${tzM}`;
2409
2668
  return { pdfDate, xmpDate };
2410
2669
  }
2411
- function buildXMPMetadata(title, createDate, pdfaPart = 2, pdfaConformance = "B", author) {
2670
+ function buildXMPMetadata(title, createDate, pdfaPart = 2, pdfaConformance = "B", author, subject, keywords) {
2412
2671
  const escapedTitle = escapeXml(title);
2413
2672
  const lines = [
2414
2673
  '<?xpacket begin="\xEF\xBB\xBF" id="W5M0MpCehiHzreSzNTczkc9d"?>',
@@ -2427,7 +2686,9 @@ function buildXMPMetadata(title, createDate, pdfaPart = 2, pdfaConformance = "B"
2427
2686
  ` <xmp:ModifyDate>${createDate}</xmp:ModifyDate>`,
2428
2687
  ` <xmp:MetadataDate>${createDate}</xmp:MetadataDate>`,
2429
2688
  ` <pdfaid:part>${pdfaPart}</pdfaid:part>`,
2430
- ` <pdfaid:conformance>${pdfaConformance}</pdfaid:conformance>`,
2689
+ ` <pdfaid:conformance>${pdfaConformance}</pdfaid:conformance>`
2690
+ );
2691
+ lines.push(
2431
2692
  " </rdf:Description>",
2432
2693
  " </rdf:RDF>",
2433
2694
  "</x:xmpmeta>",
@@ -2435,6 +2696,35 @@ function buildXMPMetadata(title, createDate, pdfaPart = 2, pdfaConformance = "B"
2435
2696
  );
2436
2697
  return lines.join("\n");
2437
2698
  }
2699
+ function utf8EncodeBinaryString(str) {
2700
+ let out = "";
2701
+ for (let i = 0; i < str.length; i++) {
2702
+ let cp = str.charCodeAt(i);
2703
+ if (cp >= 55296 && cp <= 56319 && i + 1 < str.length) {
2704
+ const lo = str.charCodeAt(i + 1);
2705
+ if (lo >= 56320 && lo <= 57343) {
2706
+ cp = (cp - 55296 << 10) + (lo - 56320) + 65536;
2707
+ i++;
2708
+ }
2709
+ }
2710
+ if (cp < 128) {
2711
+ out += String.fromCharCode(cp);
2712
+ } else if (cp < 2048) {
2713
+ out += String.fromCharCode(192 | cp >> 6);
2714
+ out += String.fromCharCode(128 | cp & 63);
2715
+ } else if (cp < 65536) {
2716
+ out += String.fromCharCode(224 | cp >> 12);
2717
+ out += String.fromCharCode(128 | cp >> 6 & 63);
2718
+ out += String.fromCharCode(128 | cp & 63);
2719
+ } else {
2720
+ out += String.fromCharCode(240 | cp >> 18);
2721
+ out += String.fromCharCode(128 | cp >> 12 & 63);
2722
+ out += String.fromCharCode(128 | cp >> 6 & 63);
2723
+ out += String.fromCharCode(128 | cp & 63);
2724
+ }
2725
+ }
2726
+ return out;
2727
+ }
2438
2728
  function buildOutputIntentDict(iccStreamObjNum, subtype = "GTS_PDFA1") {
2439
2729
  return `<< /Type /OutputIntent /S /${subtype} /OutputConditionIdentifier (sRGB IEC61966-2.1) /RegistryName (http://www.color.org) /DestOutputProfile ${iccStreamObjNum} 0 R >>`;
2440
2730
  }
@@ -2592,22 +2882,22 @@ function txt(str, x, y, font, sz, enc) {
2592
2882
  }
2593
2883
  return parts.join("\n");
2594
2884
  }
2595
- function txtR(str, rightX, y, font, sz, enc) {
2596
- const width = enc.isUnicode ? enc.tw(str, sz) : helveticaWidth(toWinAnsi(str), sz);
2885
+ function txtR(str, rightX, y, font, sz, enc, bold = false) {
2886
+ const width = enc.isUnicode ? enc.tw(str, sz) : bold ? helveticaBoldWidth(str, sz) : helveticaWidth(toWinAnsi(str), sz);
2597
2887
  return txt(str, rightX - width, y, font, sz, enc);
2598
2888
  }
2599
- function txtC(str, leftX, y, font, sz, colW, enc) {
2600
- const width = enc.isUnicode ? enc.tw(str, sz) : helveticaWidth(toWinAnsi(str), sz);
2889
+ function txtC(str, leftX, y, font, sz, colW, enc, bold = false) {
2890
+ const width = enc.isUnicode ? enc.tw(str, sz) : bold ? helveticaBoldWidth(str, sz) : helveticaWidth(toWinAnsi(str), sz);
2601
2891
  return txt(str, leftX + (colW - width) / 2, y, font, sz, enc);
2602
2892
  }
2603
2893
  function txtTagged(str, x, y, font, sz, enc, mcid) {
2604
2894
  return wrapSpan(txt(str, x, y, font, sz, enc), str, mcid);
2605
2895
  }
2606
- function txtRTagged(str, rightX, y, font, sz, enc, mcid) {
2607
- return wrapSpan(txtR(str, rightX, y, font, sz, enc), str, mcid);
2896
+ function txtRTagged(str, rightX, y, font, sz, enc, mcid, bold = false) {
2897
+ return wrapSpan(txtR(str, rightX, y, font, sz, enc, bold), str, mcid);
2608
2898
  }
2609
- function txtCTagged(str, leftX, y, font, sz, colW, enc, mcid) {
2610
- return wrapSpan(txtC(str, leftX, y, font, sz, colW, enc), str, mcid);
2899
+ function txtCTagged(str, leftX, y, font, sz, colW, enc, mcid, bold = false) {
2900
+ return wrapSpan(txtC(str, leftX, y, font, sz, colW, enc, bold), str, mcid);
2611
2901
  }
2612
2902
  function encodePdfTextString(str) {
2613
2903
  let ascii = true;
@@ -2681,13 +2971,42 @@ var DEFAULT_COLUMNS = [
2681
2971
  { f: 0.18, a: "c", mx: 20, mxH: 20 }
2682
2972
  ];
2683
2973
  function computeColumnPositions(columns, marginLeft, contentWidth) {
2684
- const cx = [];
2685
- const cwi = [];
2974
+ const n = columns.length;
2975
+ const cwi = new Array(n).fill(0);
2976
+ const fixed = new Array(n).fill(false);
2977
+ let totalFixed = 0;
2978
+ let freeWeight = 0;
2979
+ for (let i = 0; i < n; i++) {
2980
+ const col = columns[i];
2981
+ let w = col.f * contentWidth;
2982
+ let clamped = false;
2983
+ if (col.minWidth !== void 0 && w < col.minWidth) {
2984
+ w = col.minWidth;
2985
+ clamped = true;
2986
+ }
2987
+ if (col.maxWidth !== void 0 && w > col.maxWidth) {
2988
+ w = col.maxWidth;
2989
+ clamped = true;
2990
+ }
2991
+ if (clamped) {
2992
+ cwi[i] = w;
2993
+ fixed[i] = true;
2994
+ totalFixed += w;
2995
+ } else {
2996
+ freeWeight += col.f;
2997
+ }
2998
+ }
2999
+ const remaining = contentWidth - totalFixed;
3000
+ if (freeWeight > 0) {
3001
+ for (let i = 0; i < n; i++) {
3002
+ if (!fixed[i]) cwi[i] = columns[i].f / freeWeight * remaining;
3003
+ }
3004
+ }
3005
+ const cx = new Array(n);
2686
3006
  let x = marginLeft;
2687
- for (const col of columns) {
2688
- cx.push(x);
2689
- cwi.push(col.f * contentWidth);
2690
- x += col.f * contentWidth;
3007
+ for (let i = 0; i < n; i++) {
3008
+ cx[i] = x;
3009
+ x += cwi[i];
2691
3010
  }
2692
3011
  return { cx, cwi };
2693
3012
  }
@@ -3036,17 +3355,17 @@ function _buildTableHeader(y, headers, enc, cx, cwi, columns, cw, mgL, mgR, pgW,
3036
3355
  const thEl = { type: "TH", children: [mcref] };
3037
3356
  thChildren.push(thEl);
3038
3357
  if (columns[i].a === "r") {
3039
- ops.push(txtRTagged(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid));
3358
+ ops.push(txtRTagged(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid, true));
3040
3359
  } else if (columns[i].a === "c") {
3041
- ops.push(txtCTagged(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc, mcid));
3360
+ ops.push(txtCTagged(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc, mcid, true));
3042
3361
  } else {
3043
3362
  ops.push(txtTagged(t, cx[i] + 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid));
3044
3363
  }
3045
3364
  } else {
3046
3365
  if (columns[i].a === "r") {
3047
- ops.push(txtR(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc));
3366
+ ops.push(txtR(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc, true));
3048
3367
  } else if (columns[i].a === "c") {
3049
- ops.push(txtC(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc));
3368
+ ops.push(txtC(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc, true));
3050
3369
  } else {
3051
3370
  ops.push(txt(t, cx[i] + 3, y - TH_H + 4, enc.f2, fs.th, enc));
3052
3371
  }
@@ -3157,7 +3476,9 @@ function buildPDF(params, layoutOptions) {
3157
3476
  const fs = DEFAULT_FONT_SIZES;
3158
3477
  const { cx, cwi } = computeColumnPositions(columns, mg.l, cw);
3159
3478
  const fontEntries = params.fontEntries || (fontData ? [{ fontData, fontRef: "/F3", lang: "unknown" }] : []);
3160
- const enc = createEncodingContext(fontEntries);
3479
+ const pdfaConfig = resolvePdfAConfig();
3480
+ const tagged = pdfaConfig.enabled;
3481
+ const enc = createEncodingContext(fontEntries, tagged);
3161
3482
  const footerTpl = {
3162
3483
  left: footerText || void 0,
3163
3484
  right: "{page}/{pages}"
@@ -3178,8 +3499,6 @@ function buildPDF(params, layoutOptions) {
3178
3499
  totalPages = 1 + Math.ceil((totalRows - rowsPage1) / rowsPerPage);
3179
3500
  }
3180
3501
  if (totalPages < 1) totalPages = 1;
3181
- const pdfaConfig = resolvePdfAConfig();
3182
- const tagged = pdfaConfig.enabled;
3183
3502
  const encState = null;
3184
3503
  const wmExtraObjs = 0;
3185
3504
  const mcidAlloc = tagged ? createMCIDAllocator() : void 0;
@@ -3309,8 +3628,17 @@ function buildPDF(params, layoutOptions) {
3309
3628
  kids.push(`${pageObjStart + p * 2} 0 R`);
3310
3629
  }
3311
3630
  emitObj(2, `<< /Type /Pages /Kids [${kids.join(" ")}] /Count ${totalPages} >>`);
3312
- emitObj(3, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
3313
- emitObj(4, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>");
3631
+ if (tagged) {
3632
+ const pf = fontEntries[0];
3633
+ const bfName = `/${pf.fontData.fontName.replace(/[^A-Za-z0-9-]/g, "")}`;
3634
+ const primaryBase = 5;
3635
+ const refDict = `<< /Type /Font /Subtype /Type0 /BaseFont ${bfName} /Encoding /Identity-H /DescendantFonts [${primaryBase + 1} 0 R] /ToUnicode ${primaryBase + 4} 0 R >>`;
3636
+ emitObj(3, refDict);
3637
+ emitObj(4, refDict);
3638
+ } else {
3639
+ emitObj(3, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
3640
+ emitObj(4, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>");
3641
+ }
3314
3642
  for (let fi = 0; fi < fontEntries.length; fi++) {
3315
3643
  const fe = fontEntries[fi];
3316
3644
  const fd = fe.fontData;
@@ -3404,7 +3732,7 @@ function buildPDF(params, layoutOptions) {
3404
3732
  structTreeRootObjNum = tree.structTreeRootObjNum;
3405
3733
  totalObjs = treeStart + tree.totalObjects - 1;
3406
3734
  xmpObjNum = totalObjs + 1;
3407
- const xmpContent = buildXMPMetadata(infoTitle, isoDate, pdfaConfig.pdfaPart, pdfaConfig.pdfaConformance);
3735
+ const xmpContent = utf8EncodeBinaryString(buildXMPMetadata(infoTitle, isoDate, pdfaConfig.pdfaPart, pdfaConfig.pdfaConformance));
3408
3736
  emitStreamObj(
3409
3737
  xmpObjNum,
3410
3738
  `<< /Type /Metadata /Subtype /XML /Length ${xmpContent.length}`,