postext-pdf 1.4.1 → 1.5.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 (208) hide show
  1. package/README.md +3 -3
  2. package/dist/__tests__/accessibility.test.js +66 -0
  3. package/dist/__tests__/accessibility.test.js.map +1 -1
  4. package/dist/__tests__/alpha.test.d.ts +2 -0
  5. package/dist/__tests__/alpha.test.d.ts.map +1 -0
  6. package/dist/__tests__/alpha.test.js +164 -0
  7. package/dist/__tests__/alpha.test.js.map +1 -0
  8. package/dist/__tests__/bleedClip.test.d.ts +2 -0
  9. package/dist/__tests__/bleedClip.test.d.ts.map +1 -0
  10. package/dist/__tests__/bleedClip.test.js +83 -0
  11. package/dist/__tests__/bleedClip.test.js.map +1 -0
  12. package/dist/__tests__/boxBorderInside.test.d.ts +2 -0
  13. package/dist/__tests__/boxBorderInside.test.d.ts.map +1 -0
  14. package/dist/__tests__/boxBorderInside.test.js +63 -0
  15. package/dist/__tests__/boxBorderInside.test.js.map +1 -0
  16. package/dist/__tests__/calloutRuntTracking.test.d.ts +2 -0
  17. package/dist/__tests__/calloutRuntTracking.test.d.ts.map +1 -0
  18. package/dist/__tests__/calloutRuntTracking.test.js +72 -0
  19. package/dist/__tests__/calloutRuntTracking.test.js.map +1 -0
  20. package/dist/__tests__/calloutStripeRadius.test.d.ts +2 -0
  21. package/dist/__tests__/calloutStripeRadius.test.d.ts.map +1 -0
  22. package/dist/__tests__/calloutStripeRadius.test.js +90 -0
  23. package/dist/__tests__/calloutStripeRadius.test.js.map +1 -0
  24. package/dist/__tests__/chipVerticalPadding.test.d.ts +2 -0
  25. package/dist/__tests__/chipVerticalPadding.test.d.ts.map +1 -0
  26. package/dist/__tests__/chipVerticalPadding.test.js +71 -0
  27. package/dist/__tests__/chipVerticalPadding.test.js.map +1 -0
  28. package/dist/__tests__/chips.test.js +20 -3
  29. package/dist/__tests__/chips.test.js.map +1 -1
  30. package/dist/__tests__/documentLanguage.test.d.ts +2 -0
  31. package/dist/__tests__/documentLanguage.test.d.ts.map +1 -0
  32. package/dist/__tests__/documentLanguage.test.js +44 -0
  33. package/dist/__tests__/documentLanguage.test.js.map +1 -0
  34. package/dist/__tests__/fallbackSpaces.test.d.ts +2 -0
  35. package/dist/__tests__/fallbackSpaces.test.d.ts.map +1 -0
  36. package/dist/__tests__/fallbackSpaces.test.js +156 -0
  37. package/dist/__tests__/fallbackSpaces.test.js.map +1 -0
  38. package/dist/__tests__/floatReadingOrder.test.d.ts +2 -0
  39. package/dist/__tests__/floatReadingOrder.test.d.ts.map +1 -0
  40. package/dist/__tests__/floatReadingOrder.test.js +208 -0
  41. package/dist/__tests__/floatReadingOrder.test.js.map +1 -0
  42. package/dist/__tests__/fontEmbedding.test.d.ts +2 -0
  43. package/dist/__tests__/fontEmbedding.test.d.ts.map +1 -0
  44. package/dist/__tests__/fontEmbedding.test.js +118 -0
  45. package/dist/__tests__/fontEmbedding.test.js.map +1 -0
  46. package/dist/__tests__/fontFaces.test.d.ts +2 -0
  47. package/dist/__tests__/fontFaces.test.d.ts.map +1 -0
  48. package/dist/__tests__/fontFaces.test.js +193 -0
  49. package/dist/__tests__/fontFaces.test.js.map +1 -0
  50. package/dist/__tests__/fontStack.test.d.ts +2 -0
  51. package/dist/__tests__/fontStack.test.d.ts.map +1 -0
  52. package/dist/__tests__/fontStack.test.js +48 -0
  53. package/dist/__tests__/fontStack.test.js.map +1 -0
  54. package/dist/__tests__/headerFooter.test.js +83 -1
  55. package/dist/__tests__/headerFooter.test.js.map +1 -1
  56. package/dist/__tests__/headingAlign.test.d.ts +2 -0
  57. package/dist/__tests__/headingAlign.test.d.ts.map +1 -0
  58. package/dist/__tests__/headingAlign.test.js +66 -0
  59. package/dist/__tests__/headingAlign.test.js.map +1 -0
  60. package/dist/__tests__/hiddenHeadings.test.d.ts +2 -0
  61. package/dist/__tests__/hiddenHeadings.test.d.ts.map +1 -0
  62. package/dist/__tests__/hiddenHeadings.test.js +96 -0
  63. package/dist/__tests__/hiddenHeadings.test.js.map +1 -0
  64. package/dist/__tests__/lineTracking.test.d.ts +2 -0
  65. package/dist/__tests__/lineTracking.test.d.ts.map +1 -0
  66. package/dist/__tests__/lineTracking.test.js +80 -0
  67. package/dist/__tests__/lineTracking.test.js.map +1 -0
  68. package/dist/__tests__/measuredLines.test.d.ts +2 -0
  69. package/dist/__tests__/measuredLines.test.d.ts.map +1 -0
  70. package/dist/__tests__/measuredLines.test.js +255 -0
  71. package/dist/__tests__/measuredLines.test.js.map +1 -0
  72. package/dist/__tests__/metadata.test.d.ts +2 -0
  73. package/dist/__tests__/metadata.test.d.ts.map +1 -0
  74. package/dist/__tests__/metadata.test.js +51 -0
  75. package/dist/__tests__/metadata.test.js.map +1 -0
  76. package/dist/__tests__/outlineTitles.test.d.ts +2 -0
  77. package/dist/__tests__/outlineTitles.test.d.ts.map +1 -0
  78. package/dist/__tests__/outlineTitles.test.js +59 -0
  79. package/dist/__tests__/outlineTitles.test.js.map +1 -0
  80. package/dist/__tests__/pdfGenerationConfig.test.d.ts +2 -0
  81. package/dist/__tests__/pdfGenerationConfig.test.d.ts.map +1 -0
  82. package/dist/__tests__/pdfGenerationConfig.test.js +76 -0
  83. package/dist/__tests__/pdfGenerationConfig.test.js.map +1 -0
  84. package/dist/__tests__/pdfWorker.test.d.ts +2 -0
  85. package/dist/__tests__/pdfWorker.test.d.ts.map +1 -0
  86. package/dist/__tests__/pdfWorker.test.js +123 -0
  87. package/dist/__tests__/pdfWorker.test.js.map +1 -0
  88. package/dist/__tests__/printMarks.test.d.ts +2 -0
  89. package/dist/__tests__/printMarks.test.d.ts.map +1 -0
  90. package/dist/__tests__/printMarks.test.js +170 -0
  91. package/dist/__tests__/printMarks.test.js.map +1 -0
  92. package/dist/__tests__/printMaster.test.d.ts +2 -0
  93. package/dist/__tests__/printMaster.test.d.ts.map +1 -0
  94. package/dist/__tests__/printMaster.test.js +161 -0
  95. package/dist/__tests__/printMaster.test.js.map +1 -0
  96. package/dist/__tests__/renderWarnings.test.d.ts +2 -0
  97. package/dist/__tests__/renderWarnings.test.d.ts.map +1 -0
  98. package/dist/__tests__/renderWarnings.test.js +89 -0
  99. package/dist/__tests__/renderWarnings.test.js.map +1 -0
  100. package/dist/__tests__/repeatedHyphen.test.d.ts +2 -0
  101. package/dist/__tests__/repeatedHyphen.test.d.ts.map +1 -0
  102. package/dist/__tests__/repeatedHyphen.test.js +125 -0
  103. package/dist/__tests__/repeatedHyphen.test.js.map +1 -0
  104. package/dist/__tests__/scriptStacking.test.d.ts +2 -0
  105. package/dist/__tests__/scriptStacking.test.d.ts.map +1 -0
  106. package/dist/__tests__/scriptStacking.test.js +66 -0
  107. package/dist/__tests__/scriptStacking.test.js.map +1 -0
  108. package/dist/__tests__/sectionColumnRule.test.d.ts +2 -0
  109. package/dist/__tests__/sectionColumnRule.test.d.ts.map +1 -0
  110. package/dist/__tests__/sectionColumnRule.test.js +144 -0
  111. package/dist/__tests__/sectionColumnRule.test.js.map +1 -0
  112. package/dist/__tests__/svgFormReuse.test.d.ts +2 -0
  113. package/dist/__tests__/svgFormReuse.test.d.ts.map +1 -0
  114. package/dist/__tests__/svgFormReuse.test.js +102 -0
  115. package/dist/__tests__/svgFormReuse.test.js.map +1 -0
  116. package/dist/__tests__/svgVector.test.js +85 -11
  117. package/dist/__tests__/svgVector.test.js.map +1 -1
  118. package/dist/__tests__/tableFillSeams.test.d.ts +2 -0
  119. package/dist/__tests__/tableFillSeams.test.d.ts.map +1 -0
  120. package/dist/__tests__/tableFillSeams.test.js +72 -0
  121. package/dist/__tests__/tableFillSeams.test.js.map +1 -0
  122. package/dist/__tests__/tocLeaderKerning.test.d.ts +2 -0
  123. package/dist/__tests__/tocLeaderKerning.test.d.ts.map +1 -0
  124. package/dist/__tests__/tocLeaderKerning.test.js +84 -0
  125. package/dist/__tests__/tocLeaderKerning.test.js.map +1 -0
  126. package/dist/__tests__/tocNumberBaseline.test.d.ts +2 -0
  127. package/dist/__tests__/tocNumberBaseline.test.d.ts.map +1 -0
  128. package/dist/__tests__/tocNumberBaseline.test.js +72 -0
  129. package/dist/__tests__/tocNumberBaseline.test.js.map +1 -0
  130. package/dist/__tests__/tocTagging.test.d.ts +2 -0
  131. package/dist/__tests__/tocTagging.test.d.ts.map +1 -0
  132. package/dist/__tests__/tocTagging.test.js +120 -0
  133. package/dist/__tests__/tocTagging.test.js.map +1 -0
  134. package/dist/__tests__/trackedLineAlign.test.d.ts +2 -0
  135. package/dist/__tests__/trackedLineAlign.test.d.ts.map +1 -0
  136. package/dist/__tests__/trackedLineAlign.test.js +73 -0
  137. package/dist/__tests__/trackedLineAlign.test.js.map +1 -0
  138. package/dist/__tests__/uriLinks.test.d.ts +2 -0
  139. package/dist/__tests__/uriLinks.test.d.ts.map +1 -0
  140. package/dist/__tests__/uriLinks.test.js +103 -0
  141. package/dist/__tests__/uriLinks.test.js.map +1 -0
  142. package/dist/colors.d.ts +18 -3
  143. package/dist/colors.d.ts.map +1 -1
  144. package/dist/colors.js +77 -25
  145. package/dist/colors.js.map +1 -1
  146. package/dist/fontCache.d.ts +67 -4
  147. package/dist/fontCache.d.ts.map +1 -1
  148. package/dist/fontCache.js +200 -23
  149. package/dist/fontCache.js.map +1 -1
  150. package/dist/index.d.ts +1 -1
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/pdf-backend/blockRender.d.ts.map +1 -1
  153. package/dist/pdf-backend/blockRender.js +127 -28
  154. package/dist/pdf-backend/blockRender.js.map +1 -1
  155. package/dist/pdf-backend/fallbackSpaces.d.ts +37 -0
  156. package/dist/pdf-backend/fallbackSpaces.d.ts.map +1 -0
  157. package/dist/pdf-backend/fallbackSpaces.js +119 -0
  158. package/dist/pdf-backend/fallbackSpaces.js.map +1 -0
  159. package/dist/pdf-backend/fontHelpers.d.ts +10 -1
  160. package/dist/pdf-backend/fontHelpers.d.ts.map +1 -1
  161. package/dist/pdf-backend/fontHelpers.js +111 -65
  162. package/dist/pdf-backend/fontHelpers.js.map +1 -1
  163. package/dist/pdf-backend/headerFooter.d.ts +4 -2
  164. package/dist/pdf-backend/headerFooter.d.ts.map +1 -1
  165. package/dist/pdf-backend/headerFooter.js +105 -48
  166. package/dist/pdf-backend/headerFooter.js.map +1 -1
  167. package/dist/pdf-backend/index.d.ts +55 -6
  168. package/dist/pdf-backend/index.d.ts.map +1 -1
  169. package/dist/pdf-backend/index.js +135 -35
  170. package/dist/pdf-backend/index.js.map +1 -1
  171. package/dist/pdf-backend/links.d.ts +82 -1
  172. package/dist/pdf-backend/links.d.ts.map +1 -1
  173. package/dist/pdf-backend/links.js +141 -3
  174. package/dist/pdf-backend/links.js.map +1 -1
  175. package/dist/pdf-backend/outlines.d.ts.map +1 -1
  176. package/dist/pdf-backend/outlines.js +7 -0
  177. package/dist/pdf-backend/outlines.js.map +1 -1
  178. package/dist/pdf-backend/pageDecorations.d.ts.map +1 -1
  179. package/dist/pdf-backend/pageDecorations.js +42 -26
  180. package/dist/pdf-backend/pageDecorations.js.map +1 -1
  181. package/dist/pdf-backend/primitives.d.ts +61 -1
  182. package/dist/pdf-backend/primitives.d.ts.map +1 -1
  183. package/dist/pdf-backend/primitives.js +318 -18
  184. package/dist/pdf-backend/primitives.js.map +1 -1
  185. package/dist/pdf-backend/renderResourceBlock.d.ts +3 -1
  186. package/dist/pdf-backend/renderResourceBlock.d.ts.map +1 -1
  187. package/dist/pdf-backend/renderResourceBlock.js +97 -19
  188. package/dist/pdf-backend/renderResourceBlock.js.map +1 -1
  189. package/dist/pdf-backend/structureFlow.d.ts +70 -3
  190. package/dist/pdf-backend/structureFlow.d.ts.map +1 -1
  191. package/dist/pdf-backend/structureFlow.js +209 -27
  192. package/dist/pdf-backend/structureFlow.js.map +1 -1
  193. package/dist/pdf-backend/svgVector.d.ts +7 -2
  194. package/dist/pdf-backend/svgVector.d.ts.map +1 -1
  195. package/dist/pdf-backend/svgVector.js +157 -72
  196. package/dist/pdf-backend/svgVector.js.map +1 -1
  197. package/dist/pdf-backend/tagging.d.ts +1 -1
  198. package/dist/pdf-backend/tagging.d.ts.map +1 -1
  199. package/dist/pdf-backend/tagging.js.map +1 -1
  200. package/dist/worker/client.d.ts +1 -1
  201. package/dist/worker/client.d.ts.map +1 -1
  202. package/dist/worker/client.js +8 -0
  203. package/dist/worker/client.js.map +1 -1
  204. package/dist/worker/pdf.worker.js +1 -0
  205. package/dist/worker/pdf.worker.js.map +1 -1
  206. package/dist/worker/protocol.d.ts +8 -1
  207. package/dist/worker/protocol.d.ts.map +1 -1
  208. package/package.json +3 -3
@@ -0,0 +1,208 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import { PDFArray, PDFDict, PDFDocument, PDFName, PDFRef } 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
+ // EF-146: a tagged page painted, and tagged, its opener, then each column,
9
+ // then its floats, so the structure tree read every figure, table and
10
+ // floated box after all the text of its page (after the closing table,
11
+ // too), and a list that went on after the page was cut in two by the
12
+ // float. A float is now read right after the text that first cites it —
13
+ // on its own page or an earlier one —, a floated box after the text
14
+ // before its fence, and the lists and contents around it stay whole.
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 PNG = Uint8Array.from(Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==', 'base64'));
33
+ const figure = (id, extra = {}) => ({
34
+ id, typeId: 'figure', kind: 'bitmap', caption: `Figure ${id}.`, altText: `Alt ${id}`, createdAt: 0, updatedAt: 0,
35
+ bitmap: { fileId: 'x.png', format: 'png', width: 400, height: 240 }, ...extra,
36
+ });
37
+ const resources = [
38
+ figure('cited'),
39
+ figure('embedded', { placement: { position: 'top' } }),
40
+ figure('late'),
41
+ ];
42
+ const words = (n, tag) => Array.from({ length: n }, (_, i) => `${tag}${i}`).join(' ');
43
+ const markdown = [
44
+ '# Chapter',
45
+ '',
46
+ `${words(40, 'opening')}.`,
47
+ '',
48
+ `The figure is cited here, :ref{id=cited}, ${words(30, 'citing')}.`,
49
+ '',
50
+ `${words(60, 'after')}.`,
51
+ '',
52
+ ...Array.from({ length: 14 }, (_, i) => `- Item ${i} ${words(12, `item${i}w`)}.`),
53
+ '',
54
+ `${words(50, 'before')}.`,
55
+ '',
56
+ '::resource{id="embedded"}',
57
+ '',
58
+ `${words(80, 'middle')}.`,
59
+ '',
60
+ ':::callout{placement="top"}',
61
+ 'A floated box.',
62
+ ':::',
63
+ '',
64
+ `${words(150, 'tail')}.`,
65
+ '',
66
+ `The late figure, :ref{id=late}, ${words(120, 'end')}.`,
67
+ '',
68
+ `${words(200, 'closing')}.`,
69
+ ].join('\n');
70
+ const config = {
71
+ page: { width: pt(360), height: pt(420), margins: { top: pt(24), bottom: pt(24), left: pt(24), right: pt(24) } },
72
+ layout: { layoutType: 'double' },
73
+ locale: 'en-us',
74
+ header: { elements: [] },
75
+ footer: { elements: [] },
76
+ };
77
+ function readElem(pdf, ref) {
78
+ const dict = pdf.context.lookup(ref, PDFDict);
79
+ const alt = dict.get(PDFName.of('Alt'));
80
+ const elem = { type: dict.get(PDFName.of('S')).decodeText(), kids: [], ...(alt ? { alt: alt.decodeText() } : {}) };
81
+ const k = dict.get(PDFName.of('K'));
82
+ for (const kid of k instanceof PDFArray ? k.asArray() : k ? [k] : []) {
83
+ if (kid instanceof PDFRef)
84
+ elem.kids.push(readElem(pdf, kid));
85
+ }
86
+ return elem;
87
+ }
88
+ let doc;
89
+ let root;
90
+ beforeAll(async () => {
91
+ doc = buildDocument({ markdown, resources }, config);
92
+ const bytes = await renderToPdf(doc, { fontProvider, resourceBytes: () => PNG });
93
+ const out = process.env.POSTEXT_FLOAT_ORDER_PDF_OUT;
94
+ if (out)
95
+ fs.writeFileSync(out, bytes);
96
+ const pdf = await PDFDocument.load(bytes);
97
+ const treeRoot = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
98
+ root = readElem(pdf, treeRoot.get(PDFName.of('K')).get(0));
99
+ if (process.env.POSTEXT_DUMP_FLOATS) {
100
+ for (const page of doc.pages) {
101
+ console.log(`page ${page.index}`, page.columns.map((c) => c.blocks.map((b) => `${b.type}@${b.contentIndex}`).join(' ')), (page.floats ?? []).map((f) => `${f.type}:${f.resourceBlock?.resource.id ?? f.containerId}@${f.contentIndex}`));
102
+ }
103
+ console.log(root.kids.map((k) => (k.type === 'Figure' ? `Figure(${k.alt})` : k.type)).join(' '));
104
+ }
105
+ }, 60_000);
106
+ const topTypes = () => root.kids.map((k) => (k.type === 'Figure' ? `Figure:${k.alt}` : k.type));
107
+ const floatPage = (id) => doc.pages.find((p) => (p.floats ?? []).some((f) => f.resourceBlock?.resource.id === id))?.index;
108
+ describe('floats in the reading order of a tagged PDF (EF-146)', () => {
109
+ it('lays the sample out with its floats where the test expects them', () => {
110
+ expect(floatPage('cited')).toBeDefined();
111
+ expect(floatPage('embedded')).toBeDefined();
112
+ expect(floatPage('late')).toBeDefined();
113
+ expect(doc.pages.some((p) => (p.floats ?? []).some((f) => f.type === 'callout'))).toBe(true);
114
+ // The late figure is cited on one page and placed on a later one.
115
+ const citing = doc.blocks.find((b) => b.lines.some((l) => (l.segments ?? []).some((s) => s.refResourceId === 'late')));
116
+ expect(floatPage('late')).toBeGreaterThan(citing.pageIndex);
117
+ });
118
+ it('reads a figure right after the paragraph that cites it', () => {
119
+ const types = topTypes();
120
+ // H1, the opening paragraph, the citing one, then the figure.
121
+ expect(types.slice(0, 4)).toEqual(['H1', 'P', 'P', 'Figure:Alt cited']);
122
+ });
123
+ it('reads a figure placed on a later page after its citation too', () => {
124
+ const types = topTypes();
125
+ const late = types.indexOf('Figure:Alt late');
126
+ // The citing paragraph is the one right before, and the text after it
127
+ // (the closing paragraph) comes after the figure.
128
+ expect(late).toBeGreaterThan(0);
129
+ expect(types[late - 1]).toBe('P');
130
+ expect(types.slice(late + 1)).toContain('P');
131
+ expect(late).toBeLessThan(types.length - 1);
132
+ });
133
+ it('reads an embedded figure and a floated box where their lines stand', () => {
134
+ const types = topTypes();
135
+ const list = types.indexOf('L');
136
+ const embedded = types.indexOf('Figure:Alt embedded');
137
+ const box = types.indexOf('Div');
138
+ // list, "before…" paragraph, embedded figure, "middle…" paragraph, box.
139
+ expect(types.slice(list, list + 5)).toEqual(['L', 'P', 'Figure:Alt embedded', 'P', 'Div']);
140
+ expect(embedded).toBeLessThan(box);
141
+ });
142
+ it('keeps a list that goes on after a page with a float in one element', () => {
143
+ expect(topTypes().filter((t) => t === 'L')).toHaveLength(1);
144
+ const list = root.kids.find((k) => k.type === 'L');
145
+ expect(list.kids).toHaveLength(14);
146
+ });
147
+ });
148
+ // A heading inside a floated box used to be clamped where the box was
149
+ // painted (after the page's columns), then read at its fence, ahead of
150
+ // headings painted before it: `# Chapter`, a box holding `### Inside the
151
+ // box`, then `## Section` came out as H1, Div›H3, H2, which skips a level
152
+ // (PDF/UA-1 7.4.2). The box's headings are now clamped where it is read.
153
+ describe('headings of a float are clamped where the float is read (EF-146)', () => {
154
+ const headingsOf = (elem, out = []) => {
155
+ if (/^H\d$/.test(elem.type) || elem.type === 'Div')
156
+ out.push(elem.type);
157
+ for (const kid of elem.kids)
158
+ headingsOf(kid, out);
159
+ return out;
160
+ };
161
+ const skips = (sequence) => {
162
+ let last = 0;
163
+ const found = [];
164
+ for (const type of sequence) {
165
+ const level = /^H(\d)$/.exec(type)?.[1];
166
+ if (!level)
167
+ continue;
168
+ if (Number(level) > last + 1)
169
+ found.push(`H${last}→${type}`);
170
+ last = Number(level);
171
+ }
172
+ return found;
173
+ };
174
+ const tagged = async (md) => {
175
+ const layout = buildDocument({ markdown: md }, {
176
+ ...config,
177
+ headings: { levels: [{ level: 1, breakBefore: { enabled: false } }] },
178
+ });
179
+ const pdf = await PDFDocument.load(await renderToPdf(layout, { fontProvider }));
180
+ const treeRoot = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
181
+ return { layout, tree: readElem(pdf, treeRoot.get(PDFName.of('K')).get(0)) };
182
+ };
183
+ const boxIsFloat = (layout) => layout.pages.some((p) => (p.floats ?? []).some((f) => f.type === 'callout'));
184
+ it('reads a deeper heading of a box ahead of flow headings painted before it without a skip', async () => {
185
+ const { layout, tree } = await tagged([
186
+ '# Chapter', '', `${words(30, 'a')}.`, '',
187
+ ':::callout{placement="bottom"}', '### Inside the box', '', 'Box text.', ':::', '',
188
+ '## Section', '', `${words(60, 'b')}.`, '', '## Another', '', `${words(300, 'c')}.`,
189
+ ].join('\n'));
190
+ expect(boxIsFloat(layout)).toBe(true);
191
+ const sequence = headingsOf(tree);
192
+ expect(sequence).toEqual(['H1', 'Div', 'H2', 'H2', 'H2']);
193
+ expect(skips(sequence)).toEqual([]);
194
+ });
195
+ it('keeps a shallower heading of a box from opening a skip before the next flow heading', async () => {
196
+ const { layout, tree } = await tagged([
197
+ '# Chapter', '', '## Section', '', '### Topic', '', `${words(30, 'a')}.`, '',
198
+ ':::callout{placement="bottom"}', '# Box title', '', 'Box text.', ':::', '',
199
+ '#### Detail', '', `${words(60, 'b')}.`, '', `${words(300, 'c')}.`,
200
+ ].join('\n'));
201
+ expect(boxIsFloat(layout)).toBe(true);
202
+ const sequence = headingsOf(tree);
203
+ // The box's title may not drop below H3, or the H4 after it would skip.
204
+ expect(sequence).toEqual(['H1', 'H2', 'H3', 'Div', 'H3', 'H4']);
205
+ expect(skips(sequence)).toEqual([]);
206
+ });
207
+ });
208
+ //# sourceMappingURL=floatReadingOrder.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"floatReadingOrder.test.js","sourceRoot":"","sources":["../../src/__tests__/floatReadingOrder.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fontEmbedding.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fontEmbedding.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fontEmbedding.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import { PDFDict, PDFDocument, PDFName, PDFRawStream } from 'pdf-lib';
4
+ import fontkit from '@pdf-lib/fontkit';
5
+ import { buildDocument } from 'postext';
6
+ import { renderToPdf } from '../pdf-backend';
7
+ import { FontCache } from '../fontCache';
8
+ import { parseFontString } from '../fontString';
9
+ // EF-121. `renderToPdf` embedded one font program per requested (family,
10
+ // weight, style), even when the provider answered two requests with the
11
+ // same file, and pdf-lib wrote every embedded font, even one no page drew
12
+ // with. A face is now embedded once per distinct file, and only the fonts
13
+ // a page uses are written.
14
+ const lora = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
15
+ const frauncesBold = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Fraunces-Bold.ttf', import.meta.url));
16
+ const face = fontkit.create(lora);
17
+ class StubCtx {
18
+ font = '';
19
+ measureText(s) {
20
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
21
+ return { width: (face.layout(s).advanceWidth / face.unitsPerEm) * sizePx };
22
+ }
23
+ }
24
+ globalThis.OffscreenCanvas = class {
25
+ getContext() {
26
+ return new StubCtx();
27
+ }
28
+ };
29
+ const pt = (value) => ({ value, unit: 'pt' });
30
+ const config = {
31
+ page: { width: pt(300), height: pt(420), margins: { top: pt(30), bottom: pt(30), left: pt(24), right: pt(24) } },
32
+ layout: { layoutType: 'single' },
33
+ header: { elements: [] },
34
+ footer: { elements: [] },
35
+ bodyText: { fontFamily: 'Lora' },
36
+ };
37
+ async function embedded(bytes) {
38
+ const pdf = await PDFDocument.load(bytes);
39
+ const refs = [];
40
+ let programs = 0;
41
+ for (const [ref, obj] of pdf.context.enumerateIndirectObjects()) {
42
+ const dict = obj instanceof PDFDict ? obj : obj instanceof PDFRawStream ? obj.dict : undefined;
43
+ if (!dict)
44
+ continue;
45
+ if (dict.get(PDFName.of('Type')) === PDFName.of('Font') && dict.get(PDFName.of('Subtype')) === PDFName.of('Type0'))
46
+ refs.push(ref.toString());
47
+ if (dict.get(PDFName.of('Type')) === PDFName.of('FontDescriptor')) {
48
+ programs += ['FontFile', 'FontFile2', 'FontFile3'].filter((k) => dict.has(PDFName.of(k))).length;
49
+ }
50
+ }
51
+ const used = new Set();
52
+ for (const page of pdf.getPages()) {
53
+ const fonts = page.node.Resources()?.lookupMaybe(PDFName.of('Font'), PDFDict);
54
+ for (const [, ref] of fonts?.entries() ?? [])
55
+ used.add(ref.toString());
56
+ }
57
+ return { fonts: refs.length, programs, unused: refs.filter((r) => !used.has(r)).length };
58
+ }
59
+ describe('font programs in the PDF (EF-121)', () => {
60
+ it('embeds one font for faces the provider answers with the same file', async () => {
61
+ const doc = buildDocument({ markdown: 'Some *italic*, **bold** and ***both*** words.' }, config);
62
+ const asked = [];
63
+ // A provider that stands one file in for every face, a fresh copy each time.
64
+ const provider = async (family, weight, style) => {
65
+ asked.push(`${family} ${weight} ${style}`);
66
+ return new Uint8Array(lora);
67
+ };
68
+ const facts = await embedded(await renderToPdf(doc, { fontProvider: provider }));
69
+ expect(asked).toHaveLength(4);
70
+ expect(facts).toEqual({ fonts: 1, programs: 1, unused: 0 });
71
+ }, 60_000);
72
+ it('keeps a font per distinct file', async () => {
73
+ const doc = buildDocument({ markdown: 'Some *italic*, **bold** and ***both*** words.' }, config);
74
+ const provider = async (_family, weight) => new Uint8Array(weight >= 600 ? frauncesBold : lora);
75
+ expect(await embedded(await renderToPdf(doc, { fontProvider: provider }))).toEqual({ fonts: 2, programs: 2, unused: 0 });
76
+ }, 60_000);
77
+ it('writes no font that no page draws with', async () => {
78
+ const doc = buildDocument({ markdown: 'Some *italic*, **bold** and ***both*** words.' }, config);
79
+ const pdfDoc = await PDFDocument.create();
80
+ pdfDoc.registerFontkit(fontkit);
81
+ const cache = new FontCache(pdfDoc, async (_family, weight) => new Uint8Array(weight >= 600 ? frauncesBold : lora));
82
+ await cache.preloadFontStrings([doc.blocks[0].fontString, doc.blocks[0].boldFontString]);
83
+ const page = pdfDoc.addPage();
84
+ // Only the regular face is drawn.
85
+ page.drawText('Text', { font: cache.get(doc.blocks[0].fontString), size: 12 });
86
+ cache.dropUnusedFonts();
87
+ expect(await embedded(await pdfDoc.save())).toEqual({ fonts: 1, programs: 1, unused: 0 });
88
+ }, 60_000);
89
+ it('drops the font of a figure drawn as a picture', async () => {
90
+ // The figure's text asks for its face before the drawing is known to
91
+ // be outside the vector subset (a filter); it is then rasterised, and
92
+ // nothing is set in that face.
93
+ const svg = new TextEncoder().encode('<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100" viewBox="0 0 200 100">'
94
+ + '<filter id="f"><feGaussianBlur stdDeviation="2"/></filter>'
95
+ + '<rect width="200" height="100" fill="#8ab" filter="url(#f)"/>'
96
+ + '<text x="10" y="50" font-family="Figure Face" font-size="20">Label</text></svg>');
97
+ const png = Uint8Array.from(atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='), (c) => c.charCodeAt(0));
98
+ const resources = [{
99
+ id: 'fig', typeId: 'figure', kind: 'svg', createdAt: 0, updatedAt: 0,
100
+ svg: { fileId: 'fig.svg', width: 200, height: 100 },
101
+ placement: { position: 'here' },
102
+ }];
103
+ const doc = buildDocument({ markdown: 'Text.\n\n::resource{id="fig"}', resources }, config);
104
+ const asked = [];
105
+ const provider = async (family) => {
106
+ asked.push(family);
107
+ return new Uint8Array(family === 'Figure Face' ? frauncesBold : lora);
108
+ };
109
+ const facts = await embedded(await renderToPdf(doc, {
110
+ fontProvider: provider,
111
+ resourceBytes: (id) => (id === 'fig.svg' ? svg : undefined),
112
+ rasterizeSvg: async () => png,
113
+ }));
114
+ expect(asked).toContain('Figure Face');
115
+ expect(facts).toEqual({ fonts: 1, programs: 1, unused: 0 });
116
+ }, 60_000);
117
+ });
118
+ //# sourceMappingURL=fontEmbedding.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fontEmbedding.test.js","sourceRoot":"","sources":["../../src/__tests__/fontEmbedding.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAe,MAAM,SAAS,CAAC;AACnF,OAAO,OAAO,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,EAAE,WAAW,EAAwB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,yEAAyE;AACzE,wEAAwE;AACxE,0EAA0E;AAC1E,0EAA0E;AAC1E,2BAA2B;AAE3B,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,oDAAoD,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7G,MAAM,YAAY,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,qDAAqD,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACtH,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAElC,MAAM,OAAO;IACX,IAAI,GAAG,EAAE,CAAC;IACV,WAAW,CAAC,CAAS;QACnB,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC;QACxD,OAAO,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,MAAM,EAAE,CAAC;IAC7E,CAAC;CACF;AACA,UAAsD,CAAC,eAAe,GAAG;IACxE,UAAU;QACR,OAAO,IAAI,OAAO,EAAE,CAAC;IACvB,CAAC;CACF,CAAC;AAEF,MAAM,EAAE,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAa,EAAE,CAAC,CAAC;AAC/D,MAAM,MAAM,GAAkB;IAC5B,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;IAChH,MAAM,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;IAChC,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;IACxB,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;IACxB,QAAQ,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;CACjC,CAAC;AAWF,KAAK,UAAU,QAAQ,CAAC,KAAiB;IACvC,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,wBAAwB,EAAE,EAAE,CAAC;QAChE,MAAM,IAAI,GAAG,GAAG,YAAY,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,YAAY,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAC/F,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,KAAK,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC9I,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,OAAO,CAAC,EAAE,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAClE,QAAQ,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACnG,CAAC;IACH,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;QAC9E,KAAK,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE;YAAE,IAAI,CAAC,GAAG,CAAE,GAAc,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAC3F,CAAC;AAED,QAAQ,CAAC,mCAAmC,EAAE,GAAG,EAAE;IACjD,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,GAAG,GAAG,aAAa,CAAC,EAAE,QAAQ,EAAE,+CAA+C,EAAE,EAAE,MAAM,CAAC,CAAC;QACjG,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,6EAA6E;QAC7E,MAAM,QAAQ,GAAoB,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;YAChE,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC,CAAC;YAC3C,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,WAAW,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QACjF,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,EAAE,MAAM,CAAC,CAAC;IAEX,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,GAAG,GAAG,aAAa,CAAC,EAAE,QAAQ,EAAE,+CAA+C,EAAE,EAAE,MAAM,CAAC,CAAC;QACjG,MAAM,QAAQ,GAAoB,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACjH,MAAM,CAAC,MAAM,QAAQ,CAAC,MAAM,WAAW,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3H,CAAC,EAAE,MAAM,CAAC,CAAC;IAEX,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,GAAG,GAAG,aAAa,CAAC,EAAE,QAAQ,EAAE,+CAA+C,EAAE,EAAE,MAAM,CAAC,CAAC;QACjG,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,CAAC;QAC1C,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,IAAI,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACpH,MAAM,KAAK,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,cAAc,CAAC,CAAC,CAAC;QAC3F,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QAC9B,kCAAkC;QAClC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,UAAU,CAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACjF,KAAK,CAAC,eAAe,EAAE,CAAC;QACxB,MAAM,CAAC,MAAM,QAAQ,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC,EAAE,MAAM,CAAC,CAAC;IAEX,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,qEAAqE;QACrE,sEAAsE;QACtE,+BAA+B;QAC/B,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAClC,yFAAyF;cACvF,4DAA4D;cAC5D,+DAA+D;cAC/D,iFAAiF,CACpF,CAAC;QACF,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,kGAAkG,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9J,MAAM,SAAS,GAAe,CAAC;gBAC7B,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;gBACpE,GAAG,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;gBACnD,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;aAChC,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,aAAa,CAAC,EAAE,QAAQ,EAAE,+BAA+B,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;QAC5F,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAoB,KAAK,EAAE,MAAM,EAAE,EAAE;YACjD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnB,OAAO,IAAI,UAAU,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACxE,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,WAAW,CAAC,GAAG,EAAE;YAClD,YAAY,EAAE,QAAQ;YACtB,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;YAC3D,YAAY,EAAE,KAAK,IAAI,EAAE,CAAC,GAAG;SAC9B,CAAC,CAAC,CAAC;QACJ,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,EAAE,MAAM,CAAC,CAAC;AACb,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fontFaces.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fontFaces.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fontFaces.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,193 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import fontkit from '@pdf-lib/fontkit';
4
+ import { buildDocument } from 'postext';
5
+ import { renderToPdf } from '../pdf-backend';
6
+ import { FontCache } from '../fontCache';
7
+ import { fontKey, parseFontString } from '../fontString';
8
+ /** Keeps the font fallbacks among the render warnings. */
9
+ function fallbacksInto(list, w) {
10
+ if (w.kind === 'fontFallback')
11
+ list.push(w);
12
+ }
13
+ // EF-20: the font provider is asked for the faces the pages actually paint
14
+ // — not the italic and bold-italic cut of every family a block could use —
15
+ // and a face it rejects falls back to one of the same family, with a
16
+ // warning, instead of failing the whole render.
17
+ const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
18
+ const face = fontkit.create(fontBytes);
19
+ class StubCtx {
20
+ font = '';
21
+ measureText(s) {
22
+ const sizePx = parseFontString(this.font)?.sizePx ?? 16;
23
+ return { width: (face.layout(s).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 hex = (h) => ({ hex: h, model: 'hex' });
33
+ const base = {
34
+ page: { width: pt(300), height: pt(420), margins: { top: pt(30), bottom: pt(30), left: pt(24), right: pt(24) } },
35
+ bodyText: { fontFamily: 'Lora' },
36
+ headings: { fontFamily: 'Oswald', levels: [{ level: 1, breakBefore: { enabled: false } }] },
37
+ };
38
+ /** A provider that serves any face (the Lora file) and records the asks. */
39
+ function recordingProvider(reject) {
40
+ const asked = [];
41
+ const provider = async (family, weight, style) => {
42
+ asked.push(fontKey(family, weight, style));
43
+ if (reject?.(family, weight, style))
44
+ throw new Error(`no ${family} ${weight} ${style}`);
45
+ return new Uint8Array(fontBytes);
46
+ };
47
+ return { asked, provider };
48
+ }
49
+ afterEach(() => {
50
+ vi.restoreAllMocks();
51
+ });
52
+ describe('renderToPdf — faces requested from the font provider (EF-20)', () => {
53
+ it('never asks for an italic cut the document does not set', async () => {
54
+ const doc = buildDocument({ markdown: '# Title\n\nPlain body text, nothing emphasised.' }, base);
55
+ const { asked, provider } = recordingProvider();
56
+ await renderToPdf(doc, { fontProvider: provider });
57
+ expect(asked.some((k) => k.endsWith('|italic'))).toBe(false);
58
+ // The heading face is asked for; its unused italic is not.
59
+ expect(asked.some((k) => k.startsWith('Oswald|'))).toBe(true);
60
+ expect(asked).not.toContain('Oswald|700|italic');
61
+ // Body bold is not used either.
62
+ expect(asked).not.toContain('Lora|700|normal');
63
+ });
64
+ it('asks for the italic and bold faces once the text sets them', async () => {
65
+ const doc = buildDocument({ markdown: 'Some *italic*, **bold** and ***both*** words.' }, base);
66
+ const { asked, provider } = recordingProvider();
67
+ await renderToPdf(doc, { fontProvider: provider });
68
+ expect(asked).toEqual(expect.arrayContaining(['Lora|400|normal', 'Lora|400|italic', 'Lora|700|normal', 'Lora|700|italic']));
69
+ });
70
+ it('asks for every face the pages draw, and only those (resources, lists, chips, callouts, slots)', async () => {
71
+ const resources = [{
72
+ id: 'tab',
73
+ typeId: 'table',
74
+ kind: 'table',
75
+ caption: 'A table with an *italic* word.',
76
+ note: 'Source: **bold** note.',
77
+ createdAt: 0,
78
+ updatedAt: 0,
79
+ table: { model: { headerRowCount: 1, rows: [[{ content: 'Head', isHeader: true }, { content: '**B**', isHeader: true }], [{ content: '*i* cell' }, { content: ':chip[tag]' }]] } },
80
+ placement: { position: 'here' },
81
+ }];
82
+ const config = {
83
+ ...base,
84
+ chipStyles: [{ id: 'chip', background: hex('#eeeeee'), bold: true }],
85
+ header: { elements: [{ kind: 'text', id: 'rh', content: 'Running head', fontSize: pt(8), overflow: 'clip', placement: { anchor: { to: 'container', edge: 'top-left' } } }] },
86
+ };
87
+ const markdown = [
88
+ '# Chapter',
89
+ '',
90
+ 'Body with *italic*.',
91
+ '',
92
+ '- item one',
93
+ '- item **two**',
94
+ '',
95
+ '1. first',
96
+ '2. second',
97
+ '',
98
+ ':::callout{title="Note"}',
99
+ 'Inside a box, ***bold italic***.',
100
+ ':::',
101
+ '',
102
+ '::resource{id="tab"}',
103
+ ].join('\n');
104
+ const doc = buildDocument({ markdown, resources }, config);
105
+ const { asked, provider } = recordingProvider();
106
+ const lookups = [];
107
+ const get = FontCache.prototype.get;
108
+ vi.spyOn(FontCache.prototype, 'get').mockImplementation(function (fontString) {
109
+ const parsed = parseFontString(fontString);
110
+ if (parsed)
111
+ lookups.push(fontKey(parsed.family, parsed.weight, parsed.style));
112
+ return get.call(this, fontString);
113
+ });
114
+ await renderToPdf(doc, { fontProvider: provider });
115
+ const requested = new Set(asked);
116
+ // Nothing the pages draw was left out of the plan.
117
+ for (const key of new Set(lookups))
118
+ expect(requested, key).toContain(key);
119
+ // And nothing was fetched that no page draws.
120
+ for (const key of requested)
121
+ expect(lookups, key).toContain(key);
122
+ });
123
+ });
124
+ describe('renderToPdf — per-face fallback (EF-20)', () => {
125
+ it('sets a rejected italic in the upright cut of the same family and warns', async () => {
126
+ // A family without italics (a display face, a naive Fontsource provider).
127
+ const doc = buildDocument({ markdown: '# Title\n\nA *slanted* word.' }, base);
128
+ const { asked, provider } = recordingProvider((family, _w, style) => family === 'Lora' && style === 'italic');
129
+ const warnings = [];
130
+ const bytes = await renderToPdf(doc, { fontProvider: provider, onWarning: (w) => fallbacksInto(warnings, w) });
131
+ expect(bytes.byteLength).toBeGreaterThan(0);
132
+ expect(asked).toContain('Lora|400|italic');
133
+ expect(warnings).toHaveLength(1);
134
+ expect(warnings[0]).toMatchObject({
135
+ kind: 'fontFallback',
136
+ family: 'Lora',
137
+ weight: 400,
138
+ style: 'italic',
139
+ fallback: { weight: 400, style: 'normal' },
140
+ reason: 'no Lora 400 italic',
141
+ });
142
+ expect(warnings[0].message).toContain('"Lora" 400 italic');
143
+ });
144
+ it('snaps a rejected weight to the regular or bold cut', async () => {
145
+ const config = { ...base, headings: { ...base.headings, fontWeight: 600 } };
146
+ const doc = buildDocument({ markdown: '# Semibold title\n\nBody.' }, config);
147
+ const { provider } = recordingProvider((family, weight) => family === 'Oswald' && weight === 600);
148
+ const warnings = [];
149
+ await renderToPdf(doc, { fontProvider: provider, onWarning: (w) => fallbacksInto(warnings, w) });
150
+ expect(warnings[0]).toMatchObject({ family: 'Oswald', weight: 600, fallback: { weight: 700, style: 'normal' } });
151
+ });
152
+ it('takes any cut of the family the provider has, heavier or lighter, upright or italic', async () => {
153
+ // A provider that serves Oswald at 700 only: a 400 heading takes it.
154
+ const doc = buildDocument({ markdown: '# Regular title\n\nBody.' }, { ...base, headings: { ...base.headings, fontWeight: 400 } });
155
+ const only700 = recordingProvider((family, weight, style) => family === 'Oswald' && !(weight === 700 && style === 'normal'));
156
+ const warnings = [];
157
+ await renderToPdf(doc, { fontProvider: only700.provider, onWarning: (w) => fallbacksInto(warnings, w) });
158
+ expect(warnings).toHaveLength(1);
159
+ expect(warnings[0]).toMatchObject({ family: 'Oswald', weight: 400, style: 'normal', fallback: { weight: 700, style: 'normal' } });
160
+ // A family shipped as italics only sets its upright text in them.
161
+ const italicsOnly = recordingProvider((family, _w, style) => family === 'Lora' && style === 'normal');
162
+ const italicWarnings = [];
163
+ await renderToPdf(buildDocument({ markdown: 'Plain body.' }, base), { fontProvider: italicsOnly.provider, onWarning: (w) => fallbacksInto(italicWarnings, w) });
164
+ expect(italicWarnings[0]).toMatchObject({ family: 'Lora', weight: 400, style: 'normal', fallback: { weight: 400, style: 'italic' } });
165
+ });
166
+ it('tries the other weights in the order CSS font matching does', async () => {
167
+ const heading = (fontWeight) => buildDocument({ markdown: '# Title\n\nBody.' }, { ...base, headings: { ...base.headings, fontWeight } });
168
+ const serves = (weights) => recordingProvider((family, weight) => family === 'Oswald' && !weights.includes(weight));
169
+ const fallbackFor = async (fontWeight, weights) => {
170
+ const warnings = [];
171
+ await renderToPdf(heading(fontWeight), { fontProvider: serves(weights).provider, onWarning: (w) => fallbacksInto(warnings, w) });
172
+ return warnings[0]?.fallback.weight;
173
+ };
174
+ // 400 → 500 first, then lighter; 300 → lighter first; 600 → heavier first.
175
+ expect(await fallbackFor(400, [300, 500])).toBe(500);
176
+ expect(await fallbackFor(300, [200, 400])).toBe(200);
177
+ expect(await fallbackFor(600, [500, 800])).toBe(800);
178
+ });
179
+ it('warns on the console when no onWarning is given', async () => {
180
+ const doc = buildDocument({ markdown: 'A *slanted* word.' }, base);
181
+ const { provider } = recordingProvider((family, _w, style) => family === 'Lora' && style === 'italic');
182
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
183
+ await renderToPdf(doc, { fontProvider: provider });
184
+ expect(warn).toHaveBeenCalledTimes(1);
185
+ expect(String(warn.mock.calls[0][0])).toContain('Lora');
186
+ });
187
+ it('still fails when no face of the family can be had', async () => {
188
+ const doc = buildDocument({ markdown: '# Title\n\nBody.' }, base);
189
+ const { provider } = recordingProvider((family) => family === 'Oswald');
190
+ await expect(renderToPdf(doc, { fontProvider: provider, onWarning: () => undefined })).rejects.toThrow(/failed to load font\(s\): Oswald 700/);
191
+ });
192
+ });
193
+ //# sourceMappingURL=fontFaces.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fontFaces.test.js","sourceRoot":"","sources":["../../src/__tests__/fontFaces.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fontStack.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fontStack.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fontStack.test.ts"],"names":[],"mappings":""}