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