@jasy/pdf 1.0.0-beta.3 → 1.0.0-beta.4
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/content.d.ts +5 -5
- package/dist/api/content.js +12 -6
- 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/index.d.ts +2 -0
- package/dist/api/index.js +1 -0
- package/dist/api/layout.d.ts +6 -0
- package/dist/api/layout.js +7 -2
- 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 +99 -22
- package/dist/common/diagnostics.d.ts +53 -0
- package/dist/common/diagnostics.js +77 -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 +5 -1
- package/dist/elements/container-element.js +35 -15
- 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 +39 -1
- package/dist/elements/image-element.js +95 -12
- 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/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 +12 -4
- package/dist/elements/layout/deferred-element.js +24 -7
- package/dist/elements/layout/expanded-element.d.ts +7 -0
- package/dist/elements/layout/expanded-element.js +40 -18
- package/dist/elements/layout/keep-together-element.d.ts +8 -18
- package/dist/elements/layout/keep-together-element.js +21 -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 +15 -1
- package/dist/elements/layout/padding-element.js +55 -27
- 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 +11 -0
- package/dist/elements/layout/positioned-element.d.ts +14 -2
- package/dist/elements/layout/positioned-element.js +36 -10
- package/dist/elements/layout/proxy-element.d.ts +43 -0
- package/dist/elements/layout/proxy-element.js +94 -0
- package/dist/elements/layout/repeating-header-element.d.ts +6 -0
- package/dist/elements/layout/repeating-header-element.js +40 -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 +7 -0
- package/dist/elements/layout/rotated-element.js +25 -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 +75 -15
- package/dist/elements/pdf-element.js +68 -18
- package/dist/elements/rectangle-element.d.ts +14 -0
- package/dist/elements/rectangle-element.js +97 -52
- package/dist/elements/row-element.d.ts +11 -0
- package/dist/elements/row-element.js +58 -8
- package/dist/elements/svg-element.d.ts +30 -6
- package/dist/elements/svg-element.js +88 -17
- package/dist/elements/text-element.d.ts +32 -13
- package/dist/elements/text-element.js +119 -97
- 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 +12 -2
- package/dist/layout/box-constraints.d.ts +5 -0
- package/dist/layout/box-constraints.js +14 -0
- package/dist/layout/collect-images.d.ts +6 -4
- package/dist/layout/collect-images.js +6 -4
- package/dist/layout/flex-item.d.ts +9 -0
- package/dist/layout/flex-item.js +39 -0
- package/dist/layout/fragmentation.d.ts +12 -4
- package/dist/layout/fragmentation.js +37 -14
- package/dist/layout/layout-cache.d.ts +53 -0
- package/dist/layout/layout-cache.js +107 -0
- 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/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/expanded-renderer.d.ts +2 -1
- package/dist/renderer/expanded-renderer.js +2 -3
- 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 +5 -2
- 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 +28 -7
- package/dist/renderer/pdf-backend.d.ts +4 -3
- package/dist/renderer/pdf-backend.js +5 -5
- package/dist/renderer/pdf-document-renderer.d.ts +1 -8
- package/dist/renderer/pdf-document-renderer.js +15 -16
- package/dist/renderer/pdf-renderer.js +11 -11
- package/dist/renderer/positioned-renderer.d.ts +6 -1
- package/dist/renderer/positioned-renderer.js +6 -5
- package/dist/renderer/rectangle-renderer.d.ts +2 -1
- package/dist/renderer/rectangle-renderer.js +9 -4
- 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 +8 -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 +101 -111
- package/dist/svg/gradients.js +5 -0
- package/dist/svg/index.js +10 -1
- package/dist/svg/style.d.ts +2 -0
- package/dist/svg/style.js +11 -0
- 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 +34 -31
- package/dist/text/line-breaker.js +364 -176
- package/dist/text/line-metrics.d.ts +6 -3
- package/dist/text/line-metrics.js +7 -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 +34 -8
- 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 +13 -13
- package/dist/utils/flex-layout.js +318 -216
- package/dist/utils/font-metrics.d.ts +4 -3
- package/dist/utils/image-helper.js +2 -1
- package/dist/utils/pdf-object-manager.d.ts +11 -3
- package/dist/utils/pdf-object-manager.js +39 -13
- package/dist/utils/renderer-registry.d.ts +43 -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 +27 -4
- 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
|
@@ -12,6 +12,7 @@ import { DestRegistry } from "./dest-registry.js";
|
|
|
12
12
|
import { AcroFormCollector } from "../forms/acroform.js";
|
|
13
13
|
import type { PDFConfig } from "../renderer/pdf-document-class.js";
|
|
14
14
|
import type { FontMetrics } from "./font-metrics.js";
|
|
15
|
+
import { Diagnostics } from "../common/diagnostics.js";
|
|
15
16
|
interface FontIndexes {
|
|
16
17
|
fontIndex: number;
|
|
17
18
|
resourceIndex: number;
|
|
@@ -44,8 +45,8 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
44
45
|
/** Shaped runs, cached face -> feature set -> text. Nested, not one map under a composed key: that
|
|
45
46
|
* key would carry the whole run's text, so every lookup would copy it into a fresh string. */
|
|
46
47
|
private shapeCache;
|
|
47
|
-
/** Bumped
|
|
48
|
-
* family name can mean a different face afterwards. */
|
|
48
|
+
/** Bumped when a face is really ADDED (a repeat registration returns early, untouched); every
|
|
49
|
+
* memoised measurement watches it, since a family name can mean a different face afterwards. */
|
|
49
50
|
fontEpoch: number;
|
|
50
51
|
private shapedOrigins;
|
|
51
52
|
private attachments;
|
|
@@ -78,7 +79,9 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
78
79
|
* character went missing instead of a server log nobody reads.
|
|
79
80
|
*/
|
|
80
81
|
private readonly missingGlyphs;
|
|
81
|
-
reportMissingGlyph(codePoints: number[]): void;
|
|
82
|
+
reportMissingGlyph(codePoints: number[], about?: object, page?: number): void;
|
|
83
|
+
/** The render's diagnostics channel (`onDiagnostic`); see `common/diagnostics.ts`. */
|
|
84
|
+
readonly diagnostics: Diagnostics;
|
|
82
85
|
/** The dropped code points, in the order first seen. Empty for a document that hit none. */
|
|
83
86
|
getMissingGlyphs(): number[];
|
|
84
87
|
setOverflowPolicy(policy: OverflowPolicy): void;
|
|
@@ -97,6 +100,11 @@ export declare class PDFObjectManager implements FontMetrics {
|
|
|
97
100
|
* A string INSIDE a content stream needs none of this - the stream is enciphered as a whole.
|
|
98
101
|
*/
|
|
99
102
|
pdfString(text: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* A URI: 7-bit ASCII by definition, so anything else is percent-encoded as UTF-8 - the way a browser
|
|
105
|
+
* writes the link - rather than squeezed into a text encoding.
|
|
106
|
+
*/
|
|
107
|
+
pdfUri(uri: string): string;
|
|
100
108
|
private streamPayload;
|
|
101
109
|
private stream;
|
|
102
110
|
addContentStream(content: string): number;
|
|
@@ -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,7 @@ 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";
|
|
37
39
|
export var FontStyle;
|
|
38
40
|
(function (FontStyle) {
|
|
39
41
|
FontStyle["Normal"] = "normal";
|
|
@@ -184,8 +186,8 @@ export class PDFObjectManager {
|
|
|
184
186
|
/** Shaped runs, cached face -> feature set -> text. Nested, not one map under a composed key: that
|
|
185
187
|
* key would carry the whole run's text, so every lookup would copy it into a fresh string. */
|
|
186
188
|
this.shapeCache = new Map();
|
|
187
|
-
/** Bumped
|
|
188
|
-
* family name can mean a different face afterwards. */
|
|
189
|
+
/** Bumped when a face is really ADDED (a repeat registration returns early, untouched); every
|
|
190
|
+
* memoised measurement watches it, since a family name can mean a different face afterwards. */
|
|
189
191
|
this.fontEpoch = 0;
|
|
190
192
|
// Per font, glyph -> the code points it stands for. A shaped glyph is often absent from the cmap (a
|
|
191
193
|
// ligature) or maps back to a presentation form, so `ToUnicode` cannot come from the cmap alone.
|
|
@@ -220,10 +222,11 @@ export class PDFObjectManager {
|
|
|
220
222
|
* character went missing instead of a server log nobody reads.
|
|
221
223
|
*/
|
|
222
224
|
this.missingGlyphs = new Set();
|
|
225
|
+
/** The render's diagnostics channel (`onDiagnostic`); see `common/diagnostics.ts`. */
|
|
226
|
+
this.diagnostics = new Diagnostics();
|
|
223
227
|
// 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.
|
|
228
|
+
// and drawing (the backend emits a `TJ`) both consult it, so they can never disagree. The default
|
|
229
|
+
// here is off; `renderPdf` turns it on unless the document says `kerning: false`.
|
|
227
230
|
this.kerningEnabled = false;
|
|
228
231
|
if (pageSize)
|
|
229
232
|
this.pageFormat = pageFormats[pageSize];
|
|
@@ -247,9 +250,18 @@ export class PDFObjectManager {
|
|
|
247
250
|
setCompress(on) {
|
|
248
251
|
this.compress = on;
|
|
249
252
|
}
|
|
250
|
-
reportMissingGlyph(codePoints) {
|
|
253
|
+
reportMissingGlyph(codePoints, about, page) {
|
|
251
254
|
for (const cp of codePoints)
|
|
252
255
|
this.missingGlyphs.add(cp);
|
|
256
|
+
const chars = codePoints.map((cp) => String.fromCodePoint(cp)).join("");
|
|
257
|
+
this.diagnostics.report({
|
|
258
|
+
code: "missing-glyphs",
|
|
259
|
+
severity: "warning",
|
|
260
|
+
message: `No font can draw ${JSON.stringify(chars)}; removed (PDF/A forbids .notdef).`,
|
|
261
|
+
about,
|
|
262
|
+
page,
|
|
263
|
+
fallback: "silent",
|
|
264
|
+
});
|
|
253
265
|
}
|
|
254
266
|
/** The dropped code points, in the order first seen. Empty for a document that hit none. */
|
|
255
267
|
getMissingGlyphs() {
|
|
@@ -263,7 +275,7 @@ export class PDFObjectManager {
|
|
|
263
275
|
}
|
|
264
276
|
// A unique, binary-safe placeholder for a stream's bytes, swapped for ciphertext in finalizeEncryption().
|
|
265
277
|
encToken(id) {
|
|
266
|
-
return
|
|
278
|
+
return `\x00\x01JENC:${id}\x01\x00`;
|
|
267
279
|
}
|
|
268
280
|
// The same trick for a literal STRING. A separate marker because the two are swapped differently: a
|
|
269
281
|
// stream keeps its delimiters and only the payload changes, a string is replaced whole by a hex one.
|
|
@@ -282,15 +294,28 @@ export class PDFObjectManager {
|
|
|
282
294
|
* A string INSIDE a content stream needs none of this - the stream is enciphered as a whole.
|
|
283
295
|
*/
|
|
284
296
|
pdfString(text) {
|
|
297
|
+
// A TEXT string: PDFDocEncoding where it can say everything, else UTF-16BE (text/pdf-text-string.ts).
|
|
298
|
+
const bytes = encodePdfText(text);
|
|
285
299
|
if (!this.security) {
|
|
286
|
-
|
|
300
|
+
// Plain Latin-1 comes out of the Windows-1252 page encoder as the same bytes PDFDocEncoding reads
|
|
301
|
+
// back, so it stays a readable literal; anything else is written as hex, past that encoder.
|
|
302
|
+
if (isPlainPdfText(text)) {
|
|
303
|
+
return `(${text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)")})`;
|
|
304
|
+
}
|
|
305
|
+
return `<${[...bytes].map((b) => b.toString(16).padStart(2, "0")).join("")}>`;
|
|
287
306
|
}
|
|
288
307
|
// Encrypt the bytes the unencrypted path would have written, so a viewer decrypts to the same value.
|
|
289
308
|
// 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
309
|
const id = this.strJobs.push(bytes) - 1;
|
|
292
310
|
return this.strToken(id);
|
|
293
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* A URI: 7-bit ASCII by definition, so anything else is percent-encoded as UTF-8 - the way a browser
|
|
314
|
+
* writes the link - rather than squeezed into a text encoding.
|
|
315
|
+
*/
|
|
316
|
+
pdfUri(uri) {
|
|
317
|
+
return this.pdfString(uri.replace(/[^\x20-\x7e]/gu, (c) => encodeURIComponent(c)));
|
|
318
|
+
}
|
|
294
319
|
// The single encryption choke-point every stream emitter routes through (stream(), registerImage). Without
|
|
295
320
|
// a security handler: the bytes as a latin1 string + their length. With one: register the raw bytes for the
|
|
296
321
|
// finalize pass and return a placeholder + the pre-computable encrypted length (16-byte IV + PKCS#7 padding).
|
|
@@ -473,7 +498,7 @@ endstream`;
|
|
|
473
498
|
async finalizeEncryption() {
|
|
474
499
|
if (!this.security)
|
|
475
500
|
return;
|
|
476
|
-
if (this.outputIntent !== undefined
|
|
501
|
+
if (this.outputIntent !== undefined) {
|
|
477
502
|
throw new Error("@jasy/pdf: cannot encrypt a PDF/A document - PDF/A forbids encryption.");
|
|
478
503
|
}
|
|
479
504
|
for (let id = 0; id < this.encJobs.length; id++) {
|
|
@@ -565,10 +590,10 @@ endstream`;
|
|
|
565
590
|
}
|
|
566
591
|
// Registers a font
|
|
567
592
|
registerFont(fontName, fontStyle = FontStyle.Normal, fullName = fontName) {
|
|
568
|
-
this.fontEpoch++;
|
|
569
593
|
if (this.fonts.hasFont(fontName, fontStyle)) {
|
|
570
594
|
return this.fonts.getFont(fontName, fontStyle); // Already exists? Return it!
|
|
571
595
|
}
|
|
596
|
+
this.fontEpoch++;
|
|
572
597
|
this.verticalsCache.delete(fontName);
|
|
573
598
|
this.decorationCache.delete(fontName);
|
|
574
599
|
const data = STANDARD_AFM[fullName];
|
|
@@ -596,7 +621,6 @@ endstream`;
|
|
|
596
621
|
// metrics and emits its PDF font objects. All variants share the family `name`; bold/italic are
|
|
597
622
|
// separate .ttf files registered under the same name with a different style.
|
|
598
623
|
registerCustomFont(name, data, style = FontStyle.Normal) {
|
|
599
|
-
this.fontEpoch++;
|
|
600
624
|
let byStyle = this.customFonts.get(name);
|
|
601
625
|
if (!byStyle) {
|
|
602
626
|
byStyle = new Map();
|
|
@@ -616,6 +640,7 @@ endstream`;
|
|
|
616
640
|
"is loaded asynchronously, so it happens one step earlier in renderPdf.");
|
|
617
641
|
}
|
|
618
642
|
byStyle.set(style, new TTFParser(isWoff(data) ? woffToSfnt(data) : data));
|
|
643
|
+
this.fontEpoch++; // only once the face is really there: a font that fails to parse adds nothing
|
|
619
644
|
this.verticalsCache.delete(name); // this family now answers from the .ttf, not from an AFM
|
|
620
645
|
this.decorationCache.delete(name);
|
|
621
646
|
// Emission (the PDF font objects + page resource) is DEFERRED to first use via ensureEmitted(),
|
|
@@ -1004,7 +1029,8 @@ endstream`;
|
|
|
1004
1029
|
for (let i = 0; i < chars.length - 1; i++) {
|
|
1005
1030
|
const a = chars[i];
|
|
1006
1031
|
const b = chars[i + 1];
|
|
1007
|
-
|
|
1032
|
+
// A no-break space is a space: never kerned (the AFM names it `space`, with pairs of its own).
|
|
1033
|
+
if (a === " " || b === " " || a === "\u00a0" || b === "\u00a0") {
|
|
1008
1034
|
out.push(0);
|
|
1009
1035
|
}
|
|
1010
1036
|
else if (ttf) {
|
|
@@ -1,3 +1,27 @@
|
|
|
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
|
+
}
|
|
15
|
+
/** The scope for the children of an element at `offset` (relative to `scope.origin`). */
|
|
16
|
+
export declare function within(scope: RenderScope, offset: {
|
|
17
|
+
x?: number;
|
|
18
|
+
y?: number;
|
|
19
|
+
}): RenderScope;
|
|
20
|
+
/**
|
|
21
|
+
* Renders one element to IR. Typed so that a call which forgets to pass the scope on does not
|
|
22
|
+
* compile - a forgotten origin draws at the page corner without a word.
|
|
23
|
+
*/
|
|
24
|
+
export type ElementRenderer = (element: any, objectManager: PDFObjectManager, scope: RenderScope) => Promise<IRNode[]>;
|
|
1
25
|
/**
|
|
2
26
|
* Thrown when an element reaches the render pass with no registered renderer.
|
|
3
27
|
*
|
|
@@ -10,9 +34,26 @@ export declare class MissingRendererError extends Error {
|
|
|
10
34
|
readonly elementName: string;
|
|
11
35
|
constructor(elementName: string);
|
|
12
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Thrown when one element INSTANCE is drawn twice on the same page. An element holds one position - the
|
|
39
|
+
* place its last layout gave it - so both copies would land there and one would silently go missing.
|
|
40
|
+
*/
|
|
41
|
+
export declare class ElementReusedError extends Error {
|
|
42
|
+
readonly elementName: string;
|
|
43
|
+
constructor(elementName: string);
|
|
44
|
+
}
|
|
13
45
|
export declare class RendererRegistry {
|
|
14
46
|
private static renderers;
|
|
15
|
-
static
|
|
16
|
-
static
|
|
47
|
+
private static drawnOnPage;
|
|
48
|
+
static register(elementClass: Function, renderer: ElementRenderer): void;
|
|
49
|
+
static getRenderer(element: object): ElementRenderer;
|
|
50
|
+
/** A new page starts for this document: nothing is drawn on it yet. */
|
|
51
|
+
static beginPage(objectManager: PDFObjectManager): void;
|
|
52
|
+
/**
|
|
53
|
+
* Draws `element` with its renderer. Every draw goes through here, so it is where reuse is caught - for
|
|
54
|
+
* an element that DRAWS something: one that draws nothing (a Spacer) has no place to land wrongly, and
|
|
55
|
+
* reusing it stays harmless.
|
|
56
|
+
*/
|
|
57
|
+
static render(element: object, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
|
|
17
58
|
static isRendererAsync(renderer: Function): boolean;
|
|
18
59
|
}
|
|
@@ -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()\`) rather than reusing one value.`);
|
|
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);
|
|
@@ -392,17 +409,23 @@ export class TTFParser {
|
|
|
392
409
|
}
|
|
393
410
|
if (kernLookups.size === 0)
|
|
394
411
|
return;
|
|
395
|
-
// The PairPos subtables of those lookups (a lookup of another type in the set is ignored).
|
|
412
|
+
// The PairPos subtables of those lookups (a lookup of another type in the set is ignored). A large
|
|
413
|
+
// font wraps its lookups in Extension subtables (type 9) for their 32-bit offsets - read through.
|
|
396
414
|
const lookupCount = u16(this.data, lookupListOffset);
|
|
397
415
|
for (const idx of kernLookups) {
|
|
398
416
|
if (idx >= lookupCount)
|
|
399
417
|
continue;
|
|
400
418
|
const lookup = lookupListOffset + u16(this.data, lookupListOffset + 2 + idx * 2);
|
|
401
|
-
|
|
402
|
-
|
|
419
|
+
const type = u16(this.data, lookup);
|
|
420
|
+
if (type !== 2 && type !== 9)
|
|
421
|
+
continue; // 2 = pair adjustment, 9 = extension
|
|
403
422
|
const subTableCount = u16(this.data, lookup + 4);
|
|
404
423
|
for (let s = 0; s < subTableCount; s++) {
|
|
405
|
-
|
|
424
|
+
const sub = lookup + u16(this.data, lookup + 6 + s * 2);
|
|
425
|
+
if (type === 2)
|
|
426
|
+
this.gposPairPos.push(sub);
|
|
427
|
+
else if (u16(this.data, sub + 2) === 2)
|
|
428
|
+
this.gposPairPos.push(sub + u32(this.data, sub + 4));
|
|
406
429
|
}
|
|
407
430
|
}
|
|
408
431
|
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.4",
|
|
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 {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { SizedPDFElement, } from "../pdf-element.js";
|
|
2
|
-
import { BoxConstraints } from "../../layout/box-constraints.js";
|
|
3
|
-
export class SizedContainerElement extends SizedPDFElement {
|
|
4
|
-
constructor({ width, height, children }) {
|
|
5
|
-
super({ x: 0, y: 0, width, height });
|
|
6
|
-
this.children = children;
|
|
7
|
-
}
|
|
8
|
-
calculateLayout(constraints, offset, ctx) {
|
|
9
|
-
if (constraints.hasBoundedWidth)
|
|
10
|
-
this.width = constraints.maxWidth;
|
|
11
|
-
if (constraints.hasBoundedHeight)
|
|
12
|
-
this.height = constraints.maxHeight;
|
|
13
|
-
// Absolute placement from the parent; assignment (not +=) so re-layout is idempotent.
|
|
14
|
-
this.x = offset.x;
|
|
15
|
-
this.y = offset.y;
|
|
16
|
-
const width = this.width ?? 0;
|
|
17
|
-
const height = this.height ?? 0;
|
|
18
|
-
if (this.children)
|
|
19
|
-
this.children.forEach((child) => child.calculateLayout(BoxConstraints.loose(width, height), { x: this.x, y: this.y }, ctx));
|
|
20
|
-
// Top-left coordinates; the Y-flip now happens once at the IR -> backend seam.
|
|
21
|
-
return { width, height };
|
|
22
|
-
}
|
|
23
|
-
getProps() {
|
|
24
|
-
return {
|
|
25
|
-
x: this.x,
|
|
26
|
-
y: this.y,
|
|
27
|
-
width: this.width,
|
|
28
|
-
height: this.height,
|
|
29
|
-
children: this.children,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
}
|