@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,7 +1,9 @@
|
|
|
1
|
+
import { flexibleOf } from "../layout/flex-item.js";
|
|
1
2
|
import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
|
|
2
|
-
import { FlexLayoutHelper, HORIZONTAL_AXIS } from "../utils/flex-layout.js";
|
|
3
|
+
import { FlexLayoutHelper, HORIZONTAL_AXIS, } from "../utils/flex-layout.js";
|
|
3
4
|
import { stackMinIntrinsic, withDeclaredExtent } from "../layout/min-intrinsic.js";
|
|
4
|
-
import {
|
|
5
|
+
import { diagnose, pageOf } from "../common/diagnostics.js";
|
|
6
|
+
import { SizedPDFElement } from "./pdf-element.js";
|
|
5
7
|
/**
|
|
6
8
|
* Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
|
|
7
9
|
* left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
|
|
@@ -45,7 +47,7 @@ export class RowElement extends SizedPDFElement {
|
|
|
45
47
|
: [this.requested.height, this.requested.heightFactor];
|
|
46
48
|
if (requested.some((v) => v !== undefined))
|
|
47
49
|
return false;
|
|
48
|
-
const ownFlexChild = horizontal && this.children.some((c) => c
|
|
50
|
+
const ownFlexChild = horizontal && this.children.some((c) => flexibleOf(c) !== undefined);
|
|
49
51
|
return ownFlexChild || this.children.some((c) => c.needsBoundedMain(horizontal));
|
|
50
52
|
}
|
|
51
53
|
/** A Row lays out side by side, so widths add up (gaps included) and heights overlap. */
|
|
@@ -56,6 +58,53 @@ export class RowElement extends SizedPDFElement {
|
|
|
56
58
|
calculateLayout(constraints, offset, ctx) {
|
|
57
59
|
this.x = offset.x;
|
|
58
60
|
this.y = offset.y;
|
|
61
|
+
this.reportIgnoredBreak(ctx);
|
|
62
|
+
const size = this.sizeWith(constraints, ctx, "layout");
|
|
63
|
+
this.width = size.width;
|
|
64
|
+
this.height = size.height;
|
|
65
|
+
return size;
|
|
66
|
+
}
|
|
67
|
+
computeSize(constraints, ctx) {
|
|
68
|
+
return this.sizeWith(constraints, ctx, "measure");
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A row is one line: nothing inside it starts a new page. A `breakBefore` / `breakAfter` in a cell
|
|
72
|
+
* (a table row is a Row too) is therefore ignored - say so. A `PageBreak` reports itself.
|
|
73
|
+
*/
|
|
74
|
+
reportIgnoredBreak(ctx) {
|
|
75
|
+
if (this.declaredBreak === undefined) {
|
|
76
|
+
const find = (el, depth) => {
|
|
77
|
+
if (el.breaksBefore() || el.breaksAfter())
|
|
78
|
+
return el;
|
|
79
|
+
if (depth > 64)
|
|
80
|
+
return null;
|
|
81
|
+
for (const child of el.layoutChildren()) {
|
|
82
|
+
const hit = find(child, depth + 1);
|
|
83
|
+
if (hit)
|
|
84
|
+
return hit;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
this.declaredBreak = null;
|
|
89
|
+
for (const child of this.layoutChildren()) {
|
|
90
|
+
this.declaredBreak = find(child, 0);
|
|
91
|
+
if (this.declaredBreak)
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!this.declaredBreak)
|
|
96
|
+
return;
|
|
97
|
+
diagnose(ctx.diagnostics, {
|
|
98
|
+
code: "break-ignored",
|
|
99
|
+
severity: "warning",
|
|
100
|
+
message: "breakBefore / breakAfter inside a Row (or a table row) has no effect: a row is one line and " +
|
|
101
|
+
"never starts a new page. Put the break on the Row itself, or on a child of a Column.",
|
|
102
|
+
about: this.declaredBreak,
|
|
103
|
+
page: pageOf(ctx),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** The one sizing path: `layout` places the children (relative to this box), `measure` only sizes them. */
|
|
107
|
+
sizeWith(constraints, ctx, mode) {
|
|
59
108
|
// Relative sizing: a pinned extent (fixed or a fraction of the offered box, clamped) wins; else
|
|
60
109
|
// width fills the offered space (flex children split the leftover) and height is the tallest child.
|
|
61
110
|
const specs = extentSpecs(this.requested);
|
|
@@ -77,18 +126,19 @@ export class RowElement extends SizedPDFElement {
|
|
|
77
126
|
const crossAvail = boundedHeight ?? Infinity;
|
|
78
127
|
let result = { mainUsed: 0, crossUsed: 0 };
|
|
79
128
|
if (this.children.length > 0) {
|
|
80
|
-
result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail,
|
|
129
|
+
result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, {
|
|
81
130
|
gap: this.gap,
|
|
82
131
|
main: this.main,
|
|
83
132
|
cross: this.cross,
|
|
84
133
|
reverse: this.reverse,
|
|
85
134
|
wrap: this.wrap,
|
|
86
135
|
alignContent: this.alignContent,
|
|
87
|
-
}, ctx);
|
|
136
|
+
}, ctx, mode);
|
|
88
137
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
138
|
+
return { width: boundedWidth ?? result.mainUsed, height: boundedHeight ?? result.crossUsed };
|
|
139
|
+
}
|
|
140
|
+
layoutChildren() {
|
|
141
|
+
return this.children;
|
|
92
142
|
}
|
|
93
143
|
getProps() {
|
|
94
144
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type SvgSize } from "../svg/index.js";
|
|
2
2
|
import { BoxConstraints, Offset, Size, SizingParams } from "../layout/box-constraints.js";
|
|
3
3
|
import { LayoutContext, SizedPDFElement } from "./pdf-element.js";
|
|
4
|
-
/** Markup, a file path (Node only), or the file's bytes. */
|
|
4
|
+
/** Markup, a URL (`http(s):`, `data:`), a file path (Node only), or the file's bytes. */
|
|
5
5
|
export type SvgSource = string | Uint8Array;
|
|
6
6
|
interface SvgElementParams extends SizingParams {
|
|
7
7
|
source: SvgSource;
|
|
@@ -16,26 +16,50 @@ interface SvgElementParams extends SizingParams {
|
|
|
16
16
|
*
|
|
17
17
|
* Unlike a bitmap it needs no decoder, so its intrinsic size is known SYNCHRONOUSLY, in the
|
|
18
18
|
* constructor - which also means a broken file fails on the line that asked for it rather than deep
|
|
19
|
-
* inside a later render, the same bargain `addFont` makes.
|
|
19
|
+
* inside a later render, the same bargain `addFont` makes. An SVG from a URL is the exception: like
|
|
20
|
+
* an image from a URL it is fetched when the document renders (`resolveIntrinsicSize`, or the
|
|
21
|
+
* renderer for a subtree built during layout), and a dead link stops the render naming it.
|
|
20
22
|
*/
|
|
21
23
|
export declare class SvgElement extends SizedPDFElement {
|
|
22
|
-
private source
|
|
23
|
-
private intrinsic
|
|
24
|
+
private source?;
|
|
25
|
+
private intrinsic?;
|
|
26
|
+
private readonly url?;
|
|
27
|
+
private loading?;
|
|
28
|
+
private failed;
|
|
24
29
|
private alt?;
|
|
25
30
|
private requested;
|
|
26
31
|
private sizing;
|
|
27
32
|
private widthFactor?;
|
|
28
33
|
private heightFactor?;
|
|
29
34
|
constructor(params: SvgElementParams);
|
|
35
|
+
/**
|
|
36
|
+
* Parsed on purpose where the source is given: an unreadable file is an error where it was NAMED,
|
|
37
|
+
* not inside a render three call frames later. The whole document is walked, not just its root, so
|
|
38
|
+
* an element outside the subset surfaces here too. A logo parses in well under a millisecond.
|
|
39
|
+
*/
|
|
40
|
+
private parse;
|
|
41
|
+
/**
|
|
42
|
+
* Fetches and parses an SVG from a URL - once per render, however often it is asked. A no-op
|
|
43
|
+
* otherwise. A failure sticks for that render only: `fresh` (a new render begins) asks again.
|
|
44
|
+
*/
|
|
45
|
+
load(fresh?: boolean): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* The pre-layout pass, as for an `ImageElement`: best-effort, so a failure leaves the size unknown
|
|
48
|
+
* and the renderer, which loads again, surfaces the real error.
|
|
49
|
+
*/
|
|
50
|
+
resolveIntrinsicSize(): Promise<void>;
|
|
30
51
|
getProps(): {
|
|
31
52
|
x: number;
|
|
32
53
|
y: number;
|
|
33
54
|
width: number | undefined;
|
|
34
55
|
height: number | undefined;
|
|
35
|
-
source: string;
|
|
36
|
-
intrinsic: SvgSize;
|
|
56
|
+
source: string | undefined;
|
|
57
|
+
intrinsic: SvgSize | undefined;
|
|
37
58
|
alt: string | undefined;
|
|
38
59
|
};
|
|
39
60
|
calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
|
|
61
|
+
protected computeSize(constraints: BoxConstraints): Size;
|
|
62
|
+
/** Its size under these constraints - the one computation both `measure` and layout use. */
|
|
63
|
+
private sizeFor;
|
|
40
64
|
}
|
|
41
65
|
export {};
|
|
@@ -2,6 +2,8 @@ import { readFileBytes } from "../platform/node-fs.js";
|
|
|
2
2
|
import { SvgParseError, svgSize, svgToIr } from "../svg/index.js";
|
|
3
3
|
import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
|
|
4
4
|
import { SizedPDFElement } from "./pdf-element.js";
|
|
5
|
+
import { IMAGE_FETCH_LIMITS, ImageUrlError, isImageUrl } from "./image-element.js";
|
|
6
|
+
import { fetchBytes } from "../utils/fetch-bytes.js";
|
|
5
7
|
/** Markup is recognised by its first non-space character - anything else is treated as a path. */
|
|
6
8
|
const looksLikeMarkup = (value) => /^\s*[<]/.test(value);
|
|
7
9
|
/**
|
|
@@ -24,30 +26,78 @@ function readSource(path) {
|
|
|
24
26
|
*
|
|
25
27
|
* Unlike a bitmap it needs no decoder, so its intrinsic size is known SYNCHRONOUSLY, in the
|
|
26
28
|
* constructor - which also means a broken file fails on the line that asked for it rather than deep
|
|
27
|
-
* inside a later render, the same bargain `addFont` makes.
|
|
29
|
+
* inside a later render, the same bargain `addFont` makes. An SVG from a URL is the exception: like
|
|
30
|
+
* an image from a URL it is fetched when the document renders (`resolveIntrinsicSize`, or the
|
|
31
|
+
* renderer for a subtree built during layout), and a dead link stops the render naming it.
|
|
28
32
|
*/
|
|
29
33
|
export class SvgElement extends SizedPDFElement {
|
|
30
34
|
constructor(params) {
|
|
31
35
|
super({ x: 0, y: 0, width: params.width, height: params.height });
|
|
36
|
+
this.failed = false;
|
|
32
37
|
const { source, alt, widthFactor, heightFactor, ...sizing } = params;
|
|
33
38
|
this.widthFactor = widthFactor;
|
|
34
39
|
this.heightFactor = heightFactor;
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
if (typeof source === "string" && isImageUrl(source))
|
|
41
|
+
this.url = source;
|
|
42
|
+
else
|
|
43
|
+
this.parse(typeof source === "string"
|
|
37
44
|
? looksLikeMarkup(source)
|
|
38
45
|
? source
|
|
39
46
|
: new TextDecoder().decode(readSource(source))
|
|
40
|
-
: new TextDecoder().decode(source);
|
|
41
|
-
// Parsed here on purpose: an unreadable file is an error where it was NAMED, not inside a render
|
|
42
|
-
// three call frames later. The whole document is walked, not just its root, so an element outside
|
|
43
|
-
// the subset surfaces here too - which is what makes the error predictable rather than a lottery
|
|
44
|
-
// between construction and render time. A logo parses in well under a millisecond.
|
|
45
|
-
this.intrinsic = svgSize(this.source);
|
|
46
|
-
svgToIr(this.source, { x: 0, y: 0, width: 1, height: 1 });
|
|
47
|
+
: new TextDecoder().decode(source));
|
|
47
48
|
this.alt = alt;
|
|
48
49
|
this.sizing = sizing;
|
|
49
50
|
this.requested = { width: params.width, height: params.height };
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Parsed on purpose where the source is given: an unreadable file is an error where it was NAMED,
|
|
54
|
+
* not inside a render three call frames later. The whole document is walked, not just its root, so
|
|
55
|
+
* an element outside the subset surfaces here too. A logo parses in well under a millisecond.
|
|
56
|
+
*/
|
|
57
|
+
parse(source) {
|
|
58
|
+
this.intrinsic = svgSize(source);
|
|
59
|
+
svgToIr(source, { x: 0, y: 0, width: 1, height: 1 });
|
|
60
|
+
this.source = source;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Fetches and parses an SVG from a URL - once per render, however often it is asked. A no-op
|
|
64
|
+
* otherwise. A failure sticks for that render only: `fresh` (a new render begins) asks again.
|
|
65
|
+
*/
|
|
66
|
+
async load(fresh = false) {
|
|
67
|
+
const url = this.url;
|
|
68
|
+
if (url === undefined)
|
|
69
|
+
return;
|
|
70
|
+
if (fresh && this.failed)
|
|
71
|
+
this.loading = undefined;
|
|
72
|
+
this.loading ?? (this.loading = fetchBytes(url, "SVG", ImageUrlError, IMAGE_FETCH_LIMITS)
|
|
73
|
+
.then((bytes) => {
|
|
74
|
+
try {
|
|
75
|
+
this.parse(new TextDecoder().decode(bytes));
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
throw new ImageUrlError(`the SVG at ${url} could not be read: ${String(e?.message ?? e)}`);
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.then(() => {
|
|
82
|
+
this.failed = false;
|
|
83
|
+
}, (e) => {
|
|
84
|
+
this.failed = true;
|
|
85
|
+
throw e;
|
|
86
|
+
}));
|
|
87
|
+
await this.loading;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The pre-layout pass, as for an `ImageElement`: best-effort, so a failure leaves the size unknown
|
|
91
|
+
* and the renderer, which loads again, surfaces the real error.
|
|
92
|
+
*/
|
|
93
|
+
async resolveIntrinsicSize() {
|
|
94
|
+
try {
|
|
95
|
+
await this.load(true); // a render begins here
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// the renderer reports it
|
|
99
|
+
}
|
|
100
|
+
}
|
|
51
101
|
getProps() {
|
|
52
102
|
return {
|
|
53
103
|
x: this.x,
|
|
@@ -60,8 +110,18 @@ export class SvgElement extends SizedPDFElement {
|
|
|
60
110
|
};
|
|
61
111
|
}
|
|
62
112
|
calculateLayout(constraints, offset, _ctx) {
|
|
113
|
+
const size = this.sizeFor(constraints);
|
|
63
114
|
this.x = offset.x;
|
|
64
115
|
this.y = offset.y;
|
|
116
|
+
this.width = size.width;
|
|
117
|
+
this.height = size.height;
|
|
118
|
+
return size;
|
|
119
|
+
}
|
|
120
|
+
computeSize(constraints) {
|
|
121
|
+
return this.sizeFor(constraints);
|
|
122
|
+
}
|
|
123
|
+
/** Its size under these constraints - the one computation both `measure` and layout use. */
|
|
124
|
+
sizeFor(constraints) {
|
|
65
125
|
const specs = extentSpecs({
|
|
66
126
|
...this.sizing,
|
|
67
127
|
width: this.requested.width,
|
|
@@ -73,12 +133,22 @@ export class SvgElement extends SizedPDFElement {
|
|
|
73
133
|
let w = resolved.width;
|
|
74
134
|
let h = resolved.height;
|
|
75
135
|
const bounds = resolved.constraints;
|
|
136
|
+
// An SVG from a URL that was never fetched sits in content built DURING layout (a PageBuilder, an auto
|
|
137
|
+
// table column), which the pass before layout cannot reach. Its ratio is unknown here, so it needs
|
|
138
|
+
// its whole box - a named error instead of a drawing sized to nothing. A FAILED fetch is not this
|
|
139
|
+
// case: the renderer reports that one by its URL.
|
|
140
|
+
if (this.url !== undefined &&
|
|
141
|
+
this.loading === undefined &&
|
|
142
|
+
(w === undefined || h === undefined)) {
|
|
143
|
+
throw new ImageUrlError(`the SVG at ${this.url} sits in content built during layout (a PageBuilder or an auto table ` +
|
|
144
|
+
`column), where its size cannot be read before it is placed - give it both a width and a ` +
|
|
145
|
+
`height, or an aspectRatio`);
|
|
146
|
+
}
|
|
76
147
|
// One pinned axis derives the other from the drawing's own ratio (CSS `width: 120; height: auto`).
|
|
77
148
|
// A bitmap needs a decode pass before it can do this; an SVG knows it from its viewBox.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const ratio = this.intrinsic.width / this.intrinsic.height;
|
|
149
|
+
const intrinsic = this.intrinsic ?? { width: 0, height: 0 };
|
|
150
|
+
if (this.sizing.aspectRatio === undefined && intrinsic.width > 0 && intrinsic.height > 0) {
|
|
151
|
+
const ratio = intrinsic.width / intrinsic.height;
|
|
82
152
|
if (w !== undefined && h === undefined)
|
|
83
153
|
h = bounds.constrainHeight(w / ratio);
|
|
84
154
|
else if (h !== undefined && w === undefined)
|
|
@@ -86,8 +156,9 @@ export class SvgElement extends SizedPDFElement {
|
|
|
86
156
|
}
|
|
87
157
|
// Nothing pinned: draw at the intrinsic size, as an unsized `<img>` does - never fill the region,
|
|
88
158
|
// which would blow a 24pt icon up to the whole page.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
159
|
+
return {
|
|
160
|
+
width: w ?? bounds.constrainWidth(intrinsic.width),
|
|
161
|
+
height: h ?? bounds.constrainHeight(intrinsic.height),
|
|
162
|
+
};
|
|
92
163
|
}
|
|
93
164
|
}
|
|
@@ -4,7 +4,7 @@ import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
|
|
|
4
4
|
import type { Direction } from "../text/bidi.js";
|
|
5
5
|
import { type TextTransform } from "../text/text-style.js";
|
|
6
6
|
import { Fragmentable, FragmentResult } from "../layout/fragmentation.js";
|
|
7
|
-
import { TextOverflow } from "../text/line-breaker.js";
|
|
7
|
+
import { type SegmentDefaults, TextOverflow } from "../text/line-breaker.js";
|
|
8
8
|
import { HorizontalAlignment, LayoutContext, SizedPDFElement } from "./pdf-element.js";
|
|
9
9
|
import type { Hyphenator } from "../text/word-splitting.js";
|
|
10
10
|
export interface TextSegment {
|
|
@@ -83,6 +83,9 @@ interface TextElementParams {
|
|
|
83
83
|
hyphenate?: Hyphenator;
|
|
84
84
|
/** Accessibility role for the tagged structure tree (heading level or paragraph; default `"p"`). */
|
|
85
85
|
role?: TextRole;
|
|
86
|
+
/** Set by pagination, not by authors: this element is the first N lines of a paragraph that
|
|
87
|
+
* continues on the next page. See `LineOptions.shownLines`. */
|
|
88
|
+
shownLines?: number;
|
|
86
89
|
}
|
|
87
90
|
export declare class TextElement extends SizedPDFElement implements Fragmentable {
|
|
88
91
|
private readonly rawFontSize?;
|
|
@@ -123,12 +126,17 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
|
|
|
123
126
|
private ligatures;
|
|
124
127
|
private hyphenate?;
|
|
125
128
|
private content;
|
|
129
|
+
/** What `covered` had to remove, reported where the text is laid out on its page (see
|
|
130
|
+
* `calculateLayout`). A split paragraph's first piece carries its parent's, as `lostBefore`. */
|
|
131
|
+
private lostGlyphs;
|
|
132
|
+
private lostBefore;
|
|
126
133
|
private maxLines?;
|
|
127
134
|
private orphans?;
|
|
128
135
|
private widows?;
|
|
129
136
|
private overflow;
|
|
130
137
|
private readonly role?;
|
|
131
|
-
|
|
138
|
+
private readonly shownLines?;
|
|
139
|
+
constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow, lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, breakWord, hyphenate, ligatures, role, shownLines, }: TextElementParams);
|
|
132
140
|
private resolveStyle;
|
|
133
141
|
/**
|
|
134
142
|
* The last `display(metrics)` result, with what it depended on - recasing, coverage and the fallback
|
|
@@ -153,7 +161,7 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
|
|
|
153
161
|
* back unchanged, so an ordinary document is byte-identical.
|
|
154
162
|
*/
|
|
155
163
|
private covered;
|
|
156
|
-
/** The content with `text-transform` applied, and nothing else. */
|
|
164
|
+
/** The content as spans, with `text-transform` applied, and nothing else. */
|
|
157
165
|
private recased;
|
|
158
166
|
private lineOptions;
|
|
159
167
|
private applyStyle;
|
|
@@ -161,23 +169,29 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
|
|
|
161
169
|
* Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
|
|
162
170
|
* the rest become a remainder `TextElement` re-wrapped on the next page. If not even one
|
|
163
171
|
* line fits, nothing is forced here - the caller (the container) decides whether to move
|
|
164
|
-
* the whole element on for progress.
|
|
172
|
+
* the whole element on for progress.
|
|
165
173
|
*/
|
|
166
174
|
fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
|
|
167
|
-
|
|
168
|
-
private
|
|
175
|
+
/** How the text is broken: the knobs `textHeight` and the renderer pass too. */
|
|
176
|
+
private segmentDefaults;
|
|
169
177
|
/** The orphan/widow minimums for this paragraph; unset means the CSS default of 2 lines each. */
|
|
170
178
|
private breakRule;
|
|
171
179
|
private cloneWithContent;
|
|
172
180
|
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
181
|
+
/**
|
|
182
|
+
* The size `calculateLayout` would take, without placing anything. Resolving the inherited style
|
|
183
|
+
* and filling the memos below are its only writes, and both are a pure function of the context:
|
|
184
|
+
* the same inputs leave the same state, which a later layout reads back unchanged.
|
|
185
|
+
*/
|
|
186
|
+
/** A text's size reads only the width it may take: it wraps to it, and a height bound never changes
|
|
187
|
+
* the lines (a box that is too short cuts them, it does not reflow them). */
|
|
188
|
+
protected sizeInputs(constraints: BoxConstraints): BoxConstraints;
|
|
189
|
+
protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
|
|
190
|
+
private measuredHeight;
|
|
173
191
|
/** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
|
|
174
|
-
* Must match the
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* into one term; we must group the same way. Adding word and space as two separate steps is
|
|
178
|
-
* algebraically equal but, because floating-point addition is not associative, drifts by a sub-ULP
|
|
179
|
-
* - enough to tip a borderline string (e.g. "20 Jun 2026", wider than "04 Jul 2026" only because
|
|
180
|
-
* 'n' beats 'l') one bit over its own width, dropping the last word onto a second line. */
|
|
192
|
+
* Must match the breaker's one-line sum BIT for bit - it adds `width + (space + word)`, and the same
|
|
193
|
+
* numbers grouped differently drift by a sub-ULP, enough to wrap a text inside its own natural-width
|
|
194
|
+
* box. `singleLineWidth` is that sum. */
|
|
181
195
|
private naturalWidth;
|
|
182
196
|
/**
|
|
183
197
|
* The widest piece of this text that cannot be broken - CSS's `min-content` width. A shrinking flex
|
|
@@ -198,6 +212,10 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
|
|
|
198
212
|
fontStyle: FontStyle;
|
|
199
213
|
color: Color;
|
|
200
214
|
content: string | TextSegment[];
|
|
215
|
+
/** The content as it is measured and drawn: spans, recased, split by font, drawable glyphs only. */
|
|
216
|
+
spans: TextSegment[];
|
|
217
|
+
/** How the text is broken - the settings it is measured with, so drawing breaks it the same. */
|
|
218
|
+
breaking: SegmentDefaults;
|
|
201
219
|
textAlignment: HorizontalAlignment;
|
|
202
220
|
maxLines: number | undefined;
|
|
203
221
|
orphans: number | undefined;
|
|
@@ -215,6 +233,7 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
|
|
|
215
233
|
ligatures: boolean;
|
|
216
234
|
hyphenate: Hyphenator | undefined;
|
|
217
235
|
role: TextRole | undefined;
|
|
236
|
+
shownLines: number | undefined;
|
|
218
237
|
};
|
|
219
238
|
}
|
|
220
239
|
export {};
|