@jasy/pdf 1.0.0-beta.4 → 1.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/api/color.d.ts +1 -24
- package/dist/api/color.js +2 -278
- package/dist/api/content.d.ts +7 -2
- package/dist/api/content.js +17 -7
- package/dist/api/descriptor.js +30 -3
- package/dist/api/flow.d.ts +55 -0
- package/dist/api/flow.js +71 -0
- package/dist/api/gradient.d.ts +1 -53
- package/dist/api/gradient.js +2 -97
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/layout.js +4 -3
- package/dist/api/structure.d.ts +1 -1
- package/dist/api/table.js +3 -1
- package/dist/api/text.d.ts +8 -8
- package/dist/api/text.js +6 -0
- package/dist/canvas/painter.d.ts +2 -2
- package/dist/canvas/painter.js +2 -2
- package/dist/common/color-input.d.ts +24 -0
- package/dist/common/color-input.js +278 -0
- package/dist/common/diagnostics.d.ts +9 -1
- package/dist/common/gradient.d.ts +53 -0
- package/dist/common/gradient.js +97 -0
- package/dist/elements/container-element.d.ts +3 -0
- package/dist/elements/container-element.js +17 -5
- package/dist/elements/image-element.d.ts +37 -26
- package/dist/elements/image-element.js +88 -50
- package/dist/elements/index.d.ts +3 -0
- package/dist/elements/index.js +3 -0
- package/dist/elements/layout/column-break-element.d.ts +17 -0
- package/dist/elements/layout/column-break-element.js +26 -0
- package/dist/elements/layout/deferred-element.d.ts +9 -1
- package/dist/elements/layout/deferred-element.js +18 -3
- package/dist/elements/layout/expanded-element.d.ts +4 -0
- package/dist/elements/layout/expanded-element.js +14 -1
- package/dist/elements/layout/flow-element.d.ts +109 -0
- package/dist/elements/layout/flow-element.js +317 -0
- package/dist/elements/layout/keep-together-element.js +14 -2
- package/dist/elements/layout/padding-element.d.ts +3 -0
- package/dist/elements/layout/padding-element.js +9 -2
- package/dist/elements/layout/page-builder-element.js +2 -2
- package/dist/elements/layout/positioned-element.d.ts +9 -2
- package/dist/elements/layout/positioned-element.js +6 -2
- package/dist/elements/layout/proxy-element.d.ts +4 -0
- package/dist/elements/layout/proxy-element.js +12 -0
- package/dist/elements/layout/region-element.d.ts +53 -0
- package/dist/elements/layout/region-element.js +109 -0
- package/dist/elements/layout/repeating-header-element.d.ts +3 -0
- package/dist/elements/layout/repeating-header-element.js +16 -1
- package/dist/elements/layout/rotated-element.d.ts +2 -0
- package/dist/elements/layout/rotated-element.js +8 -0
- package/dist/elements/pdf-element.d.ts +32 -3
- package/dist/elements/pdf-element.js +50 -1
- package/dist/elements/rectangle-element.d.ts +12 -6
- package/dist/elements/rectangle-element.js +31 -12
- package/dist/elements/row-element.d.ts +6 -12
- package/dist/elements/row-element.js +34 -4
- package/dist/elements/svg-element.js +4 -10
- package/dist/elements/text-element.d.ts +17 -1
- package/dist/elements/text-element.js +63 -40
- package/dist/ir/display-list.d.ts +6 -1
- package/dist/layout/box-constraints.d.ts +12 -0
- package/dist/layout/box-constraints.js +17 -0
- package/dist/layout/collect-images.js +8 -4
- package/dist/layout/flex-item.d.ts +2 -0
- package/dist/layout/flex-item.js +14 -2
- package/dist/layout/fragmentation.d.ts +11 -5
- package/dist/layout/fragmentation.js +27 -7
- package/dist/layout/layout-cache.d.ts +8 -8
- package/dist/layout/layout-cache.js +10 -7
- package/dist/platform/browser-fs.d.ts +1 -2
- package/dist/platform/browser-fs.js +3 -6
- package/dist/platform/node-fs.d.ts +0 -1
- package/dist/platform/node-fs.js +0 -4
- package/dist/renderer/column-break-renderer.d.ts +8 -0
- package/dist/renderer/column-break-renderer.js +15 -0
- package/dist/renderer/emoji-image.js +3 -1
- package/dist/renderer/flow-renderer.d.ts +16 -0
- package/dist/renderer/flow-renderer.js +21 -0
- package/dist/renderer/image-renderer.js +31 -54
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.js +2 -0
- package/dist/renderer/page-renderer.js +3 -4
- package/dist/renderer/pdf-backend.js +9 -2
- package/dist/renderer/pdf-document-renderer.js +2 -1
- package/dist/renderer/pdf-renderer.js +9 -0
- package/dist/renderer/positioned-renderer.d.ts +14 -2
- package/dist/renderer/positioned-renderer.js +40 -4
- package/dist/renderer/rectangle-renderer.d.ts +1 -1
- package/dist/renderer/rectangle-renderer.js +11 -6
- package/dist/renderer/rotated-renderer.js +3 -2
- package/dist/renderer/text-renderer.js +25 -28
- package/dist/svg/index.d.ts +1 -1
- package/dist/svg/index.js +26 -5
- package/dist/svg/style.js +1 -1
- package/dist/text/line-breaker.d.ts +23 -0
- package/dist/text/line-breaker.js +142 -65
- package/dist/text/line-metrics.d.ts +2 -0
- package/dist/text/line-metrics.js +5 -1
- package/dist/text/word-splitting.js +11 -5
- package/dist/utils/flex-layout.d.ts +3 -1
- package/dist/utils/flex-layout.js +141 -33
- package/dist/utils/image-helper.d.ts +59 -11
- package/dist/utils/image-helper.js +207 -80
- package/dist/utils/pdf-object-manager.d.ts +6 -3
- package/dist/utils/pdf-object-manager.js +31 -7
- package/dist/utils/renderer-registry.d.ts +6 -0
- package/dist/utils/renderer-registry.js +1 -1
- package/dist/utils/ttf-parser.js +3 -2
- package/package.json +9 -2
|
@@ -14,10 +14,14 @@ import { PDFElement } from "../pdf-element.js";
|
|
|
14
14
|
* (ISSUE-4). Content that lands somewhere unasked is worse than content that does not render.
|
|
15
15
|
*/
|
|
16
16
|
export class PositionedElement extends PDFElement {
|
|
17
|
-
constructor({ child, top, right, bottom, left, h, v, x, y }) {
|
|
17
|
+
constructor({ child, top, right, bottom, left, h, v, x, y, zIndex }) {
|
|
18
18
|
super();
|
|
19
|
+
if (zIndex !== undefined && !Number.isFinite(zIndex)) {
|
|
20
|
+
throw new Error(`Invalid zIndex ${zIndex}: it must be a number.`);
|
|
21
|
+
}
|
|
19
22
|
this.child = child;
|
|
20
23
|
this.insets = { top, right, bottom, left, h, v, x, y };
|
|
24
|
+
this.zIndex = zIndex ?? 0;
|
|
21
25
|
}
|
|
22
26
|
calculateLayout(_constraints, _offset, ctx) {
|
|
23
27
|
if (!ctx.frame) {
|
|
@@ -75,7 +79,7 @@ export class PositionedElement extends PDFElement {
|
|
|
75
79
|
this.child.calculateLayout(constraints, { x, y }, ctx);
|
|
76
80
|
}
|
|
77
81
|
getProps() {
|
|
78
|
-
return { child: this.child };
|
|
82
|
+
return { child: this.child, zIndex: this.zIndex };
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
/**
|
|
@@ -29,11 +29,15 @@ export declare abstract class ProxyElement extends PDFElement implements Fragmen
|
|
|
29
29
|
protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
|
|
30
30
|
layoutChildren(): readonly PDFElement[];
|
|
31
31
|
/** Transparent to a surrounding Row or Column too: it sees the child's flex properties. */
|
|
32
|
+
proxiedContext(ctx: LayoutContext): LayoutContext;
|
|
32
33
|
proxied(): PDFElement;
|
|
33
34
|
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
35
|
+
maxExtent(horizontal: boolean): number;
|
|
34
36
|
needsBoundedMain(horizontal: boolean): boolean;
|
|
35
37
|
minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
|
|
38
|
+
needsRegion(): boolean;
|
|
36
39
|
isPageBreak(): boolean;
|
|
40
|
+
isColumnBreak(): boolean;
|
|
37
41
|
hasForcedBreak(): boolean;
|
|
38
42
|
breaksBefore(): boolean;
|
|
39
43
|
breaksAfter(): boolean;
|
|
@@ -39,21 +39,33 @@ export class ProxyElement extends PDFElement {
|
|
|
39
39
|
return [this.child];
|
|
40
40
|
}
|
|
41
41
|
/** Transparent to a surrounding Row or Column too: it sees the child's flex properties. */
|
|
42
|
+
proxiedContext(ctx) {
|
|
43
|
+
return this.childContext(ctx);
|
|
44
|
+
}
|
|
42
45
|
proxied() {
|
|
43
46
|
return this.child;
|
|
44
47
|
}
|
|
45
48
|
relativeSizeFactor(horizontal) {
|
|
46
49
|
return this.child.relativeSizeFactor(horizontal);
|
|
47
50
|
}
|
|
51
|
+
maxExtent(horizontal) {
|
|
52
|
+
return this.child.maxExtent(horizontal);
|
|
53
|
+
}
|
|
48
54
|
needsBoundedMain(horizontal) {
|
|
49
55
|
return this.child.needsBoundedMain(horizontal);
|
|
50
56
|
}
|
|
51
57
|
minIntrinsicMain(horizontal, ctx) {
|
|
52
58
|
return this.child.minIntrinsicMain(horizontal, this.childContext(ctx));
|
|
53
59
|
}
|
|
60
|
+
needsRegion() {
|
|
61
|
+
return this.child.needsRegion();
|
|
62
|
+
}
|
|
54
63
|
isPageBreak() {
|
|
55
64
|
return this.child.isPageBreak();
|
|
56
65
|
}
|
|
66
|
+
isColumnBreak() {
|
|
67
|
+
return this.child.isColumnBreak();
|
|
68
|
+
}
|
|
57
69
|
hasForcedBreak() {
|
|
58
70
|
return this.child.hasForcedBreak();
|
|
59
71
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BoxConstraints, Offset, Size, SizingParams } from "../../layout/box-constraints.js";
|
|
2
|
+
import { LayoutContext, PDFElement, SizedPDFElement } from "../pdf-element.js";
|
|
3
|
+
interface RegionElementParams extends SizingParams {
|
|
4
|
+
widthFactor?: number;
|
|
5
|
+
heightFactor?: number;
|
|
6
|
+
}
|
|
7
|
+
/** A `Region` was laid out somewhere no `Flow` claimed it: outside a template, or inside the content. */
|
|
8
|
+
export declare class RegionOutsideFlowError extends Error {
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
/** One Region instance handed to two pages of a Flow (or two Flows): both would draw the same content. */
|
|
12
|
+
export declare class RegionReusedError extends Error {
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A place in a `Flow`'s template that the content flows through. It has no size of its own: the layout
|
|
17
|
+
* around it sizes it (`Expanded`, `width`, `height`, `%`), and with nothing given it fills what it is
|
|
18
|
+
* offered, as a Canvas does. The Flow claims it, then hands it the piece of content that fits.
|
|
19
|
+
*/
|
|
20
|
+
export declare class RegionElement extends SizedPDFElement {
|
|
21
|
+
private requested;
|
|
22
|
+
private sizing;
|
|
23
|
+
private widthFactor?;
|
|
24
|
+
private heightFactor?;
|
|
25
|
+
private owner?;
|
|
26
|
+
private content;
|
|
27
|
+
constructor(params?: RegionElementParams);
|
|
28
|
+
/** Marks it as part of one page of a Flow; a second page claiming it throws. */
|
|
29
|
+
private claim;
|
|
30
|
+
/** The content it holds on this page (`null`: the content ran out before it). */
|
|
31
|
+
hold(content: PDFElement | null): void;
|
|
32
|
+
get held(): PDFElement | null;
|
|
33
|
+
/** A height the author gave (points or `%`): kept on the Flow's last page instead of shrinking. */
|
|
34
|
+
hasOwnHeight(): boolean;
|
|
35
|
+
/** Where it has no size of its own it fills what it is offered, so the stack around it must offer
|
|
36
|
+
* something - as for a Spacer. */
|
|
37
|
+
needsBoundedMain(horizontal: boolean): boolean;
|
|
38
|
+
/** A `%` size is a share of the stack it sits in, which has to know to resolve it - as for a Box. */
|
|
39
|
+
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
40
|
+
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
41
|
+
protected computeSize(constraints: BoxConstraints): Size;
|
|
42
|
+
/** Its size under these constraints - the one computation both `measure` and layout use. */
|
|
43
|
+
private sizeFor;
|
|
44
|
+
layoutChildren(): readonly PDFElement[];
|
|
45
|
+
getProps(): {
|
|
46
|
+
child?: PDFElement | undefined;
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
width: number | undefined;
|
|
50
|
+
height: number | undefined;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { BoxConstraints, extentSpecs, resolveSize, } from "../../layout/box-constraints.js";
|
|
2
|
+
import { SizedPDFElement } from "../pdf-element.js";
|
|
3
|
+
/** A `Region` was laid out somewhere no `Flow` claimed it: outside a template, or inside the content. */
|
|
4
|
+
export class RegionOutsideFlowError extends Error {
|
|
5
|
+
constructor() {
|
|
6
|
+
super("@jasy/pdf: a Region only means something in a Flow's template - it marks where the content " +
|
|
7
|
+
"flows. Put it in the template (in @jasy/vue: the #page slot; in @jasy/react: the page prop), not " +
|
|
8
|
+
"in the content or anywhere else.");
|
|
9
|
+
this.name = "RegionOutsideFlowError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** One Region instance handed to two pages of a Flow (or two Flows): both would draw the same content. */
|
|
13
|
+
export class RegionReusedError extends Error {
|
|
14
|
+
constructor() {
|
|
15
|
+
super("@jasy/pdf: a Region is used by two pages of a Flow (or by two Flows). A template is called for " +
|
|
16
|
+
"every page - build the Regions, and the layout around them, inside the function.");
|
|
17
|
+
this.name = "RegionReusedError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A place in a `Flow`'s template that the content flows through. It has no size of its own: the layout
|
|
22
|
+
* around it sizes it (`Expanded`, `width`, `height`, `%`), and with nothing given it fills what it is
|
|
23
|
+
* offered, as a Canvas does. The Flow claims it, then hands it the piece of content that fits.
|
|
24
|
+
*/
|
|
25
|
+
export class RegionElement extends SizedPDFElement {
|
|
26
|
+
constructor(params = {}) {
|
|
27
|
+
super({ x: 0, y: 0, width: params.width, height: params.height });
|
|
28
|
+
this.content = null;
|
|
29
|
+
const { widthFactor, heightFactor, ...sizing } = params;
|
|
30
|
+
this.widthFactor = widthFactor;
|
|
31
|
+
this.heightFactor = heightFactor;
|
|
32
|
+
this.sizing = sizing;
|
|
33
|
+
this.requested = { width: params.width, height: params.height };
|
|
34
|
+
}
|
|
35
|
+
/** Marks it as part of one page of a Flow; a second page claiming it throws. */
|
|
36
|
+
claim(owner) {
|
|
37
|
+
if (this.owner && this.owner !== owner)
|
|
38
|
+
throw new RegionReusedError();
|
|
39
|
+
this.owner = owner;
|
|
40
|
+
}
|
|
41
|
+
/** The content it holds on this page (`null`: the content ran out before it). */
|
|
42
|
+
hold(content) {
|
|
43
|
+
this.content = content;
|
|
44
|
+
}
|
|
45
|
+
get held() {
|
|
46
|
+
return this.content;
|
|
47
|
+
}
|
|
48
|
+
/** A height the author gave (points or `%`): kept on the Flow's last page instead of shrinking. */
|
|
49
|
+
hasOwnHeight() {
|
|
50
|
+
return this.requested.height !== undefined || this.heightFactor !== undefined;
|
|
51
|
+
}
|
|
52
|
+
/** Where it has no size of its own it fills what it is offered, so the stack around it must offer
|
|
53
|
+
* something - as for a Spacer. */
|
|
54
|
+
needsBoundedMain(horizontal) {
|
|
55
|
+
return horizontal
|
|
56
|
+
? this.requested.width === undefined && this.widthFactor === undefined
|
|
57
|
+
: !this.hasOwnHeight();
|
|
58
|
+
}
|
|
59
|
+
/** A `%` size is a share of the stack it sits in, which has to know to resolve it - as for a Box. */
|
|
60
|
+
relativeSizeFactor(horizontal) {
|
|
61
|
+
return horizontal ? this.widthFactor : this.heightFactor;
|
|
62
|
+
}
|
|
63
|
+
calculateLayout(constraints, offset, ctx) {
|
|
64
|
+
// Laid out in a Flow's template, it belongs to that page; anywhere else it has no meaning.
|
|
65
|
+
if (!ctx.flowPage)
|
|
66
|
+
throw new RegionOutsideFlowError();
|
|
67
|
+
this.claim(ctx.flowPage);
|
|
68
|
+
const size = this.sizeFor(constraints);
|
|
69
|
+
this.x = offset.x;
|
|
70
|
+
this.y = offset.y;
|
|
71
|
+
this.width = size.width;
|
|
72
|
+
this.height = size.height;
|
|
73
|
+
// Its content is not the template: a Region in it is outside any Flow (or in its own inner one).
|
|
74
|
+
this.content?.calculateLayout(BoxConstraints.loose(size.width, size.height), { x: 0, y: 0 }, { ...ctx, flowPage: undefined });
|
|
75
|
+
return size;
|
|
76
|
+
}
|
|
77
|
+
computeSize(constraints) {
|
|
78
|
+
return this.sizeFor(constraints);
|
|
79
|
+
}
|
|
80
|
+
/** Its size under these constraints - the one computation both `measure` and layout use. */
|
|
81
|
+
sizeFor(constraints) {
|
|
82
|
+
const specs = extentSpecs({
|
|
83
|
+
...this.sizing,
|
|
84
|
+
width: this.requested.width,
|
|
85
|
+
height: this.requested.height,
|
|
86
|
+
widthFactor: this.widthFactor,
|
|
87
|
+
heightFactor: this.heightFactor,
|
|
88
|
+
});
|
|
89
|
+
const resolved = resolveSize(specs.width, specs.height, this.sizing.aspectRatio, constraints);
|
|
90
|
+
const bounds = resolved.constraints;
|
|
91
|
+
return {
|
|
92
|
+
width: resolved.width ?? (bounds.hasBoundedWidth ? bounds.maxWidth : 0),
|
|
93
|
+
height: resolved.height ?? (bounds.hasBoundedHeight ? bounds.maxHeight : 0),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
layoutChildren() {
|
|
97
|
+
return this.content ? [this.content] : [];
|
|
98
|
+
}
|
|
99
|
+
getProps() {
|
|
100
|
+
return {
|
|
101
|
+
x: this.x,
|
|
102
|
+
y: this.y,
|
|
103
|
+
width: this.width,
|
|
104
|
+
height: this.height,
|
|
105
|
+
// Only when it holds something: the pre-layout walks read `child` as an element.
|
|
106
|
+
...(this.content ? { child: this.content } : {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -16,6 +16,9 @@ export declare class RepeatingHeaderElement extends PDFElement implements Fragme
|
|
|
16
16
|
private width;
|
|
17
17
|
private height;
|
|
18
18
|
constructor(header: PDFElement, body: PDFElement, gap?: number);
|
|
19
|
+
/** The header for the next piece: a copy, since two pieces may share a page (two Regions of a Flow)
|
|
20
|
+
* and one element has one position - that tags as the SAME header. */
|
|
21
|
+
private nextHeader;
|
|
19
22
|
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
20
23
|
protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
|
|
21
24
|
/** The one sizing path: `layout` places header and body (relative to this box), `measure` sizes. */
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { BoxConstraints } from "../../layout/box-constraints.js";
|
|
2
2
|
import { insetContext, isFragmentable, } from "../../layout/fragmentation.js";
|
|
3
3
|
import { PDFElement } from "../pdf-element.js";
|
|
4
|
+
/** Gives every element of `fresh` the tagging identity of its twin in `from` (the same shape). */
|
|
5
|
+
function adoptStructIds(fresh, from) {
|
|
6
|
+
fresh.adoptStructId(from);
|
|
7
|
+
const a = fresh.layoutChildren();
|
|
8
|
+
const b = from.layoutChildren();
|
|
9
|
+
for (let i = 0; i < a.length && i < b.length; i++)
|
|
10
|
+
adoptStructIds(a[i], b[i]);
|
|
11
|
+
}
|
|
4
12
|
/**
|
|
5
13
|
* Stacks a `header` above a `body` and, when it paginates, **repeats the header on every
|
|
6
14
|
* fragment**. Used by `Table` so column headings reappear at the top of each page. Layout
|
|
@@ -18,6 +26,13 @@ export class RepeatingHeaderElement extends PDFElement {
|
|
|
18
26
|
this.width = 0;
|
|
19
27
|
this.height = 0;
|
|
20
28
|
}
|
|
29
|
+
/** The header for the next piece: a copy, since two pieces may share a page (two Regions of a Flow)
|
|
30
|
+
* and one element has one position - that tags as the SAME header. */
|
|
31
|
+
nextHeader() {
|
|
32
|
+
const copy = this.header.clone();
|
|
33
|
+
adoptStructIds(copy, this.header);
|
|
34
|
+
return copy;
|
|
35
|
+
}
|
|
21
36
|
calculateLayout(constraints, offset, ctx) {
|
|
22
37
|
const size = this.sizeWith(constraints, ctx, "layout");
|
|
23
38
|
this.x = offset.x;
|
|
@@ -58,7 +73,7 @@ export class RepeatingHeaderElement extends PDFElement {
|
|
|
58
73
|
? new RepeatingHeaderElement(this.header, split.fitted, this.gap).asPieceOf(this)
|
|
59
74
|
: null,
|
|
60
75
|
remainder: split.remainder
|
|
61
|
-
? new RepeatingHeaderElement(this.
|
|
76
|
+
? new RepeatingHeaderElement(this.nextHeader(), split.remainder, this.gap).asPieceOf(this)
|
|
62
77
|
: null,
|
|
63
78
|
forceBreak: split.forceBreak,
|
|
64
79
|
};
|
|
@@ -23,7 +23,9 @@ export declare class RotatedElement extends PDFElement {
|
|
|
23
23
|
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
24
24
|
protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
|
|
25
25
|
layoutChildren(): readonly PDFElement[];
|
|
26
|
+
proxied(): PDFElement;
|
|
26
27
|
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
28
|
+
maxExtent(horizontal: boolean): number;
|
|
27
29
|
needsBoundedMain(horizontal: boolean): boolean;
|
|
28
30
|
minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
|
|
29
31
|
breaksBefore(): boolean;
|
|
@@ -34,11 +34,19 @@ export class RotatedElement extends PDFElement {
|
|
|
34
34
|
layoutChildren() {
|
|
35
35
|
return [this.child];
|
|
36
36
|
}
|
|
37
|
+
// The spin is paint-only, so to a surrounding Row or Column the child is the item (CSS: a `transform`
|
|
38
|
+
// leaves an item an item).
|
|
39
|
+
proxied() {
|
|
40
|
+
return this.child;
|
|
41
|
+
}
|
|
37
42
|
// It takes the child's space, so the child's sizing hints and its break-before/after are its own.
|
|
38
43
|
// It is never split - half a rotation on each page means nothing - so a break INSIDE stays inside.
|
|
39
44
|
relativeSizeFactor(horizontal) {
|
|
40
45
|
return this.child.relativeSizeFactor(horizontal);
|
|
41
46
|
}
|
|
47
|
+
maxExtent(horizontal) {
|
|
48
|
+
return this.child.maxExtent(horizontal);
|
|
49
|
+
}
|
|
42
50
|
needsBoundedMain(horizontal) {
|
|
43
51
|
return this.child.needsBoundedMain(horizontal);
|
|
44
52
|
}
|
|
@@ -74,10 +74,17 @@ export interface LayoutContext {
|
|
|
74
74
|
* means "move to the next page", never an overflow - only content at the top of a fresh page is
|
|
75
75
|
* forced on and reported. Set by `packChildren`; absent = top of the page. */
|
|
76
76
|
belowContent?: boolean;
|
|
77
|
-
/**
|
|
78
|
-
|
|
77
|
+
/** Packing into a Region of a `Flow`, where a `ColumnBreak` moves on to the next Region. */
|
|
78
|
+
inFlowRegion?: boolean;
|
|
79
|
+
/** Laying out a page of a `Flow`'s template: the page a `Region` laid out here belongs to. */
|
|
80
|
+
flowPage?: object;
|
|
81
|
+
/** What a `Box`'s percentage padding resolves against (CSS: the containing block). A Row sets it for
|
|
82
|
+
* its children - it offers them no width, so the Box could not know it; otherwise the Box sets it
|
|
83
|
+
* from what it was offered. Consumed by the Box's own padding, so it never travels further down. */
|
|
79
84
|
boxPaddingBase?: number;
|
|
80
85
|
}
|
|
86
|
+
/** `ctx` with `boxPaddingBase` set to `base` - the same object when nothing changes. */
|
|
87
|
+
export declare function withPaddingBase(ctx: LayoutContext, base: number | undefined): LayoutContext;
|
|
81
88
|
export declare abstract class PDFElement {
|
|
82
89
|
private _structId;
|
|
83
90
|
get structId(): number;
|
|
@@ -130,6 +137,12 @@ export declare abstract class PDFElement {
|
|
|
130
137
|
* `calculateLayout`, or a measured size and a laid-out one can differ.
|
|
131
138
|
*/
|
|
132
139
|
protected abstract computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
|
|
140
|
+
/**
|
|
141
|
+
* A copy of this element and of every element it holds, as it was built - to place one element
|
|
142
|
+
* twice, which a single instance cannot (it has one position). Everything else stays shared: an
|
|
143
|
+
* image's pixels, a callback. The copy tags as an element of its own.
|
|
144
|
+
*/
|
|
145
|
+
clone(): this;
|
|
133
146
|
/** The elements this one lays out, for walks that must see the whole tree. Leaves have none. */
|
|
134
147
|
layoutChildren(): readonly PDFElement[];
|
|
135
148
|
/**
|
|
@@ -137,6 +150,11 @@ export declare abstract class PDFElement {
|
|
|
137
150
|
* Row or Column looks through it for the flex item (`layout/flex-item.ts`).
|
|
138
151
|
*/
|
|
139
152
|
proxied(): PDFElement | undefined;
|
|
153
|
+
/** The context the element `proxied()` names is laid out in: this one's, unless it hands something
|
|
154
|
+
* down (a `DefaultTextStyle`). */
|
|
155
|
+
proxiedContext(ctx: LayoutContext): LayoutContext;
|
|
156
|
+
/** A `Box` with percentage padding: a surrounding Row tells it its own width (`boxPaddingBase`). */
|
|
157
|
+
hasPercentPadding(): boolean;
|
|
140
158
|
/** Lays out its own `Positioned` descendants (a page, a `relative` Box); a walk for them stops here. */
|
|
141
159
|
isPositioningFrame(): boolean;
|
|
142
160
|
/**
|
|
@@ -146,6 +164,9 @@ export declare abstract class PDFElement {
|
|
|
146
164
|
* fraction would have nothing to resolve against. Default: the element is not relatively sized.
|
|
147
165
|
*/
|
|
148
166
|
relativeSizeFactor(_horizontal: boolean): number | undefined;
|
|
167
|
+
/** The most this element may be on an axis in points (its `maxWidth`/`maxHeight`): a flex line freezes
|
|
168
|
+
* a flex child there. A percentage max resolves against what the child is given, so it is not one. */
|
|
169
|
+
maxExtent(_horizontal: boolean): number;
|
|
149
170
|
/**
|
|
150
171
|
* Whether this element needs a BOUNDED extent on the given axis to lay itself out (`true` for a
|
|
151
172
|
* horizontal axis = width). A flex parent measures its fixed children with an unbounded main axis, so
|
|
@@ -165,15 +186,23 @@ export declare abstract class PDFElement {
|
|
|
165
186
|
* override it; containers recurse, since a Row is only as unsqueezable as its children.
|
|
166
187
|
*/
|
|
167
188
|
minIntrinsicMain(_horizontal: boolean, _ctx: LayoutContext): number;
|
|
189
|
+
/**
|
|
190
|
+
* Whether this element takes its height from the REGION it is fragmented into (a `Flow`), anywhere
|
|
191
|
+
* in a subtree that has no height of its own. Such an element has no natural height to measure, so
|
|
192
|
+
* the packer fragments it into the space left instead of placing it whole. Default: no.
|
|
193
|
+
*/
|
|
194
|
+
needsRegion(): boolean;
|
|
168
195
|
/**
|
|
169
196
|
* Whether this element IS a forced page break (a `PageBreak`). The pagination packer cuts the flow
|
|
170
197
|
* at such a marker: everything before it stays, everything after starts a new page. Default: no.
|
|
171
198
|
*/
|
|
172
199
|
isPageBreak(): boolean;
|
|
200
|
+
/** Whether this element IS a `ColumnBreak`: the packer of a Flow's Region cuts there. Default: no. */
|
|
201
|
+
isColumnBreak(): boolean;
|
|
173
202
|
/**
|
|
174
203
|
* Whether this element is, or CONTAINS anywhere in its subtree, a forced page break. A container
|
|
175
204
|
* that merely fits by height must still be fragmented when this is true, so a break nested inside
|
|
176
|
-
* it is
|
|
205
|
+
* it is honored rather than swallowed. Containers override this to recurse into their children.
|
|
177
206
|
* Default: no.
|
|
178
207
|
*/
|
|
179
208
|
hasForcedBreak(): boolean;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { SizeCache } from "../layout/layout-cache.js";
|
|
2
|
+
/** `ctx` with `boxPaddingBase` set to `base` - the same object when nothing changes. */
|
|
3
|
+
export function withPaddingBase(ctx, base) {
|
|
4
|
+
return ctx.boxPaddingBase === base ? ctx : { ...ctx, boxPaddingBase: base };
|
|
5
|
+
}
|
|
2
6
|
let _nextStructId = 0;
|
|
3
7
|
export class PDFElement {
|
|
4
8
|
constructor() {
|
|
@@ -67,6 +71,25 @@ export class PDFElement {
|
|
|
67
71
|
dependsOnPage() {
|
|
68
72
|
return (this.pageDependent ?? (this.pageDependent = this.layoutChildren().some((child) => child.dependsOnPage())));
|
|
69
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* A copy of this element and of every element it holds, as it was built - to place one element
|
|
76
|
+
* twice, which a single instance cannot (it has one position). Everything else stays shared: an
|
|
77
|
+
* image's pixels, a callback. The copy tags as an element of its own.
|
|
78
|
+
*/
|
|
79
|
+
clone() {
|
|
80
|
+
const copyOf = (value) => value instanceof PDFElement
|
|
81
|
+
? value.clone()
|
|
82
|
+
: Array.isArray(value)
|
|
83
|
+
? value.map(copyOf)
|
|
84
|
+
: value;
|
|
85
|
+
const copy = Object.create(Object.getPrototypeOf(this));
|
|
86
|
+
for (const [key, value] of Object.entries(this)) {
|
|
87
|
+
copy[key] = copyOf(value);
|
|
88
|
+
}
|
|
89
|
+
copy.sizeCache = undefined;
|
|
90
|
+
copy._structId = _nextStructId++;
|
|
91
|
+
return copy;
|
|
92
|
+
}
|
|
70
93
|
/** The elements this one lays out, for walks that must see the whole tree. Leaves have none. */
|
|
71
94
|
layoutChildren() {
|
|
72
95
|
return [];
|
|
@@ -78,6 +101,15 @@ export class PDFElement {
|
|
|
78
101
|
proxied() {
|
|
79
102
|
return undefined;
|
|
80
103
|
}
|
|
104
|
+
/** The context the element `proxied()` names is laid out in: this one's, unless it hands something
|
|
105
|
+
* down (a `DefaultTextStyle`). */
|
|
106
|
+
proxiedContext(ctx) {
|
|
107
|
+
return ctx;
|
|
108
|
+
}
|
|
109
|
+
/** A `Box` with percentage padding: a surrounding Row tells it its own width (`boxPaddingBase`). */
|
|
110
|
+
hasPercentPadding() {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
81
113
|
/** Lays out its own `Positioned` descendants (a page, a `relative` Box); a walk for them stops here. */
|
|
82
114
|
isPositioningFrame() {
|
|
83
115
|
return false;
|
|
@@ -91,6 +123,11 @@ export class PDFElement {
|
|
|
91
123
|
relativeSizeFactor(_horizontal) {
|
|
92
124
|
return undefined;
|
|
93
125
|
}
|
|
126
|
+
/** The most this element may be on an axis in points (its `maxWidth`/`maxHeight`): a flex line freezes
|
|
127
|
+
* a flex child there. A percentage max resolves against what the child is given, so it is not one. */
|
|
128
|
+
maxExtent(_horizontal) {
|
|
129
|
+
return Infinity;
|
|
130
|
+
}
|
|
94
131
|
/**
|
|
95
132
|
* Whether this element needs a BOUNDED extent on the given axis to lay itself out (`true` for a
|
|
96
133
|
* horizontal axis = width). A flex parent measures its fixed children with an unbounded main axis, so
|
|
@@ -114,6 +151,14 @@ export class PDFElement {
|
|
|
114
151
|
minIntrinsicMain(_horizontal, _ctx) {
|
|
115
152
|
return 0;
|
|
116
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Whether this element takes its height from the REGION it is fragmented into (a `Flow`), anywhere
|
|
156
|
+
* in a subtree that has no height of its own. Such an element has no natural height to measure, so
|
|
157
|
+
* the packer fragments it into the space left instead of placing it whole. Default: no.
|
|
158
|
+
*/
|
|
159
|
+
needsRegion() {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
117
162
|
/**
|
|
118
163
|
* Whether this element IS a forced page break (a `PageBreak`). The pagination packer cuts the flow
|
|
119
164
|
* at such a marker: everything before it stays, everything after starts a new page. Default: no.
|
|
@@ -121,10 +166,14 @@ export class PDFElement {
|
|
|
121
166
|
isPageBreak() {
|
|
122
167
|
return false;
|
|
123
168
|
}
|
|
169
|
+
/** Whether this element IS a `ColumnBreak`: the packer of a Flow's Region cuts there. Default: no. */
|
|
170
|
+
isColumnBreak() {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
124
173
|
/**
|
|
125
174
|
* Whether this element is, or CONTAINS anywhere in its subtree, a forced page break. A container
|
|
126
175
|
* that merely fits by height must still be fragmented when this is true, so a break nested inside
|
|
127
|
-
* it is
|
|
176
|
+
* it is honored rather than swallowed. Containers override this to recurse into their children.
|
|
128
177
|
* Default: no.
|
|
129
178
|
*/
|
|
130
179
|
hasForcedBreak() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Radii } from "../ir/display-list.js";
|
|
2
|
-
import type { GradientInput } from "../
|
|
2
|
+
import type { GradientInput } from "../common/gradient.js";
|
|
3
3
|
import { Color } from "../common/color.js";
|
|
4
4
|
import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
|
|
5
5
|
import { Fragmentable, FragmentResult } from "../layout/fragmentation.js";
|
|
@@ -14,7 +14,7 @@ export interface SideBorders {
|
|
|
14
14
|
}
|
|
15
15
|
interface RectangleElementParams extends SizedElement, WithChildren {
|
|
16
16
|
color?: Color;
|
|
17
|
-
/** Solid
|
|
17
|
+
/** Solid color, or a box-relative gradient the renderer resolves against this box. */
|
|
18
18
|
backgroundColor?: Color | GradientInput;
|
|
19
19
|
borderWidth?: number;
|
|
20
20
|
/** Corner radius in points - one number for all four corners, or per corner. 0 = sharp (default). */
|
|
@@ -74,15 +74,18 @@ export declare class RectangleElement extends SizedPDFElement implements Fragmen
|
|
|
74
74
|
hasForcedBreak(): boolean;
|
|
75
75
|
private cloneWithChildren;
|
|
76
76
|
relativeSizeFactor(horizontal: boolean): number | undefined;
|
|
77
|
+
maxExtent(horizontal: boolean): number;
|
|
77
78
|
/** A box has no flex children of its own, but it must not swallow the need of the stack inside it: a
|
|
78
79
|
* `Spacer` in a box can only resolve if the box itself was given a bounded extent to pass down. With an
|
|
79
80
|
* explicit size on an axis the box already knows its extent there and asks for nothing. */
|
|
80
81
|
needsBoundedMain(horizontal: boolean): boolean;
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*
|
|
82
|
+
/** As a Column: a height of its own is the region; without one, a region-sized child needs the page's. */
|
|
83
|
+
needsRegion(): boolean;
|
|
84
|
+
/** A Box's own padding resolves a percentage against its containing block, not against the inner
|
|
85
|
+
* width it hands its children - so it is told: the base a surrounding Row set, else what the Box was
|
|
86
|
+
* offered. Neither (an unbounded offer outside a Row) leaves the Box's own width. */
|
|
85
87
|
private paddingContext;
|
|
88
|
+
hasPercentPadding(): boolean;
|
|
86
89
|
/** A Box stacks its children vertically inside its border, which is on both sides of the axis. */
|
|
87
90
|
minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
|
|
88
91
|
calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
|
|
@@ -93,6 +96,9 @@ export declare class RectangleElement extends SizedPDFElement implements Fragmen
|
|
|
93
96
|
*/
|
|
94
97
|
private sizeWith;
|
|
95
98
|
isPositioningFrame(): boolean;
|
|
99
|
+
/** Sorts the `zIndex` of the `Positioned` below it: a frame does, and so does a clip - what it crops
|
|
100
|
+
* stays cropped, whatever its `zIndex`. */
|
|
101
|
+
isStackingContext(): boolean;
|
|
96
102
|
layoutChildren(): readonly PDFElement[];
|
|
97
103
|
getProps(): {
|
|
98
104
|
x: number;
|
|
@@ -4,8 +4,8 @@ import { BoxConstraints, extentSpecs, resolveSize, } from "../layout/box-constra
|
|
|
4
4
|
import { stackMinIntrinsic, withDeclaredExtent } from "../layout/min-intrinsic.js";
|
|
5
5
|
import { positionedIn } from "./layout/positioned-element.js";
|
|
6
6
|
import { PaddingElement } from "./layout/padding-element.js";
|
|
7
|
-
import { childrenForceBreak, insetContext, packChildren, } from "../layout/fragmentation.js";
|
|
8
|
-
import { SizedPDFElement, } from "./pdf-element.js";
|
|
7
|
+
import { childrenForceBreak, insetContext, packChildren, packedUnchanged, } from "../layout/fragmentation.js";
|
|
8
|
+
import { SizedPDFElement, withPaddingBase, } from "./pdf-element.js";
|
|
9
9
|
export class RectangleElement extends SizedPDFElement {
|
|
10
10
|
constructor({ children = [], color = new Color(0, 0, 0), backgroundColor, borderWidth, width, height, radius, sideBorders, relative, overflow, breakBefore, breakAfter, ...sizing }) {
|
|
11
11
|
super({ x: 0, y: 0, width, height });
|
|
@@ -51,7 +51,7 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
51
51
|
// Fits with no forced cut: return the whole box unchanged. A trailing forced break is the
|
|
52
52
|
// exception - fall through so the box is rebuilt from the packed children (without the consumed
|
|
53
53
|
// break marker), rather than handing back the original that still holds it.
|
|
54
|
-
if (remainder.length === 0 && !forceBreak) {
|
|
54
|
+
if (remainder.length === 0 && !forceBreak && packedUnchanged(this.children, fitted)) {
|
|
55
55
|
// The content fits, but a fixed height may not - such a box cannot shrink, so it moves on whole.
|
|
56
56
|
const own = this.measure(BoxConstraints.loose(width, Infinity), ctx).height;
|
|
57
57
|
return own <= maxHeight
|
|
@@ -67,10 +67,12 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
67
67
|
fitted: this.cloneWithChildren(fitted, contentHeight(fitted) + 2 * this.borderWidth),
|
|
68
68
|
// A trailing forced break leaves nothing to carry over: no remainder box, no extra page.
|
|
69
69
|
// Only the CONTINUATION carries `breakAfter` (a box that splits still breaks after its LAST
|
|
70
|
-
// fragment, not its first); `breakBefore` is dropped, already
|
|
70
|
+
// fragment, not its first); `breakBefore` is dropped, already honored before the split.
|
|
71
|
+
// It gets no height, as a Column's piece does: it is sized where it lands, which may be a
|
|
72
|
+
// narrower or wider region (a Flow's), or one that gives a region-sized rest its height.
|
|
71
73
|
remainder: remainder.length === 0
|
|
72
74
|
? null
|
|
73
|
-
: this.cloneWithChildren(remainder,
|
|
75
|
+
: this.cloneWithChildren(remainder, undefined, this.breakAfter),
|
|
74
76
|
forceBreak,
|
|
75
77
|
};
|
|
76
78
|
}
|
|
@@ -99,6 +101,9 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
99
101
|
relativeSizeFactor(horizontal) {
|
|
100
102
|
return horizontal ? this.sizeMemory.widthFactor : this.sizeMemory.heightFactor;
|
|
101
103
|
}
|
|
104
|
+
maxExtent(horizontal) {
|
|
105
|
+
return (horizontal ? this.sizeMemory.maxWidth : this.sizeMemory.maxHeight) ?? Infinity;
|
|
106
|
+
}
|
|
102
107
|
/** A box has no flex children of its own, but it must not swallow the need of the stack inside it: a
|
|
103
108
|
* `Spacer` in a box can only resolve if the box itself was given a bounded extent to pass down. With an
|
|
104
109
|
* explicit size on an axis the box already knows its extent there and asks for nothing. */
|
|
@@ -110,15 +115,24 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
110
115
|
return false;
|
|
111
116
|
return this.children.some((c) => c.needsBoundedMain(horizontal));
|
|
112
117
|
}
|
|
113
|
-
/**
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
/** As a Column: a height of its own is the region; without one, a region-sized child needs the page's. */
|
|
119
|
+
needsRegion() {
|
|
120
|
+
if (this.sizeMemory.height !== undefined || this.sizeMemory.heightFactor !== undefined) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return this.children.some((c) => c.needsRegion());
|
|
124
|
+
}
|
|
125
|
+
/** A Box's own padding resolves a percentage against its containing block, not against the inner
|
|
126
|
+
* width it hands its children - so it is told: the base a surrounding Row set, else what the Box was
|
|
127
|
+
* offered. Neither (an unbounded offer outside a Row) leaves the Box's own width. */
|
|
117
128
|
paddingContext(ctx, offered) {
|
|
129
|
+
if (!this.hasPercentPadding())
|
|
130
|
+
return withPaddingBase(ctx, undefined);
|
|
131
|
+
return withPaddingBase(ctx, ctx.boxPaddingBase ?? (Number.isFinite(offered) ? offered : undefined));
|
|
132
|
+
}
|
|
133
|
+
hasPercentPadding() {
|
|
118
134
|
const own = this.children[0];
|
|
119
|
-
return own instanceof PaddingElement && own.ofBox &&
|
|
120
|
-
? { ...ctx, boxPaddingBase: offered }
|
|
121
|
-
: ctx;
|
|
135
|
+
return own instanceof PaddingElement && own.ofBox && own.hasPercentSide;
|
|
122
136
|
}
|
|
123
137
|
/** A Box stacks its children vertically inside its border, which is on both sides of the axis. */
|
|
124
138
|
minIntrinsicMain(horizontal, ctx) {
|
|
@@ -242,6 +256,11 @@ export class RectangleElement extends SizedPDFElement {
|
|
|
242
256
|
isPositioningFrame() {
|
|
243
257
|
return this.relative;
|
|
244
258
|
}
|
|
259
|
+
/** Sorts the `zIndex` of the `Positioned` below it: a frame does, and so does a clip - what it crops
|
|
260
|
+
* stays cropped, whatever its `zIndex`. */
|
|
261
|
+
isStackingContext() {
|
|
262
|
+
return this.relative || this.overflow === "hidden";
|
|
263
|
+
}
|
|
245
264
|
layoutChildren() {
|
|
246
265
|
return this.children ?? [];
|
|
247
266
|
}
|