postext-pdf 1.34.0 → 1.35.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 (40) hide show
  1. package/dist/__tests__/composedFixture.test.d.ts +2 -0
  2. package/dist/__tests__/composedFixture.test.d.ts.map +1 -0
  3. package/dist/__tests__/composedFixture.test.js +64 -0
  4. package/dist/__tests__/composedFixture.test.js.map +1 -0
  5. package/dist/__tests__/composedPage.test.d.ts +2 -0
  6. package/dist/__tests__/composedPage.test.d.ts.map +1 -0
  7. package/dist/__tests__/composedPage.test.js +167 -0
  8. package/dist/__tests__/composedPage.test.js.map +1 -0
  9. package/dist/__tests__/composedReadingOrder.test.d.ts +2 -0
  10. package/dist/__tests__/composedReadingOrder.test.d.ts.map +1 -0
  11. package/dist/__tests__/composedReadingOrder.test.js +80 -0
  12. package/dist/__tests__/composedReadingOrder.test.js.map +1 -0
  13. package/dist/__tests__/composedSpread.test.d.ts +2 -0
  14. package/dist/__tests__/composedSpread.test.d.ts.map +1 -0
  15. package/dist/__tests__/composedSpread.test.js +126 -0
  16. package/dist/__tests__/composedSpread.test.js.map +1 -0
  17. package/dist/pdf-backend/annotations.d.ts +4 -0
  18. package/dist/pdf-backend/annotations.d.ts.map +1 -1
  19. package/dist/pdf-backend/annotations.js +19 -1
  20. package/dist/pdf-backend/annotations.js.map +1 -1
  21. package/dist/pdf-backend/blockRender.d.ts.map +1 -1
  22. package/dist/pdf-backend/blockRender.js +6 -1
  23. package/dist/pdf-backend/blockRender.js.map +1 -1
  24. package/dist/pdf-backend/fontHelpers.d.ts.map +1 -1
  25. package/dist/pdf-backend/fontHelpers.js +2 -0
  26. package/dist/pdf-backend/fontHelpers.js.map +1 -1
  27. package/dist/pdf-backend/headerFooter.d.ts.map +1 -1
  28. package/dist/pdf-backend/headerFooter.js +11 -0
  29. package/dist/pdf-backend/headerFooter.js.map +1 -1
  30. package/dist/pdf-backend/index.d.ts.map +1 -1
  31. package/dist/pdf-backend/index.js +96 -2
  32. package/dist/pdf-backend/index.js.map +1 -1
  33. package/dist/pdf-backend/renderResourceBlock.d.ts.map +1 -1
  34. package/dist/pdf-backend/renderResourceBlock.js +6 -3
  35. package/dist/pdf-backend/renderResourceBlock.js.map +1 -1
  36. package/dist/pdf-backend/structureFlow.d.ts +17 -1
  37. package/dist/pdf-backend/structureFlow.d.ts.map +1 -1
  38. package/dist/pdf-backend/structureFlow.js +64 -3
  39. package/dist/pdf-backend/structureFlow.js.map +1 -1
  40. package/package.json +3 -3
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=composedFixture.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedFixture.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/composedFixture.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import { PDFDocument } from 'pdf-lib';
4
+ import fontkit from '@pdf-lib/fontkit';
5
+ import { buildDocument } from 'postext';
6
+ import { renderToPdf } from '../pdf-backend';
7
+ import { parseFontString } from '../fontString';
8
+ // #855: a composed page laid out by the engine (a tabbed title, running
9
+ // text, two boxes with tabs, one turned) and written as a tagged PDF.
10
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
11
+ const face = fontkit.create(fontBytes);
12
+ class StubCtx {
13
+ font = '';
14
+ measureText(s) {
15
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
16
+ return { width: (face.layout(s).advanceWidth / face.unitsPerEm) * sizePx };
17
+ }
18
+ }
19
+ globalThis.OffscreenCanvas = class {
20
+ getContext() {
21
+ return new StubCtx();
22
+ }
23
+ };
24
+ describe('composed page to PDF (#855)', () => {
25
+ it('writes a composed page as one tagged page', async () => {
26
+ const mm = (value) => ({ value, unit: 'mm' });
27
+ const config = {
28
+ calloutStyles: [{ id: 'box', backgroundEnabled: true, background: { hex: '#d8ecf9' }, label: { position: 'top-left', offset: mm(5), height: mm(5) } }],
29
+ pageTemplates: [{
30
+ id: 'page',
31
+ decoration: { elements: [{ kind: 'box', id: 'card', placement: { anchor: { to: 'container', edge: 'top-left' }, size: { width: 'fill', height: 'fill' } }, style: { backgroundColor: { hex: '#ffffff' } } }] },
32
+ frames: [
33
+ { name: 'title', takes: 'h1', placement: { anchor: { to: 'container', edge: 'top-left' } } },
34
+ { name: 'text', flow: true, placement: { anchor: { to: '#title', edge: 'below' } } },
35
+ { name: 'money', style: 'box', width: mm(100), placement: { anchor: { to: '#text', edge: 'below' }, offset: { y: mm(8) } } },
36
+ { name: 'saving', style: 'box', width: mm(80), rotate: -4, layer: 2, height: mm(15), placement: { anchor: { to: '#money', edge: 'below' }, offset: { y: mm(8) } } },
37
+ ],
38
+ }],
39
+ };
40
+ const markdown = [
41
+ '::::page{template="page"}',
42
+ '# Money and everyday life',
43
+ '',
44
+ 'Every family needs food, clothes and a home.',
45
+ '',
46
+ ':::frame{slot="money" label="What money is for"}',
47
+ 'Money lets us swap our work for the things we need.',
48
+ ':::',
49
+ '',
50
+ ':::frame{slot="saving" label="Saving"}',
51
+ 'When we spend less than we earn, we can save for later. '.repeat(6),
52
+ ':::',
53
+ '::::',
54
+ ].join('\n');
55
+ const doc = buildDocument({ markdown }, config);
56
+ expect(doc.pages[0].composed.frames.find((f) => f.name === 'saving').overflowPx).toBeGreaterThan(0);
57
+ expect(doc.pages[0].composed).toBeDefined();
58
+ const bytes = await renderToPdf(doc, { fontProvider: async () => new Uint8Array(fontBytes), accessible: true });
59
+ const pdf = await PDFDocument.load(bytes);
60
+ expect(pdf.getPageCount()).toBe(1);
61
+ expect(bytes.byteLength).toBeGreaterThan(1000);
62
+ });
63
+ });
64
+ //# sourceMappingURL=composedFixture.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedFixture.test.js","sourceRoot":"","sources":["../../src/__tests__/composedFixture.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=composedPage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedPage.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/composedPage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,167 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import { PDFArray, PDFDict, PDFDocument, PDFName, PDFRawStream, PDFRef, decodePDFRawStream } from 'pdf-lib';
4
+ import fontkit from '@pdf-lib/fontkit';
5
+ import { buildDocument, frameMatrix } from 'postext';
6
+ import { renderToPdf } from '../pdf-backend';
7
+ import { collectFontText } from '../pdf-backend/fontHelpers';
8
+ import { sheetMatrixToPdf } from '../pdf-backend/comic';
9
+ import { parseFontString } from '../fontString';
10
+ // #855: the PDF paints a composed page's frames in layer order (the
11
+ // decoration at layer 0), each turned by its matrix (`cm`) and clipped to
12
+ // its box when its content overflows (`W n`), embeds the decoration's
13
+ // face, and reads the frames in Markdown order, not in paint order.
14
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
15
+ const face = fontkit.create(fontBytes);
16
+ const asked = [];
17
+ const fontProvider = async (family) => {
18
+ asked.push(family);
19
+ return new Uint8Array(fontBytes);
20
+ };
21
+ class StubCtx {
22
+ font = '';
23
+ measureText(s) {
24
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
25
+ const run = face.layout(s);
26
+ return { width: (run.advanceWidth / face.unitsPerEm) * sizePx };
27
+ }
28
+ }
29
+ globalThis.OffscreenCanvas = class {
30
+ getContext() {
31
+ return new StubCtx();
32
+ }
33
+ };
34
+ const markdown = [
35
+ 'Intro paragraph.',
36
+ '',
37
+ ':::callout{placement="top"}',
38
+ 'Alpha one.',
39
+ ':::',
40
+ '',
41
+ ':::callout{placement="top"}',
42
+ 'Bravo one.',
43
+ '',
44
+ 'Bravo two.',
45
+ ':::',
46
+ '',
47
+ ':::callout{placement="bottom"}',
48
+ 'Charlie one.',
49
+ '',
50
+ 'Charlie two.',
51
+ '',
52
+ 'Charlie three.',
53
+ ':::',
54
+ '',
55
+ 'Outro.',
56
+ ].join('\n');
57
+ /** A laid-out page whose three floated boxes are made the frames of a
58
+ * composed page by hand: alpha (one paragraph) turned 15° at layer 2,
59
+ * bravo (two) clipped at layer 1, charlie (three) at layer −1, under a
60
+ * decoration set in Decofont (the page's running head, moved). */
61
+ function composedDoc() {
62
+ const doc = buildDocument({ markdown }, {
63
+ locale: 'en-us',
64
+ header: { elements: [{ kind: 'text', id: 'deco', content: 'DECO', fontFamily: 'Decofont', fontSize: { value: 10, unit: 'pt' }, placement: { anchor: { to: 'container', edge: 'top-left' } } }] },
65
+ footer: { elements: [] },
66
+ });
67
+ const page = doc.pages[0];
68
+ const floats = page.floats ?? [];
69
+ const boxes = floats.filter((f) => f.type === 'callout');
70
+ const frame = (name, i, layer, rotate, overflowPx) => {
71
+ const box = boxes[i];
72
+ return {
73
+ id: `cp0:${name}`,
74
+ name,
75
+ rect: { ...box.bbox },
76
+ rotate,
77
+ layer,
78
+ scale: 1,
79
+ overflowPx,
80
+ blockIds: floats.filter((f) => f.containerId === box.containerId).map((f) => f.id),
81
+ configPath: `/pageTemplates/0/frames/${i}`,
82
+ };
83
+ };
84
+ const frames = {
85
+ alpha: frame('alpha', 0, 2, 15, 0),
86
+ bravo: frame('bravo', 1, 1, 0, 24),
87
+ charlie: frame('charlie', 2, -1, 0, 0),
88
+ };
89
+ page.composed = {
90
+ template: 't',
91
+ fenceIndex: 0,
92
+ role: 'composed',
93
+ attrs: {},
94
+ attrSources: {},
95
+ frames: [frames.alpha, frames.bravo, frames.charlie],
96
+ decoration: page.header,
97
+ log: [],
98
+ configPath: '/pageTemplates/0',
99
+ sourceStart: 0,
100
+ sourceEnd: 0,
101
+ };
102
+ delete page.header;
103
+ return { doc, page, frames };
104
+ }
105
+ function readElem(pdf, ref) {
106
+ const dict = pdf.context.lookup(ref, PDFDict);
107
+ const elem = { type: dict.get(PDFName.of('S')).decodeText(), kids: [] };
108
+ const k = dict.get(PDFName.of('K'));
109
+ for (const kid of k instanceof PDFArray ? k.asArray() : k ? [k] : []) {
110
+ if (kid instanceof PDFRef)
111
+ elem.kids.push(readElem(pdf, kid));
112
+ }
113
+ return elem;
114
+ }
115
+ function pageText(pdf, index) {
116
+ const contents = pdf.getPage(index).node.Contents();
117
+ const streams = contents instanceof PDFArray ? contents.asArray().map((r) => pdf.context.lookup(r)) : [contents];
118
+ return streams.map((s) => (s instanceof PDFRawStream ? Buffer.from(decodePDFRawStream(s).decode()).toString('latin1') : '')).join('\n');
119
+ }
120
+ let fixture;
121
+ let pdf;
122
+ let root;
123
+ beforeAll(async () => {
124
+ fixture = composedDoc();
125
+ const bytes = await renderToPdf(fixture.doc, { fontProvider, accessible: true });
126
+ pdf = await PDFDocument.load(bytes);
127
+ const treeRoot = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
128
+ root = readElem(pdf, treeRoot.get(PDFName.of('K')).get(0));
129
+ }, 60_000);
130
+ describe('a composed page in the PDF (#855)', () => {
131
+ it('turns the rotated frame through its matrix', () => {
132
+ const text = pageText(pdf, 0);
133
+ const m = sheetMatrixToPdf(frameMatrix(fixture.frames.alpha), 72 / fixture.doc.config.page.dpi, fixture.page.height * (72 / fixture.doc.config.page.dpi));
134
+ // cos 15° and sin 15° (a clockwise turn on the sheet is −15° in PDF space).
135
+ expect(m[0]).toBeCloseTo(0.9659, 4);
136
+ expect(m[1]).toBeCloseTo(-0.2588, 4);
137
+ expect(/(^|\s)0\.9659\d* -0\.2588\d* 0\.2588\d* 0\.9659\d* [-\d.]+ [-\d.]+ cm/.test(text)).toBe(true);
138
+ });
139
+ it('clips the overflowing frame to its box', () => {
140
+ const text = pageText(pdf, 0);
141
+ const s = 72 / fixture.doc.config.page.dpi;
142
+ const r = fixture.frames.bravo.rect;
143
+ const H = fixture.page.height * s;
144
+ const re = [r.x * s, H - (r.y + r.height) * s, r.width * s, r.height * s];
145
+ const ops = text.split(/\s+/);
146
+ // `x y w h re W n` with the frame's box.
147
+ const found = ops.some((op, i) => op === 're' && ops[i + 1] === 'W' && ops[i + 2] === 'n'
148
+ && ops.slice(i - 4, i).every((v, j) => Math.abs(Number(v) - re[j]) < 0.01));
149
+ expect(found).toBe(true);
150
+ });
151
+ it('balances its graphics states', () => {
152
+ const text = pageText(pdf, 0);
153
+ const ops = text.split(/\s+/);
154
+ expect(ops.filter((o) => o === 'q').length).toBe(ops.filter((o) => o === 'Q').length);
155
+ });
156
+ it('collects and embeds the decoration face', () => {
157
+ const fonts = collectFontText(fixture.doc);
158
+ expect([...fonts.keys()].some((k) => k.includes('Decofont'))).toBe(true);
159
+ expect(asked).toContain('Decofont');
160
+ });
161
+ it('reads the frames in Markdown order, whatever order the layers paint them in', () => {
162
+ const top = root.kids.map((k) => (k.type === 'Div' ? `Div(${k.kids.filter((c) => c.type === 'P').length})` : k.type));
163
+ // Intro, then alpha (one paragraph), bravo (two), charlie (three), outro.
164
+ expect(top).toEqual(['P', 'Div(1)', 'Div(2)', 'Div(3)', 'P']);
165
+ });
166
+ });
167
+ //# sourceMappingURL=composedPage.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedPage.test.js","sourceRoot":"","sources":["../../src/__tests__/composedPage.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=composedReadingOrder.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedReadingOrder.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/composedReadingOrder.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import { PDFArray, PDFDict, PDFDocument, PDFHexString, PDFName, PDFRef, PDFString } from 'pdf-lib';
4
+ import fontkit from '@pdf-lib/fontkit';
5
+ import { buildDocument } from 'postext';
6
+ import { renderToPdf } from '../pdf-backend';
7
+ import { parseFontString } from '../fontString';
8
+ // #862: the tagged PDF of the stand-in for a textbook's page 8 (#855's
9
+ // fixture) reads the composed page's frames in Markdown order — the
10
+ // title, the running text, each box followed by its photo — whatever
11
+ // order the template's layers paint them in, and a `:::figure`'s labels
12
+ // as part of its figure's alternative text.
13
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
14
+ const face = fontkit.create(fontBytes);
15
+ class StubCtx {
16
+ font = '';
17
+ measureText(s) {
18
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
19
+ return { width: (face.layout(s).advanceWidth / face.unitsPerEm) * sizePx };
20
+ }
21
+ }
22
+ globalThis.OffscreenCanvas = class {
23
+ getContext() {
24
+ return new StubCtx();
25
+ }
26
+ };
27
+ /** The fixture, read from the engine's tests as it is (outside this
28
+ * package's sources: a dynamic import the compiler does not follow). */
29
+ const FIXTURE = new URL('../../../postext/src/__tests__/composed/fixtures/page8.ts', import.meta.url).href;
30
+ function readElem(pdf, ref) {
31
+ const dict = pdf.context.lookup(ref, PDFDict);
32
+ const altObj = dict.get(PDFName.of('Alt'));
33
+ const alt = altObj instanceof PDFHexString || altObj instanceof PDFString ? altObj.decodeText() : undefined;
34
+ const elem = { type: dict.get(PDFName.of('S')).decodeText(), ...(alt !== undefined ? { alt } : {}), kids: [] };
35
+ const k = dict.get(PDFName.of('K'));
36
+ for (const kid of k instanceof PDFArray ? k.asArray() : k ? [k] : []) {
37
+ if (kid instanceof PDFRef)
38
+ elem.kids.push(readElem(pdf, kid));
39
+ }
40
+ return elem;
41
+ }
42
+ /** An element as `Type(kid types)`, figures with their alternative text. */
43
+ const shape = (e) => (e.type === 'Figure' ? `Figure[${e.alt}]` : `${e.type}(${e.kids.map(shape).join(' ')})`);
44
+ let root;
45
+ beforeAll(async () => {
46
+ const { page8Fixture } = (await import(/* @vite-ignore */ FIXTURE));
47
+ const fixture = page8Fixture('en');
48
+ const markdown = fixture.markdown.replace('::resource{id="foto-gasto" slot="gasto-fotos"}', [':::figure{resource="foto-gasto" slot="gasto-fotos"}', '::label[Rent]{at="20% 30%"}', '::label[Electricity]{at="70% 25%"}', '::label[Food]{at="50% 80%"}', ':::'].join('\n'));
49
+ const alt = { 'foto-dinero': 'A card payment', 'foto-gasto': 'A shopping basket', 'foto-ahorro': 'A piggy bank' };
50
+ const resources = fixture.resources.map((r) => (alt[r.id] ? { ...r, altText: alt[r.id] } : r));
51
+ const doc = buildDocument({ markdown, resources }, fixture.config);
52
+ expect(doc.pages[0].composed?.frames.map((f) => f.layer)).toEqual([1, 1, 1, 2, 1, 2, 1, 2]);
53
+ const bytes = await renderToPdf(doc, { fontProvider: async () => new Uint8Array(fontBytes), accessible: true });
54
+ const pdf = await PDFDocument.load(bytes);
55
+ const treeRoot = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
56
+ root = readElem(pdf, treeRoot.get(PDFName.of('K')).get(0));
57
+ }, 60_000);
58
+ describe('the composed page 8 stand-in in the tagged PDF (#862)', () => {
59
+ it('reads the frames in Markdown order, each photo after its box, not in layer order', () => {
60
+ expect(root.kids.map(shape)).toEqual([
61
+ // titulo: the tabbed title.
62
+ 'Div(H1())',
63
+ // texto: the running text.
64
+ 'Div(H2() P() L(LI(Lbl() LBody()) LI(Lbl() LBody())))',
65
+ // dinero (its tab is its title paragraph), then its photo.
66
+ 'Div(P() P() P())',
67
+ 'Div(Figure[A card payment])',
68
+ 'Div(P() P() L(LI(Lbl() LBody()) LI(Lbl() LBody()) LI(Lbl() LBody())))',
69
+ 'Div(Figure[A shopping basket (Rent, Electricity, Food)])',
70
+ 'Div(P() P())',
71
+ 'Div(Figure[A piggy bank])',
72
+ ]);
73
+ });
74
+ it('leaves the decoration out of the structure', () => {
75
+ const all = JSON.stringify(root);
76
+ expect(all).not.toContain('banda');
77
+ expect(root.kids.every((k) => k.type === 'Div')).toBe(true);
78
+ });
79
+ });
80
+ //# sourceMappingURL=composedReadingOrder.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedReadingOrder.test.js","sourceRoot":"","sources":["../../src/__tests__/composedReadingOrder.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=composedSpread.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedSpread.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/composedSpread.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,126 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import { PDFArray, PDFDict, PDFDocument, PDFName, PDFRawStream, PDFRef, decodePDFRawStream } from 'pdf-lib';
4
+ import fontkit from '@pdf-lib/fontkit';
5
+ import { buildDocument } from 'postext';
6
+ import { renderToPdf } from '../pdf-backend';
7
+ import { parseFontString } from '../fontString';
8
+ // #861: a spread template in the PDF is two pages, each painting its share
9
+ // of the sheet (clipped to its bleed box); the half of a frame crossing the
10
+ // spine that the facing page holds is an artifact there, so the tagged
11
+ // text reads it once.
12
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
13
+ const face = fontkit.create(fontBytes);
14
+ class StubCtx {
15
+ font = '';
16
+ measureText(s) {
17
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
18
+ return { width: (face.layout(s).advanceWidth / face.unitsPerEm) * sizePx };
19
+ }
20
+ }
21
+ globalThis.OffscreenCanvas = class {
22
+ getContext() {
23
+ return new StubCtx();
24
+ }
25
+ };
26
+ const mm = (value) => ({ value, unit: 'mm' });
27
+ const config = {
28
+ page: { width: mm(200), height: mm(260), margins: { top: mm(20), bottom: mm(20), left: mm(20), right: mm(20) } },
29
+ calloutStyles: [{ id: 'band', backgroundEnabled: true, background: { hex: '#f2a65a' } }],
30
+ pageTemplates: [{
31
+ id: 'tl',
32
+ spread: true,
33
+ gutter: mm(10),
34
+ grid: { columns: 4, rows: 4, columnGap: mm(12) },
35
+ decoration: { elements: [{ kind: 'box', id: 'card', placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(10), y: mm(10) }, size: { width: mm(380), height: mm(240) } }, style: { backgroundColor: { hex: '#ffffff' } } }] },
36
+ frames: [
37
+ { name: 'band', style: 'band', crossGutter: true, grid: { col: 1, row: 1, cols: 4 } },
38
+ { name: 'left', grid: { col: 1, row: 2 } },
39
+ { name: 'right', grid: { col: 4, row: 2 } },
40
+ ],
41
+ }],
42
+ };
43
+ const markdown = [
44
+ 'Before the spread.',
45
+ '',
46
+ '::::page{template="tl" spread}',
47
+ ':::frame{slot="band"}',
48
+ 'Band across the spine',
49
+ ':::',
50
+ '',
51
+ ':::frame{slot="left"}',
52
+ 'Left card',
53
+ ':::',
54
+ '',
55
+ ':::frame{slot="right"}',
56
+ 'Right card',
57
+ ':::',
58
+ '::::',
59
+ ].join('\n');
60
+ function readElem(pdf, ref) {
61
+ const dict = pdf.context.lookup(ref, PDFDict);
62
+ const elem = { type: dict.get(PDFName.of('S')).decodeText(), kids: [] };
63
+ const k = dict.get(PDFName.of('K'));
64
+ for (const kid of k instanceof PDFArray ? k.asArray() : k ? [k] : []) {
65
+ if (kid instanceof PDFRef)
66
+ elem.kids.push(readElem(pdf, kid));
67
+ }
68
+ return elem;
69
+ }
70
+ const count = (e, type) => (e.type === type ? 1 : 0) + e.kids.reduce((n, k) => n + count(k, type), 0);
71
+ function pageText(pdf, index) {
72
+ const contents = pdf.getPage(index).node.Contents();
73
+ const streams = contents instanceof PDFArray ? contents.asArray().map((r) => pdf.context.lookup(r)) : [contents];
74
+ return streams.map((s) => (s instanceof PDFRawStream ? Buffer.from(decodePDFRawStream(s).decode()).toString('latin1') : '')).join('\n');
75
+ }
76
+ async function render(doc) {
77
+ const bytes = await renderToPdf(doc, { fontProvider: async () => new Uint8Array(fontBytes), accessible: true });
78
+ const pdf = await PDFDocument.load(bytes);
79
+ const treeRoot = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
80
+ const k = treeRoot.get(PDFName.of('K'));
81
+ const first = k instanceof PDFArray ? k.get(0) : k;
82
+ return { pdf, root: readElem(pdf, first) };
83
+ }
84
+ let doc;
85
+ let verso;
86
+ let recto;
87
+ let out;
88
+ beforeAll(async () => {
89
+ doc = buildDocument({ markdown }, config);
90
+ [verso, recto] = doc.pages.filter((p) => p.composed?.spread);
91
+ out = await render(doc);
92
+ }, 60_000);
93
+ describe('a spread template in the PDF (#861)', () => {
94
+ it('writes the sheet as two facing pages after the text before it', () => {
95
+ expect(out.pdf.getPageCount()).toBe(3);
96
+ expect([verso.index, recto.index]).toEqual([1, 2]);
97
+ expect(verso.composed.spread).toBe('left');
98
+ });
99
+ it('clips each page to its share of the sheet', () => {
100
+ const s = 72 / doc.config.page.dpi;
101
+ for (const page of [verso, recto]) {
102
+ const c = page.composed.sheetClip;
103
+ const H = page.height * s;
104
+ const re = [c.x * s, H - (c.y + c.height) * s, c.width * s, c.height * s];
105
+ const ops = pageText(out.pdf, page.index).split(/\s+/);
106
+ const found = ops.some((op, i) => op === 're' && ops[i + 1] === 'W' && ops[i + 2] === 'n'
107
+ && ops.slice(i - 4, i).every((v, j) => Math.abs(Number(v) - re[j]) < 0.01));
108
+ expect(found).toBe(true);
109
+ expect(ops.filter((o) => o === 'q').length).toBe(ops.filter((o) => o === 'Q').length);
110
+ }
111
+ });
112
+ it('paints the facing half of the band as a layout artifact, so its text is tagged once', async () => {
113
+ const band = recto.composed.frames.find((f) => f.name === 'band');
114
+ expect(band.facingBlocks.length).toBeGreaterThan(0);
115
+ expect(pageText(out.pdf, recto.index)).toMatch(/\/Artifact\s*<<\s*\/Type\s*\/Layout\s*>>\s*BDC/);
116
+ // Without the copy the structure tree is the same.
117
+ const bare = buildDocument({ markdown }, config);
118
+ for (const p of bare.pages)
119
+ for (const f of p.composed?.frames ?? [])
120
+ delete f.facingBlocks;
121
+ const plain = await render(bare);
122
+ expect(count(out.root, 'P')).toBe(count(plain.root, 'P'));
123
+ expect(count(out.root, 'P')).toBeGreaterThan(0);
124
+ });
125
+ });
126
+ //# sourceMappingURL=composedSpread.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composedSpread.test.js","sourceRoot":"","sources":["../../src/__tests__/composedSpread.test.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,10 @@ import type { VDTKunten, VDTLine, VDTLineSegment, VDTRuby, VDTWarichu } from 'po
16
16
  import type { FontCache } from '../fontCache';
17
17
  import { type PageCtx } from './primitives';
18
18
  import { type StructElem } from './tagging';
19
+ /** Draw the highlight bands of a line (`VDTLine.highlights`, `:mark[…]`,
20
+ * #888) as artifacts, under its text: called before the line is
21
+ * painted. */
22
+ export declare function paintLineHighlights(ctx: PageCtx, line: VDTLine): void;
19
23
  /** Draw the marks of a line (`VDTLine.marks`) as artifacts, in `ink`
20
24
  * unless a mark has its own colour. */
21
25
  export declare function paintLineMarks(ctx: PageCtx, line: VDTLine, ink: Color): void;
@@ -1 +1 @@
1
- {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/pdf-backend/annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAYL,KAAK,KAAK,EACV,KAAK,OAAO,EAEb,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAoB,SAAS,EAAE,OAAO,EAAe,cAAc,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEtH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAmD,MAAM,cAAc,CAAC;AAC7F,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAmGrE;wCACwC;AACxC,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,CAK5E;AAkBD;qBACqB;AACrB,KAAK,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC;AA0BhC;;;GAGG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,OAAO,EACb,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GAAG,SAAS,GAC3B,IAAI,CAIN;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,SAAS,EACjB,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GAAG,SAAS,GAC3B,IAAI,CAON;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,UAAU,EACnB,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GAAG,SAAS,GAC3B,IAAI,CAGN;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,EACjD,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,GAChB,IAAI,CAEN"}
1
+ {"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/pdf-backend/annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAYL,KAAK,KAAK,EACV,KAAK,OAAO,EAEb,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAoB,SAAS,EAAE,OAAO,EAAe,cAAc,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEtH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAkE,MAAM,cAAc,CAAC;AAC5G,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAmGrE;;eAEe;AACf,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAarE;AAED;wCACwC;AACxC,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,CAK5E;AAkBD;qBACqB;AACrB,KAAK,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC;AA0BhC;;;GAGG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,OAAO,EACb,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GAAG,SAAS,GAC3B,IAAI,CAIN;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,SAAS,EACjB,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GAAG,SAAS,GAC3B,IAAI,CAON;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,UAAU,EACnB,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,UAAU,GAAG,SAAS,GAC3B,IAAI,CAGN;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,EACjD,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,OAAO,GAChB,IAAI,CAEN"}
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { appendBezierCurve, closePath, fill, lineTo, moveTo, popGraphicsState, pushGraphicsState, setFillingColor, setLineWidth, setStrokingColor, stroke, } from 'pdf-lib';
15
15
  import { parseFontString } from '../fontString';
16
- import { alphaOf, alphaStateOp, colorFromHex, drawTextPx } from './primitives';
16
+ import { alphaOf, alphaStateOp, colorFromHex, drawTextPx, fillOutlinePx } from './primitives';
17
17
  import { tagArtifact, tagContent } from './tagging';
18
18
  /** A sesame dot (﹅): a lens `size` long and 0.3 of it wide,
19
19
  * leaning a third of a right angle (as the canvas draws it). Two
@@ -109,6 +109,24 @@ function markOps(ctx, m, xPx, yPx) {
109
109
  }
110
110
  }
111
111
  }
112
+ /** Draw the highlight bands of a line (`VDTLine.highlights`, `:mark[…]`,
113
+ * #888) as artifacts, under its text: called before the line is
114
+ * painted. */
115
+ export function paintLineHighlights(ctx, line) {
116
+ const bands = line.highlights;
117
+ if (!bands || bands.length === 0)
118
+ return;
119
+ tagArtifact(ctx, { type: 'Layout' });
120
+ for (const b of bands) {
121
+ fillOutlinePx(ctx, {
122
+ x: line.bbox.x + b.x,
123
+ y: line.baseline + b.y,
124
+ width: b.width,
125
+ height: b.height,
126
+ radii: [b.radiusLeft, b.radiusRight, b.radiusRight, b.radiusLeft],
127
+ }, colorFromHex(b.color, ctx.colorSpace));
128
+ }
129
+ }
112
130
  /** Draw the marks of a line (`VDTLine.marks`) as artifacts, in `ink`
113
131
  * unless a mark has its own colour. */
114
132
  export function paintLineMarks(ctx, line, ink) {