@jasy/pdf 1.0.0-alpha.5 → 1.0.0-alpha.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/dist/api/content.d.ts +6 -3
- package/dist/api/content.js +12 -3
- package/dist/api/descriptor.js +22 -1
- package/dist/api/dimension.d.ts +20 -0
- package/dist/api/dimension.js +16 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/layout.d.ts +65 -3
- package/dist/api/layout.js +71 -2
- package/dist/api/page-builder.d.ts +32 -0
- package/dist/api/page-builder.js +32 -0
- package/dist/api/text.d.ts +6 -0
- package/dist/api/text.js +31 -4
- package/dist/elements/container-element.d.ts +17 -1
- package/dist/elements/container-element.js +54 -22
- package/dist/elements/image-element.d.ts +19 -1
- package/dist/elements/image-element.js +52 -11
- package/dist/elements/index.d.ts +6 -0
- package/dist/elements/index.js +6 -0
- package/dist/elements/layout/anchor-element.d.ts +23 -0
- package/dist/elements/layout/anchor-element.js +27 -0
- package/dist/elements/layout/bookmark-element.d.ts +27 -0
- package/dist/elements/layout/bookmark-element.js +30 -0
- package/dist/elements/layout/link-element.d.ts +33 -0
- package/dist/elements/layout/link-element.js +39 -0
- package/dist/elements/layout/page-builder-element.d.ts +32 -0
- package/dist/elements/layout/page-builder-element.js +43 -0
- package/dist/elements/layout/rotated-box-element.d.ts +35 -0
- package/dist/elements/layout/rotated-box-element.js +54 -0
- package/dist/elements/layout/rotated-element.d.ts +32 -0
- package/dist/elements/layout/rotated-element.js +41 -0
- package/dist/elements/page-element.d.ts +8 -0
- package/dist/elements/page-element.js +13 -1
- package/dist/elements/pdf-element.d.ts +31 -0
- package/dist/elements/pdf-element.js +19 -0
- package/dist/elements/rectangle-element.d.ts +11 -1
- package/dist/elements/rectangle-element.js +49 -13
- package/dist/elements/row-element.d.ts +11 -1
- package/dist/elements/row-element.js +37 -8
- package/dist/elements/text-element.d.ts +4 -0
- package/dist/ir/display-list.d.ts +59 -1
- package/dist/layout/box-constraints.d.ts +8 -0
- package/dist/layout/box-constraints.js +14 -0
- package/dist/layout/collect-images.d.ts +9 -0
- package/dist/layout/collect-images.js +34 -0
- package/dist/renderer/anchor-renderer.d.ts +6 -0
- package/dist/renderer/anchor-renderer.js +11 -0
- package/dist/renderer/bookmark-renderer.d.ts +6 -0
- package/dist/renderer/bookmark-renderer.js +11 -0
- package/dist/renderer/link-renderer.d.ts +6 -0
- package/dist/renderer/link-renderer.js +11 -0
- package/dist/renderer/page-builder-renderer.d.ts +6 -0
- package/dist/renderer/page-builder-renderer.js +11 -0
- package/dist/renderer/page-renderer.js +37 -2
- package/dist/renderer/pdf-backend.d.ts +10 -0
- package/dist/renderer/pdf-backend.js +77 -1
- package/dist/renderer/pdf-document-renderer.d.ts +16 -7
- package/dist/renderer/pdf-document-renderer.js +65 -33
- package/dist/renderer/pdf-renderer.js +47 -2
- package/dist/renderer/rotated-renderer.d.ts +7 -0
- package/dist/renderer/rotated-renderer.js +29 -0
- package/dist/renderer/text-renderer.js +26 -5
- package/dist/utils/afm-parser.js +6 -4
- package/dist/utils/dest-registry.d.ts +19 -0
- package/dist/utils/dest-registry.js +32 -0
- package/dist/utils/flex-layout.d.ts +10 -3
- package/dist/utils/flex-layout.js +26 -6
- package/dist/utils/outline.d.ts +21 -0
- package/dist/utils/outline.js +80 -0
- package/dist/utils/pdf-object-manager.d.ts +6 -0
- package/dist/utils/pdf-object-manager.js +0 -0
- package/package.json +1 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { FlexLayoutHelper, VERTICAL_AXIS } from "../utils/flex-layout.js";
|
|
2
|
+
import { resolveExtent } from "../layout/box-constraints.js";
|
|
2
3
|
import { packChildren } from "../layout/fragmentation.js";
|
|
3
|
-
import { SizedPDFElement, } from "./pdf-element.js";
|
|
4
|
+
import { FlexiblePDFElement, SizedPDFElement, } from "./pdf-element.js";
|
|
4
5
|
export class ContainerElement extends SizedPDFElement {
|
|
5
|
-
constructor({ x, y, width, height, children, gap, main, cross }) {
|
|
6
|
+
constructor({ x, y, width, height, children, gap, main, cross, widthFactor, heightFactor, }) {
|
|
6
7
|
super({ x, y, width, height });
|
|
7
8
|
this.children = children;
|
|
8
9
|
this.gap = gap ?? 0;
|
|
9
10
|
this.main = main ?? "start";
|
|
10
11
|
this.cross = cross ?? "stretch";
|
|
12
|
+
this.requested = { width, height, widthFactor, heightFactor };
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Splits the vertical stack across pages. Children are measured against the content
|
|
@@ -35,27 +37,59 @@ export class ContainerElement extends SizedPDFElement {
|
|
|
35
37
|
return new ContainerElement({
|
|
36
38
|
x: this.x,
|
|
37
39
|
y: this.y,
|
|
38
|
-
width
|
|
39
|
-
|
|
40
|
+
// Carry the requested WIDTH (a %-width column keeps its width on every page); the height is
|
|
41
|
+
// intentionally dropped so each fragment shrink-wraps to the content it actually holds.
|
|
42
|
+
width: this.requested.width,
|
|
43
|
+
widthFactor: this.requested.widthFactor,
|
|
40
44
|
children,
|
|
41
45
|
gap: this.gap,
|
|
42
46
|
main: this.main,
|
|
43
47
|
cross: this.cross,
|
|
44
48
|
});
|
|
45
49
|
}
|
|
50
|
+
relativeSizeFactor(horizontal) {
|
|
51
|
+
return horizontal ? this.requested.widthFactor : this.requested.heightFactor;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A Column stacks vertically, so a flex child of its OWN only ever needs a bounded height. But the need
|
|
55
|
+
* also propagates: a `Spacer` two levels down is just as unable to resolve against an infinite axis, so
|
|
56
|
+
* we ask the children too - on both axes, since a nested Row's `Spacer` needs a bounded WIDTH and a
|
|
57
|
+
* Column nested in a Row is exactly where that width goes missing.
|
|
58
|
+
*
|
|
59
|
+
* An explicit extent on an axis short-circuits it: the element already knows how big it is there, and
|
|
60
|
+
* with no flex descendant nothing needs distributing, so it keeps shrink-wrapping. That is what leaves
|
|
61
|
+
* every existing layout untouched.
|
|
62
|
+
*/
|
|
63
|
+
needsBoundedMain(horizontal) {
|
|
64
|
+
const requested = horizontal
|
|
65
|
+
? [this.requested.width, this.requested.widthFactor]
|
|
66
|
+
: [this.requested.height, this.requested.heightFactor];
|
|
67
|
+
if (requested.some((v) => v !== undefined))
|
|
68
|
+
return false;
|
|
69
|
+
const ownFlexChild = !horizontal && this.children.some((c) => c instanceof FlexiblePDFElement);
|
|
70
|
+
return ownFlexChild || this.children.some((c) => c.needsBoundedMain(horizontal));
|
|
71
|
+
}
|
|
46
72
|
calculateLayout(constraints, offset, ctx) {
|
|
47
|
-
// The container fills the width/height it is offered; when an axis is unbounded it
|
|
48
|
-
// shrink-wraps to its children instead (mirrors how Row shrink-wraps). Width unbounded
|
|
49
|
-
// happens for a Column nested in a Row (the Row offers its fixed children unbounded
|
|
50
|
-
// width); passing 0 there would collapse every child to width 0.
|
|
51
|
-
if (constraints.hasBoundedWidth)
|
|
52
|
-
this.width = constraints.maxWidth;
|
|
53
|
-
if (constraints.hasBoundedHeight)
|
|
54
|
-
this.height = constraints.maxHeight;
|
|
55
73
|
this.x = offset.x;
|
|
56
74
|
this.y = offset.y;
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
// Relative sizing: a pinned extent (fixed points or a fraction of the offered box, clamped into
|
|
76
|
+
// the constraints) wins; else fill a bounded axis; else stay `undefined` and shrink-wrap below.
|
|
77
|
+
// A Column nested in a Row gets unbounded width - passing 0 there would collapse children to 0.
|
|
78
|
+
const explicitWidth = resolveExtent(this.requested.width, this.requested.widthFactor, constraints.maxWidth, constraints.hasBoundedWidth);
|
|
79
|
+
const explicitHeight = resolveExtent(this.requested.height, this.requested.heightFactor, constraints.maxHeight, constraints.hasBoundedHeight);
|
|
80
|
+
const boundedWidth = explicitWidth !== undefined
|
|
81
|
+
? constraints.constrainWidth(explicitWidth)
|
|
82
|
+
: constraints.hasBoundedWidth
|
|
83
|
+
? constraints.maxWidth
|
|
84
|
+
: undefined;
|
|
85
|
+
const boundedHeight = explicitHeight !== undefined
|
|
86
|
+
? constraints.constrainHeight(explicitHeight)
|
|
87
|
+
: constraints.hasBoundedHeight
|
|
88
|
+
? constraints.maxHeight
|
|
89
|
+
: undefined;
|
|
90
|
+
// Vertical stack: cross = width (children fill it), main = height (the stacking extent).
|
|
91
|
+
const crossAvail = boundedWidth ?? Infinity;
|
|
92
|
+
const mainAvail = boundedHeight ?? Infinity;
|
|
59
93
|
let result = { mainUsed: 0, crossUsed: 0 };
|
|
60
94
|
if (this.children) {
|
|
61
95
|
// Vertical flex stack (main = height, cross = width). The shared helper measures
|
|
@@ -63,14 +97,12 @@ export class ContainerElement extends SizedPDFElement {
|
|
|
63
97
|
// in source order.
|
|
64
98
|
result = FlexLayoutHelper.layout(this.children, VERTICAL_AXIS, mainAvail, crossAvail, this.y, this.x, { gap: this.gap, main: this.main, cross: this.cross }, ctx);
|
|
65
99
|
}
|
|
66
|
-
//
|
|
67
|
-
// stack, width = the widest child). Top-left coordinates; the container draws nothing,
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.width
|
|
72
|
-
this.height = height;
|
|
73
|
-
return { width, height };
|
|
100
|
+
// Pinned/bounded axis takes that extent; an unbounded one shrink-wraps to the children (height =
|
|
101
|
+
// the stack, width = the widest child). Top-left coordinates; the container draws nothing, and
|
|
102
|
+
// the Y-flip happens once at the IR -> backend seam.
|
|
103
|
+
this.width = boundedWidth ?? result.crossUsed;
|
|
104
|
+
this.height = boundedHeight ?? result.mainUsed;
|
|
105
|
+
return { width: this.width, height: this.height };
|
|
74
106
|
}
|
|
75
107
|
getProps() {
|
|
76
108
|
return {
|
|
@@ -19,6 +19,7 @@ export declare class CustomLocalImage extends CustomImage {
|
|
|
19
19
|
private imagePath;
|
|
20
20
|
private fileBuffer;
|
|
21
21
|
private fileRawData;
|
|
22
|
+
private dims?;
|
|
22
23
|
constructor(imagePath: string);
|
|
23
24
|
init(): Promise<void>;
|
|
24
25
|
getImageType(): Promise<string>;
|
|
@@ -36,6 +37,7 @@ export declare class CustomLocalImage extends CustomImage {
|
|
|
36
37
|
export declare class CustomBytesImage extends CustomImage {
|
|
37
38
|
private bytes;
|
|
38
39
|
private fileRawData;
|
|
40
|
+
private dims?;
|
|
39
41
|
constructor(bytes: Uint8Array);
|
|
40
42
|
init(): Promise<void>;
|
|
41
43
|
getImageType(): Promise<string>;
|
|
@@ -49,6 +51,10 @@ interface ImageElementParams {
|
|
|
49
51
|
image: CustomImage;
|
|
50
52
|
width?: number;
|
|
51
53
|
height?: number;
|
|
54
|
+
/** Width as a fraction (0..1) of the offered width instead of a fixed `width` (relative sizing). */
|
|
55
|
+
widthFactor?: number;
|
|
56
|
+
/** Height as a fraction (0..1) of the offered height; see `widthFactor`. */
|
|
57
|
+
heightFactor?: number;
|
|
52
58
|
fit?: BoxFit;
|
|
53
59
|
/** Corner radius in points; rounds the image box (0 = sharp, default). */
|
|
54
60
|
radius?: number;
|
|
@@ -57,10 +63,22 @@ interface ImageElementParams {
|
|
|
57
63
|
}
|
|
58
64
|
export declare class ImageElement extends SizedPDFElement {
|
|
59
65
|
private image;
|
|
66
|
+
private widthFactor?;
|
|
67
|
+
private heightFactor?;
|
|
60
68
|
private fit;
|
|
61
69
|
private radius;
|
|
62
70
|
private readonly alt?;
|
|
63
|
-
|
|
71
|
+
private requested;
|
|
72
|
+
private intrinsic?;
|
|
73
|
+
constructor({ image, width, height, widthFactor, heightFactor, fit, radius, alt, }: ImageElementParams);
|
|
74
|
+
/**
|
|
75
|
+
* Loads the image and records its intrinsic pixel size. Runs in the async pre-layout pass (layout
|
|
76
|
+
* itself is synchronous and cannot await jimp) so `calculateLayout` can turn a width-only image
|
|
77
|
+
* into a proportional box. Best-effort: a load failure leaves the size unresolved (the renderer
|
|
78
|
+
* surfaces the real error) and the image just keeps whatever explicit size it was given.
|
|
79
|
+
*/
|
|
80
|
+
resolveIntrinsicSize(): Promise<void>;
|
|
81
|
+
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
64
82
|
calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
|
|
65
83
|
getProps(): {
|
|
66
84
|
x: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getImageDimensions } from "../utils/image-helper.js";
|
|
2
2
|
import { latin1FromBytes } from "../utils/bytes.js";
|
|
3
3
|
import { readFileBytesAsync } from "../platform/node-fs.js";
|
|
4
|
+
import { resolveExtent } from "../layout/box-constraints.js";
|
|
4
5
|
import { SizedPDFElement } from "./pdf-element.js";
|
|
5
6
|
// path.extname without node:path (browser-safe): the substring from the last dot, if it sits after the
|
|
6
7
|
// last slash (so a dot in a directory name does not count). Enough for image file extensions.
|
|
@@ -24,6 +25,8 @@ export class CustomLocalImage extends CustomImage {
|
|
|
24
25
|
this.imagePath = imagePath;
|
|
25
26
|
}
|
|
26
27
|
async init() {
|
|
28
|
+
if (this.fileBuffer)
|
|
29
|
+
return; // idempotent: the pre-layout size pass and the renderer both call init()
|
|
27
30
|
try {
|
|
28
31
|
// Loading image and convert it to base64
|
|
29
32
|
await this.loadImage(this.imagePath);
|
|
@@ -59,13 +62,13 @@ export class CustomLocalImage extends CustomImage {
|
|
|
59
62
|
return this.fileRawData;
|
|
60
63
|
}
|
|
61
64
|
async getImageDimensions() {
|
|
65
|
+
if (this.dims)
|
|
66
|
+
return this.dims; // reuse: the pre-layout size pass and the renderer both ask
|
|
62
67
|
if (!this.fileBuffer) {
|
|
63
68
|
throw new Error("You must first call the `loadAndConvertImage` method");
|
|
64
69
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const dimensions = await getImageDimensions(this.fileBuffer);
|
|
68
|
-
return dimensions;
|
|
70
|
+
this.dims = await getImageDimensions(this.fileBuffer);
|
|
71
|
+
return this.dims;
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
/**
|
|
@@ -91,26 +94,64 @@ export class CustomBytesImage extends CustomImage {
|
|
|
91
94
|
return this.fileRawData;
|
|
92
95
|
}
|
|
93
96
|
async getImageDimensions() {
|
|
94
|
-
|
|
97
|
+
if (!this.dims)
|
|
98
|
+
this.dims = await getImageDimensions(this.bytes); // reuse across pre-pass + render
|
|
99
|
+
return this.dims;
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
export class ImageElement extends SizedPDFElement {
|
|
98
|
-
constructor({ image, width, height, fit = BoxFit.none, radius, alt }) {
|
|
103
|
+
constructor({ image, width, height, widthFactor, heightFactor, fit = BoxFit.none, radius, alt, }) {
|
|
104
|
+
// Seed this.width/height from the request so getProps() reflects it before layout; calculateLayout
|
|
105
|
+
// then reads `requested` (never the mutated fields) and overwrites these with the laid-out size.
|
|
99
106
|
super({ x: 0, y: 0, width });
|
|
100
107
|
this.image = image;
|
|
101
108
|
this.height = height;
|
|
109
|
+
this.requested = { width, height };
|
|
110
|
+
this.widthFactor = widthFactor;
|
|
111
|
+
this.heightFactor = heightFactor;
|
|
102
112
|
this.fit = fit;
|
|
103
113
|
this.radius = radius ?? 0;
|
|
104
114
|
this.alt = alt;
|
|
105
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Loads the image and records its intrinsic pixel size. Runs in the async pre-layout pass (layout
|
|
118
|
+
* itself is synchronous and cannot await jimp) so `calculateLayout` can turn a width-only image
|
|
119
|
+
* into a proportional box. Best-effort: a load failure leaves the size unresolved (the renderer
|
|
120
|
+
* surfaces the real error) and the image just keeps whatever explicit size it was given.
|
|
121
|
+
*/
|
|
122
|
+
async resolveIntrinsicSize() {
|
|
123
|
+
try {
|
|
124
|
+
await this.image.init();
|
|
125
|
+
this.intrinsic = await this.image.getImageDimensions();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// leave intrinsic undefined - aspect derivation simply doesn't fire
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
relativeSizeFactor(horizontal) {
|
|
132
|
+
return horizontal ? this.widthFactor : this.heightFactor;
|
|
133
|
+
}
|
|
106
134
|
calculateLayout(constraints, offset, _ctx) {
|
|
107
135
|
this.x = offset.x;
|
|
108
136
|
this.y = offset.y;
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
// Relative sizing: a fixed size or a fraction of the offered box (fraction only in a bounded axis).
|
|
138
|
+
// Read from `requested` (not this.width/height, which we overwrite below) so every pass re-resolves.
|
|
139
|
+
let w = resolveExtent(this.requested.width, this.widthFactor, constraints.maxWidth, constraints.hasBoundedWidth);
|
|
140
|
+
let h = resolveExtent(this.requested.height, this.heightFactor, constraints.maxHeight, constraints.hasBoundedHeight);
|
|
141
|
+
// Aspect auto-size: when the user pinned exactly ONE axis, derive the other from the intrinsic
|
|
142
|
+
// ratio (CSS `width: 50%; height: auto`). Only fires once the pre-pass resolved the pixel size.
|
|
143
|
+
if (this.intrinsic && this.intrinsic.width > 0 && this.intrinsic.height > 0) {
|
|
144
|
+
const ratio = this.intrinsic.width / this.intrinsic.height;
|
|
145
|
+
if (w !== undefined && h === undefined)
|
|
146
|
+
h = w / ratio;
|
|
147
|
+
else if (h !== undefined && w === undefined)
|
|
148
|
+
w = h * ratio;
|
|
149
|
+
}
|
|
150
|
+
// Fall back to the prior behavior for an unpinned axis: a bounded axis fills, otherwise keep the
|
|
151
|
+
// requested fixed size (or undefined). Read from `requested`, never the mutated this.width/height.
|
|
152
|
+
this.width = w ?? (constraints.hasBoundedWidth ? constraints.maxWidth : this.requested.width);
|
|
153
|
+
this.height =
|
|
154
|
+
h ?? (constraints.hasBoundedHeight ? constraints.maxHeight : this.requested.height);
|
|
114
155
|
// Top-left coordinates; the fit logic (renderer) and the Y-flip (seam) run later.
|
|
115
156
|
return { width: this.width ?? 0, height: this.height ?? 0 };
|
|
116
157
|
}
|
package/dist/elements/index.d.ts
CHANGED
|
@@ -8,5 +8,11 @@ export * from "./layout/expanded-element.js";
|
|
|
8
8
|
export * from "./layout/padding-element.js";
|
|
9
9
|
export * from "./layout/default-text-style-element.js";
|
|
10
10
|
export * from "./layout/positioned-element.js";
|
|
11
|
+
export * from "./layout/link-element.js";
|
|
12
|
+
export * from "./layout/bookmark-element.js";
|
|
13
|
+
export * from "./layout/anchor-element.js";
|
|
14
|
+
export * from "./layout/page-builder-element.js";
|
|
15
|
+
export * from "./layout/rotated-element.js";
|
|
16
|
+
export * from "./layout/rotated-box-element.js";
|
|
11
17
|
export * from "./line-element.js";
|
|
12
18
|
export * from "./row-element.js";
|
package/dist/elements/index.js
CHANGED
|
@@ -8,5 +8,11 @@ export * from "./layout/expanded-element.js";
|
|
|
8
8
|
export * from "./layout/padding-element.js";
|
|
9
9
|
export * from "./layout/default-text-style-element.js";
|
|
10
10
|
export * from "./layout/positioned-element.js";
|
|
11
|
+
export * from "./layout/link-element.js";
|
|
12
|
+
export * from "./layout/bookmark-element.js";
|
|
13
|
+
export * from "./layout/anchor-element.js";
|
|
14
|
+
export * from "./layout/page-builder-element.js";
|
|
15
|
+
export * from "./layout/rotated-element.js";
|
|
16
|
+
export * from "./layout/rotated-box-element.js";
|
|
11
17
|
export * from "./line-element.js";
|
|
12
18
|
export * from "./row-element.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PDFElement, LayoutContext } from "../pdf-element.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
+
/**
|
|
4
|
+
* Marks its child as a named jump target for an internal `Link({ to: name })`. Layout-transparent: it
|
|
5
|
+
* delegates `calculateLayout` to the child and records the child's top, which the renderer turns into an
|
|
6
|
+
* `Anchor` IR node (and PDFRenderer into a /Names /Dests entry). It draws nothing - the child renders
|
|
7
|
+
* exactly as it would on its own.
|
|
8
|
+
*/
|
|
9
|
+
export declare class AnchorElement extends PDFElement {
|
|
10
|
+
private name;
|
|
11
|
+
private child;
|
|
12
|
+
private y;
|
|
13
|
+
constructor({ name, child }: {
|
|
14
|
+
name: string;
|
|
15
|
+
child: PDFElement;
|
|
16
|
+
});
|
|
17
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
18
|
+
getProps(): {
|
|
19
|
+
name: string;
|
|
20
|
+
child: PDFElement;
|
|
21
|
+
y: number;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PDFElement } from "../pdf-element.js";
|
|
2
|
+
/**
|
|
3
|
+
* Marks its child as a named jump target for an internal `Link({ to: name })`. Layout-transparent: it
|
|
4
|
+
* delegates `calculateLayout` to the child and records the child's top, which the renderer turns into an
|
|
5
|
+
* `Anchor` IR node (and PDFRenderer into a /Names /Dests entry). It draws nothing - the child renders
|
|
6
|
+
* exactly as it would on its own.
|
|
7
|
+
*/
|
|
8
|
+
export class AnchorElement extends PDFElement {
|
|
9
|
+
constructor({ name, child }) {
|
|
10
|
+
super();
|
|
11
|
+
this.y = 0;
|
|
12
|
+
this.name = name;
|
|
13
|
+
this.child = child;
|
|
14
|
+
}
|
|
15
|
+
calculateLayout(constraints, offset, ctx) {
|
|
16
|
+
const size = this.child.calculateLayout(constraints, offset, ctx);
|
|
17
|
+
this.y = offset.y; // the child's top - the scroll target for a link that jumps here
|
|
18
|
+
return size;
|
|
19
|
+
}
|
|
20
|
+
getProps() {
|
|
21
|
+
return {
|
|
22
|
+
name: this.name,
|
|
23
|
+
child: this.child,
|
|
24
|
+
y: this.y,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PDFElement, LayoutContext } from "../pdf-element.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
+
/**
|
|
4
|
+
* Adds a document-outline entry (a bookmark) that jumps to its child. Layout-transparent: it delegates
|
|
5
|
+
* `calculateLayout` to the child and records the child's top, which the renderer turns into an `Outline`
|
|
6
|
+
* IR node (and PDFRenderer into a /Outlines tree entry). `title` is the label shown in the viewer's
|
|
7
|
+
* bookmark panel; `level` (1-based, default 1) nests it under the nearest preceding smaller level. It
|
|
8
|
+
* draws nothing - the child renders exactly as it would without the bookmark.
|
|
9
|
+
*/
|
|
10
|
+
export declare class BookmarkElement extends PDFElement {
|
|
11
|
+
private title;
|
|
12
|
+
private level;
|
|
13
|
+
private child;
|
|
14
|
+
private y;
|
|
15
|
+
constructor({ title, level, child }: {
|
|
16
|
+
title: string;
|
|
17
|
+
level?: number;
|
|
18
|
+
child: PDFElement;
|
|
19
|
+
});
|
|
20
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
21
|
+
getProps(): {
|
|
22
|
+
title: string;
|
|
23
|
+
level: number;
|
|
24
|
+
child: PDFElement;
|
|
25
|
+
y: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PDFElement } from "../pdf-element.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adds a document-outline entry (a bookmark) that jumps to its child. Layout-transparent: it delegates
|
|
4
|
+
* `calculateLayout` to the child and records the child's top, which the renderer turns into an `Outline`
|
|
5
|
+
* IR node (and PDFRenderer into a /Outlines tree entry). `title` is the label shown in the viewer's
|
|
6
|
+
* bookmark panel; `level` (1-based, default 1) nests it under the nearest preceding smaller level. It
|
|
7
|
+
* draws nothing - the child renders exactly as it would without the bookmark.
|
|
8
|
+
*/
|
|
9
|
+
export class BookmarkElement extends PDFElement {
|
|
10
|
+
constructor({ title, level = 1, child }) {
|
|
11
|
+
super();
|
|
12
|
+
this.y = 0;
|
|
13
|
+
this.title = title;
|
|
14
|
+
this.level = level;
|
|
15
|
+
this.child = child;
|
|
16
|
+
}
|
|
17
|
+
calculateLayout(constraints, offset, ctx) {
|
|
18
|
+
const size = this.child.calculateLayout(constraints, offset, ctx);
|
|
19
|
+
this.y = offset.y; // the child's top - the scroll target for the bookmark
|
|
20
|
+
return size;
|
|
21
|
+
}
|
|
22
|
+
getProps() {
|
|
23
|
+
return {
|
|
24
|
+
title: this.title,
|
|
25
|
+
level: this.level,
|
|
26
|
+
child: this.child,
|
|
27
|
+
y: this.y,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PDFElement, LayoutContext } from "../pdf-element.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
+
/**
|
|
4
|
+
* Makes its child a clickable hyperlink. `href` is an external URL; `dest` is an internal named
|
|
5
|
+
* destination (an `Anchor` elsewhere in the document) - exactly one is set. Layout-transparent: it
|
|
6
|
+
* delegates `calculateLayout` to the child and records the resulting box, which the renderer turns into
|
|
7
|
+
* a `Link` IR node (and the page renderer into a /Link annotation). The link draws nothing itself -
|
|
8
|
+
* style the child (e.g. a blue Text) if you want it to look like a link.
|
|
9
|
+
*/
|
|
10
|
+
export declare class LinkElement extends PDFElement {
|
|
11
|
+
private href?;
|
|
12
|
+
private dest?;
|
|
13
|
+
private child;
|
|
14
|
+
private x;
|
|
15
|
+
private y;
|
|
16
|
+
private width;
|
|
17
|
+
private height;
|
|
18
|
+
constructor({ href, dest, child }: {
|
|
19
|
+
href?: string;
|
|
20
|
+
dest?: string;
|
|
21
|
+
child: PDFElement;
|
|
22
|
+
});
|
|
23
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
24
|
+
getProps(): {
|
|
25
|
+
href: string | undefined;
|
|
26
|
+
dest: string | undefined;
|
|
27
|
+
child: PDFElement;
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PDFElement } from "../pdf-element.js";
|
|
2
|
+
/**
|
|
3
|
+
* Makes its child a clickable hyperlink. `href` is an external URL; `dest` is an internal named
|
|
4
|
+
* destination (an `Anchor` elsewhere in the document) - exactly one is set. Layout-transparent: it
|
|
5
|
+
* delegates `calculateLayout` to the child and records the resulting box, which the renderer turns into
|
|
6
|
+
* a `Link` IR node (and the page renderer into a /Link annotation). The link draws nothing itself -
|
|
7
|
+
* style the child (e.g. a blue Text) if you want it to look like a link.
|
|
8
|
+
*/
|
|
9
|
+
export class LinkElement extends PDFElement {
|
|
10
|
+
constructor({ href, dest, child }) {
|
|
11
|
+
super();
|
|
12
|
+
this.x = 0;
|
|
13
|
+
this.y = 0;
|
|
14
|
+
this.width = 0;
|
|
15
|
+
this.height = 0;
|
|
16
|
+
this.href = href;
|
|
17
|
+
this.dest = dest;
|
|
18
|
+
this.child = child;
|
|
19
|
+
}
|
|
20
|
+
calculateLayout(constraints, offset, ctx) {
|
|
21
|
+
const size = this.child.calculateLayout(constraints, offset, ctx);
|
|
22
|
+
this.x = offset.x;
|
|
23
|
+
this.y = offset.y;
|
|
24
|
+
this.width = size.width;
|
|
25
|
+
this.height = size.height;
|
|
26
|
+
return size;
|
|
27
|
+
}
|
|
28
|
+
getProps() {
|
|
29
|
+
return {
|
|
30
|
+
href: this.href,
|
|
31
|
+
dest: this.dest,
|
|
32
|
+
child: this.child,
|
|
33
|
+
x: this.x,
|
|
34
|
+
y: this.y,
|
|
35
|
+
width: this.width,
|
|
36
|
+
height: this.height,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
2
|
+
import { LayoutContext, PageInfo, PDFElement } from "../pdf-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds its subtree from the page it lands on, so a header, a footer or plain body content can show
|
|
5
|
+
* `pageNumber` / `pageCount`. The closure runs on EVERY layout, because the numbers differ between the
|
|
6
|
+
* two driver passes: pagination has to size the element before the page total exists, so it builds
|
|
7
|
+
* against a provisional "1 of 1"; the render pass then rebuilds it with the real `PageInfo`.
|
|
8
|
+
*
|
|
9
|
+
* Two consequences, and neither is magic - both come from the same chicken-and-egg: the content decides
|
|
10
|
+
* the page count that the content displays.
|
|
11
|
+
*
|
|
12
|
+
* 1. In the flowing BODY the box is reserved by the provisional build, so a much wider final string
|
|
13
|
+
* (`"9 of 10"` vs `"1 of 1"`) can paint slightly past it. Keep dynamic body content roughly constant
|
|
14
|
+
* in width.
|
|
15
|
+
* 2. A header/footer is re-laid out per page, so its real height is honoured - but the BODY band was
|
|
16
|
+
* already sized against the provisional build. A later page whose header is SHORTER simply gains
|
|
17
|
+
* room (harmless). A later page whose header is TALLER shrinks the band its body was measured for,
|
|
18
|
+
* and that body can overflow. So: a conditional header may shrink on later pages, never grow.
|
|
19
|
+
*/
|
|
20
|
+
export declare class PageBuilderElement extends PDFElement {
|
|
21
|
+
private composed?;
|
|
22
|
+
private readonly build;
|
|
23
|
+
constructor({ build }: {
|
|
24
|
+
build: (info: PageInfo) => PDFElement;
|
|
25
|
+
});
|
|
26
|
+
/** The real page info during the render pass; a provisional "1 of 1" while paginating. */
|
|
27
|
+
private infoFrom;
|
|
28
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
29
|
+
getProps(): {
|
|
30
|
+
composed: PDFElement | undefined;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { PDFElement } from "../pdf-element.js";
|
|
2
|
+
import { resolvePageSize } from "../page-element.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds its subtree from the page it lands on, so a header, a footer or plain body content can show
|
|
5
|
+
* `pageNumber` / `pageCount`. The closure runs on EVERY layout, because the numbers differ between the
|
|
6
|
+
* two driver passes: pagination has to size the element before the page total exists, so it builds
|
|
7
|
+
* against a provisional "1 of 1"; the render pass then rebuilds it with the real `PageInfo`.
|
|
8
|
+
*
|
|
9
|
+
* Two consequences, and neither is magic - both come from the same chicken-and-egg: the content decides
|
|
10
|
+
* the page count that the content displays.
|
|
11
|
+
*
|
|
12
|
+
* 1. In the flowing BODY the box is reserved by the provisional build, so a much wider final string
|
|
13
|
+
* (`"9 of 10"` vs `"1 of 1"`) can paint slightly past it. Keep dynamic body content roughly constant
|
|
14
|
+
* in width.
|
|
15
|
+
* 2. A header/footer is re-laid out per page, so its real height is honoured - but the BODY band was
|
|
16
|
+
* already sized against the provisional build. A later page whose header is SHORTER simply gains
|
|
17
|
+
* room (harmless). A later page whose header is TALLER shrinks the band its body was measured for,
|
|
18
|
+
* and that body can overflow. So: a conditional header may shrink on later pages, never grow.
|
|
19
|
+
*/
|
|
20
|
+
export class PageBuilderElement extends PDFElement {
|
|
21
|
+
constructor({ build }) {
|
|
22
|
+
super();
|
|
23
|
+
this.build = build;
|
|
24
|
+
}
|
|
25
|
+
/** The real page info during the render pass; a provisional "1 of 1" while paginating. */
|
|
26
|
+
infoFrom(ctx) {
|
|
27
|
+
return (ctx.pageInfo ?? {
|
|
28
|
+
pageNumber: 1,
|
|
29
|
+
pageCount: 1,
|
|
30
|
+
pageSize: resolvePageSize(ctx.pageConfig),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
calculateLayout(constraints, offset, ctx) {
|
|
34
|
+
this.composed = this.build(this.infoFrom(ctx));
|
|
35
|
+
return this.composed.calculateLayout(constraints, offset, ctx);
|
|
36
|
+
}
|
|
37
|
+
// Named `composed` (not `child`) like DeferredElement: the pre-layout image walk keys on `child`, and
|
|
38
|
+
// this subtree does not exist yet when it runs. So an Image inside a PageBuilder skips aspect-ratio
|
|
39
|
+
// pre-resolution - give it both width and height, exactly as inside a Deferred/Table subtree.
|
|
40
|
+
getProps() {
|
|
41
|
+
return { composed: this.composed };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PDFElement, LayoutContext } from "../pdf-element.js";
|
|
2
|
+
import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
|
|
3
|
+
/**
|
|
4
|
+
* Rotates its child by whole quarter-turns and, unlike `RotatedElement` (paint-only), makes the
|
|
5
|
+
* rotation LAYOUT-AWARE: for a 90 / 270 turn it SWAPS width and height, so the box reports the rotated
|
|
6
|
+
* footprint UP and siblings reflow around it. A tall text becomes a narrow vertical strip that actually
|
|
7
|
+
* reserves its narrow width and its long height - the "vertical label beside a table" case.
|
|
8
|
+
*
|
|
9
|
+
* `turns` is the number of 90-degree CLOCKWISE quarter-turns (like Flutter's `RotatedBox.quarterTurns`):
|
|
10
|
+
* 0 = none, 1 = 90, 2 = 180, 3 = 270. The child lays out with the box's constraints swapped, then it is
|
|
11
|
+
* placed centered on the box's center so a plain center-rotation (the shared renderer) maps it exactly
|
|
12
|
+
* onto the swapped box.
|
|
13
|
+
*/
|
|
14
|
+
export declare class RotatedBoxElement extends PDFElement {
|
|
15
|
+
private turns;
|
|
16
|
+
private child;
|
|
17
|
+
private x;
|
|
18
|
+
private y;
|
|
19
|
+
private width;
|
|
20
|
+
private height;
|
|
21
|
+
constructor({ turns, child }: {
|
|
22
|
+
turns: number;
|
|
23
|
+
child: PDFElement;
|
|
24
|
+
});
|
|
25
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
26
|
+
/** Same shape as `RotatedElement` (angle + box), so both share `RotatedRenderer`. */
|
|
27
|
+
getProps(): {
|
|
28
|
+
angle: number;
|
|
29
|
+
child: PDFElement;
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PDFElement } from "../pdf-element.js";
|
|
2
|
+
import { BoxConstraints } from "../../layout/box-constraints.js";
|
|
3
|
+
/**
|
|
4
|
+
* Rotates its child by whole quarter-turns and, unlike `RotatedElement` (paint-only), makes the
|
|
5
|
+
* rotation LAYOUT-AWARE: for a 90 / 270 turn it SWAPS width and height, so the box reports the rotated
|
|
6
|
+
* footprint UP and siblings reflow around it. A tall text becomes a narrow vertical strip that actually
|
|
7
|
+
* reserves its narrow width and its long height - the "vertical label beside a table" case.
|
|
8
|
+
*
|
|
9
|
+
* `turns` is the number of 90-degree CLOCKWISE quarter-turns (like Flutter's `RotatedBox.quarterTurns`):
|
|
10
|
+
* 0 = none, 1 = 90, 2 = 180, 3 = 270. The child lays out with the box's constraints swapped, then it is
|
|
11
|
+
* placed centered on the box's center so a plain center-rotation (the shared renderer) maps it exactly
|
|
12
|
+
* onto the swapped box.
|
|
13
|
+
*/
|
|
14
|
+
export class RotatedBoxElement extends PDFElement {
|
|
15
|
+
constructor({ turns, child }) {
|
|
16
|
+
super();
|
|
17
|
+
this.x = 0;
|
|
18
|
+
this.y = 0;
|
|
19
|
+
this.width = 0;
|
|
20
|
+
this.height = 0;
|
|
21
|
+
this.turns = ((Math.trunc(turns) % 4) + 4) % 4;
|
|
22
|
+
this.child = child;
|
|
23
|
+
}
|
|
24
|
+
calculateLayout(constraints, offset, ctx) {
|
|
25
|
+
const quarter = this.turns % 2 === 1; // 90 or 270: axes swap
|
|
26
|
+
// A quarter-turned child experiences the box's constraints with the axes swapped.
|
|
27
|
+
const childConstraints = quarter
|
|
28
|
+
? new BoxConstraints(constraints.minHeight, constraints.maxHeight, constraints.minWidth, constraints.maxWidth)
|
|
29
|
+
: constraints;
|
|
30
|
+
// Measure the child to learn its natural size, then take that size swapped as our own box.
|
|
31
|
+
const child = this.child.calculateLayout(childConstraints, offset, ctx);
|
|
32
|
+
this.width = quarter ? child.height : child.width;
|
|
33
|
+
this.height = quarter ? child.width : child.height;
|
|
34
|
+
this.x = offset.x;
|
|
35
|
+
this.y = offset.y;
|
|
36
|
+
// Re-place the child CENTERED on the box center: a rotation around that center then maps the
|
|
37
|
+
// child's (w x h) box exactly onto our (swapped) box - so the shared renderer needs no special case.
|
|
38
|
+
const cx = this.x + this.width / 2;
|
|
39
|
+
const cy = this.y + this.height / 2;
|
|
40
|
+
this.child.calculateLayout(childConstraints, { x: cx - child.width / 2, y: cy - child.height / 2 }, ctx);
|
|
41
|
+
return { width: this.width, height: this.height };
|
|
42
|
+
}
|
|
43
|
+
/** Same shape as `RotatedElement` (angle + box), so both share `RotatedRenderer`. */
|
|
44
|
+
getProps() {
|
|
45
|
+
return {
|
|
46
|
+
angle: this.turns * 90,
|
|
47
|
+
child: this.child,
|
|
48
|
+
x: this.x,
|
|
49
|
+
y: this.y,
|
|
50
|
+
width: this.width,
|
|
51
|
+
height: this.height,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|