pdf-parser.js 5.2.30 → 5.3.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.
- package/dist/{afm-widths-BHD2HZIL.cjs → afm-widths-C6U0SQj9.cjs} +5 -1448
- package/dist/{afm-widths-B_IGbGky.js → afm-widths-p3UfaXme.js} +3 -1398
- package/dist/afm-widths.cjs +1 -1
- package/dist/afm-widths.js +1 -1
- package/dist/annotations.d.cts +3 -3
- package/dist/annotations.d.ts +3 -3
- package/dist/attachments.d.cts +2 -2
- package/dist/attachments.d.ts +2 -2
- package/dist/builtin-encoding.cjs +4 -3
- package/dist/builtin-encoding.js +2 -1
- package/dist/cmap.d.cts +1 -1
- package/dist/cmap.d.ts +1 -1
- package/dist/content-read.d.cts +1 -1
- package/dist/content-read.d.ts +1 -1
- package/dist/content-write.cjs +1 -1
- package/dist/content-write.d.cts +1 -1
- package/dist/content-write.d.ts +1 -1
- package/dist/content-write.js +1 -1
- package/dist/diagnostics-C9FxfZ6H.d.cts +22 -0
- package/dist/diagnostics-C9FxfZ6H.d.ts +22 -0
- package/dist/diagnostics.d.cts +1 -21
- package/dist/diagnostics.d.ts +1 -21
- package/dist/document.d.cts +1 -1
- package/dist/document.d.ts +1 -1
- package/dist/{embedded-font-ByD7ZiQS.d.ts → embedded-font-CQZbTTDM.d.ts} +1 -1
- package/dist/{embedded-font-DgVBedw6.d.cts → embedded-font-CqePfvrl.d.cts} +1 -1
- package/dist/embedded-font-write.d.cts +1 -1
- package/dist/embedded-font-write.d.ts +1 -1
- package/dist/embedded-font.cjs +2 -2
- package/dist/embedded-font.d.cts +1 -1
- package/dist/embedded-font.d.ts +1 -1
- package/dist/embedded-font.js +2 -2
- package/dist/encoding-glyph-unicode.cjs +353 -0
- package/dist/encoding-glyph-unicode.d.cts +4 -0
- package/dist/encoding-glyph-unicode.d.ts +4 -0
- package/dist/encoding-glyph-unicode.js +352 -0
- package/dist/encoding-standard-tables.cjs +531 -0
- package/dist/encoding-standard-tables.d.cts +7 -0
- package/dist/encoding-standard-tables.d.ts +7 -0
- package/dist/encoding-standard-tables.js +527 -0
- package/dist/encoding-symbol-tables.cjs +531 -0
- package/dist/encoding-symbol-tables.d.cts +7 -0
- package/dist/encoding-symbol-tables.d.ts +7 -0
- package/dist/encoding-symbol-tables.js +527 -0
- package/dist/encoding.cjs +1 -9
- package/dist/encoding.d.cts +1 -9
- package/dist/encoding.d.ts +1 -9
- package/dist/encoding.js +2 -2
- package/dist/encrypt.d.cts +1 -1
- package/dist/encrypt.d.ts +1 -1
- package/dist/filters.d.cts +1 -1
- package/dist/filters.d.ts +1 -1
- package/dist/font-read.cjs +4 -3
- package/dist/font-read.d.cts +2 -2
- package/dist/font-read.d.ts +2 -2
- package/dist/font-read.js +2 -1
- package/dist/font-registry.d.cts +1 -1
- package/dist/font-registry.d.ts +1 -1
- package/dist/form.d.cts +2 -2
- package/dist/form.d.ts +2 -2
- package/dist/gdef-table-D0Hzn4KD.d.cts +15 -0
- package/dist/gdef-table-DL0V8wEZ.d.ts +15 -0
- package/dist/gdef-table.d.cts +1 -14
- package/dist/gdef-table.d.ts +1 -14
- package/dist/glyf-contours.d.cts +12 -1
- package/dist/glyf-contours.d.ts +12 -1
- package/dist/gsub-context.cjs +13 -0
- package/dist/gsub-context.d.cts +34 -0
- package/dist/gsub-context.d.ts +34 -0
- package/dist/gsub-context.js +2 -0
- package/dist/gsub-table-BDpJWqpf.d.ts +52 -0
- package/dist/gsub-table-D6niKeLi.js +556 -0
- package/dist/gsub-table-DW7Wmi9V.d.cts +52 -0
- package/dist/gsub-table-hFfYmf1W.cjs +741 -0
- package/dist/gsub-table.cjs +21 -556
- package/dist/gsub-table.d.cts +2 -2
- package/dist/gsub-table.d.ts +2 -2
- package/dist/gsub-table.js +2 -554
- package/dist/images-read.d.cts +2 -2
- package/dist/images-read.d.ts +2 -2
- package/dist/index.cjs +6 -6
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -6
- package/dist/interpret-CqXdXgkY.cjs +712 -0
- package/dist/interpret-aWWhdIzr.js +677 -0
- package/dist/interpret-colors.cjs +6 -0
- package/dist/interpret-colors.d.cts +9 -0
- package/dist/interpret-colors.d.ts +9 -0
- package/dist/interpret-colors.js +2 -0
- package/dist/interpret-marked.cjs +25 -0
- package/dist/interpret-marked.d.cts +11 -0
- package/dist/interpret-marked.d.ts +11 -0
- package/dist/interpret-marked.js +23 -0
- package/dist/interpret-shapes.cjs +212 -0
- package/dist/interpret-shapes.d.cts +32 -0
- package/dist/interpret-shapes.d.ts +32 -0
- package/dist/interpret-shapes.js +199 -0
- package/dist/interpret-types.cjs +0 -0
- package/dist/interpret-types.d.cts +123 -0
- package/dist/interpret-types.d.ts +123 -0
- package/dist/interpret-types.js +0 -0
- package/dist/interpret.cjs +3 -887
- package/dist/interpret.d.cts +5 -116
- package/dist/interpret.d.ts +5 -116
- package/dist/interpret.js +2 -887
- package/dist/math-font.cjs +1 -1
- package/dist/math-font.js +1 -1
- package/dist/measure.cjs +1 -1
- package/dist/measure.js +1 -1
- package/dist/names.d.cts +2 -2
- package/dist/names.d.ts +2 -2
- package/dist/navigation.d.cts +2 -2
- package/dist/navigation.d.ts +2 -2
- package/dist/optional-content.d.cts +2 -2
- package/dist/optional-content.d.ts +2 -2
- package/dist/ot-layout-common-BcsGRFBS.d.cts +10 -0
- package/dist/ot-layout-common-BcsGRFBS.d.ts +10 -0
- package/dist/ot-layout-common.d.cts +1 -9
- package/dist/ot-layout-common.d.ts +1 -9
- package/dist/parse.d.cts +1 -1
- package/dist/parse.d.ts +1 -1
- package/dist/predictors.d.cts +1 -1
- package/dist/predictors.d.ts +1 -1
- package/dist/raster-Cdm3idP_.js +794 -0
- package/dist/raster-EG2KYRZ0.cjs +829 -0
- package/dist/raster-text.cjs +5 -0
- package/dist/raster-text.d.cts +20 -0
- package/dist/raster-text.d.ts +20 -0
- package/dist/raster-text.js +2 -0
- package/dist/raster.cjs +4 -795
- package/dist/raster.d.cts +17 -6
- package/dist/raster.d.ts +17 -6
- package/dist/raster.js +2 -792
- package/dist/read-convert.cjs +310 -0
- package/dist/read-convert.d.cts +58 -0
- package/dist/read-convert.d.ts +58 -0
- package/dist/read-convert.js +295 -0
- package/dist/read.cjs +5 -293
- package/dist/read.d.cts +1 -1
- package/dist/read.d.ts +1 -1
- package/dist/read.js +4 -292
- package/dist/structure.d.cts +2 -2
- package/dist/structure.d.ts +2 -2
- package/dist/winansi.cjs +1 -1
- package/dist/winansi.js +1 -1
- package/dist/write-annotations.cjs +132 -0
- package/dist/write-annotations.d.cts +29 -0
- package/dist/write-annotations.d.ts +29 -0
- package/dist/write-annotations.js +121 -0
- package/dist/write-font-objects.cjs +76 -0
- package/dist/write-font-objects.d.cts +35 -0
- package/dist/write-font-objects.d.ts +35 -0
- package/dist/write-font-objects.js +75 -0
- package/dist/write-form.cjs +91 -0
- package/dist/write-form.d.cts +19 -0
- package/dist/write-form.d.ts +19 -0
- package/dist/write-form.js +89 -0
- package/dist/write-images.cjs +148 -0
- package/dist/write-images.d.cts +25 -0
- package/dist/write-images.d.ts +25 -0
- package/dist/write-images.js +140 -0
- package/dist/write-outline.cjs +46 -0
- package/dist/write-outline.d.cts +18 -0
- package/dist/write-outline.d.ts +18 -0
- package/dist/write-outline.js +45 -0
- package/dist/write-page-emit.cjs +97 -0
- package/dist/write-page-emit.d.cts +63 -0
- package/dist/write-page-emit.d.ts +63 -0
- package/dist/write-page-emit.js +96 -0
- package/dist/write-pages.cjs +37 -0
- package/dist/write-pages.d.cts +21 -0
- package/dist/write-pages.d.ts +21 -0
- package/dist/write-pages.js +36 -0
- package/dist/write-strings.cjs +41 -0
- package/dist/write-strings.d.cts +9 -0
- package/dist/write-strings.d.ts +9 -0
- package/dist/write-strings.js +37 -0
- package/dist/write.cjs +53 -553
- package/dist/write.d.cts +1 -1
- package/dist/write.d.ts +1 -1
- package/dist/write.js +49 -549
- package/dist/xref.d.cts +1 -1
- package/dist/xref.d.ts +1 -1
- package/package.json +1 -1
- package/dist/glyf-contours-B1PsI9oA.d.ts +0 -13
- package/dist/glyf-contours-NHBpgks-.d.cts +0 -13
- package/dist/gsub-table-Bm9kSMzk.d.cts +0 -16
- package/dist/gsub-table-CdfESH2-.d.ts +0 -16
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { asArray, asName, asNumber, dictGet } from "./objects.js";
|
|
2
|
+
import { concatBytes } from "./bytes/writer.js";
|
|
3
|
+
import { BEZIER_KAPPA, applyMatrix, multiplyMatrices, scaleMatrix, translationMatrix } from "./matrix.js";
|
|
4
|
+
import { decodeStream } from "./filters.js";
|
|
5
|
+
import { parseSfnt } from "./sfnt.js";
|
|
6
|
+
import { buildCmapLookup } from "./cmap-table.js";
|
|
7
|
+
import { parseHead, parseMaxp } from "./font-tables.js";
|
|
8
|
+
import { readImageXObject } from "./images-read.js";
|
|
9
|
+
import { readOptionalContent } from "./optional-content.js";
|
|
10
|
+
import { NOOP_DIAGNOSTIC_SINK, PdfParseError } from "./diagnostics.js";
|
|
11
|
+
import { openPdfDocument } from "./document.js";
|
|
12
|
+
import { throwIfAborted } from "./util/abort.js";
|
|
13
|
+
import { createFontResolver } from "./font-read.js";
|
|
14
|
+
import { t as interpretContentStream } from "./interpret-aWWhdIzr.js";
|
|
15
|
+
import { normalizeRotation, pageRotationTransform } from "./read.js";
|
|
16
|
+
import { parseGlyf } from "./glyf.js";
|
|
17
|
+
import { decodeGlyphOutline } from "./glyf-contours.js";
|
|
18
|
+
//#region src/raster-text.ts
|
|
19
|
+
const CFF_HEADER_SIZE = 4;
|
|
20
|
+
function openEmbeddedProgram(descriptorOwner, resolver) {
|
|
21
|
+
const descriptor = resolver.resolveDict(dictGet(descriptorOwner, "FontDescriptor"));
|
|
22
|
+
if (descriptor === void 0) return { kind: "absent" };
|
|
23
|
+
for (const key of ["FontFile2", "FontFile3"]) {
|
|
24
|
+
const stream = resolver.resolve(dictGet(descriptor, key));
|
|
25
|
+
if (stream?.kind !== "stream") continue;
|
|
26
|
+
const bytes = decodeStream(stream.raw, stream.dict, NOOP_DIAGNOSTIC_SINK).bytes;
|
|
27
|
+
if (bytes[0] === 1 && bytes[1] === 0 && bytes[2] === CFF_HEADER_SIZE) return { kind: "cff" };
|
|
28
|
+
const sfnt = parseSfnt(bytes);
|
|
29
|
+
if (sfnt === void 0) continue;
|
|
30
|
+
if (sfnt.tables.has("CFF ")) return { kind: "cff" };
|
|
31
|
+
const head = parseHead(sfnt);
|
|
32
|
+
const maxp = parseMaxp(sfnt);
|
|
33
|
+
if (head === void 0 || maxp === void 0) continue;
|
|
34
|
+
const glyf = parseGlyf(sfnt, {
|
|
35
|
+
numGlyphs: maxp.numGlyphs,
|
|
36
|
+
indexToLocFormat: head.indexToLocFormat
|
|
37
|
+
});
|
|
38
|
+
if (glyf === void 0) continue;
|
|
39
|
+
return {
|
|
40
|
+
kind: "glyf",
|
|
41
|
+
sfnt,
|
|
42
|
+
face: {
|
|
43
|
+
glyf,
|
|
44
|
+
unitsPerEm: head.unitsPerEm
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { kind: "absent" };
|
|
49
|
+
}
|
|
50
|
+
function resolveTextOutlineFace(fontResourceName, resources, resolver, fontResolver, outlineFaces, sink) {
|
|
51
|
+
const fontsDict = resolver.resolveDict(dictGet(resources, "Font"));
|
|
52
|
+
const fontDict = fontsDict !== void 0 ? resolver.resolveDict(dictGet(fontsDict, fontResourceName)) : void 0;
|
|
53
|
+
if (fontDict === void 0) return;
|
|
54
|
+
if (outlineFaces.has(fontDict)) return outlineFaces.get(fontDict);
|
|
55
|
+
const face = buildTextOutlineFace(fontDict, resolver, fontResolver, fontResourceName, resources, sink);
|
|
56
|
+
outlineFaces.set(fontDict, face);
|
|
57
|
+
return face;
|
|
58
|
+
}
|
|
59
|
+
const BITS_PER_BYTE = 8;
|
|
60
|
+
function buildTextOutlineFace(fontDict, resolver, fontResolver, fontResourceName, resources, sink) {
|
|
61
|
+
const faceName = asName(dictGet(fontDict, "BaseFont")) ?? asName(dictGet(fontDict, "Subtype")) ?? "font";
|
|
62
|
+
const unavailable = (reason) => {
|
|
63
|
+
sink({
|
|
64
|
+
code: "raster/text-outlines-unavailable",
|
|
65
|
+
severity: "warning",
|
|
66
|
+
message: `font resource /${fontResourceName} (${faceName}) has no drawable outline (${reason}); its text is not rendered`
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const cff = () => {
|
|
70
|
+
sink({
|
|
71
|
+
code: "raster/text-cff-outlines",
|
|
72
|
+
severity: "warning",
|
|
73
|
+
message: `font resource /${fontResourceName} (${faceName}) carries CFF outlines; this raster surface fills sfnt (TrueType/glyf) outlines only, so its text is not rendered rather than approximated`
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
const subtype = asName(dictGet(fontDict, "Subtype"));
|
|
77
|
+
if (subtype === "Type0") {
|
|
78
|
+
const encoding = resolver.resolve(dictGet(fontDict, "Encoding"));
|
|
79
|
+
if (encoding?.kind !== "name" || encoding.name !== "Identity-H" && encoding.name !== "Identity-V") return unavailable("a Type0 font whose /Encoding is neither Identity-H nor Identity-V (a predefined or embedded CMap this raster walk does not decode)");
|
|
80
|
+
const descendants = asArray(dictGet(fontDict, "DescendantFonts"));
|
|
81
|
+
const descendant = descendants !== void 0 ? resolver.resolveDict(descendants[0]) : void 0;
|
|
82
|
+
if (descendant === void 0) return unavailable("a Type0 font with no readable /DescendantFonts entry");
|
|
83
|
+
const descendantSubtype = asName(dictGet(descendant, "Subtype"));
|
|
84
|
+
if (descendantSubtype === "CIDFontType0") return cff();
|
|
85
|
+
if (descendantSubtype !== "CIDFontType2") return unavailable(`a descendant font of subtype ${descendantSubtype ?? "(none)"}`);
|
|
86
|
+
const cidToGidMap = resolver.resolve(dictGet(descendant, "CIDToGIDMap"));
|
|
87
|
+
const program = openEmbeddedProgram(descendant, resolver);
|
|
88
|
+
if (program.kind === "cff") return cff();
|
|
89
|
+
if (program.kind === "absent") return unavailable("no readable /FontFile2 (or glyf-bearing /FontFile3) stream");
|
|
90
|
+
if (cidToGidMap !== void 0 && cidToGidMap.kind !== "name" && cidToGidMap.kind !== "stream") return unavailable("a /CIDToGIDMap that is neither /Identity nor a readable stream");
|
|
91
|
+
if (cidToGidMap?.kind === "stream") {
|
|
92
|
+
const decodedBytes = decodeStream(cidToGidMap.raw, cidToGidMap.dict, NOOP_DIAGNOSTIC_SINK).bytes;
|
|
93
|
+
const entries = [];
|
|
94
|
+
for (let i = 0; i + 1 < decodedBytes.length; i += 2) entries.push(decodedBytes[i] << BITS_PER_BYTE | decodedBytes[i + 1]);
|
|
95
|
+
return {
|
|
96
|
+
glyf: program.face.glyf,
|
|
97
|
+
unitsPerEm: program.face.unitsPerEm,
|
|
98
|
+
glyphIdOf: (codes, offset) => {
|
|
99
|
+
const cid = codes[offset] << BITS_PER_BYTE | codes[offset + 1];
|
|
100
|
+
return entries[cid];
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
glyf: program.face.glyf,
|
|
106
|
+
unitsPerEm: program.face.unitsPerEm,
|
|
107
|
+
glyphIdOf: (codes, offset) => codes[offset] << BITS_PER_BYTE | codes[offset + 1]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (subtype === "TrueType") {
|
|
111
|
+
const program = openEmbeddedProgram(fontDict, resolver);
|
|
112
|
+
if (program.kind === "cff") return cff();
|
|
113
|
+
if (program.kind === "absent") return unavailable("no /FontDescriptor or no readable embedded program (a standard-14 or otherwise unembedded face states no outlines at all)");
|
|
114
|
+
const cmapLookup = buildCmapLookup(program.sfnt);
|
|
115
|
+
if (cmapLookup === void 0) return unavailable("an embedded program with no usable Unicode cmap subtable");
|
|
116
|
+
return {
|
|
117
|
+
glyf: program.face.glyf,
|
|
118
|
+
unitsPerEm: program.face.unitsPerEm,
|
|
119
|
+
glyphIdOf: (codes, offset) => {
|
|
120
|
+
const codePoint = (fontResolver.resolve(fontResourceName, resources)?.decodeToUnicode(codes.subarray(offset, offset + 1)))?.codePointAt(0);
|
|
121
|
+
return codePoint === void 0 ? void 0 : cmapLookup(codePoint);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (subtype === "Type1" || subtype === "MMType1") {
|
|
126
|
+
if (openEmbeddedProgram(fontDict, resolver).kind === "cff") return cff();
|
|
127
|
+
return unavailable("a Type 1 PostScript program, whose outlines are charstrings rather than sfnt glyf data");
|
|
128
|
+
}
|
|
129
|
+
return unavailable(`a font of subtype ${subtype ?? "(none)"} (a Type3 glyph-procedure font has no static outline at all)`);
|
|
130
|
+
}
|
|
131
|
+
const METRICS_PER_1000_SCALE = 1e3;
|
|
132
|
+
function drawTextRun(item, fontResolver, resolver, outlineFaces, interpretToDeviceMatrix, rasteriser, sink) {
|
|
133
|
+
const face = resolveTextOutlineFace(item.fontResourceName, item.resources, resolver, fontResolver, outlineFaces, sink);
|
|
134
|
+
if (face === void 0) return;
|
|
135
|
+
const placements = [];
|
|
136
|
+
let offset = 0;
|
|
137
|
+
let cumulative = 0;
|
|
138
|
+
while (offset < item.codes.length) {
|
|
139
|
+
const advance = fontResolver.metrics.glyphAdvance(item.fontResourceName, item.resources, item.codes, offset);
|
|
140
|
+
const byteLength = advance.byteLengthConsumed;
|
|
141
|
+
placements.push({
|
|
142
|
+
glyphId: face.glyphIdOf(item.codes, offset),
|
|
143
|
+
advance: cumulative,
|
|
144
|
+
positionX: (advance.vertical?.positionXPer1000 ?? 0) / METRICS_PER_1000_SCALE,
|
|
145
|
+
positionY: (advance.vertical?.positionYPer1000 ?? 0) / METRICS_PER_1000_SCALE
|
|
146
|
+
});
|
|
147
|
+
cumulative += (advance.vertical?.displacementPer1000 ?? advance.widthPer1000) / METRICS_PER_1000_SCALE * item.sizePt;
|
|
148
|
+
offset += byteLength;
|
|
149
|
+
}
|
|
150
|
+
const startDeviceMatrix = multiplyMatrices(item.startMatrix, interpretToDeviceMatrix);
|
|
151
|
+
const endDeviceMatrix = multiplyMatrices(item.endMatrix, interpretToDeviceMatrix);
|
|
152
|
+
const vertical = item.vertical === true;
|
|
153
|
+
const startPoint = applyMatrix(startDeviceMatrix, {
|
|
154
|
+
x: 0,
|
|
155
|
+
y: 0
|
|
156
|
+
});
|
|
157
|
+
const endPoint = applyMatrix(endDeviceMatrix, {
|
|
158
|
+
x: 0,
|
|
159
|
+
y: 0
|
|
160
|
+
});
|
|
161
|
+
const rawEndPoint = applyMatrix(startDeviceMatrix, vertical ? {
|
|
162
|
+
x: 0,
|
|
163
|
+
y: cumulative
|
|
164
|
+
} : {
|
|
165
|
+
x: cumulative,
|
|
166
|
+
y: 0
|
|
167
|
+
});
|
|
168
|
+
const rawExtent = Math.hypot(rawEndPoint.x - startPoint.x, rawEndPoint.y - startPoint.y);
|
|
169
|
+
const actualExtent = Math.hypot(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
|
|
170
|
+
const correction = rawExtent > 1e-9 ? actualExtent / rawExtent : 1;
|
|
171
|
+
const firstPlacement = placements[0];
|
|
172
|
+
const glyphScale = scaleMatrix(1 / face.unitsPerEm, 1 / face.unitsPerEm);
|
|
173
|
+
for (const placement of placements) {
|
|
174
|
+
if (placement.glyphId === void 0) continue;
|
|
175
|
+
const outline = decodeGlyphOutline(face.glyf, placement.glyphId);
|
|
176
|
+
if (outline === void 0) continue;
|
|
177
|
+
const trm = multiplyMatrices(translationMatrix((firstPlacement?.positionX ?? 0) - placement.positionX, (firstPlacement?.positionY ?? 0) - placement.positionY), multiplyMatrices(vertical ? translationMatrix(0, placement.advance * correction) : translationMatrix(placement.advance * correction, 0), item.startMatrix));
|
|
178
|
+
drawGlyphOutline(outline, multiplyMatrices(glyphScale, multiplyMatrices(trm, interpretToDeviceMatrix)), item.color, rasteriser);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function drawGlyphOutline(outline, glyphMatrix, color, rasteriser) {
|
|
182
|
+
const subpaths = glyphOutlineSubpaths(outline, glyphMatrix);
|
|
183
|
+
if (subpaths.length === 0) return;
|
|
184
|
+
rasteriser.draw({
|
|
185
|
+
kind: "path",
|
|
186
|
+
subpaths,
|
|
187
|
+
fill: {
|
|
188
|
+
color,
|
|
189
|
+
fillRule: "nonzero"
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const MIN_CONTOUR_POINTS = 3;
|
|
194
|
+
const QUADRATIC_TO_CUBIC_CONTROL_FRACTION = 2 / 3;
|
|
195
|
+
function glyphOutlineSubpaths(outline, matrix) {
|
|
196
|
+
const subpaths = [];
|
|
197
|
+
for (const contour of outline.contours) {
|
|
198
|
+
if (contour.length < MIN_CONTOUR_POINTS) continue;
|
|
199
|
+
const firstOn = contour.findIndex((point) => point.onCurve);
|
|
200
|
+
const contourPoints = contour.map((point) => ({
|
|
201
|
+
x: point.x,
|
|
202
|
+
y: point.y,
|
|
203
|
+
onCurve: point.onCurve
|
|
204
|
+
}));
|
|
205
|
+
const hasLeadingOnCurvePoint = firstOn >= 0;
|
|
206
|
+
const ordered = hasLeadingOnCurvePoint ? [...contourPoints.slice(firstOn), ...contourPoints.slice(0, firstOn)] : contourPoints;
|
|
207
|
+
let current = hasLeadingOnCurvePoint ? {
|
|
208
|
+
x: contourPoints[firstOn].x,
|
|
209
|
+
y: contourPoints[firstOn].y
|
|
210
|
+
} : {
|
|
211
|
+
x: (contourPoints[contourPoints.length - 1].x + contourPoints[0].x) / 2,
|
|
212
|
+
y: (contourPoints[contourPoints.length - 1].y + contourPoints[0].y) / 2
|
|
213
|
+
};
|
|
214
|
+
const start = current;
|
|
215
|
+
const segments = [];
|
|
216
|
+
let pendingOffCurve;
|
|
217
|
+
const emitQuad = (from, control, to) => {
|
|
218
|
+
const p0 = applyMatrix(matrix, from);
|
|
219
|
+
const q = applyMatrix(matrix, control);
|
|
220
|
+
const p1 = applyMatrix(matrix, to);
|
|
221
|
+
segments.push({
|
|
222
|
+
kind: "cubic",
|
|
223
|
+
c1xPx: p0.x + QUADRATIC_TO_CUBIC_CONTROL_FRACTION * (q.x - p0.x),
|
|
224
|
+
c1yPx: p0.y + QUADRATIC_TO_CUBIC_CONTROL_FRACTION * (q.y - p0.y),
|
|
225
|
+
c2xPx: p1.x + QUADRATIC_TO_CUBIC_CONTROL_FRACTION * (q.x - p1.x),
|
|
226
|
+
c2yPx: p1.y + QUADRATIC_TO_CUBIC_CONTROL_FRACTION * (q.y - p1.y),
|
|
227
|
+
xPx: p1.x,
|
|
228
|
+
yPx: p1.y
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
const emitLine = (to) => {
|
|
232
|
+
const p1 = applyMatrix(matrix, to);
|
|
233
|
+
segments.push({
|
|
234
|
+
kind: "line",
|
|
235
|
+
xPx: p1.x,
|
|
236
|
+
yPx: p1.y
|
|
237
|
+
});
|
|
238
|
+
};
|
|
239
|
+
for (const point of ordered) if (point.onCurve) {
|
|
240
|
+
if (pendingOffCurve === void 0) emitLine(point);
|
|
241
|
+
else emitQuad(current, pendingOffCurve, point);
|
|
242
|
+
current = point;
|
|
243
|
+
pendingOffCurve = void 0;
|
|
244
|
+
} else {
|
|
245
|
+
if (pendingOffCurve !== void 0) {
|
|
246
|
+
const implied = {
|
|
247
|
+
x: (pendingOffCurve.x + point.x) / 2,
|
|
248
|
+
y: (pendingOffCurve.y + point.y) / 2
|
|
249
|
+
};
|
|
250
|
+
emitQuad(current, pendingOffCurve, implied);
|
|
251
|
+
current = implied;
|
|
252
|
+
}
|
|
253
|
+
pendingOffCurve = point;
|
|
254
|
+
}
|
|
255
|
+
if (pendingOffCurve !== void 0) emitQuad(current, pendingOffCurve, start);
|
|
256
|
+
const startPx = applyMatrix(matrix, start);
|
|
257
|
+
subpaths.push({
|
|
258
|
+
startXPx: startPx.x,
|
|
259
|
+
startYPx: startPx.y,
|
|
260
|
+
segments,
|
|
261
|
+
closed: true
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return subpaths;
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/raster.ts
|
|
268
|
+
const PIXEL_ROUNDING_EPSILON = 1e-9;
|
|
269
|
+
const POINTS_PER_INCH = 72;
|
|
270
|
+
const EXTENT_ZERO_EPSILON = 1e-9;
|
|
271
|
+
function regionPixels(extentPt, scale) {
|
|
272
|
+
return Math.max(1, Math.ceil(extentPt * scale - PIXEL_ROUNDING_EPSILON));
|
|
273
|
+
}
|
|
274
|
+
const HEADER_SEARCH_WINDOW = 1024;
|
|
275
|
+
const DEFAULT_PAGE_WIDTH_PT = 612;
|
|
276
|
+
const DEFAULT_PAGE_HEIGHT_PT = 792;
|
|
277
|
+
const NEWLINE_BYTE = 10;
|
|
278
|
+
function hasPdfHeader(bytes) {
|
|
279
|
+
const window = bytes.subarray(0, Math.min(HEADER_SEARCH_WINDOW, bytes.length));
|
|
280
|
+
return new TextDecoder("latin1").decode(window).includes("%PDF-");
|
|
281
|
+
}
|
|
282
|
+
function readDeclaredPageBox(page, key) {
|
|
283
|
+
const arr = asArray(dictGet(page, key));
|
|
284
|
+
if (arr === void 0) return;
|
|
285
|
+
const a = asNumber(arr[0]) ?? 0;
|
|
286
|
+
const b = asNumber(arr[1]) ?? 0;
|
|
287
|
+
const c = asNumber(arr[2]) ?? 0;
|
|
288
|
+
const d = asNumber(arr[3]) ?? 0;
|
|
289
|
+
return {
|
|
290
|
+
llx: Math.min(a, c),
|
|
291
|
+
lly: Math.min(b, d),
|
|
292
|
+
urx: Math.max(a, c),
|
|
293
|
+
ury: Math.max(b, d)
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function readMediaBox(page) {
|
|
297
|
+
return readDeclaredPageBox(page, "MediaBox") ?? {
|
|
298
|
+
llx: 0,
|
|
299
|
+
lly: 0,
|
|
300
|
+
urx: DEFAULT_PAGE_WIDTH_PT,
|
|
301
|
+
ury: DEFAULT_PAGE_HEIGHT_PT
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function rotatedRectBounds(rect, matrix) {
|
|
305
|
+
const transformed = [
|
|
306
|
+
[rect.llx, rect.lly],
|
|
307
|
+
[rect.urx, rect.lly],
|
|
308
|
+
[rect.llx, rect.ury],
|
|
309
|
+
[rect.urx, rect.ury]
|
|
310
|
+
].map(([x, y]) => applyMatrix(matrix, {
|
|
311
|
+
x,
|
|
312
|
+
y
|
|
313
|
+
}));
|
|
314
|
+
const xs = transformed.map((point) => point.x);
|
|
315
|
+
const ys = transformed.map((point) => point.y);
|
|
316
|
+
return {
|
|
317
|
+
minX: Math.min(...xs),
|
|
318
|
+
minY: Math.min(...ys),
|
|
319
|
+
maxX: Math.max(...xs),
|
|
320
|
+
maxY: Math.max(...ys)
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function readPageContentBytes(page, resolver, sink) {
|
|
324
|
+
const contentsObj = resolver.resolve(dictGet(page, "Contents"));
|
|
325
|
+
if (contentsObj?.kind === "stream") return decodeStream(contentsObj.raw, contentsObj.dict, sink).bytes;
|
|
326
|
+
if (contentsObj?.kind === "array") {
|
|
327
|
+
const chunks = [];
|
|
328
|
+
for (const entry of contentsObj.items) {
|
|
329
|
+
const streamObj = resolver.resolve(entry);
|
|
330
|
+
if (streamObj?.kind === "stream") chunks.push(decodeStream(streamObj.raw, streamObj.dict, sink).bytes, new Uint8Array([NEWLINE_BYTE]));
|
|
331
|
+
}
|
|
332
|
+
return concatBytes(chunks);
|
|
333
|
+
}
|
|
334
|
+
return /* @__PURE__ */ new Uint8Array(0);
|
|
335
|
+
}
|
|
336
|
+
function renderPdfPage(pdfBytes, pageIndex, options, rasteriser) {
|
|
337
|
+
const sink = options.sink ?? NOOP_DIAGNOSTIC_SINK;
|
|
338
|
+
if (options.scale !== void 0 && options.dpi !== void 0) throw new Error("renderPdfPage accepts at most one of scale and dpi; they name the same factor in different units (dpi = 72 x scale)");
|
|
339
|
+
const scale = options.dpi !== void 0 ? options.dpi / POINTS_PER_INCH : options.scale;
|
|
340
|
+
if (scale !== void 0 && !(scale > 0)) throw new Error(`renderPdfPage requires a positive scale factor (received scale ${options.scale}, dpi ${options.dpi})`);
|
|
341
|
+
const pixelsPerPt = scale ?? 1;
|
|
342
|
+
if (options.clipPt !== void 0 && (!(options.clipPt.widthPt > 0) || !(options.clipPt.heightPt > 0))) throw new Error("renderPdfPage clipPt must carry positive widthPt and heightPt; a zero-extent region has no pixels to render");
|
|
343
|
+
throwIfAborted(options.signal);
|
|
344
|
+
if (!hasPdfHeader(pdfBytes)) throw new PdfParseError("pdf/no-header", "no \"%PDF-\" header found within the first bytes of the file; this does not look like a PDF at all");
|
|
345
|
+
const doc = openPdfDocument(pdfBytes, sink);
|
|
346
|
+
const pages = doc.pages();
|
|
347
|
+
if (pageIndex < 0 || pageIndex >= pages.length) throw new PdfParseError("pdf/page-index-out-of-range", `page index ${pageIndex} is outside this document's page tree (it declares ${pages.length} page${pages.length === 1 ? "" : "s"})`);
|
|
348
|
+
const page = pages[pageIndex];
|
|
349
|
+
const mediaBox = readMediaBox(page);
|
|
350
|
+
let cropBox = readDeclaredPageBox(page, "CropBox") ?? mediaBox;
|
|
351
|
+
if (cropBox.urx - cropBox.llx <= 0 || cropBox.ury - cropBox.lly <= 0) {
|
|
352
|
+
sink({
|
|
353
|
+
code: "pdf/invalid-crop-box",
|
|
354
|
+
severity: "warning",
|
|
355
|
+
pageIndex,
|
|
356
|
+
message: "page /CropBox is degenerate (zero width or height); falling back to the /MediaBox as the visible region"
|
|
357
|
+
});
|
|
358
|
+
cropBox = mediaBox;
|
|
359
|
+
}
|
|
360
|
+
const rotation = normalizeRotation(asNumber(dictGet(page, "Rotate")));
|
|
361
|
+
const rotationResult = pageRotationTransform(rotation, 0, 0);
|
|
362
|
+
const visibleRect = rotatedRectBounds(cropBox, rotationResult.matrix);
|
|
363
|
+
const pageWidthPt = visibleRect.maxX - visibleRect.minX;
|
|
364
|
+
const pageHeightPt = visibleRect.maxY - visibleRect.minY;
|
|
365
|
+
const pageMatrix = multiplyMatrices(rotationResult.matrix, translationMatrix(-visibleRect.minX, -visibleRect.minY));
|
|
366
|
+
const requested = options.clipPt ?? {
|
|
367
|
+
xPt: 0,
|
|
368
|
+
yPt: 0,
|
|
369
|
+
widthPt: pageWidthPt,
|
|
370
|
+
heightPt: pageHeightPt
|
|
371
|
+
};
|
|
372
|
+
const clipLeft = Math.max(0, requested.xPt);
|
|
373
|
+
const clipBottom = Math.max(0, requested.yPt);
|
|
374
|
+
const clipRight = Math.min(pageWidthPt, requested.xPt + requested.widthPt);
|
|
375
|
+
const clipTop = Math.min(pageHeightPt, requested.yPt + requested.heightPt);
|
|
376
|
+
if (clipRight - clipLeft <= 0 || clipTop - clipBottom <= 0) throw new Error(`renderPdfPage clipPt does not intersect the page's visible region (clip x ${requested.xPt}..${requested.xPt + requested.widthPt}, y ${requested.yPt}..${requested.yPt + requested.heightPt}; page 0..${pageWidthPt} x 0..${pageHeightPt})`);
|
|
377
|
+
const regionWidthPt = clipRight - clipLeft;
|
|
378
|
+
const regionHeightPt = clipTop - clipBottom;
|
|
379
|
+
const widthPx = regionPixels(regionWidthPt, pixelsPerPt);
|
|
380
|
+
const heightPx = regionPixels(regionHeightPt, pixelsPerPt);
|
|
381
|
+
const pageToDeviceMatrix = [
|
|
382
|
+
pixelsPerPt,
|
|
383
|
+
0,
|
|
384
|
+
0,
|
|
385
|
+
-pixelsPerPt,
|
|
386
|
+
-clipLeft * pixelsPerPt,
|
|
387
|
+
(clipBottom + regionHeightPt) * pixelsPerPt
|
|
388
|
+
];
|
|
389
|
+
const interpretToDeviceMatrix = multiplyMatrices(pageMatrix, pageToDeviceMatrix);
|
|
390
|
+
const resources = doc.resolveDict(dictGet(page, "Resources"));
|
|
391
|
+
rasteriser.beginPage({
|
|
392
|
+
widthPx,
|
|
393
|
+
heightPx,
|
|
394
|
+
scale: pixelsPerPt,
|
|
395
|
+
widthPt: regionWidthPt,
|
|
396
|
+
heightPt: regionHeightPt
|
|
397
|
+
});
|
|
398
|
+
if (resources === void 0) {
|
|
399
|
+
sink({
|
|
400
|
+
code: "pdf/object-missing-value",
|
|
401
|
+
severity: "warning",
|
|
402
|
+
message: "page has no /Resources dict; its content stream cannot be interpreted"
|
|
403
|
+
});
|
|
404
|
+
return rasteriser.finish();
|
|
405
|
+
}
|
|
406
|
+
const fontResolver = createFontResolver({
|
|
407
|
+
resolver: doc,
|
|
408
|
+
sink
|
|
409
|
+
});
|
|
410
|
+
const { layers, layerNameOf } = readOptionalContent(doc.catalog, doc, sink);
|
|
411
|
+
const hiddenLayers = new Set(layers.filter((layer) => !layer.visible).map((layer) => layer.name));
|
|
412
|
+
const extracted = interpretContentStream(readPageContentBytes(page, doc, sink), resources, {
|
|
413
|
+
fontMetrics: fontResolver.metrics,
|
|
414
|
+
resolver: doc,
|
|
415
|
+
sink,
|
|
416
|
+
layerNameOf
|
|
417
|
+
});
|
|
418
|
+
const outlineFaces = /* @__PURE__ */ new Map();
|
|
419
|
+
for (const item of extracted) {
|
|
420
|
+
throwIfAborted(options.signal);
|
|
421
|
+
if (item.layerName !== void 0 && hiddenLayers.has(item.layerName)) continue;
|
|
422
|
+
drawExtractedItem(item, doc, fontResolver, outlineFaces, interpretToDeviceMatrix, pixelsPerPt, rasteriser, sink);
|
|
423
|
+
}
|
|
424
|
+
return rasteriser.finish();
|
|
425
|
+
}
|
|
426
|
+
function drawExtractedItem(item, resolver, fontResolver, outlineFaces, interpretToDeviceMatrix, pixelsPerPt, rasteriser, sink) {
|
|
427
|
+
if (item.kind === "text") {
|
|
428
|
+
drawTextRun(item, fontResolver, resolver, outlineFaces, interpretToDeviceMatrix, rasteriser, sink);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (item.kind === "rect") {
|
|
432
|
+
drawRect(item, interpretToDeviceMatrix, pixelsPerPt, rasteriser);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (item.kind === "ellipse") {
|
|
436
|
+
drawEllipse(item, interpretToDeviceMatrix, pixelsPerPt, rasteriser);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (item.kind === "line") {
|
|
440
|
+
drawLine(item, interpretToDeviceMatrix, pixelsPerPt, rasteriser);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (item.kind === "path") {
|
|
444
|
+
drawPath(item, interpretToDeviceMatrix, pixelsPerPt, rasteriser);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (item.kind === "image") {
|
|
448
|
+
drawImageXObjectItem(item, resolver, interpretToDeviceMatrix, rasteriser, sink);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
drawInlineImageItem(item, resolver, interpretToDeviceMatrix, rasteriser, sink);
|
|
452
|
+
}
|
|
453
|
+
function drawRect(item, matrix, pixelsPerPt, rasteriser) {
|
|
454
|
+
const corners = [
|
|
455
|
+
applyMatrix(matrix, {
|
|
456
|
+
x: item.xPt,
|
|
457
|
+
y: item.yPt
|
|
458
|
+
}),
|
|
459
|
+
applyMatrix(matrix, {
|
|
460
|
+
x: item.xPt + item.widthPt,
|
|
461
|
+
y: item.yPt
|
|
462
|
+
}),
|
|
463
|
+
applyMatrix(matrix, {
|
|
464
|
+
x: item.xPt,
|
|
465
|
+
y: item.yPt + item.heightPt
|
|
466
|
+
}),
|
|
467
|
+
applyMatrix(matrix, {
|
|
468
|
+
x: item.xPt + item.widthPt,
|
|
469
|
+
y: item.yPt + item.heightPt
|
|
470
|
+
})
|
|
471
|
+
];
|
|
472
|
+
const minX = Math.min(...corners.map((corner) => corner.x));
|
|
473
|
+
const minY = Math.min(...corners.map((corner) => corner.y));
|
|
474
|
+
const maxX = Math.max(...corners.map((corner) => corner.x));
|
|
475
|
+
const maxY = Math.max(...corners.map((corner) => corner.y));
|
|
476
|
+
if (item.fill !== void 0) rasteriser.draw({
|
|
477
|
+
kind: "fillRect",
|
|
478
|
+
xPx: minX,
|
|
479
|
+
yPx: minY,
|
|
480
|
+
widthPx: maxX - minX,
|
|
481
|
+
heightPx: maxY - minY,
|
|
482
|
+
color: item.fill
|
|
483
|
+
});
|
|
484
|
+
if (item.stroke !== void 0) rasteriser.draw({
|
|
485
|
+
kind: "path",
|
|
486
|
+
subpaths: [{
|
|
487
|
+
startXPx: minX,
|
|
488
|
+
startYPx: minY,
|
|
489
|
+
segments: [
|
|
490
|
+
{
|
|
491
|
+
kind: "line",
|
|
492
|
+
xPx: maxX,
|
|
493
|
+
yPx: minY
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
kind: "line",
|
|
497
|
+
xPx: maxX,
|
|
498
|
+
yPx: maxY
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
kind: "line",
|
|
502
|
+
xPx: minX,
|
|
503
|
+
yPx: maxY
|
|
504
|
+
}
|
|
505
|
+
],
|
|
506
|
+
closed: true
|
|
507
|
+
}],
|
|
508
|
+
stroke: strokeSpec(item.stroke, void 0, pixelsPerPt)
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
const ELLIPSE_QUADRANT_COUNT = 4;
|
|
512
|
+
function drawEllipse(item, matrix, pixelsPerPt, rasteriser) {
|
|
513
|
+
const cx = item.xPt + item.widthPt / 2;
|
|
514
|
+
const cy = item.yPt + item.heightPt / 2;
|
|
515
|
+
const rx = item.widthPt / 2;
|
|
516
|
+
const ry = item.heightPt / 2;
|
|
517
|
+
const dx = rx * BEZIER_KAPPA;
|
|
518
|
+
const dy = ry * BEZIER_KAPPA;
|
|
519
|
+
const cardinalPoints = [
|
|
520
|
+
[cx + rx, cy],
|
|
521
|
+
[cx, cy + ry],
|
|
522
|
+
[cx - rx, cy],
|
|
523
|
+
[cx, cy - ry]
|
|
524
|
+
];
|
|
525
|
+
const controls = [
|
|
526
|
+
[cx + rx, cy + dy],
|
|
527
|
+
[cx + dx, cy + ry],
|
|
528
|
+
[cx - dx, cy + ry],
|
|
529
|
+
[cx - rx, cy + dy],
|
|
530
|
+
[cx - rx, cy - dy],
|
|
531
|
+
[cx - dx, cy - ry],
|
|
532
|
+
[cx + dx, cy - ry],
|
|
533
|
+
[cx + rx, cy - dy]
|
|
534
|
+
];
|
|
535
|
+
const transformed = (point) => applyMatrix(matrix, {
|
|
536
|
+
x: point[0],
|
|
537
|
+
y: point[1]
|
|
538
|
+
});
|
|
539
|
+
const start = transformed(cardinalPoints[0]);
|
|
540
|
+
const segments = [];
|
|
541
|
+
for (let i = 0; i < ELLIPSE_QUADRANT_COUNT; i++) {
|
|
542
|
+
const end = transformed(cardinalPoints[(i + 1) % ELLIPSE_QUADRANT_COUNT]);
|
|
543
|
+
const c1 = transformed(controls[i * 2]);
|
|
544
|
+
const c2 = transformed(controls[i * 2 + 1]);
|
|
545
|
+
segments.push({
|
|
546
|
+
kind: "cubic",
|
|
547
|
+
c1xPx: c1.x,
|
|
548
|
+
c1yPx: c1.y,
|
|
549
|
+
c2xPx: c2.x,
|
|
550
|
+
c2yPx: c2.y,
|
|
551
|
+
xPx: end.x,
|
|
552
|
+
yPx: end.y
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
rasteriser.draw({
|
|
556
|
+
kind: "path",
|
|
557
|
+
subpaths: [{
|
|
558
|
+
startXPx: start.x,
|
|
559
|
+
startYPx: start.y,
|
|
560
|
+
segments,
|
|
561
|
+
closed: true
|
|
562
|
+
}],
|
|
563
|
+
...item.fill !== void 0 ? { fill: {
|
|
564
|
+
color: item.fill,
|
|
565
|
+
fillRule: "nonzero"
|
|
566
|
+
} } : {},
|
|
567
|
+
...item.stroke !== void 0 ? { stroke: strokeSpec(item.stroke, void 0, pixelsPerPt) } : {}
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
function drawLine(item, matrix, pixelsPerPt, rasteriser) {
|
|
571
|
+
const p1 = applyMatrix(matrix, {
|
|
572
|
+
x: item.x1Pt,
|
|
573
|
+
y: item.y1Pt
|
|
574
|
+
});
|
|
575
|
+
const p2 = applyMatrix(matrix, {
|
|
576
|
+
x: item.x2Pt,
|
|
577
|
+
y: item.y2Pt
|
|
578
|
+
});
|
|
579
|
+
if (item.style === "dotted") {
|
|
580
|
+
drawDottedSegment(p1, p2, item.widthPt * pixelsPerPt, item.color, rasteriser);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
rasteriser.draw({
|
|
584
|
+
kind: "path",
|
|
585
|
+
subpaths: [{
|
|
586
|
+
startXPx: p1.x,
|
|
587
|
+
startYPx: p1.y,
|
|
588
|
+
segments: [{
|
|
589
|
+
kind: "line",
|
|
590
|
+
xPx: p2.x,
|
|
591
|
+
yPx: p2.y
|
|
592
|
+
}],
|
|
593
|
+
closed: false
|
|
594
|
+
}],
|
|
595
|
+
stroke: strokeSpec({
|
|
596
|
+
color: item.color,
|
|
597
|
+
widthPt: item.widthPt
|
|
598
|
+
}, item.style, pixelsPerPt)
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
function drawPath(item, matrix, pixelsPerPt, rasteriser) {
|
|
602
|
+
if (item.stroke !== void 0 && item.style === "dotted") {
|
|
603
|
+
const widthPx = item.stroke.widthPt * pixelsPerPt;
|
|
604
|
+
for (const subpath of item.subpaths) {
|
|
605
|
+
let prev = applyMatrix(matrix, {
|
|
606
|
+
x: subpath.startXPt,
|
|
607
|
+
y: subpath.startYPt
|
|
608
|
+
});
|
|
609
|
+
const emitTo = (end) => {
|
|
610
|
+
drawDottedSegment(prev, end, widthPx, item.stroke.color, rasteriser);
|
|
611
|
+
prev = end;
|
|
612
|
+
};
|
|
613
|
+
for (const segment of subpath.segments) if (segment.kind === "line") emitTo(applyMatrix(matrix, {
|
|
614
|
+
x: segment.xPt,
|
|
615
|
+
y: segment.yPt
|
|
616
|
+
}));
|
|
617
|
+
else {
|
|
618
|
+
const flattened = flattenCubic(prev, applyMatrix(matrix, {
|
|
619
|
+
x: segment.c1xPt,
|
|
620
|
+
y: segment.c1yPt
|
|
621
|
+
}), applyMatrix(matrix, {
|
|
622
|
+
x: segment.c2xPt,
|
|
623
|
+
y: segment.c2yPt
|
|
624
|
+
}), applyMatrix(matrix, {
|
|
625
|
+
x: segment.xPt,
|
|
626
|
+
y: segment.yPt
|
|
627
|
+
}));
|
|
628
|
+
for (const point of flattened) emitTo(point);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const subpaths = item.subpaths.map((subpath) => ({
|
|
634
|
+
startXPx: applyMatrix(matrix, {
|
|
635
|
+
x: subpath.startXPt,
|
|
636
|
+
y: subpath.startYPt
|
|
637
|
+
}).x,
|
|
638
|
+
startYPx: applyMatrix(matrix, {
|
|
639
|
+
x: subpath.startXPt,
|
|
640
|
+
y: subpath.startYPt
|
|
641
|
+
}).y,
|
|
642
|
+
segments: subpath.segments.map((segment) => {
|
|
643
|
+
if (segment.kind === "line") {
|
|
644
|
+
const end = applyMatrix(matrix, {
|
|
645
|
+
x: segment.xPt,
|
|
646
|
+
y: segment.yPt
|
|
647
|
+
});
|
|
648
|
+
return {
|
|
649
|
+
kind: "line",
|
|
650
|
+
xPx: end.x,
|
|
651
|
+
yPx: end.y
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const c1 = applyMatrix(matrix, {
|
|
655
|
+
x: segment.c1xPt,
|
|
656
|
+
y: segment.c1yPt
|
|
657
|
+
});
|
|
658
|
+
const c2 = applyMatrix(matrix, {
|
|
659
|
+
x: segment.c2xPt,
|
|
660
|
+
y: segment.c2yPt
|
|
661
|
+
});
|
|
662
|
+
const end = applyMatrix(matrix, {
|
|
663
|
+
x: segment.xPt,
|
|
664
|
+
y: segment.yPt
|
|
665
|
+
});
|
|
666
|
+
return {
|
|
667
|
+
kind: "cubic",
|
|
668
|
+
c1xPx: c1.x,
|
|
669
|
+
c1yPx: c1.y,
|
|
670
|
+
c2xPx: c2.x,
|
|
671
|
+
c2yPx: c2.y,
|
|
672
|
+
xPx: end.x,
|
|
673
|
+
yPx: end.y
|
|
674
|
+
};
|
|
675
|
+
}),
|
|
676
|
+
closed: subpath.closed
|
|
677
|
+
}));
|
|
678
|
+
rasteriser.draw({
|
|
679
|
+
kind: "path",
|
|
680
|
+
subpaths,
|
|
681
|
+
...item.fill !== void 0 ? { fill: {
|
|
682
|
+
color: item.fill,
|
|
683
|
+
fillRule: item.fillRule
|
|
684
|
+
} } : {},
|
|
685
|
+
...item.stroke !== void 0 ? { stroke: strokeSpec(item.stroke, item.style, pixelsPerPt) } : {}
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function drawImageXObjectItem(item, resolver, interpretToDeviceMatrix, rasteriser, sink) {
|
|
689
|
+
const xobjects = resolver.resolveDict(dictGet(item.resources, "XObject"));
|
|
690
|
+
const stream = xobjects !== void 0 ? resolver.resolve(dictGet(xobjects, item.resourceName)) : void 0;
|
|
691
|
+
if (stream?.kind !== "stream") return;
|
|
692
|
+
const image = readImageXObject(stream.dict, stream.raw, resolver, sink);
|
|
693
|
+
if (image === void 0) return;
|
|
694
|
+
drawImage(image, item.matrix, interpretToDeviceMatrix, rasteriser);
|
|
695
|
+
}
|
|
696
|
+
function drawInlineImageItem(item, resolver, interpretToDeviceMatrix, rasteriser, sink) {
|
|
697
|
+
const image = readImageXObject(item.dict, item.data, resolver, sink);
|
|
698
|
+
if (image === void 0) return;
|
|
699
|
+
drawImage(image, item.matrix, interpretToDeviceMatrix, rasteriser);
|
|
700
|
+
}
|
|
701
|
+
function drawImage(image, ctm, interpretToDeviceMatrix, rasteriser) {
|
|
702
|
+
const matrix = multiplyMatrices([
|
|
703
|
+
1,
|
|
704
|
+
0,
|
|
705
|
+
0,
|
|
706
|
+
-1,
|
|
707
|
+
0,
|
|
708
|
+
1
|
|
709
|
+
], multiplyMatrices(ctm, interpretToDeviceMatrix));
|
|
710
|
+
rasteriser.draw({
|
|
711
|
+
kind: "image",
|
|
712
|
+
format: image.format,
|
|
713
|
+
bytes: image.bytes,
|
|
714
|
+
sourceWidthPx: image.widthPx,
|
|
715
|
+
sourceHeightPx: image.heightPx,
|
|
716
|
+
matrix
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
const DASHED_STROKE_WIDTH_MULTIPLE = 3;
|
|
720
|
+
function strokeSpec(stroke, style, pixelsPerPt) {
|
|
721
|
+
const widthPx = stroke.widthPt * pixelsPerPt;
|
|
722
|
+
if (style === "dashed") return {
|
|
723
|
+
color: stroke.color,
|
|
724
|
+
widthPx,
|
|
725
|
+
dashPx: [widthPx * DASHED_STROKE_WIDTH_MULTIPLE, widthPx * DASHED_STROKE_WIDTH_MULTIPLE]
|
|
726
|
+
};
|
|
727
|
+
return {
|
|
728
|
+
color: stroke.color,
|
|
729
|
+
widthPx
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function drawDottedSegment(p1, p2, widthPx, color, rasteriser) {
|
|
733
|
+
const length = Math.hypot(p2.x - p1.x, p2.y - p1.y);
|
|
734
|
+
if (length === 0) return;
|
|
735
|
+
const spacing = Math.max(widthPx * 2, 1);
|
|
736
|
+
const dotHalf = widthPx / 2;
|
|
737
|
+
for (let distance = 0; distance <= length; distance += spacing) {
|
|
738
|
+
const t = distance / length;
|
|
739
|
+
rasteriser.draw({
|
|
740
|
+
kind: "fillRect",
|
|
741
|
+
xPx: p1.x + (p2.x - p1.x) * t - dotHalf,
|
|
742
|
+
yPx: p1.y + (p2.y - p1.y) * t - dotHalf,
|
|
743
|
+
widthPx,
|
|
744
|
+
heightPx: widthPx,
|
|
745
|
+
color
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const STROKE_FLATTEN_TOLERANCE_PX = .05;
|
|
750
|
+
const MAX_FLATTEN_DEPTH = 16;
|
|
751
|
+
function flattenCubic(p0, c1, c2, p1) {
|
|
752
|
+
const points = [];
|
|
753
|
+
const flatten = (a, b, c, d, depth) => {
|
|
754
|
+
const chordX = d.x - a.x;
|
|
755
|
+
const chordY = d.y - a.y;
|
|
756
|
+
const chordLength = Math.hypot(chordX, chordY) || 1;
|
|
757
|
+
const dist1 = Math.abs((b.x - a.x) * chordY - (b.y - a.y) * chordX) / chordLength;
|
|
758
|
+
const dist2 = Math.abs((c.x - a.x) * chordY - (c.y - a.y) * chordX) / chordLength;
|
|
759
|
+
if (depth >= MAX_FLATTEN_DEPTH || Math.max(dist1, dist2) <= STROKE_FLATTEN_TOLERANCE_PX) {
|
|
760
|
+
points.push(d);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const ab = {
|
|
764
|
+
x: (a.x + b.x) / 2,
|
|
765
|
+
y: (a.y + b.y) / 2
|
|
766
|
+
};
|
|
767
|
+
const bc = {
|
|
768
|
+
x: (b.x + c.x) / 2,
|
|
769
|
+
y: (b.y + c.y) / 2
|
|
770
|
+
};
|
|
771
|
+
const cd = {
|
|
772
|
+
x: (c.x + d.x) / 2,
|
|
773
|
+
y: (c.y + d.y) / 2
|
|
774
|
+
};
|
|
775
|
+
const abc = {
|
|
776
|
+
x: (ab.x + bc.x) / 2,
|
|
777
|
+
y: (ab.y + bc.y) / 2
|
|
778
|
+
};
|
|
779
|
+
const bcd = {
|
|
780
|
+
x: (bc.x + cd.x) / 2,
|
|
781
|
+
y: (bc.y + cd.y) / 2
|
|
782
|
+
};
|
|
783
|
+
const mid = {
|
|
784
|
+
x: (abc.x + bcd.x) / 2,
|
|
785
|
+
y: (abc.y + bcd.y) / 2
|
|
786
|
+
};
|
|
787
|
+
flatten(a, ab, abc, mid, depth + 1);
|
|
788
|
+
flatten(mid, bcd, cd, d, depth + 1);
|
|
789
|
+
};
|
|
790
|
+
flatten(p0, c1, c2, p1, 0);
|
|
791
|
+
return points;
|
|
792
|
+
}
|
|
793
|
+
//#endregion
|
|
794
|
+
export { drawTextRun as a, drawGlyphOutline as i, flattenCubic as n, glyphOutlineSubpaths as o, renderPdfPage as r, EXTENT_ZERO_EPSILON as t };
|