@jasy/pdf 1.0.0-beta.4 → 1.0.0-beta.5

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.
Files changed (109) hide show
  1. package/dist/api/color.d.ts +1 -24
  2. package/dist/api/color.js +2 -278
  3. package/dist/api/content.d.ts +7 -2
  4. package/dist/api/content.js +17 -7
  5. package/dist/api/descriptor.js +30 -3
  6. package/dist/api/flow.d.ts +55 -0
  7. package/dist/api/flow.js +71 -0
  8. package/dist/api/gradient.d.ts +1 -53
  9. package/dist/api/gradient.js +2 -97
  10. package/dist/api/index.d.ts +2 -0
  11. package/dist/api/index.js +2 -0
  12. package/dist/api/layout.js +4 -3
  13. package/dist/api/table.js +3 -1
  14. package/dist/api/text.d.ts +8 -8
  15. package/dist/api/text.js +6 -0
  16. package/dist/canvas/painter.d.ts +2 -2
  17. package/dist/canvas/painter.js +2 -2
  18. package/dist/common/color-input.d.ts +24 -0
  19. package/dist/common/color-input.js +278 -0
  20. package/dist/common/diagnostics.d.ts +9 -1
  21. package/dist/common/gradient.d.ts +53 -0
  22. package/dist/common/gradient.js +97 -0
  23. package/dist/elements/container-element.d.ts +3 -0
  24. package/dist/elements/container-element.js +17 -5
  25. package/dist/elements/image-element.d.ts +37 -26
  26. package/dist/elements/image-element.js +88 -50
  27. package/dist/elements/index.d.ts +3 -0
  28. package/dist/elements/index.js +3 -0
  29. package/dist/elements/layout/column-break-element.d.ts +17 -0
  30. package/dist/elements/layout/column-break-element.js +26 -0
  31. package/dist/elements/layout/deferred-element.d.ts +9 -1
  32. package/dist/elements/layout/deferred-element.js +18 -3
  33. package/dist/elements/layout/expanded-element.d.ts +4 -0
  34. package/dist/elements/layout/expanded-element.js +14 -1
  35. package/dist/elements/layout/flow-element.d.ts +109 -0
  36. package/dist/elements/layout/flow-element.js +317 -0
  37. package/dist/elements/layout/keep-together-element.js +14 -2
  38. package/dist/elements/layout/padding-element.d.ts +3 -0
  39. package/dist/elements/layout/padding-element.js +9 -2
  40. package/dist/elements/layout/page-builder-element.js +2 -2
  41. package/dist/elements/layout/positioned-element.d.ts +9 -2
  42. package/dist/elements/layout/positioned-element.js +6 -2
  43. package/dist/elements/layout/proxy-element.d.ts +4 -0
  44. package/dist/elements/layout/proxy-element.js +12 -0
  45. package/dist/elements/layout/region-element.d.ts +53 -0
  46. package/dist/elements/layout/region-element.js +108 -0
  47. package/dist/elements/layout/repeating-header-element.d.ts +3 -0
  48. package/dist/elements/layout/repeating-header-element.js +16 -1
  49. package/dist/elements/layout/rotated-element.d.ts +2 -0
  50. package/dist/elements/layout/rotated-element.js +8 -0
  51. package/dist/elements/pdf-element.d.ts +32 -3
  52. package/dist/elements/pdf-element.js +50 -1
  53. package/dist/elements/rectangle-element.d.ts +12 -6
  54. package/dist/elements/rectangle-element.js +31 -12
  55. package/dist/elements/row-element.d.ts +6 -12
  56. package/dist/elements/row-element.js +34 -4
  57. package/dist/elements/svg-element.js +4 -10
  58. package/dist/elements/text-element.d.ts +17 -1
  59. package/dist/elements/text-element.js +63 -40
  60. package/dist/ir/display-list.d.ts +6 -1
  61. package/dist/layout/box-constraints.d.ts +12 -0
  62. package/dist/layout/box-constraints.js +17 -0
  63. package/dist/layout/collect-images.js +8 -4
  64. package/dist/layout/flex-item.d.ts +2 -0
  65. package/dist/layout/flex-item.js +14 -2
  66. package/dist/layout/fragmentation.d.ts +11 -5
  67. package/dist/layout/fragmentation.js +27 -7
  68. package/dist/layout/layout-cache.d.ts +8 -8
  69. package/dist/layout/layout-cache.js +10 -7
  70. package/dist/platform/browser-fs.d.ts +1 -2
  71. package/dist/platform/browser-fs.js +3 -6
  72. package/dist/platform/node-fs.d.ts +0 -1
  73. package/dist/platform/node-fs.js +0 -4
  74. package/dist/renderer/column-break-renderer.d.ts +8 -0
  75. package/dist/renderer/column-break-renderer.js +15 -0
  76. package/dist/renderer/emoji-image.js +3 -1
  77. package/dist/renderer/flow-renderer.d.ts +16 -0
  78. package/dist/renderer/flow-renderer.js +21 -0
  79. package/dist/renderer/image-renderer.js +31 -54
  80. package/dist/renderer/index.d.ts +2 -0
  81. package/dist/renderer/index.js +2 -0
  82. package/dist/renderer/page-renderer.js +3 -4
  83. package/dist/renderer/pdf-backend.js +9 -2
  84. package/dist/renderer/pdf-document-renderer.js +2 -1
  85. package/dist/renderer/pdf-renderer.js +9 -0
  86. package/dist/renderer/positioned-renderer.d.ts +14 -2
  87. package/dist/renderer/positioned-renderer.js +40 -4
  88. package/dist/renderer/rectangle-renderer.d.ts +1 -1
  89. package/dist/renderer/rectangle-renderer.js +11 -6
  90. package/dist/renderer/rotated-renderer.js +3 -2
  91. package/dist/renderer/text-renderer.js +25 -28
  92. package/dist/svg/index.d.ts +1 -1
  93. package/dist/svg/index.js +26 -5
  94. package/dist/svg/style.js +1 -1
  95. package/dist/text/line-breaker.d.ts +23 -0
  96. package/dist/text/line-breaker.js +142 -65
  97. package/dist/text/line-metrics.d.ts +2 -0
  98. package/dist/text/line-metrics.js +5 -1
  99. package/dist/text/word-splitting.js +11 -5
  100. package/dist/utils/flex-layout.d.ts +3 -1
  101. package/dist/utils/flex-layout.js +141 -33
  102. package/dist/utils/image-helper.d.ts +59 -11
  103. package/dist/utils/image-helper.js +207 -80
  104. package/dist/utils/pdf-object-manager.d.ts +6 -3
  105. package/dist/utils/pdf-object-manager.js +31 -7
  106. package/dist/utils/renderer-registry.d.ts +6 -0
  107. package/dist/utils/renderer-registry.js +1 -1
  108. package/dist/utils/ttf-parser.js +3 -2
  109. package/package.json +1 -1
@@ -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,108 @@
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), not in the content or anywhere else.");
8
+ this.name = "RegionOutsideFlowError";
9
+ }
10
+ }
11
+ /** One Region instance handed to two pages of a Flow (or two Flows): both would draw the same content. */
12
+ export class RegionReusedError extends Error {
13
+ constructor() {
14
+ super("@jasy/pdf: a Region is used by two pages of a Flow (or by two Flows). A template is called for " +
15
+ "every page - build the Regions, and the layout around them, inside the function.");
16
+ this.name = "RegionReusedError";
17
+ }
18
+ }
19
+ /**
20
+ * A place in a `Flow`'s template that the content flows through. It has no size of its own: the layout
21
+ * around it sizes it (`Expanded`, `width`, `height`, `%`), and with nothing given it fills what it is
22
+ * offered, as a Canvas does. The Flow claims it, then hands it the piece of content that fits.
23
+ */
24
+ export class RegionElement extends SizedPDFElement {
25
+ constructor(params = {}) {
26
+ super({ x: 0, y: 0, width: params.width, height: params.height });
27
+ this.content = null;
28
+ const { widthFactor, heightFactor, ...sizing } = params;
29
+ this.widthFactor = widthFactor;
30
+ this.heightFactor = heightFactor;
31
+ this.sizing = sizing;
32
+ this.requested = { width: params.width, height: params.height };
33
+ }
34
+ /** Marks it as part of one page of a Flow; a second page claiming it throws. */
35
+ claim(owner) {
36
+ if (this.owner && this.owner !== owner)
37
+ throw new RegionReusedError();
38
+ this.owner = owner;
39
+ }
40
+ /** The content it holds on this page (`null`: the content ran out before it). */
41
+ hold(content) {
42
+ this.content = content;
43
+ }
44
+ get held() {
45
+ return this.content;
46
+ }
47
+ /** A height the author gave (points or `%`): kept on the Flow's last page instead of shrinking. */
48
+ hasOwnHeight() {
49
+ return this.requested.height !== undefined || this.heightFactor !== undefined;
50
+ }
51
+ /** Where it has no size of its own it fills what it is offered, so the stack around it must offer
52
+ * something - as for a Spacer. */
53
+ needsBoundedMain(horizontal) {
54
+ return horizontal
55
+ ? this.requested.width === undefined && this.widthFactor === undefined
56
+ : !this.hasOwnHeight();
57
+ }
58
+ /** A `%` size is a share of the stack it sits in, which has to know to resolve it - as for a Box. */
59
+ relativeSizeFactor(horizontal) {
60
+ return horizontal ? this.widthFactor : this.heightFactor;
61
+ }
62
+ calculateLayout(constraints, offset, ctx) {
63
+ // Laid out in a Flow's template, it belongs to that page; anywhere else it has no meaning.
64
+ if (!ctx.flowPage)
65
+ throw new RegionOutsideFlowError();
66
+ this.claim(ctx.flowPage);
67
+ const size = this.sizeFor(constraints);
68
+ this.x = offset.x;
69
+ this.y = offset.y;
70
+ this.width = size.width;
71
+ this.height = size.height;
72
+ // Its content is not the template: a Region in it is outside any Flow (or in its own inner one).
73
+ this.content?.calculateLayout(BoxConstraints.loose(size.width, size.height), { x: 0, y: 0 }, { ...ctx, flowPage: undefined });
74
+ return size;
75
+ }
76
+ computeSize(constraints) {
77
+ return this.sizeFor(constraints);
78
+ }
79
+ /** Its size under these constraints - the one computation both `measure` and layout use. */
80
+ sizeFor(constraints) {
81
+ const specs = extentSpecs({
82
+ ...this.sizing,
83
+ width: this.requested.width,
84
+ height: this.requested.height,
85
+ widthFactor: this.widthFactor,
86
+ heightFactor: this.heightFactor,
87
+ });
88
+ const resolved = resolveSize(specs.width, specs.height, this.sizing.aspectRatio, constraints);
89
+ const bounds = resolved.constraints;
90
+ return {
91
+ width: resolved.width ?? (bounds.hasBoundedWidth ? bounds.maxWidth : 0),
92
+ height: resolved.height ?? (bounds.hasBoundedHeight ? bounds.maxHeight : 0),
93
+ };
94
+ }
95
+ layoutChildren() {
96
+ return this.content ? [this.content] : [];
97
+ }
98
+ getProps() {
99
+ return {
100
+ x: this.x,
101
+ y: this.y,
102
+ width: this.width,
103
+ height: this.height,
104
+ // Only when it holds something: the pre-layout walks read `child` as an element.
105
+ ...(this.content ? { child: this.content } : {}),
106
+ };
107
+ }
108
+ }
@@ -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.header, split.remainder, this.gap).asPieceOf(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
- /** What a `Box`'s percentage padding resolves against: the width the Box itself was offered (CSS:
78
- * the containing block), not its inner width. Set by the Box for its own padding only. */
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 honoured rather than swallowed. Containers override this to recurse into their children.
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 honoured rather than swallowed. Containers override this to recurse into their children.
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 "../api/gradient.js";
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 colour, or a box-relative gradient the renderer resolves against this box. */
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
- /** A Box's own padding resolves a percentage against what the Box was OFFERED (CSS: the containing
82
- * block), not against the inner width it hands its children - so it is told. Only a bounded offer:
83
- * in a Row a child is offered no width at all, and there the padding keeps resolving against the
84
- * Box's own width, as it always did. */
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 honoured before the split.
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, contentHeight(remainder) + 2 * this.borderWidth, this.breakAfter),
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
- /** A Box's own padding resolves a percentage against what the Box was OFFERED (CSS: the containing
114
- * block), not against the inner width it hands its children - so it is told. Only a bounded offer:
115
- * in a Row a child is offered no width at all, and there the padding keeps resolving against the
116
- * Box's own width, as it always did. */
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 && Number.isFinite(offered)
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
  }