@jasy/pdf 1.0.0-alpha.1 → 1.0.0-alpha.10
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/LICENSE +21 -0
- package/README.md +35 -21
- package/dist/api/args.d.ts +1 -1
- package/dist/api/args.js +2 -5
- package/dist/api/color.d.ts +4 -4
- package/dist/api/color.js +11 -17
- package/dist/api/content.d.ts +20 -13
- package/dist/api/content.js +44 -29
- package/dist/api/descriptor.d.ts +2 -2
- package/dist/api/descriptor.js +121 -31
- package/dist/api/dimension.d.ts +76 -0
- package/dist/api/dimension.js +43 -0
- package/dist/api/font-url.d.ts +28 -0
- package/dist/api/font-url.js +134 -0
- package/dist/api/forms.d.ts +347 -0
- package/dist/api/forms.js +189 -0
- package/dist/api/gradient.d.ts +53 -0
- package/dist/api/gradient.js +97 -0
- package/dist/api/index.d.ts +13 -8
- package/dist/api/index.js +13 -24
- package/dist/api/insets.d.ts +19 -12
- package/dist/api/insets.js +19 -12
- package/dist/api/layout.d.ts +149 -25
- package/dist/api/layout.js +182 -51
- package/dist/api/page-builder.d.ts +40 -0
- package/dist/api/page-builder.js +44 -0
- package/dist/api/structure.d.ts +100 -13
- package/dist/api/structure.js +179 -88
- package/dist/api/table.d.ts +5 -5
- package/dist/api/table.js +38 -26
- package/dist/api/text.d.ts +90 -4
- package/dist/api/text.js +116 -31
- package/dist/assets/font-data.d.ts +2 -0
- package/dist/assets/font-data.js +21 -0
- package/dist/assets/font-data.ts +37 -0
- package/dist/common/color.js +1 -5
- package/dist/constants/page-sizes.js +3 -6
- package/dist/constants/pdf-parts.js +1 -4
- package/dist/crypto/legacy-handler.d.ts +43 -0
- package/dist/crypto/legacy-handler.js +145 -0
- package/dist/crypto/security-handler.d.ts +62 -0
- package/dist/crypto/security-handler.js +155 -0
- package/dist/crypto/webcrypto.d.ts +11 -0
- package/dist/crypto/webcrypto.js +62 -0
- package/dist/edit/acroform-reader.d.ts +70 -0
- package/dist/edit/acroform-reader.js +126 -0
- package/dist/edit/appearance.d.ts +47 -0
- package/dist/edit/appearance.js +171 -0
- package/dist/edit/document.d.ts +145 -0
- package/dist/edit/document.js +642 -0
- package/dist/edit/fill.d.ts +65 -0
- package/dist/edit/fill.js +424 -0
- package/dist/edit/flatten.d.ts +54 -0
- package/dist/edit/flatten.js +462 -0
- package/dist/edit/index.d.ts +20 -0
- package/dist/edit/index.js +18 -0
- package/dist/edit/lexer.d.ts +30 -0
- package/dist/edit/lexer.js +285 -0
- package/dist/edit/objects.d.ts +68 -0
- package/dist/edit/objects.js +83 -0
- package/dist/edit/writer.d.ts +60 -0
- package/dist/edit/writer.js +283 -0
- package/dist/elements/container-element.d.ts +50 -5
- package/dist/elements/container-element.js +100 -39
- package/dist/elements/forms/checkbox-element.d.ts +51 -0
- package/dist/elements/forms/checkbox-element.js +48 -0
- package/dist/elements/forms/choice-element.d.ts +53 -0
- package/dist/elements/forms/choice-element.js +59 -0
- package/dist/elements/forms/push-button-element.d.ts +56 -0
- package/dist/elements/forms/push-button-element.js +49 -0
- package/dist/elements/forms/radio-element.d.ts +52 -0
- package/dist/elements/forms/radio-element.js +49 -0
- package/dist/elements/forms/signature-element.d.ts +54 -0
- package/dist/elements/forms/signature-element.js +47 -0
- package/dist/elements/forms/text-field-element.d.ts +61 -0
- package/dist/elements/forms/text-field-element.js +54 -0
- package/dist/elements/image-element.d.ts +58 -6
- package/dist/elements/image-element.js +145 -111
- package/dist/elements/index.d.ts +26 -10
- package/dist/elements/index.js +26 -28
- package/dist/elements/layout/anchor-element.d.ts +23 -0
- package/dist/elements/layout/anchor-element.js +27 -0
- package/dist/elements/layout/bookmark-element.d.ts +27 -0
- package/dist/elements/layout/bookmark-element.js +30 -0
- package/dist/elements/layout/default-text-style-element.d.ts +30 -0
- package/dist/elements/layout/default-text-style-element.js +47 -0
- package/dist/elements/layout/deferred-element.d.ts +3 -3
- package/dist/elements/layout/deferred-element.js +4 -8
- package/dist/elements/layout/expanded-element.d.ts +6 -4
- package/dist/elements/layout/expanded-element.js +22 -17
- package/dist/elements/layout/keep-together-element.d.ts +42 -0
- package/dist/elements/layout/keep-together-element.js +85 -0
- package/dist/elements/layout/link-element.d.ts +33 -0
- package/dist/elements/layout/link-element.js +39 -0
- package/dist/elements/layout/padding-element.d.ts +10 -5
- package/dist/elements/layout/padding-element.js +25 -16
- package/dist/elements/layout/page-break-element.d.ts +17 -0
- package/dist/elements/layout/page-break-element.js +24 -0
- package/dist/elements/layout/page-builder-element.d.ts +32 -0
- package/dist/elements/layout/page-builder-element.js +45 -0
- package/dist/elements/layout/positioned-element.d.ts +47 -0
- package/dist/elements/layout/positioned-element.js +73 -0
- package/dist/elements/layout/repeating-header-element.d.ts +3 -3
- package/dist/elements/layout/repeating-header-element.js +8 -12
- package/dist/elements/layout/rotated-box-element.d.ts +35 -0
- package/dist/elements/layout/rotated-box-element.js +54 -0
- package/dist/elements/layout/rotated-element.d.ts +32 -0
- package/dist/elements/layout/rotated-element.js +41 -0
- package/dist/elements/layout/sized-container-element.d.ts +2 -2
- package/dist/elements/layout/sized-container-element.js +6 -11
- package/dist/elements/layout/struct-group.d.ts +28 -0
- package/dist/elements/layout/struct-group.js +42 -0
- package/dist/elements/line-element.d.ts +3 -3
- package/dist/elements/line-element.js +5 -10
- package/dist/elements/page-element.d.ts +26 -6
- package/dist/elements/page-element.js +72 -27
- package/dist/elements/pdf-document-element.d.ts +10 -4
- package/dist/elements/pdf-document-element.js +11 -10
- package/dist/elements/pdf-element.d.ts +150 -4
- package/dist/elements/pdf-element.js +138 -20
- package/dist/elements/rectangle-element.d.ts +52 -11
- package/dist/elements/rectangle-element.js +129 -36
- package/dist/elements/row-element.d.ts +45 -4
- package/dist/elements/row-element.js +66 -18
- package/dist/elements/text-element.d.ts +132 -11
- package/dist/elements/text-element.js +206 -56
- package/dist/forms/acroform.d.ts +63 -0
- package/dist/forms/acroform.js +427 -0
- package/dist/forms/appearance.d.ts +58 -0
- package/dist/forms/appearance.js +204 -0
- package/dist/forms/field.d.ts +164 -0
- package/dist/forms/field.js +1 -0
- package/dist/forms/pdf.d.ts +19 -0
- package/dist/forms/pdf.js +28 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -19
- package/dist/ir/display-list.d.ts +197 -8
- package/dist/ir/display-list.js +8 -2
- package/dist/layout/alignment.d.ts +10 -0
- package/dist/layout/alignment.js +6 -0
- package/dist/layout/box-constraints.d.ts +61 -0
- package/dist/layout/box-constraints.js +88 -6
- package/dist/layout/collect-images.d.ts +9 -0
- package/dist/layout/collect-images.js +34 -0
- package/dist/layout/fragmentation.d.ts +20 -1
- package/dist/layout/fragmentation.js +74 -18
- package/dist/layout/insets.d.ts +28 -0
- package/dist/layout/insets.js +20 -0
- package/dist/platform/browser-fs.d.ts +2 -0
- package/dist/platform/browser-fs.js +9 -0
- package/dist/platform/browser-image.d.ts +5 -0
- package/dist/platform/browser-image.js +13 -0
- package/dist/platform/node-fs.d.ts +2 -0
- package/dist/platform/node-fs.js +10 -0
- package/dist/platform/node-image.d.ts +5 -0
- package/dist/platform/node-image.js +9 -0
- package/dist/renderer/anchor-renderer.d.ts +6 -0
- package/dist/renderer/anchor-renderer.js +11 -0
- package/dist/renderer/bookmark-renderer.d.ts +6 -0
- package/dist/renderer/bookmark-renderer.js +11 -0
- package/dist/renderer/container-renderer.d.ts +3 -3
- package/dist/renderer/container-renderer.js +11 -28
- package/dist/renderer/default-text-style-renderer.d.ts +6 -0
- package/dist/renderer/default-text-style-renderer.js +10 -0
- package/dist/renderer/deferred-renderer.d.ts +3 -3
- package/dist/renderer/deferred-renderer.js +8 -23
- package/dist/renderer/emoji-image.d.ts +9 -0
- package/dist/renderer/emoji-image.js +68 -0
- package/dist/renderer/expanded-renderer.d.ts +3 -3
- package/dist/renderer/expanded-renderer.js +6 -21
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/image-renderer.d.ts +4 -4
- package/dist/renderer/image-renderer.js +86 -75
- package/dist/renderer/index.d.ts +10 -10
- package/dist/renderer/index.js +10 -26
- package/dist/renderer/keep-together-renderer.d.ts +10 -0
- package/dist/renderer/keep-together-renderer.js +12 -0
- package/dist/renderer/line-renderer.d.ts +3 -3
- package/dist/renderer/line-renderer.js +13 -28
- package/dist/renderer/link-renderer.d.ts +6 -0
- package/dist/renderer/link-renderer.js +11 -0
- package/dist/renderer/padding-renderer.d.ts +3 -3
- package/dist/renderer/padding-renderer.js +6 -21
- package/dist/renderer/page-break-renderer.d.ts +14 -0
- package/dist/renderer/page-break-renderer.js +15 -0
- package/dist/renderer/page-builder-renderer.d.ts +6 -0
- package/dist/renderer/page-builder-renderer.js +11 -0
- package/dist/renderer/page-renderer.d.ts +2 -2
- package/dist/renderer/page-renderer.js +115 -77
- package/dist/renderer/pdf-backend.d.ts +31 -4
- package/dist/renderer/pdf-backend.js +287 -49
- package/dist/renderer/pdf-config.js +4 -7
- package/dist/renderer/pdf-document-class.d.ts +5 -5
- package/dist/renderer/pdf-document-class.js +27 -41
- package/dist/renderer/pdf-document-renderer.d.ts +19 -10
- package/dist/renderer/pdf-document-renderer.js +135 -91
- package/dist/renderer/pdf-renderer.d.ts +2 -2
- package/dist/renderer/pdf-renderer.js +165 -90
- package/dist/renderer/positioned-renderer.d.ts +6 -0
- package/dist/renderer/positioned-renderer.js +10 -0
- package/dist/renderer/rectangle-renderer.d.ts +3 -3
- package/dist/renderer/rectangle-renderer.js +49 -44
- package/dist/renderer/repeating-header-renderer.d.ts +3 -3
- package/dist/renderer/repeating-header-renderer.js +10 -26
- package/dist/renderer/rotated-renderer.d.ts +7 -0
- package/dist/renderer/rotated-renderer.js +29 -0
- package/dist/renderer/row-renderer.d.ts +3 -3
- package/dist/renderer/row-renderer.js +11 -28
- package/dist/renderer/struct-group-renderer.d.ts +10 -0
- package/dist/renderer/struct-group-renderer.js +24 -0
- package/dist/renderer/text-renderer.d.ts +9 -5
- package/dist/renderer/text-renderer.js +437 -89
- package/dist/text/advance.d.ts +34 -0
- package/dist/text/advance.js +45 -0
- package/dist/text/arabic.d.ts +20 -0
- package/dist/text/arabic.js +101 -0
- package/dist/text/bidi.d.ts +33 -0
- package/dist/text/bidi.js +94 -0
- package/dist/text/emoji-codepoints.d.ts +1 -0
- package/dist/text/emoji-codepoints.js +29 -0
- package/dist/text/font-fallback.d.ts +25 -0
- package/dist/text/font-fallback.js +38 -0
- package/dist/text/line-breaker.d.ts +50 -12
- package/dist/text/line-breaker.js +158 -37
- package/dist/text/line-metrics.d.ts +49 -0
- package/dist/text/line-metrics.js +57 -0
- package/dist/text/orphans-widows.d.ts +27 -0
- package/dist/text/orphans-widows.js +53 -0
- package/dist/text/shape.d.ts +29 -0
- package/dist/text/shape.js +67 -0
- package/dist/text/text-decoration.d.ts +50 -0
- package/dist/text/text-decoration.js +62 -0
- package/dist/text/text-style.d.ts +60 -0
- package/dist/text/text-style.js +79 -0
- package/dist/utils/afm-parser.d.ts +34 -0
- package/dist/utils/afm-parser.js +76 -17
- package/dist/utils/bytes.d.ts +24 -0
- package/dist/utils/bytes.js +76 -0
- package/dist/utils/dest-registry.d.ts +22 -0
- package/dist/utils/dest-registry.js +32 -0
- package/dist/utils/flex-layout.d.ts +36 -9
- package/dist/utils/flex-layout.js +207 -30
- package/dist/utils/font-metrics.d.ts +20 -1
- package/dist/utils/font-metrics.js +1 -2
- package/dist/utils/font-path.js +3 -6
- package/dist/utils/gsub.d.ts +36 -0
- package/dist/utils/gsub.js +178 -0
- package/dist/utils/image-helper.d.ts +6 -5
- package/dist/utils/image-helper.js +101 -111
- package/dist/utils/inflate.d.ts +16 -0
- package/dist/utils/inflate.js +52 -0
- package/dist/utils/md5.d.ts +4 -0
- package/dist/utils/md5.js +79 -0
- package/dist/utils/outline.d.ts +21 -0
- package/dist/utils/outline.js +79 -0
- package/dist/utils/pdf-object-manager.d.ts +110 -8
- package/dist/utils/pdf-object-manager.js +583 -141
- package/dist/utils/renderer-registry.d.ts +13 -1
- package/dist/utils/renderer-registry.js +23 -6
- package/dist/utils/struct-tree.d.ts +44 -0
- package/dist/utils/struct-tree.js +129 -0
- package/dist/utils/ttf-parser.d.ts +134 -2
- package/dist/utils/ttf-parser.js +873 -50
- package/dist/utils/ttf-subsetter.d.ts +1 -1
- package/dist/utils/ttf-subsetter.js +40 -42
- package/dist/utils/ua-xmp.d.ts +9 -0
- package/dist/utils/ua-xmp.js +32 -0
- package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
- package/dist/utils/utf8-to-windows1252-encoder.js +11 -4
- package/dist/utils/woff.d.ts +10 -0
- package/dist/utils/woff.js +116 -0
- package/dist/validators/element-validator.d.ts +2 -2
- package/dist/validators/element-validator.js +31 -26
- package/package.json +22 -2
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { isDict, isRef, isStream, isString, stringsIn, } from "./objects.js";
|
|
2
|
+
/**
|
|
3
|
+
* Writing changes back into an existing PDF, as an **incremental update**.
|
|
4
|
+
*
|
|
5
|
+
* The original bytes are never rewritten. Changed objects are APPENDED, followed by a fresh
|
|
6
|
+
* cross-reference section whose `/Prev` points back at the old one. That is what the format was designed
|
|
7
|
+
* for, and it buys three things: we never have to understand (let alone re-emit) the parts of a foreign
|
|
8
|
+
* file we did not come for; an existing signature over the original bytes stays intact; and the result
|
|
9
|
+
* carries a property worth testing - **the original file is a literal PREFIX of the output**.
|
|
10
|
+
*/
|
|
11
|
+
const enc = (s) => new TextEncoder().encode(s);
|
|
12
|
+
/**
|
|
13
|
+
* A PDF string, written back byte-faithfully.
|
|
14
|
+
*
|
|
15
|
+
* Only printable ASCII goes into a `(literal)`. Anything else becomes a `<hex>` string, which carries
|
|
16
|
+
* the SAME bytes but survives the UTF-8 encoder this file is finally written through - a literal holding
|
|
17
|
+
* byte 0xE4 would otherwise leave as the two bytes 0xC3 0xA4 and read back as mojibake.
|
|
18
|
+
*/
|
|
19
|
+
const stringToken = (b) => {
|
|
20
|
+
const printable = b.every((byte) => byte >= 0x20 && byte <= 0x7e);
|
|
21
|
+
if (!printable) {
|
|
22
|
+
return `<${Array.from(b, (byte) => byte.toString(16).padStart(2, "0").toUpperCase()).join("")}>`;
|
|
23
|
+
}
|
|
24
|
+
let out = "";
|
|
25
|
+
for (const byte of b) {
|
|
26
|
+
if (byte === 0x28 || byte === 0x29 || byte === 0x5c)
|
|
27
|
+
out += "\\" + String.fromCharCode(byte);
|
|
28
|
+
else
|
|
29
|
+
out += String.fromCharCode(byte);
|
|
30
|
+
}
|
|
31
|
+
return `(${out})`;
|
|
32
|
+
};
|
|
33
|
+
/** Escape a PDF name: everything outside the regular characters becomes `#XX`. */
|
|
34
|
+
const escNameToken = (s) => Array.from(enc(s))
|
|
35
|
+
.map((b) => b >= 0x21 && b <= 0x7e && !"()<>[]{}/%#".includes(String.fromCharCode(b))
|
|
36
|
+
? String.fromCharCode(b)
|
|
37
|
+
: "#" + b.toString(16).padStart(2, "0").toUpperCase())
|
|
38
|
+
.join("");
|
|
39
|
+
const num = (n) => (Number.isInteger(n) ? String(n) : String(Number(n.toFixed(6))));
|
|
40
|
+
/**
|
|
41
|
+
* Serialise an object back to PDF syntax. Used only for objects we deliberately change - everything we
|
|
42
|
+
* do not touch stays in the original bytes, untouched and unre-encoded.
|
|
43
|
+
*
|
|
44
|
+
* A string keeps the spelling it was read with: a name written as `<FEFF…>` stays hex, because that is
|
|
45
|
+
* how the producer encodes unicode and rewriting it as a literal would corrupt it.
|
|
46
|
+
*/
|
|
47
|
+
export function serialize(o, cipher) {
|
|
48
|
+
if (o === undefined || o === null)
|
|
49
|
+
return "null";
|
|
50
|
+
if (typeof o === "number")
|
|
51
|
+
return num(o);
|
|
52
|
+
if (typeof o === "boolean")
|
|
53
|
+
return String(o);
|
|
54
|
+
if (Array.isArray(o))
|
|
55
|
+
return `[${o.map((e) => serialize(e, cipher)).join(" ")}]`;
|
|
56
|
+
if (isRef(o))
|
|
57
|
+
return `${o.num} ${o.gen} R`;
|
|
58
|
+
if (isString(o)) {
|
|
59
|
+
// In an encrypted document every string was deciphered on open, so it is PLAINTEXT here. Writing it
|
|
60
|
+
// back as-is would leak it; the caller pre-enciphers each one and passes the result in.
|
|
61
|
+
const enciphered = cipher?.get(o);
|
|
62
|
+
if (enciphered !== undefined)
|
|
63
|
+
return enciphered;
|
|
64
|
+
// A string the producer wrote as hex stays hex; the rest is decided by its bytes.
|
|
65
|
+
return o.hex
|
|
66
|
+
? `<${Array.from(o.bytes, (b) => b.toString(16).padStart(2, "0").toUpperCase()).join("")}>`
|
|
67
|
+
: stringToken(o.bytes);
|
|
68
|
+
}
|
|
69
|
+
if (isStream(o))
|
|
70
|
+
return `${serialize(o.dict, cipher)}\nstream\n<<stream data>>\nendstream`;
|
|
71
|
+
if (isDict(o)) {
|
|
72
|
+
const parts = [];
|
|
73
|
+
for (const [k, v] of o.map)
|
|
74
|
+
parts.push(`/${escNameToken(k)} ${serialize(v, cipher)}`);
|
|
75
|
+
return `<< ${parts.join(" ")} >>`;
|
|
76
|
+
}
|
|
77
|
+
return `/${escNameToken(o.name)}`;
|
|
78
|
+
}
|
|
79
|
+
export class IncrementalWriter {
|
|
80
|
+
constructor(doc) {
|
|
81
|
+
this.doc = doc;
|
|
82
|
+
/** Object number -> its new body, in PDF syntax. */
|
|
83
|
+
this.changed = new Map();
|
|
84
|
+
/** Numbers this writer invented, as opposed to ones it replaces - they have no recorded generation. */
|
|
85
|
+
this.added = new Set();
|
|
86
|
+
// New objects continue after the highest number the file already uses.
|
|
87
|
+
this.nextNum = Math.max(0, ...doc.objectNumbers()) + 1;
|
|
88
|
+
}
|
|
89
|
+
/** Replace an existing object. */
|
|
90
|
+
update(objNum, body) {
|
|
91
|
+
this.changed.set(objNum, body);
|
|
92
|
+
}
|
|
93
|
+
/** Append a new object and return its number. */
|
|
94
|
+
add(body) {
|
|
95
|
+
const n = this.nextNum++;
|
|
96
|
+
this.added.add(n);
|
|
97
|
+
this.changed.set(n, body);
|
|
98
|
+
return n;
|
|
99
|
+
}
|
|
100
|
+
/** A shared Helvetica for regenerated appearances, added once and only when one is drawn. */
|
|
101
|
+
helv() {
|
|
102
|
+
return (this.helvNum ?? (this.helvNum = this.add("<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>")));
|
|
103
|
+
}
|
|
104
|
+
/** The generation to write for an object: the file's own for one we replace, 0 for one we add. */
|
|
105
|
+
generationOf(objNum) {
|
|
106
|
+
return this.added.has(objNum) ? 0 : this.doc.generationOf(objNum);
|
|
107
|
+
}
|
|
108
|
+
get hasChanges() {
|
|
109
|
+
return this.changed.size > 0;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Produce the updated file: the original bytes, then every changed object, then a new cross-reference
|
|
113
|
+
* section chained to the old one.
|
|
114
|
+
*/
|
|
115
|
+
save() {
|
|
116
|
+
if (this.changed.size === 0)
|
|
117
|
+
return this.doc.bytes;
|
|
118
|
+
const original = this.doc.bytes;
|
|
119
|
+
const chunks = [original];
|
|
120
|
+
let offset = original.length;
|
|
121
|
+
// A file that does not end in a newline would run its last line into our first object.
|
|
122
|
+
if (original[original.length - 1] !== 10 && original[original.length - 1] !== 13) {
|
|
123
|
+
chunks.push(enc("\n"));
|
|
124
|
+
offset += 1;
|
|
125
|
+
}
|
|
126
|
+
const offsets = new Map();
|
|
127
|
+
for (const [objNum, body] of [...this.changed].sort((a, b) => a[0] - b[0])) {
|
|
128
|
+
offsets.set(objNum, offset);
|
|
129
|
+
// The generation has to match what the file records, or the new entry describes a different
|
|
130
|
+
// object than the one being replaced. Objects we ADD are new numbers, and those start at 0.
|
|
131
|
+
const head = enc(`${objNum} ${this.generationOf(objNum)} obj\n`);
|
|
132
|
+
chunks.push(head);
|
|
133
|
+
offset += head.length;
|
|
134
|
+
if (typeof body === "string") {
|
|
135
|
+
const b = enc(`${body}\nendobj\n`);
|
|
136
|
+
chunks.push(b);
|
|
137
|
+
offset += b.length;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const open = enc(`<< ${body.dict} /Length ${body.data.length} >>\nstream\n`);
|
|
141
|
+
const close = enc(`\nendstream\nendobj\n`);
|
|
142
|
+
chunks.push(open, body.data, close);
|
|
143
|
+
offset += open.length + body.data.length + close.length;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const xrefOffset = offset;
|
|
147
|
+
chunks.push(this.xrefSection(offsets, xrefOffset));
|
|
148
|
+
let total = 0;
|
|
149
|
+
for (const c of chunks)
|
|
150
|
+
total += c.length;
|
|
151
|
+
const out = new Uint8Array(total);
|
|
152
|
+
let at = 0;
|
|
153
|
+
for (const c of chunks) {
|
|
154
|
+
out.set(c, at);
|
|
155
|
+
at += c.length;
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The new cross-reference section, as BYTES.
|
|
161
|
+
*
|
|
162
|
+
* Bytes, not a string: an xref stream is binary, and running it through a UTF-8 encoder would turn
|
|
163
|
+
* every value above 127 into two bytes - which is most offsets, so the whole index becomes garbage.
|
|
164
|
+
*
|
|
165
|
+
* A classic table is written when the file already had one; an xref-STREAM file gets a stream,
|
|
166
|
+
* because mixing the two leaves readers that follow only one kind unable to walk the chain back.
|
|
167
|
+
*/
|
|
168
|
+
xrefSection(offsets, xrefOffset) {
|
|
169
|
+
const prev = this.previousStartXref();
|
|
170
|
+
const rootRef = this.doc.trailer.map.get("Root");
|
|
171
|
+
const idRef = this.doc.trailer.map.get("ID");
|
|
172
|
+
const infoRef = this.doc.trailer.map.get("Info");
|
|
173
|
+
// /Encrypt MUST be carried over. Dropping it declares the file plaintext while its contents are
|
|
174
|
+
// still ciphertext, so every reader - including ours - hands back raw bytes as field names.
|
|
175
|
+
const encryptRef = this.doc.trailer.map.get("Encrypt");
|
|
176
|
+
// An xref stream is itself an object and has to appear in its own index, so its number is taken
|
|
177
|
+
// first and its offset is where this whole section begins.
|
|
178
|
+
const all = new Map(offsets);
|
|
179
|
+
let selfNum;
|
|
180
|
+
if (this.doc.usesXrefStream) {
|
|
181
|
+
selfNum = this.nextNum++;
|
|
182
|
+
all.set(selfNum, xrefOffset);
|
|
183
|
+
}
|
|
184
|
+
const size = Math.max(this.nextNum, ...all.keys()) + 1;
|
|
185
|
+
// Group consecutive object numbers into subsections, which is what both formats want.
|
|
186
|
+
const nums = [...all.keys()].sort((a, b) => a - b);
|
|
187
|
+
const runs = [];
|
|
188
|
+
for (const n of nums) {
|
|
189
|
+
const last = runs[runs.length - 1];
|
|
190
|
+
if (last && n === last[0] + last[1].length)
|
|
191
|
+
last[1].push(n);
|
|
192
|
+
else
|
|
193
|
+
runs.push([n, [n]]);
|
|
194
|
+
}
|
|
195
|
+
const entry = (key, ref) => ref === undefined ? "" : `${key} ${serialize(ref)}`;
|
|
196
|
+
if (selfNum !== undefined) {
|
|
197
|
+
// Rows of `/W [1 4 2]`: in-use marker, 4-byte offset, 2-byte generation. Left uncompressed - a
|
|
198
|
+
// filter is optional, and a readable appended section is worth more here than a few hundred bytes.
|
|
199
|
+
const rows = new Uint8Array(nums.length * 7);
|
|
200
|
+
nums.forEach((n, i) => {
|
|
201
|
+
const off = all.get(n);
|
|
202
|
+
const g = n === selfNum ? 0 : this.generationOf(n);
|
|
203
|
+
rows.set([
|
|
204
|
+
1,
|
|
205
|
+
(off >>> 24) & 0xff,
|
|
206
|
+
(off >>> 16) & 0xff,
|
|
207
|
+
(off >>> 8) & 0xff,
|
|
208
|
+
off & 0xff,
|
|
209
|
+
(g >>> 8) & 0xff,
|
|
210
|
+
g & 0xff,
|
|
211
|
+
], i * 7);
|
|
212
|
+
});
|
|
213
|
+
const index = runs.map(([start, group]) => `${start} ${group.length}`).join(" ");
|
|
214
|
+
const dict = [
|
|
215
|
+
"/Type /XRef",
|
|
216
|
+
`/Size ${size}`,
|
|
217
|
+
`/Index [${index}]`,
|
|
218
|
+
"/W [1 4 2]",
|
|
219
|
+
entry("/Root", rootRef),
|
|
220
|
+
entry("/Info", infoRef),
|
|
221
|
+
entry("/ID", idRef),
|
|
222
|
+
entry("/Encrypt", encryptRef),
|
|
223
|
+
prev !== undefined ? `/Prev ${prev}` : "",
|
|
224
|
+
`/Length ${rows.length}`,
|
|
225
|
+
].filter(Boolean);
|
|
226
|
+
const head = enc(`${selfNum} 0 obj\n<< ${dict.join(" ")} >>\nstream\n`);
|
|
227
|
+
const tail = enc(`\nendstream\nendobj\nstartxref\n${xrefOffset}\n%%EOF\n`);
|
|
228
|
+
const out = new Uint8Array(head.length + rows.length + tail.length);
|
|
229
|
+
out.set(head, 0);
|
|
230
|
+
out.set(rows, head.length);
|
|
231
|
+
out.set(tail, head.length + rows.length);
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
let table = "xref\n";
|
|
235
|
+
for (const [start, group] of runs) {
|
|
236
|
+
table += `${start} ${group.length}\n`;
|
|
237
|
+
for (const n of group) {
|
|
238
|
+
const g = String(this.generationOf(n)).padStart(5, "0");
|
|
239
|
+
table += `${String(all.get(n)).padStart(10, "0")} ${g} n \n`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const trailer = [
|
|
243
|
+
`/Size ${size}`,
|
|
244
|
+
entry("/Root", rootRef),
|
|
245
|
+
entry("/Info", infoRef),
|
|
246
|
+
entry("/ID", idRef),
|
|
247
|
+
entry("/Encrypt", encryptRef),
|
|
248
|
+
prev !== undefined ? `/Prev ${prev}` : "",
|
|
249
|
+
].filter(Boolean);
|
|
250
|
+
return enc(`${table}trailer\n<< ${trailer.join(" ")} >>\nstartxref\n${xrefOffset}\n%%EOF\n`);
|
|
251
|
+
}
|
|
252
|
+
/** The offset the ORIGINAL file's `startxref` pointed at - our `/Prev`. */
|
|
253
|
+
previousStartXref() {
|
|
254
|
+
const tail = new TextDecoder("latin1").decode(this.doc.bytes.subarray(Math.max(0, this.doc.bytes.length - 2048)));
|
|
255
|
+
const at = tail.lastIndexOf("startxref");
|
|
256
|
+
if (at < 0)
|
|
257
|
+
return undefined;
|
|
258
|
+
const m = /startxref\s+(\d+)/.exec(tail.slice(at));
|
|
259
|
+
return m ? Number(m[1]) : undefined;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** Bytes as an enciphered PDF hex string, the form every rewritten string takes in an encrypted file. */
|
|
263
|
+
const hexString = (b) => `<${Array.from(b, (x) => x.toString(16).padStart(2, "0").toUpperCase()).join("")}>`;
|
|
264
|
+
/**
|
|
265
|
+
* Encipher every string the given objects carry, into the cipher map `serialize` consults.
|
|
266
|
+
*
|
|
267
|
+
* Not optional on an encrypted document: `serialize` writes any string missing from the map as
|
|
268
|
+
* PLAINTEXT, so an object rewritten without this pass leaks whatever it holds.
|
|
269
|
+
*/
|
|
270
|
+
export async function carryStrings(doc, objects, into = new Map()) {
|
|
271
|
+
if (!doc.isEncrypted)
|
|
272
|
+
return into;
|
|
273
|
+
for (const o of objects) {
|
|
274
|
+
for (const str of stringsIn(o)) {
|
|
275
|
+
if (into.has(str))
|
|
276
|
+
continue;
|
|
277
|
+
const cipher = await doc.encryptForWrite(str.bytes);
|
|
278
|
+
if (cipher)
|
|
279
|
+
into.set(str, hexString(cipher));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return into;
|
|
283
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { MainAlign, CrossAlign } from "../utils/flex-layout";
|
|
2
|
-
import { BoxConstraints, Offset, Size } from "../layout/box-constraints";
|
|
3
|
-
import { Fragmentable, FragmentResult } from "../layout/fragmentation";
|
|
4
|
-
import { LayoutContext, SizedElement, SizedPDFElement, WithChildren } from "./pdf-element";
|
|
1
|
+
import { MainAlign, CrossAlign } from "../utils/flex-layout.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
|
|
3
|
+
import { Fragmentable, FragmentResult } from "../layout/fragmentation.js";
|
|
4
|
+
import { LayoutContext, SizedElement, SizedPDFElement, WithChildren } from "./pdf-element.js";
|
|
5
5
|
interface ContainerElementParams extends SizedElement, WithChildren {
|
|
6
6
|
/** Space between children. */
|
|
7
7
|
gap?: number;
|
|
@@ -9,13 +9,45 @@ interface ContainerElementParams extends SizedElement, WithChildren {
|
|
|
9
9
|
main?: MainAlign;
|
|
10
10
|
/** Horizontal alignment of each child (cross axis); defaults to `stretch`. */
|
|
11
11
|
cross?: CrossAlign;
|
|
12
|
+
/** Lay the children out backwards along the main axis (CSS `row-reverse`). */
|
|
13
|
+
reverse?: boolean;
|
|
14
|
+
/** Let the children flow onto further lines when they do not fit (CSS `flex-wrap`). */
|
|
15
|
+
wrap?: boolean;
|
|
16
|
+
/** How the block of wrapped lines sits across the axis (CSS `align-content`). */
|
|
17
|
+
alignContent?: MainAlign;
|
|
18
|
+
/** Width/height as a fraction (0..1) of the offered box instead of `width`/`height` (relative sizing). */
|
|
19
|
+
widthFactor?: number;
|
|
20
|
+
heightFactor?: number;
|
|
21
|
+
/** Lower / upper bounds per axis, in points or as a fraction; see `SizingParams`. */
|
|
22
|
+
minWidth?: number;
|
|
23
|
+
maxWidth?: number;
|
|
24
|
+
minHeight?: number;
|
|
25
|
+
maxHeight?: number;
|
|
26
|
+
minWidthFactor?: number;
|
|
27
|
+
maxWidthFactor?: number;
|
|
28
|
+
minHeightFactor?: number;
|
|
29
|
+
maxHeightFactor?: number;
|
|
30
|
+
/** width / height; derives whichever axis is left open (CSS `aspect-ratio`). */
|
|
31
|
+
aspectRatio?: number;
|
|
32
|
+
/** Start this stack on a fresh page (CSS `break-before: page`). */
|
|
33
|
+
breakBefore?: boolean;
|
|
34
|
+
/** Start everything after this stack on a fresh page (CSS `break-after: page`). */
|
|
35
|
+
breakAfter?: boolean;
|
|
12
36
|
}
|
|
13
37
|
export declare class ContainerElement extends SizedPDFElement implements Fragmentable {
|
|
14
38
|
private children;
|
|
15
39
|
private gap;
|
|
16
40
|
private main;
|
|
17
41
|
private cross;
|
|
18
|
-
|
|
42
|
+
private reverse;
|
|
43
|
+
private wrap;
|
|
44
|
+
private alignContent?;
|
|
45
|
+
private breakBefore;
|
|
46
|
+
private breakAfter;
|
|
47
|
+
private requested;
|
|
48
|
+
constructor({ x, y, width, height, children, gap, main, cross, reverse, wrap, alignContent, breakBefore, breakAfter, ...sizing }: ContainerElementParams);
|
|
49
|
+
breaksBefore(): boolean;
|
|
50
|
+
breaksAfter(): boolean;
|
|
19
51
|
/**
|
|
20
52
|
* Splits the vertical stack across pages. Children are measured against the content
|
|
21
53
|
* width and packed until one would exceed `maxHeight`. That straddling child is itself
|
|
@@ -27,8 +59,21 @@ export declare class ContainerElement extends SizedPDFElement implements Fragmen
|
|
|
27
59
|
* Flex children make the container absorb the leftover space, so it never overflows -
|
|
28
60
|
* in that case we don't fragment and hand the whole container back as `fitted`.
|
|
29
61
|
*/
|
|
62
|
+
hasForcedBreak(): boolean;
|
|
30
63
|
fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
|
|
31
64
|
private cloneWithChildren;
|
|
65
|
+
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* A Column stacks vertically, so a flex child of its OWN only ever needs a bounded height. But the need
|
|
68
|
+
* also propagates: a `Spacer` two levels down is just as unable to resolve against an infinite axis, so
|
|
69
|
+
* we ask the children too - on both axes, since a nested Row's `Spacer` needs a bounded WIDTH and a
|
|
70
|
+
* Column nested in a Row is exactly where that width goes missing.
|
|
71
|
+
*
|
|
72
|
+
* An explicit extent on an axis short-circuits it: the element already knows how big it is there, and
|
|
73
|
+
* with no flex descendant nothing needs distributing, so it keeps shrink-wrapping. That is what leaves
|
|
74
|
+
* every existing layout untouched.
|
|
75
|
+
*/
|
|
76
|
+
needsBoundedMain(horizontal: boolean): boolean;
|
|
32
77
|
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
33
78
|
getProps(): ContainerElementParams;
|
|
34
79
|
}
|
|
@@ -1,16 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ContainerElement extends pdf_element_1.SizedPDFElement {
|
|
8
|
-
constructor({ x, y, width, height, children, gap, main, cross }) {
|
|
1
|
+
import { FlexLayoutHelper, VERTICAL_AXIS } from "../utils/flex-layout.js";
|
|
2
|
+
import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
|
|
3
|
+
import { childrenForceBreak, packChildren, } from "../layout/fragmentation.js";
|
|
4
|
+
import { FlexiblePDFElement, SizedPDFElement, } from "./pdf-element.js";
|
|
5
|
+
export class ContainerElement extends SizedPDFElement {
|
|
6
|
+
constructor({ x, y, width, height, children, gap, main, cross, reverse, wrap, alignContent, breakBefore, breakAfter, ...sizing }) {
|
|
9
7
|
super({ x, y, width, height });
|
|
10
8
|
this.children = children;
|
|
11
|
-
this.gap = gap
|
|
12
|
-
this.main = main
|
|
13
|
-
this.cross = cross
|
|
9
|
+
this.gap = gap ?? 0;
|
|
10
|
+
this.main = main ?? "start";
|
|
11
|
+
this.cross = cross ?? "stretch";
|
|
12
|
+
this.reverse = reverse ?? false;
|
|
13
|
+
this.wrap = wrap ?? false;
|
|
14
|
+
this.alignContent = alignContent;
|
|
15
|
+
this.requested = { ...sizing, width, height };
|
|
16
|
+
this.breakBefore = breakBefore ?? false;
|
|
17
|
+
this.breakAfter = breakAfter ?? false;
|
|
18
|
+
}
|
|
19
|
+
breaksBefore() {
|
|
20
|
+
return this.breakBefore;
|
|
21
|
+
}
|
|
22
|
+
breaksAfter() {
|
|
23
|
+
return this.breakAfter;
|
|
14
24
|
}
|
|
15
25
|
/**
|
|
16
26
|
* Splits the vertical stack across pages. Children are measured against the content
|
|
@@ -23,57 +33,109 @@ class ContainerElement extends pdf_element_1.SizedPDFElement {
|
|
|
23
33
|
* Flex children make the container absorb the leftover space, so it never overflows -
|
|
24
34
|
* in that case we don't fragment and hand the whole container back as `fitted`.
|
|
25
35
|
*/
|
|
36
|
+
hasForcedBreak() {
|
|
37
|
+
return childrenForceBreak(this.children);
|
|
38
|
+
}
|
|
26
39
|
fragment(maxHeight, width, ctx) {
|
|
27
|
-
const { fitted, remainder } =
|
|
28
|
-
// Fits as one region: hand the whole container back so the page renders
|
|
29
|
-
// (its normal layout distributes flex / fills the page).
|
|
30
|
-
|
|
40
|
+
const { fitted, remainder, forceBreak } = packChildren(this.children, maxHeight, width, ctx, this.gap);
|
|
41
|
+
// Fits as one region with no forced cut: hand the whole container back so the page renders
|
|
42
|
+
// unchanged (its normal layout distributes flex / fills the page). A forced break is excluded
|
|
43
|
+
// here even when nothing follows it (a trailing `PageBreak`): return the packed children, which
|
|
44
|
+
// no longer hold the consumed break marker, so it does not leak into the render pass.
|
|
45
|
+
if (remainder.length === 0 && !forceBreak)
|
|
31
46
|
return { fitted: this, remainder: null };
|
|
32
47
|
return {
|
|
33
48
|
fitted: this.cloneWithChildren(fitted),
|
|
34
|
-
|
|
49
|
+
// Only the CONTINUATION carries `breakAfter`, so a stack that splits across pages still breaks
|
|
50
|
+
// after its LAST fragment (not after the first one). `breakBefore` is dropped on both: it was
|
|
51
|
+
// already honoured by the parent before this stack was ever fragmented.
|
|
52
|
+
remainder: remainder.length === 0 ? null : this.cloneWithChildren(remainder, this.breakAfter),
|
|
53
|
+
forceBreak,
|
|
35
54
|
};
|
|
36
55
|
}
|
|
37
|
-
cloneWithChildren(children) {
|
|
56
|
+
cloneWithChildren(children, breakAfter = false) {
|
|
38
57
|
return new ContainerElement({
|
|
39
58
|
x: this.x,
|
|
40
59
|
y: this.y,
|
|
41
|
-
width
|
|
42
|
-
|
|
60
|
+
// Carry the requested WIDTH (a %-width column keeps its width on every page); the height is
|
|
61
|
+
// intentionally dropped so each fragment shrink-wraps to the content it actually holds.
|
|
62
|
+
width: this.requested.width,
|
|
63
|
+
widthFactor: this.requested.widthFactor,
|
|
43
64
|
children,
|
|
44
65
|
gap: this.gap,
|
|
45
66
|
main: this.main,
|
|
46
67
|
cross: this.cross,
|
|
68
|
+
reverse: this.reverse,
|
|
69
|
+
wrap: this.wrap,
|
|
70
|
+
alignContent: this.alignContent,
|
|
71
|
+
breakAfter,
|
|
47
72
|
});
|
|
48
73
|
}
|
|
74
|
+
relativeSizeFactor(horizontal) {
|
|
75
|
+
return horizontal ? this.requested.widthFactor : this.requested.heightFactor;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A Column stacks vertically, so a flex child of its OWN only ever needs a bounded height. But the need
|
|
79
|
+
* also propagates: a `Spacer` two levels down is just as unable to resolve against an infinite axis, so
|
|
80
|
+
* we ask the children too - on both axes, since a nested Row's `Spacer` needs a bounded WIDTH and a
|
|
81
|
+
* Column nested in a Row is exactly where that width goes missing.
|
|
82
|
+
*
|
|
83
|
+
* An explicit extent on an axis short-circuits it: the element already knows how big it is there, and
|
|
84
|
+
* with no flex descendant nothing needs distributing, so it keeps shrink-wrapping. That is what leaves
|
|
85
|
+
* every existing layout untouched.
|
|
86
|
+
*/
|
|
87
|
+
needsBoundedMain(horizontal) {
|
|
88
|
+
const requested = horizontal
|
|
89
|
+
? [this.requested.width, this.requested.widthFactor]
|
|
90
|
+
: [this.requested.height, this.requested.heightFactor];
|
|
91
|
+
if (requested.some((v) => v !== undefined))
|
|
92
|
+
return false;
|
|
93
|
+
const ownFlexChild = !horizontal && this.children.some((c) => c instanceof FlexiblePDFElement);
|
|
94
|
+
return ownFlexChild || this.children.some((c) => c.needsBoundedMain(horizontal));
|
|
95
|
+
}
|
|
49
96
|
calculateLayout(constraints, offset, ctx) {
|
|
50
|
-
// The container fills the width/height it is offered; when an axis is unbounded it
|
|
51
|
-
// shrink-wraps to its children instead (mirrors how Row shrink-wraps). Width unbounded
|
|
52
|
-
// happens for a Column nested in a Row (the Row offers its fixed children unbounded
|
|
53
|
-
// width); passing 0 there would collapse every child to width 0.
|
|
54
|
-
if (constraints.hasBoundedWidth)
|
|
55
|
-
this.width = constraints.maxWidth;
|
|
56
|
-
if (constraints.hasBoundedHeight)
|
|
57
|
-
this.height = constraints.maxHeight;
|
|
58
97
|
this.x = offset.x;
|
|
59
98
|
this.y = offset.y;
|
|
60
|
-
|
|
61
|
-
|
|
99
|
+
// Relative sizing: a pinned extent (fixed points or a fraction of the offered box, clamped into
|
|
100
|
+
// the constraints) wins; else fill a bounded axis; else stay `undefined` and shrink-wrap below.
|
|
101
|
+
// A Column nested in a Row gets unbounded width - passing 0 there would collapse children to 0.
|
|
102
|
+
const specs = extentSpecs(this.requested);
|
|
103
|
+
const resolved = resolveSize(specs.width, specs.height, this.requested.aspectRatio, constraints);
|
|
104
|
+
const { width: explicitWidth, height: explicitHeight, constraints: bounds } = resolved;
|
|
105
|
+
// Fill-or-shrink-wrap is decided by the constraints we were GIVEN; a min/max only caps the result.
|
|
106
|
+
const boundedWidth = explicitWidth !== undefined
|
|
107
|
+
? bounds.constrainWidth(explicitWidth)
|
|
108
|
+
: constraints.hasBoundedWidth
|
|
109
|
+
? bounds.maxWidth
|
|
110
|
+
: undefined;
|
|
111
|
+
const boundedHeight = explicitHeight !== undefined
|
|
112
|
+
? bounds.constrainHeight(explicitHeight)
|
|
113
|
+
: constraints.hasBoundedHeight
|
|
114
|
+
? bounds.maxHeight
|
|
115
|
+
: undefined;
|
|
116
|
+
// Vertical stack: cross = width (children fill it), main = height (the stacking extent).
|
|
117
|
+
const crossAvail = boundedWidth ?? Infinity;
|
|
118
|
+
const mainAvail = boundedHeight ?? Infinity;
|
|
62
119
|
let result = { mainUsed: 0, crossUsed: 0 };
|
|
63
120
|
if (this.children) {
|
|
64
121
|
// Vertical flex stack (main = height, cross = width). The shared helper measures
|
|
65
122
|
// fixed children, distributes the leftover to flex children, and places everything
|
|
66
123
|
// in source order.
|
|
67
|
-
result =
|
|
124
|
+
result = FlexLayoutHelper.layout(this.children, VERTICAL_AXIS, mainAvail, crossAvail, this.y, this.x, {
|
|
125
|
+
gap: this.gap,
|
|
126
|
+
main: this.main,
|
|
127
|
+
cross: this.cross,
|
|
128
|
+
reverse: this.reverse,
|
|
129
|
+
wrap: this.wrap,
|
|
130
|
+
alignContent: this.alignContent,
|
|
131
|
+
}, ctx);
|
|
68
132
|
}
|
|
69
|
-
//
|
|
70
|
-
// stack, width = the widest child). Top-left coordinates; the container draws nothing,
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.width
|
|
75
|
-
this.height = height;
|
|
76
|
-
return { width, height };
|
|
133
|
+
// Pinned/bounded axis takes that extent; an unbounded one shrink-wraps to the children (height =
|
|
134
|
+
// the stack, width = the widest child). Top-left coordinates; the container draws nothing, and
|
|
135
|
+
// the Y-flip happens once at the IR -> backend seam.
|
|
136
|
+
this.width = boundedWidth ?? result.crossUsed;
|
|
137
|
+
this.height = boundedHeight ?? result.mainUsed;
|
|
138
|
+
return { width: this.width, height: this.height };
|
|
77
139
|
}
|
|
78
140
|
getProps() {
|
|
79
141
|
return {
|
|
@@ -88,4 +150,3 @@ class ContainerElement extends pdf_element_1.SizedPDFElement {
|
|
|
88
150
|
};
|
|
89
151
|
}
|
|
90
152
|
}
|
|
91
|
-
exports.ContainerElement = ContainerElement;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
+
import { LayoutContext, SizedPDFElement } from "../pdf-element.js";
|
|
4
|
+
import type { CheckboxSpec, FieldStyle } from "../../forms/field.js";
|
|
5
|
+
export interface CheckboxParams {
|
|
6
|
+
/** The field name (/T) - unique in the document. */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Whether it starts checked. */
|
|
9
|
+
checked?: boolean;
|
|
10
|
+
/** The "on" export value (default `"Yes"`). */
|
|
11
|
+
onValue?: string;
|
|
12
|
+
/** Tooltip / accessible name. */
|
|
13
|
+
tooltip?: string;
|
|
14
|
+
/** Show but do not allow toggling. */
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
/** Mark it as a field that must be filled in before submitting. */
|
|
17
|
+
required?: boolean;
|
|
18
|
+
/** Hide the widget entirely - neither on screen nor in print. */
|
|
19
|
+
hidden?: boolean;
|
|
20
|
+
/** Include the widget when printing (default true). */
|
|
21
|
+
print?: boolean;
|
|
22
|
+
/** Box side length in points (default 14). */
|
|
23
|
+
size?: number;
|
|
24
|
+
/** Checkmark colour (default black). */
|
|
25
|
+
color?: Color;
|
|
26
|
+
/** Box border colour (default a mid grey, so the box is visible). */
|
|
27
|
+
border?: Color;
|
|
28
|
+
/** Box background fill. */
|
|
29
|
+
background?: Color;
|
|
30
|
+
/** Border thickness (default 1). */
|
|
31
|
+
borderWidth?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A checkbox (AcroForm /Btn). A small square leaf that draws nothing itself: it reserves its box and
|
|
35
|
+
* emits a `formfield` IR node whose checkbox spec bakes an /AP checkmark at the seam.
|
|
36
|
+
*/
|
|
37
|
+
export declare class CheckboxElement extends SizedPDFElement {
|
|
38
|
+
private spec;
|
|
39
|
+
private style;
|
|
40
|
+
private size;
|
|
41
|
+
constructor(p: CheckboxParams);
|
|
42
|
+
calculateLayout(_constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
|
|
43
|
+
getProps(): {
|
|
44
|
+
x: number;
|
|
45
|
+
y: number;
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
spec: CheckboxSpec;
|
|
49
|
+
style: FieldStyle;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Color } from "../../common/color.js";
|
|
2
|
+
import { SizedPDFElement } from "../pdf-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* A checkbox (AcroForm /Btn). A small square leaf that draws nothing itself: it reserves its box and
|
|
5
|
+
* emits a `formfield` IR node whose checkbox spec bakes an /AP checkmark at the seam.
|
|
6
|
+
*/
|
|
7
|
+
export class CheckboxElement extends SizedPDFElement {
|
|
8
|
+
constructor(p) {
|
|
9
|
+
const size = p.size ?? 14;
|
|
10
|
+
super({ x: 0, y: 0, width: size, height: size });
|
|
11
|
+
this.size = size;
|
|
12
|
+
this.spec = {
|
|
13
|
+
kind: "checkbox",
|
|
14
|
+
name: p.name,
|
|
15
|
+
checked: p.checked,
|
|
16
|
+
onValue: p.onValue,
|
|
17
|
+
tooltip: p.tooltip,
|
|
18
|
+
readOnly: p.readOnly,
|
|
19
|
+
required: p.required,
|
|
20
|
+
hidden: p.hidden,
|
|
21
|
+
print: p.print,
|
|
22
|
+
};
|
|
23
|
+
this.style = {
|
|
24
|
+
border: p.border ?? new Color(102, 102, 102),
|
|
25
|
+
background: p.background,
|
|
26
|
+
color: p.color ?? new Color(0, 0, 0),
|
|
27
|
+
fontSize: 0,
|
|
28
|
+
borderWidth: p.borderWidth ?? 1,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
calculateLayout(_constraints, offset, _ctx) {
|
|
32
|
+
this.x = offset.x;
|
|
33
|
+
this.y = offset.y;
|
|
34
|
+
this.width = this.size;
|
|
35
|
+
this.height = this.size;
|
|
36
|
+
return { width: this.size, height: this.size };
|
|
37
|
+
}
|
|
38
|
+
getProps() {
|
|
39
|
+
return {
|
|
40
|
+
x: this.x,
|
|
41
|
+
y: this.y,
|
|
42
|
+
width: this.width ?? 0,
|
|
43
|
+
height: this.height ?? 0,
|
|
44
|
+
spec: this.spec,
|
|
45
|
+
style: this.style,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|