@jasy/pdf 1.0.0-beta.3 → 1.0.0-beta.5
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/api/color.d.ts +1 -24
- package/dist/api/color.js +2 -278
- package/dist/api/content.d.ts +10 -5
- package/dist/api/content.js +29 -13
- package/dist/api/descriptor.js +30 -3
- package/dist/api/flow.d.ts +55 -0
- package/dist/api/flow.js +71 -0
- package/dist/api/font-url.d.ts +1 -2
- package/dist/api/font-url.js +8 -74
- package/dist/api/forms.d.ts +3 -0
- package/dist/api/forms.js +3 -0
- package/dist/api/gradient.d.ts +1 -53
- package/dist/api/gradient.js +2 -97
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.js +3 -0
- package/dist/api/layout.d.ts +6 -0
- package/dist/api/layout.js +9 -3
- package/dist/api/page-builder.d.ts +4 -0
- package/dist/api/page-builder.js +4 -0
- package/dist/api/structure.d.ts +10 -2
- package/dist/api/structure.js +9 -6
- package/dist/api/table.d.ts +5 -3
- package/dist/api/table.js +101 -22
- package/dist/api/text.d.ts +8 -8
- package/dist/api/text.js +6 -0
- package/dist/canvas/painter.d.ts +2 -2
- package/dist/canvas/painter.js +2 -2
- package/dist/common/color-input.d.ts +24 -0
- package/dist/common/color-input.js +278 -0
- package/dist/common/diagnostics.d.ts +61 -0
- package/dist/common/diagnostics.js +77 -0
- package/dist/common/gradient.d.ts +53 -0
- package/dist/common/gradient.js +97 -0
- package/dist/edit/acroform-reader.js +7 -7
- package/dist/edit/appearance.js +3 -3
- package/dist/edit/attachments.d.ts +52 -0
- package/dist/edit/attachments.js +178 -0
- package/dist/edit/document.d.ts +38 -8
- package/dist/edit/document.js +208 -51
- package/dist/edit/fill.js +8 -23
- package/dist/edit/filters.d.ts +15 -0
- package/dist/edit/filters.js +157 -0
- package/dist/edit/index.d.ts +4 -1
- package/dist/edit/index.js +3 -1
- package/dist/edit/objects.d.ts +3 -4
- package/dist/edit/objects.js +5 -18
- package/dist/elements/canvas-element.d.ts +3 -0
- package/dist/elements/canvas-element.js +14 -3
- package/dist/elements/container-element.d.ts +8 -1
- package/dist/elements/container-element.js +49 -17
- package/dist/elements/forms/checkbox-element.d.ts +2 -0
- package/dist/elements/forms/checkbox-element.js +12 -3
- package/dist/elements/forms/choice-element.d.ts +6 -1
- package/dist/elements/forms/choice-element.js +17 -6
- package/dist/elements/forms/field-font.d.ts +8 -0
- package/dist/elements/forms/field-font.js +5 -0
- package/dist/elements/forms/push-button-element.d.ts +6 -1
- package/dist/elements/forms/push-button-element.js +16 -4
- package/dist/elements/forms/radio-element.d.ts +2 -0
- package/dist/elements/forms/radio-element.js +12 -3
- package/dist/elements/forms/signature-element.d.ts +6 -1
- package/dist/elements/forms/signature-element.js +16 -4
- package/dist/elements/forms/text-field-element.d.ts +6 -1
- package/dist/elements/forms/text-field-element.js +16 -4
- package/dist/elements/image-element.d.ts +70 -21
- package/dist/elements/image-element.js +173 -52
- package/dist/elements/index.d.ts +3 -0
- package/dist/elements/index.js +3 -0
- package/dist/elements/layout/anchor-element.d.ts +8 -10
- package/dist/elements/layout/anchor-element.js +10 -18
- package/dist/elements/layout/bookmark-element.d.ts +9 -11
- package/dist/elements/layout/bookmark-element.js +11 -20
- package/dist/elements/layout/column-break-element.d.ts +17 -0
- package/dist/elements/layout/column-break-element.js +26 -0
- package/dist/elements/layout/default-text-style-element.d.ts +11 -13
- package/dist/elements/layout/default-text-style-element.js +14 -31
- package/dist/elements/layout/deferred-element.d.ts +20 -4
- package/dist/elements/layout/deferred-element.js +41 -9
- package/dist/elements/layout/expanded-element.d.ts +11 -0
- package/dist/elements/layout/expanded-element.js +53 -18
- package/dist/elements/layout/flow-element.d.ts +109 -0
- package/dist/elements/layout/flow-element.js +317 -0
- package/dist/elements/layout/keep-together-element.d.ts +8 -18
- package/dist/elements/layout/keep-together-element.js +33 -45
- package/dist/elements/layout/link-element.d.ts +8 -13
- package/dist/elements/layout/link-element.js +9 -19
- package/dist/elements/layout/padding-element.d.ts +18 -1
- package/dist/elements/layout/padding-element.js +63 -28
- package/dist/elements/layout/page-break-element.d.ts +4 -1
- package/dist/elements/layout/page-break-element.js +6 -1
- package/dist/elements/layout/page-builder-element.d.ts +5 -0
- package/dist/elements/layout/page-builder-element.js +13 -2
- package/dist/elements/layout/positioned-element.d.ts +23 -4
- package/dist/elements/layout/positioned-element.js +42 -12
- package/dist/elements/layout/proxy-element.d.ts +47 -0
- package/dist/elements/layout/proxy-element.js +106 -0
- package/dist/elements/layout/region-element.d.ts +53 -0
- package/dist/elements/layout/region-element.js +108 -0
- package/dist/elements/layout/repeating-header-element.d.ts +9 -0
- package/dist/elements/layout/repeating-header-element.js +55 -13
- package/dist/elements/layout/rotated-box-element.d.ts +6 -0
- package/dist/elements/layout/rotated-box-element.js +27 -14
- package/dist/elements/layout/rotated-element.d.ts +9 -0
- package/dist/elements/layout/rotated-element.js +33 -2
- package/dist/elements/layout/struct-group.d.ts +9 -17
- package/dist/elements/layout/struct-group.js +10 -35
- package/dist/elements/line-element.d.ts +1 -0
- package/dist/elements/line-element.js +6 -0
- package/dist/elements/page-element.d.ts +10 -3
- package/dist/elements/page-element.js +39 -31
- package/dist/elements/pdf-document-element.d.ts +3 -0
- package/dist/elements/pdf-document-element.js +7 -0
- package/dist/elements/pdf-element.d.ts +105 -16
- package/dist/elements/pdf-element.js +118 -19
- package/dist/elements/rectangle-element.d.ts +22 -2
- package/dist/elements/rectangle-element.js +119 -55
- package/dist/elements/row-element.d.ts +17 -12
- package/dist/elements/row-element.js +89 -9
- package/dist/elements/svg-element.d.ts +30 -6
- package/dist/elements/svg-element.js +91 -26
- package/dist/elements/text-element.d.ts +48 -13
- package/dist/elements/text-element.js +161 -116
- package/dist/forms/acroform.d.ts +9 -1
- package/dist/forms/acroform.js +83 -34
- package/dist/forms/appearance.d.ts +17 -6
- package/dist/forms/appearance.js +18 -16
- package/dist/ir/display-list.d.ts +18 -3
- package/dist/layout/box-constraints.d.ts +17 -0
- package/dist/layout/box-constraints.js +31 -0
- package/dist/layout/collect-images.d.ts +6 -4
- package/dist/layout/collect-images.js +14 -8
- package/dist/layout/flex-item.d.ts +11 -0
- package/dist/layout/flex-item.js +51 -0
- package/dist/layout/fragmentation.d.ts +23 -9
- package/dist/layout/fragmentation.js +62 -19
- package/dist/layout/layout-cache.d.ts +53 -0
- package/dist/layout/layout-cache.js +110 -0
- package/dist/platform/browser-fs.d.ts +1 -2
- package/dist/platform/browser-fs.js +3 -6
- package/dist/platform/node-fs.d.ts +0 -1
- package/dist/platform/node-fs.js +0 -4
- package/dist/renderer/anchor-renderer.d.ts +2 -1
- package/dist/renderer/anchor-renderer.js +5 -4
- package/dist/renderer/bookmark-renderer.d.ts +2 -1
- package/dist/renderer/bookmark-renderer.js +5 -4
- package/dist/renderer/canvas-renderer.d.ts +2 -1
- package/dist/renderer/canvas-renderer.js +5 -2
- package/dist/renderer/column-break-renderer.d.ts +8 -0
- package/dist/renderer/column-break-renderer.js +15 -0
- package/dist/renderer/container-renderer.d.ts +2 -1
- package/dist/renderer/container-renderer.js +6 -5
- package/dist/renderer/default-text-style-renderer.d.ts +2 -1
- package/dist/renderer/default-text-style-renderer.js +2 -3
- package/dist/renderer/deferred-renderer.d.ts +2 -1
- package/dist/renderer/deferred-renderer.js +2 -3
- package/dist/renderer/emoji-image.js +3 -1
- package/dist/renderer/expanded-renderer.d.ts +2 -1
- package/dist/renderer/expanded-renderer.js +2 -3
- package/dist/renderer/flow-renderer.d.ts +16 -0
- package/dist/renderer/flow-renderer.js +21 -0
- package/dist/renderer/form-field-renderer.d.ts +2 -1
- package/dist/renderer/form-field-renderer.js +16 -3
- package/dist/renderer/image-renderer.d.ts +2 -1
- package/dist/renderer/image-renderer.js +33 -53
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.js +2 -0
- package/dist/renderer/keep-together-renderer.d.ts +2 -1
- package/dist/renderer/keep-together-renderer.js +2 -3
- package/dist/renderer/line-renderer.d.ts +2 -1
- package/dist/renderer/line-renderer.js +7 -2
- package/dist/renderer/link-renderer.d.ts +2 -1
- package/dist/renderer/link-renderer.js +36 -5
- package/dist/renderer/padding-renderer.d.ts +2 -1
- package/dist/renderer/padding-renderer.js +5 -5
- package/dist/renderer/page-break-renderer.d.ts +6 -5
- package/dist/renderer/page-break-renderer.js +14 -7
- package/dist/renderer/page-builder-renderer.d.ts +2 -1
- package/dist/renderer/page-builder-renderer.js +2 -3
- package/dist/renderer/page-renderer.js +30 -10
- package/dist/renderer/pdf-backend.d.ts +4 -3
- package/dist/renderer/pdf-backend.js +14 -7
- package/dist/renderer/pdf-document-renderer.d.ts +1 -8
- package/dist/renderer/pdf-document-renderer.js +17 -17
- package/dist/renderer/pdf-renderer.js +20 -11
- package/dist/renderer/positioned-renderer.d.ts +18 -1
- package/dist/renderer/positioned-renderer.js +43 -6
- package/dist/renderer/rectangle-renderer.d.ts +2 -1
- package/dist/renderer/rectangle-renderer.js +18 -8
- package/dist/renderer/repeating-header-renderer.d.ts +2 -1
- package/dist/renderer/repeating-header-renderer.js +6 -5
- package/dist/renderer/rotated-renderer.d.ts +2 -1
- package/dist/renderer/rotated-renderer.js +9 -5
- package/dist/renderer/row-renderer.d.ts +2 -1
- package/dist/renderer/row-renderer.js +6 -5
- package/dist/renderer/struct-group-renderer.d.ts +2 -1
- package/dist/renderer/struct-group-renderer.js +2 -3
- package/dist/renderer/svg-renderer.d.ts +2 -1
- package/dist/renderer/svg-renderer.js +8 -2
- package/dist/renderer/text-renderer.d.ts +4 -6
- package/dist/renderer/text-renderer.js +114 -127
- package/dist/svg/gradients.js +5 -0
- package/dist/svg/index.d.ts +1 -1
- package/dist/svg/index.js +36 -6
- package/dist/svg/style.d.ts +2 -0
- package/dist/svg/style.js +12 -1
- package/dist/text/advance.d.ts +4 -0
- package/dist/text/advance.js +17 -4
- package/dist/text/bidi.d.ts +0 -2
- package/dist/text/bidi.js +0 -10
- package/dist/text/glyph-coverage.d.ts +1 -1
- package/dist/text/glyph-coverage.js +7 -3
- package/dist/text/hyphen-breaks.d.ts +17 -0
- package/dist/text/hyphen-breaks.js +54 -0
- package/dist/text/line-breaker.d.ts +55 -29
- package/dist/text/line-breaker.js +453 -188
- package/dist/text/line-metrics.d.ts +8 -3
- package/dist/text/line-metrics.js +11 -2
- package/dist/text/pdf-text-string.d.ts +21 -0
- package/dist/text/pdf-text-string.js +114 -0
- package/dist/text/text-decoration.d.ts +5 -5
- package/dist/text/text-decoration.js +5 -5
- package/dist/text/word-splitting.d.ts +15 -2
- package/dist/text/word-splitting.js +45 -13
- package/dist/utils/afm-parser.d.ts +2 -0
- package/dist/utils/afm-parser.js +10 -1
- package/dist/utils/dest-registry.d.ts +1 -1
- package/dist/utils/dest-registry.js +3 -2
- package/dist/utils/fetch-bytes.d.ts +19 -0
- package/dist/utils/fetch-bytes.js +79 -0
- package/dist/utils/flex-layout.d.ts +16 -14
- package/dist/utils/flex-layout.js +427 -217
- package/dist/utils/font-metrics.d.ts +4 -3
- package/dist/utils/image-helper.d.ts +59 -11
- package/dist/utils/image-helper.js +209 -81
- package/dist/utils/pdf-object-manager.d.ts +17 -6
- package/dist/utils/pdf-object-manager.js +70 -20
- package/dist/utils/renderer-registry.d.ts +49 -2
- package/dist/utils/renderer-registry.js +40 -0
- package/dist/utils/struct-tree.d.ts +24 -0
- package/dist/utils/struct-tree.js +61 -9
- package/dist/utils/ttf-parser.d.ts +4 -0
- package/dist/utils/ttf-parser.js +30 -6
- package/dist/validators/element-validator.js +2 -1
- package/package.json +1 -1
- package/dist/elements/layout/sized-container-element.d.ts +0 -14
- package/dist/elements/layout/sized-container-element.js +0 -32
|
@@ -26,6 +26,7 @@ export function featureKey(f) {
|
|
|
26
26
|
}
|
|
27
27
|
const features = (ligatures) => ligatures ? LATIN_FEATURES : NO_FEATURES;
|
|
28
28
|
import { getArrayBuffer, isWindows1252 } from "./utf8-to-windows1252-encoder.js";
|
|
29
|
+
import { encodePdfText, isPlainPdfText } from "../text/pdf-text-string.js";
|
|
29
30
|
import { isEmojiCodePoint } from "../text/emoji-codepoints.js";
|
|
30
31
|
import { StructTree } from "./struct-tree.js";
|
|
31
32
|
import { OutlineBuilder } from "./outline.js";
|
|
@@ -34,6 +35,14 @@ import { AcroFormCollector } from "../forms/acroform.js";
|
|
|
34
35
|
// Enums come from the leaf config module (never in a cycle); the config type is
|
|
35
36
|
// erased at runtime so it can come from the cyclic module safely.
|
|
36
37
|
import { ColorMode, Orientation } from "../renderer/pdf-config.js";
|
|
38
|
+
import { Diagnostics } from "../common/diagnostics.js";
|
|
39
|
+
import { JpegFormatError } from "./image-helper.js";
|
|
40
|
+
/** The PDF color space of each image color space, and its channel count (for `/Decode`). */
|
|
41
|
+
const IMAGE_COLOR_SPACE = {
|
|
42
|
+
gray: { name: "DeviceGray", channels: 1 },
|
|
43
|
+
rgb: { name: "DeviceRGB", channels: 3 },
|
|
44
|
+
cmyk: { name: "DeviceCMYK", channels: 4 },
|
|
45
|
+
};
|
|
37
46
|
export var FontStyle;
|
|
38
47
|
(function (FontStyle) {
|
|
39
48
|
FontStyle["Normal"] = "normal";
|
|
@@ -184,8 +193,8 @@ export class PDFObjectManager {
|
|
|
184
193
|
/** Shaped runs, cached face -> feature set -> text. Nested, not one map under a composed key: that
|
|
185
194
|
* key would carry the whole run's text, so every lookup would copy it into a fresh string. */
|
|
186
195
|
this.shapeCache = new Map();
|
|
187
|
-
/** Bumped
|
|
188
|
-
* family name can mean a different face afterwards. */
|
|
196
|
+
/** Bumped when a face is really ADDED (a repeat registration returns early, untouched); every
|
|
197
|
+
* memoised measurement watches it, since a family name can mean a different face afterwards. */
|
|
189
198
|
this.fontEpoch = 0;
|
|
190
199
|
// Per font, glyph -> the code points it stands for. A shaped glyph is often absent from the cmap (a
|
|
191
200
|
// ligature) or maps back to a presentation form, so `ToUnicode` cannot come from the cmap alone.
|
|
@@ -220,10 +229,11 @@ export class PDFObjectManager {
|
|
|
220
229
|
* character went missing instead of a server log nobody reads.
|
|
221
230
|
*/
|
|
222
231
|
this.missingGlyphs = new Set();
|
|
232
|
+
/** The render's diagnostics channel (`onDiagnostic`); see `common/diagnostics.ts`. */
|
|
233
|
+
this.diagnostics = new Diagnostics();
|
|
223
234
|
// Whether to kern. Document-level: measuring (via `runAdvance`, which reads this off the metrics)
|
|
224
|
-
// and drawing (the backend emits a `TJ`) both consult it, so they can never disagree.
|
|
225
|
-
//
|
|
226
|
-
// fonts finer than a user's own font, which is worse than none. Flip to on when GPOS lands.
|
|
235
|
+
// and drawing (the backend emits a `TJ`) both consult it, so they can never disagree. The default
|
|
236
|
+
// here is off; `renderPdf` turns it on unless the document says `kerning: false`.
|
|
227
237
|
this.kerningEnabled = false;
|
|
228
238
|
if (pageSize)
|
|
229
239
|
this.pageFormat = pageFormats[pageSize];
|
|
@@ -247,9 +257,18 @@ export class PDFObjectManager {
|
|
|
247
257
|
setCompress(on) {
|
|
248
258
|
this.compress = on;
|
|
249
259
|
}
|
|
250
|
-
reportMissingGlyph(codePoints) {
|
|
260
|
+
reportMissingGlyph(codePoints, about, page) {
|
|
251
261
|
for (const cp of codePoints)
|
|
252
262
|
this.missingGlyphs.add(cp);
|
|
263
|
+
const chars = codePoints.map((cp) => String.fromCodePoint(cp)).join("");
|
|
264
|
+
this.diagnostics.report({
|
|
265
|
+
code: "missing-glyphs",
|
|
266
|
+
severity: "warning",
|
|
267
|
+
message: `No font can draw ${JSON.stringify(chars)}; removed (PDF/A forbids .notdef).`,
|
|
268
|
+
about,
|
|
269
|
+
page,
|
|
270
|
+
fallback: "silent",
|
|
271
|
+
});
|
|
253
272
|
}
|
|
254
273
|
/** The dropped code points, in the order first seen. Empty for a document that hit none. */
|
|
255
274
|
getMissingGlyphs() {
|
|
@@ -263,7 +282,7 @@ export class PDFObjectManager {
|
|
|
263
282
|
}
|
|
264
283
|
// A unique, binary-safe placeholder for a stream's bytes, swapped for ciphertext in finalizeEncryption().
|
|
265
284
|
encToken(id) {
|
|
266
|
-
return
|
|
285
|
+
return `\x00\x01JENC:${id}\x01\x00`;
|
|
267
286
|
}
|
|
268
287
|
// The same trick for a literal STRING. A separate marker because the two are swapped differently: a
|
|
269
288
|
// stream keeps its delimiters and only the payload changes, a string is replaced whole by a hex one.
|
|
@@ -282,15 +301,28 @@ export class PDFObjectManager {
|
|
|
282
301
|
* A string INSIDE a content stream needs none of this - the stream is enciphered as a whole.
|
|
283
302
|
*/
|
|
284
303
|
pdfString(text) {
|
|
304
|
+
// A TEXT string: PDFDocEncoding where it can say everything, else UTF-16BE (text/pdf-text-string.ts).
|
|
305
|
+
const bytes = encodePdfText(text);
|
|
285
306
|
if (!this.security) {
|
|
286
|
-
|
|
307
|
+
// Plain Latin-1 comes out of the Windows-1252 page encoder as the same bytes PDFDocEncoding reads
|
|
308
|
+
// back, so it stays a readable literal; anything else is written as hex, past that encoder.
|
|
309
|
+
if (isPlainPdfText(text)) {
|
|
310
|
+
return `(${text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)")})`;
|
|
311
|
+
}
|
|
312
|
+
return `<${[...bytes].map((b) => b.toString(16).padStart(2, "0")).join("")}>`;
|
|
287
313
|
}
|
|
288
314
|
// Encrypt the bytes the unencrypted path would have written, so a viewer decrypts to the same value.
|
|
289
315
|
// No escaping is applied: the result is emitted as a HEX string, which has no escapes to get wrong.
|
|
290
|
-
const bytes = new Uint8Array(getArrayBuffer(text));
|
|
291
316
|
const id = this.strJobs.push(bytes) - 1;
|
|
292
317
|
return this.strToken(id);
|
|
293
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* A URI: 7-bit ASCII by definition, so anything else is percent-encoded as UTF-8 - the way a browser
|
|
321
|
+
* writes the link - rather than squeezed into a text encoding.
|
|
322
|
+
*/
|
|
323
|
+
pdfUri(uri) {
|
|
324
|
+
return this.pdfString(uri.replace(/[^\x20-\x7e]/gu, (c) => encodeURIComponent(c)));
|
|
325
|
+
}
|
|
294
326
|
// The single encryption choke-point every stream emitter routes through (stream(), registerImage). Without
|
|
295
327
|
// a security handler: the bytes as a latin1 string + their length. With one: register the raw bytes for the
|
|
296
328
|
// finalize pass and return a placeholder + the pre-computable encrypted length (16-byte IV + PKCS#7 padding).
|
|
@@ -371,7 +403,7 @@ export class PDFObjectManager {
|
|
|
371
403
|
return this.parentObjectNumber;
|
|
372
404
|
}
|
|
373
405
|
// Registers an image
|
|
374
|
-
registerImage(width, height, imageType, imageData, smaskData) {
|
|
406
|
+
registerImage(width, height, imageType, imageData, smaskData, color = { space: "rgb" }) {
|
|
375
407
|
// A transparent PNG carries its alpha as a separate DeviceGray /SMask image the viewer composites with.
|
|
376
408
|
let smaskEntry = "";
|
|
377
409
|
if (smaskData) {
|
|
@@ -389,14 +421,23 @@ ${s.body}
|
|
|
389
421
|
endstream`;
|
|
390
422
|
smaskEntry = ` /SMask ${this.addObject(smaskObject)} 0 R\n`;
|
|
391
423
|
}
|
|
424
|
+
// PDF/A: the sRGB output intent forbids DeviceCMYK.
|
|
425
|
+
if (color.space === "cmyk" && this.outputIntent !== undefined) {
|
|
426
|
+
throw new JpegFormatError("a CMYK JPEG in a PDF/A document: its sRGB output intent forbids DeviceCMYK. " +
|
|
427
|
+
"Convert the image to RGB first.");
|
|
428
|
+
}
|
|
429
|
+
const space = IMAGE_COLOR_SPACE[color.space];
|
|
430
|
+
const decode = color.inverted
|
|
431
|
+
? ` /Decode [${Array.from({ length: space.channels }, () => "1 0").join(" ")}]\n`
|
|
432
|
+
: "";
|
|
392
433
|
const img = this.streamPayload(bytesFromLatin1(imageData));
|
|
393
434
|
const imageObject = `<< /Type /XObject
|
|
394
435
|
/Subtype /Image
|
|
395
436
|
/Width ${width}
|
|
396
437
|
/Height ${height}
|
|
397
|
-
/ColorSpace
|
|
438
|
+
/ColorSpace /${space.name}
|
|
398
439
|
/BitsPerComponent 8
|
|
399
|
-
/Filter /${imageType}
|
|
440
|
+
${decode} /Filter /${imageType}
|
|
400
441
|
${smaskEntry} /Length ${img.length} >>
|
|
401
442
|
stream
|
|
402
443
|
${img.body}
|
|
@@ -473,7 +514,7 @@ endstream`;
|
|
|
473
514
|
async finalizeEncryption() {
|
|
474
515
|
if (!this.security)
|
|
475
516
|
return;
|
|
476
|
-
if (this.outputIntent !== undefined
|
|
517
|
+
if (this.outputIntent !== undefined) {
|
|
477
518
|
throw new Error("@jasy/pdf: cannot encrypt a PDF/A document - PDF/A forbids encryption.");
|
|
478
519
|
}
|
|
479
520
|
for (let id = 0; id < this.encJobs.length; id++) {
|
|
@@ -565,10 +606,10 @@ endstream`;
|
|
|
565
606
|
}
|
|
566
607
|
// Registers a font
|
|
567
608
|
registerFont(fontName, fontStyle = FontStyle.Normal, fullName = fontName) {
|
|
568
|
-
this.fontEpoch++;
|
|
569
609
|
if (this.fonts.hasFont(fontName, fontStyle)) {
|
|
570
610
|
return this.fonts.getFont(fontName, fontStyle); // Already exists? Return it!
|
|
571
611
|
}
|
|
612
|
+
this.fontEpoch++;
|
|
572
613
|
this.verticalsCache.delete(fontName);
|
|
573
614
|
this.decorationCache.delete(fontName);
|
|
574
615
|
const data = STANDARD_AFM[fullName];
|
|
@@ -596,7 +637,6 @@ endstream`;
|
|
|
596
637
|
// metrics and emits its PDF font objects. All variants share the family `name`; bold/italic are
|
|
597
638
|
// separate .ttf files registered under the same name with a different style.
|
|
598
639
|
registerCustomFont(name, data, style = FontStyle.Normal) {
|
|
599
|
-
this.fontEpoch++;
|
|
600
640
|
let byStyle = this.customFonts.get(name);
|
|
601
641
|
if (!byStyle) {
|
|
602
642
|
byStyle = new Map();
|
|
@@ -616,6 +656,7 @@ endstream`;
|
|
|
616
656
|
"is loaded asynchronously, so it happens one step earlier in renderPdf.");
|
|
617
657
|
}
|
|
618
658
|
byStyle.set(style, new TTFParser(isWoff(data) ? woffToSfnt(data) : data));
|
|
659
|
+
this.fontEpoch++; // only once the face is really there: a font that fails to parse adds nothing
|
|
619
660
|
this.verticalsCache.delete(name); // this family now answers from the .ttf, not from an AFM
|
|
620
661
|
this.decorationCache.delete(name);
|
|
621
662
|
// Emission (the PDF font objects + page resource) is DEFERRED to first use via ensureEmitted(),
|
|
@@ -742,7 +783,7 @@ endstream`;
|
|
|
742
783
|
return isWindows1252(codePoint);
|
|
743
784
|
}
|
|
744
785
|
/**
|
|
745
|
-
* Whether the document draws this code point through the
|
|
786
|
+
* Whether the document draws this code point through the color-emoji path instead of the text
|
|
746
787
|
* font - a fallback emoji FONT, or an image source (`Document({ emoji })`). Such a code point is
|
|
747
788
|
* missing from the text font ON PURPOSE, so glyph coverage must leave it alone. Mirrors exactly
|
|
748
789
|
* what `getCharWidth` measures it as, or a dropped emoji would have been measured and not drawn.
|
|
@@ -873,7 +914,9 @@ endstream`;
|
|
|
873
914
|
const all = ttf.glyphWidths();
|
|
874
915
|
const w = [...used]
|
|
875
916
|
.sort((a, b) => a - b)
|
|
876
|
-
|
|
917
|
+
// Four decimals: a 2048-unit font's widths are not whole thousandths, and rounding them drew every
|
|
918
|
+
// line a fraction wider or narrower than it was measured. A 1000-unit font stays byte-identical.
|
|
919
|
+
.map((g) => `${g} [${Number((all[g] ?? 1000).toFixed(4))}]`)
|
|
877
920
|
.join(" ");
|
|
878
921
|
return (`<< /Type /Font /Subtype /CIDFontType2 /BaseFont /${pdfName(name)} ` +
|
|
879
922
|
`/CIDSystemInfo << /Registry ${this.pdfString("Adobe")} /Ordering ${this.pdfString("Identity")} /Supplement 0 >> ` +
|
|
@@ -972,14 +1015,20 @@ endstream`;
|
|
|
972
1015
|
* `GPOS` tables are unparsed), so it returns zeros - never a wrong value, just no adjustment.
|
|
973
1016
|
*/
|
|
974
1017
|
/** Kerning between adjacent GLYPHS - what a shaped run needs, and what the backend already holds.
|
|
975
|
-
* Same lookup as the character path; `getKerning` is keyed by glyph id either way.
|
|
1018
|
+
* Same lookup as the character path; `getKerning` is keyed by glyph id either way. A pair that
|
|
1019
|
+
* touches a space is never kerned, as on the character path: the line breaker measures words and
|
|
1020
|
+
* spaces apart, so a pair across a space would be drawn but never measured. */
|
|
976
1021
|
getGlyphKernPairs(glyphs, fontFamily, fontStyle) {
|
|
977
1022
|
const ttf = this.getCustomFont(fontFamily, fontStyle);
|
|
978
1023
|
if (!ttf)
|
|
979
1024
|
return [];
|
|
1025
|
+
const space = ttf.getGlyphIndex(0x20);
|
|
1026
|
+
const noBreakSpace = ttf.getGlyphIndex(0xa0);
|
|
1027
|
+
const isSpace = (g) => g !== 0 && (g === space || g === noBreakSpace); // 0 = no such glyph
|
|
980
1028
|
const pairs = [];
|
|
981
1029
|
for (let i = 0; i < glyphs.length - 1; i++) {
|
|
982
|
-
|
|
1030
|
+
const [a, b] = [glyphs[i], glyphs[i + 1]];
|
|
1031
|
+
pairs.push(isSpace(a) || isSpace(b) ? 0 : ttf.getKerning(a, b));
|
|
983
1032
|
}
|
|
984
1033
|
return pairs;
|
|
985
1034
|
}
|
|
@@ -1004,7 +1053,8 @@ endstream`;
|
|
|
1004
1053
|
for (let i = 0; i < chars.length - 1; i++) {
|
|
1005
1054
|
const a = chars[i];
|
|
1006
1055
|
const b = chars[i + 1];
|
|
1007
|
-
|
|
1056
|
+
// A no-break space is a space: never kerned (the AFM names it `space`, with pairs of its own).
|
|
1057
|
+
if (a === " " || b === " " || a === "\u00a0" || b === "\u00a0") {
|
|
1008
1058
|
out.push(0);
|
|
1009
1059
|
}
|
|
1010
1060
|
else if (ttf) {
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
import type { Offset } from "../layout/box-constraints.js";
|
|
2
|
+
import type { IRNode } from "../ir/display-list.js";
|
|
3
|
+
import type { PDFObjectManager } from "./pdf-object-manager.js";
|
|
4
|
+
/**
|
|
5
|
+
* Where an element is drawn. `origin` is the ABSOLUTE top-left that the element's own offset is
|
|
6
|
+
* relative to; a renderer adds it to its position and hands its children the origin THEY are relative
|
|
7
|
+
* to. `frame` is the absolute top-left of the nearest positioning frame (the page's content box, or a
|
|
8
|
+
* `relative` Box): a `Positioned` is laid out relative to it and draws from there, at its own place in
|
|
9
|
+
* the tree - tree order is paint order.
|
|
10
|
+
*/
|
|
11
|
+
export interface RenderScope {
|
|
12
|
+
origin: Offset;
|
|
13
|
+
frame: Offset;
|
|
14
|
+
/** Where a `Positioned` with a `zIndex` hands its drawing, for the nearest stacking context to sort.
|
|
15
|
+
* It still RENDERS at its own place, so everything it registers there (its tag's parent) is right. */
|
|
16
|
+
stacked?: {
|
|
17
|
+
zIndex: number;
|
|
18
|
+
nodes: IRNode[];
|
|
19
|
+
}[];
|
|
20
|
+
}
|
|
21
|
+
/** The scope for the children of an element at `offset` (relative to `scope.origin`). */
|
|
22
|
+
export declare function within(scope: RenderScope, offset: {
|
|
23
|
+
x?: number;
|
|
24
|
+
y?: number;
|
|
25
|
+
}): RenderScope;
|
|
26
|
+
/**
|
|
27
|
+
* Renders one element to IR. Typed so that a call which forgets to pass the scope on does not
|
|
28
|
+
* compile - a forgotten origin draws at the page corner without a word.
|
|
29
|
+
*/
|
|
30
|
+
export type ElementRenderer = (element: any, objectManager: PDFObjectManager, scope: RenderScope) => Promise<IRNode[]>;
|
|
1
31
|
/**
|
|
2
32
|
* Thrown when an element reaches the render pass with no registered renderer.
|
|
3
33
|
*
|
|
@@ -10,9 +40,26 @@ export declare class MissingRendererError extends Error {
|
|
|
10
40
|
readonly elementName: string;
|
|
11
41
|
constructor(elementName: string);
|
|
12
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Thrown when one element INSTANCE is drawn twice on the same page. An element holds one position - the
|
|
45
|
+
* place its last layout gave it - so both copies would land there and one would silently go missing.
|
|
46
|
+
*/
|
|
47
|
+
export declare class ElementReusedError extends Error {
|
|
48
|
+
readonly elementName: string;
|
|
49
|
+
constructor(elementName: string);
|
|
50
|
+
}
|
|
13
51
|
export declare class RendererRegistry {
|
|
14
52
|
private static renderers;
|
|
15
|
-
static
|
|
16
|
-
static
|
|
53
|
+
private static drawnOnPage;
|
|
54
|
+
static register(elementClass: Function, renderer: ElementRenderer): void;
|
|
55
|
+
static getRenderer(element: object): ElementRenderer;
|
|
56
|
+
/** A new page starts for this document: nothing is drawn on it yet. */
|
|
57
|
+
static beginPage(objectManager: PDFObjectManager): void;
|
|
58
|
+
/**
|
|
59
|
+
* Draws `element` with its renderer. Every draw goes through here, so it is where reuse is caught - for
|
|
60
|
+
* an element that DRAWS something: one that draws nothing (a Spacer) has no place to land wrongly, and
|
|
61
|
+
* reusing it stays harmless.
|
|
62
|
+
*/
|
|
63
|
+
static render(element: object, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
|
|
17
64
|
static isRendererAsync(renderer: Function): boolean;
|
|
18
65
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** The scope for the children of an element at `offset` (relative to `scope.origin`). */
|
|
2
|
+
export function within(scope, offset) {
|
|
3
|
+
return {
|
|
4
|
+
...scope,
|
|
5
|
+
origin: { x: scope.origin.x + (offset.x ?? 0), y: scope.origin.y + (offset.y ?? 0) },
|
|
6
|
+
};
|
|
7
|
+
}
|
|
1
8
|
/**
|
|
2
9
|
* Thrown when an element reaches the render pass with no registered renderer.
|
|
3
10
|
*
|
|
@@ -15,6 +22,18 @@ export class MissingRendererError extends Error {
|
|
|
15
22
|
this.elementName = elementName;
|
|
16
23
|
}
|
|
17
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Thrown when one element INSTANCE is drawn twice on the same page. An element holds one position - the
|
|
27
|
+
* place its last layout gave it - so both copies would land there and one would silently go missing.
|
|
28
|
+
*/
|
|
29
|
+
export class ElementReusedError extends Error {
|
|
30
|
+
constructor(elementName) {
|
|
31
|
+
super(`@jasy/pdf: the same ${elementName} element is used twice on one page. An element has ONE ` +
|
|
32
|
+
`position, so both copies would be drawn in the same place. Create it twice instead - call the ` +
|
|
33
|
+
`factory again (e.g. a function \`const divider = () => Divider()\`) or place \`element.clone()\`.`);
|
|
34
|
+
this.elementName = elementName;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
18
37
|
export class RendererRegistry {
|
|
19
38
|
static register(elementClass, renderer) {
|
|
20
39
|
if (!RendererRegistry.renderers.has(elementClass)) {
|
|
@@ -29,8 +48,29 @@ export class RendererRegistry {
|
|
|
29
48
|
throw new MissingRendererError(element.constructor.name);
|
|
30
49
|
return renderer;
|
|
31
50
|
}
|
|
51
|
+
/** A new page starts for this document: nothing is drawn on it yet. */
|
|
52
|
+
static beginPage(objectManager) {
|
|
53
|
+
RendererRegistry.drawnOnPage.set(objectManager, new WeakSet());
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Draws `element` with its renderer. Every draw goes through here, so it is where reuse is caught - for
|
|
57
|
+
* an element that DRAWS something: one that draws nothing (a Spacer) has no place to land wrongly, and
|
|
58
|
+
* reusing it stays harmless.
|
|
59
|
+
*/
|
|
60
|
+
static async render(element, objectManager, scope) {
|
|
61
|
+
const nodes = await RendererRegistry.getRenderer(element)(element, objectManager, scope);
|
|
62
|
+
const drawn = RendererRegistry.drawnOnPage.get(objectManager);
|
|
63
|
+
if (drawn && nodes.length > 0) {
|
|
64
|
+
if (drawn.has(element))
|
|
65
|
+
throw new ElementReusedError(element.constructor.name);
|
|
66
|
+
drawn.add(element);
|
|
67
|
+
}
|
|
68
|
+
return nodes;
|
|
69
|
+
}
|
|
32
70
|
static isRendererAsync(renderer) {
|
|
33
71
|
return renderer.constructor.name === "AsyncFunction";
|
|
34
72
|
}
|
|
35
73
|
}
|
|
36
74
|
RendererRegistry.renderers = new Map();
|
|
75
|
+
// What each document has drawn on its current page (keyed per document: two can render at once).
|
|
76
|
+
RendererRegistry.drawnOnPage = new WeakMap();
|
|
@@ -24,18 +24,42 @@ export declare class StructTree {
|
|
|
24
24
|
private byStructId;
|
|
25
25
|
private stack;
|
|
26
26
|
private pageObjByStructParents;
|
|
27
|
+
private annotParents;
|
|
28
|
+
private carried;
|
|
27
29
|
/** Register a structure element for a logical element (keyed by its stable `structId`, so a paragraph or
|
|
28
30
|
* table split across pages resolves to the SAME element - MCIDs then accrue from every page). Its parent
|
|
29
31
|
* is the currently-open container, else the Document. Containers also push()/pop() around their children. */
|
|
30
32
|
openElement(structId: number, role: string, opts?: {
|
|
31
33
|
alt?: string;
|
|
32
34
|
}): number;
|
|
35
|
+
/**
|
|
36
|
+
* An element no layout element stands behind - an inline link inside a paragraph: a child of `parent`,
|
|
37
|
+
* new every time (nothing to deduplicate).
|
|
38
|
+
*/
|
|
39
|
+
addElement(role: string, parent: number, opts?: {
|
|
40
|
+
alt?: string;
|
|
41
|
+
}): number;
|
|
42
|
+
/** A piece of paragraph `owner` ended inside link element `key`: its next piece may continue it. */
|
|
43
|
+
carryLink(owner: number, target: string, key: number): void;
|
|
44
|
+
/** The link the previous piece of `owner` ended in. Taken by the next piece, whether it continues it. */
|
|
45
|
+
takeCarriedLink(owner: number): {
|
|
46
|
+
target: string;
|
|
47
|
+
key: number;
|
|
48
|
+
} | undefined;
|
|
33
49
|
push(key: number): void;
|
|
34
50
|
pop(): void;
|
|
35
51
|
/** Begin a page: allocates its StructParents index (its slot in the ParentTree). */
|
|
36
52
|
beginPage(): PageStructContext;
|
|
37
53
|
/** Tie a page's StructParents index to its now-known page object number (for /Pg references). */
|
|
38
54
|
setPageObject(structParents: number, pageObjNum: number): void;
|
|
55
|
+
/**
|
|
56
|
+
* PDF/UA 7.18: an annotation belongs to a structure element (a link to its `/Link`, a widget to its
|
|
57
|
+
* `/Form`). Returns the `/StructParent` the annotation dictionary carries - its own slot in the
|
|
58
|
+
* ParentTree, numbered alongside the pages'.
|
|
59
|
+
*/
|
|
60
|
+
annotationParent(key: number): number;
|
|
61
|
+
/** The annotation object, once written: an `/OBJR` kid of its element, on the page that lists it. */
|
|
62
|
+
linkAnnotation(key: number, obj: number, pageStructParents: number): void;
|
|
39
63
|
/** Link a page-local MCID to an element (called by the backend while serializing). */
|
|
40
64
|
linkMarkedContent(key: number, structParents: number, mcid: number): void;
|
|
41
65
|
/** Emit the struct-tree objects and return the catalog additions (`/MarkInfo`, `/StructTreeRoot`, `/Lang`).
|
|
@@ -32,6 +32,9 @@ export class StructTree {
|
|
|
32
32
|
this.byStructId = new Map(); // element structId -> element key (dedup across pages)
|
|
33
33
|
this.stack = []; // open containers; top is the current parent
|
|
34
34
|
this.pageObjByStructParents = new Map();
|
|
35
|
+
this.annotParents = new Map(); // an annotation's /StructParent -> its element key
|
|
36
|
+
// An inline link a page break cut: paragraph structId -> the link it ended in, for its next piece.
|
|
37
|
+
this.carried = new Map();
|
|
35
38
|
}
|
|
36
39
|
/** Register a structure element for a logical element (keyed by its stable `structId`, so a paragraph or
|
|
37
40
|
* table split across pages resolves to the SAME element - MCIDs then accrue from every page). Its parent
|
|
@@ -48,9 +51,29 @@ export class StructTree {
|
|
|
48
51
|
alt: opts?.alt,
|
|
49
52
|
parent: this.stack[this.stack.length - 1],
|
|
50
53
|
mc: [],
|
|
54
|
+
annots: [],
|
|
51
55
|
});
|
|
52
56
|
return key;
|
|
53
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* An element no layout element stands behind - an inline link inside a paragraph: a child of `parent`,
|
|
60
|
+
* new every time (nothing to deduplicate).
|
|
61
|
+
*/
|
|
62
|
+
addElement(role, parent, opts) {
|
|
63
|
+
const key = this.counter++;
|
|
64
|
+
this.elems.set(key, { key, role, alt: opts?.alt, parent, mc: [], annots: [] });
|
|
65
|
+
return key;
|
|
66
|
+
}
|
|
67
|
+
/** A piece of paragraph `owner` ended inside link element `key`: its next piece may continue it. */
|
|
68
|
+
carryLink(owner, target, key) {
|
|
69
|
+
this.carried.set(owner, { target, key });
|
|
70
|
+
}
|
|
71
|
+
/** The link the previous piece of `owner` ended in. Taken by the next piece, whether it continues it. */
|
|
72
|
+
takeCarriedLink(owner) {
|
|
73
|
+
const carried = this.carried.get(owner);
|
|
74
|
+
this.carried.delete(owner);
|
|
75
|
+
return carried;
|
|
76
|
+
}
|
|
54
77
|
push(key) {
|
|
55
78
|
this.stack.push(key);
|
|
56
79
|
}
|
|
@@ -65,6 +88,20 @@ export class StructTree {
|
|
|
65
88
|
setPageObject(structParents, pageObjNum) {
|
|
66
89
|
this.pageObjByStructParents.set(structParents, pageObjNum);
|
|
67
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* PDF/UA 7.18: an annotation belongs to a structure element (a link to its `/Link`, a widget to its
|
|
93
|
+
* `/Form`). Returns the `/StructParent` the annotation dictionary carries - its own slot in the
|
|
94
|
+
* ParentTree, numbered alongside the pages'.
|
|
95
|
+
*/
|
|
96
|
+
annotationParent(key) {
|
|
97
|
+
const structParent = this.structParentsCounter++;
|
|
98
|
+
this.annotParents.set(structParent, key);
|
|
99
|
+
return structParent;
|
|
100
|
+
}
|
|
101
|
+
/** The annotation object, once written: an `/OBJR` kid of its element, on the page that lists it. */
|
|
102
|
+
linkAnnotation(key, obj, pageStructParents) {
|
|
103
|
+
this.elems.get(key)?.annots.push({ obj, structParents: pageStructParents });
|
|
104
|
+
}
|
|
68
105
|
/** Link a page-local MCID to an element (called by the backend while serializing). */
|
|
69
106
|
linkMarkedContent(key, structParents, mcid) {
|
|
70
107
|
this.elems.get(key)?.mc.push({ structParents, mcid });
|
|
@@ -83,11 +120,24 @@ export class StructTree {
|
|
|
83
120
|
objNum.set(key, om.addObject(""));
|
|
84
121
|
const pg = (structParents) => this.pageObjByStructParents.get(structParents);
|
|
85
122
|
const childrenOf = (parent) => [...this.elems.values()].filter((e) => e.parent === parent); // Map order = reading order
|
|
123
|
+
const before = (a, b) => a.structParents - b.structParents || a.mcid - b.mcid;
|
|
124
|
+
const firstOf = (e) => [...e.mc, ...childrenOf(e.key).flatMap((c) => firstOf(c) ?? [])].sort(before)[0];
|
|
125
|
+
const mcr = (m) => `<< /Type /MCR /Pg ${pg(m.structParents)} 0 R /MCID ${m.mcid} >>`;
|
|
86
126
|
// One StructElem per registered element: /K = its child elements ++ its marked-content references.
|
|
127
|
+
// An element holding BOTH (a paragraph with an inline link) interleaves them in reading order.
|
|
87
128
|
for (const e of this.elems.values()) {
|
|
129
|
+
const children = childrenOf(e.key);
|
|
130
|
+
const content = children.length > 0 && e.mc.length > 0
|
|
131
|
+
? [
|
|
132
|
+
...children.map((c) => ({ at: firstOf(c), ref: `${objNum.get(c.key)} 0 R` })),
|
|
133
|
+
...e.mc.map((m) => ({ at: m, ref: mcr(m) })),
|
|
134
|
+
]
|
|
135
|
+
.sort((a, b) => (a.at && b.at ? before(a.at, b.at) : 0))
|
|
136
|
+
.map((k) => k.ref)
|
|
137
|
+
: [...children.map((c) => `${objNum.get(c.key)} 0 R`), ...e.mc.map(mcr)];
|
|
88
138
|
const kids = [
|
|
89
|
-
...
|
|
90
|
-
...e.
|
|
139
|
+
...content,
|
|
140
|
+
...e.annots.map((a) => `<< /Type /OBJR /Pg ${pg(a.structParents)} 0 R /Obj ${a.obj} 0 R >>`),
|
|
91
141
|
].join(" ");
|
|
92
142
|
const alt = e.alt ? ` /Alt ${om.pdfString(e.alt)}` : "";
|
|
93
143
|
// PDF/UA 7.5: a header cell needs a Scope so a reader can associate data cells with it. Our headers
|
|
@@ -110,17 +160,19 @@ export class StructTree {
|
|
|
110
160
|
byPage.set(m.structParents, list);
|
|
111
161
|
}
|
|
112
162
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const refs =
|
|
117
|
-
.get(sp)
|
|
163
|
+
// A page's entry is an array indexed by MCID; an annotation's is its element itself.
|
|
164
|
+
const entries = new Map();
|
|
165
|
+
for (const [sp, list] of byPage) {
|
|
166
|
+
const refs = list
|
|
118
167
|
.slice()
|
|
119
168
|
.sort((a, b) => a.mcid - b.mcid)
|
|
120
169
|
.map((r) => `${objNum.get(r.key)} 0 R`)
|
|
121
170
|
.join(" ");
|
|
122
|
-
|
|
123
|
-
}
|
|
171
|
+
entries.set(sp, `[ ${refs} ]`);
|
|
172
|
+
}
|
|
173
|
+
for (const [sp, key] of this.annotParents)
|
|
174
|
+
entries.set(sp, `${objNum.get(key)} 0 R`);
|
|
175
|
+
const nums = [...entries.keys()].sort((a, b) => a - b).map((sp) => `${sp} ${entries.get(sp)}`);
|
|
124
176
|
om.replaceObject(parentTreeNum, `<< /Nums [ ${nums.join(" ")} ] >>`);
|
|
125
177
|
om.replaceObject(rootNum, `<< /Type /StructTreeRoot /K ${docNum} 0 R /ParentTree ${parentTreeNum} 0 R ` +
|
|
126
178
|
`/ParentTreeNextKey ${this.structParentsCounter} >>`);
|
|
@@ -51,6 +51,10 @@ export interface ColorGlyphLayer {
|
|
|
51
51
|
}
|
|
52
52
|
/** Sorts x-intervals and unions the overlapping ones. */
|
|
53
53
|
export declare function mergeSpans(spans: Array<[number, number]>): Array<[number, number]>;
|
|
54
|
+
/** Thrown when font bytes are a format jasy cannot embed - said by name, never embedded broken. */
|
|
55
|
+
export declare class FontFormatError extends Error {
|
|
56
|
+
constructor(message: string);
|
|
57
|
+
}
|
|
54
58
|
export declare class TTFParser {
|
|
55
59
|
private data;
|
|
56
60
|
unitsPerEm: number;
|
package/dist/utils/ttf-parser.js
CHANGED
|
@@ -46,6 +46,12 @@ function applyAffine(m, x, y) {
|
|
|
46
46
|
function detScale(m) {
|
|
47
47
|
return Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
|
|
48
48
|
}
|
|
49
|
+
/** Thrown when font bytes are a format jasy cannot embed - said by name, never embedded broken. */
|
|
50
|
+
export class FontFormatError extends Error {
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super(`@jasy/pdf: ${message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
49
55
|
export class TTFParser {
|
|
50
56
|
constructor(data) {
|
|
51
57
|
this.data = data;
|
|
@@ -88,6 +94,17 @@ export class TTFParser {
|
|
|
88
94
|
// subtables (coverage + classDef) per pair, and a long document repeats the same pairs thousands
|
|
89
95
|
// of times, so the first lookup of a pair pays; the rest are O(1).
|
|
90
96
|
this.kernCache = new Map();
|
|
97
|
+
// Every font path meets here (bytes, a file, a URL, an unwrapped WOFF/WOFF2). A CFF font has the
|
|
98
|
+
// same tables up front and measured fine, then went into the PDF as a TrueType program it is not.
|
|
99
|
+
const tag = String.fromCharCode(...data.subarray(0, 4));
|
|
100
|
+
if (tag === "OTTO") {
|
|
101
|
+
throw new FontFormatError("this is an OpenType font with PostScript (CFF) outlines, which jasy does not embed yet - " +
|
|
102
|
+
"use its TrueType-flavoured version (.ttf, or a .woff/.woff2 made from one)");
|
|
103
|
+
}
|
|
104
|
+
if (tag === "ttcf") {
|
|
105
|
+
throw new FontFormatError("this is a TrueType Collection (.ttc), which jasy does not read yet - extract the one face " +
|
|
106
|
+
"you need into a .ttf");
|
|
107
|
+
}
|
|
91
108
|
this.readTableDirectory();
|
|
92
109
|
const head = this.table("head").offset;
|
|
93
110
|
this.unitsPerEm = u16(this.data, head + 18);
|
|
@@ -132,11 +149,12 @@ export class TTFParser {
|
|
|
132
149
|
glyphCount() {
|
|
133
150
|
return this.numGlyphs;
|
|
134
151
|
}
|
|
135
|
-
// Advance width of every glyph in PDF glyph space (the /W array of the CIDFont)
|
|
152
|
+
// Advance width of every glyph in PDF glyph space (the /W array of the CIDFont), NOT rounded: layout
|
|
153
|
+
// measures the exact width, so the drawn one must be the same number.
|
|
136
154
|
glyphWidths() {
|
|
137
155
|
const out = [];
|
|
138
156
|
for (let g = 0; g < this.numGlyphs; g++) {
|
|
139
|
-
out.push(
|
|
157
|
+
out.push((this.getAdvanceWidth(g) * 1000) / this.unitsPerEm);
|
|
140
158
|
}
|
|
141
159
|
return out;
|
|
142
160
|
}
|
|
@@ -392,17 +410,23 @@ export class TTFParser {
|
|
|
392
410
|
}
|
|
393
411
|
if (kernLookups.size === 0)
|
|
394
412
|
return;
|
|
395
|
-
// The PairPos subtables of those lookups (a lookup of another type in the set is ignored).
|
|
413
|
+
// The PairPos subtables of those lookups (a lookup of another type in the set is ignored). A large
|
|
414
|
+
// font wraps its lookups in Extension subtables (type 9) for their 32-bit offsets - read through.
|
|
396
415
|
const lookupCount = u16(this.data, lookupListOffset);
|
|
397
416
|
for (const idx of kernLookups) {
|
|
398
417
|
if (idx >= lookupCount)
|
|
399
418
|
continue;
|
|
400
419
|
const lookup = lookupListOffset + u16(this.data, lookupListOffset + 2 + idx * 2);
|
|
401
|
-
|
|
402
|
-
|
|
420
|
+
const type = u16(this.data, lookup);
|
|
421
|
+
if (type !== 2 && type !== 9)
|
|
422
|
+
continue; // 2 = pair adjustment, 9 = extension
|
|
403
423
|
const subTableCount = u16(this.data, lookup + 4);
|
|
404
424
|
for (let s = 0; s < subTableCount; s++) {
|
|
405
|
-
|
|
425
|
+
const sub = lookup + u16(this.data, lookup + 6 + s * 2);
|
|
426
|
+
if (type === 2)
|
|
427
|
+
this.gposPairPos.push(sub);
|
|
428
|
+
else if (u16(this.data, sub + 2) === 2)
|
|
429
|
+
this.gposPairPos.push(sub + u32(this.data, sub + 4));
|
|
406
430
|
}
|
|
407
431
|
}
|
|
408
432
|
if (this.gposPairPos.length > 0)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { flexibleOf } from "../layout/flex-item.js";
|
|
1
2
|
import { PDFDocumentElement } from "../elements/pdf-document-element.js";
|
|
2
3
|
import { FlexiblePDFElement, hasChildProp, SizedPDFElement, } from "../elements/pdf-element.js";
|
|
3
4
|
export class Validator {
|
|
@@ -58,7 +59,7 @@ export class Validator {
|
|
|
58
59
|
}
|
|
59
60
|
// Ensure a flexible element does not contain another flexible element
|
|
60
61
|
if (hasChildProp(element)) {
|
|
61
|
-
if (element.child
|
|
62
|
+
if (flexibleOf(element.child) !== undefined) {
|
|
62
63
|
throw new Error(`Flexible element ${element.constructor.name} cannot hold another flexible element`);
|
|
63
64
|
}
|
|
64
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jasy/pdf",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"description": "Declarative, component-based PDF generation in pure TypeScript - Flutter-style components, real AFM font metrics, a hand-rolled PDF writer. No headless browser, no Java.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"declarative",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { SizedElement, WithChildren, SizedPDFElement, LayoutContext } from "../pdf-element.js";
|
|
2
|
-
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
-
interface ContainerElementParams extends SizedElement, WithChildren {
|
|
4
|
-
color?: [number, number, number];
|
|
5
|
-
backgroundColor?: [number, number, number];
|
|
6
|
-
borderWidth?: number;
|
|
7
|
-
}
|
|
8
|
-
export declare class SizedContainerElement extends SizedPDFElement {
|
|
9
|
-
private children;
|
|
10
|
-
constructor({ width, height, children }: ContainerElementParams);
|
|
11
|
-
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
12
|
-
getProps(): ContainerElementParams;
|
|
13
|
-
}
|
|
14
|
-
export {};
|