postext-pdf 1.4.2 → 1.5.1
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.
- package/README.md +3 -3
- package/dist/__tests__/accessibility.test.js +66 -0
- package/dist/__tests__/accessibility.test.js.map +1 -1
- package/dist/__tests__/alpha.test.d.ts +2 -0
- package/dist/__tests__/alpha.test.d.ts.map +1 -0
- package/dist/__tests__/alpha.test.js +164 -0
- package/dist/__tests__/alpha.test.js.map +1 -0
- package/dist/__tests__/bleedClip.test.d.ts +2 -0
- package/dist/__tests__/bleedClip.test.d.ts.map +1 -0
- package/dist/__tests__/bleedClip.test.js +83 -0
- package/dist/__tests__/bleedClip.test.js.map +1 -0
- package/dist/__tests__/boxBorderInside.test.d.ts +2 -0
- package/dist/__tests__/boxBorderInside.test.d.ts.map +1 -0
- package/dist/__tests__/boxBorderInside.test.js +63 -0
- package/dist/__tests__/boxBorderInside.test.js.map +1 -0
- package/dist/__tests__/calloutRuntTracking.test.d.ts +2 -0
- package/dist/__tests__/calloutRuntTracking.test.d.ts.map +1 -0
- package/dist/__tests__/calloutRuntTracking.test.js +72 -0
- package/dist/__tests__/calloutRuntTracking.test.js.map +1 -0
- package/dist/__tests__/calloutStripeRadius.test.d.ts +2 -0
- package/dist/__tests__/calloutStripeRadius.test.d.ts.map +1 -0
- package/dist/__tests__/calloutStripeRadius.test.js +90 -0
- package/dist/__tests__/calloutStripeRadius.test.js.map +1 -0
- package/dist/__tests__/chipVerticalPadding.test.d.ts +2 -0
- package/dist/__tests__/chipVerticalPadding.test.d.ts.map +1 -0
- package/dist/__tests__/chipVerticalPadding.test.js +71 -0
- package/dist/__tests__/chipVerticalPadding.test.js.map +1 -0
- package/dist/__tests__/chips.test.js +20 -3
- package/dist/__tests__/chips.test.js.map +1 -1
- package/dist/__tests__/documentLanguage.test.d.ts +2 -0
- package/dist/__tests__/documentLanguage.test.d.ts.map +1 -0
- package/dist/__tests__/documentLanguage.test.js +44 -0
- package/dist/__tests__/documentLanguage.test.js.map +1 -0
- package/dist/__tests__/fallbackSpaces.test.d.ts +2 -0
- package/dist/__tests__/fallbackSpaces.test.d.ts.map +1 -0
- package/dist/__tests__/fallbackSpaces.test.js +156 -0
- package/dist/__tests__/fallbackSpaces.test.js.map +1 -0
- package/dist/__tests__/floatReadingOrder.test.d.ts +2 -0
- package/dist/__tests__/floatReadingOrder.test.d.ts.map +1 -0
- package/dist/__tests__/floatReadingOrder.test.js +208 -0
- package/dist/__tests__/floatReadingOrder.test.js.map +1 -0
- package/dist/__tests__/fontEmbedding.test.d.ts +2 -0
- package/dist/__tests__/fontEmbedding.test.d.ts.map +1 -0
- package/dist/__tests__/fontEmbedding.test.js +118 -0
- package/dist/__tests__/fontEmbedding.test.js.map +1 -0
- package/dist/__tests__/fontFaces.test.d.ts +2 -0
- package/dist/__tests__/fontFaces.test.d.ts.map +1 -0
- package/dist/__tests__/fontFaces.test.js +193 -0
- package/dist/__tests__/fontFaces.test.js.map +1 -0
- package/dist/__tests__/fontStack.test.d.ts +2 -0
- package/dist/__tests__/fontStack.test.d.ts.map +1 -0
- package/dist/__tests__/fontStack.test.js +48 -0
- package/dist/__tests__/fontStack.test.js.map +1 -0
- package/dist/__tests__/headerFooter.test.js +83 -1
- package/dist/__tests__/headerFooter.test.js.map +1 -1
- package/dist/__tests__/headingAlign.test.d.ts +2 -0
- package/dist/__tests__/headingAlign.test.d.ts.map +1 -0
- package/dist/__tests__/headingAlign.test.js +66 -0
- package/dist/__tests__/headingAlign.test.js.map +1 -0
- package/dist/__tests__/hiddenHeadings.test.d.ts +2 -0
- package/dist/__tests__/hiddenHeadings.test.d.ts.map +1 -0
- package/dist/__tests__/hiddenHeadings.test.js +96 -0
- package/dist/__tests__/hiddenHeadings.test.js.map +1 -0
- package/dist/__tests__/lineTracking.test.d.ts +2 -0
- package/dist/__tests__/lineTracking.test.d.ts.map +1 -0
- package/dist/__tests__/lineTracking.test.js +80 -0
- package/dist/__tests__/lineTracking.test.js.map +1 -0
- package/dist/__tests__/measuredLines.test.d.ts +2 -0
- package/dist/__tests__/measuredLines.test.d.ts.map +1 -0
- package/dist/__tests__/measuredLines.test.js +255 -0
- package/dist/__tests__/measuredLines.test.js.map +1 -0
- package/dist/__tests__/metadata.test.d.ts +2 -0
- package/dist/__tests__/metadata.test.d.ts.map +1 -0
- package/dist/__tests__/metadata.test.js +51 -0
- package/dist/__tests__/metadata.test.js.map +1 -0
- package/dist/__tests__/outlineTitles.test.d.ts +2 -0
- package/dist/__tests__/outlineTitles.test.d.ts.map +1 -0
- package/dist/__tests__/outlineTitles.test.js +59 -0
- package/dist/__tests__/outlineTitles.test.js.map +1 -0
- package/dist/__tests__/pdfGenerationConfig.test.d.ts +2 -0
- package/dist/__tests__/pdfGenerationConfig.test.d.ts.map +1 -0
- package/dist/__tests__/pdfGenerationConfig.test.js +76 -0
- package/dist/__tests__/pdfGenerationConfig.test.js.map +1 -0
- package/dist/__tests__/pdfWorker.test.d.ts +2 -0
- package/dist/__tests__/pdfWorker.test.d.ts.map +1 -0
- package/dist/__tests__/pdfWorker.test.js +123 -0
- package/dist/__tests__/pdfWorker.test.js.map +1 -0
- package/dist/__tests__/printMarks.test.d.ts +2 -0
- package/dist/__tests__/printMarks.test.d.ts.map +1 -0
- package/dist/__tests__/printMarks.test.js +170 -0
- package/dist/__tests__/printMarks.test.js.map +1 -0
- package/dist/__tests__/printMaster.test.d.ts +2 -0
- package/dist/__tests__/printMaster.test.d.ts.map +1 -0
- package/dist/__tests__/printMaster.test.js +161 -0
- package/dist/__tests__/printMaster.test.js.map +1 -0
- package/dist/__tests__/renderWarnings.test.d.ts +2 -0
- package/dist/__tests__/renderWarnings.test.d.ts.map +1 -0
- package/dist/__tests__/renderWarnings.test.js +89 -0
- package/dist/__tests__/renderWarnings.test.js.map +1 -0
- package/dist/__tests__/repeatedHyphen.test.d.ts +2 -0
- package/dist/__tests__/repeatedHyphen.test.d.ts.map +1 -0
- package/dist/__tests__/repeatedHyphen.test.js +125 -0
- package/dist/__tests__/repeatedHyphen.test.js.map +1 -0
- package/dist/__tests__/scriptStacking.test.d.ts +2 -0
- package/dist/__tests__/scriptStacking.test.d.ts.map +1 -0
- package/dist/__tests__/scriptStacking.test.js +66 -0
- package/dist/__tests__/scriptStacking.test.js.map +1 -0
- package/dist/__tests__/sectionColumnRule.test.d.ts +2 -0
- package/dist/__tests__/sectionColumnRule.test.d.ts.map +1 -0
- package/dist/__tests__/sectionColumnRule.test.js +144 -0
- package/dist/__tests__/sectionColumnRule.test.js.map +1 -0
- package/dist/__tests__/svgFormReuse.test.d.ts +2 -0
- package/dist/__tests__/svgFormReuse.test.d.ts.map +1 -0
- package/dist/__tests__/svgFormReuse.test.js +102 -0
- package/dist/__tests__/svgFormReuse.test.js.map +1 -0
- package/dist/__tests__/svgVector.test.js +85 -11
- package/dist/__tests__/svgVector.test.js.map +1 -1
- package/dist/__tests__/tableFillSeams.test.d.ts +2 -0
- package/dist/__tests__/tableFillSeams.test.d.ts.map +1 -0
- package/dist/__tests__/tableFillSeams.test.js +72 -0
- package/dist/__tests__/tableFillSeams.test.js.map +1 -0
- package/dist/__tests__/tocLeaderKerning.test.d.ts +2 -0
- package/dist/__tests__/tocLeaderKerning.test.d.ts.map +1 -0
- package/dist/__tests__/tocLeaderKerning.test.js +84 -0
- package/dist/__tests__/tocLeaderKerning.test.js.map +1 -0
- package/dist/__tests__/tocNumberBaseline.test.d.ts +2 -0
- package/dist/__tests__/tocNumberBaseline.test.d.ts.map +1 -0
- package/dist/__tests__/tocNumberBaseline.test.js +72 -0
- package/dist/__tests__/tocNumberBaseline.test.js.map +1 -0
- package/dist/__tests__/tocTagging.test.d.ts +2 -0
- package/dist/__tests__/tocTagging.test.d.ts.map +1 -0
- package/dist/__tests__/tocTagging.test.js +120 -0
- package/dist/__tests__/tocTagging.test.js.map +1 -0
- package/dist/__tests__/trackedLineAlign.test.d.ts +2 -0
- package/dist/__tests__/trackedLineAlign.test.d.ts.map +1 -0
- package/dist/__tests__/trackedLineAlign.test.js +73 -0
- package/dist/__tests__/trackedLineAlign.test.js.map +1 -0
- package/dist/__tests__/uriLinks.test.d.ts +2 -0
- package/dist/__tests__/uriLinks.test.d.ts.map +1 -0
- package/dist/__tests__/uriLinks.test.js +103 -0
- package/dist/__tests__/uriLinks.test.js.map +1 -0
- package/dist/colors.d.ts +18 -3
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +77 -25
- package/dist/colors.js.map +1 -1
- package/dist/fontCache.d.ts +67 -4
- package/dist/fontCache.d.ts.map +1 -1
- package/dist/fontCache.js +200 -23
- package/dist/fontCache.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/pdf-backend/blockRender.d.ts.map +1 -1
- package/dist/pdf-backend/blockRender.js +127 -28
- package/dist/pdf-backend/blockRender.js.map +1 -1
- package/dist/pdf-backend/fallbackSpaces.d.ts +37 -0
- package/dist/pdf-backend/fallbackSpaces.d.ts.map +1 -0
- package/dist/pdf-backend/fallbackSpaces.js +119 -0
- package/dist/pdf-backend/fallbackSpaces.js.map +1 -0
- package/dist/pdf-backend/fontHelpers.d.ts +10 -1
- package/dist/pdf-backend/fontHelpers.d.ts.map +1 -1
- package/dist/pdf-backend/fontHelpers.js +111 -65
- package/dist/pdf-backend/fontHelpers.js.map +1 -1
- package/dist/pdf-backend/headerFooter.d.ts +4 -2
- package/dist/pdf-backend/headerFooter.d.ts.map +1 -1
- package/dist/pdf-backend/headerFooter.js +105 -48
- package/dist/pdf-backend/headerFooter.js.map +1 -1
- package/dist/pdf-backend/index.d.ts +55 -6
- package/dist/pdf-backend/index.d.ts.map +1 -1
- package/dist/pdf-backend/index.js +135 -35
- package/dist/pdf-backend/index.js.map +1 -1
- package/dist/pdf-backend/links.d.ts +82 -1
- package/dist/pdf-backend/links.d.ts.map +1 -1
- package/dist/pdf-backend/links.js +141 -3
- package/dist/pdf-backend/links.js.map +1 -1
- package/dist/pdf-backend/outlines.d.ts.map +1 -1
- package/dist/pdf-backend/outlines.js +7 -0
- package/dist/pdf-backend/outlines.js.map +1 -1
- package/dist/pdf-backend/pageDecorations.d.ts.map +1 -1
- package/dist/pdf-backend/pageDecorations.js +42 -26
- package/dist/pdf-backend/pageDecorations.js.map +1 -1
- package/dist/pdf-backend/primitives.d.ts +61 -1
- package/dist/pdf-backend/primitives.d.ts.map +1 -1
- package/dist/pdf-backend/primitives.js +318 -18
- package/dist/pdf-backend/primitives.js.map +1 -1
- package/dist/pdf-backend/renderResourceBlock.d.ts +3 -1
- package/dist/pdf-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/pdf-backend/renderResourceBlock.js +97 -19
- package/dist/pdf-backend/renderResourceBlock.js.map +1 -1
- package/dist/pdf-backend/structureFlow.d.ts +70 -3
- package/dist/pdf-backend/structureFlow.d.ts.map +1 -1
- package/dist/pdf-backend/structureFlow.js +209 -27
- package/dist/pdf-backend/structureFlow.js.map +1 -1
- package/dist/pdf-backend/svgVector.d.ts +7 -2
- package/dist/pdf-backend/svgVector.d.ts.map +1 -1
- package/dist/pdf-backend/svgVector.js +157 -72
- package/dist/pdf-backend/svgVector.js.map +1 -1
- package/dist/pdf-backend/tagging.d.ts +1 -1
- package/dist/pdf-backend/tagging.d.ts.map +1 -1
- package/dist/pdf-backend/tagging.js.map +1 -1
- package/dist/worker/client.d.ts +1 -1
- package/dist/worker/client.d.ts.map +1 -1
- package/dist/worker/client.js +8 -0
- package/dist/worker/client.js.map +1 -1
- package/dist/worker/pdf.worker.js +1 -0
- package/dist/worker/pdf.worker.js.map +1 -1
- package/dist/worker/protocol.d.ts +8 -1
- package/dist/worker/protocol.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
// EF-145: the rows of a `:::toc` were tagged like body text. An unnumbered
|
|
9
|
+
// row was `P › Link`, and a numbered one, whose number is painted as a
|
|
10
|
+
// list marker, a one-item list of its own (`L › LI › Lbl + LBody › Link`).
|
|
11
|
+
// They are now one `TOC` with a `TOCI` per row, in row order: `Lbl` for the
|
|
12
|
+
// number, `Reference › Link` for the title and page (PDF 1.7 §14.8.4.3.2).
|
|
13
|
+
const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
|
|
14
|
+
const fontProvider = async () => new Uint8Array(fontBytes);
|
|
15
|
+
const face = fontkit.create(fontBytes);
|
|
16
|
+
class StubCtx {
|
|
17
|
+
font = '';
|
|
18
|
+
measureText(s) {
|
|
19
|
+
const sizePx = parseFontString(this.font)?.sizePx ?? 16;
|
|
20
|
+
const run = face.layout(s);
|
|
21
|
+
return { width: (run.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 filler = (n) => Array.from({ length: n }, (_, i) => `Paragraph ${i} with enough words to consume vertical space and force the page to overflow.`).join('\n\n');
|
|
31
|
+
const config = {
|
|
32
|
+
page: {
|
|
33
|
+
width: pt(360), height: pt(300),
|
|
34
|
+
margins: { top: pt(18), bottom: pt(18), left: pt(18), right: pt(18) },
|
|
35
|
+
},
|
|
36
|
+
layout: { layoutType: 'single' },
|
|
37
|
+
locale: 'en-us',
|
|
38
|
+
headings: { levels: [{ level: 1, breakBefore: { enabled: true, parity: 'odd' } }] },
|
|
39
|
+
headingStyles: [{ id: 'front', numbered: false }],
|
|
40
|
+
toc: { levels: [{ level: 1 }, { level: 2 }], parts: { enabled: true } },
|
|
41
|
+
};
|
|
42
|
+
const book = `# Contents {style="front" toc="false"}
|
|
43
|
+
|
|
44
|
+
:::toc
|
|
45
|
+
|
|
46
|
+
# Preface {style="front"}
|
|
47
|
+
|
|
48
|
+
${filler(2)}
|
|
49
|
+
|
|
50
|
+
:::part{number="I" title="Foundations"}
|
|
51
|
+
:::
|
|
52
|
+
|
|
53
|
+
# The lantern
|
|
54
|
+
|
|
55
|
+
${filler(2)}
|
|
56
|
+
|
|
57
|
+
## Wicks
|
|
58
|
+
|
|
59
|
+
${filler(1)}
|
|
60
|
+
|
|
61
|
+
# Trimming
|
|
62
|
+
|
|
63
|
+
${filler(1)}`;
|
|
64
|
+
const textOf = (v) => (v instanceof PDFHexString || v instanceof PDFString ? v.decodeText() : undefined);
|
|
65
|
+
function readElem(pdf, ref) {
|
|
66
|
+
const dict = pdf.context.lookup(ref, PDFDict);
|
|
67
|
+
const elem = { type: dict.get(PDFName.of('S')).decodeText(), kids: [] };
|
|
68
|
+
const k = dict.get(PDFName.of('K'));
|
|
69
|
+
for (const kid of k instanceof PDFArray ? k.asArray() : k ? [k] : []) {
|
|
70
|
+
if (kid instanceof PDFRef)
|
|
71
|
+
elem.kids.push(readElem(pdf, kid));
|
|
72
|
+
else if (kid instanceof PDFDict && kid.get(PDFName.of('Type'))?.decodeText() === 'OBJR') {
|
|
73
|
+
elem.contents = textOf(kid.lookup(PDFName.of('Obj'), PDFDict).get(PDFName.of('Contents')));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return elem;
|
|
77
|
+
}
|
|
78
|
+
let root;
|
|
79
|
+
beforeAll(async () => {
|
|
80
|
+
const doc = buildDocument({ markdown: book, metadata: { title: 'Contents book' } }, config);
|
|
81
|
+
const bytes = await renderToPdf(doc, { fontProvider });
|
|
82
|
+
const out = process.env.POSTEXT_TOC_TAGS_PDF_OUT;
|
|
83
|
+
if (out)
|
|
84
|
+
fs.writeFileSync(out, bytes);
|
|
85
|
+
const pdf = await PDFDocument.load(bytes);
|
|
86
|
+
const treeRoot = pdf.catalog.lookup(PDFName.of('StructTreeRoot'), PDFDict);
|
|
87
|
+
root = readElem(pdf, treeRoot.get(PDFName.of('K')).get(0));
|
|
88
|
+
}, 60_000);
|
|
89
|
+
describe('contents rows in the tagged PDF (EF-145)', () => {
|
|
90
|
+
it('tags the contents as one TOC, right after its heading', () => {
|
|
91
|
+
const tocs = root.kids.filter((k) => k.type === 'TOC');
|
|
92
|
+
expect(tocs).toHaveLength(1);
|
|
93
|
+
expect(root.kids[0].type).toBe('H1');
|
|
94
|
+
expect(root.kids[1].type).toBe('TOC');
|
|
95
|
+
// No row left as a paragraph or a one-item list.
|
|
96
|
+
expect(root.kids.slice(2, 4).map((k) => k.type)).not.toContain('L');
|
|
97
|
+
});
|
|
98
|
+
it('gives every row a TOCI, in row order, the part row included', () => {
|
|
99
|
+
const toc = root.kids.find((k) => k.type === 'TOC');
|
|
100
|
+
expect(toc.kids.every((k) => k.type === 'TOCI')).toBe(true);
|
|
101
|
+
const linkText = (item) => {
|
|
102
|
+
const ref = item.kids.find((k) => k.type === 'Reference');
|
|
103
|
+
return ref.kids.find((k) => k.type === 'Link').contents ?? '';
|
|
104
|
+
};
|
|
105
|
+
// The link's description: the row's text and page, a part's number and title.
|
|
106
|
+
const rows = toc.kids.map(linkText);
|
|
107
|
+
const expected = ['Preface', 'I Foundations', 'The lantern', 'Wicks', 'Trimming'];
|
|
108
|
+
expect(rows).toHaveLength(expected.length);
|
|
109
|
+
rows.forEach((row, i) => expect(row.startsWith(expected[i]), `${row} / ${expected[i]}`).toBe(true));
|
|
110
|
+
});
|
|
111
|
+
it('labels a numbered row\'s number and links its title', () => {
|
|
112
|
+
const toc = root.kids.find((k) => k.type === 'TOC');
|
|
113
|
+
const lantern = toc.kids[2];
|
|
114
|
+
expect(lantern.kids.map((k) => k.type)).toEqual(['Lbl', 'Reference']);
|
|
115
|
+
expect(lantern.kids[1].kids.map((k) => k.type)).toEqual(['Link']);
|
|
116
|
+
const preface = toc.kids[0];
|
|
117
|
+
expect(preface.kids.map((k) => k.type)).toEqual(['Reference']);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=tocTagging.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tocTagging.test.js","sourceRoot":"","sources":["../../src/__tests__/tocTagging.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackedLineAlign.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/trackedLineAlign.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { PDFArray, PDFDocument, PDFRawStream, 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
|
+
// EF-153 for text in the flow: a centred or right-aligned tracked line is
|
|
9
|
+
// placed by its letters, the tracking after the last one left out, as on
|
|
10
|
+
// canvas, in HTML and in design text.
|
|
11
|
+
const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
|
|
12
|
+
const fontProvider = async () => new Uint8Array(fontBytes);
|
|
13
|
+
const face = fontkit.create(fontBytes);
|
|
14
|
+
class StubCtx {
|
|
15
|
+
font = '';
|
|
16
|
+
measureText(s) {
|
|
17
|
+
const sizePx = parseFontString(this.font)?.sizePx ?? 16;
|
|
18
|
+
const run = face.layout(s);
|
|
19
|
+
return { width: (run.advanceWidth / face.unitsPerEm) * sizePx };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
globalThis.OffscreenCanvas = class {
|
|
23
|
+
getContext() {
|
|
24
|
+
return new StubCtx();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const pt = (value) => ({ value, unit: 'pt' });
|
|
28
|
+
/** 72 dpi: 1 pt = 1 px. An H1 tracked 3 pt. */
|
|
29
|
+
const config = (textAlign) => ({
|
|
30
|
+
page: { width: pt(300), height: pt(200), dpi: 72, margins: { top: pt(20), bottom: pt(20), left: pt(20), right: pt(20) } },
|
|
31
|
+
layout: { layoutType: 'single' },
|
|
32
|
+
headings: { textAlign, levels: [{ level: 1, letterSpacing: pt(3), breakBefore: { enabled: false } }] },
|
|
33
|
+
});
|
|
34
|
+
async function contentOf(doc) {
|
|
35
|
+
const pdf = await PDFDocument.load(await renderToPdf(doc, { fontProvider, accessible: false }));
|
|
36
|
+
const contents = pdf.getPage(0).node.Contents();
|
|
37
|
+
const refs = contents instanceof PDFArray ? contents.asArray() : [contents];
|
|
38
|
+
return refs
|
|
39
|
+
.map((ref) => {
|
|
40
|
+
const s = pdf.context.lookup(ref);
|
|
41
|
+
return s instanceof PDFRawStream ? new TextDecoder('latin1').decode(decodePDFRawStream(s).decode()) : '';
|
|
42
|
+
})
|
|
43
|
+
.join('\n');
|
|
44
|
+
}
|
|
45
|
+
/** The x (pt) of the first text object on the heading's baseline. */
|
|
46
|
+
async function headingX(doc) {
|
|
47
|
+
const heading = doc.blocks.find((b) => b.type === 'heading');
|
|
48
|
+
const y = doc.pages[0].height - heading.lines[0].baseline;
|
|
49
|
+
const content = await contentOf(doc);
|
|
50
|
+
for (const m of content.matchAll(/1 0 0 1 ([\d.-]+) ([\d.-]+) Tm/g)) {
|
|
51
|
+
if (Math.abs(Number(m[2]) - y) < 0.01)
|
|
52
|
+
return Number(m[1]);
|
|
53
|
+
}
|
|
54
|
+
throw new Error('heading text not found');
|
|
55
|
+
}
|
|
56
|
+
describe('tracked flow lines in the PDF (EF-153)', () => {
|
|
57
|
+
it('centres and right-aligns a tracked heading by its letters', async () => {
|
|
58
|
+
for (const [align, share] of [['left', 0], ['center', 0.5], ['right', 1]]) {
|
|
59
|
+
const doc = buildDocument({ markdown: '# Title\n\nText.' }, config(align));
|
|
60
|
+
const heading = doc.blocks.find((b) => b.type === 'heading');
|
|
61
|
+
expect(heading.letterSpacing).toBeCloseTo(3, 6);
|
|
62
|
+
// The line's width counts 3 pt after the final 'e'; its letters end
|
|
63
|
+
// 3 pt sooner.
|
|
64
|
+
const slack = heading.bbox.width - (heading.lines[0].bbox.width - 3);
|
|
65
|
+
expect(await headingX(doc)).toBeCloseTo(heading.bbox.x + slack * share, 1);
|
|
66
|
+
// A line without segments takes the same position.
|
|
67
|
+
for (const line of heading.lines)
|
|
68
|
+
delete line.segments;
|
|
69
|
+
expect(await headingX(doc)).toBeCloseTo(heading.bbox.x + slack * share, 1);
|
|
70
|
+
}
|
|
71
|
+
}, 60_000);
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=trackedLineAlign.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackedLineAlign.test.js","sourceRoot":"","sources":["../../src/__tests__/trackedLineAlign.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uriLinks.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/uriLinks.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { PDFArray, PDFDict, PDFDocument, PDFHexString, PDFName, PDFNumber, 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
|
+
// EF-36: the words of a Markdown link carry its URL (`VDTLineSegment.href`)
|
|
9
|
+
// and the PDF makes them a live link — one URI link annotation per run of
|
|
10
|
+
// linked words on a line, and a `Link` element in a tagged PDF.
|
|
11
|
+
const fontBytes = fs.readFileSync(new URL('../../../../apps/web/public/fonts/Lora-Regular.ttf', import.meta.url));
|
|
12
|
+
const face = fontkit.create(fontBytes);
|
|
13
|
+
class StubCtx {
|
|
14
|
+
font = '';
|
|
15
|
+
measureText(s) {
|
|
16
|
+
const sizePx = parseFontString(this.font)?.sizePx ?? 16;
|
|
17
|
+
return { width: (face.layout(s).advanceWidth / face.unitsPerEm) * sizePx };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
globalThis.OffscreenCanvas = class {
|
|
21
|
+
getContext() {
|
|
22
|
+
return new StubCtx();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const pt = (value) => ({ value, unit: 'pt' });
|
|
26
|
+
const config = {
|
|
27
|
+
page: { width: pt(360), height: pt(360), margins: { top: pt(20), bottom: pt(20), left: pt(20), right: pt(20) } },
|
|
28
|
+
bodyText: { fontFamily: 'Lora' },
|
|
29
|
+
headings: { fontFamily: 'Lora', levels: [{ level: 1, breakBefore: { enabled: false } }] },
|
|
30
|
+
};
|
|
31
|
+
function uriAnnots(pdf, pageIndex = 0) {
|
|
32
|
+
const annots = pdf.getPage(pageIndex).node.lookup(PDFName.of('Annots'));
|
|
33
|
+
if (!(annots instanceof PDFArray))
|
|
34
|
+
return [];
|
|
35
|
+
const out = [];
|
|
36
|
+
for (let i = 0; i < annots.size(); i++) {
|
|
37
|
+
const a = annots.lookup(i);
|
|
38
|
+
if (!(a instanceof PDFDict))
|
|
39
|
+
continue;
|
|
40
|
+
const action = a.lookup(PDFName.of('A'));
|
|
41
|
+
if (!(action instanceof PDFDict))
|
|
42
|
+
continue;
|
|
43
|
+
const uri = action.lookup(PDFName.of('URI'));
|
|
44
|
+
const rect = a.lookup(PDFName.of('Rect'));
|
|
45
|
+
const contents = a.lookup(PDFName.of('Contents'));
|
|
46
|
+
const sp = a.lookup(PDFName.of('StructParent'));
|
|
47
|
+
out.push({
|
|
48
|
+
uri: uri instanceof PDFString || uri instanceof PDFHexString ? uri.decodeText() : String(uri),
|
|
49
|
+
rect: rect instanceof PDFArray ? rect.asArray().map((n) => n.asNumber()) : [],
|
|
50
|
+
...(contents instanceof PDFHexString || contents instanceof PDFString ? { contents: contents.decodeText() } : {}),
|
|
51
|
+
...(sp instanceof PDFNumber ? { structParent: sp.asNumber() } : {}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
async function render(markdown, accessible = true, resources = []) {
|
|
57
|
+
const doc = buildDocument({ markdown, resources }, config);
|
|
58
|
+
const bytes = await renderToPdf(doc, { fontProvider: async () => new Uint8Array(fontBytes), accessible });
|
|
59
|
+
return PDFDocument.load(bytes);
|
|
60
|
+
}
|
|
61
|
+
describe('renderToPdf — Markdown links (EF-36)', () => {
|
|
62
|
+
it('annotates each linked run with a URI action, absolute URLs only', async () => {
|
|
63
|
+
const pdf = await render('Visit [the site](https://postext.dev/docs) today, or [write](mailto:team@postext.example). A [relative guide](../guide) stays text.');
|
|
64
|
+
const annots = uriAnnots(pdf);
|
|
65
|
+
expect(annots.map((a) => a.uri)).toEqual(['https://postext.dev/docs', 'mailto:team@postext.example']);
|
|
66
|
+
// "the site": one annotation spanning both words.
|
|
67
|
+
const [site] = annots;
|
|
68
|
+
expect(site.rect[2] - site.rect[0]).toBeGreaterThan(20);
|
|
69
|
+
expect(site.contents).toBe('the site');
|
|
70
|
+
expect(site.structParent).toBeTypeOf('number');
|
|
71
|
+
});
|
|
72
|
+
it('splits a link broken across lines into one annotation per line', async () => {
|
|
73
|
+
const words = 'a long run of linked words that has to wrap over more than one line of this narrow page';
|
|
74
|
+
const pdf = await render(`Before [${words}](https://postext.dev/wrap) after.`);
|
|
75
|
+
const annots = uriAnnots(pdf).filter((a) => a.uri === 'https://postext.dev/wrap');
|
|
76
|
+
expect(annots.length).toBeGreaterThan(1);
|
|
77
|
+
// Lines go down the page.
|
|
78
|
+
for (let i = 1; i < annots.length; i++)
|
|
79
|
+
expect(annots[i].rect[1]).toBeLessThan(annots[i - 1].rect[1]);
|
|
80
|
+
});
|
|
81
|
+
it('annotates links in captions and table cells, and in an untagged PDF too', async () => {
|
|
82
|
+
const resources = [{
|
|
83
|
+
id: 't',
|
|
84
|
+
typeId: 'table',
|
|
85
|
+
kind: 'table',
|
|
86
|
+
caption: 'From [the census](https://a.example/census).',
|
|
87
|
+
createdAt: 0,
|
|
88
|
+
updatedAt: 0,
|
|
89
|
+
table: { model: { rows: [[{ content: 'See [row](https://a.example/row)' }]] } },
|
|
90
|
+
placement: { position: 'here' },
|
|
91
|
+
}];
|
|
92
|
+
const pdf = await render('Text.\n\n::resource{id="t"}', false, resources);
|
|
93
|
+
const uris = uriAnnots(pdf).map((a) => a.uri).sort();
|
|
94
|
+
expect(uris).toEqual(['https://a.example/census', 'https://a.example/row']);
|
|
95
|
+
expect(uriAnnots(pdf).every((a) => a.structParent === undefined)).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
it('percent-encodes characters a PDF URI cannot hold', async () => {
|
|
98
|
+
const pdf = await render('See [niño](https://a.example/año?q=(1)) here.');
|
|
99
|
+
// Balanced parentheses belong to the destination, as CommonMark reads it.
|
|
100
|
+
expect(uriAnnots(pdf)[0].uri).toBe('https://a.example/a%C3%B1o?q=(1)');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=uriLinks.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uriLinks.test.js","sourceRoot":"","sources":["../../src/__tests__/uriLinks.test.ts"],"names":[],"mappings":"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"}
|
package/dist/colors.d.ts
CHANGED
|
@@ -9,12 +9,27 @@ export interface CmykTuple {
|
|
|
9
9
|
y: number;
|
|
10
10
|
k: number;
|
|
11
11
|
}
|
|
12
|
+
export interface RgbaTuple extends RgbTuple {
|
|
13
|
+
/** Opacity, 0 (clear) … 1 (opaque). */
|
|
14
|
+
a: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse the colour strings a postext document carries — `#rgb`, `#rgba`,
|
|
18
|
+
* `#rrggbb`, `#rrggbbaa`, `rgb()` / `rgba()` (comma or space syntax, with a
|
|
19
|
+
* `/ alpha`) and `transparent` — into 0..1 RGB components plus an opacity.
|
|
20
|
+
* Null for anything else (a keyword such as `red` or `none`, a typo).
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseCssColor(value: string): RgbaTuple | null;
|
|
12
23
|
/**
|
|
13
|
-
* Parse a
|
|
14
|
-
*
|
|
15
|
-
*
|
|
24
|
+
* Parse a document colour (see {@link parseCssColor}) into normalized 0..1
|
|
25
|
+
* RGB components suitable for pdf-lib's `rgb()` factory. The alpha channel
|
|
26
|
+
* is dropped (read it with {@link colorAlpha}); malformed inputs fall back
|
|
27
|
+
* to black.
|
|
16
28
|
*/
|
|
17
29
|
export declare function hexToRgb(hex: string): RgbTuple;
|
|
30
|
+
/** Opacity of a document colour, 0 … 1; 1 when it carries none or does not
|
|
31
|
+
* parse. */
|
|
32
|
+
export declare function colorAlpha(value: string): number;
|
|
18
33
|
/** Naive RGB → CMYK conversion (no ICC profile). Adequate for forcing a
|
|
19
34
|
* 4-channel output when the source is authored in sRGB hex. */
|
|
20
35
|
export declare function rgbToCmyk({ r, g, b }: RgbTuple): CmykTuple;
|
package/dist/colors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED
|
|
1
|
+
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,uCAAuC;IACvC,CAAC,EAAE,MAAM,CAAC;CACX;AAqBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAmC7D;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAG9C;AAED;aACa;AACb,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED;gEACgE;AAChE,wBAAgB,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,GAAG,SAAS,CAU1D;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAE5D"}
|
package/dist/colors.js
CHANGED
|
@@ -1,33 +1,85 @@
|
|
|
1
|
+
const HEX_DIGITS_RE = /^[0-9a-f]+$/i;
|
|
2
|
+
/** A CSS `rgb()` channel (`0…255` or a percentage) as 0..1; null when
|
|
3
|
+
* malformed. */
|
|
4
|
+
function rgbChannel(token) {
|
|
5
|
+
const pct = token.endsWith('%');
|
|
6
|
+
const n = Number(pct ? token.slice(0, -1) : token);
|
|
7
|
+
if (token === '' || !Number.isFinite(n))
|
|
8
|
+
return null;
|
|
9
|
+
return Math.max(0, Math.min(1, pct ? n / 100 : n / 255));
|
|
10
|
+
}
|
|
11
|
+
/** A CSS alpha value (`0…1` or a percentage); null when malformed. */
|
|
12
|
+
function alphaChannel(token) {
|
|
13
|
+
const pct = token.endsWith('%');
|
|
14
|
+
const n = Number(pct ? token.slice(0, -1) : token);
|
|
15
|
+
if (token === '' || !Number.isFinite(n))
|
|
16
|
+
return null;
|
|
17
|
+
return Math.max(0, Math.min(1, pct ? n / 100 : n));
|
|
18
|
+
}
|
|
1
19
|
/**
|
|
2
|
-
* Parse
|
|
3
|
-
*
|
|
4
|
-
*
|
|
20
|
+
* Parse the colour strings a postext document carries — `#rgb`, `#rgba`,
|
|
21
|
+
* `#rrggbb`, `#rrggbbaa`, `rgb()` / `rgba()` (comma or space syntax, with a
|
|
22
|
+
* `/ alpha`) and `transparent` — into 0..1 RGB components plus an opacity.
|
|
23
|
+
* Null for anything else (a keyword such as `red` or `none`, a typo).
|
|
5
24
|
*/
|
|
6
|
-
export function
|
|
7
|
-
if (!
|
|
8
|
-
return
|
|
9
|
-
|
|
10
|
-
if (s.
|
|
11
|
-
|
|
12
|
-
if (s.
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
export function parseCssColor(value) {
|
|
26
|
+
if (!value || typeof value !== 'string')
|
|
27
|
+
return null;
|
|
28
|
+
const s = value.trim();
|
|
29
|
+
if (s.toLowerCase() === 'transparent')
|
|
30
|
+
return { r: 0, g: 0, b: 0, a: 0 };
|
|
31
|
+
if (s.startsWith('#')) {
|
|
32
|
+
const h = s.slice(1);
|
|
33
|
+
if (!HEX_DIGITS_RE.test(h))
|
|
34
|
+
return null;
|
|
35
|
+
const byte = (i, short) => parseInt(short ? h[i] + h[i] : h.slice(i * 2, i * 2 + 2), 16) / 255;
|
|
36
|
+
if (h.length === 3 || h.length === 4) {
|
|
37
|
+
return { r: byte(0, true), g: byte(1, true), b: byte(2, true), a: h.length === 4 ? byte(3, true) : 1 };
|
|
18
38
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (s.length === 6 || s.length === 8) {
|
|
22
|
-
const r = parseInt(s.slice(0, 2), 16);
|
|
23
|
-
const g = parseInt(s.slice(2, 4), 16);
|
|
24
|
-
const b = parseInt(s.slice(4, 6), 16);
|
|
25
|
-
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
|
|
26
|
-
return { r: 0, g: 0, b: 0 };
|
|
39
|
+
if (h.length === 6 || h.length === 8) {
|
|
40
|
+
return { r: byte(0, false), g: byte(1, false), b: byte(2, false), a: h.length === 8 ? byte(3, false) : 1 };
|
|
27
41
|
}
|
|
28
|
-
return
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const fn = /^rgba?\(\s*([^)]*?)\s*\)$/i.exec(s);
|
|
45
|
+
if (!fn)
|
|
46
|
+
return null;
|
|
47
|
+
// `r, g, b[, a]` or `r g b[ / a]`.
|
|
48
|
+
const body = fn[1];
|
|
49
|
+
let parts;
|
|
50
|
+
let alpha;
|
|
51
|
+
if (body.includes(',')) {
|
|
52
|
+
parts = body.split(',').map((p) => p.trim());
|
|
53
|
+
if (parts.length === 4)
|
|
54
|
+
alpha = parts.pop();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const [channels, slashAlpha] = body.split('/').map((p) => p.trim());
|
|
58
|
+
parts = (channels ?? '').split(/\s+/).filter(Boolean);
|
|
59
|
+
alpha = slashAlpha;
|
|
29
60
|
}
|
|
30
|
-
|
|
61
|
+
if (parts.length !== 3)
|
|
62
|
+
return null;
|
|
63
|
+
const [r, g, b] = parts.map(rgbChannel);
|
|
64
|
+
const a = alpha === undefined ? 1 : alphaChannel(alpha);
|
|
65
|
+
if (r == null || g == null || b == null || a == null)
|
|
66
|
+
return null;
|
|
67
|
+
return { r, g, b, a };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parse a document colour (see {@link parseCssColor}) into normalized 0..1
|
|
71
|
+
* RGB components suitable for pdf-lib's `rgb()` factory. The alpha channel
|
|
72
|
+
* is dropped (read it with {@link colorAlpha}); malformed inputs fall back
|
|
73
|
+
* to black.
|
|
74
|
+
*/
|
|
75
|
+
export function hexToRgb(hex) {
|
|
76
|
+
const c = parseCssColor(hex);
|
|
77
|
+
return c ? { r: c.r, g: c.g, b: c.b } : { r: 0, g: 0, b: 0 };
|
|
78
|
+
}
|
|
79
|
+
/** Opacity of a document colour, 0 … 1; 1 when it carries none or does not
|
|
80
|
+
* parse. */
|
|
81
|
+
export function colorAlpha(value) {
|
|
82
|
+
return parseCssColor(value)?.a ?? 1;
|
|
31
83
|
}
|
|
32
84
|
/** Naive RGB → CMYK conversion (no ICC profile). Adequate for forcing a
|
|
33
85
|
* 4-channel output when the source is authored in sRGB hex. */
|
package/dist/colors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"colors.js","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"colors.js","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"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"}
|
package/dist/fontCache.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type PDFDocument, type PDFFont } from 'pdf-lib';
|
|
2
2
|
/** pdf-lib builds a CID font's `W` (advance widths) array from the glyphs
|
|
3
3
|
* reachable through the cmap, so a face embedded whole leaves every
|
|
4
4
|
* GSUB-only glyph — the fi/fl ligatures, contextual alternates — at the
|
|
@@ -22,20 +22,83 @@ export declare function toUnicodeCmap(glyphs: Array<{
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function coverAllGlyphUnicode(font: PDFFont): void;
|
|
24
24
|
export type PdfFontProvider = (family: string, weight: number, style: 'normal' | 'italic') => Promise<Uint8Array>;
|
|
25
|
+
/** A face the document asked for, and the face of the same family set in
|
|
26
|
+
* its place because the provider rejected it (see
|
|
27
|
+
* {@link FontCache.preloadFontStrings}). */
|
|
28
|
+
export interface FontFallback {
|
|
29
|
+
family: string;
|
|
30
|
+
weight: number;
|
|
31
|
+
style: 'normal' | 'italic';
|
|
32
|
+
fallback: {
|
|
33
|
+
weight: number;
|
|
34
|
+
style: 'normal' | 'italic';
|
|
35
|
+
};
|
|
36
|
+
/** The provider's error message for the requested face. */
|
|
37
|
+
reason: string;
|
|
38
|
+
}
|
|
39
|
+
interface FaceSpec {
|
|
40
|
+
family: string;
|
|
41
|
+
weight: number;
|
|
42
|
+
style: 'normal' | 'italic';
|
|
43
|
+
}
|
|
44
|
+
/** `weights` in the order CSS font matching tries them for `desired`
|
|
45
|
+
* (CSS Fonts 4 §5.2): from 400 to 500, the weights up to 500 ascending,
|
|
46
|
+
* then the lighter ones descending, then the heavier ones ascending; below
|
|
47
|
+
* 400, lighter (or equal) descending, then heavier ascending; above 500,
|
|
48
|
+
* heavier (or equal) ascending, then lighter descending. The browser picks
|
|
49
|
+
* the same face, so the PDF matches the preview. */
|
|
50
|
+
export declare function cssWeightOrder(desired: number, weights: readonly number[]): number[];
|
|
51
|
+
/** Faces of the same family to try, in order, when the provider rejects
|
|
52
|
+
* `spec`: every standard weight in the requested style, in CSS matching
|
|
53
|
+
* order ({@link cssWeightOrder}), then every weight in the other style
|
|
54
|
+
* the same way — italic falls back to upright, and upright to italic. A
|
|
55
|
+
* family with no italics (a display face) sets its italic runs upright; a
|
|
56
|
+
* family shipping only 400 and 700 takes a 600 as 700; one shipping a
|
|
57
|
+
* single cut sets everything in it. */
|
|
58
|
+
export declare function fallbackFaces(spec: FaceSpec): FaceSpec[];
|
|
25
59
|
/**
|
|
26
60
|
* Per-document mapping from `family|weight|style` to an embedded pdf-lib
|
|
27
61
|
* font. Entries are populated up-front by `preload()` so that block-level
|
|
28
|
-
* rendering (`get()`) is a sync lookup. A
|
|
29
|
-
*
|
|
62
|
+
* rendering (`get()`) is a sync lookup. A face the provider rejects falls
|
|
63
|
+
* back to another face of its family ({@link fallbackFaces}), reported
|
|
64
|
+
* through `onFallback`; a fontString that fails to parse, or whose family
|
|
65
|
+
* the provider cannot supply at all (no standard weight in either style),
|
|
66
|
+
* is mapped to `null` and reported back via `missing()`. Faces the
|
|
67
|
+
* provider answers with the same file share one embedded font, and
|
|
68
|
+
* `dropUnusedFonts()` keeps the fonts no page draws with out of the file.
|
|
30
69
|
*/
|
|
31
70
|
export declare class FontCache {
|
|
32
71
|
private pdfDoc;
|
|
33
72
|
private provider;
|
|
73
|
+
private onFallback?;
|
|
34
74
|
private map;
|
|
35
75
|
private failed;
|
|
36
|
-
|
|
76
|
+
/** One load per actual face, shared by every key that falls back to it. */
|
|
77
|
+
private loads;
|
|
78
|
+
private errors;
|
|
79
|
+
/** Loaded faces by lower-cased family, for lookups nothing preloaded. */
|
|
80
|
+
private byFamily;
|
|
81
|
+
/** Embeds by the byte length of their file: a provider that answers
|
|
82
|
+
* several faces with one file (a stand-in for a cut the family lacks)
|
|
83
|
+
* gets it embedded once, whatever the faces. */
|
|
84
|
+
private byLength;
|
|
85
|
+
/** Every font this cache embedded. */
|
|
86
|
+
private embedded;
|
|
87
|
+
constructor(pdfDoc: PDFDocument, provider: PdfFontProvider, onFallback?: ((fallback: FontFallback) => void) | undefined);
|
|
88
|
+
/** Embed a font file, once per distinct content. */
|
|
89
|
+
private embedBytes;
|
|
90
|
+
/** Fetch and embed one face, once. Null when the provider rejects it or
|
|
91
|
+
* its bytes do not embed. */
|
|
92
|
+
private loadFace;
|
|
37
93
|
preloadFontStrings(fontStrings: Iterable<string | undefined>): Promise<void>;
|
|
38
94
|
get(fontString: string): PDFFont | null;
|
|
39
95
|
missing(): string[];
|
|
96
|
+
/** Leave out of the file every font this cache embedded that no page
|
|
97
|
+
* draws with: a face asked for but never set (the text of a figure
|
|
98
|
+
* later drawn as a picture, say) would otherwise be written anyway,
|
|
99
|
+
* since pdf-lib writes every font it embedded. Call it once all pages
|
|
100
|
+
* are drawn, right before `save()`. */
|
|
101
|
+
dropUnusedFonts(): void;
|
|
40
102
|
}
|
|
103
|
+
export {};
|
|
41
104
|
//# sourceMappingURL=fontCache.d.ts.map
|
package/dist/fontCache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fontCache.d.ts","sourceRoot":"","sources":["../src/fontCache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"fontCache.d.ts","sourceRoot":"","sources":["../src/fontCache.ts"],"names":[],"mappings":"AACA,OAAO,EAAuC,KAAK,WAAW,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAwB9F;;;;;gEAKgE;AAChE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAavD;AAgBD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GAAG,MAAM,CAkBzF;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAyBxD;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,GAAG,QAAQ,KACvB,OAAO,CAAC,UAAU,CAAC,CAAC;AAEzB;;6CAE6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC3B,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAA;KAAE,CAAC;IACzD,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC5B;AAKD;;;;;qDAKqD;AACrD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWpF;AAED;;;;;;wCAMwC;AACxC,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAWxD;AAED;;;;;;;;;;GAUG;AACH,qBAAa,SAAS;IAgBlB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,UAAU,CAAC;IAjBrB,OAAO,CAAC,GAAG,CAAqC;IAChD,OAAO,CAAC,MAAM,CAAqB;IACnC,2EAA2E;IAC3E,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,MAAM,CAA6B;IAC3C,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAA0D;IAC1E;;qDAEiD;IACjD,OAAO,CAAC,QAAQ,CAA2E;IAC3F,sCAAsC;IACtC,OAAO,CAAC,QAAQ,CAAsB;gBAG5B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,eAAe,EACzB,UAAU,CAAC,GAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,aAAA;IAGvD,oDAAoD;IACpD,OAAO,CAAC,UAAU;IA2BlB;kCAC8B;IAC9B,OAAO,CAAC,QAAQ;IAsBV,kBAAkB,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqClF,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAgBvC,OAAO,IAAI,MAAM,EAAE;IAInB;;;;4CAIwC;IACxC,eAAe,IAAI,IAAI;CAWxB"}
|