@jasy/pdf 1.0.0-alpha.8 → 1.0.0-beta.1
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 +29 -10
- package/dist/api/color.js +69 -0
- package/dist/api/content.d.ts +45 -5
- package/dist/api/content.js +96 -7
- package/dist/api/descriptor.js +6 -1
- package/dist/api/dimension.d.ts +57 -0
- package/dist/api/dimension.js +27 -0
- package/dist/api/font-url.d.ts +28 -0
- package/dist/api/font-url.js +135 -0
- package/dist/api/gradient.d.ts +53 -0
- package/dist/api/gradient.js +97 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/insets.d.ts +19 -12
- package/dist/api/insets.js +18 -7
- package/dist/api/layout.d.ts +20 -6
- package/dist/api/layout.js +31 -6
- package/dist/api/page-builder.d.ts +8 -0
- package/dist/api/page-builder.js +12 -0
- package/dist/api/structure.d.ts +12 -0
- package/dist/api/structure.js +54 -7
- package/dist/api/text.d.ts +65 -8
- package/dist/api/text.js +38 -5
- package/dist/canvas/painter.d.ts +102 -0
- package/dist/canvas/painter.js +183 -0
- package/dist/edit/document.d.ts +14 -4
- package/dist/edit/document.js +15 -22
- package/dist/edit/fill.d.ts +19 -2
- package/dist/edit/fill.js +63 -34
- package/dist/edit/flatten.d.ts +13 -1
- package/dist/edit/flatten.js +38 -13
- package/dist/edit/index.d.ts +2 -0
- package/dist/edit/index.js +3 -1
- package/dist/edit/objects.d.ts +5 -0
- package/dist/edit/objects.js +27 -0
- package/dist/edit/writer.d.ts +7 -0
- package/dist/edit/writer.js +23 -1
- package/dist/elements/canvas-element.d.ts +38 -0
- package/dist/elements/canvas-element.js +47 -0
- package/dist/elements/container-element.d.ts +23 -1
- package/dist/elements/container-element.js +31 -10
- package/dist/elements/image-element.d.ts +18 -4
- package/dist/elements/image-element.js +27 -12
- package/dist/elements/index.d.ts +2 -0
- package/dist/elements/index.js +2 -0
- package/dist/elements/layout/expanded-element.d.ts +5 -1
- package/dist/elements/layout/expanded-element.js +16 -3
- package/dist/elements/layout/keep-together-element.d.ts +2 -0
- package/dist/elements/layout/keep-together-element.js +4 -0
- package/dist/elements/layout/padding-element.d.ts +9 -2
- package/dist/elements/layout/padding-element.js +12 -2
- package/dist/elements/layout/page-builder-element.js +2 -0
- package/dist/elements/layout/struct-group.d.ts +2 -0
- package/dist/elements/layout/struct-group.js +4 -0
- package/dist/elements/pdf-element.d.ts +75 -1
- package/dist/elements/pdf-element.js +80 -1
- package/dist/elements/rectangle-element.d.ts +22 -6
- package/dist/elements/rectangle-element.js +31 -12
- package/dist/elements/row-element.d.ts +26 -1
- package/dist/elements/row-element.js +31 -10
- package/dist/elements/svg-element.d.ts +41 -0
- package/dist/elements/svg-element.js +93 -0
- package/dist/elements/text-element.d.ts +92 -1
- package/dist/elements/text-element.js +208 -33
- package/dist/ir/display-list.d.ts +67 -11
- package/dist/ir/display-list.js +8 -1
- package/dist/layout/alignment.d.ts +10 -0
- package/dist/layout/alignment.js +6 -0
- package/dist/layout/box-constraints.d.ts +53 -0
- package/dist/layout/box-constraints.js +72 -0
- package/dist/layout/insets.d.ts +28 -0
- package/dist/layout/insets.js +20 -0
- package/dist/layout/min-intrinsic.d.ts +29 -0
- package/dist/layout/min-intrinsic.js +37 -0
- package/dist/renderer/anchor-renderer.js +1 -1
- package/dist/renderer/bookmark-renderer.js +1 -1
- package/dist/renderer/canvas-renderer.d.ts +14 -0
- package/dist/renderer/canvas-renderer.js +36 -0
- package/dist/renderer/container-renderer.js +1 -3
- package/dist/renderer/default-text-style-renderer.js +1 -1
- package/dist/renderer/deferred-renderer.js +1 -1
- package/dist/renderer/expanded-renderer.js +1 -1
- package/dist/renderer/image-renderer.js +2 -1
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.js +2 -0
- package/dist/renderer/keep-together-renderer.js +1 -1
- package/dist/renderer/link-renderer.js +1 -1
- package/dist/renderer/padding-renderer.js +1 -1
- package/dist/renderer/page-builder-renderer.js +1 -1
- package/dist/renderer/page-renderer.js +1 -3
- package/dist/renderer/pdf-backend.d.ts +21 -4
- package/dist/renderer/pdf-backend.js +198 -61
- package/dist/renderer/pdf-document-renderer.js +8 -1
- package/dist/renderer/pdf-renderer.js +5 -1
- package/dist/renderer/positioned-renderer.js +1 -1
- package/dist/renderer/rectangle-renderer.js +9 -5
- package/dist/renderer/repeating-header-renderer.js +1 -2
- package/dist/renderer/rotated-renderer.js +1 -1
- package/dist/renderer/row-renderer.js +1 -3
- package/dist/renderer/struct-group-renderer.js +1 -1
- package/dist/renderer/svg-renderer.d.ts +11 -0
- package/dist/renderer/svg-renderer.js +27 -0
- package/dist/renderer/text-renderer.d.ts +2 -2
- package/dist/renderer/text-renderer.js +176 -57
- package/dist/svg/css.d.ts +38 -0
- package/dist/svg/css.js +115 -0
- package/dist/svg/errors.d.ts +24 -0
- package/dist/svg/errors.js +30 -0
- package/dist/svg/gradients.d.ts +34 -0
- package/dist/svg/gradients.js +156 -0
- package/dist/svg/index.d.ts +22 -0
- package/dist/svg/index.js +331 -0
- package/dist/svg/shapes.d.ts +6 -0
- package/dist/svg/shapes.js +177 -0
- package/dist/svg/style.d.ts +61 -0
- package/dist/svg/style.js +144 -0
- package/dist/svg/transform.d.ts +13 -0
- package/dist/svg/transform.js +68 -0
- package/dist/svg/units.d.ts +11 -0
- package/dist/svg/units.js +73 -0
- package/dist/svg/xml.d.ts +24 -0
- package/dist/svg/xml.js +158 -0
- package/dist/text/advance.d.ts +3 -0
- package/dist/text/advance.js +8 -4
- package/dist/text/arabic.d.ts +20 -0
- package/dist/text/arabic.js +101 -0
- package/dist/text/bidi.d.ts +33 -0
- package/dist/text/bidi.js +94 -0
- package/dist/text/font-fallback.d.ts +25 -0
- package/dist/text/font-fallback.js +38 -0
- package/dist/text/glyph-coverage.d.ts +15 -0
- package/dist/text/glyph-coverage.js +77 -0
- package/dist/text/line-breaker.d.ts +43 -1
- package/dist/text/line-breaker.js +181 -45
- package/dist/text/orphans-widows.d.ts +27 -0
- package/dist/text/orphans-widows.js +53 -0
- package/dist/text/shape.d.ts +29 -0
- package/dist/text/shape.js +91 -0
- package/dist/text/text-style.d.ts +38 -0
- package/dist/text/text-style.js +50 -1
- package/dist/text/whitespace.d.ts +4 -0
- package/dist/text/whitespace.js +43 -0
- package/dist/text/word-splitting.d.ts +42 -0
- package/dist/text/word-splitting.js +72 -0
- package/dist/utils/flex-layout.d.ts +24 -4
- package/dist/utils/flex-layout.js +218 -7
- package/dist/utils/font-metrics.d.ts +13 -2
- package/dist/utils/gsub.d.ts +36 -0
- package/dist/utils/gsub.js +178 -0
- package/dist/utils/inflate.d.ts +16 -0
- package/dist/utils/inflate.js +52 -0
- package/dist/utils/pdf-object-manager.d.ts +44 -3
- package/dist/utils/pdf-object-manager.js +176 -14
- package/dist/utils/renderer-registry.d.ts +13 -1
- package/dist/utils/renderer-registry.js +22 -1
- package/dist/utils/ttf-parser.d.ts +6 -0
- package/dist/utils/ttf-parser.js +19 -0
- package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
- package/dist/utils/utf8-to-windows1252-encoder.js +10 -0
- package/dist/utils/woff.d.ts +10 -0
- package/dist/utils/woff.js +116 -0
- package/dist/utils/woff2.d.ts +6 -0
- package/dist/utils/woff2.js +651 -0
- package/dist/validators/element-validator.js +9 -2
- package/dist/vector/path.d.ts +33 -0
- package/dist/vector/path.js +81 -0
- package/package.json +6 -2
|
@@ -5,6 +5,24 @@ export class PDFElement {
|
|
|
5
5
|
// fragmentation copies (adoptStructId), so a paragraph or table split across pages stays ONE structure
|
|
6
6
|
// element. Only the elements that actually tag (Text, Image, StructGroup) read it.
|
|
7
7
|
this._structId = _nextStructId++;
|
|
8
|
+
/**
|
|
9
|
+
* Where this child sits among its siblings (CSS `order`), lowest first, source order deciding ties.
|
|
10
|
+
* It moves the child in the LAYOUT only - the element tree, and therefore the reading order a tagged
|
|
11
|
+
* PDF exposes, is untouched. Default 0.
|
|
12
|
+
*/
|
|
13
|
+
this.order = 0;
|
|
14
|
+
/**
|
|
15
|
+
* How willingly this child gives up main-axis space when the line overflows (CSS `flex-shrink`),
|
|
16
|
+
* weighted by its own size as CSS does. Default 1, as in CSS: an overflowing line squeezes its
|
|
17
|
+
* children back inside instead of letting them run past the edge of the box. `0` opts out and keeps
|
|
18
|
+
* a child at its natural size.
|
|
19
|
+
*
|
|
20
|
+
* This was 0 until the alpha, so that adding flexShrink could not move an existing layout. That
|
|
21
|
+
* caution outlived its use: four of the gallery cases turned out to be overflowing their page
|
|
22
|
+
* margin without anyone noticing, because nothing ever pulled them back. Shrinking is bounded from
|
|
23
|
+
* below by `minIntrinsicMain`, so a child is never squeezed past what it can actually hold.
|
|
24
|
+
*/
|
|
25
|
+
this.flexShrink = 1;
|
|
8
26
|
}
|
|
9
27
|
get structId() {
|
|
10
28
|
return this._structId;
|
|
@@ -33,6 +51,19 @@ export class PDFElement {
|
|
|
33
51
|
needsBoundedMain(_horizontal) {
|
|
34
52
|
return false;
|
|
35
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The smallest main-axis extent this element can be squeezed into and still hold its content - CSS's
|
|
56
|
+
* `min-content` size, which is what an automatic `min-width` resolves to on a flex item. The shrink
|
|
57
|
+
* pass floors every child at this value, so a line that overflows badly stops squeezing rather than
|
|
58
|
+
* grinding a paragraph down to one word per line (which would only trade a wide box for a tall one).
|
|
59
|
+
*
|
|
60
|
+
* `horizontal` = the width. Default 0: an element with no opinion may be squeezed to nothing, which
|
|
61
|
+
* is what an empty box or a plain drawing can do without harm. Text and the layout containers
|
|
62
|
+
* override it; containers recurse, since a Row is only as unsqueezable as its children.
|
|
63
|
+
*/
|
|
64
|
+
minIntrinsicMain(_horizontal, _ctx) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
36
67
|
/**
|
|
37
68
|
* Whether this element IS a forced page break (a `PageBreak`). The pagination packer cuts the flow
|
|
38
69
|
* at such a marker: everything before it stays, everything after starts a new page. Default: no.
|
|
@@ -65,6 +96,31 @@ export class PDFElement {
|
|
|
65
96
|
breaksAfter() {
|
|
66
97
|
return false;
|
|
67
98
|
}
|
|
99
|
+
/** Sets `alignSelf` and returns the element, so a factory can thread it through in one expression. */
|
|
100
|
+
withAlignSelf(align) {
|
|
101
|
+
if (align !== undefined)
|
|
102
|
+
this.alignSelf = align;
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
/** Sets `flexShrink` and returns the element. */
|
|
106
|
+
withFlexShrink(shrink) {
|
|
107
|
+
if (shrink !== undefined) {
|
|
108
|
+
if (!Number.isFinite(shrink) || shrink < 0) {
|
|
109
|
+
throw new Error(`Invalid flexShrink ${shrink}: it must be a number of at least 0.`);
|
|
110
|
+
}
|
|
111
|
+
this.flexShrink = shrink;
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/** Sets `order` and returns the element, so a factory can thread it through in one expression. */
|
|
116
|
+
withOrder(order) {
|
|
117
|
+
if (order !== undefined) {
|
|
118
|
+
if (!Number.isFinite(order))
|
|
119
|
+
throw new Error(`Invalid order ${order}: it must be a number.`);
|
|
120
|
+
this.order = order;
|
|
121
|
+
}
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
68
124
|
}
|
|
69
125
|
export class SizedPDFElement extends PDFElement {
|
|
70
126
|
constructor(data) {
|
|
@@ -83,17 +139,40 @@ export class FlexiblePDFElement extends PDFElement {
|
|
|
83
139
|
super();
|
|
84
140
|
this.flex = data.flex;
|
|
85
141
|
this.verticalChildAlignment = data.verticalChildAlignment || VerticalAlignment.middle;
|
|
142
|
+
this.basis = data.basis ?? 0;
|
|
143
|
+
this.basisFactor = data.basisFactor;
|
|
86
144
|
}
|
|
87
145
|
getFlex() {
|
|
88
146
|
return this.flex;
|
|
89
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* The main extent this child starts from, before the leftover is shared out. `lineMain` is what the
|
|
150
|
+
* line offers its items (its extent minus the gaps), so a percentage basis resolves against the same
|
|
151
|
+
* base a percentage WIDTH already does - and falls back to 0 on an unbounded axis, where a fraction
|
|
152
|
+
* has no meaning.
|
|
153
|
+
*/
|
|
154
|
+
/** Whether a PERCENTAGE basis was given - it resolves to 0 on an unbounded axis, so asking for the
|
|
155
|
+
* number alone cannot tell "no basis" from "a basis that does not apply here". */
|
|
156
|
+
hasBasisFactor() {
|
|
157
|
+
return this.basisFactor !== undefined;
|
|
158
|
+
}
|
|
159
|
+
getBasis(lineMain) {
|
|
160
|
+
if (this.basisFactor !== undefined) {
|
|
161
|
+
return Number.isFinite(lineMain) ? lineMain * this.basisFactor : 0;
|
|
162
|
+
}
|
|
163
|
+
return this.basis;
|
|
164
|
+
}
|
|
90
165
|
}
|
|
91
166
|
export var HorizontalAlignment;
|
|
92
167
|
(function (HorizontalAlignment) {
|
|
168
|
+
/** CSS `text-align: start` - the side the writing STARTS on: left in `ltr`, right in `rtl`. The
|
|
169
|
+
* default, so a right-to-left paragraph begins on the right without anyone asking for it. */
|
|
170
|
+
HorizontalAlignment["start"] = "START";
|
|
93
171
|
HorizontalAlignment["left"] = "LEFT";
|
|
94
172
|
HorizontalAlignment["right"] = "RIGHT";
|
|
95
173
|
HorizontalAlignment["center"] = "CENTER";
|
|
96
|
-
|
|
174
|
+
/** CSS `text-align: justify` - both edges line up, the last line of a paragraph excepted. */
|
|
175
|
+
HorizontalAlignment["justify"] = "JUSTIFY";
|
|
97
176
|
})(HorizontalAlignment || (HorizontalAlignment = {}));
|
|
98
177
|
export var VerticalAlignment;
|
|
99
178
|
(function (VerticalAlignment) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Radii } from "../ir/display-list.js";
|
|
2
|
+
import type { GradientInput } from "../api/gradient.js";
|
|
1
3
|
import { Color } from "../common/color.js";
|
|
2
4
|
import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
|
|
3
5
|
import { Fragmentable, FragmentResult } from "../layout/fragmentation.js";
|
|
@@ -12,10 +14,11 @@ export interface SideBorders {
|
|
|
12
14
|
}
|
|
13
15
|
interface RectangleElementParams extends SizedElement, WithChildren {
|
|
14
16
|
color?: Color;
|
|
15
|
-
|
|
17
|
+
/** Solid colour, or a box-relative gradient the renderer resolves against this box. */
|
|
18
|
+
backgroundColor?: Color | GradientInput;
|
|
16
19
|
borderWidth?: number;
|
|
17
|
-
/** Corner radius in points
|
|
18
|
-
radius?: number;
|
|
20
|
+
/** Corner radius in points - one number for all four corners, or per corner. 0 = sharp (default). */
|
|
21
|
+
radius?: number | Radii;
|
|
19
22
|
/** Individual side borders; overrides the uniform `color` border when present. */
|
|
20
23
|
sideBorders?: SideBorders;
|
|
21
24
|
/** When true, this box is a positioning frame for `Positioned` descendants (CSS `relative`). */
|
|
@@ -28,6 +31,17 @@ interface RectangleElementParams extends SizedElement, WithChildren {
|
|
|
28
31
|
widthFactor?: number;
|
|
29
32
|
/** Height as a fraction (0..1) of the offered height; see `widthFactor`. */
|
|
30
33
|
heightFactor?: number;
|
|
34
|
+
/** Lower / upper bounds per axis, in points or as a fraction; see `SizingParams`. */
|
|
35
|
+
minWidth?: number;
|
|
36
|
+
maxWidth?: number;
|
|
37
|
+
minHeight?: number;
|
|
38
|
+
maxHeight?: number;
|
|
39
|
+
minWidthFactor?: number;
|
|
40
|
+
maxWidthFactor?: number;
|
|
41
|
+
minHeightFactor?: number;
|
|
42
|
+
maxHeightFactor?: number;
|
|
43
|
+
/** width / height; derives whichever axis is left open (CSS `aspect-ratio`). */
|
|
44
|
+
aspectRatio?: number;
|
|
31
45
|
/** Start this box on a fresh page (CSS `break-before: page`). */
|
|
32
46
|
breakBefore?: boolean;
|
|
33
47
|
/** Start everything after this box on a fresh page (CSS `break-after: page`). */
|
|
@@ -45,7 +59,7 @@ export declare class RectangleElement extends SizedPDFElement implements Fragmen
|
|
|
45
59
|
private breakBefore;
|
|
46
60
|
private breakAfter;
|
|
47
61
|
private sizeMemory;
|
|
48
|
-
constructor({ children, color, backgroundColor, borderWidth, width, height, radius, sideBorders, relative, overflow,
|
|
62
|
+
constructor({ children, color, backgroundColor, borderWidth, width, height, radius, sideBorders, relative, overflow, breakBefore, breakAfter, ...sizing }: RectangleElementParams);
|
|
49
63
|
breaksBefore(): boolean;
|
|
50
64
|
breaksAfter(): boolean;
|
|
51
65
|
/**
|
|
@@ -64,6 +78,8 @@ export declare class RectangleElement extends SizedPDFElement implements Fragmen
|
|
|
64
78
|
* `Spacer` in a box can only resolve if the box itself was given a bounded extent to pass down. With an
|
|
65
79
|
* explicit size on an axis the box already knows its extent there and asks for nothing. */
|
|
66
80
|
needsBoundedMain(horizontal: boolean): boolean;
|
|
81
|
+
/** A Box stacks its children vertically inside its border, which is on both sides of the axis. */
|
|
82
|
+
minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
|
|
67
83
|
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
68
84
|
getProps(): {
|
|
69
85
|
x: number;
|
|
@@ -72,9 +88,9 @@ export declare class RectangleElement extends SizedPDFElement implements Fragmen
|
|
|
72
88
|
height: number | undefined;
|
|
73
89
|
children: PDFElement[];
|
|
74
90
|
color: Color;
|
|
75
|
-
backgroundColor: Color | undefined;
|
|
91
|
+
backgroundColor: Color | GradientInput | undefined;
|
|
76
92
|
borderWidth: number;
|
|
77
|
-
radius: number;
|
|
93
|
+
radius: number | Radii;
|
|
78
94
|
sideBorders: SideBorders | undefined;
|
|
79
95
|
overflow: "hidden" | "visible";
|
|
80
96
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Color } from "../common/color.js";
|
|
2
|
-
import { BoxConstraints,
|
|
2
|
+
import { BoxConstraints, extentSpecs, resolveSize, } from "../layout/box-constraints.js";
|
|
3
|
+
import { stackMinIntrinsic, withDeclaredExtent } from "../layout/min-intrinsic.js";
|
|
3
4
|
import { childrenForceBreak, packChildren, } from "../layout/fragmentation.js";
|
|
4
5
|
import { SizedPDFElement, } from "./pdf-element.js";
|
|
5
6
|
export class RectangleElement extends SizedPDFElement {
|
|
6
|
-
constructor({ children = [], color = new Color(0, 0, 0), backgroundColor, borderWidth, width, height, radius, sideBorders, relative, overflow,
|
|
7
|
+
constructor({ children = [], color = new Color(0, 0, 0), backgroundColor, borderWidth, width, height, radius, sideBorders, relative, overflow, breakBefore, breakAfter, ...sizing }) {
|
|
7
8
|
super({ x: 0, y: 0, width, height });
|
|
8
9
|
this.children = [];
|
|
9
10
|
this.children = children;
|
|
@@ -17,7 +18,7 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
17
18
|
this.overflow = overflow ?? "visible";
|
|
18
19
|
this.breakBefore = breakBefore ?? false;
|
|
19
20
|
this.breakAfter = breakAfter ?? false;
|
|
20
|
-
this.sizeMemory = { x: 0, y: 0, width, height
|
|
21
|
+
this.sizeMemory = { ...sizing, x: 0, y: 0, width, height };
|
|
21
22
|
}
|
|
22
23
|
breaksBefore() {
|
|
23
24
|
return this.breakBefore;
|
|
@@ -99,18 +100,29 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
99
100
|
return false;
|
|
100
101
|
return this.children.some((c) => c.needsBoundedMain(horizontal));
|
|
101
102
|
}
|
|
103
|
+
/** A Box stacks its children vertically inside its border, which is on both sides of the axis. */
|
|
104
|
+
minIntrinsicMain(horizontal, ctx) {
|
|
105
|
+
const content = stackMinIntrinsic(this.children, 0, !horizontal, horizontal, ctx) + 2 * this.borderWidth;
|
|
106
|
+
return withDeclaredExtent(content, horizontal ? this.sizeMemory.width : this.sizeMemory.height, horizontal ? this.sizeMemory.minWidth : this.sizeMemory.minHeight);
|
|
107
|
+
}
|
|
102
108
|
calculateLayout(constraints, offset, ctx) {
|
|
103
109
|
// An explicit extent is a fixed point size or a fraction of the offered box (relative sizing);
|
|
104
110
|
// the fraction only resolves in a bounded region. `undefined` = fill / shrink-wrap below.
|
|
105
|
-
const
|
|
106
|
-
const
|
|
111
|
+
const specs = extentSpecs(this.sizeMemory);
|
|
112
|
+
const resolved = resolveSize(specs.width, specs.height, this.sizeMemory.aspectRatio, constraints);
|
|
113
|
+
const explicitWidth = resolved.width;
|
|
114
|
+
const explicitHeight = resolved.height;
|
|
115
|
+
// min/max come back as narrowed constraints, so the fill and shrink-wrap paths below obey them too.
|
|
116
|
+
const bounds = resolved.constraints;
|
|
107
117
|
// Width: an explicit size wins (clamped), else fill the offered box. (Without this a
|
|
108
118
|
// fixed box would balloon to the parent's size.)
|
|
119
|
+
// Whether there is a region to fill is decided by the constraints we were GIVEN: a `maxWidth` caps
|
|
120
|
+
// a box, it does not turn a shrink-wrapping one into a filling one (CSS `max-width` does not either).
|
|
109
121
|
this.width =
|
|
110
122
|
explicitWidth !== undefined
|
|
111
|
-
?
|
|
123
|
+
? bounds.constrainWidth(explicitWidth)
|
|
112
124
|
: constraints.hasBoundedWidth
|
|
113
|
-
?
|
|
125
|
+
? bounds.maxWidth
|
|
114
126
|
: this.width;
|
|
115
127
|
// Width shrink-wrap: no explicit width AND an unbounded region (e.g. a `Box` badge inside a
|
|
116
128
|
// `Positioned`). Resolved after the children are measured, just below.
|
|
@@ -121,9 +133,9 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
121
133
|
const shrinkWrapHeight = explicitHeight === undefined && !constraints.hasBoundedHeight;
|
|
122
134
|
this.height =
|
|
123
135
|
explicitHeight !== undefined
|
|
124
|
-
?
|
|
136
|
+
? bounds.constrainHeight(explicitHeight)
|
|
125
137
|
: constraints.hasBoundedHeight
|
|
126
|
-
?
|
|
138
|
+
? bounds.maxHeight
|
|
127
139
|
: this.height;
|
|
128
140
|
this.x = this.sizeMemory.x + offset.x;
|
|
129
141
|
this.y = this.sizeMemory.y + offset.y;
|
|
@@ -161,11 +173,18 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
161
173
|
contentWidth = Math.max(contentWidth, childSize.width);
|
|
162
174
|
}
|
|
163
175
|
// No explicit width and no bounded region: shrink-wrap to the widest child.
|
|
176
|
+
// Clamped, so a min/max still applies to a box that sized itself to its content.
|
|
164
177
|
if (shrinkWrapWidth)
|
|
165
|
-
this.width = contentWidth + 2 * this.borderWidth;
|
|
178
|
+
this.width = bounds.constrainWidth(contentWidth + 2 * this.borderWidth);
|
|
179
|
+
// A ratio on a shrink-wrapping box can only be applied HERE: before the children were measured
|
|
180
|
+
// there was no width to derive the height from.
|
|
181
|
+
const ratio = this.sizeMemory.aspectRatio;
|
|
182
|
+
const ratioSizesHeight = ratio !== undefined && ratio > 0 && shrinkWrapWidth && shrinkWrapHeight;
|
|
166
183
|
// No explicit height and no bounded region: the border hugs its content.
|
|
167
|
-
if (
|
|
168
|
-
this.height =
|
|
184
|
+
if (ratioSizesHeight)
|
|
185
|
+
this.height = bounds.constrainHeight((this.width ?? 0) / ratio);
|
|
186
|
+
else if (shrinkWrapHeight)
|
|
187
|
+
this.height = bounds.constrainHeight(contentHeight + 2 * this.borderWidth);
|
|
169
188
|
// The box is sized now: place any out-of-flow `Positioned` descendants against its box.
|
|
170
189
|
if (frame) {
|
|
171
190
|
frame.origin = { x: this.x, y: this.y };
|
|
@@ -8,11 +8,28 @@ interface RowElementParams extends WithChildren {
|
|
|
8
8
|
main?: MainAlign;
|
|
9
9
|
/** Vertical alignment of each child (cross axis); defaults to `stretch`. */
|
|
10
10
|
cross?: CrossAlign;
|
|
11
|
+
/** Lay the children out backwards along the main axis (CSS `row-reverse`). */
|
|
12
|
+
reverse?: boolean;
|
|
13
|
+
/** Let the children flow onto further lines when they do not fit (CSS `flex-wrap`). */
|
|
14
|
+
wrap?: boolean;
|
|
15
|
+
/** How the block of wrapped lines sits across the axis (CSS `align-content`). */
|
|
16
|
+
alignContent?: MainAlign;
|
|
11
17
|
/** Width/height as points (fixed) or a fraction (0..1) of the offered box (relative sizing). */
|
|
12
18
|
width?: number;
|
|
13
19
|
height?: number;
|
|
14
20
|
widthFactor?: number;
|
|
15
21
|
heightFactor?: number;
|
|
22
|
+
/** Lower / upper bounds per axis, in points or as a fraction; see `SizingParams`. */
|
|
23
|
+
minWidth?: number;
|
|
24
|
+
maxWidth?: number;
|
|
25
|
+
minHeight?: number;
|
|
26
|
+
maxHeight?: number;
|
|
27
|
+
minWidthFactor?: number;
|
|
28
|
+
maxWidthFactor?: number;
|
|
29
|
+
minHeightFactor?: number;
|
|
30
|
+
maxHeightFactor?: number;
|
|
31
|
+
/** width / height; derives whichever axis is left open (CSS `aspect-ratio`). */
|
|
32
|
+
aspectRatio?: number;
|
|
16
33
|
/** Start this row on a fresh page (CSS `break-before: page`). */
|
|
17
34
|
breakBefore?: boolean;
|
|
18
35
|
/** Start everything after this row on a fresh page (CSS `break-after: page`). */
|
|
@@ -35,16 +52,21 @@ export declare class RowElement extends SizedPDFElement {
|
|
|
35
52
|
private gap;
|
|
36
53
|
private main;
|
|
37
54
|
private cross;
|
|
55
|
+
private reverse;
|
|
56
|
+
private wrap;
|
|
57
|
+
private alignContent?;
|
|
38
58
|
private breakBefore;
|
|
39
59
|
private breakAfter;
|
|
40
60
|
private requested;
|
|
41
|
-
constructor({ children, gap, main, cross,
|
|
61
|
+
constructor({ children, gap, main, cross, reverse, wrap, alignContent, width, height, breakBefore, breakAfter, ...sizing }: RowElementParams);
|
|
42
62
|
breaksBefore(): boolean;
|
|
43
63
|
breaksAfter(): boolean;
|
|
44
64
|
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
45
65
|
/** The mirror of `ContainerElement`: a Row's OWN flex child needs a bounded width, and the need of any
|
|
46
66
|
* descendant propagates on both axes (see the Column for the reasoning). */
|
|
47
67
|
needsBoundedMain(horizontal: boolean): boolean;
|
|
68
|
+
/** A Row lays out side by side, so widths add up (gaps included) and heights overlap. */
|
|
69
|
+
minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
|
|
48
70
|
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
49
71
|
getProps(): {
|
|
50
72
|
x: number;
|
|
@@ -55,6 +77,9 @@ export declare class RowElement extends SizedPDFElement {
|
|
|
55
77
|
gap: number;
|
|
56
78
|
main: MainAlign;
|
|
57
79
|
cross: CrossAlign;
|
|
80
|
+
reverse: boolean;
|
|
81
|
+
wrap: boolean;
|
|
82
|
+
alignContent: MainAlign | undefined;
|
|
58
83
|
};
|
|
59
84
|
}
|
|
60
85
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
|
|
2
2
|
import { FlexLayoutHelper, HORIZONTAL_AXIS } from "../utils/flex-layout.js";
|
|
3
|
+
import { stackMinIntrinsic, withDeclaredExtent } from "../layout/min-intrinsic.js";
|
|
3
4
|
import { FlexiblePDFElement, SizedPDFElement, } from "./pdf-element.js";
|
|
4
5
|
/**
|
|
5
6
|
* Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
|
|
@@ -14,13 +15,16 @@ import { FlexiblePDFElement, SizedPDFElement, } from "./pdf-element.js";
|
|
|
14
15
|
* Grid/Table concern.
|
|
15
16
|
*/
|
|
16
17
|
export class RowElement extends SizedPDFElement {
|
|
17
|
-
constructor({ children, gap, main, cross,
|
|
18
|
+
constructor({ children, gap, main, cross, reverse, wrap, alignContent, width, height, breakBefore, breakAfter, ...sizing }) {
|
|
18
19
|
super({ x: 0, y: 0 });
|
|
19
20
|
this.children = children;
|
|
20
21
|
this.gap = gap ?? 0;
|
|
21
22
|
this.main = main ?? "start";
|
|
22
23
|
this.cross = cross ?? "stretch";
|
|
23
|
-
this.
|
|
24
|
+
this.reverse = reverse ?? false;
|
|
25
|
+
this.wrap = wrap ?? false;
|
|
26
|
+
this.alignContent = alignContent;
|
|
27
|
+
this.requested = { ...sizing, width, height };
|
|
24
28
|
this.breakBefore = breakBefore ?? false;
|
|
25
29
|
this.breakAfter = breakAfter ?? false;
|
|
26
30
|
}
|
|
@@ -44,29 +48,43 @@ export class RowElement extends SizedPDFElement {
|
|
|
44
48
|
const ownFlexChild = horizontal && this.children.some((c) => c instanceof FlexiblePDFElement);
|
|
45
49
|
return ownFlexChild || this.children.some((c) => c.needsBoundedMain(horizontal));
|
|
46
50
|
}
|
|
51
|
+
/** A Row lays out side by side, so widths add up (gaps included) and heights overlap. */
|
|
52
|
+
minIntrinsicMain(horizontal, ctx) {
|
|
53
|
+
const content = stackMinIntrinsic(this.children, this.gap, horizontal, horizontal, ctx, this.wrap);
|
|
54
|
+
return withDeclaredExtent(content, horizontal ? this.requested.width : this.requested.height, horizontal ? this.requested.minWidth : this.requested.minHeight);
|
|
55
|
+
}
|
|
47
56
|
calculateLayout(constraints, offset, ctx) {
|
|
48
57
|
this.x = offset.x;
|
|
49
58
|
this.y = offset.y;
|
|
50
59
|
// Relative sizing: a pinned extent (fixed or a fraction of the offered box, clamped) wins; else
|
|
51
60
|
// width fills the offered space (flex children split the leftover) and height is the tallest child.
|
|
52
|
-
const
|
|
53
|
-
const
|
|
61
|
+
const specs = extentSpecs(this.requested);
|
|
62
|
+
const resolved = resolveSize(specs.width, specs.height, this.requested.aspectRatio, constraints);
|
|
63
|
+
const { width: explicitWidth, height: explicitHeight, constraints: bounds } = resolved;
|
|
64
|
+
// Fill-or-shrink-wrap is decided by the constraints we were GIVEN; a min/max only caps the result.
|
|
54
65
|
const boundedWidth = explicitWidth !== undefined
|
|
55
|
-
?
|
|
66
|
+
? bounds.constrainWidth(explicitWidth)
|
|
56
67
|
: constraints.hasBoundedWidth
|
|
57
|
-
?
|
|
68
|
+
? bounds.maxWidth
|
|
58
69
|
: undefined;
|
|
59
70
|
const boundedHeight = explicitHeight !== undefined
|
|
60
|
-
?
|
|
71
|
+
? bounds.constrainHeight(explicitHeight)
|
|
61
72
|
: constraints.hasBoundedHeight
|
|
62
|
-
?
|
|
73
|
+
? bounds.maxHeight
|
|
63
74
|
: undefined;
|
|
64
75
|
// Horizontal stack: main = width (children fill it), cross = height (children can stretch to it).
|
|
65
76
|
const mainAvail = boundedWidth ?? Infinity;
|
|
66
77
|
const crossAvail = boundedHeight ?? Infinity;
|
|
67
78
|
let result = { mainUsed: 0, crossUsed: 0 };
|
|
68
79
|
if (this.children.length > 0) {
|
|
69
|
-
result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, this.x, this.y, {
|
|
80
|
+
result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, this.x, this.y, {
|
|
81
|
+
gap: this.gap,
|
|
82
|
+
main: this.main,
|
|
83
|
+
cross: this.cross,
|
|
84
|
+
reverse: this.reverse,
|
|
85
|
+
wrap: this.wrap,
|
|
86
|
+
alignContent: this.alignContent,
|
|
87
|
+
}, ctx);
|
|
70
88
|
}
|
|
71
89
|
this.width = boundedWidth ?? result.mainUsed;
|
|
72
90
|
this.height = boundedHeight ?? result.crossUsed;
|
|
@@ -82,6 +100,9 @@ export class RowElement extends SizedPDFElement {
|
|
|
82
100
|
gap: this.gap,
|
|
83
101
|
main: this.main,
|
|
84
102
|
cross: this.cross,
|
|
103
|
+
reverse: this.reverse,
|
|
104
|
+
wrap: this.wrap,
|
|
105
|
+
alignContent: this.alignContent,
|
|
85
106
|
};
|
|
86
107
|
}
|
|
87
108
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type SvgSize } from "../svg/index.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size, SizingParams } from "../layout/box-constraints.js";
|
|
3
|
+
import { LayoutContext, SizedPDFElement } from "./pdf-element.js";
|
|
4
|
+
/** Markup, a file path (Node only), or the file's bytes. */
|
|
5
|
+
export type SvgSource = string | Uint8Array;
|
|
6
|
+
interface SvgElementParams extends SizingParams {
|
|
7
|
+
source: SvgSource;
|
|
8
|
+
/** Width/height as a fraction (0..1) of the offered box, for `"50%"`. */
|
|
9
|
+
widthFactor?: number;
|
|
10
|
+
heightFactor?: number;
|
|
11
|
+
/** Alternate text for the tagged structure tree; makes the drawing a Figure rather than decoration. */
|
|
12
|
+
alt?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* An SVG drawing placed in the layout like an image.
|
|
16
|
+
*
|
|
17
|
+
* Unlike a bitmap it needs no decoder, so its intrinsic size is known SYNCHRONOUSLY, in the
|
|
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.
|
|
20
|
+
*/
|
|
21
|
+
export declare class SvgElement extends SizedPDFElement {
|
|
22
|
+
private source;
|
|
23
|
+
private intrinsic;
|
|
24
|
+
private alt?;
|
|
25
|
+
private requested;
|
|
26
|
+
private sizing;
|
|
27
|
+
private widthFactor?;
|
|
28
|
+
private heightFactor?;
|
|
29
|
+
constructor(params: SvgElementParams);
|
|
30
|
+
getProps(): {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
width: number | undefined;
|
|
34
|
+
height: number | undefined;
|
|
35
|
+
source: string;
|
|
36
|
+
intrinsic: SvgSize;
|
|
37
|
+
alt: string | undefined;
|
|
38
|
+
};
|
|
39
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
|
|
40
|
+
}
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFileBytes } from "../platform/node-fs.js";
|
|
2
|
+
import { SvgParseError, svgSize, svgToIr } from "../svg/index.js";
|
|
3
|
+
import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
|
|
4
|
+
import { SizedPDFElement } from "./pdf-element.js";
|
|
5
|
+
/** Markup is recognised by its first non-space character - anything else is treated as a path. */
|
|
6
|
+
const looksLikeMarkup = (value) => /^\s*[<]/.test(value);
|
|
7
|
+
/**
|
|
8
|
+
* Reads the file, or says what went wrong in the caller's terms. A string that is neither markup nor
|
|
9
|
+
* a readable path would otherwise surface as a bare `ENOENT`, which never mentions that the argument
|
|
10
|
+
* was taken as a PATH in the first place.
|
|
11
|
+
*/
|
|
12
|
+
function readSource(path) {
|
|
13
|
+
try {
|
|
14
|
+
return readFileBytes(path);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
const shown = path.length > 80 ? `${path.slice(0, 77)}...` : path;
|
|
18
|
+
throw new SvgParseError(`could not read "${shown}" as an SVG file. It does not start with "<", so it was taken as a ` +
|
|
19
|
+
`path: ${error.message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* An SVG drawing placed in the layout like an image.
|
|
24
|
+
*
|
|
25
|
+
* Unlike a bitmap it needs no decoder, so its intrinsic size is known SYNCHRONOUSLY, in the
|
|
26
|
+
* 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.
|
|
28
|
+
*/
|
|
29
|
+
export class SvgElement extends SizedPDFElement {
|
|
30
|
+
constructor(params) {
|
|
31
|
+
super({ x: 0, y: 0, width: params.width, height: params.height });
|
|
32
|
+
const { source, alt, widthFactor, heightFactor, ...sizing } = params;
|
|
33
|
+
this.widthFactor = widthFactor;
|
|
34
|
+
this.heightFactor = heightFactor;
|
|
35
|
+
this.source =
|
|
36
|
+
typeof source === "string"
|
|
37
|
+
? looksLikeMarkup(source)
|
|
38
|
+
? source
|
|
39
|
+
: 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
|
+
this.alt = alt;
|
|
48
|
+
this.sizing = sizing;
|
|
49
|
+
this.requested = { width: params.width, height: params.height };
|
|
50
|
+
}
|
|
51
|
+
getProps() {
|
|
52
|
+
return {
|
|
53
|
+
x: this.x,
|
|
54
|
+
y: this.y,
|
|
55
|
+
width: this.width,
|
|
56
|
+
height: this.height,
|
|
57
|
+
source: this.source,
|
|
58
|
+
intrinsic: this.intrinsic,
|
|
59
|
+
alt: this.alt,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
calculateLayout(constraints, offset, _ctx) {
|
|
63
|
+
this.x = offset.x;
|
|
64
|
+
this.y = offset.y;
|
|
65
|
+
const specs = extentSpecs({
|
|
66
|
+
...this.sizing,
|
|
67
|
+
width: this.requested.width,
|
|
68
|
+
height: this.requested.height,
|
|
69
|
+
widthFactor: this.widthFactor,
|
|
70
|
+
heightFactor: this.heightFactor,
|
|
71
|
+
});
|
|
72
|
+
const resolved = resolveSize(specs.width, specs.height, this.sizing.aspectRatio, constraints);
|
|
73
|
+
let w = resolved.width;
|
|
74
|
+
let h = resolved.height;
|
|
75
|
+
const bounds = resolved.constraints;
|
|
76
|
+
// One pinned axis derives the other from the drawing's own ratio (CSS `width: 120; height: auto`).
|
|
77
|
+
// A bitmap needs a decode pass before it can do this; an SVG knows it from its viewBox.
|
|
78
|
+
if (this.sizing.aspectRatio === undefined &&
|
|
79
|
+
this.intrinsic.width > 0 &&
|
|
80
|
+
this.intrinsic.height > 0) {
|
|
81
|
+
const ratio = this.intrinsic.width / this.intrinsic.height;
|
|
82
|
+
if (w !== undefined && h === undefined)
|
|
83
|
+
h = bounds.constrainHeight(w / ratio);
|
|
84
|
+
else if (h !== undefined && w === undefined)
|
|
85
|
+
w = bounds.constrainWidth(h * ratio);
|
|
86
|
+
}
|
|
87
|
+
// Nothing pinned: draw at the intrinsic size, as an unsized `<img>` does - never fill the region,
|
|
88
|
+
// which would blow a 24pt icon up to the whole page.
|
|
89
|
+
this.width = w ?? bounds.constrainWidth(this.intrinsic.width);
|
|
90
|
+
this.height = h ?? bounds.constrainHeight(this.intrinsic.height);
|
|
91
|
+
return { width: this.width, height: this.height };
|
|
92
|
+
}
|
|
93
|
+
}
|