@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
|
@@ -32,6 +32,9 @@ export interface TextRun {
|
|
|
32
32
|
x: number;
|
|
33
33
|
y: number;
|
|
34
34
|
text: string;
|
|
35
|
+
/** The glyphs to draw, when the run was SHAPED. The backend emits these instead of encoding `text`,
|
|
36
|
+
* which stays logical for `ToUnicode` and the accessible tree. */
|
|
37
|
+
glyphs?: number[];
|
|
35
38
|
fontFamily: string;
|
|
36
39
|
fontStyle: FontStyle;
|
|
37
40
|
fontSize: number;
|
|
@@ -40,17 +43,31 @@ export interface TextRun {
|
|
|
40
43
|
tag?: StructTag;
|
|
41
44
|
}
|
|
42
45
|
/** An axis-aligned rectangle. An absent `fill` or `stroke` means that part is not drawn. */
|
|
46
|
+
/**
|
|
47
|
+
* Corner radii in points (CSS `border-radius`). Any corner left out falls back to 0. A single number
|
|
48
|
+
* everywhere is the common case and stays a plain `number` on the nodes, so nothing changed for a
|
|
49
|
+
* document that never asks for different corners.
|
|
50
|
+
*/
|
|
51
|
+
export interface Radii {
|
|
52
|
+
tl?: number;
|
|
53
|
+
tr?: number;
|
|
54
|
+
br?: number;
|
|
55
|
+
bl?: number;
|
|
56
|
+
}
|
|
57
|
+
/** True when any corner asks for a radius - the gate between a plain rect and a curved path. */
|
|
58
|
+
export declare function isRounded(radius: number | Radii | undefined): boolean;
|
|
43
59
|
export interface Rect {
|
|
44
60
|
type: "rect";
|
|
45
61
|
x: number;
|
|
46
62
|
y: number;
|
|
47
63
|
width: number;
|
|
48
64
|
height: number;
|
|
49
|
-
|
|
65
|
+
/** Solid colour, or a gradient whose anchor points are in absolute page coordinates. */
|
|
66
|
+
fill?: Color | Gradient;
|
|
50
67
|
stroke?: Color;
|
|
51
68
|
strokeWidth: number;
|
|
52
|
-
/** Corner radius in points
|
|
53
|
-
radius?: number;
|
|
69
|
+
/** Corner radius in points - one number for all four, or per corner. Absent/0 = sharp (plain `re`). */
|
|
70
|
+
radius?: number | Radii;
|
|
54
71
|
tag?: StructTag;
|
|
55
72
|
}
|
|
56
73
|
/** A straight line segment between two points. */
|
|
@@ -84,8 +101,8 @@ export interface Image {
|
|
|
84
101
|
width: number;
|
|
85
102
|
height: number;
|
|
86
103
|
};
|
|
87
|
-
/** Corner radius in points for the image box; absent/0 = sharp
|
|
88
|
-
radius?: number;
|
|
104
|
+
/** Corner radius in points for the image box - one number or per corner; absent/0 = sharp. */
|
|
105
|
+
radius?: number | Radii;
|
|
89
106
|
tag?: StructTag;
|
|
90
107
|
}
|
|
91
108
|
/**
|
|
@@ -99,7 +116,7 @@ export interface ClipPush {
|
|
|
99
116
|
y: number;
|
|
100
117
|
width: number;
|
|
101
118
|
height: number;
|
|
102
|
-
radius?: number;
|
|
119
|
+
radius?: number | Radii;
|
|
103
120
|
}
|
|
104
121
|
/** Closes the most recent `ClipPush` (restores the graphics state). */
|
|
105
122
|
export interface ClipPop {
|
|
@@ -216,6 +233,9 @@ export interface LinearGradient {
|
|
|
216
233
|
y1: number;
|
|
217
234
|
stops: GradientStop[];
|
|
218
235
|
extend: "pad" | "repeat" | "reflect";
|
|
236
|
+
/** One opacity for the whole shading (0-1). A PDF shading is DeviceRGB and has no alpha channel,
|
|
237
|
+
* so a PER-STOP alpha is not representable - but a uniform one is, as an ExtGState. */
|
|
238
|
+
alpha?: number;
|
|
219
239
|
}
|
|
220
240
|
/** A radial gradient between two circles, in absolute page coordinates. */
|
|
221
241
|
export interface RadialGradient {
|
|
@@ -228,19 +248,55 @@ export interface RadialGradient {
|
|
|
228
248
|
r1: number;
|
|
229
249
|
stops: GradientStop[];
|
|
230
250
|
extend: "pad" | "repeat" | "reflect";
|
|
251
|
+
/** One opacity for the whole shading (0-1). A PDF shading is DeviceRGB and has no alpha channel,
|
|
252
|
+
* so a PER-STOP alpha is not representable - but a uniform one is, as an ExtGState. */
|
|
253
|
+
alpha?: number;
|
|
231
254
|
}
|
|
232
255
|
export type Gradient = LinearGradient | RadialGradient;
|
|
233
256
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
257
|
+
* How a path's outline is stroked. The names are SVG's, the semantics are the PDF operators they map
|
|
258
|
+
* to (`w`, `J`, `j`, `M`, `d`), and the defaults are SVG's initial values - not PDF's, which differ on
|
|
259
|
+
* the miter limit. Opacity rides on the `Color`, as everywhere else in the IR.
|
|
260
|
+
*/
|
|
261
|
+
export interface StrokeStyle {
|
|
262
|
+
color: Color;
|
|
263
|
+
width: number;
|
|
264
|
+
/** `J`. Default `butt`. */
|
|
265
|
+
cap?: "butt" | "round" | "square";
|
|
266
|
+
/** `j`. Default `miter`. */
|
|
267
|
+
join?: "miter" | "round" | "bevel";
|
|
268
|
+
/** `M`. Default 4 (SVG); PDF's own default is 10, so we always emit it when a join can miter. */
|
|
269
|
+
miterLimit?: number;
|
|
270
|
+
/** `d`: dash lengths in points, plus the phase to start at. Absent or empty = solid. */
|
|
271
|
+
dash?: number[];
|
|
272
|
+
dashOffset?: number;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* A vector path - one or more subpaths that share one fill and/or one stroke. `fill` uses the nonzero
|
|
276
|
+
* winding rule unless `fillRule` says otherwise, so a glyph's inner contour cuts a hole by default.
|
|
277
|
+
* A color glyph's layer is one `Path` filled with a solid `Color` (COLR v0) or a `Gradient` (v1); an
|
|
278
|
+
* SVG shape is one `Path` that may also carry a stroke, or carry ONLY a stroke.
|
|
237
279
|
*/
|
|
238
280
|
export interface Path {
|
|
239
281
|
type: "path";
|
|
240
282
|
commands: PathCommand[];
|
|
241
|
-
|
|
283
|
+
/** Absent = not filled (a stroke-only outline). */
|
|
284
|
+
fill?: Color | Gradient;
|
|
285
|
+
/** `evenodd` maps to the `*` operators; absent = nonzero, which is what glyphs need. */
|
|
286
|
+
fillRule?: "nonzero" | "evenodd";
|
|
287
|
+
stroke?: StrokeStyle;
|
|
242
288
|
/** Accessibility structure tag (absent = drawn as an artifact); color-emoji layers stay untagged. */
|
|
243
289
|
tag?: StructTag;
|
|
244
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* Clips to an arbitrary path until the matching `clip-pop` - what SVG's `<clipPath>` needs, and what
|
|
293
|
+
* `ClipPush` cannot express, being a rectangle. The commands are in the same space as a `Path`'s.
|
|
294
|
+
*/
|
|
295
|
+
export interface ClipPathPush {
|
|
296
|
+
type: "clip-path-push";
|
|
297
|
+
commands: PathCommand[];
|
|
298
|
+
/** `evenodd` maps to `W*`; absent = nonzero. SVG spells this `clip-rule`. */
|
|
299
|
+
fillRule?: "nonzero" | "evenodd";
|
|
300
|
+
}
|
|
245
301
|
/** The closed set of primitives the PDF backend knows how to draw. */
|
|
246
|
-
export type IRNode = TextRun | Rect | Line | Image | ClipPush | ClipPop | Path | Link | Outline | Anchor | FormFieldNode | TransformPush | TransformPop;
|
|
302
|
+
export type IRNode = TextRun | Rect | Line | Image | ClipPush | ClipPathPush | ClipPop | Path | Link | Outline | Anchor | FormFieldNode | TransformPush | TransformPop;
|
package/dist/ir/display-list.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/** True when any corner asks for a radius - the gate between a plain rect and a curved path. */
|
|
2
|
+
export function isRounded(radius) {
|
|
3
|
+
if (radius === undefined)
|
|
4
|
+
return false;
|
|
5
|
+
if (typeof radius === "number")
|
|
6
|
+
return radius > 0;
|
|
7
|
+
return ((radius.tl ?? 0) > 0 || (radius.tr ?? 0) > 0 || (radius.br ?? 0) > 0 || (radius.bl ?? 0) > 0);
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flex alignment vocabulary. It lives here rather than in `utils/flex-layout.ts` because the base
|
|
3
|
+
* `PDFElement` needs `CrossAlign` for `alignSelf`, and flex-layout imports the elements - defining it
|
|
4
|
+
* there would close a cycle.
|
|
5
|
+
*/
|
|
6
|
+
/** Distribution along the MAIN axis (CSS `justify-content`). */
|
|
7
|
+
export type MainAlign = "start" | "center" | "end" | "between" | "around";
|
|
8
|
+
/** Position/size ACROSS the axis (CSS `align-items` / `align-self`). `stretch` fills the cross extent;
|
|
9
|
+
* the others place the child at its natural cross size. */
|
|
10
|
+
export type CrossAlign = "start" | "center" | "end" | "stretch";
|
|
@@ -41,6 +41,12 @@ export declare class BoxConstraints {
|
|
|
41
41
|
* what a padding/border element hands its child after reserving its own insets.
|
|
42
42
|
*/
|
|
43
43
|
deflate(horizontal: number, vertical: number): BoxConstraints;
|
|
44
|
+
/**
|
|
45
|
+
* Tightens this box by an element's own `min`/`max` on either axis. A max never widens the offered
|
|
46
|
+
* room, and a min may push past it - `minHeight: 500` in a 300pt region means 500, and pagination
|
|
47
|
+
* deals with the overflow, the same answer CSS gives.
|
|
48
|
+
*/
|
|
49
|
+
narrow(minWidth?: number, maxWidth?: number, minHeight?: number, maxHeight?: number): BoxConstraints;
|
|
44
50
|
/** Returns the constraints clamped to lie within `parent` (Flutter's enforce). */
|
|
45
51
|
enforce(parent: BoxConstraints): BoxConstraints;
|
|
46
52
|
}
|
|
@@ -52,6 +58,53 @@ export declare class BoxConstraints {
|
|
|
52
58
|
* Shared by every sized element so `width: "50%"` behaves identically across Box / Column / Row / Image.
|
|
53
59
|
*/
|
|
54
60
|
export declare function resolveExtent(fixed: number | undefined, factor: number | undefined, boundedMax: number, hasBounded: boolean): number | undefined;
|
|
61
|
+
/** What one axis of a sized element was asked for: a size, plus the bounds it must stay inside. */
|
|
62
|
+
export interface ExtentSpec {
|
|
63
|
+
fixed?: number;
|
|
64
|
+
factor?: number;
|
|
65
|
+
min?: number;
|
|
66
|
+
minFactor?: number;
|
|
67
|
+
max?: number;
|
|
68
|
+
maxFactor?: number;
|
|
69
|
+
}
|
|
70
|
+
/** The sizing options every sized element accepts. Each `*Factor` is the percentage form of its pair. */
|
|
71
|
+
export interface SizingParams {
|
|
72
|
+
width?: number;
|
|
73
|
+
height?: number;
|
|
74
|
+
widthFactor?: number;
|
|
75
|
+
heightFactor?: number;
|
|
76
|
+
minWidth?: number;
|
|
77
|
+
maxWidth?: number;
|
|
78
|
+
minHeight?: number;
|
|
79
|
+
maxHeight?: number;
|
|
80
|
+
minWidthFactor?: number;
|
|
81
|
+
maxWidthFactor?: number;
|
|
82
|
+
minHeightFactor?: number;
|
|
83
|
+
maxHeightFactor?: number;
|
|
84
|
+
/** width / height. Derives whichever axis is left open; see `resolveSize`. */
|
|
85
|
+
aspectRatio?: number;
|
|
86
|
+
}
|
|
87
|
+
/** Split `SizingParams` into the per-axis form `resolveSize` takes. */
|
|
88
|
+
export declare function extentSpecs(p: SizingParams): {
|
|
89
|
+
width: ExtentSpec;
|
|
90
|
+
height: ExtentSpec;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Resolve both axes of a sized element: relative sizing, then `aspectRatio`, then `min`/`max`.
|
|
94
|
+
*
|
|
95
|
+
* The order follows CSS: a ratio fills in the axis you left open, and min/max clamp afterwards - so an
|
|
96
|
+
* explicit bound wins over the ratio, exactly as `min-height` beats `aspect-ratio` in a browser.
|
|
97
|
+
*
|
|
98
|
+
* `min`/`max` also come back as NARROWED constraints, because an axis with no explicit size still has
|
|
99
|
+
* to obey them. `maxWidth` alone means "fill, but no wider than this", and the caller's own fill and
|
|
100
|
+
* shrink-wrap paths get that for free by using the returned constraints instead of the ones passed in.
|
|
101
|
+
* A `width` of `undefined` keeps its usual meaning: fill or shrink-wrap.
|
|
102
|
+
*/
|
|
103
|
+
export declare function resolveSize(width: ExtentSpec, height: ExtentSpec, aspectRatio: number | undefined, constraints: BoxConstraints): {
|
|
104
|
+
width?: number;
|
|
105
|
+
height?: number;
|
|
106
|
+
constraints: BoxConstraints;
|
|
107
|
+
};
|
|
55
108
|
/** The size an element resolves to and returns UP the tree. */
|
|
56
109
|
export interface Size {
|
|
57
110
|
width: number;
|
|
@@ -62,6 +62,16 @@ export class BoxConstraints {
|
|
|
62
62
|
deflate(horizontal, vertical) {
|
|
63
63
|
return new BoxConstraints(Math.max(0, this.minWidth - horizontal), Math.max(0, this.maxWidth - horizontal), Math.max(0, this.minHeight - vertical), Math.max(0, this.maxHeight - vertical));
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Tightens this box by an element's own `min`/`max` on either axis. A max never widens the offered
|
|
67
|
+
* room, and a min may push past it - `minHeight: 500` in a 300pt region means 500, and pagination
|
|
68
|
+
* deals with the overflow, the same answer CSS gives.
|
|
69
|
+
*/
|
|
70
|
+
narrow(minWidth, maxWidth, minHeight, maxHeight) {
|
|
71
|
+
const lo = (own, current) => Math.max(current, own ?? 0);
|
|
72
|
+
const hi = (own, current) => own === undefined ? current : Math.min(current, own);
|
|
73
|
+
return new BoxConstraints(lo(minWidth, this.minWidth), Math.max(hi(maxWidth, this.maxWidth), lo(minWidth, this.minWidth)), lo(minHeight, this.minHeight), Math.max(hi(maxHeight, this.maxHeight), lo(minHeight, this.minHeight)));
|
|
74
|
+
}
|
|
65
75
|
/** Returns the constraints clamped to lie within `parent` (Flutter's enforce). */
|
|
66
76
|
enforce(parent) {
|
|
67
77
|
return new BoxConstraints(Math.max(parent.minWidth, Math.min(this.minWidth, parent.maxWidth)), Math.max(parent.minWidth, Math.min(this.maxWidth, parent.maxWidth)), Math.max(parent.minHeight, Math.min(this.minHeight, parent.maxHeight)), Math.max(parent.minHeight, Math.min(this.maxHeight, parent.maxHeight)));
|
|
@@ -81,3 +91,65 @@ export function resolveExtent(fixed, factor, boundedMax, hasBounded) {
|
|
|
81
91
|
return boundedMax * factor;
|
|
82
92
|
return undefined;
|
|
83
93
|
}
|
|
94
|
+
/** Split `SizingParams` into the per-axis form `resolveSize` takes. */
|
|
95
|
+
export function extentSpecs(p) {
|
|
96
|
+
return {
|
|
97
|
+
width: {
|
|
98
|
+
fixed: p.width,
|
|
99
|
+
factor: p.widthFactor,
|
|
100
|
+
min: p.minWidth,
|
|
101
|
+
minFactor: p.minWidthFactor,
|
|
102
|
+
max: p.maxWidth,
|
|
103
|
+
maxFactor: p.maxWidthFactor,
|
|
104
|
+
},
|
|
105
|
+
height: {
|
|
106
|
+
fixed: p.height,
|
|
107
|
+
factor: p.heightFactor,
|
|
108
|
+
min: p.minHeight,
|
|
109
|
+
minFactor: p.minHeightFactor,
|
|
110
|
+
max: p.maxHeight,
|
|
111
|
+
maxFactor: p.maxHeightFactor,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve both axes of a sized element: relative sizing, then `aspectRatio`, then `min`/`max`.
|
|
117
|
+
*
|
|
118
|
+
* The order follows CSS: a ratio fills in the axis you left open, and min/max clamp afterwards - so an
|
|
119
|
+
* explicit bound wins over the ratio, exactly as `min-height` beats `aspect-ratio` in a browser.
|
|
120
|
+
*
|
|
121
|
+
* `min`/`max` also come back as NARROWED constraints, because an axis with no explicit size still has
|
|
122
|
+
* to obey them. `maxWidth` alone means "fill, but no wider than this", and the caller's own fill and
|
|
123
|
+
* shrink-wrap paths get that for free by using the returned constraints instead of the ones passed in.
|
|
124
|
+
* A `width` of `undefined` keeps its usual meaning: fill or shrink-wrap.
|
|
125
|
+
*/
|
|
126
|
+
export function resolveSize(width, height, aspectRatio, constraints) {
|
|
127
|
+
const bounds = constraints.narrow(resolveExtent(width.min, width.minFactor, constraints.maxWidth, constraints.hasBoundedWidth), resolveExtent(width.max, width.maxFactor, constraints.maxWidth, constraints.hasBoundedWidth), resolveExtent(height.min, height.minFactor, constraints.maxHeight, constraints.hasBoundedHeight), resolveExtent(height.max, height.maxFactor, constraints.maxHeight, constraints.hasBoundedHeight));
|
|
128
|
+
// Against the OFFERED box, not the narrowed one: `width: "50%"` with `maxWidth: 100` in a 400pt
|
|
129
|
+
// region is 200 capped to 100, not 50% of 100. The clamp below applies the bound.
|
|
130
|
+
let w = resolveExtent(width.fixed, width.factor, constraints.maxWidth, constraints.hasBoundedWidth);
|
|
131
|
+
let h = resolveExtent(height.fixed, height.factor, constraints.maxHeight, constraints.hasBoundedHeight);
|
|
132
|
+
if (aspectRatio !== undefined && aspectRatio > 0) {
|
|
133
|
+
if (w !== undefined && h === undefined)
|
|
134
|
+
h = w / aspectRatio;
|
|
135
|
+
else if (h !== undefined && w === undefined)
|
|
136
|
+
w = h * aspectRatio;
|
|
137
|
+
else if (w === undefined && h === undefined) {
|
|
138
|
+
// Neither axis pinned: take the offered width and let the ratio give the height (CSS block
|
|
139
|
+
// behaviour). Falls back to the height when only that axis is bounded.
|
|
140
|
+
if (bounds.hasBoundedWidth) {
|
|
141
|
+
w = bounds.maxWidth;
|
|
142
|
+
h = w / aspectRatio;
|
|
143
|
+
}
|
|
144
|
+
else if (bounds.hasBoundedHeight) {
|
|
145
|
+
h = bounds.maxHeight;
|
|
146
|
+
w = h * aspectRatio;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
width: w === undefined ? undefined : bounds.constrainWidth(w),
|
|
152
|
+
height: h === undefined ? undefined : bounds.constrainHeight(h),
|
|
153
|
+
constraints: bounds,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved and unresolved edge insets, on the LAYOUT side of the seam.
|
|
3
|
+
*
|
|
4
|
+
* The public `Insets` input (points, percentages, four shapes) lives in `api/insets.ts` and normalizes
|
|
5
|
+
* into the specs below - the same split `dimension.ts` and `box-constraints.ts` already use for sizes,
|
|
6
|
+
* so an element never sees a percentage string and the api layer never sees layout geometry.
|
|
7
|
+
*/
|
|
8
|
+
/** One side: an absolute point size, or a fraction of the available width. */
|
|
9
|
+
export interface EdgeSpec {
|
|
10
|
+
points?: number;
|
|
11
|
+
factor?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `[top, right, bottom, left]`, each still unresolved.
|
|
15
|
+
*
|
|
16
|
+
* A plain number is accepted as shorthand for `{ points: n }`: the engine elements are exported for
|
|
17
|
+
* power users and used to take four numbers, so requiring `{ points: 5 }` would break them for a
|
|
18
|
+
* feature they are not using.
|
|
19
|
+
*/
|
|
20
|
+
export type EdgeSpecs = [EdgeInput, EdgeInput, EdgeInput, EdgeInput];
|
|
21
|
+
/** A side as given: points for short, or a spec when it may be a percentage. */
|
|
22
|
+
export type EdgeInput = number | EdgeSpec;
|
|
23
|
+
/**
|
|
24
|
+
* Turn the specs into points. `available` is the WIDTH the element was offered - a percentage resolves
|
|
25
|
+
* against the width on ALL FOUR sides, top and bottom included, which is the CSS rule (and Yoga's, so
|
|
26
|
+
* react-pdf agrees). An unbounded width leaves a percentage at 0, the no-op a percentage size gives.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveEdges(specs: EdgeSpecs, available: number): [number, number, number, number];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved and unresolved edge insets, on the LAYOUT side of the seam.
|
|
3
|
+
*
|
|
4
|
+
* The public `Insets` input (points, percentages, four shapes) lives in `api/insets.ts` and normalizes
|
|
5
|
+
* into the specs below - the same split `dimension.ts` and `box-constraints.ts` already use for sizes,
|
|
6
|
+
* so an element never sees a percentage string and the api layer never sees layout geometry.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Turn the specs into points. `available` is the WIDTH the element was offered - a percentage resolves
|
|
10
|
+
* against the width on ALL FOUR sides, top and bottom included, which is the CSS rule (and Yoga's, so
|
|
11
|
+
* react-pdf agrees). An unbounded width leaves a percentage at 0, the no-op a percentage size gives.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveEdges(specs, available) {
|
|
14
|
+
const one = (e) => {
|
|
15
|
+
if (typeof e === "number")
|
|
16
|
+
return e;
|
|
17
|
+
return (e.points ?? (e.factor !== undefined && Number.isFinite(available) ? available * e.factor : 0));
|
|
18
|
+
};
|
|
19
|
+
return [one(specs[0]), one(specs[1]), one(specs[2]), one(specs[3])];
|
|
20
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `min-content` sizing - the smallest extent a subtree can be squeezed into and still hold together.
|
|
3
|
+
*
|
|
4
|
+
* The shrink pass in `FlexLayoutHelper` floors every child at this, so an overflowing line stops
|
|
5
|
+
* squeezing instead of grinding a paragraph down to one word per line. It is the same rule a browser
|
|
6
|
+
* applies through the automatic `min-width` of a flex item (CSS Flexbox 4.5).
|
|
7
|
+
*
|
|
8
|
+
* Only the leaves know a real number - a `Text` its longest word, everything else nothing in
|
|
9
|
+
* particular. These helpers are how a container folds its children's answers into its own.
|
|
10
|
+
*/
|
|
11
|
+
import type { LayoutContext } from "../elements/pdf-element.js";
|
|
12
|
+
import type { PDFElement } from "../elements/pdf-element.js";
|
|
13
|
+
/**
|
|
14
|
+
* The children's floor, folded along one axis: children laid out ALONG the queried axis have to fit
|
|
15
|
+
* side by side, so their floors add up (plus the gaps between them); across it they merely overlap,
|
|
16
|
+
* so the widest one wins.
|
|
17
|
+
*
|
|
18
|
+
* A wrapping stack adds up nothing: it may put every child on its own line, so it is only as wide as
|
|
19
|
+
* its widest child - which is what CSS says about a multi-line flex container too.
|
|
20
|
+
*/
|
|
21
|
+
export declare function stackMinIntrinsic(children: PDFElement[], gap: number, alongAxis: boolean, horizontal: boolean, ctx: LayoutContext, wraps?: boolean): number;
|
|
22
|
+
/**
|
|
23
|
+
* Folds an element's own declared extent into the floor its content asks for. CSS calls these the
|
|
24
|
+
* "specified size suggestion" and the "content size suggestion" and takes the SMALLER: a box that
|
|
25
|
+
* declares 400pt around content needing 50 may still be squeezed to 50, while one declaring 400
|
|
26
|
+
* around content needing 500 cannot go below 400. An extent given as a fraction is not a suggestion
|
|
27
|
+
* at all - it has no size of its own until the parent is known - so it is ignored here.
|
|
28
|
+
*/
|
|
29
|
+
export declare function withDeclaredExtent(contentFloor: number, declared: number | undefined, explicitMin?: number): number;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The children's floor, folded along one axis: children laid out ALONG the queried axis have to fit
|
|
3
|
+
* side by side, so their floors add up (plus the gaps between them); across it they merely overlap,
|
|
4
|
+
* so the widest one wins.
|
|
5
|
+
*
|
|
6
|
+
* A wrapping stack adds up nothing: it may put every child on its own line, so it is only as wide as
|
|
7
|
+
* its widest child - which is what CSS says about a multi-line flex container too.
|
|
8
|
+
*/
|
|
9
|
+
export function stackMinIntrinsic(children, gap, alongAxis, horizontal, ctx, wraps = false) {
|
|
10
|
+
const floors = children.map((c) => c.minIntrinsicMain(horizontal, ctx));
|
|
11
|
+
if (floors.length === 0)
|
|
12
|
+
return 0;
|
|
13
|
+
if (!alongAxis || wraps)
|
|
14
|
+
return Math.max(...floors);
|
|
15
|
+
return floors.reduce((sum, f) => sum + f, 0) + gap * (floors.length - 1);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Folds an element's own declared extent into the floor its content asks for. CSS calls these the
|
|
19
|
+
* "specified size suggestion" and the "content size suggestion" and takes the SMALLER: a box that
|
|
20
|
+
* declares 400pt around content needing 50 may still be squeezed to 50, while one declaring 400
|
|
21
|
+
* around content needing 500 cannot go below 400. An extent given as a fraction is not a suggestion
|
|
22
|
+
* at all - it has no size of its own until the parent is known - so it is ignored here.
|
|
23
|
+
*/
|
|
24
|
+
export function withDeclaredExtent(contentFloor, declared, explicitMin) {
|
|
25
|
+
// An explicit `minWidth`/`minHeight` REPLACES the automatic minimum - it does not compete with it.
|
|
26
|
+
// CSS applies the automatic minimum only while `min-width` is `auto`, so a smaller explicit value
|
|
27
|
+
// wins too: `min-width: 0` is the standard way to let a flex item shrink past its longest word, and
|
|
28
|
+
// taking the larger of the two would quietly ignore it. Measured in Chrome on one long word in a
|
|
29
|
+
// 200pt line: 132.08pt with `auto`, 84.64pt with `0`.
|
|
30
|
+
if (explicitMin !== undefined)
|
|
31
|
+
return explicitMin;
|
|
32
|
+
// Without an explicit bound the floor is CSS's content-based minimum: the smaller of what the
|
|
33
|
+
// element declares and what its content actually needs. It matters because the shrink pass must not
|
|
34
|
+
// aim below a size the child will refuse - the share it was supposed to give would never arrive,
|
|
35
|
+
// and the line would stay over its width.
|
|
36
|
+
return declared === undefined ? contentFloor : Math.min(declared, contentFloor);
|
|
37
|
+
}
|
|
@@ -3,7 +3,7 @@ export class AnchorRenderer {
|
|
|
3
3
|
static async render(element, objectManager) {
|
|
4
4
|
const { name, child, y } = element.getProps();
|
|
5
5
|
const renderer = RendererRegistry.getRenderer(child);
|
|
6
|
-
const childNodes =
|
|
6
|
+
const childNodes = await renderer(child, objectManager);
|
|
7
7
|
// The child draws normally; an Anchor IR node carries the name + scroll target for the page renderer
|
|
8
8
|
// to register as a named destination (it produces no content-stream ops itself).
|
|
9
9
|
return [...childNodes, { type: "anchor", y, name }];
|
|
@@ -3,7 +3,7 @@ export class BookmarkRenderer {
|
|
|
3
3
|
static async render(element, objectManager) {
|
|
4
4
|
const { title, level, child, y } = element.getProps();
|
|
5
5
|
const renderer = RendererRegistry.getRenderer(child);
|
|
6
|
-
const childNodes =
|
|
6
|
+
const childNodes = await renderer(child, objectManager);
|
|
7
7
|
// The child draws normally; an Outline IR node carries the title + nesting + scroll target for the
|
|
8
8
|
// page renderer to collect into the /Outlines tree (it produces no content-stream ops itself).
|
|
9
9
|
return [...childNodes, { type: "outline", y, title, level }];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PDFObjectManager } from "../utils/pdf-object-manager.js";
|
|
2
|
+
import { CanvasElement } from "../elements/canvas-element.js";
|
|
3
|
+
import type { IRNode } from "../ir/display-list.js";
|
|
4
|
+
/**
|
|
5
|
+
* Runs the caller's paint callback and hands back what it drew.
|
|
6
|
+
*
|
|
7
|
+
* The painter works in CANVAS coordinates (0,0 is the box's own corner), so the whole drawing is
|
|
8
|
+
* wrapped in one translate rather than every point being offset - the same shape `Svg` uses, and it
|
|
9
|
+
* keeps the callback independent of where the box ended up. The clip is the box: a drawing that runs
|
|
10
|
+
* past its own edge would otherwise bleed into the layout beside it.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CanvasRenderer {
|
|
13
|
+
static render(element: CanvasElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CanvasPainter } from "../canvas/painter.js";
|
|
2
|
+
/**
|
|
3
|
+
* Runs the caller's paint callback and hands back what it drew.
|
|
4
|
+
*
|
|
5
|
+
* The painter works in CANVAS coordinates (0,0 is the box's own corner), so the whole drawing is
|
|
6
|
+
* wrapped in one translate rather than every point being offset - the same shape `Svg` uses, and it
|
|
7
|
+
* keeps the callback independent of where the box ended up. The clip is the box: a drawing that runs
|
|
8
|
+
* past its own edge would otherwise bleed into the layout beside it.
|
|
9
|
+
*/
|
|
10
|
+
export class CanvasRenderer {
|
|
11
|
+
static async render(element, objectManager) {
|
|
12
|
+
const { x, y, width, height, paint, alt } = element.getProps();
|
|
13
|
+
// The layout bounds are kept as plain numbers, and the painter and the callback each get their
|
|
14
|
+
// OWN size object. Sharing one meant a callback that wrote to `size.width` moved the clip away
|
|
15
|
+
// from the box layout had reserved - the drawing would then bleed into what sits beside it.
|
|
16
|
+
const w = width ?? 0;
|
|
17
|
+
const h = height ?? 0;
|
|
18
|
+
const painter = new CanvasPainter({ width: w, height: h });
|
|
19
|
+
paint(painter, { width: w, height: h });
|
|
20
|
+
const drawn = painter.drawn();
|
|
21
|
+
const nodes = [
|
|
22
|
+
{ type: "clip-push", x: x ?? 0, y: y ?? 0, width: w, height: h },
|
|
23
|
+
{ type: "transform-push", matrix: [1, 0, 0, 1, x ?? 0, y ?? 0] },
|
|
24
|
+
...drawn,
|
|
25
|
+
{ type: "transform-pop" },
|
|
26
|
+
{ type: "clip-pop" },
|
|
27
|
+
];
|
|
28
|
+
if (!alt || !objectManager.struct.enabled)
|
|
29
|
+
return nodes;
|
|
30
|
+
const tag = {
|
|
31
|
+
role: "Figure",
|
|
32
|
+
key: objectManager.struct.openElement(element.structId, "Figure", { alt }),
|
|
33
|
+
};
|
|
34
|
+
return nodes.map((node) => (node.type === "path" ? { ...node, tag } : node));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -6,9 +6,7 @@ export class ContainerRenderer {
|
|
|
6
6
|
if (children)
|
|
7
7
|
for (const child of children) {
|
|
8
8
|
const renderer = RendererRegistry.getRenderer(child);
|
|
9
|
-
|
|
10
|
-
nodes.push(...(await renderer(child, objectManager)));
|
|
11
|
-
}
|
|
9
|
+
nodes.push(...(await renderer(child, objectManager)));
|
|
12
10
|
}
|
|
13
11
|
return nodes;
|
|
14
12
|
}
|
|
@@ -5,6 +5,6 @@ export class DefaultTextStyleRenderer {
|
|
|
5
5
|
static async render(element, objectManager) {
|
|
6
6
|
const { child } = element.getProps();
|
|
7
7
|
const renderer = RendererRegistry.getRenderer(child);
|
|
8
|
-
return renderer
|
|
8
|
+
return renderer(child, objectManager);
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -3,6 +3,6 @@ export class ExpandedRenderer {
|
|
|
3
3
|
static async render(expandedElement, objectManager) {
|
|
4
4
|
const { child } = expandedElement.getProps();
|
|
5
5
|
const renderer = RendererRegistry.getRenderer(child);
|
|
6
|
-
return renderer
|
|
6
|
+
return renderer(child, objectManager);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BoxFit } from "../elements/image-element.js";
|
|
2
2
|
import { bytesFromLatin1 } from "../utils/bytes.js";
|
|
3
3
|
import { applyContainFit, applyCoverFit, applyFillFit, applyFitNone, decodePngToRgbFlate, } from "../utils/image-helper.js";
|
|
4
|
+
import { isRounded } from "../ir/display-list.js";
|
|
4
5
|
export class ImageRenderer {
|
|
5
6
|
static async render(imageElement, objectManager) {
|
|
6
7
|
// Load the image and convert it in a binary string
|
|
@@ -56,7 +57,7 @@ export class ImageRenderer {
|
|
|
56
57
|
}
|
|
57
58
|
// A radius rounds the image BOX (the element frame), so it clips to that frame too -
|
|
58
59
|
// independent of the cover/contain overflow clip.
|
|
59
|
-
const wantsClip = mustCreateOverflowContainer || (radius
|
|
60
|
+
const wantsClip = mustCreateOverflowContainer || isRounded(radius);
|
|
60
61
|
// The fitted geometry becomes a display-list primitive; the backend registers
|
|
61
62
|
// the XObject and emits the placement (+ clip, rounded when a radius is set).
|
|
62
63
|
const node = {
|
package/dist/renderer/index.d.ts
CHANGED
package/dist/renderer/index.js
CHANGED
|
@@ -7,6 +7,6 @@ export class KeepTogetherRenderer {
|
|
|
7
7
|
static async render(group, objectManager) {
|
|
8
8
|
const { child } = group.getProps();
|
|
9
9
|
const renderer = RendererRegistry.getRenderer(child);
|
|
10
|
-
return renderer
|
|
10
|
+
return renderer(child, objectManager);
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -3,7 +3,7 @@ export class LinkRenderer {
|
|
|
3
3
|
static async render(element, objectManager) {
|
|
4
4
|
const { href, dest, child, x, y, width, height } = element.getProps();
|
|
5
5
|
const renderer = RendererRegistry.getRenderer(child);
|
|
6
|
-
const childNodes =
|
|
6
|
+
const childNodes = await renderer(child, objectManager);
|
|
7
7
|
// The child draws normally; a Link IR node carries the clickable rect + target (external href or
|
|
8
8
|
// internal dest) for the page renderer to emit as a /Link annotation (no content-stream ops itself).
|
|
9
9
|
return [...childNodes, { type: "link", x, y, width, height, href, dest }];
|
|
@@ -3,6 +3,6 @@ export class PaddingRenderer {
|
|
|
3
3
|
static async render(paddingElement, objectManager) {
|
|
4
4
|
const { child } = paddingElement.getProps();
|
|
5
5
|
const renderer = RendererRegistry.getRenderer(child);
|
|
6
|
-
return renderer
|
|
6
|
+
return renderer(child, objectManager);
|
|
7
7
|
}
|
|
8
8
|
}
|
|
@@ -21,9 +21,7 @@ export class PageRenderer {
|
|
|
21
21
|
const nodes = [];
|
|
22
22
|
for (const element of renderables) {
|
|
23
23
|
const renderer = RendererRegistry.getRenderer(element);
|
|
24
|
-
|
|
25
|
-
nodes.push(...(await renderer(element, objectManager)));
|
|
26
|
-
}
|
|
24
|
+
nodes.push(...(await renderer(element, objectManager)));
|
|
27
25
|
}
|
|
28
26
|
// Accessible tagging: one struct context per page (allocates its StructParents index), threaded into
|
|
29
27
|
// serialize so each drawable node is wrapped in marked content. Undefined = tagging off (byte-identical).
|