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