@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
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { FieldAlign, FieldStyle } from "./field.js";
|
|
2
2
|
import type { FontMetrics } from "../utils/font-metrics.js";
|
|
3
3
|
import type { FontStyle } from "../utils/pdf-object-manager.js";
|
|
4
|
+
/**
|
|
5
|
+
* The font an appearance draws its text in: its resource name inside the appearance stream's own
|
|
6
|
+
* /Resources, and how a string becomes a `Tj` operand in it. One object, so the caller that picks the
|
|
7
|
+
* font also decides its encoding - a WinAnsi literal for Helvetica, glyph ids for an embedded font.
|
|
8
|
+
*/
|
|
9
|
+
export interface FaceFont {
|
|
10
|
+
res: string;
|
|
11
|
+
encode(text: string): string;
|
|
12
|
+
}
|
|
13
|
+
/** The standard Helvetica (`/Helv`) a field's /DA names: WinAnsi literal strings. */
|
|
14
|
+
export declare const HELV: FaceFont;
|
|
4
15
|
/** The unchecked appearance: just the box. */
|
|
5
16
|
export declare function checkboxOff(w: number, h: number, style: FieldStyle): string;
|
|
6
17
|
/** The checked appearance: the box plus a vector checkmark (font-free, so it renders everywhere). */
|
|
@@ -12,16 +23,16 @@ export declare function radioOn(w: number, h: number, style: FieldStyle): string
|
|
|
12
23
|
/**
|
|
13
24
|
* A push button's face: the box plus its caption, centred. The caller measures the caption (this module
|
|
14
25
|
* stays free of font metrics and the writer) and passes `captionWidth` in points and `capHeight` as an em
|
|
15
|
-
* fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `
|
|
16
|
-
*
|
|
26
|
+
* fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `font` is
|
|
27
|
+
* what the caption is drawn in.
|
|
17
28
|
*/
|
|
18
|
-
export declare function pushButtonFace(w: number, h: number, style: FieldStyle, caption: string, captionWidth: number, capHeight: number,
|
|
29
|
+
export declare function pushButtonFace(w: number, h: number, style: FieldStyle, caption: string, captionWidth: number, capHeight: number, font: FaceFont, size: number): string;
|
|
19
30
|
/**
|
|
20
31
|
* An EMPTY signature field: the box, a signing rule across the lower third, and an optional hint centred
|
|
21
32
|
* just above it. This is the placeholder look - once someone actually signs, the signing tool replaces
|
|
22
33
|
* this appearance with its own.
|
|
23
34
|
*/
|
|
24
|
-
export declare function signatureFace(w: number, h: number, style: FieldStyle, label: string, labelWidth: number,
|
|
35
|
+
export declare function signatureFace(w: number, h: number, style: FieldStyle, label: string, labelWidth: number, font: FaceFont, size: number): string;
|
|
25
36
|
/** One pre-measured line of field text. The caller owns wrapping + measuring, so this module stays free
|
|
26
37
|
* of font metrics (and can be reused verbatim when filling an existing PDF). */
|
|
27
38
|
/**
|
|
@@ -50,9 +61,9 @@ export interface FieldLine {
|
|
|
50
61
|
* single line sits optically centred; a multi-line field stacks from the top, both measured off the
|
|
51
62
|
* font's cap height rather than a guessed ratio.
|
|
52
63
|
*/
|
|
53
|
-
export declare function textFieldFace(w: number, h: number, style: FieldStyle, lines: FieldLine[], capHeight: number, size: number,
|
|
64
|
+
export declare function textFieldFace(w: number, h: number, style: FieldStyle, lines: FieldLine[], capHeight: number, size: number, font: FaceFont, multiline: boolean, align?: FieldAlign): string;
|
|
54
65
|
/**
|
|
55
66
|
* A list box, baked: the visible options stacked from the top, with a highlight behind the selected
|
|
56
67
|
* ones. A combo box (dropdown) uses `textFieldFace` instead - it shows only its current value.
|
|
57
68
|
*/
|
|
58
|
-
export declare function listBoxFace(w: number, h: number, style: FieldStyle, lines: FieldLine[], capHeight: number, size: number,
|
|
69
|
+
export declare function listBoxFace(w: number, h: number, style: FieldStyle, lines: FieldLine[], capHeight: number, size: number, font: FaceFont, align?: FieldAlign): string;
|
package/dist/forms/appearance.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { escPdf, num2, pdfColor } from "./pdf.js";
|
|
2
2
|
import { wrapStringIntoLines } from "../text/line-breaker.js";
|
|
3
|
+
/** The standard Helvetica (`/Helv`) a field's /DA names: WinAnsi literal strings. */
|
|
4
|
+
export const HELV = { res: "Helv", encode: (text) => `(${escPdf(text)})` };
|
|
3
5
|
// Appearance streams (/AP): the actual drawn content of a field's box, in the field's OWN coordinate
|
|
4
6
|
// space (bottom-left origin, [0 0 w h]). Shared between CREATE (bake the field's look now) and, later,
|
|
5
7
|
// FILL-existing (regenerate a field's look after setting its value). This is why it is its own module.
|
|
@@ -63,34 +65,34 @@ export function radioOn(w, h, style) {
|
|
|
63
65
|
/**
|
|
64
66
|
* A push button's face: the box plus its caption, centred. The caller measures the caption (this module
|
|
65
67
|
* stays free of font metrics and the writer) and passes `captionWidth` in points and `capHeight` as an em
|
|
66
|
-
* fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `
|
|
67
|
-
*
|
|
68
|
+
* fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `font` is
|
|
69
|
+
* what the caption is drawn in.
|
|
68
70
|
*/
|
|
69
|
-
export function pushButtonFace(w, h, style, caption, captionWidth, capHeight,
|
|
71
|
+
export function pushButtonFace(w, h, style, caption, captionWidth, capHeight, font, size) {
|
|
70
72
|
// Baseline placed so the CAPITALS sit optically centred in the box, not the em square.
|
|
71
73
|
const y = (h - capHeight * size) / 2;
|
|
72
|
-
const caption_ = caption ? centredText(w, h, style, caption, captionWidth, y,
|
|
74
|
+
const caption_ = caption ? centredText(w, h, style, caption, captionWidth, y, font, size) : "";
|
|
73
75
|
return `${box(w, h, style)}\n${caption_}`;
|
|
74
76
|
}
|
|
75
77
|
/** One centred, clipped text run inside a field box. Clipping means an over-long caption is cut at the
|
|
76
78
|
* border instead of bleeding across the page. Shared by the push button and the signature placeholder. */
|
|
77
|
-
function centredText(w, h, style, text, textWidth, baselineY,
|
|
79
|
+
function centredText(w, h, style, text, textWidth, baselineY, font, size) {
|
|
78
80
|
return (`q 0 0 ${num2(w)} ${num2(h)} re W n ` +
|
|
79
|
-
`BT /${
|
|
80
|
-
`${num2((w - textWidth) / 2)} ${num2(baselineY)} Td
|
|
81
|
+
`BT /${font.res} ${num2(size)} Tf ${pdfColor(style.color)} rg ` +
|
|
82
|
+
`${num2((w - textWidth) / 2)} ${num2(baselineY)} Td ${font.encode(text)} Tj ET Q`);
|
|
81
83
|
}
|
|
82
84
|
/**
|
|
83
85
|
* An EMPTY signature field: the box, a signing rule across the lower third, and an optional hint centred
|
|
84
86
|
* just above it. This is the placeholder look - once someone actually signs, the signing tool replaces
|
|
85
87
|
* this appearance with its own.
|
|
86
88
|
*/
|
|
87
|
-
export function signatureFace(w, h, style, label, labelWidth,
|
|
89
|
+
export function signatureFace(w, h, style, label, labelWidth, font, size) {
|
|
88
90
|
const ruleY = h * 0.3;
|
|
89
91
|
const inset = w * 0.08;
|
|
90
92
|
const stroke = style.border ?? style.color;
|
|
91
93
|
const rule = `${pdfColor(stroke)} RG ${num2(Math.max(0.5, style.borderWidth * 0.75))} w ` +
|
|
92
94
|
`${num2(inset)} ${num2(ruleY)} m ${num2(w - inset)} ${num2(ruleY)} l S`;
|
|
93
|
-
const hint = label ? centredText(w, h, style, label, labelWidth, ruleY + 6,
|
|
95
|
+
const hint = label ? centredText(w, h, style, label, labelWidth, ruleY + 6, font, size) : "";
|
|
94
96
|
return `${box(w, h, style)}\n${rule}\n${hint}`;
|
|
95
97
|
}
|
|
96
98
|
// Horizontal padding inside a field box, on top of the border - the same small inset Acrobat leaves so
|
|
@@ -147,11 +149,11 @@ function alignX(boxWidth, pad, width, align = "left") {
|
|
|
147
149
|
* visibly shift on first click. The measurement matches: `getStringWidth` is the plain glyph sum, so
|
|
148
150
|
* measured still equals drawn.
|
|
149
151
|
*/
|
|
150
|
-
function textBlock(lines, style, size,
|
|
152
|
+
function textBlock(lines, style, size, font) {
|
|
151
153
|
const runs = lines
|
|
152
|
-
.map((l) => `1 0 0 1 ${num2(l.x)} ${num2(l.y)} Tm
|
|
154
|
+
.map((l) => `1 0 0 1 ${num2(l.x)} ${num2(l.y)} Tm ${font.encode(l.text)} Tj`)
|
|
153
155
|
.join("\n");
|
|
154
|
-
return `BT /${
|
|
156
|
+
return `BT /${font.res} ${num2(size)} Tf ${pdfColor(style.color)} rg\n${runs}\nET`;
|
|
155
157
|
}
|
|
156
158
|
/** Everything a field draws is clipped to its own box, so a long value is cut at the frame rather than
|
|
157
159
|
* bleeding across the page. */
|
|
@@ -163,7 +165,7 @@ function clipped(w, h, ops) {
|
|
|
163
165
|
* single line sits optically centred; a multi-line field stacks from the top, both measured off the
|
|
164
166
|
* font's cap height rather than a guessed ratio.
|
|
165
167
|
*/
|
|
166
|
-
export function textFieldFace(w, h, style, lines, capHeight, size,
|
|
168
|
+
export function textFieldFace(w, h, style, lines, capHeight, size, font, multiline, align = "left") {
|
|
167
169
|
const face = box(w, h, style);
|
|
168
170
|
if (lines.length === 0)
|
|
169
171
|
return face;
|
|
@@ -176,13 +178,13 @@ export function textFieldFace(w, h, style, lines, capHeight, size, fontRes, mult
|
|
|
176
178
|
// Single line: centre the capitals in the box.
|
|
177
179
|
y: multiline ? h - pad - capHeight * size - i * lineHeight : (h - capHeight * size) / 2,
|
|
178
180
|
}));
|
|
179
|
-
return `${face}\n${clipped(w, h, textBlock(placed, style, size,
|
|
181
|
+
return `${face}\n${clipped(w, h, textBlock(placed, style, size, font))}`;
|
|
180
182
|
}
|
|
181
183
|
/**
|
|
182
184
|
* A list box, baked: the visible options stacked from the top, with a highlight behind the selected
|
|
183
185
|
* ones. A combo box (dropdown) uses `textFieldFace` instead - it shows only its current value.
|
|
184
186
|
*/
|
|
185
|
-
export function listBoxFace(w, h, style, lines, capHeight, size,
|
|
187
|
+
export function listBoxFace(w, h, style, lines, capHeight, size, font, align = "left") {
|
|
186
188
|
const pad = FIELD_PAD + style.borderWidth;
|
|
187
189
|
const lineHeight = size * 1.15;
|
|
188
190
|
const rowTop = (i) => h - pad - (i + 1) * lineHeight;
|
|
@@ -199,6 +201,6 @@ export function listBoxFace(w, h, style, lines, capHeight, size, fontRes, align
|
|
|
199
201
|
// Baseline inside the row: lift it off the row bottom by the slack under the capitals.
|
|
200
202
|
y: rowTop(i) + (lineHeight - capHeight * size) / 2,
|
|
201
203
|
}));
|
|
202
|
-
const inner = [highlights, textBlock(placed, style, size,
|
|
204
|
+
const inner = [highlights, textBlock(placed, style, size, font)].filter(Boolean).join("\n");
|
|
203
205
|
return `${box(w, h, style)}\n${clipped(w, h, inner)}`;
|
|
204
206
|
}
|
|
@@ -9,8 +9,8 @@ import type { FormFieldSpec, FieldStyle } from "../forms/field.js";
|
|
|
9
9
|
* are intentionally "dumb": absolute geometry + semantic style, with no PDF
|
|
10
10
|
* operators, no font indices, and no object numbers - those are the backend's job.
|
|
11
11
|
*
|
|
12
|
-
* Coordinates are in PDF points (1/72")
|
|
13
|
-
*
|
|
12
|
+
* Coordinates are in PDF points (1/72") with the engine's top-left origin. The Y-flip to PDF's
|
|
13
|
+
* bottom-left happens once per page, at this seam (`PdfBackend.flipY`).
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
16
|
* Structure tag for accessible (PDF/UA) tagging. `role` is the PDF structure type the content maps to
|
|
@@ -140,6 +140,9 @@ export interface Link {
|
|
|
140
140
|
height: number;
|
|
141
141
|
href?: string;
|
|
142
142
|
dest?: string;
|
|
143
|
+
/** Accessible tagging only: the `/Link` structure element the annotation belongs to, and its text. */
|
|
144
|
+
structKey?: number;
|
|
145
|
+
alt?: string;
|
|
143
146
|
}
|
|
144
147
|
/**
|
|
145
148
|
* A named destination anchor - a jump TARGET for an internal `Link({ to })`. Like `Link` it draws
|
|
@@ -177,6 +180,13 @@ export interface FormFieldNode {
|
|
|
177
180
|
height: number;
|
|
178
181
|
field: FormFieldSpec;
|
|
179
182
|
style: FieldStyle;
|
|
183
|
+
/** Accessible tagging only: the `/Form` structure element the widget belongs to. */
|
|
184
|
+
structKey?: number;
|
|
185
|
+
/** The font the field's text inherits; its baked appearance draws in it when it is embedded. */
|
|
186
|
+
font?: {
|
|
187
|
+
family: string;
|
|
188
|
+
style: FontStyle;
|
|
189
|
+
};
|
|
180
190
|
}
|
|
181
191
|
/**
|
|
182
192
|
* Pushes an affine transform (a `q` + `cm` in the content stream): everything between this and the
|
|
@@ -84,6 +84,11 @@ export interface SizingParams {
|
|
|
84
84
|
/** width / height. Derives whichever axis is left open; see `resolveSize`. */
|
|
85
85
|
aspectRatio?: number;
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* The width rules of a box, for a piece of it split across pages: it keeps how wide it may be, and
|
|
89
|
+
* its height comes from what it holds on that page (CSS `box-decoration-break: clone`).
|
|
90
|
+
*/
|
|
91
|
+
export declare function widthRules(s: SizingParams): SizingParams;
|
|
87
92
|
/** Split `SizingParams` into the per-axis form `resolveSize` takes. */
|
|
88
93
|
export declare function extentSpecs(p: SizingParams): {
|
|
89
94
|
width: ExtentSpec;
|
|
@@ -91,6 +91,20 @@ export function resolveExtent(fixed, factor, boundedMax, hasBounded) {
|
|
|
91
91
|
return boundedMax * factor;
|
|
92
92
|
return undefined;
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* The width rules of a box, for a piece of it split across pages: it keeps how wide it may be, and
|
|
96
|
+
* its height comes from what it holds on that page (CSS `box-decoration-break: clone`).
|
|
97
|
+
*/
|
|
98
|
+
export function widthRules(s) {
|
|
99
|
+
return {
|
|
100
|
+
width: s.width,
|
|
101
|
+
widthFactor: s.widthFactor,
|
|
102
|
+
minWidth: s.minWidth,
|
|
103
|
+
maxWidth: s.maxWidth,
|
|
104
|
+
minWidthFactor: s.minWidthFactor,
|
|
105
|
+
maxWidthFactor: s.maxWidthFactor,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
94
108
|
/** Split `SizingParams` into the per-axis form `resolveSize` takes. */
|
|
95
109
|
export function extentSpecs(p) {
|
|
96
110
|
return {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { PDFElement } from "../elements/pdf-element.js";
|
|
2
2
|
import { ImageElement } from "../elements/image-element.js";
|
|
3
|
+
import { SvgElement } from "../elements/svg-element.js";
|
|
3
4
|
/**
|
|
4
5
|
* Walks the element tree (via `getProps`, the same shallow-reflection style as the validator) and
|
|
5
|
-
* returns every
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* returns every image - a bitmap, or an SVG (which needs this only when it comes from a URL). A
|
|
7
|
+
* pre-layout pass uses this to resolve each one's intrinsic size asynchronously: layout is synchronous
|
|
8
|
+
* and cannot await a decoder or a fetch, yet it needs the aspect ratio to give a width-only image its
|
|
9
|
+
* proportional height. Cheap - images are few and this runs once.
|
|
8
10
|
*/
|
|
9
|
-
export declare function collectImageElements(root: PDFElement): ImageElement[];
|
|
11
|
+
export declare function collectImageElements(root: PDFElement): (ImageElement | SvgElement)[];
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { PDFElement, hasChildrenProp, hasChildProp } from "../elements/pdf-element.js";
|
|
2
2
|
import { ImageElement } from "../elements/image-element.js";
|
|
3
|
+
import { SvgElement } from "../elements/svg-element.js";
|
|
3
4
|
/**
|
|
4
5
|
* Walks the element tree (via `getProps`, the same shallow-reflection style as the validator) and
|
|
5
|
-
* returns every
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* returns every image - a bitmap, or an SVG (which needs this only when it comes from a URL). A
|
|
7
|
+
* pre-layout pass uses this to resolve each one's intrinsic size asynchronously: layout is synchronous
|
|
8
|
+
* and cannot await a decoder or a fetch, yet it needs the aspect ratio to give a width-only image its
|
|
9
|
+
* proportional height. Cheap - images are few and this runs once.
|
|
8
10
|
*/
|
|
9
11
|
export function collectImageElements(root) {
|
|
10
12
|
const out = [];
|
|
11
13
|
const visit = (el) => {
|
|
12
|
-
if (el instanceof ImageElement)
|
|
14
|
+
if (el instanceof ImageElement || el instanceof SvgElement)
|
|
13
15
|
out.push(el);
|
|
14
16
|
const props = el.getProps();
|
|
15
17
|
if (hasChildrenProp(props))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FlexiblePDFElement, PDFElement } from "../elements/pdf-element.js";
|
|
2
|
+
import type { CrossAlign } from "./alignment.js";
|
|
3
|
+
/** The `Expanded`/`Spacer` that makes this child flexible, if any. */
|
|
4
|
+
export declare function flexibleOf(child: PDFElement): FlexiblePDFElement | undefined;
|
|
5
|
+
export declare function alignSelfOf(child: PDFElement): CrossAlign | undefined;
|
|
6
|
+
/** CSS `order`, 0 when unset. */
|
|
7
|
+
export declare function orderOf(child: PDFElement): number;
|
|
8
|
+
/** CSS `flex-shrink`, 1 when unset (the CSS default). */
|
|
9
|
+
export declare function flexShrinkOf(child: PDFElement): number;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { FlexiblePDFElement } from "../elements/pdf-element.js";
|
|
2
|
+
/**
|
|
3
|
+
* How a Row or Column sees a child as a flex item. A wrapper that only marks its child (a `Link`, an
|
|
4
|
+
* `Anchor`, a `DefaultTextStyle` - see `PDFElement.proxied`) is looked through, as CSS
|
|
5
|
+
* `display: contents` is: an `Expanded`, `alignSelf`, `order` or `flexShrink` inside it acts as if the
|
|
6
|
+
* wrapper were not there. A value set on the wrapper itself wins over one set inside it.
|
|
7
|
+
*/
|
|
8
|
+
/** The child, then each element it is transparent for, outermost first. */
|
|
9
|
+
function* itemChain(child) {
|
|
10
|
+
for (let el = child; el; el = el.proxied())
|
|
11
|
+
yield el;
|
|
12
|
+
}
|
|
13
|
+
/** The `Expanded`/`Spacer` that makes this child flexible, if any. */
|
|
14
|
+
export function flexibleOf(child) {
|
|
15
|
+
for (const el of itemChain(child))
|
|
16
|
+
if (el instanceof FlexiblePDFElement)
|
|
17
|
+
return el;
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
export function alignSelfOf(child) {
|
|
21
|
+
for (const el of itemChain(child))
|
|
22
|
+
if (el.alignSelf !== undefined)
|
|
23
|
+
return el.alignSelf;
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/** CSS `order`, 0 when unset. */
|
|
27
|
+
export function orderOf(child) {
|
|
28
|
+
for (const el of itemChain(child))
|
|
29
|
+
if (el.order !== undefined)
|
|
30
|
+
return el.order;
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
/** CSS `flex-shrink`, 1 when unset (the CSS default). */
|
|
34
|
+
export function flexShrinkOf(child) {
|
|
35
|
+
for (const el of itemChain(child))
|
|
36
|
+
if (el.flexShrink !== undefined)
|
|
37
|
+
return el.flexShrink;
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
@@ -33,11 +33,19 @@ export declare function isFragmentable(element: PDFElement): element is PDFEleme
|
|
|
33
33
|
* logs and clips, `"ignore"` clips silently.
|
|
34
34
|
*/
|
|
35
35
|
export type OverflowPolicy = "error" | "warn" | "ignore";
|
|
36
|
-
export declare function reportOverflow(child: PDFElement, childHeight: number, maxHeight: number, policy: OverflowPolicy): void;
|
|
37
36
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* The context for content inset by `chrome` on every page (a border, padding, a repeated header): a
|
|
38
|
+
* fresh page gives it that much less room. Every element that shrinks the region for its children
|
|
39
|
+
* hands this down with it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function insetContext(ctx: LayoutContext, chrome: number): LayoutContext;
|
|
42
|
+
export declare function reportOverflow(child: PDFElement, childHeight: number, maxHeight: number, ctx: LayoutContext): void;
|
|
43
|
+
/**
|
|
44
|
+
* True if packing `children` will hit a forced cut INSIDE them: a child that IS a break, contains one
|
|
45
|
+
* deeper in its subtree, or asks to break before/after itself. A flow container reports this from
|
|
46
|
+
* `hasForcedBreak` so its parent fragments it (runs `packChildren`) instead of placing it whole and
|
|
47
|
+
* swallowing the cut. A break before the FIRST child is not inside: the stack takes it as its own
|
|
48
|
+
* (`breaksBefore`), so it moves whole - which is what a `keepTogether` around it relies on.
|
|
41
49
|
*/
|
|
42
50
|
export declare function childrenForceBreak(children: PDFElement[]): boolean;
|
|
43
51
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BoxConstraints } from "./box-constraints.js";
|
|
2
|
+
import { diagnose, pageOf } from "../common/diagnostics.js";
|
|
2
3
|
export function isFragmentable(element) {
|
|
3
4
|
if (!("fragment" in element) ||
|
|
4
5
|
typeof element.fragment !== "function") {
|
|
@@ -10,24 +11,43 @@ export function isFragmentable(element) {
|
|
|
10
11
|
const canFragment = element.canFragment;
|
|
11
12
|
return typeof canFragment !== "function" || canFragment.call(element);
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The context for content inset by `chrome` on every page (a border, padding, a repeated header): a
|
|
16
|
+
* fresh page gives it that much less room. Every element that shrinks the region for its children
|
|
17
|
+
* hands this down with it.
|
|
18
|
+
*/
|
|
19
|
+
export function insetContext(ctx, chrome) {
|
|
20
|
+
if (ctx.pageBodyHeight === undefined || chrome === 0)
|
|
21
|
+
return ctx;
|
|
22
|
+
return { ...ctx, pageBodyHeight: ctx.pageBodyHeight - chrome };
|
|
23
|
+
}
|
|
24
|
+
export function reportOverflow(child, childHeight, maxHeight, ctx) {
|
|
25
|
+
const policy = ctx.onOverflow ?? "ignore";
|
|
16
26
|
const name = child.constructor.name.replace(/Element$/, "");
|
|
17
27
|
const detail = `${name} is ${Math.round(childHeight)}pt tall but the page region is only ` +
|
|
18
28
|
`${Math.round(maxHeight)}pt and it cannot be broken - reduce its size, give it a bounded ` +
|
|
19
29
|
`height, or let it split across pages.`;
|
|
20
30
|
if (policy === "error")
|
|
21
31
|
throw new Error(`Layout overflow: ${detail}`);
|
|
22
|
-
|
|
32
|
+
diagnose(ctx.diagnostics, {
|
|
33
|
+
code: "overflow-clipped",
|
|
34
|
+
severity: "warning",
|
|
35
|
+
message: `Layout overflow (clipped): ${detail}`,
|
|
36
|
+
about: child,
|
|
37
|
+
page: pageOf(ctx),
|
|
38
|
+
// `ignore` means "no console" - an `onDiagnostic` handler still hears of it.
|
|
39
|
+
fallback: policy === "ignore" ? "silent" : "warn",
|
|
40
|
+
});
|
|
23
41
|
}
|
|
24
42
|
/**
|
|
25
|
-
* True if packing `children` will hit a forced cut: a child that IS a break, contains one
|
|
26
|
-
* subtree, or asks to break before/after itself. A flow container reports this from
|
|
27
|
-
* its parent fragments it (runs `packChildren`) instead of placing it whole and
|
|
43
|
+
* True if packing `children` will hit a forced cut INSIDE them: a child that IS a break, contains one
|
|
44
|
+
* deeper in its subtree, or asks to break before/after itself. A flow container reports this from
|
|
45
|
+
* `hasForcedBreak` so its parent fragments it (runs `packChildren`) instead of placing it whole and
|
|
46
|
+
* swallowing the cut. A break before the FIRST child is not inside: the stack takes it as its own
|
|
47
|
+
* (`breaksBefore`), so it moves whole - which is what a `keepTogether` around it relies on.
|
|
28
48
|
*/
|
|
29
49
|
export function childrenForceBreak(children) {
|
|
30
|
-
return children.some((child) => child.hasForcedBreak() || child.breaksBefore() || child.breaksAfter());
|
|
50
|
+
return children.some((child, i) => child.hasForcedBreak() || (i > 0 && child.breaksBefore()) || child.breaksAfter());
|
|
31
51
|
}
|
|
32
52
|
/**
|
|
33
53
|
* Packs a vertical stack of children into `maxHeight`, in order. Children are measured
|
|
@@ -64,7 +84,7 @@ export function packChildren(children, maxHeight, width, ctx, gap = 0) {
|
|
|
64
84
|
// (CSS ignores break-before at the top of a page); that guard also prevents an empty page + a loop.
|
|
65
85
|
if (child.breaksBefore() && fitted.length > 0)
|
|
66
86
|
return spill(i, true);
|
|
67
|
-
const childHeight = child.
|
|
87
|
+
const childHeight = child.measure(BoxConstraints.loose(width, Infinity), ctx).height;
|
|
68
88
|
// A gap precedes every child except the first one placed in this region.
|
|
69
89
|
const lead = fitted.length > 0 ? gap : 0;
|
|
70
90
|
// Place whole only if it fits AND holds no forced break: a child that fits by height but contains
|
|
@@ -82,8 +102,10 @@ export function packChildren(children, maxHeight, width, ctx, gap = 0) {
|
|
|
82
102
|
const remaining = maxHeight - usedHeight - lead;
|
|
83
103
|
let placedPart = false;
|
|
84
104
|
let childBroke = false;
|
|
105
|
+
const below = ctx.belowContent || fitted.length > 0;
|
|
85
106
|
if (isFragmentable(child)) {
|
|
86
|
-
const
|
|
107
|
+
const childCtx = below && !ctx.belowContent ? { ...ctx, belowContent: true } : ctx;
|
|
108
|
+
const split = child.fragment(Math.max(0, remaining), width, childCtx);
|
|
87
109
|
childBroke = split.forceBreak ?? false;
|
|
88
110
|
if (split.fitted) {
|
|
89
111
|
fitted.push(split.fitted);
|
|
@@ -101,10 +123,11 @@ export function packChildren(children, maxHeight, width, ctx, gap = 0) {
|
|
|
101
123
|
}
|
|
102
124
|
}
|
|
103
125
|
if (!placedPart) {
|
|
104
|
-
if (
|
|
105
|
-
// Taller than
|
|
106
|
-
// so the next region still advances - and surface it per the overflow policy.
|
|
107
|
-
|
|
126
|
+
if (!below && !childBroke) {
|
|
127
|
+
// Taller than a whole fresh page and unsplittable: force it on (it overflows and is clipped)
|
|
128
|
+
// so the next region still advances - and surface it per the overflow policy. Below other
|
|
129
|
+
// content it simply waits for the next page.
|
|
130
|
+
reportOverflow(child, childHeight, maxHeight, ctx);
|
|
108
131
|
fitted.push(child);
|
|
109
132
|
}
|
|
110
133
|
else {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { BoxConstraints, Size } from "./box-constraints.js";
|
|
2
|
+
import type { LayoutContext } from "../elements/pdf-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* The layout cache - the one place a size is remembered. `PDFElement.measure` asks it before
|
|
5
|
+
* `computeSize` runs, and stores what `computeSize` returned.
|
|
6
|
+
*
|
|
7
|
+
* An element never changes after it is built, so its size is a pure function of its INPUTS: the
|
|
8
|
+
* constraints, plus the context fields listed in `Key` below. The same inputs again are answered from
|
|
9
|
+
* the element's own cache. That is what Flutter's dry-layout cache and Yoga's measure cache do; flex
|
|
10
|
+
* asks the same child the same question several times while it distributes space.
|
|
11
|
+
*
|
|
12
|
+
* What keeps it correct:
|
|
13
|
+
* - `computeSize` reads only the context fields in `Key`. A new field that can change a size is added
|
|
14
|
+
* HERE. `frame`, `belowContent`, `pageBodyHeight` and `onOverflow` steer placement and page breaks,
|
|
15
|
+
* never a size, so they are not part of it.
|
|
16
|
+
* - The page (`pageConfig`, `pageInfo`) is part of the key only for an element that
|
|
17
|
+
* `dependsOnPage()`. Every page and both passes bring new page objects, so keying everything on them
|
|
18
|
+
* would make the cache miss on each page.
|
|
19
|
+
* - Context values compare by IDENTITY. Code that derives one (a merged text style) returns the same
|
|
20
|
+
* object for the same input, or every element below it misses.
|
|
21
|
+
* - `fontEpoch` moves when a font face is added, and kerning can be switched on the same metrics; both
|
|
22
|
+
* change widths.
|
|
23
|
+
*
|
|
24
|
+
* The same key, without the constraints, decides when a subtree built at layout time can be reused
|
|
25
|
+
* (`BuiltFromContext`, below).
|
|
26
|
+
*
|
|
27
|
+
* Below this level sit value memos that are not size caches: glyph advances per font
|
|
28
|
+
* (`text/advance.ts`) and a Text's displayed content (`TextElement.display`).
|
|
29
|
+
*/
|
|
30
|
+
export declare const layoutCache: {
|
|
31
|
+
/** Off only in the test that renders every pinned document both ways and demands the same bytes. */
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
};
|
|
34
|
+
/** One element's remembered sizes, oldest overwritten first. */
|
|
35
|
+
export declare class SizeCache {
|
|
36
|
+
private entries;
|
|
37
|
+
private next;
|
|
38
|
+
get(constraints: BoxConstraints, ctx: LayoutContext, onPage: boolean): Size | undefined;
|
|
39
|
+
set(constraints: BoxConstraints, ctx: LayoutContext, onPage: boolean, size: Size): void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A subtree an element builds from the context at layout time (`Deferred`: a table whose `auto`
|
|
43
|
+
* columns are measured from its cells and fitted to its width). It is built again only when the
|
|
44
|
+
* context key or the width changes - the size rules and the one constraint building reads. Whether the
|
|
45
|
+
* page belongs to the key is decided by what was built: a subtree without a `PageBuilder` is the same
|
|
46
|
+
* on every page.
|
|
47
|
+
*/
|
|
48
|
+
export declare class BuiltFromContext<T extends {
|
|
49
|
+
dependsOnPage(): boolean;
|
|
50
|
+
}> {
|
|
51
|
+
private last?;
|
|
52
|
+
get(ctx: LayoutContext, width: number, build: () => T): T;
|
|
53
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The layout cache - the one place a size is remembered. `PDFElement.measure` asks it before
|
|
3
|
+
* `computeSize` runs, and stores what `computeSize` returned.
|
|
4
|
+
*
|
|
5
|
+
* An element never changes after it is built, so its size is a pure function of its INPUTS: the
|
|
6
|
+
* constraints, plus the context fields listed in `Key` below. The same inputs again are answered from
|
|
7
|
+
* the element's own cache. That is what Flutter's dry-layout cache and Yoga's measure cache do; flex
|
|
8
|
+
* asks the same child the same question several times while it distributes space.
|
|
9
|
+
*
|
|
10
|
+
* What keeps it correct:
|
|
11
|
+
* - `computeSize` reads only the context fields in `Key`. A new field that can change a size is added
|
|
12
|
+
* HERE. `frame`, `belowContent`, `pageBodyHeight` and `onOverflow` steer placement and page breaks,
|
|
13
|
+
* never a size, so they are not part of it.
|
|
14
|
+
* - The page (`pageConfig`, `pageInfo`) is part of the key only for an element that
|
|
15
|
+
* `dependsOnPage()`. Every page and both passes bring new page objects, so keying everything on them
|
|
16
|
+
* would make the cache miss on each page.
|
|
17
|
+
* - Context values compare by IDENTITY. Code that derives one (a merged text style) returns the same
|
|
18
|
+
* object for the same input, or every element below it misses.
|
|
19
|
+
* - `fontEpoch` moves when a font face is added, and kerning can be switched on the same metrics; both
|
|
20
|
+
* change widths.
|
|
21
|
+
*
|
|
22
|
+
* The same key, without the constraints, decides when a subtree built at layout time can be reused
|
|
23
|
+
* (`BuiltFromContext`, below).
|
|
24
|
+
*
|
|
25
|
+
* Below this level sit value memos that are not size caches: glyph advances per font
|
|
26
|
+
* (`text/advance.ts`) and a Text's displayed content (`TextElement.display`).
|
|
27
|
+
*/
|
|
28
|
+
export const layoutCache = {
|
|
29
|
+
/** Off only in the test that renders every pinned document both ways and demands the same bytes. */
|
|
30
|
+
enabled: true,
|
|
31
|
+
};
|
|
32
|
+
const contextKeyOf = (ctx, onPage) => ({
|
|
33
|
+
metrics: ctx.metrics,
|
|
34
|
+
fontEpoch: ctx.metrics?.fontEpoch ?? 0,
|
|
35
|
+
kerning: ctx.metrics?.kerningEnabled,
|
|
36
|
+
textStyle: ctx.textStyle,
|
|
37
|
+
pageConfig: onPage ? ctx.pageConfig : undefined,
|
|
38
|
+
pageInfo: onPage ? ctx.pageInfo : undefined,
|
|
39
|
+
boxPaddingBase: ctx.boxPaddingBase,
|
|
40
|
+
});
|
|
41
|
+
const sameContext = (a, b) => a.metrics === b.metrics &&
|
|
42
|
+
a.fontEpoch === b.fontEpoch &&
|
|
43
|
+
a.kerning === b.kerning &&
|
|
44
|
+
a.textStyle === b.textStyle &&
|
|
45
|
+
a.pageConfig === b.pageConfig &&
|
|
46
|
+
a.pageInfo === b.pageInfo &&
|
|
47
|
+
a.boxPaddingBase === b.boxPaddingBase;
|
|
48
|
+
const keyOf = (c, ctx, onPage) => ({
|
|
49
|
+
minWidth: c.minWidth,
|
|
50
|
+
maxWidth: c.maxWidth,
|
|
51
|
+
minHeight: c.minHeight,
|
|
52
|
+
maxHeight: c.maxHeight,
|
|
53
|
+
context: contextKeyOf(ctx, onPage),
|
|
54
|
+
});
|
|
55
|
+
const sameKey = (a, b) => a.minWidth === b.minWidth &&
|
|
56
|
+
a.maxWidth === b.maxWidth &&
|
|
57
|
+
a.minHeight === b.minHeight &&
|
|
58
|
+
a.maxHeight === b.maxHeight &&
|
|
59
|
+
sameContext(a.context, b.context);
|
|
60
|
+
/**
|
|
61
|
+
* How many different questions one element remembers. Flex asks a child at more than one width (its
|
|
62
|
+
* natural width, then its share), so one entry would evict itself.
|
|
63
|
+
*/
|
|
64
|
+
const ENTRIES = 4;
|
|
65
|
+
/** One element's remembered sizes, oldest overwritten first. */
|
|
66
|
+
export class SizeCache {
|
|
67
|
+
constructor() {
|
|
68
|
+
this.entries = [];
|
|
69
|
+
this.next = 0;
|
|
70
|
+
}
|
|
71
|
+
get(constraints, ctx, onPage) {
|
|
72
|
+
if (!layoutCache.enabled)
|
|
73
|
+
return undefined;
|
|
74
|
+
const key = keyOf(constraints, ctx, onPage);
|
|
75
|
+
const hit = this.entries.find((e) => sameKey(e.key, key));
|
|
76
|
+
// A copy: a caller that adjusts the size it was handed must not change the remembered one.
|
|
77
|
+
return hit && { ...hit.size };
|
|
78
|
+
}
|
|
79
|
+
set(constraints, ctx, onPage, size) {
|
|
80
|
+
const entry = { key: keyOf(constraints, ctx, onPage), size: { ...size } };
|
|
81
|
+
if (this.entries.length < ENTRIES)
|
|
82
|
+
this.entries.push(entry);
|
|
83
|
+
else
|
|
84
|
+
this.entries[this.next] = entry;
|
|
85
|
+
this.next = (this.next + 1) % ENTRIES;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A subtree an element builds from the context at layout time (`Deferred`: a table whose `auto`
|
|
90
|
+
* columns are measured from its cells and fitted to its width). It is built again only when the
|
|
91
|
+
* context key or the width changes - the size rules and the one constraint building reads. Whether the
|
|
92
|
+
* page belongs to the key is decided by what was built: a subtree without a `PageBuilder` is the same
|
|
93
|
+
* on every page.
|
|
94
|
+
*/
|
|
95
|
+
export class BuiltFromContext {
|
|
96
|
+
get(ctx, width, build) {
|
|
97
|
+
const last = this.last;
|
|
98
|
+
if (layoutCache.enabled &&
|
|
99
|
+
last &&
|
|
100
|
+
last.width === width &&
|
|
101
|
+
sameContext(last.key, contextKeyOf(ctx, last.value.dependsOnPage())))
|
|
102
|
+
return last.value;
|
|
103
|
+
const value = build();
|
|
104
|
+
this.last = { key: contextKeyOf(ctx, value.dependsOnPage()), width, value };
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PDFObjectManager } from "../utils/pdf-object-manager.js";
|
|
2
2
|
import { AnchorElement } from "../elements/layout/anchor-element.js";
|
|
3
|
+
import { type RenderScope } from "../utils/renderer-registry.js";
|
|
3
4
|
import { IRNode } from "../ir/display-list.js";
|
|
4
5
|
export declare class AnchorRenderer {
|
|
5
|
-
static render(element: AnchorElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
|
|
6
|
+
static render(element: AnchorElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
|
|
6
7
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { RendererRegistry } from "../utils/renderer-registry.js";
|
|
2
2
|
export class AnchorRenderer {
|
|
3
|
-
static async render(element, objectManager) {
|
|
4
|
-
const { name, child, y } = element.getProps();
|
|
5
|
-
|
|
6
|
-
const
|
|
3
|
+
static async render(element, objectManager, scope) {
|
|
4
|
+
const { name, child, y: ownY } = element.getProps();
|
|
5
|
+
// Its own position is relative to `scope.origin`; the IR wants page coordinates.
|
|
6
|
+
const y = scope.origin.y + (ownY ?? 0);
|
|
7
|
+
const childNodes = await RendererRegistry.render(child, objectManager, scope);
|
|
7
8
|
// The child draws normally; an Anchor IR node carries the name + scroll target for the page renderer
|
|
8
9
|
// to register as a named destination (it produces no content-stream ops itself).
|
|
9
10
|
return [...childNodes, { type: "anchor", y, name }];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PDFObjectManager } from "../utils/pdf-object-manager.js";
|
|
2
2
|
import { BookmarkElement } from "../elements/layout/bookmark-element.js";
|
|
3
|
+
import { type RenderScope } from "../utils/renderer-registry.js";
|
|
3
4
|
import { IRNode } from "../ir/display-list.js";
|
|
4
5
|
export declare class BookmarkRenderer {
|
|
5
|
-
static render(element: BookmarkElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
|
|
6
|
+
static render(element: BookmarkElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
|
|
6
7
|
}
|