postext-pdf 1.14.0 → 1.15.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.
Files changed (100) hide show
  1. package/README.md +13 -1
  2. package/dist/__bench__/arabicShaping.bench.test.d.ts +2 -0
  3. package/dist/__bench__/arabicShaping.bench.test.d.ts.map +1 -0
  4. package/dist/__bench__/arabicShaping.bench.test.js +53 -0
  5. package/dist/__bench__/arabicShaping.bench.test.js.map +1 -0
  6. package/dist/__tests__/arabicShaping.test.d.ts +2 -0
  7. package/dist/__tests__/arabicShaping.test.d.ts.map +1 -0
  8. package/dist/__tests__/arabicShaping.test.js +261 -0
  9. package/dist/__tests__/arabicShaping.test.js.map +1 -0
  10. package/dist/__tests__/complexShaping.test.d.ts +2 -0
  11. package/dist/__tests__/complexShaping.test.d.ts.map +1 -0
  12. package/dist/__tests__/complexShaping.test.js +145 -0
  13. package/dist/__tests__/complexShaping.test.js.map +1 -0
  14. package/dist/__tests__/designTextRtl.test.d.ts +2 -0
  15. package/dist/__tests__/designTextRtl.test.d.ts.map +1 -0
  16. package/dist/__tests__/designTextRtl.test.js +136 -0
  17. package/dist/__tests__/designTextRtl.test.js.map +1 -0
  18. package/dist/__tests__/harfbuzz.test.d.ts +2 -0
  19. package/dist/__tests__/harfbuzz.test.d.ts.map +1 -0
  20. package/dist/__tests__/harfbuzz.test.js +103 -0
  21. package/dist/__tests__/harfbuzz.test.js.map +1 -0
  22. package/dist/__tests__/measuredLines.test.js +13 -5
  23. package/dist/__tests__/measuredLines.test.js.map +1 -1
  24. package/dist/__tests__/mirroredPdf.test.d.ts +2 -0
  25. package/dist/__tests__/mirroredPdf.test.d.ts.map +1 -0
  26. package/dist/__tests__/mirroredPdf.test.js +104 -0
  27. package/dist/__tests__/mirroredPdf.test.js.map +1 -0
  28. package/dist/__tests__/rtlPdf.test.d.ts +2 -0
  29. package/dist/__tests__/rtlPdf.test.d.ts.map +1 -0
  30. package/dist/__tests__/rtlPdf.test.js +312 -0
  31. package/dist/__tests__/rtlPdf.test.js.map +1 -0
  32. package/dist/__tests__/verticalText.test.js +4 -4
  33. package/dist/__tests__/verticalText.test.js.map +1 -1
  34. package/dist/complexShaping.d.ts +113 -0
  35. package/dist/complexShaping.d.ts.map +1 -0
  36. package/dist/complexShaping.js +188 -0
  37. package/dist/complexShaping.js.map +1 -0
  38. package/dist/faceFiles.d.ts +9 -1
  39. package/dist/faceFiles.d.ts.map +1 -1
  40. package/dist/faceFiles.js +47 -5
  41. package/dist/faceFiles.js.map +1 -1
  42. package/dist/harfbuzz.d.ts +119 -0
  43. package/dist/harfbuzz.d.ts.map +1 -0
  44. package/dist/harfbuzz.js +280 -0
  45. package/dist/harfbuzz.js.map +1 -0
  46. package/dist/harfbuzz.wasm +0 -0
  47. package/dist/index.d.ts +2 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/pdf-backend/blockRender.d.ts.map +1 -1
  51. package/dist/pdf-backend/blockRender.js +113 -40
  52. package/dist/pdf-backend/blockRender.js.map +1 -1
  53. package/dist/pdf-backend/chip.d.ts.map +1 -1
  54. package/dist/pdf-backend/chip.js +11 -2
  55. package/dist/pdf-backend/chip.js.map +1 -1
  56. package/dist/pdf-backend/directedLine.d.ts +38 -0
  57. package/dist/pdf-backend/directedLine.d.ts.map +1 -0
  58. package/dist/pdf-backend/directedLine.js +103 -0
  59. package/dist/pdf-backend/directedLine.js.map +1 -0
  60. package/dist/pdf-backend/fontHelpers.d.ts.map +1 -1
  61. package/dist/pdf-backend/fontHelpers.js +6 -0
  62. package/dist/pdf-backend/fontHelpers.js.map +1 -1
  63. package/dist/pdf-backend/headerFooter.d.ts.map +1 -1
  64. package/dist/pdf-backend/headerFooter.js +11 -3
  65. package/dist/pdf-backend/headerFooter.js.map +1 -1
  66. package/dist/pdf-backend/index.d.ts +32 -3
  67. package/dist/pdf-backend/index.d.ts.map +1 -1
  68. package/dist/pdf-backend/index.js +40 -6
  69. package/dist/pdf-backend/index.js.map +1 -1
  70. package/dist/pdf-backend/links.d.ts +7 -2
  71. package/dist/pdf-backend/links.d.ts.map +1 -1
  72. package/dist/pdf-backend/links.js +9 -3
  73. package/dist/pdf-backend/links.js.map +1 -1
  74. package/dist/pdf-backend/outlines.js +2 -2
  75. package/dist/pdf-backend/outlines.js.map +1 -1
  76. package/dist/pdf-backend/primitives.d.ts +46 -2
  77. package/dist/pdf-backend/primitives.d.ts.map +1 -1
  78. package/dist/pdf-backend/primitives.js +67 -5
  79. package/dist/pdf-backend/primitives.js.map +1 -1
  80. package/dist/pdf-backend/renderResourceBlock.d.ts.map +1 -1
  81. package/dist/pdf-backend/renderResourceBlock.js +48 -14
  82. package/dist/pdf-backend/renderResourceBlock.js.map +1 -1
  83. package/dist/pdf-backend/rtlRun.d.ts +12 -0
  84. package/dist/pdf-backend/rtlRun.d.ts.map +1 -0
  85. package/dist/pdf-backend/rtlRun.js +31 -0
  86. package/dist/pdf-backend/rtlRun.js.map +1 -0
  87. package/dist/pdf-backend/shapedText.d.ts +122 -0
  88. package/dist/pdf-backend/shapedText.d.ts.map +1 -0
  89. package/dist/pdf-backend/shapedText.js +635 -0
  90. package/dist/pdf-backend/shapedText.js.map +1 -0
  91. package/dist/pdf-backend/svgVector.d.ts +9 -2
  92. package/dist/pdf-backend/svgVector.d.ts.map +1 -1
  93. package/dist/pdf-backend/svgVector.js +42 -18
  94. package/dist/pdf-backend/svgVector.js.map +1 -1
  95. package/dist/worker/client.d.ts.map +1 -1
  96. package/dist/worker/client.js +10 -1
  97. package/dist/worker/client.js.map +1 -1
  98. package/dist/worker/protocol.d.ts +5 -1
  99. package/dist/worker/protocol.d.ts.map +1 -1
  100. package/package.json +6 -5
package/README.md CHANGED
@@ -46,7 +46,7 @@ In the browser, load the web fonts (`document.fonts.load(...)`) before calling `
46
46
 
47
47
  ## API
48
48
 
49
- - `renderToPdf(doc | doc[], options)` — renders one document or a whole book (an array of chapters) to PDF bytes. Options: `fontProvider` (required; asked only for the faces the pages paint), `resourceBytes` (images, SVGs and single-page PDF masters by file id; an SVG figure's `svg.pdfFileId` master is asked for first), `outlines`, `accessible`, `colorSpace` (each one, when left out, from the first document's `config.pdfGeneration`, `colorSpace` only while its `forceColorSpace` is on; else bookmarks on, tagged, RGB), `pageNegative`, `characterGrid` (draws the character grid of `cjk.grid.show`, off by default), `onProgress`, `onWarning` (a face the provider rejects is set in another cut of its family — the nearest standard weight in CSS font-matching order, same style first, then the other style — and reported here, `console.warn` by default; the render fails only when the provider has no cut of the family at all), `rasterizeSvg`.
49
+ - `renderToPdf(doc | doc[], options)` — renders one document or a whole book (an array of chapters) to PDF bytes. Options: `fontProvider` (required; asked only for the faces the pages paint), `resourceBytes` (images, SVGs and single-page PDF masters by file id; an SVG figure's `svg.pdfFileId` master is asked for first), `outlines`, `accessible`, `colorSpace` (each one, when left out, from the first document's `config.pdfGeneration`, `colorSpace` only while its `forceColorSpace` is on; else bookmarks on, tagged, RGB), `pageNegative`, `characterGrid` (draws the character grid of `cjk.grid.show`, off by default), `onProgress`, `onWarning` (a face the provider rejects is set in another cut of its family — the nearest standard weight in CSS font-matching order, same style first, then the other style — and reported here, `console.warn` by default; the render fails only when the provider has no cut of the family at all), `rasterizeSvg`, `harfbuzzWasm` (where to load `harfbuzz.wasm` from, a URL or its bytes; see below).
50
50
  - `decompressWoff2(bytes)` — WOFF2 → TTF/OTF, for font providers that fetch web fonts.
51
51
  - `postext-pdf/worker` — `createPdfWorker()` renders off the main thread; fonts and SVG rasters are still resolved on the caller's thread when the worker asks for them. `postext-pdf/worker/entry` is the worker script for bundlers that want to control the worker URL. From a CDN such as esm.sh, start the worker yourself from a same-origin blob that imports the entry — `createPdfWorker({ worker: new Worker(URL.createObjectURL(new Blob(["import 'https://esm.sh/postext-pdf/worker/entry';"], { type: 'text/javascript' })), { type: 'module' }) })` — because a page cannot start a worker script from another origin.
52
52
  - Helpers: `svgToVectorDrawing`, `sniffBytes`, `rasterizeSvgWithDom`.
@@ -60,6 +60,18 @@ In the browser, load the web fonts (`document.fonts.load(...)`) before calling `
60
60
 
61
61
  Some bundlers (Vite, webpack, Turbopack) evaluate the emscripten glue as a web module, where it never assigns `module.exports`, and `decompressWoff2` then fails with `wawoff2: no decompress export`. The fix is a one-line patch to `wawoff2/build/decompress_binding.js` and `compress_binding.js` appending `module.exports = Module;` — the [postext monorepo](https://github.com/drnachio/postext/blob/main/patches/wawoff2.patch) applies it through pnpm's `patchedDependencies`. Alternatively, supply TTF/OTF bytes to your font provider directly and never call `decompressWoff2`.
62
62
 
63
+ ## Arabic, Hebrew and other right-to-left scripts
64
+
65
+ Text in a right-to-left or joining script (Hebrew, Arabic, Syriac, Thaana, N'Ko and the other right-to-left blocks) is shaped with [HarfBuzz](https://www.npmjs.com/package/harfbuzzjs) compiled to WebAssembly: joining forms, ligatures, harakat stacked as the font places them, brackets mirrored in right-to-left runs, numbers and Latin words kept left to right. Its `harfbuzz.wasm` (about 430 KB, 145 KB brotli) is loaded only when a document sets such text. Character spacing is never applied to joining letters.
66
+
67
+ postext-pdf instantiates the binary itself, without the harfbuzzjs JavaScript glue, so it loads the same in Node, a page, a Worker and a bundled app, and from a CDN that rebuilds packages for the browser (esm.sh). It looks for the file, in order:
68
+
69
+ 1. `options.harfbuzzWasm`: a URL (a relative one resolves against the page) or the file's bytes;
70
+ 2. `harfbuzz.wasm` beside postext-pdf's own module (`dist/harfbuzz.wasm`), as `new URL('./harfbuzz.wasm', import.meta.url)`: Node reads the file, Vite, webpack and Turbopack emit it as an asset, and jsDelivr or unpkg serve it from the package;
71
+ 3. the same harfbuzzjs release from jsDelivr, then from esm.sh, for a page that imports postext-pdf from esm.sh (which serves the module from a path with no `.wasm` beside it).
72
+
73
+ When none loads, the PDF is still written, shaped with fontkit: Arabic keeps its joining but loses its mark positions. The render reports it as a `complexShapingUnavailable` warning (to `onWarning`, else `console.warn`), with every place tried and why it failed.
74
+
63
75
  ## License
64
76
 
65
77
  MIT
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=arabicShaping.bench.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arabicShaping.bench.test.d.ts","sourceRoot":"","sources":["../../src/__bench__/arabicShaping.bench.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Cost of shaping Arabic words for the PDF (#380): HarfBuzz through
3
+ * `drawShapedTextPx` against the fontkit path every word took before
4
+ * (`showTextShaped`), 20 000 words of a vocalised text, each painted once.
5
+ * Skipped by `pnpm test`; run with `pnpm bench` (BENCH=1).
6
+ */
7
+ import { describe, it } from 'vitest';
8
+ import fs from 'node:fs';
9
+ import { PDFDocument, rgb } from 'pdf-lib';
10
+ import fontkit from '@pdf-lib/fontkit';
11
+ import { loadComplexShaper } from '../complexShaping';
12
+ import { drawShapedTextPx } from '../pdf-backend/shapedText';
13
+ import { showTextShaped } from '../pdf-backend/primitives';
14
+ const AMIRI = new Uint8Array(fs.readFileSync(new URL('../__tests__/fixtures/arabic/amiri-subset.ttf', import.meta.url)));
15
+ const WORDS = 'بِسْمِ ٱللَّهِ ٱلرَّحْمَٰنِ ٱلرَّحِيمِ السلام عليكم ورحمة الله وبركاته لا إله إلا كتـــاب عام سنة كلمة قوس'.split(' ');
16
+ const MARKS = ['', 'َ', 'ِ', 'ُ', 'ّ', 'ْ', 'ًّ'];
17
+ const nodeProcess = globalThis.process;
18
+ /** 20 000 words, most of them different (as in a book, where the cache
19
+ * helps less than in a list of repeats): a vocabulary word's head, a mark,
20
+ * another word's tail. */
21
+ function words() {
22
+ const out = [];
23
+ for (let i = 0; i < 20_000; i++) {
24
+ const a = WORDS[i % WORDS.length];
25
+ const b = WORDS[Math.floor(i / WORDS.length) % WORDS.length];
26
+ const mark = MARKS[Math.floor(i / (WORDS.length * WORDS.length)) % MARKS.length];
27
+ out.push(a.slice(0, 1 + (i % 3)) + mark + b.slice(Math.floor(i / 7) % Math.max(1, b.length - 1)));
28
+ }
29
+ return out;
30
+ }
31
+ describe.skipIf(!nodeProcess?.env?.BENCH)('Arabic shaping bench', () => {
32
+ it('shapes 20k words with HarfBuzz and with fontkit', { timeout: 600_000 }, async () => {
33
+ await loadComplexShaper();
34
+ const list = words();
35
+ nodeProcess?.stdout.write(`${new Set(list).size} distinct words\n`);
36
+ for (const pass of ['harfbuzz', 'fontkit']) {
37
+ const doc = await PDFDocument.create();
38
+ doc.registerFontkit(fontkit);
39
+ const font = await doc.embedFont(AMIRI, { subset: true });
40
+ const page = doc.addPage();
41
+ const ctx = { page, pageHeightPt: 800, scale: 1, colorSpace: 'rgb' };
42
+ const t = performance.now();
43
+ for (const w of list) {
44
+ if (pass === 'harfbuzz')
45
+ drawShapedTextPx(ctx, w, 10, 10, font, 12, rgb(0, 0, 0), { direction: 'rtl' });
46
+ else
47
+ showTextShaped(font, w);
48
+ }
49
+ nodeProcess?.stdout.write(`${pass}: ${(performance.now() - t).toFixed(0)} ms\n`);
50
+ }
51
+ });
52
+ });
53
+ //# sourceMappingURL=arabicShaping.bench.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arabicShaping.bench.test.js","sourceRoot":"","sources":["../../src/__bench__/arabicShaping.bench.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=arabicShaping.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arabicShaping.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/arabicShaping.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,261 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { execFileSync } from 'node:child_process';
6
+ import fontkit from '@pdf-lib/fontkit';
7
+ import { PDFArray, PDFDocument, PDFHexString, PDFRawStream, decodePDFRawStream } from 'pdf-lib';
8
+ import { buildDocument, lineLevels, resolveParagraph, visualOrder } from 'postext';
9
+ import { renderToPdf } from '../pdf-backend';
10
+ import { parseFontString } from '../fontString';
11
+ import { complexShaperReady, shapeRun } from '../complexShaping';
12
+ // Issue #380: Arabic in the PDF is shaped by HarfBuzz (marks raised and
13
+ // lowered with the text rise, numbers and Latin kept left to right) and
14
+ // reads back in logical order.
15
+ const AMIRI = new Uint8Array(fs.readFileSync(new URL('./fixtures/arabic/amiri-subset.ttf', import.meta.url)));
16
+ const fontProvider = async () => AMIRI;
17
+ const face = fontkit.create(Buffer.from(AMIRI));
18
+ /** Measures like Chrome: HarfBuzz once it is loaded, each piece in the
19
+ * direction of its first strong letter; fontkit, whose advances agree,
20
+ * before that (the Latin-only document of the first test). */
21
+ class HarfBuzzCtx {
22
+ font = '';
23
+ measureText(s) {
24
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
25
+ const run = complexShaperReady() ? shapeRun(AMIRI, s, { direction: resolveParagraph(s).paragraphLevel === 1 ? 'rtl' : 'ltr' }) : undefined;
26
+ const advance = run ? run.glyphs.reduce((sum, g) => sum + g.xAdvance, 0) / run.upem : face.layout(s).advanceWidth / face.unitsPerEm;
27
+ return { width: advance * sizePx };
28
+ }
29
+ }
30
+ globalThis.OffscreenCanvas = class {
31
+ getContext() {
32
+ return new HarfBuzzCtx();
33
+ }
34
+ };
35
+ const pt = (value) => ({ value, unit: 'pt' });
36
+ const BASMALA = 'بِسْمِ ٱللَّهِ ٱلرَّحْمَٰنِ ٱلرَّحِيمِ';
37
+ const SALAM = 'السلام عليكم ورحمة الله وبركاته';
38
+ const KASHIDA = 'لا إله إلا الله، كتـــاب';
39
+ const MIXED = 'كلمة Latin 2024 كلمة (قوس) سنة ١٤٤٥ هـ';
40
+ const PARAGRAPHS = [BASMALA, SALAM, KASHIDA, MIXED];
41
+ function config(bodyText = {}) {
42
+ return {
43
+ page: { width: pt(420), height: pt(260), dpi: 72, margins: { top: pt(20), bottom: pt(20), left: pt(20), right: pt(20) } },
44
+ locale: 'ar',
45
+ // These tests look at the shaped runs on an unmirrored page; the
46
+ // mirrored frame of a right-to-left document (#370) has its own tests
47
+ // (`mirroredPdf.test.ts`).
48
+ direction: 'ltr',
49
+ layout: { layoutType: 'single' },
50
+ header: { elements: [] },
51
+ footer: { elements: [] },
52
+ bodyText: { fontFamily: 'Amiri', fontSize: pt(18), lineHeight: pt(36), firstLineIndent: pt(0), textAlign: 'left', hyphenation: { enabled: false }, ...bodyText },
53
+ };
54
+ }
55
+ function lines(doc) {
56
+ return doc.pages.flatMap((p) => p.columns.flatMap((c) => c.blocks.flatMap((b) => b.lines)));
57
+ }
58
+ /** Give a VDT the directions the engine will (SPEC D9): each segment's
59
+ * level from the paragraph's (right to left), `rtl` on the odd ones, and
60
+ * the line's visual order. */
61
+ /** `doc` as a VDT from before the engine resolved directions (#369):
62
+ * its lines with no `order`, no `rtl`, no `level`. */
63
+ function legacy(doc) {
64
+ for (const line of lines(doc)) {
65
+ delete line.order;
66
+ for (const seg of line.segments ?? []) {
67
+ delete seg.rtl;
68
+ delete seg.level;
69
+ }
70
+ }
71
+ return doc;
72
+ }
73
+ function directed(doc) {
74
+ // The engine's own directions (#369) are replaced by these.
75
+ legacy(doc);
76
+ for (const line of lines(doc)) {
77
+ const segments = line.segments ?? [];
78
+ const text = segments.map((s) => s.text).join('');
79
+ const levels = lineLevels(resolveParagraph(text, 'rtl'));
80
+ const segLevels = [];
81
+ let at = 0;
82
+ for (const seg of segments) {
83
+ const level = seg.text ? levels[at] : 1;
84
+ segLevels.push(level);
85
+ if (level % 2 === 1)
86
+ seg.rtl = true;
87
+ if (level > 1)
88
+ seg.level = level;
89
+ at += seg.text.length;
90
+ }
91
+ line.order = visualOrder(segLevels);
92
+ }
93
+ return doc;
94
+ }
95
+ async function render(doc) {
96
+ return PDFDocument.load(await renderToPdf(doc, { fontProvider }));
97
+ }
98
+ function pageContent(pdf) {
99
+ const contents = pdf.getPage(0).node.Contents();
100
+ const refs = contents instanceof PDFArray ? contents.asArray() : [contents];
101
+ return refs
102
+ .map((ref) => {
103
+ const s = pdf.context.lookup(ref);
104
+ return s instanceof PDFRawStream ? new TextDecoder('latin1').decode(decodePDFRawStream(s).decode()) : '';
105
+ })
106
+ .join('\n');
107
+ }
108
+ function actualTexts(content) {
109
+ return [...content.matchAll(/\/Span <<\n\/ActualText (<[0-9A-Fa-f]*>)\n>> BDC/g)].map((m) => PDFHexString.of(m[1].slice(1, -1)).decodeText());
110
+ }
111
+ /** The x of each text object's `Tm`, in content order, per baseline. */
112
+ function textOrigins(content) {
113
+ const out = new Map();
114
+ for (const m of content.matchAll(/^1 0 0 1 (-?[\d.]+) (-?[\d.]+) Tm$/gm)) {
115
+ const y = Math.round(Number(m[2]));
116
+ out.set(y, [...(out.get(y) ?? []), Number(m[1])]);
117
+ }
118
+ return out;
119
+ }
120
+ const hasPdftotext = (() => {
121
+ try {
122
+ execFileSync('pdftotext', ['-v'], { stdio: 'ignore' });
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ })();
129
+ /** The text pdftotext reads from the PDF in reading order, one row per
130
+ * line, without the embedding controls Poppler wraps the right-to-left
131
+ * lines and their left-to-right runs in, and without spaces: Poppler
132
+ * infers them from the gaps between glyphs, and puts one before a comma
133
+ * that ends a right-to-left word. */
134
+ function extract(bytes, name) {
135
+ const file = path.join(os.tmpdir(), `postext-arabic-${process.pid}-${name}.pdf`);
136
+ fs.writeFileSync(file, bytes);
137
+ const text = execFileSync('pdftotext', ['-enc', 'UTF-8', file, '-'], { encoding: 'utf8' });
138
+ fs.unlinkSync(file);
139
+ return text.split('\n').map(squeeze).filter(Boolean);
140
+ }
141
+ function squeeze(text) {
142
+ return text.replace(/[\s\f\u202a-\u202e]/g, '');
143
+ }
144
+ /** Write a PDF for a look (`POSTEXT_ARABIC_PDF_DIR`). */
145
+ function keep(bytes, name) {
146
+ const dir = process.env.POSTEXT_ARABIC_PDF_DIR;
147
+ if (dir)
148
+ fs.writeFileSync(path.join(dir, `${name}.pdf`), bytes);
149
+ }
150
+ describe('Arabic in the PDF (HarfBuzz)', () => {
151
+ it('never loads HarfBuzz for a document without complex scripts', async () => {
152
+ const doc = buildDocument({ markdown: 'Latin text, 2024 (only).' }, config());
153
+ await renderToPdf(doc, { fontProvider });
154
+ expect(complexShaperReady()).toBe(false);
155
+ });
156
+ it('loads it for a document with Arabic and raises and lowers the marks', async () => {
157
+ const doc = buildDocument({ markdown: PARAGRAPHS.join('\n\n') }, config());
158
+ const bytes = await renderToPdf(doc, { fontProvider });
159
+ expect(complexShaperReady()).toBe(true);
160
+ keep(bytes, 'legacy');
161
+ const content = pageContent(await PDFDocument.load(bytes));
162
+ // The basmala's harakat stack with the text rise; the rise is reset
163
+ // before the glyphs on the baseline.
164
+ const rises = [...content.matchAll(/^(-?[\d.]+) Ts$/gm)].map((m) => Number(m[1]));
165
+ expect(rises.some((r) => r > 1)).toBe(true);
166
+ expect(rises.some((r) => r < -1)).toBe(true);
167
+ expect(rises).toContain(0);
168
+ });
169
+ it('lays a line with directions out in its order and paints it in logical order', async () => {
170
+ const doc = directed(buildDocument({ markdown: PARAGRAPHS.join('\n\n') }, config({ textAlign: 'right' })));
171
+ const mixed = lines(doc).find((l) => l.text.startsWith('كلمة'));
172
+ // The segments' visual order: هـ, ١٤٤٥, سنة, (قوس), كلمة, Latin 2024, كلمة.
173
+ const visual = mixed.order.map((i) => mixed.segments[i]).filter((s) => s.kind === 'text').map((s) => s.text);
174
+ expect(visual).toEqual(['هـ', '١٤٤٥', 'سنة', '(قوس)', 'كلمة', 'Latin', '2024', 'كلمة']);
175
+ const bytes = await renderToPdf(doc, { fontProvider });
176
+ keep(bytes, 'directed');
177
+ const content = pageContent(await PDFDocument.load(bytes));
178
+ // The words are laid out left to right in `order` and painted in
179
+ // logical order: the first word written is the rightmost one, the
180
+ // text objects read هـ … كلمة from right to left, Latin and 2024 left
181
+ // to right between them.
182
+ const xs = textOrigins(content).get(Math.round(260 - mixed.baseline));
183
+ // (A tagged render paints the word spaces too.)
184
+ const words = mixed.segments.filter((s) => s.text !== '').map((s) => s.text);
185
+ expect(xs).toHaveLength(words.length);
186
+ const byX = words.map((w, i) => ({ w, x: xs[i] })).sort((p, q) => p.x - q.x).map((p) => p.w).filter((w) => w.trim() !== '');
187
+ expect(byX).toEqual(visual);
188
+ expect(xs[0]).toBeGreaterThan(xs[xs.length - 1]);
189
+ // Glyphs read back through the ToUnicode map; a vocalised letter (its
190
+ // marks raised and lowered) reads through a span of its own, in
191
+ // logical order.
192
+ const spans = actualTexts(content);
193
+ expect(spans.length).toBeGreaterThan(4);
194
+ for (const span of spans) {
195
+ expect(BASMALA).toContain(span);
196
+ expect(span).toMatch(/^\p{L}\p{M}+$/u);
197
+ }
198
+ expect(spans).toContain('لَّ');
199
+ });
200
+ it('turns character spacing off for joining letters only', async () => {
201
+ const doc = directed(buildDocument({ markdown: `${SALAM} AAA` }, config()));
202
+ for (const block of doc.pages[0].columns[0].blocks)
203
+ block.letterSpacing = 0.5;
204
+ const content = pageContent(await render(doc));
205
+ const objects = content.split('BT\n').slice(1);
206
+ const arabic = objects.filter((o) => /\[ .*\] TJ/.test(o) && /^0 Tc$/m.test(o));
207
+ expect(arabic.length).toBeGreaterThan(0);
208
+ // The Latin word keeps the line's spacing.
209
+ expect(content).toMatch(/^0\.5 Tc$/m);
210
+ });
211
+ it('leaves the kashidas justification inserted out of the text read', async () => {
212
+ const doc = directed(buildDocument({ markdown: 'كتــاب الله' }, config()));
213
+ expect(actualTexts(pageContent(await render(doc)))).toEqual([]);
214
+ lines(doc)[0].kashida = 2;
215
+ const bytes = await renderToPdf(doc, { fontProvider });
216
+ const spans = actualTexts(pageContent(await PDFDocument.load(bytes)));
217
+ // Amiri sets the two tatweels as one glyph: it reads as nothing.
218
+ expect(spans).toEqual(['']);
219
+ if (hasPdftotext)
220
+ expect(extract(bytes, 'kashida')).toEqual([squeeze('كتاب الله')]);
221
+ });
222
+ it('keeps a tatweel the author typed when the word lists the ones inserted', async () => {
223
+ // The author typed one tatweel; justification added two after it.
224
+ const doc = directed(buildDocument({ markdown: 'كتـــاب الله' }, config()));
225
+ const line = lines(doc)[0];
226
+ line.kashida = 2;
227
+ const seg = line.segments.find((s) => s.text.includes('ـ'));
228
+ seg.kashida = [3, 4];
229
+ const bytes = await renderToPdf(doc, { fontProvider });
230
+ // Amiri joins the tatweels and the alef into one glyph: its span reads
231
+ // the typed tatweel and the alef.
232
+ expect(actualTexts(pageContent(await PDFDocument.load(bytes)))).toEqual(['ـا']);
233
+ // Poppler reads a span of two characters in a right-to-left line
234
+ // backwards (see the module notes): count the tatweels only.
235
+ if (hasPdftotext)
236
+ expect([...extract(bytes, 'typed-kashida').join('')].filter((c) => c === 'ـ')).toHaveLength(1);
237
+ });
238
+ it.skipIf(!hasPdftotext)('reads back in logical order (pdftotext)', async () => {
239
+ for (const [name, doc] of [
240
+ ['legacy', legacy(buildDocument({ markdown: PARAGRAPHS.join('\n\n') }, config()))],
241
+ ['directed', directed(buildDocument({ markdown: PARAGRAPHS.join('\n\n') }, config({ textAlign: 'justify' })))],
242
+ ]) {
243
+ const rows = extract(await renderToPdf(doc, { fontProvider }), name);
244
+ // Poppler reads the unvocalised lines as written. (In a vocalised word
245
+ // it puts a mark before its letter now and then.)
246
+ for (const p of [SALAM, KASHIDA, MIXED])
247
+ expect(rows, name).toContain(squeeze(p));
248
+ expect(rows, name).toHaveLength(4);
249
+ }
250
+ });
251
+ it('reads unvocalised words through the ToUnicode map alone', async () => {
252
+ // No mark raised, no glyph that reads as other text: no span. (Poppler
253
+ // takes a line's direction from most of its letters: Latin here.)
254
+ const doc = buildDocument({ markdown: 'AAA Latin السلام عليكم ZZZ Latin' }, config());
255
+ const bytes = await renderToPdf(doc, { fontProvider });
256
+ expect(actualTexts(pageContent(await PDFDocument.load(bytes)))).toEqual([]);
257
+ if (hasPdftotext)
258
+ expect(extract(bytes, 'ltr')).toEqual([squeeze('AAA Latin السلام عليكم ZZZ Latin')]);
259
+ });
260
+ });
261
+ //# sourceMappingURL=arabicShaping.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arabicShaping.test.js","sourceRoot":"","sources":["../../src/__tests__/arabicShaping.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=complexShaping.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complexShaping.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/complexShaping.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,145 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
3
+ import fontkit from '@pdf-lib/fontkit';
4
+ import { clusterTexts, complexShaperReady, joinsLetters, loadComplexShaper, needsComplexShaping, shapeRun, } from '../complexShaping';
5
+ import { lineRuns, resolveParagraph } from 'postext';
6
+ // Amiri and Noto Naskh Arabic cut to the test sentences (OFL, see
7
+ // fixtures/arabic/make_fixtures.py).
8
+ const AMIRI = new Uint8Array(readFileSync(new URL('./fixtures/arabic/amiri-subset.ttf', import.meta.url)));
9
+ const NOTO = new Uint8Array(readFileSync(new URL('./fixtures/arabic/noto-naskh-subset.ttf', import.meta.url)));
10
+ /** Glyphs in `hb-shape --no-glyph-names` notation
11
+ * (`gid=cluster@xOffset,yOffset+xAdvance`), as native HarfBuzz 13.0.1
12
+ * shaped these fixtures. */
13
+ function hbShape(serialized) {
14
+ return serialized.slice(1, -1).split('|').map((item) => {
15
+ const m = /^(\d+)=(\d+)(?:@(-?\d+),(-?\d+))?\+(-?\d+)$/.exec(item);
16
+ return { gid: Number(m[1]), cluster: Number(m[2]), xOffset: Number(m[3] ?? 0), yOffset: Number(m[4] ?? 0), xAdvance: Number(m[5]) };
17
+ });
18
+ }
19
+ function shaped(font, text, direction = 'rtl') {
20
+ const run = shapeRun(font, text, { direction });
21
+ expect(run).toBeDefined();
22
+ return run.glyphs.map(({ gid, cluster, xOffset, yOffset, xAdvance }) => ({ gid, cluster, xOffset, yOffset, xAdvance }));
23
+ }
24
+ describe('needsComplexShaping', () => {
25
+ it('flags right-to-left and joining scripts only', () => {
26
+ expect(needsComplexShaping('السلام')).toBe(true);
27
+ expect(needsComplexShaping('Latin 2024 (x)')).toBe(false);
28
+ expect(needsComplexShaping('שָׁלוֹם')).toBe(true);
29
+ expect(needsComplexShaping('ܫܠܡܐ')).toBe(true); // Syriac
30
+ expect(needsComplexShaping('ދިވެހި')).toBe(true); // Thaana
31
+ expect(needsComplexShaping('ߒߞߏ')).toBe(true); // N'Ko
32
+ expect(needsComplexShaping('ﷲ')).toBe(true); // presentation form
33
+ expect(needsComplexShaping('𞤀𞤣𞤤𞤢𞤥')).toBe(true); // Adlam (supplementary plane)
34
+ expect(needsComplexShaping('中文 Ελληνικά русский')).toBe(false);
35
+ });
36
+ it('tells joining letters from right-to-left ones that do not join', () => {
37
+ expect(joinsLetters('كتاب')).toBe(true);
38
+ expect(joinsLetters('ܫܠܡܐ')).toBe(true);
39
+ expect(joinsLetters('ߒߞߏ')).toBe(true);
40
+ expect(joinsLetters('שָׁלוֹם')).toBe(false);
41
+ expect(joinsLetters('ދިވެހި')).toBe(false);
42
+ expect(joinsLetters('Latin ١٤٤٥')).toBe(true); // Arabic-Indic digits sit in the Arabic block
43
+ expect(joinsLetters('Latin 2024')).toBe(false);
44
+ });
45
+ });
46
+ // The PDF cuts text that comes without directions (a running head, a
47
+ // caption, a VDT from before #369) with the engine's UAX #9 (`bidi.ts`),
48
+ // as the layout resolves a paragraph.
49
+ describe('bidi runs of undirected text (engine bidi)', () => {
50
+ const texts = (text, base = 'auto') => lineRuns(resolveParagraph(text, base)).map((r) => `${r.level % 2 === 1 ? 'R' : 'L'}:${text.slice(r.start, r.end)}`);
51
+ it('keeps numbers and Latin words left to right inside an Arabic line', () => {
52
+ expect(texts('عام 2024 م')).toEqual(['R: م', 'L:2024', 'R:عام ']);
53
+ expect(texts('سنة ١٤٤٥ هـ')).toEqual(['R: هـ', 'L:١٤٤٥', 'R:سنة ']);
54
+ // A number after a Latin word joins it (W7); the brackets go with the
55
+ // Arabic around them (N0).
56
+ expect(texts('كلمة Latin 2024 كلمة (قوس)')).toEqual(['R: كلمة (قوس)', 'L:Latin 2024', 'R:كلمة ']);
57
+ // Decimal and thousands separators stay inside the number (W4).
58
+ expect(texts('بلغ 1,234.5 كم')).toEqual(['R: كم', 'L:1,234.5', 'R:بلغ ']);
59
+ // A percent sign after European digits joins them (W5).
60
+ expect(texts('AAA 50% ZZZ', 'rtl')).toEqual(['L:AAA 50% ZZZ']);
61
+ });
62
+ it('keeps an Arabic phrase whole inside a left-to-right line', () => {
63
+ expect(texts('AAA السلام عليكم ZZZ')).toEqual(['L:AAA ', 'R:السلام عليكم', 'L: ZZZ']);
64
+ expect(texts('AAA عام 2024 ZZZ')).toEqual(['L:AAA ', 'L:2024', 'R:عام ', 'L: ZZZ']);
65
+ });
66
+ it('honours isolates and embeddings the minimal splitter read as neutrals', () => {
67
+ // An RLI … PDI isolate keeps the Latin word inside the Arabic run's
68
+ // order; the old splitter took the controls as neutrals.
69
+ expect(texts('AAA \u2067كتاب BBB\u2069 ZZZ').map((t) => t.replace(/[\u2066-\u2069]/g, ''))).toEqual(['L:AAA ', 'L:BBB', 'R:كتاب ', 'L: ZZZ']);
70
+ });
71
+ });
72
+ describe('shapeRun (HarfBuzz)', () => {
73
+ beforeAll(async () => {
74
+ expect(await loadComplexShaper()).toBe(true);
75
+ expect(complexShaperReady()).toBe(true);
76
+ });
77
+ it('shapes vocalised Amiri as native HarfBuzz does, marks raised and lowered', () => {
78
+ // بِسْمِ ٱللَّهِ: the shadda and fatha of Allah stack (two y offsets in
79
+ // one cluster), the kasra sits under the ba; a letter and its marks are
80
+ // one cluster.
81
+ const glyphs = shaped(AMIRI, 'بِسْمِ ٱللَّهِ');
82
+ expect(glyphs).toEqual(hbShape('[29=12@-75,181+0|161=12+352|209=9@65,-164+0|30=9@-135,-215+0|160=9+230|159=8+155|44=7+217|1=6+292|29=4@76,0+0|527=4+565|31=2@-175,0+0|494=2+291|29=0@-133,0+0|491=0+219]'));
83
+ expect(glyphs.filter((g) => g.yOffset !== 0).length).toBeGreaterThanOrEqual(3);
84
+ // Visual order: the clusters run right to left.
85
+ expect(glyphs[0].cluster).toBeGreaterThan(glyphs[glyphs.length - 1].cluster);
86
+ expect(shaped(AMIRI, 'ٱلرَّحْمَٰنِ')).toEqual(hbShape('[29=10@56,-64+0|315=10+615|43=7@-223,88+0|28=7@-173,0+0|575=7+216|31=5@68,0+0|573=5+514|209=2@66,51+0|30=2@-134,0+0|483=2+292|482=1+216|44=0+217]'));
87
+ });
88
+ it('shapes Noto Naskh marks where native HarfBuzz puts them', () => {
89
+ expect(shaped(NOTO, 'بِسْمِ ٱللَّهِ')).toEqual(hbShape('[125=12@149,-62+0|49=12+452|122=9@-3,104+0|118=9@-1,-18+0|36=9+245|38=8+212|113=7@11,-53+0|2=7+238|1=6+221|125=4@238,-62+0|43=4+528|123=2@294,-222+0|20=2+663|125=0@39,-201+0|114=0@70,-31+0|10=0+275]'));
90
+ expect(shaped(NOTO, 'كتـــاب')).toEqual(hbShape('[114=6@308,-31+0|6=6+772|3=5+253|58=4+210|58=3+210|58=2+210|111=1@53,-444+0|9=1+360|33=0+415]'));
91
+ });
92
+ it('joins lam-alef and ligates Amiri tatweels', () => {
93
+ expect(shaped(AMIRI, 'لا إله')).toEqual(hbShape('[567=5+404|565=4+141|10=3+217|1=2+292|375=1+340|373=0+302]'));
94
+ expect(shaped(AMIRI, 'كتـــاب')).toEqual(hbShape('[12=6+926|694=2+786|296=1+244|307=0+659]'));
95
+ });
96
+ it('mirrors brackets in a right-to-left run but not the ornate Quranic ones', () => {
97
+ const ltr = shaped(AMIRI, '()', 'ltr');
98
+ const rtl = shaped(AMIRI, '(قوس)');
99
+ expect(rtl).toEqual(hbShape('[2=4+458|17=3+992|321=2+345|297=1+295|3=0+458]'));
100
+ // The leftmost glyph is the closing bracket's, drawn as an opening one.
101
+ expect(rtl[0].gid).toBe(ltr[0].gid);
102
+ expect(rtl[rtl.length - 1].gid).toBe(ltr[1].gid);
103
+ // U+FD3E/U+FD3F are not mirrored (Unicode 14): each keeps its glyph.
104
+ const ornate = shaped(AMIRI, '﴿﴾', 'ltr');
105
+ expect(shaped(AMIRI, '﴿قوس﴾').map((g) => g.gid)).toEqual([ornate[1].gid, 17, 321, 297, ornate[0].gid]);
106
+ });
107
+ it('gives each cluster its characters, once', () => {
108
+ const text = 'لا إله';
109
+ // Noto Naskh sets إ as an alef and a hamza below: two glyphs of one
110
+ // cluster, the first met carrying the letter.
111
+ const run = shapeRun(NOTO, text, { direction: 'rtl' });
112
+ expect(run.glyphs.map((g) => g.cluster)).toEqual([5, 4, 3, 3, 2, 1, 0]);
113
+ expect(clusterTexts(run, text)).toEqual(['ه', 'ل', 'إ', '', ' ', 'ا', 'ل']);
114
+ expect([...clusterTexts(run, text)].reverse().join('')).toBe(text);
115
+ });
116
+ it('caches runs per face, text and direction', () => {
117
+ const a = shapeRun(AMIRI, 'السلام عليكم', { direction: 'rtl' });
118
+ expect(shapeRun(AMIRI, 'السلام عليكم', { direction: 'rtl' })).toBe(a);
119
+ expect(shapeRun(AMIRI, 'السلام عليكم', { direction: 'ltr' })).not.toBe(a);
120
+ expect(shapeRun(NOTO, 'السلام عليكم', { direction: 'rtl' })).not.toBe(a);
121
+ });
122
+ it('applies the font features asked for', () => {
123
+ const plain = shaped(AMIRI, 'Latin', 'ltr');
124
+ const run = shapeRun(AMIRI, 'Latin', { direction: 'ltr', features: { kern: false, liga: false } });
125
+ expect(run.glyphs.map((g) => g.gid)).toEqual(plain.map((g) => g.gid));
126
+ });
127
+ it('shapes a book of words far faster than fontkit', () => {
128
+ const words = 'السلام عليكم ورحمة الله وبركاته لا إله إلا الله كتـــاب سنة عام كلمة قوس'.split(' ');
129
+ const marks = ['', 'َ', 'ِ', 'ُ', 'ّ', 'ْ'];
130
+ const book = [];
131
+ for (let i = 0; i < 4000; i++)
132
+ book.push(words[i % words.length] + marks[i % marks.length] + (i % 7 === 0 ? 'ـ' : ''));
133
+ const face = fontkit.create(Buffer.from(AMIRI));
134
+ let t = performance.now();
135
+ for (const w of book)
136
+ face.layout(w);
137
+ const fontkitMs = performance.now() - t;
138
+ t = performance.now();
139
+ for (const w of book)
140
+ shapeRun(AMIRI, `${w}​`, { direction: 'rtl' });
141
+ const harfbuzzMs = performance.now() - t;
142
+ expect(harfbuzzMs).toBeLessThan(fontkitMs);
143
+ });
144
+ });
145
+ //# sourceMappingURL=complexShaping.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complexShaping.test.js","sourceRoot":"","sources":["../../src/__tests__/complexShaping.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=designTextRtl.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"designTextRtl.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/designTextRtl.test.ts"],"names":[],"mappings":""}