@jasy/pdf 1.0.0-beta.4 → 1.0.0-beta.6
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/README.md +3 -2
- package/dist/api/color.d.ts +1 -24
- package/dist/api/color.js +2 -278
- package/dist/api/content.d.ts +7 -2
- package/dist/api/content.js +17 -7
- package/dist/api/descriptor.js +30 -3
- package/dist/api/flow.d.ts +55 -0
- package/dist/api/flow.js +71 -0
- package/dist/api/gradient.d.ts +1 -53
- package/dist/api/gradient.js +2 -97
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/layout.js +4 -3
- package/dist/api/structure.d.ts +1 -1
- package/dist/api/table.js +3 -1
- package/dist/api/text.d.ts +8 -8
- package/dist/api/text.js +6 -0
- package/dist/canvas/painter.d.ts +2 -2
- package/dist/canvas/painter.js +2 -2
- package/dist/common/color-input.d.ts +24 -0
- package/dist/common/color-input.js +278 -0
- package/dist/common/diagnostics.d.ts +9 -1
- package/dist/common/gradient.d.ts +53 -0
- package/dist/common/gradient.js +97 -0
- package/dist/elements/container-element.d.ts +3 -0
- package/dist/elements/container-element.js +17 -5
- package/dist/elements/image-element.d.ts +37 -26
- package/dist/elements/image-element.js +88 -50
- package/dist/elements/index.d.ts +3 -0
- package/dist/elements/index.js +3 -0
- package/dist/elements/layout/column-break-element.d.ts +17 -0
- package/dist/elements/layout/column-break-element.js +26 -0
- package/dist/elements/layout/deferred-element.d.ts +9 -1
- package/dist/elements/layout/deferred-element.js +18 -3
- package/dist/elements/layout/expanded-element.d.ts +4 -0
- package/dist/elements/layout/expanded-element.js +14 -1
- package/dist/elements/layout/flow-element.d.ts +109 -0
- package/dist/elements/layout/flow-element.js +317 -0
- package/dist/elements/layout/keep-together-element.js +14 -2
- package/dist/elements/layout/padding-element.d.ts +3 -0
- package/dist/elements/layout/padding-element.js +9 -2
- package/dist/elements/layout/page-builder-element.js +2 -2
- package/dist/elements/layout/positioned-element.d.ts +9 -2
- package/dist/elements/layout/positioned-element.js +6 -2
- package/dist/elements/layout/proxy-element.d.ts +4 -0
- package/dist/elements/layout/proxy-element.js +12 -0
- package/dist/elements/layout/region-element.d.ts +53 -0
- package/dist/elements/layout/region-element.js +109 -0
- package/dist/elements/layout/repeating-header-element.d.ts +3 -0
- package/dist/elements/layout/repeating-header-element.js +16 -1
- package/dist/elements/layout/rotated-element.d.ts +2 -0
- package/dist/elements/layout/rotated-element.js +8 -0
- package/dist/elements/pdf-element.d.ts +32 -3
- package/dist/elements/pdf-element.js +50 -1
- package/dist/elements/rectangle-element.d.ts +12 -6
- package/dist/elements/rectangle-element.js +31 -12
- package/dist/elements/row-element.d.ts +6 -12
- package/dist/elements/row-element.js +34 -4
- package/dist/elements/svg-element.js +4 -10
- package/dist/elements/text-element.d.ts +17 -1
- package/dist/elements/text-element.js +63 -40
- package/dist/ir/display-list.d.ts +6 -1
- package/dist/layout/box-constraints.d.ts +12 -0
- package/dist/layout/box-constraints.js +17 -0
- package/dist/layout/collect-images.js +8 -4
- package/dist/layout/flex-item.d.ts +2 -0
- package/dist/layout/flex-item.js +14 -2
- package/dist/layout/fragmentation.d.ts +11 -5
- package/dist/layout/fragmentation.js +27 -7
- package/dist/layout/layout-cache.d.ts +8 -8
- package/dist/layout/layout-cache.js +10 -7
- package/dist/platform/browser-fs.d.ts +1 -2
- package/dist/platform/browser-fs.js +3 -6
- package/dist/platform/node-fs.d.ts +0 -1
- package/dist/platform/node-fs.js +0 -4
- package/dist/renderer/column-break-renderer.d.ts +8 -0
- package/dist/renderer/column-break-renderer.js +15 -0
- package/dist/renderer/emoji-image.js +3 -1
- package/dist/renderer/flow-renderer.d.ts +16 -0
- package/dist/renderer/flow-renderer.js +21 -0
- package/dist/renderer/image-renderer.js +31 -54
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.js +2 -0
- package/dist/renderer/page-renderer.js +3 -4
- package/dist/renderer/pdf-backend.js +9 -2
- package/dist/renderer/pdf-document-renderer.js +2 -1
- package/dist/renderer/pdf-renderer.js +9 -0
- package/dist/renderer/positioned-renderer.d.ts +14 -2
- package/dist/renderer/positioned-renderer.js +40 -4
- package/dist/renderer/rectangle-renderer.d.ts +1 -1
- package/dist/renderer/rectangle-renderer.js +11 -6
- package/dist/renderer/rotated-renderer.js +3 -2
- package/dist/renderer/text-renderer.js +25 -28
- package/dist/svg/index.d.ts +1 -1
- package/dist/svg/index.js +26 -5
- package/dist/svg/style.js +1 -1
- package/dist/text/line-breaker.d.ts +23 -0
- package/dist/text/line-breaker.js +142 -65
- package/dist/text/line-metrics.d.ts +2 -0
- package/dist/text/line-metrics.js +5 -1
- package/dist/text/word-splitting.js +11 -5
- package/dist/utils/flex-layout.d.ts +3 -1
- package/dist/utils/flex-layout.js +141 -33
- package/dist/utils/image-helper.d.ts +59 -11
- package/dist/utils/image-helper.js +207 -80
- package/dist/utils/pdf-object-manager.d.ts +6 -3
- package/dist/utils/pdf-object-manager.js +31 -7
- package/dist/utils/renderer-registry.d.ts +6 -0
- package/dist/utils/renderer-registry.js +1 -1
- package/dist/utils/ttf-parser.js +3 -2
- package/package.json +9 -2
|
@@ -35,18 +35,6 @@ interface RowElementParams extends WithChildren {
|
|
|
35
35
|
/** Start everything after this row on a fresh page (CSS `break-after: page`). */
|
|
36
36
|
breakAfter?: boolean;
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
|
|
40
|
-
* left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
|
|
41
|
-
* take their natural width, `ExpandedElement`/Spacer children split the leftover width by
|
|
42
|
-
* `flex`, and `gap` is inserted between them. The row fills the width it is offered and
|
|
43
|
-
* shrink-wraps its height to the tallest child (unless a height is forced on it).
|
|
44
|
-
*
|
|
45
|
-
* Cross/main alignment is the next foundation slice; today children sit at the top-left
|
|
46
|
-
* (cross start, main start). The row is atomic w.r.t. pagination - it reflows whole if it
|
|
47
|
-
* does not fit (handled by the parent's `packChildren`); synchronized cell splitting is a
|
|
48
|
-
* Grid/Table concern.
|
|
49
|
-
*/
|
|
50
38
|
export declare class RowElement extends SizedPDFElement {
|
|
51
39
|
private children;
|
|
52
40
|
private gap;
|
|
@@ -62,6 +50,7 @@ export declare class RowElement extends SizedPDFElement {
|
|
|
62
50
|
breaksBefore(): boolean;
|
|
63
51
|
breaksAfter(): boolean;
|
|
64
52
|
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
53
|
+
maxExtent(horizontal: boolean): number;
|
|
65
54
|
/** The mirror of `ContainerElement`: a Row's OWN flex child needs a bounded width, and the need of any
|
|
66
55
|
* descendant propagates on both axes (see the Column for the reasoning). */
|
|
67
56
|
needsBoundedMain(horizontal: boolean): boolean;
|
|
@@ -78,6 +67,11 @@ export declare class RowElement extends SizedPDFElement {
|
|
|
78
67
|
private reportIgnoredBreak;
|
|
79
68
|
/** The one sizing path: `layout` places the children (relative to this box), `measure` only sizes them. */
|
|
80
69
|
private sizeWith;
|
|
70
|
+
private percentPaddingChild?;
|
|
71
|
+
/** A child's `%` padding resolves against this Row's width (CSS: the containing block), which the Row
|
|
72
|
+
* never offers - its children get their natural size - so it is told. Only when a child uses it,
|
|
73
|
+
* since every key below would otherwise change with the Row's width. */
|
|
74
|
+
private childContext;
|
|
81
75
|
layoutChildren(): readonly PDFElement[];
|
|
82
76
|
getProps(): {
|
|
83
77
|
x: number;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { flexibleOf } from "../layout/flex-item.js";
|
|
1
|
+
import { flexibleOf, hasPercentPaddingItem } from "../layout/flex-item.js";
|
|
2
2
|
import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
|
|
3
3
|
import { FlexLayoutHelper, HORIZONTAL_AXIS, } from "../utils/flex-layout.js";
|
|
4
4
|
import { stackMinIntrinsic, withDeclaredExtent } from "../layout/min-intrinsic.js";
|
|
5
5
|
import { diagnose, pageOf } from "../common/diagnostics.js";
|
|
6
|
-
import { SizedPDFElement } from "./pdf-element.js";
|
|
6
|
+
import { SizedPDFElement, withPaddingBase, } from "./pdf-element.js";
|
|
7
7
|
/**
|
|
8
8
|
* Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
|
|
9
9
|
* left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
|
|
@@ -16,6 +16,8 @@ import { SizedPDFElement } from "./pdf-element.js";
|
|
|
16
16
|
* does not fit (handled by the parent's `packChildren`); synchronized cell splitting is a
|
|
17
17
|
* Grid/Table concern.
|
|
18
18
|
*/
|
|
19
|
+
/** How far a line may run past its box before it is reported, in points (floating-point slack). */
|
|
20
|
+
const WIDE_EPSILON = 0.01;
|
|
19
21
|
export class RowElement extends SizedPDFElement {
|
|
20
22
|
constructor({ children, gap, main, cross, reverse, wrap, alignContent, width, height, breakBefore, breakAfter, ...sizing }) {
|
|
21
23
|
super({ x: 0, y: 0 });
|
|
@@ -39,6 +41,9 @@ export class RowElement extends SizedPDFElement {
|
|
|
39
41
|
relativeSizeFactor(horizontal) {
|
|
40
42
|
return horizontal ? this.requested.widthFactor : this.requested.heightFactor;
|
|
41
43
|
}
|
|
44
|
+
maxExtent(horizontal) {
|
|
45
|
+
return (horizontal ? this.requested.maxWidth : this.requested.maxHeight) ?? Infinity;
|
|
46
|
+
}
|
|
42
47
|
/** The mirror of `ContainerElement`: a Row's OWN flex child needs a bounded width, and the need of any
|
|
43
48
|
* descendant propagates on both axes (see the Column for the reasoning). */
|
|
44
49
|
needsBoundedMain(horizontal) {
|
|
@@ -124,7 +129,10 @@ export class RowElement extends SizedPDFElement {
|
|
|
124
129
|
// Horizontal stack: main = width (children fill it), cross = height (children can stretch to it).
|
|
125
130
|
const mainAvail = boundedWidth ?? Infinity;
|
|
126
131
|
const crossAvail = boundedHeight ?? Infinity;
|
|
127
|
-
let result = {
|
|
132
|
+
let result = {
|
|
133
|
+
mainUsed: 0,
|
|
134
|
+
crossUsed: 0,
|
|
135
|
+
};
|
|
128
136
|
if (this.children.length > 0) {
|
|
129
137
|
result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, {
|
|
130
138
|
gap: this.gap,
|
|
@@ -133,10 +141,32 @@ export class RowElement extends SizedPDFElement {
|
|
|
133
141
|
reverse: this.reverse,
|
|
134
142
|
wrap: this.wrap,
|
|
135
143
|
alignContent: this.alignContent,
|
|
136
|
-
}, ctx, mode);
|
|
144
|
+
}, this.childContext(ctx, mainAvail), mode);
|
|
145
|
+
}
|
|
146
|
+
const used = result.widestLine ?? result.mainUsed;
|
|
147
|
+
if (mode === "layout" && used > mainAvail + WIDE_EPSILON) {
|
|
148
|
+
diagnose(ctx.diagnostics, {
|
|
149
|
+
code: "overflow-wide",
|
|
150
|
+
severity: "warning",
|
|
151
|
+
message: `A Row's children need ${Math.round(used)}pt but it is ` +
|
|
152
|
+
`${Math.round(mainAvail)}pt wide; they are drawn past its edge. Let them wrap ` +
|
|
153
|
+
'(`breakWord`, `wrap`), make room, or clip with a Box\'s `overflow: "hidden"`.',
|
|
154
|
+
about: this,
|
|
155
|
+
page: pageOf(ctx),
|
|
156
|
+
fallback: "silent",
|
|
157
|
+
});
|
|
137
158
|
}
|
|
138
159
|
return { width: boundedWidth ?? result.mainUsed, height: boundedHeight ?? result.crossUsed };
|
|
139
160
|
}
|
|
161
|
+
/** A child's `%` padding resolves against this Row's width (CSS: the containing block), which the Row
|
|
162
|
+
* never offers - its children get their natural size - so it is told. Only when a child uses it,
|
|
163
|
+
* since every key below would otherwise change with the Row's width. */
|
|
164
|
+
childContext(ctx, mainAvail) {
|
|
165
|
+
this.percentPaddingChild ?? (this.percentPaddingChild = this.children.some(hasPercentPaddingItem));
|
|
166
|
+
if (!this.percentPaddingChild)
|
|
167
|
+
return ctx;
|
|
168
|
+
return withPaddingBase(ctx, Number.isFinite(mainAvail) ? mainAvail : undefined);
|
|
169
|
+
}
|
|
140
170
|
layoutChildren() {
|
|
141
171
|
return this.children;
|
|
142
172
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileBytes } from "../platform/node-fs.js";
|
|
2
2
|
import { SvgParseError, svgSize, svgToIr } from "../svg/index.js";
|
|
3
|
-
import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
|
|
3
|
+
import { extentSpecs, resolveSize, sizeAtOwnRatio, } from "../layout/box-constraints.js";
|
|
4
4
|
import { SizedPDFElement } from "./pdf-element.js";
|
|
5
5
|
import { IMAGE_FETCH_LIMITS, ImageUrlError, isImageUrl } from "./image-element.js";
|
|
6
6
|
import { fetchBytes } from "../utils/fetch-bytes.js";
|
|
@@ -144,18 +144,12 @@ export class SvgElement extends SizedPDFElement {
|
|
|
144
144
|
`column), where its size cannot be read before it is placed - give it both a width and a ` +
|
|
145
145
|
`height, or an aspectRatio`);
|
|
146
146
|
}
|
|
147
|
-
//
|
|
148
|
-
//
|
|
147
|
+
// Its own ratio fills an open axis, as for a bitmap - which needs a decode pass for it; an SVG
|
|
148
|
+
// knows it from its viewBox. Unsized, it is its own size scaled down to fit: a 24pt icon stays 24pt.
|
|
149
149
|
const intrinsic = this.intrinsic ?? { width: 0, height: 0 };
|
|
150
150
|
if (this.sizing.aspectRatio === undefined && intrinsic.width > 0 && intrinsic.height > 0) {
|
|
151
|
-
|
|
152
|
-
if (w !== undefined && h === undefined)
|
|
153
|
-
h = bounds.constrainHeight(w / ratio);
|
|
154
|
-
else if (h !== undefined && w === undefined)
|
|
155
|
-
w = bounds.constrainWidth(h * ratio);
|
|
151
|
+
({ width: w, height: h } = sizeAtOwnRatio(w, h, intrinsic, bounds));
|
|
156
152
|
}
|
|
157
|
-
// Nothing pinned: draw at the intrinsic size, as an unsized `<img>` does - never fill the region,
|
|
158
|
-
// which would blow a 24pt icon up to the whole page.
|
|
159
153
|
return {
|
|
160
154
|
width: w ?? bounds.constrainWidth(intrinsic.width),
|
|
161
155
|
height: h ?? bounds.constrainHeight(intrinsic.height),
|
|
@@ -22,8 +22,16 @@ export interface TextSegment {
|
|
|
22
22
|
/** Unset inherits the Text's own setting; `true`/`false` overrides it for this run only. */
|
|
23
23
|
underline?: boolean;
|
|
24
24
|
strikethrough?: boolean;
|
|
25
|
+
skipInk?: boolean;
|
|
25
26
|
/** Extra space after every glyph, in points; unset inherits the Text's own value. */
|
|
26
27
|
letterSpacing?: number;
|
|
28
|
+
textTransform?: TextTransform;
|
|
29
|
+
wordSpacing?: number;
|
|
30
|
+
ligatures?: boolean;
|
|
31
|
+
/** How a word wider than the box splits, for the characters of THIS run (Chrome: a break after a
|
|
32
|
+
* character is allowed by that character's own `overflow-wrap`). */
|
|
33
|
+
breakWord?: boolean;
|
|
34
|
+
hyphenate?: Hyphenator;
|
|
27
35
|
/**
|
|
28
36
|
* CSS `vertical-align`, for a footnote marker or a formula index. Shifts THIS run's baseline;
|
|
29
37
|
* it does not resize the text, and it does not change the line's height - set `fontSize` yourself
|
|
@@ -119,6 +127,7 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
|
|
|
119
127
|
private direction;
|
|
120
128
|
private fontFallback;
|
|
121
129
|
private resolved?;
|
|
130
|
+
private broken;
|
|
122
131
|
private textTransform;
|
|
123
132
|
private wordSpacing;
|
|
124
133
|
private textIndent;
|
|
@@ -187,7 +196,14 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
|
|
|
187
196
|
* the lines (a box that is too short cuts them, it does not reflow them). */
|
|
188
197
|
protected sizeInputs(constraints: BoxConstraints): BoxConstraints;
|
|
189
198
|
protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
|
|
190
|
-
|
|
199
|
+
/**
|
|
200
|
+
* The one sizing path, for measuring and layout alike: a bounded width wraps to it (in a Column), an
|
|
201
|
+
* unbounded one takes the natural single-line width (in a Row). The lines are what layout checks.
|
|
202
|
+
* The style is resolved here, as drawing reads it.
|
|
203
|
+
*/
|
|
204
|
+
/** The text broken at `width`: once per context and width, for measuring, layout and splitting. */
|
|
205
|
+
private linesAt;
|
|
206
|
+
private sizeWith;
|
|
191
207
|
/** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
|
|
192
208
|
* Must match the breaker's one-line sum BIT for bit - it adds `width + (space + word)`, and the same
|
|
193
209
|
* numbers grouped differently drift by a sub-ULP, enough to wrap a text inside its own natural-width
|
|
@@ -3,14 +3,15 @@ import { BoxConstraints } from "../layout/box-constraints.js";
|
|
|
3
3
|
import { applyTextTransform } from "../text/text-style.js";
|
|
4
4
|
import { splitByFont } from "../text/font-fallback.js";
|
|
5
5
|
import { runAdvance } from "../text/advance.js";
|
|
6
|
-
import { singleLineWidth, MAX_SPACE_SHRINK, breakSegmentsIntoLines, } from "../text/line-breaker.js";
|
|
6
|
+
import { runStyleOf, singleLineWidth, MAX_SPACE_SHRINK, breakSegmentsIntoLines, } from "../text/line-breaker.js";
|
|
7
7
|
import { hyphenPieces } from "../text/hyphen-breaks.js";
|
|
8
8
|
import { adjustForOrphansWidows, DEFAULT_ORPHANS, DEFAULT_WIDOWS, } from "../text/orphans-widows.js";
|
|
9
|
-
import { lineBoxForSegmentLine,
|
|
9
|
+
import { lineBoxForSegmentLine, linesHeight } from "../text/line-metrics.js";
|
|
10
10
|
import { HorizontalAlignment, SizedPDFElement } from "./pdf-element.js";
|
|
11
11
|
import { normalizeContent } from "../text/whitespace.js";
|
|
12
12
|
import { coverText } from "../text/glyph-coverage.js";
|
|
13
|
-
import { pageOf } from "../common/diagnostics.js";
|
|
13
|
+
import { diagnose, pageOf } from "../common/diagnostics.js";
|
|
14
|
+
import { BuiltFromContext } from "../layout/layout-cache.js";
|
|
14
15
|
/** How far a run sits off the baseline, as a fraction of its font size (up is positive). */
|
|
15
16
|
export const VERTICAL_TEXT_SHIFT = {
|
|
16
17
|
baseline: 0,
|
|
@@ -31,6 +32,7 @@ function finitePoints(value, name) {
|
|
|
31
32
|
export class TextElement extends SizedPDFElement {
|
|
32
33
|
constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow = "clip", lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, breakWord, hyphenate, ligatures, role, shownLines, }) {
|
|
33
34
|
super({ x: 0, y: 0 });
|
|
35
|
+
this.broken = new BuiltFromContext(() => false);
|
|
34
36
|
/** What `covered` had to remove, reported where the text is laid out on its page (see
|
|
35
37
|
* `calculateLayout`). A split paragraph's first piece carries its parent's, as `lostBefore`. */
|
|
36
38
|
this.lostGlyphs = [];
|
|
@@ -140,14 +142,15 @@ export class TextElement extends SizedPDFElement {
|
|
|
140
142
|
recased() {
|
|
141
143
|
// A string is one span: from here on, measuring, breaking and drawing know only spans.
|
|
142
144
|
const spans = typeof this.content === "string" ? [{ content: this.content }] : this.content;
|
|
143
|
-
|
|
145
|
+
const transformOf = (seg) => seg.textTransform ?? this.textTransform;
|
|
146
|
+
if (spans.every((seg) => transformOf(seg) === "none"))
|
|
144
147
|
return spans;
|
|
145
148
|
// The capitalisation state runs THROUGH the spans: `span("hel") + span("lo")` is one word.
|
|
146
149
|
let atWordStart = true;
|
|
147
150
|
return spans.map((seg) => {
|
|
148
|
-
const done = applyTextTransform(seg.content,
|
|
151
|
+
const done = applyTextTransform(seg.content, transformOf(seg), atWordStart);
|
|
149
152
|
atWordStart = done.atWordStart;
|
|
150
|
-
return { ...seg, content: done.text };
|
|
153
|
+
return done.text === seg.content ? seg : { ...seg, content: done.text };
|
|
151
154
|
});
|
|
152
155
|
}
|
|
153
156
|
lineOptions() {
|
|
@@ -194,7 +197,7 @@ export class TextElement extends SizedPDFElement {
|
|
|
194
197
|
// would cut a paragraph at a line count it is never drawn with. Each line's height comes from
|
|
195
198
|
// the fonts on it, so lines are packed by their cumulative height.
|
|
196
199
|
const defaults = this.segmentDefaults();
|
|
197
|
-
const lines =
|
|
200
|
+
const lines = this.linesAt(width, ctx);
|
|
198
201
|
let used = 0;
|
|
199
202
|
let packed = 0;
|
|
200
203
|
for (const line of lines) {
|
|
@@ -251,7 +254,8 @@ export class TextElement extends SizedPDFElement {
|
|
|
251
254
|
// the same width reproduces exactly those lines (greedy is deterministic).
|
|
252
255
|
cloneWithContent(content, indent = this.textIndent, piece = {}) {
|
|
253
256
|
return new TextElement({
|
|
254
|
-
|
|
257
|
+
// Cut from `display()`, already recased - a span's own transform too.
|
|
258
|
+
content: content.map((seg) => (seg.textTransform ?? "none") === "none" ? seg : { ...seg, textTransform: "none" }),
|
|
255
259
|
fontSize: this.fontSize,
|
|
256
260
|
fontFamily: this.fontFamily,
|
|
257
261
|
fontStyle: this.fontStyle,
|
|
@@ -271,8 +275,8 @@ export class TextElement extends SizedPDFElement {
|
|
|
271
275
|
textIndent: indent,
|
|
272
276
|
role: this.role,
|
|
273
277
|
fontFallback: this.fontFallback,
|
|
274
|
-
//
|
|
275
|
-
//
|
|
278
|
+
// Already recased: `capitalize` again would capitalise the half of a word continued on the next
|
|
279
|
+
// page.
|
|
276
280
|
textTransform: "none",
|
|
277
281
|
breakWord: this.breakWord,
|
|
278
282
|
hyphenate: this.hyphenate,
|
|
@@ -281,12 +285,7 @@ export class TextElement extends SizedPDFElement {
|
|
|
281
285
|
}).asPieceOf(this); // a wrapped remainder is the SAME logical paragraph (one P across pages)
|
|
282
286
|
}
|
|
283
287
|
calculateLayout(constraints, offset, ctx) {
|
|
284
|
-
|
|
285
|
-
// (e.g. inside a Row) means the text takes its natural single-line width and does
|
|
286
|
-
// not wrap. Sized by `measure`, so a measured text and a laid-out one cannot differ. The style is
|
|
287
|
-
// resolved here too: a size from the cache skips `computeSize`, and drawing reads the style.
|
|
288
|
-
this.resolveStyle(ctx);
|
|
289
|
-
const size = this.measure(constraints, ctx);
|
|
288
|
+
const { size, lines } = this.sizeWith(constraints, ctx);
|
|
290
289
|
this.x = offset.x;
|
|
291
290
|
this.y = offset.y;
|
|
292
291
|
this.resolved = this.display(ctx.metrics);
|
|
@@ -295,6 +294,20 @@ export class TextElement extends SizedPDFElement {
|
|
|
295
294
|
ctx.metrics.reportMissingGlyph?.(lost, this, pageOf(ctx));
|
|
296
295
|
this.width = size.width;
|
|
297
296
|
this.height = size.height;
|
|
297
|
+
// A line with a piece that could not break is drawn past the edge - say so.
|
|
298
|
+
const overrun = Math.max(0, ...lines.map((line) => line.overrun));
|
|
299
|
+
if (constraints.hasBoundedWidth && overrun > 0.01) {
|
|
300
|
+
diagnose(ctx.diagnostics, {
|
|
301
|
+
code: "overflow-wide",
|
|
302
|
+
severity: "warning",
|
|
303
|
+
message: `A line of this Text runs ${Math.round(overrun)}pt past its ${Math.round(size.width)}pt; ` +
|
|
304
|
+
"a word in it cannot break. Let it break (`breakWord`, `hyphenate`), make room, or clip " +
|
|
305
|
+
'with a Box\'s `overflow: "hidden"`.',
|
|
306
|
+
about: this,
|
|
307
|
+
page: pageOf(ctx),
|
|
308
|
+
fallback: "silent",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
298
311
|
// Top-left coordinates (y = top of the text box). The baseline offset and the
|
|
299
312
|
// Y-flip are applied downstream (the line-builder positions baselines, the seam
|
|
300
313
|
// flips to PDF), so the element stays coordinate-system-blind.
|
|
@@ -311,29 +324,42 @@ export class TextElement extends SizedPDFElement {
|
|
|
311
324
|
return BoxConstraints.loose(constraints.maxWidth, Infinity);
|
|
312
325
|
}
|
|
313
326
|
computeSize(constraints, ctx) {
|
|
327
|
+
return this.sizeWith(constraints, ctx).size;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* The one sizing path, for measuring and layout alike: a bounded width wraps to it (in a Column), an
|
|
331
|
+
* unbounded one takes the natural single-line width (in a Row). The lines are what layout checks.
|
|
332
|
+
* The style is resolved here, as drawing reads it.
|
|
333
|
+
*/
|
|
334
|
+
/** The text broken at `width`: once per context and width, for measuring, layout and splitting. */
|
|
335
|
+
linesAt(width, ctx) {
|
|
336
|
+
return this.broken.get(ctx, width, () => breakSegmentsIntoLines(this.display(ctx.metrics), this.segmentDefaults(), width, ctx.metrics, this.maxLines, this.overflow));
|
|
337
|
+
}
|
|
338
|
+
sizeWith(constraints, ctx) {
|
|
314
339
|
this.resolveStyle(ctx);
|
|
315
|
-
const content = this.display(ctx.metrics);
|
|
316
340
|
const width = constraints.hasBoundedWidth
|
|
317
341
|
? constraints.maxWidth
|
|
318
342
|
: this.naturalWidth(ctx.metrics);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
343
|
+
const defaults = this.segmentDefaults();
|
|
344
|
+
const lines = this.linesAt(width, ctx);
|
|
345
|
+
return {
|
|
346
|
+
size: { width, height: linesHeight(lines, defaults, ctx.metrics, this.lineHeight) },
|
|
347
|
+
lines,
|
|
348
|
+
};
|
|
323
349
|
}
|
|
324
350
|
/** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
|
|
325
351
|
* Must match the breaker's one-line sum BIT for bit - it adds `width + (space + word)`, and the same
|
|
326
352
|
* numbers grouped differently drift by a sub-ULP, enough to wrap a text inside its own natural-width
|
|
327
353
|
* box. `singleLineWidth` is that sum. */
|
|
328
354
|
naturalWidth(metrics) {
|
|
329
|
-
|
|
330
|
-
const oneLine = (text, family, size, style, letterSpacing) => singleLineWidth(text,
|
|
331
|
-
// `ligatures` is part of the font: it decides which glyphs are drawn, hence the width.
|
|
332
|
-
{ fontFamily: family, fontSize: size, fontStyle: style, ligatures: this.ligatures }, metrics, letterSpacing, this.wordSpacing);
|
|
355
|
+
const defaults = this.segmentDefaults();
|
|
333
356
|
// The FIRST line starts `textIndent` in, so an unbounded box has to be that much wider.
|
|
334
357
|
const indent = Math.max(0, this.textIndent);
|
|
335
|
-
return this.display(metrics).reduce((sum, seg) =>
|
|
336
|
-
|
|
358
|
+
return this.display(metrics).reduce((sum, seg) => {
|
|
359
|
+
// The breaker's own sum, to the last bit - see `singleLineWidth`.
|
|
360
|
+
const run = runStyleOf(seg, defaults);
|
|
361
|
+
return (sum + singleLineWidth(seg.content, run.font, metrics, run.letterSpacing, run.wordSpacing));
|
|
362
|
+
}, indent);
|
|
337
363
|
}
|
|
338
364
|
/**
|
|
339
365
|
* The widest piece of this text that cannot be broken - CSS's `min-content` width. A shrinking flex
|
|
@@ -350,25 +376,22 @@ export class TextElement extends SizedPDFElement {
|
|
|
350
376
|
return 0;
|
|
351
377
|
this.resolveStyle(ctx);
|
|
352
378
|
const metrics = ctx.metrics;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const font = {
|
|
357
|
-
fontFamily: family,
|
|
358
|
-
fontSize: size,
|
|
359
|
-
fontStyle: style,
|
|
360
|
-
ligatures: this.ligatures,
|
|
361
|
-
};
|
|
379
|
+
// The run style is the FONT a piece is measured in: `ligatures` decides which glyphs are drawn and
|
|
380
|
+
// therefore how wide it is. Leaving it out would measure a floor the drawing never matches.
|
|
381
|
+
const widest = (text, run) => {
|
|
362
382
|
// Split on the same boundaries the breaker uses - a hard break ends a line too, and so may a
|
|
363
|
-
// hyphen inside a word.
|
|
364
|
-
const pieces =
|
|
365
|
-
|
|
383
|
+
// hyphen inside a word. A run that may split its words may split them anywhere.
|
|
384
|
+
const pieces = run.splitting.breakWord || run.splitting.hyphenate
|
|
385
|
+
? [...text]
|
|
386
|
+
: text.split(/[ \n]/).flatMap(hyphenPieces);
|
|
387
|
+
return pieces.reduce((max, piece) => Math.max(max, runAdvance(metrics, piece, run.font, run.letterSpacing)), 0);
|
|
366
388
|
};
|
|
389
|
+
const defaults = this.segmentDefaults();
|
|
367
390
|
const content = this.display(metrics);
|
|
368
391
|
// Segments join without a space, so a word may straddle two of them; taking the widest piece per
|
|
369
392
|
// segment can therefore UNDER-estimate. That errs towards squeezing a little too far, which lands
|
|
370
393
|
// back on the old overflow behaviour rather than on something new.
|
|
371
|
-
return content.reduce((max, seg) => Math.max(max, widest(seg.content, seg
|
|
394
|
+
return content.reduce((max, seg) => Math.max(max, widest(seg.content, runStyleOf(seg, defaults))), 0);
|
|
372
395
|
}
|
|
373
396
|
getProps() {
|
|
374
397
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ImageColor, Orientation } from "../utils/image-helper.js";
|
|
1
2
|
import { Color } from "../common/color.js";
|
|
2
3
|
import { FontStyle } from "../utils/pdf-object-manager.js";
|
|
3
4
|
import type { FormFieldSpec, FieldStyle } from "../forms/field.js";
|
|
@@ -62,7 +63,7 @@ export interface Rect {
|
|
|
62
63
|
y: number;
|
|
63
64
|
width: number;
|
|
64
65
|
height: number;
|
|
65
|
-
/** Solid
|
|
66
|
+
/** Solid color, or a gradient whose anchor points are in absolute page coordinates. */
|
|
66
67
|
fill?: Color | Gradient;
|
|
67
68
|
stroke?: Color;
|
|
68
69
|
strokeWidth: number;
|
|
@@ -94,6 +95,10 @@ export interface Image {
|
|
|
94
95
|
imageType: string;
|
|
95
96
|
/** Flate-compressed DeviceGray alpha channel for a transparent PNG, embedded as the XObject's /SMask. */
|
|
96
97
|
smask?: string;
|
|
98
|
+
/** What the samples mean (a JPEG keeps the space it was saved in); absent = RGB. */
|
|
99
|
+
color?: ImageColor;
|
|
100
|
+
/** How the stored pixels turn upright inside the placement box (EXIF); absent = as stored. */
|
|
101
|
+
orientation?: Orientation;
|
|
97
102
|
/** cover/contain fits clip the placement to the element's original frame. */
|
|
98
103
|
clip?: {
|
|
99
104
|
x: number;
|
|
@@ -110,6 +110,18 @@ export declare function resolveSize(width: ExtentSpec, height: ExtentSpec, aspec
|
|
|
110
110
|
height?: number;
|
|
111
111
|
constraints: BoxConstraints;
|
|
112
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* The open axes of a box that has its own size (an image, an SVG), after `resolveSize`: one pinned axis
|
|
115
|
+
* derives the other from the own ratio; none pinned is the own size, scaled down to `bounds` and never
|
|
116
|
+
* up - the cap is what keeps it on the page.
|
|
117
|
+
*/
|
|
118
|
+
export declare function sizeAtOwnRatio(width: number | undefined, height: number | undefined, own: {
|
|
119
|
+
width: number;
|
|
120
|
+
height: number;
|
|
121
|
+
}, bounds: BoxConstraints): {
|
|
122
|
+
width?: number;
|
|
123
|
+
height?: number;
|
|
124
|
+
};
|
|
113
125
|
/** The size an element resolves to and returns UP the tree. */
|
|
114
126
|
export interface Size {
|
|
115
127
|
width: number;
|
|
@@ -167,3 +167,20 @@ export function resolveSize(width, height, aspectRatio, constraints) {
|
|
|
167
167
|
constraints: bounds,
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* The open axes of a box that has its own size (an image, an SVG), after `resolveSize`: one pinned axis
|
|
172
|
+
* derives the other from the own ratio; none pinned is the own size, scaled down to `bounds` and never
|
|
173
|
+
* up - the cap is what keeps it on the page.
|
|
174
|
+
*/
|
|
175
|
+
export function sizeAtOwnRatio(width, height, own, bounds) {
|
|
176
|
+
const ratio = own.width / own.height;
|
|
177
|
+
if (width === undefined && height === undefined) {
|
|
178
|
+
// The width clamp does the width; a height cap is turned into a width at the ratio.
|
|
179
|
+
width = bounds.constrainWidth(Math.min(own.width, bounds.maxHeight * ratio));
|
|
180
|
+
}
|
|
181
|
+
if (width !== undefined && height === undefined)
|
|
182
|
+
height = bounds.constrainHeight(width / ratio);
|
|
183
|
+
else if (height !== undefined && width === undefined)
|
|
184
|
+
width = bounds.constrainWidth(height * ratio);
|
|
185
|
+
return { width, height };
|
|
186
|
+
}
|
|
@@ -19,10 +19,10 @@ export function collectImageElements(root) {
|
|
|
19
19
|
visit(c);
|
|
20
20
|
else if (hasChildProp(props))
|
|
21
21
|
visit(props.child);
|
|
22
|
-
// Wrappers that hold a subtree outside `children`/`child`: a Page's header/footer
|
|
23
|
-
// RepeatingHeaderElement's `body` (a Table with a header wraps its rows there)
|
|
24
|
-
// DeferredElement
|
|
25
|
-
//
|
|
22
|
+
// Wrappers that hold a subtree outside `children`/`child`: a Page's header/footer, a
|
|
23
|
+
// RepeatingHeaderElement's `body` (a Table with a header wraps its rows there), and the `sources` a
|
|
24
|
+
// DeferredElement builds from. What a PageBuilder builds does not exist yet; an image there that
|
|
25
|
+
// cannot tell its size without waiting is reported by its layout (`image-unsized`).
|
|
26
26
|
const w = props;
|
|
27
27
|
if (w.header instanceof PDFElement)
|
|
28
28
|
visit(w.header);
|
|
@@ -30,6 +30,10 @@ export function collectImageElements(root) {
|
|
|
30
30
|
visit(w.footer);
|
|
31
31
|
if (w.body instanceof PDFElement)
|
|
32
32
|
visit(w.body);
|
|
33
|
+
if (Array.isArray(w.sources))
|
|
34
|
+
for (const s of w.sources)
|
|
35
|
+
if (s instanceof PDFElement)
|
|
36
|
+
visit(s);
|
|
33
37
|
};
|
|
34
38
|
visit(root);
|
|
35
39
|
return out;
|
|
@@ -5,5 +5,7 @@ export declare function flexibleOf(child: PDFElement): FlexiblePDFElement | unde
|
|
|
5
5
|
export declare function alignSelfOf(child: PDFElement): CrossAlign | undefined;
|
|
6
6
|
/** CSS `order`, 0 when unset. */
|
|
7
7
|
export declare function orderOf(child: PDFElement): number;
|
|
8
|
+
/** The child is a `Box` with percentage padding. */
|
|
9
|
+
export declare function hasPercentPaddingItem(child: PDFElement): boolean;
|
|
8
10
|
/** CSS `flex-shrink`, 1 when unset (the CSS default). */
|
|
9
11
|
export declare function flexShrinkOf(child: PDFElement): number;
|
package/dist/layout/flex-item.js
CHANGED
|
@@ -5,10 +5,15 @@ import { FlexiblePDFElement } from "../elements/pdf-element.js";
|
|
|
5
5
|
* `display: contents` is: an `Expanded`, `alignSelf`, `order` or `flexShrink` inside it acts as if the
|
|
6
6
|
* wrapper were not there. A value set on the wrapper itself wins over one set inside it.
|
|
7
7
|
*/
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* The child, then each element it is transparent for, outermost first. An `Expanded` is one of them:
|
|
10
|
+
* `Expanded(child)` is `flex: 1` on the child, so the child's `alignSelf` and `order` are the item's.
|
|
11
|
+
*/
|
|
9
12
|
function* itemChain(child) {
|
|
10
|
-
for (let el = child; el;
|
|
13
|
+
for (let el = child; el;) {
|
|
11
14
|
yield el;
|
|
15
|
+
el = el.proxied() ?? (el instanceof FlexiblePDFElement ? el.layoutChildren()[0] : undefined);
|
|
16
|
+
}
|
|
12
17
|
}
|
|
13
18
|
/** The `Expanded`/`Spacer` that makes this child flexible, if any. */
|
|
14
19
|
export function flexibleOf(child) {
|
|
@@ -30,6 +35,13 @@ export function orderOf(child) {
|
|
|
30
35
|
return el.order;
|
|
31
36
|
return 0;
|
|
32
37
|
}
|
|
38
|
+
/** The child is a `Box` with percentage padding. */
|
|
39
|
+
export function hasPercentPaddingItem(child) {
|
|
40
|
+
for (const el of itemChain(child))
|
|
41
|
+
if (el.hasPercentPadding())
|
|
42
|
+
return true;
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
33
45
|
/** CSS `flex-shrink`, 1 when unset (the CSS default). */
|
|
34
46
|
export function flexShrinkOf(child) {
|
|
35
47
|
for (const el of itemChain(child))
|
|
@@ -9,13 +9,15 @@ import { LayoutContext, PDFElement } from "../elements/pdf-element.js";
|
|
|
9
9
|
* rest, so no information flows back UP after a break. This one-way flow is what keeps
|
|
10
10
|
* fragmentation terminating (no overflow-cascade fixpoint).
|
|
11
11
|
*/
|
|
12
|
+
export type ForcedBreak = boolean | "column";
|
|
12
13
|
export interface FragmentResult {
|
|
13
14
|
fitted: PDFElement | null;
|
|
14
15
|
remainder: PDFElement | null;
|
|
15
|
-
/** The split ended at a FORCED
|
|
16
|
-
* remainder
|
|
17
|
-
*
|
|
18
|
-
|
|
16
|
+
/** The split ended at a FORCED break, not because the region filled up: `true` for a page break (the
|
|
17
|
+
* remainder starts a fresh page), `"column"` for a `ColumnBreak` in a Flow's Region (it starts the
|
|
18
|
+
* next Region; the Flow consumes it). An enclosing flow stops packing too - so this bubbles up
|
|
19
|
+
* through nested `fragment()` calls. Absent/false for an ordinary height-driven break. */
|
|
20
|
+
forceBreak?: ForcedBreak;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* An element that knows how to split itself across regions. Width is already finalized
|
|
@@ -40,6 +42,8 @@ export type OverflowPolicy = "error" | "warn" | "ignore";
|
|
|
40
42
|
*/
|
|
41
43
|
export declare function insetContext(ctx: LayoutContext, chrome: number): LayoutContext;
|
|
42
44
|
export declare function reportOverflow(child: PDFElement, childHeight: number, maxHeight: number, ctx: LayoutContext): void;
|
|
45
|
+
/** Content cut off at the bottom, under the author's `onOverflow` policy. */
|
|
46
|
+
export declare function reportClipped(about: PDFElement, detail: string, ctx: LayoutContext): void;
|
|
43
47
|
/**
|
|
44
48
|
* True if packing `children` will hit a forced cut INSIDE them: a child that IS a break, contains one
|
|
45
49
|
* deeper in its subtree, or asks to break before/after itself. A flow container reports this from
|
|
@@ -48,6 +52,8 @@ export declare function reportOverflow(child: PDFElement, childHeight: number, m
|
|
|
48
52
|
* (`breaksBefore`), so it moves whole - which is what a `keepTogether` around it relies on.
|
|
49
53
|
*/
|
|
50
54
|
export declare function childrenForceBreak(children: PDFElement[]): boolean;
|
|
55
|
+
/** The packer kept every child as it was - none came back as a piece of itself (a sized Flow). */
|
|
56
|
+
export declare function packedUnchanged(children: PDFElement[], fitted: PDFElement[]): boolean;
|
|
51
57
|
/**
|
|
52
58
|
* Packs a vertical stack of children into `maxHeight`, in order. Children are measured
|
|
53
59
|
* against `width` (unbounded height) and added until one would overflow; that straddling
|
|
@@ -65,5 +71,5 @@ export declare function childrenForceBreak(children: PDFElement[]): boolean;
|
|
|
65
71
|
export declare function packChildren(children: PDFElement[], maxHeight: number, width: number, ctx: LayoutContext, gap?: number): {
|
|
66
72
|
fitted: PDFElement[];
|
|
67
73
|
remainder: PDFElement[];
|
|
68
|
-
forceBreak:
|
|
74
|
+
forceBreak: ForcedBreak;
|
|
69
75
|
};
|