postext-pdf 1.22.3 → 1.24.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 (54) hide show
  1. package/dist/__tests__/booktabsTable.test.d.ts +2 -0
  2. package/dist/__tests__/booktabsTable.test.d.ts.map +1 -0
  3. package/dist/__tests__/booktabsTable.test.js +101 -0
  4. package/dist/__tests__/booktabsTable.test.js.map +1 -0
  5. package/dist/__tests__/codeBlocks.test.d.ts +2 -0
  6. package/dist/__tests__/codeBlocks.test.d.ts.map +1 -0
  7. package/dist/__tests__/codeBlocks.test.js +135 -0
  8. package/dist/__tests__/codeBlocks.test.js.map +1 -0
  9. package/dist/__tests__/dropCap.test.d.ts +2 -0
  10. package/dist/__tests__/dropCap.test.d.ts.map +1 -0
  11. package/dist/__tests__/dropCap.test.js +125 -0
  12. package/dist/__tests__/dropCap.test.js.map +1 -0
  13. package/dist/__tests__/hardBreaks.test.d.ts +2 -0
  14. package/dist/__tests__/hardBreaks.test.d.ts.map +1 -0
  15. package/dist/__tests__/hardBreaks.test.js +107 -0
  16. package/dist/__tests__/hardBreaks.test.js.map +1 -0
  17. package/dist/__tests__/lineNumbers.test.d.ts +2 -0
  18. package/dist/__tests__/lineNumbers.test.d.ts.map +1 -0
  19. package/dist/__tests__/lineNumbers.test.js +106 -0
  20. package/dist/__tests__/lineNumbers.test.js.map +1 -0
  21. package/dist/__tests__/tabStops.test.d.ts +2 -0
  22. package/dist/__tests__/tabStops.test.d.ts.map +1 -0
  23. package/dist/__tests__/tabStops.test.js +105 -0
  24. package/dist/__tests__/tabStops.test.js.map +1 -0
  25. package/dist/__tests__/textWrap.test.d.ts +2 -0
  26. package/dist/__tests__/textWrap.test.d.ts.map +1 -0
  27. package/dist/__tests__/textWrap.test.js +116 -0
  28. package/dist/__tests__/textWrap.test.js.map +1 -0
  29. package/dist/__tests__/verseLines.test.d.ts +2 -0
  30. package/dist/__tests__/verseLines.test.d.ts.map +1 -0
  31. package/dist/__tests__/verseLines.test.js +140 -0
  32. package/dist/__tests__/verseLines.test.js.map +1 -0
  33. package/dist/pdf-backend/blockRender.d.ts.map +1 -1
  34. package/dist/pdf-backend/blockRender.js +79 -18
  35. package/dist/pdf-backend/blockRender.js.map +1 -1
  36. package/dist/pdf-backend/fontHelpers.d.ts.map +1 -1
  37. package/dist/pdf-backend/fontHelpers.js +7 -1
  38. package/dist/pdf-backend/fontHelpers.js.map +1 -1
  39. package/dist/pdf-backend/headerFooter.d.ts +7 -2
  40. package/dist/pdf-backend/headerFooter.d.ts.map +1 -1
  41. package/dist/pdf-backend/headerFooter.js +11 -6
  42. package/dist/pdf-backend/headerFooter.js.map +1 -1
  43. package/dist/pdf-backend/index.d.ts.map +1 -1
  44. package/dist/pdf-backend/index.js +6 -0
  45. package/dist/pdf-backend/index.js.map +1 -1
  46. package/dist/pdf-backend/renderResourceBlock.d.ts.map +1 -1
  47. package/dist/pdf-backend/renderResourceBlock.js +6 -1
  48. package/dist/pdf-backend/renderResourceBlock.js.map +1 -1
  49. package/dist/pdf-backend/structureFlow.d.ts.map +1 -1
  50. package/dist/pdf-backend/structureFlow.js +6 -0
  51. package/dist/pdf-backend/structureFlow.js.map +1 -1
  52. package/dist/pdf-backend/tagging.d.ts +1 -1
  53. package/dist/pdf-backend/tagging.d.ts.map +1 -1
  54. package/package.json +3 -3
@@ -0,0 +1,106 @@
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, spawnSync } from 'node:child_process';
6
+ import { PDFArray, PDFDocument, PDFRawStream, decodePDFRawStream } from 'pdf-lib';
7
+ import fontkit from '@pdf-lib/fontkit';
8
+ import { buildDocument } from 'postext';
9
+ import { renderToPdf } from '../pdf-backend';
10
+ import { parseFontString } from '../fontString';
11
+ // #621: line numbers in the margin. They are painted beside their lines,
12
+ // marked as layout artifacts in a tagged PDF, and set under an empty
13
+ // `/ActualText`, so extracted and copied text runs from line to line
14
+ // without them.
15
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
16
+ const fontProvider = async () => new Uint8Array(fontBytes);
17
+ const face = fontkit.create(fontBytes);
18
+ class StubCtx {
19
+ font = '';
20
+ measureText(s) {
21
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
22
+ const run = face.layout(s);
23
+ return { width: (run.advanceWidth / face.unitsPerEm) * sizePx };
24
+ }
25
+ }
26
+ globalThis.OffscreenCanvas = class {
27
+ getContext() {
28
+ return new StubCtx();
29
+ }
30
+ };
31
+ const pt = (value) => ({ value, unit: 'pt' });
32
+ const config = {
33
+ page: { width: pt(300), height: pt(480), margins: { top: pt(24), bottom: pt(24), left: pt(48), right: pt(48) } },
34
+ locale: 'en-us',
35
+ layout: { layoutType: 'single' },
36
+ header: { elements: [] },
37
+ footer: { elements: [] },
38
+ lineNumbers: { enabled: true, interval: 5 },
39
+ };
40
+ const has = (cmd, arg) => spawnSync(cmd, [arg], { encoding: 'utf8' }).status === 0;
41
+ const hasPdftotext = (() => {
42
+ try {
43
+ execFileSync('pdftotext', ['-v'], { stdio: 'ignore' });
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ })();
50
+ const hasVerapdf = process.env.VERAPDF === '1' && has('verapdf', '--version');
51
+ function pageContent(pdf) {
52
+ const contents = pdf.getPage(0).node.Contents();
53
+ const refs = contents instanceof PDFArray ? contents.asArray() : [contents];
54
+ return refs
55
+ .map((ref) => {
56
+ const s = pdf.context.lookup(ref);
57
+ return s instanceof PDFRawStream ? new TextDecoder('latin1').decode(decodePDFRawStream(s).decode()) : '';
58
+ })
59
+ .join('\n');
60
+ }
61
+ const POEM = [
62
+ ':::verse{align=start}',
63
+ 'Yet once more, O ye laurels, and once more',
64
+ 'Ye myrtles brown, with ivy never sere,',
65
+ 'I come to pluck your berries harsh and crude,',
66
+ 'And with forced fingers rude',
67
+ 'Shatter your leaves before the mellowing year.',
68
+ 'Bitter constraint and sad occasion dear',
69
+ 'Compels me to disturb your season due;',
70
+ 'For Lycidas is dead, dead ere his prime,',
71
+ 'Young Lycidas, and hath not left his peer.',
72
+ 'Who would not sing for Lycidas? he knew',
73
+ ':::',
74
+ ].join('\n');
75
+ describe('line numbers in the PDF (#621)', () => {
76
+ const doc = buildDocument({ markdown: POEM, metadata: { title: 'Lycidas' } }, config);
77
+ it('paints the numbers as layout artifacts, each under an empty /ActualText', async () => {
78
+ expect(doc.pages[0].lineNumberMarks.map((m) => m.label)).toEqual(['5', '10']);
79
+ const pdf = await PDFDocument.load(await renderToPdf(doc, { fontProvider }));
80
+ const content = pageContent(pdf);
81
+ // Two numbers: two empty spans, inside a layout artifact (no marked
82
+ // content of the structure between them).
83
+ expect(content.match(/\/Span\s*<<\s*\/ActualText\s*<FEFF>\s*>>\s*BDC/g) ?? []).toHaveLength(2);
84
+ const before = content.slice(0, content.indexOf('<FEFF>'));
85
+ expect(before.lastIndexOf('/Artifact')).toBeGreaterThan(before.lastIndexOf('/MCID'));
86
+ expect(before.slice(before.lastIndexOf('/Artifact'))).toMatch(/^\/Artifact\s*<<[^>]*\/Layout/);
87
+ const tail = content.slice(content.indexOf('<FEFF>'));
88
+ expect(tail.slice(0, tail.lastIndexOf('<FEFF>'))).not.toContain('/MCID');
89
+ });
90
+ it.skipIf(!hasPdftotext)('leaves them out of the extracted text', async () => {
91
+ const file = path.join(os.tmpdir(), `postext-linenumbers-${process.pid}.pdf`);
92
+ fs.writeFileSync(file, await renderToPdf(doc, { fontProvider }));
93
+ const text = execFileSync('pdftotext', ['-raw', file, '-'], { encoding: 'utf8' });
94
+ fs.unlinkSync(file);
95
+ expect(text).toContain('Shatter your leaves before the mellowing year.');
96
+ expect(text).not.toMatch(/\b(5|10)\b/);
97
+ });
98
+ it.skipIf(!hasVerapdf)('passes veraPDF (PDF/UA-1)', async () => {
99
+ const file = path.join(os.tmpdir(), `postext-linenumbers-ua-${process.pid}.pdf`);
100
+ fs.writeFileSync(file, await renderToPdf(doc, { fontProvider }));
101
+ const out = spawnSync('verapdf', ['--flavour', 'ua1', '--format', 'text', file], { encoding: 'utf8' });
102
+ fs.unlinkSync(file);
103
+ expect(out.stdout).toMatch(/^PASS /m);
104
+ }, 120_000);
105
+ });
106
+ //# sourceMappingURL=lineNumbers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineNumbers.test.js","sourceRoot":"","sources":["../../src/__tests__/lineNumbers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tabStops.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabStops.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tabStops.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,105 @@
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, spawnSync } from 'node:child_process';
6
+ import { PDFArray, PDFDict, PDFDocument, PDFName, PDFRef } from 'pdf-lib';
7
+ import fontkit from '@pdf-lib/fontkit';
8
+ import { buildDocument } from 'postext';
9
+ import { renderToPdf } from '../pdf-backend';
10
+ import { parseFontString } from '../fontString';
11
+ // #622: tab stops with leaders. The leader (dots, or a rule) is painted as
12
+ // a layout artifact under an empty `/ActualText`: text extraction reads the
13
+ // words on either side of the tab, never the dots.
14
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
15
+ const fontProvider = async () => new Uint8Array(fontBytes);
16
+ const face = fontkit.create(fontBytes);
17
+ class StubCtx {
18
+ font = '';
19
+ measureText(s) {
20
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
21
+ const run = face.layout(s);
22
+ return { width: (run.advanceWidth / face.unitsPerEm) * sizePx };
23
+ }
24
+ }
25
+ globalThis.OffscreenCanvas = class {
26
+ getContext() {
27
+ return new StubCtx();
28
+ }
29
+ };
30
+ const pt = (value) => ({ value, unit: 'pt' });
31
+ const config = {
32
+ page: { width: pt(300), height: pt(480), margins: { top: pt(24), bottom: pt(24), left: pt(24), right: pt(24) } },
33
+ locale: 'en-us',
34
+ bodyText: { textAlign: 'justify', tabStops: [{ position: 'end', align: 'end', leader: '.' }] },
35
+ layout: { layoutType: 'single' },
36
+ header: { elements: [] },
37
+ footer: { elements: [] },
38
+ };
39
+ const has = (cmd, arg) => spawnSync(cmd, [arg], { encoding: 'utf8' }).status === 0;
40
+ const hasPdftotext = (() => {
41
+ try {
42
+ execFileSync('pdftotext', ['-v'], { stdio: 'ignore' });
43
+ return true;
44
+ }
45
+ catch {
46
+ return false;
47
+ }
48
+ })();
49
+ const hasVerapdf = process.env.VERAPDF === '1' && has('verapdf', '--version');
50
+ /** The structure types of the tree, depth first. */
51
+ function structTypes(pdf) {
52
+ const out = [];
53
+ const visit = (ref) => {
54
+ const dict = ref instanceof PDFRef ? pdf.context.lookup(ref) : ref;
55
+ if (dict instanceof PDFArray) {
56
+ for (const kid of dict.asArray())
57
+ visit(kid);
58
+ return;
59
+ }
60
+ if (!(dict instanceof PDFDict))
61
+ return;
62
+ const s = dict.get(PDFName.of('S'));
63
+ if (s instanceof PDFName)
64
+ out.push(s.decodeText());
65
+ const k = dict.get(PDFName.of('K'));
66
+ for (const kid of k instanceof PDFArray ? k.asArray() : k ? [k] : [])
67
+ visit(kid);
68
+ };
69
+ const root = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
70
+ visit(root.get(PDFName.of('K')));
71
+ return out;
72
+ }
73
+ const TEXT = [
74
+ 'Soup of the day\t8.50',
75
+ '',
76
+ 'Roast lamb\t21.00',
77
+ '',
78
+ 'Name: :tab{at=end leader=rule}',
79
+ ].join('\n');
80
+ describe('tab stops and leaders in the PDF (#622)', () => {
81
+ const doc = buildDocument({ markdown: TEXT, metadata: { title: 'A menu' } }, config);
82
+ const lines = doc.blocks.filter((b) => b.type === 'paragraph').map((b) => b.lines[0]);
83
+ it('sets the leaders as segments of their own, one paragraph each', async () => {
84
+ expect(lines.map((l) => l.segments.find((s) => s.leader)?.leader)).toEqual(['text', 'text', 'rule']);
85
+ const pdf = await PDFDocument.load(await renderToPdf(doc, { fontProvider }));
86
+ expect(structTypes(pdf).filter((t) => t === 'P')).toHaveLength(3);
87
+ });
88
+ it.skipIf(!hasPdftotext)('reads the words and not the dots in the extracted text', async () => {
89
+ const file = path.join(os.tmpdir(), `postext-tabs-${process.pid}.pdf`);
90
+ fs.writeFileSync(file, await renderToPdf(doc, { fontProvider }));
91
+ const text = execFileSync('pdftotext', ['-raw', file, '-'], { encoding: 'utf8' });
92
+ fs.unlinkSync(file);
93
+ expect(text).toMatch(/Soup of the day\s+8\.50/);
94
+ expect(text).toMatch(/Roast lamb\s+21\.00/);
95
+ expect(text).not.toMatch(/\.\.\./);
96
+ });
97
+ it.skipIf(!hasVerapdf)('passes veraPDF (PDF/UA-1)', async () => {
98
+ const file = path.join(os.tmpdir(), `postext-tabs-ua-${process.pid}.pdf`);
99
+ fs.writeFileSync(file, await renderToPdf(doc, { fontProvider }));
100
+ const out = spawnSync('verapdf', ['--flavour', 'ua1', '--format', 'text', file], { encoding: 'utf8' });
101
+ fs.unlinkSync(file);
102
+ expect(out.stdout).toMatch(/^PASS /m);
103
+ }, 120_000);
104
+ });
105
+ //# sourceMappingURL=tabStops.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabStops.test.js","sourceRoot":"","sources":["../../src/__tests__/tabStops.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=textWrap.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textWrap.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/textWrap.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { execFileSync, spawnSync } from 'node:child_process';
6
+ import { PDFArray, PDFDict, PDFDocument, PDFName, PDFRef } from 'pdf-lib';
7
+ import fontkit from '@pdf-lib/fontkit';
8
+ import { buildDocument } from 'postext';
9
+ import { renderToPdf } from '../pdf-backend';
10
+ import { parseFontString } from '../fontString';
11
+ // #627: text wrapped round a picture and a box. The lines beside them are
12
+ // painted at their own measure; a tagged PDF reads the picture and the box
13
+ // where they stand in the text, before the paragraph that runs beside them.
14
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
15
+ const fontProvider = async () => new Uint8Array(fontBytes);
16
+ const face = fontkit.create(fontBytes);
17
+ class StubCtx {
18
+ font = '';
19
+ measureText(s) {
20
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
21
+ const run = face.layout(s);
22
+ return { width: (run.advanceWidth / face.unitsPerEm) * sizePx };
23
+ }
24
+ }
25
+ globalThis.OffscreenCanvas = class {
26
+ getContext() {
27
+ return new StubCtx();
28
+ }
29
+ };
30
+ const pt = (value) => ({ value, unit: 'pt' });
31
+ const PNG = Uint8Array.from(Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==', 'base64'));
32
+ const resources = [{
33
+ id: 'bed', typeId: 'figure', kind: 'bitmap', caption: 'The planting bed.', altText: 'A raised bed', createdAt: 0, updatedAt: 0,
34
+ bitmap: { fileId: 'x.png', format: 'png', width: 400, height: 300 },
35
+ placement: { position: 'here', wrap: 'right', width: 0.4 },
36
+ }];
37
+ const words = (n, tag) => Array.from({ length: n }, (_, i) => `${tag}${i}`).join(' ');
38
+ const markdown = [
39
+ '# Beds',
40
+ '',
41
+ `${words(30, 'opening')}.`,
42
+ '',
43
+ '::resource{id="bed"}',
44
+ '',
45
+ `${words(120, 'beside')}.`,
46
+ '',
47
+ ':::callout{wrap="left" width=0.4}',
48
+ 'Sow thinly.',
49
+ ':::',
50
+ '',
51
+ `${words(120, 'after')}.`,
52
+ ].join('\n');
53
+ const config = {
54
+ page: { width: pt(360), height: pt(560), margins: { top: pt(24), bottom: pt(24), left: pt(24), right: pt(24) } },
55
+ layout: { layoutType: 'single' },
56
+ locale: 'en-us',
57
+ header: { elements: [] },
58
+ footer: { elements: [] },
59
+ };
60
+ function readElem(pdf, ref) {
61
+ const dict = pdf.context.lookup(ref, PDFDict);
62
+ const alt = dict.get(PDFName.of('Alt'));
63
+ const elem = { type: dict.get(PDFName.of('S')).decodeText(), kids: [], ...(alt ? { alt: alt.decodeText() } : {}) };
64
+ const k = dict.get(PDFName.of('K'));
65
+ for (const kid of k instanceof PDFArray ? k.asArray() : k ? [k] : []) {
66
+ if (kid instanceof PDFRef)
67
+ elem.kids.push(readElem(pdf, kid));
68
+ }
69
+ return elem;
70
+ }
71
+ const hasPdftotext = (() => {
72
+ try {
73
+ execFileSync('pdftotext', ['-v'], { stdio: 'ignore' });
74
+ return true;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ })();
80
+ const hasVerapdf = process.env.VERAPDF === '1' && spawnSync('verapdf', ['--version'], { encoding: 'utf8' }).status === 0;
81
+ let doc;
82
+ let bytes;
83
+ beforeAll(async () => {
84
+ doc = buildDocument({ markdown, resources, metadata: { title: 'Beds' } }, config);
85
+ bytes = await renderToPdf(doc, { fontProvider, resourceBytes: () => PNG });
86
+ }, 60_000);
87
+ describe('text wrap in the PDF (#627)', () => {
88
+ it('lays the sample out with both wraps on the first page', () => {
89
+ const col = doc.pages[0].columns[0];
90
+ expect(col.exclusions?.map((e) => e.side)).toEqual(['right', 'left']);
91
+ });
92
+ it('reads the picture and the box before the paragraphs that run beside them', async () => {
93
+ const pdf = await PDFDocument.load(bytes);
94
+ const treeRoot = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
95
+ const root = readElem(pdf, treeRoot.get(PDFName.of('K')).get(0));
96
+ const types = root.kids.map((k) => (k.type === 'Figure' ? `Figure:${k.alt}` : k.type));
97
+ expect(types).toEqual(['H1', 'P', 'Figure:A raised bed', 'P', 'Div', 'P']);
98
+ });
99
+ it.skipIf(!hasPdftotext)('extracts the lines beside the picture in order', () => {
100
+ const file = path.join(os.tmpdir(), `postext-wrap-${process.pid}.pdf`);
101
+ fs.writeFileSync(file, bytes);
102
+ const text = execFileSync('pdftotext', ['-raw', file, '-'], { encoding: 'utf8' });
103
+ fs.unlinkSync(file);
104
+ const found = text.match(/beside\d+/g).map((w) => Number(w.slice(6)));
105
+ expect(found).toEqual(found.slice().sort((a, b) => a - b));
106
+ expect(found).toHaveLength(120);
107
+ });
108
+ it.skipIf(!hasVerapdf)('passes veraPDF (PDF/UA-1)', () => {
109
+ const file = path.join(os.tmpdir(), `postext-wrap-ua-${process.pid}.pdf`);
110
+ fs.writeFileSync(file, bytes);
111
+ const out = spawnSync('verapdf', ['--flavour', 'ua1', '--format', 'text', file], { encoding: 'utf8' });
112
+ fs.unlinkSync(file);
113
+ expect(out.stdout).toMatch(/^PASS /m);
114
+ }, 120_000);
115
+ });
116
+ //# sourceMappingURL=textWrap.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textWrap.test.js","sourceRoot":"","sources":["../../src/__tests__/textWrap.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=verseLines.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verseLines.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/verseLines.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,140 @@
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, spawnSync } from 'node:child_process';
6
+ import { PDFArray, PDFDict, PDFDocument, PDFName, PDFRef } from 'pdf-lib';
7
+ import fontkit from '@pdf-lib/fontkit';
8
+ import { buildDocument } from 'postext';
9
+ import { renderToPdf } from '../pdf-backend';
10
+ import { parseFontString } from '../fontString';
11
+ // #620: a poem set line by line in the PDF. Each stanza is a paragraph
12
+ // (`P`) of the structure tree, and the extracted text keeps a line end
13
+ // after every line of verse.
14
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
15
+ const fontProvider = async () => new Uint8Array(fontBytes);
16
+ const face = fontkit.create(fontBytes);
17
+ class StubCtx {
18
+ font = '';
19
+ measureText(s) {
20
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
21
+ const run = face.layout(s);
22
+ return { width: (run.advanceWidth / face.unitsPerEm) * sizePx };
23
+ }
24
+ }
25
+ globalThis.OffscreenCanvas = class {
26
+ getContext() {
27
+ return new StubCtx();
28
+ }
29
+ };
30
+ const pt = (value) => ({ value, unit: 'pt' });
31
+ const config = {
32
+ page: { width: pt(300), height: pt(480), margins: { top: pt(24), bottom: pt(24), left: pt(24), right: pt(24) } },
33
+ locale: 'en-us',
34
+ layout: { layoutType: 'single' },
35
+ header: { elements: [] },
36
+ footer: { elements: [] },
37
+ };
38
+ const POEM = [
39
+ 'Before the poem.',
40
+ '',
41
+ ':::verse',
42
+ 'Whose woods these are I think I know.',
43
+ ' His house is in the village though;',
44
+ '',
45
+ 'He will not see me stopping here, nor watch the long road run on past the trees',
46
+ ' To watch his woods fill up with snow.',
47
+ ':::',
48
+ '',
49
+ 'After the poem.',
50
+ ].join('\n');
51
+ const has = (cmd, arg) => spawnSync(cmd, [arg], { encoding: 'utf8' }).status === 0;
52
+ const hasPdftotext = (() => {
53
+ try {
54
+ execFileSync('pdftotext', ['-v'], { stdio: 'ignore' });
55
+ return true;
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ })();
61
+ const hasVerapdf = process.env.VERAPDF === '1' && has('verapdf', '--version');
62
+ /** The structure types of the tree, depth first. */
63
+ function structTypes(pdf) {
64
+ const out = [];
65
+ const visit = (ref) => {
66
+ const dict = ref instanceof PDFRef ? pdf.context.lookup(ref) : ref;
67
+ if (dict instanceof PDFArray) {
68
+ for (const kid of dict.asArray())
69
+ visit(kid);
70
+ return;
71
+ }
72
+ if (!(dict instanceof PDFDict))
73
+ return;
74
+ const s = dict.get(PDFName.of('S'));
75
+ if (s instanceof PDFName)
76
+ out.push(s.decodeText());
77
+ const k = dict.get(PDFName.of('K'));
78
+ for (const kid of k instanceof PDFArray ? k.asArray() : k ? [k] : [])
79
+ visit(kid);
80
+ };
81
+ const root = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
82
+ visit(root.get(PDFName.of('K')));
83
+ return out;
84
+ }
85
+ describe('verse line by line in the PDF (#620)', () => {
86
+ const doc = buildDocument({ markdown: POEM, metadata: { title: 'A poem' } }, config);
87
+ const lines = doc.blocks.flatMap((b) => b.lines).filter((l) => l.verseLine);
88
+ it('tags each stanza as a paragraph', async () => {
89
+ expect(lines.some((l) => l.verseLine.turnover)).toBe(true);
90
+ const pdf = await PDFDocument.load(await renderToPdf(doc, { fontProvider }));
91
+ // Before, two stanzas, after.
92
+ expect(structTypes(pdf).filter((t) => t === 'P')).toHaveLength(4);
93
+ });
94
+ it.skipIf(!hasPdftotext)('keeps a line end after every line of verse in the extracted text', async () => {
95
+ const file = path.join(os.tmpdir(), `postext-verse-${process.pid}.pdf`);
96
+ fs.writeFileSync(file, await renderToPdf(doc, { fontProvider }));
97
+ const text = execFileSync('pdftotext', ['-raw', file, '-'], { encoding: 'utf8' });
98
+ fs.unlinkSync(file);
99
+ const rows = text.split('\n').map((r) => r.trim());
100
+ for (const l of lines)
101
+ expect(rows, l.text).toContain(l.text.trim());
102
+ expect(text).toMatch(/I think I know\.\s*\n\s*His house is in the village though;\s*\n/);
103
+ });
104
+ it.skipIf(!hasVerapdf)('passes veraPDF (PDF/UA-1)', async () => {
105
+ const file = path.join(os.tmpdir(), `postext-verse-ua-${process.pid}.pdf`);
106
+ fs.writeFileSync(file, await renderToPdf(doc, { fontProvider }));
107
+ const out = spawnSync('verapdf', ['--flavour', 'ua1', '--format', 'text', file], { encoding: 'utf8' });
108
+ fs.unlinkSync(file);
109
+ expect(out.stdout).toMatch(/^PASS /m);
110
+ }, 120_000);
111
+ });
112
+ describe('a line of verse tightened to fit the measure in the PDF (#620)', () => {
113
+ // A line 2 pt wider than the measure at its natural spacing: the page is
114
+ // made that much narrower than the line needs.
115
+ const LINE = 'The soil is bare now, nor can foot feel, being shod.';
116
+ const markdown = `:::verse\nGenerations have trod, have trod.\n${LINE}\n:::`;
117
+ const wide = buildDocument({ markdown }, { ...config, page: { ...config.page, width: pt(600) } });
118
+ const pxPerPt = wide.pages[0].width / 600;
119
+ const natural = wide.blocks.flatMap((b) => b.lines).filter((l) => l.verseLine)[1].bbox.width / pxPerPt;
120
+ const measurePt = natural - 2;
121
+ const doc = buildDocument({ markdown }, { ...config, page: { ...config.page, width: pt(measurePt + 48) } });
122
+ const lines = doc.blocks.flatMap((b) => b.lines).filter((l) => l.verseLine);
123
+ it('keeps the line on one line with its word spaces tightened', () => {
124
+ expect(lines).toHaveLength(2);
125
+ expect(lines[1].verseLine.spaceRatio).toBeGreaterThanOrEqual(0.6);
126
+ expect(lines[1].bbox.width / pxPerPt).toBeCloseTo(measurePt, 3);
127
+ });
128
+ it.skipIf(!hasPdftotext)('paints its last word ending on the measure', async () => {
129
+ const file = path.join(os.tmpdir(), `postext-verse-tight-${process.pid}.pdf`);
130
+ fs.writeFileSync(file, await renderToPdf(doc, { fontProvider }));
131
+ const html = execFileSync('pdftotext', ['-bbox', file, '-'], { encoding: 'utf8' });
132
+ fs.unlinkSync(file);
133
+ const last = [...html.matchAll(/<word xMin="([\d.]+)" yMin="[\d.]+" xMax="([\d.]+)"[^>]*>([^<]*)<\/word>/g)].find((m) => m[3] === 'shod.');
134
+ expect(last).toBeDefined();
135
+ // The right margin starts at 24 pt from the page's right edge.
136
+ expect(Number(last[2])).toBeLessThanOrEqual(measurePt + 24 + 0.5);
137
+ expect(Number(last[2])).toBeGreaterThan(measurePt + 24 - 1.5);
138
+ });
139
+ });
140
+ //# sourceMappingURL=verseLines.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verseLines.test.js","sourceRoot":"","sources":["../../src/__tests__/verseLines.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"blockRender.d.ts","sourceRoot":"","sources":["../../src/pdf-backend/blockRender.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAW,cAAc,EAAc,MAAM,SAAS,CAAC;AAE7E,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,KAAK,OAAO,EAAuP,MAAM,cAAc,CAAC;AAIjS,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAA8B,MAAM,SAAS,CAAC;AACnE,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQrD,mFAAmF;AACnF,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,gBAAgB,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AA0CD;+CAC+C;AAC/C,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,KAAK,GACnB,IAAI,CAGN;AA6iBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,IAAI,EAAE,UAAU,GAAG,SAAS,GAC3B,IAAI,CAgBN;AAED,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,QAAQ,EACf,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,EACpB,WAAW,CAAC,EAAE,qBAAqB,GAClC,IAAI,CAoEN"}
1
+ {"version":3,"file":"blockRender.d.ts","sourceRoot":"","sources":["../../src/pdf-backend/blockRender.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAuB,cAAc,EAAc,MAAM,SAAS,CAAC;AAEzF,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,KAAK,OAAO,EAAuP,MAAM,cAAc,CAAC;AAIjS,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAA8B,MAAM,SAAS,CAAC;AACnE,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQrD,mFAAmF;AACnF,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,gBAAgB,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AA0CD;+CAC+C;AAC/C,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,KAAK,GACnB,IAAI,CAGN;AAknBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,GAAG,SAAS,EACtC,IAAI,EAAE,UAAU,GAAG,SAAS,GAC3B,IAAI,CAgBN;AAED,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,QAAQ,EACf,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,EACpB,WAAW,CAAC,EAAE,qBAAqB,GAClC,IAAI,CAyEN"}