@jasy/pdf 1.0.0-beta.3 → 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.
- package/dist/api/color.d.ts +1 -24
- package/dist/api/color.js +2 -278
- package/dist/api/content.d.ts +10 -5
- package/dist/api/content.js +29 -13
- 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/font-url.d.ts +1 -2
- package/dist/api/font-url.js +8 -74
- package/dist/api/forms.d.ts +3 -0
- package/dist/api/forms.js +3 -0
- package/dist/api/gradient.d.ts +1 -53
- package/dist/api/gradient.js +2 -97
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.js +3 -0
- package/dist/api/layout.d.ts +6 -0
- package/dist/api/layout.js +9 -3
- package/dist/api/page-builder.d.ts +4 -0
- package/dist/api/page-builder.js +4 -0
- package/dist/api/structure.d.ts +10 -2
- package/dist/api/structure.js +9 -6
- package/dist/api/table.d.ts +5 -3
- package/dist/api/table.js +101 -22
- 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 +61 -0
- package/dist/common/diagnostics.js +77 -0
- package/dist/common/gradient.d.ts +53 -0
- package/dist/common/gradient.js +97 -0
- package/dist/edit/acroform-reader.js +7 -7
- package/dist/edit/appearance.js +3 -3
- package/dist/edit/attachments.d.ts +52 -0
- package/dist/edit/attachments.js +178 -0
- package/dist/edit/document.d.ts +38 -8
- package/dist/edit/document.js +208 -51
- package/dist/edit/fill.js +8 -23
- package/dist/edit/filters.d.ts +15 -0
- package/dist/edit/filters.js +157 -0
- package/dist/edit/index.d.ts +4 -1
- package/dist/edit/index.js +3 -1
- package/dist/edit/objects.d.ts +3 -4
- package/dist/edit/objects.js +5 -18
- package/dist/elements/canvas-element.d.ts +3 -0
- package/dist/elements/canvas-element.js +14 -3
- package/dist/elements/container-element.d.ts +8 -1
- package/dist/elements/container-element.js +49 -17
- package/dist/elements/forms/checkbox-element.d.ts +2 -0
- package/dist/elements/forms/checkbox-element.js +12 -3
- package/dist/elements/forms/choice-element.d.ts +6 -1
- package/dist/elements/forms/choice-element.js +17 -6
- package/dist/elements/forms/field-font.d.ts +8 -0
- package/dist/elements/forms/field-font.js +5 -0
- package/dist/elements/forms/push-button-element.d.ts +6 -1
- package/dist/elements/forms/push-button-element.js +16 -4
- package/dist/elements/forms/radio-element.d.ts +2 -0
- package/dist/elements/forms/radio-element.js +12 -3
- package/dist/elements/forms/signature-element.d.ts +6 -1
- package/dist/elements/forms/signature-element.js +16 -4
- package/dist/elements/forms/text-field-element.d.ts +6 -1
- package/dist/elements/forms/text-field-element.js +16 -4
- package/dist/elements/image-element.d.ts +70 -21
- package/dist/elements/image-element.js +173 -52
- package/dist/elements/index.d.ts +3 -0
- package/dist/elements/index.js +3 -0
- package/dist/elements/layout/anchor-element.d.ts +8 -10
- package/dist/elements/layout/anchor-element.js +10 -18
- package/dist/elements/layout/bookmark-element.d.ts +9 -11
- package/dist/elements/layout/bookmark-element.js +11 -20
- 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/default-text-style-element.d.ts +11 -13
- package/dist/elements/layout/default-text-style-element.js +14 -31
- package/dist/elements/layout/deferred-element.d.ts +20 -4
- package/dist/elements/layout/deferred-element.js +41 -9
- package/dist/elements/layout/expanded-element.d.ts +11 -0
- package/dist/elements/layout/expanded-element.js +53 -18
- 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.d.ts +8 -18
- package/dist/elements/layout/keep-together-element.js +33 -45
- package/dist/elements/layout/link-element.d.ts +8 -13
- package/dist/elements/layout/link-element.js +9 -19
- package/dist/elements/layout/padding-element.d.ts +18 -1
- package/dist/elements/layout/padding-element.js +63 -28
- package/dist/elements/layout/page-break-element.d.ts +4 -1
- package/dist/elements/layout/page-break-element.js +6 -1
- package/dist/elements/layout/page-builder-element.d.ts +5 -0
- package/dist/elements/layout/page-builder-element.js +13 -2
- package/dist/elements/layout/positioned-element.d.ts +23 -4
- package/dist/elements/layout/positioned-element.js +42 -12
- package/dist/elements/layout/proxy-element.d.ts +47 -0
- package/dist/elements/layout/proxy-element.js +106 -0
- package/dist/elements/layout/region-element.d.ts +53 -0
- package/dist/elements/layout/region-element.js +108 -0
- package/dist/elements/layout/repeating-header-element.d.ts +9 -0
- package/dist/elements/layout/repeating-header-element.js +55 -13
- package/dist/elements/layout/rotated-box-element.d.ts +6 -0
- package/dist/elements/layout/rotated-box-element.js +27 -14
- package/dist/elements/layout/rotated-element.d.ts +9 -0
- package/dist/elements/layout/rotated-element.js +33 -2
- package/dist/elements/layout/struct-group.d.ts +9 -17
- package/dist/elements/layout/struct-group.js +10 -35
- package/dist/elements/line-element.d.ts +1 -0
- package/dist/elements/line-element.js +6 -0
- package/dist/elements/page-element.d.ts +10 -3
- package/dist/elements/page-element.js +39 -31
- package/dist/elements/pdf-document-element.d.ts +3 -0
- package/dist/elements/pdf-document-element.js +7 -0
- package/dist/elements/pdf-element.d.ts +105 -16
- package/dist/elements/pdf-element.js +118 -19
- package/dist/elements/rectangle-element.d.ts +22 -2
- package/dist/elements/rectangle-element.js +119 -55
- package/dist/elements/row-element.d.ts +17 -12
- package/dist/elements/row-element.js +89 -9
- package/dist/elements/svg-element.d.ts +30 -6
- package/dist/elements/svg-element.js +91 -26
- package/dist/elements/text-element.d.ts +48 -13
- package/dist/elements/text-element.js +161 -116
- package/dist/forms/acroform.d.ts +9 -1
- package/dist/forms/acroform.js +83 -34
- package/dist/forms/appearance.d.ts +17 -6
- package/dist/forms/appearance.js +18 -16
- package/dist/ir/display-list.d.ts +18 -3
- package/dist/layout/box-constraints.d.ts +17 -0
- package/dist/layout/box-constraints.js +31 -0
- package/dist/layout/collect-images.d.ts +6 -4
- package/dist/layout/collect-images.js +14 -8
- package/dist/layout/flex-item.d.ts +11 -0
- package/dist/layout/flex-item.js +51 -0
- package/dist/layout/fragmentation.d.ts +23 -9
- package/dist/layout/fragmentation.js +62 -19
- package/dist/layout/layout-cache.d.ts +53 -0
- package/dist/layout/layout-cache.js +110 -0
- 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/anchor-renderer.d.ts +2 -1
- package/dist/renderer/anchor-renderer.js +5 -4
- package/dist/renderer/bookmark-renderer.d.ts +2 -1
- package/dist/renderer/bookmark-renderer.js +5 -4
- package/dist/renderer/canvas-renderer.d.ts +2 -1
- package/dist/renderer/canvas-renderer.js +5 -2
- package/dist/renderer/column-break-renderer.d.ts +8 -0
- package/dist/renderer/column-break-renderer.js +15 -0
- package/dist/renderer/container-renderer.d.ts +2 -1
- package/dist/renderer/container-renderer.js +6 -5
- package/dist/renderer/default-text-style-renderer.d.ts +2 -1
- package/dist/renderer/default-text-style-renderer.js +2 -3
- package/dist/renderer/deferred-renderer.d.ts +2 -1
- package/dist/renderer/deferred-renderer.js +2 -3
- package/dist/renderer/emoji-image.js +3 -1
- package/dist/renderer/expanded-renderer.d.ts +2 -1
- package/dist/renderer/expanded-renderer.js +2 -3
- package/dist/renderer/flow-renderer.d.ts +16 -0
- package/dist/renderer/flow-renderer.js +21 -0
- package/dist/renderer/form-field-renderer.d.ts +2 -1
- package/dist/renderer/form-field-renderer.js +16 -3
- package/dist/renderer/image-renderer.d.ts +2 -1
- package/dist/renderer/image-renderer.js +33 -53
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.js +2 -0
- package/dist/renderer/keep-together-renderer.d.ts +2 -1
- package/dist/renderer/keep-together-renderer.js +2 -3
- package/dist/renderer/line-renderer.d.ts +2 -1
- package/dist/renderer/line-renderer.js +7 -2
- package/dist/renderer/link-renderer.d.ts +2 -1
- package/dist/renderer/link-renderer.js +36 -5
- package/dist/renderer/padding-renderer.d.ts +2 -1
- package/dist/renderer/padding-renderer.js +5 -5
- package/dist/renderer/page-break-renderer.d.ts +6 -5
- package/dist/renderer/page-break-renderer.js +14 -7
- package/dist/renderer/page-builder-renderer.d.ts +2 -1
- package/dist/renderer/page-builder-renderer.js +2 -3
- package/dist/renderer/page-renderer.js +30 -10
- package/dist/renderer/pdf-backend.d.ts +4 -3
- package/dist/renderer/pdf-backend.js +14 -7
- package/dist/renderer/pdf-document-renderer.d.ts +1 -8
- package/dist/renderer/pdf-document-renderer.js +17 -17
- package/dist/renderer/pdf-renderer.js +20 -11
- package/dist/renderer/positioned-renderer.d.ts +18 -1
- package/dist/renderer/positioned-renderer.js +43 -6
- package/dist/renderer/rectangle-renderer.d.ts +2 -1
- package/dist/renderer/rectangle-renderer.js +18 -8
- package/dist/renderer/repeating-header-renderer.d.ts +2 -1
- package/dist/renderer/repeating-header-renderer.js +6 -5
- package/dist/renderer/rotated-renderer.d.ts +2 -1
- package/dist/renderer/rotated-renderer.js +9 -5
- package/dist/renderer/row-renderer.d.ts +2 -1
- package/dist/renderer/row-renderer.js +6 -5
- package/dist/renderer/struct-group-renderer.d.ts +2 -1
- package/dist/renderer/struct-group-renderer.js +2 -3
- package/dist/renderer/svg-renderer.d.ts +2 -1
- package/dist/renderer/svg-renderer.js +8 -2
- package/dist/renderer/text-renderer.d.ts +4 -6
- package/dist/renderer/text-renderer.js +114 -127
- package/dist/svg/gradients.js +5 -0
- package/dist/svg/index.d.ts +1 -1
- package/dist/svg/index.js +36 -6
- package/dist/svg/style.d.ts +2 -0
- package/dist/svg/style.js +12 -1
- package/dist/text/advance.d.ts +4 -0
- package/dist/text/advance.js +17 -4
- package/dist/text/bidi.d.ts +0 -2
- package/dist/text/bidi.js +0 -10
- package/dist/text/glyph-coverage.d.ts +1 -1
- package/dist/text/glyph-coverage.js +7 -3
- package/dist/text/hyphen-breaks.d.ts +17 -0
- package/dist/text/hyphen-breaks.js +54 -0
- package/dist/text/line-breaker.d.ts +55 -29
- package/dist/text/line-breaker.js +453 -188
- package/dist/text/line-metrics.d.ts +8 -3
- package/dist/text/line-metrics.js +11 -2
- package/dist/text/pdf-text-string.d.ts +21 -0
- package/dist/text/pdf-text-string.js +114 -0
- package/dist/text/text-decoration.d.ts +5 -5
- package/dist/text/text-decoration.js +5 -5
- package/dist/text/word-splitting.d.ts +15 -2
- package/dist/text/word-splitting.js +45 -13
- package/dist/utils/afm-parser.d.ts +2 -0
- package/dist/utils/afm-parser.js +10 -1
- package/dist/utils/dest-registry.d.ts +1 -1
- package/dist/utils/dest-registry.js +3 -2
- package/dist/utils/fetch-bytes.d.ts +19 -0
- package/dist/utils/fetch-bytes.js +79 -0
- package/dist/utils/flex-layout.d.ts +16 -14
- package/dist/utils/flex-layout.js +427 -217
- package/dist/utils/font-metrics.d.ts +4 -3
- package/dist/utils/image-helper.d.ts +59 -11
- package/dist/utils/image-helper.js +209 -81
- package/dist/utils/pdf-object-manager.d.ts +17 -6
- package/dist/utils/pdf-object-manager.js +70 -20
- package/dist/utils/renderer-registry.d.ts +49 -2
- package/dist/utils/renderer-registry.js +40 -0
- package/dist/utils/struct-tree.d.ts +24 -0
- package/dist/utils/struct-tree.js +61 -9
- package/dist/utils/ttf-parser.d.ts +4 -0
- package/dist/utils/ttf-parser.js +30 -6
- package/dist/validators/element-validator.js +2 -1
- package/package.json +1 -1
- package/dist/elements/layout/sized-container-element.d.ts +0 -14
- package/dist/elements/layout/sized-container-element.js +0 -32
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
// Import the renderer DIRECTLY, not via the "../renderer" barrel: the barrel pulls in
|
|
2
|
-
// pdf-renderer (and every element) while this element module is still loading, which under
|
|
3
|
-
// ESM (Vite/vitest, and the future framework bindings) duplicates the element classes and
|
|
4
|
-
// breaks the constructor-keyed RendererRegistry. A direct import keeps the graph acyclic.
|
|
5
|
-
import { TextRenderer } from "../renderer/text-renderer.js";
|
|
6
1
|
import { DEFAULT_TEXT_STYLE } from "../text/text-style.js";
|
|
2
|
+
import { BoxConstraints } from "../layout/box-constraints.js";
|
|
7
3
|
import { applyTextTransform } from "../text/text-style.js";
|
|
8
4
|
import { splitByFont } from "../text/font-fallback.js";
|
|
9
5
|
import { runAdvance } from "../text/advance.js";
|
|
10
|
-
import { singleLineWidth, MAX_SPACE_SHRINK,
|
|
6
|
+
import { runStyleOf, singleLineWidth, MAX_SPACE_SHRINK, breakSegmentsIntoLines, } from "../text/line-breaker.js";
|
|
7
|
+
import { hyphenPieces } from "../text/hyphen-breaks.js";
|
|
11
8
|
import { adjustForOrphansWidows, DEFAULT_ORPHANS, DEFAULT_WIDOWS, } from "../text/orphans-widows.js";
|
|
12
|
-
import { lineBoxForSegmentLine,
|
|
9
|
+
import { lineBoxForSegmentLine, linesHeight } from "../text/line-metrics.js";
|
|
13
10
|
import { HorizontalAlignment, SizedPDFElement } from "./pdf-element.js";
|
|
14
11
|
import { normalizeContent } from "../text/whitespace.js";
|
|
15
12
|
import { coverText } from "../text/glyph-coverage.js";
|
|
13
|
+
import { diagnose, pageOf } from "../common/diagnostics.js";
|
|
14
|
+
import { BuiltFromContext } from "../layout/layout-cache.js";
|
|
16
15
|
/** How far a run sits off the baseline, as a fraction of its font size (up is positive). */
|
|
17
16
|
export const VERTICAL_TEXT_SHIFT = {
|
|
18
17
|
baseline: 0,
|
|
@@ -31,9 +30,15 @@ function finitePoints(value, name) {
|
|
|
31
30
|
return value;
|
|
32
31
|
}
|
|
33
32
|
export class TextElement extends SizedPDFElement {
|
|
34
|
-
constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow = "clip", lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, breakWord, hyphenate, ligatures, role, }) {
|
|
33
|
+
constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow = "clip", lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, breakWord, hyphenate, ligatures, role, shownLines, }) {
|
|
35
34
|
super({ x: 0, y: 0 });
|
|
35
|
+
this.broken = new BuiltFromContext(() => false);
|
|
36
|
+
/** What `covered` had to remove, reported where the text is laid out on its page (see
|
|
37
|
+
* `calculateLayout`). A split paragraph's first piece carries its parent's, as `lostBefore`. */
|
|
38
|
+
this.lostGlyphs = [];
|
|
39
|
+
this.lostBefore = [];
|
|
36
40
|
this.role = role;
|
|
41
|
+
this.shownLines = shownLines;
|
|
37
42
|
this.rawFontSize = fontSize;
|
|
38
43
|
this.rawFontFamily = fontFamily;
|
|
39
44
|
this.rawFontStyle = fontStyle;
|
|
@@ -86,8 +91,7 @@ export class TextElement extends SizedPDFElement {
|
|
|
86
91
|
return memo.value;
|
|
87
92
|
const cased = this.recased();
|
|
88
93
|
// A SPAN may bring its own stack, so the element's being empty is not enough to skip the pass.
|
|
89
|
-
const anyStack = this.fontFallback.length > 0 ||
|
|
90
|
-
(typeof cased !== "string" && cased.some((seg) => (seg.fontFallback?.length ?? 0) > 0));
|
|
94
|
+
const anyStack = this.fontFallback.length > 0 || cased.some((seg) => (seg.fontFallback?.length ?? 0) > 0);
|
|
91
95
|
if (!metrics)
|
|
92
96
|
return cased;
|
|
93
97
|
const remember = (value) => {
|
|
@@ -104,11 +108,9 @@ export class TextElement extends SizedPDFElement {
|
|
|
104
108
|
};
|
|
105
109
|
if (!anyStack)
|
|
106
110
|
return remember(this.covered(cased, metrics));
|
|
107
|
-
// Font fallback
|
|
108
|
-
// knows how to handle. Nothing new downstream.
|
|
109
|
-
const pieces = typeof cased === "string" ? [{ content: cased }] : cased;
|
|
111
|
+
// Font fallback splits a span into more spans - one per family. Nothing new downstream.
|
|
110
112
|
const out = [];
|
|
111
|
-
for (const seg of
|
|
113
|
+
for (const seg of cased) {
|
|
112
114
|
const runs = splitByFont(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontFallback ?? this.fontFallback, seg.fontStyle ?? this.fontStyle, metrics);
|
|
113
115
|
if (!runs)
|
|
114
116
|
out.push(seg);
|
|
@@ -125,33 +127,30 @@ export class TextElement extends SizedPDFElement {
|
|
|
125
127
|
* back unchanged, so an ordinary document is byte-identical.
|
|
126
128
|
*/
|
|
127
129
|
covered(content, metrics) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
report(dropped);
|
|
133
|
-
return text;
|
|
134
|
-
}
|
|
135
|
-
return content.map((seg) => {
|
|
130
|
+
// Collected over every span: the element is reported once, with everything it lost, on the page it
|
|
131
|
+
// is laid out on - not here, which may be measuring it for an earlier page.
|
|
132
|
+
const lost = [];
|
|
133
|
+
const covered = content.map((seg) => {
|
|
136
134
|
const { text, dropped } = coverText(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontStyle ?? this.fontStyle, metrics);
|
|
137
|
-
|
|
138
|
-
report(dropped);
|
|
135
|
+
lost.push(...dropped);
|
|
139
136
|
return text === seg.content ? seg : { ...seg, content: text };
|
|
140
137
|
});
|
|
138
|
+
this.lostGlyphs = [...new Set(lost)];
|
|
139
|
+
return covered;
|
|
141
140
|
}
|
|
142
|
-
/** The content with `text-transform` applied, and nothing else. */
|
|
141
|
+
/** The content as spans, with `text-transform` applied, and nothing else. */
|
|
143
142
|
recased() {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
// A string is one span: from here on, measuring, breaking and drawing know only spans.
|
|
144
|
+
const spans = typeof this.content === "string" ? [{ content: this.content }] : this.content;
|
|
145
|
+
const transformOf = (seg) => seg.textTransform ?? this.textTransform;
|
|
146
|
+
if (spans.every((seg) => transformOf(seg) === "none"))
|
|
147
|
+
return spans;
|
|
149
148
|
// The capitalisation state runs THROUGH the spans: `span("hel") + span("lo")` is one word.
|
|
150
149
|
let atWordStart = true;
|
|
151
|
-
return
|
|
152
|
-
const done = applyTextTransform(seg.content,
|
|
150
|
+
return spans.map((seg) => {
|
|
151
|
+
const done = applyTextTransform(seg.content, transformOf(seg), atWordStart);
|
|
153
152
|
atWordStart = done.atWordStart;
|
|
154
|
-
return { ...seg, content: done.text };
|
|
153
|
+
return done.text === seg.content ? seg : { ...seg, content: done.text };
|
|
155
154
|
});
|
|
156
155
|
}
|
|
157
156
|
lineOptions() {
|
|
@@ -162,6 +161,7 @@ export class TextElement extends SizedPDFElement {
|
|
|
162
161
|
ligatures: this.ligatures,
|
|
163
162
|
// Justified lines may be squeezed to keep a word; every other alignment gets no slack.
|
|
164
163
|
shrink: this.textAlignment === HorizontalAlignment.justify ? MAX_SPACE_SHRINK : 0,
|
|
164
|
+
shownLines: this.shownLines,
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
applyStyle(ts) {
|
|
@@ -188,44 +188,16 @@ export class TextElement extends SizedPDFElement {
|
|
|
188
188
|
* Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
|
|
189
189
|
* the rest become a remainder `TextElement` re-wrapped on the next page. If not even one
|
|
190
190
|
* line fits, nothing is forced here - the caller (the container) decides whether to move
|
|
191
|
-
* the whole element on for progress.
|
|
191
|
+
* the whole element on for progress.
|
|
192
192
|
*/
|
|
193
193
|
fragment(maxHeight, width, ctx) {
|
|
194
194
|
this.resolveStyle(ctx);
|
|
195
195
|
const content = this.display(ctx.metrics);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// so `floor(maxHeight / lineBox)` lines fit.
|
|
202
|
-
fragmentString(content, maxHeight, width, ctx) {
|
|
203
|
-
const lines = wrapStringIntoLines(content, this.fontFamily, this.fontSize, this.fontStyle, width, ctx.metrics, this.maxLines, this.overflow, this.letterSpacing, this.lineOptions());
|
|
204
|
-
const box = lineBoxForString(ctx.metrics, this.fontFamily, this.fontStyle, this.fontSize, this.lineHeight);
|
|
205
|
-
const fittedLineCount = adjustForOrphansWidows(Math.floor(maxHeight / box.height), lines.length, this.breakRule());
|
|
206
|
-
if (fittedLineCount <= 0)
|
|
207
|
-
return { fitted: null, remainder: this };
|
|
208
|
-
if (fittedLineCount >= lines.length)
|
|
209
|
-
return { fitted: this, remainder: null };
|
|
210
|
-
return {
|
|
211
|
-
fitted: this.cloneWithContent(lines.slice(0, fittedLineCount).join(" ")),
|
|
212
|
-
// A continuation is no longer the paragraph's FIRST line, so it carries no indent.
|
|
213
|
-
remainder: this.cloneWithContent(lines.slice(fittedLineCount).join(" "), 0),
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
// Styled segments: each line's height comes from the fonts on it, so pack lines by cumulative
|
|
217
|
-
// box height. Rebuild the fitted/remainder halves back into TextSegment[].
|
|
218
|
-
fragmentSegments(content, maxHeight, width, ctx) {
|
|
219
|
-
const defaults = {
|
|
220
|
-
fontFamily: this.fontFamily,
|
|
221
|
-
fontSize: this.fontSize,
|
|
222
|
-
fontStyle: this.fontStyle,
|
|
223
|
-
letterSpacing: this.letterSpacing,
|
|
224
|
-
direction: this.direction,
|
|
225
|
-
wordSpacing: this.wordSpacing,
|
|
226
|
-
textIndent: this.textIndent,
|
|
227
|
-
};
|
|
228
|
-
const lines = breakSegmentsIntoLines(content, defaults, width, ctx.metrics, this.maxLines, this.overflow);
|
|
196
|
+
// The SAME knobs measuring and drawing break with (`textHeight`, the renderer), or this
|
|
197
|
+
// would cut a paragraph at a line count it is never drawn with. Each line's height comes from
|
|
198
|
+
// the fonts on it, so lines are packed by their cumulative height.
|
|
199
|
+
const defaults = this.segmentDefaults();
|
|
200
|
+
const lines = this.linesAt(width, ctx);
|
|
229
201
|
let used = 0;
|
|
230
202
|
let packed = 0;
|
|
231
203
|
for (const line of lines) {
|
|
@@ -240,9 +212,35 @@ export class TextElement extends SizedPDFElement {
|
|
|
240
212
|
return { fitted: null, remainder: this };
|
|
241
213
|
if (fittedLineCount >= lines.length)
|
|
242
214
|
return { fitted: this, remainder: null };
|
|
215
|
+
// A piece is a window onto the SAME text, not the lines glued back together: the first page draws
|
|
216
|
+
// the first lines of the whole paragraph (broken exactly as here, same text, same width), and the
|
|
217
|
+
// next one continues from where its first line starts in the source - inside its span, keeping
|
|
218
|
+
// that span's style. Hard breaks, a word split over the break and the justified last line on the
|
|
219
|
+
// page all survive that way. A continuation is no longer the paragraph's FIRST line, so it
|
|
220
|
+
// carries no indent; whatever is left of a line cap goes with it.
|
|
221
|
+
const { segment, offset } = lines[fittedLineCount].start;
|
|
222
|
+
const rest = [
|
|
223
|
+
{ ...content[segment], content: content[segment].content.slice(offset) },
|
|
224
|
+
...content.slice(segment + 1),
|
|
225
|
+
];
|
|
226
|
+
const fitted = this.cloneWithContent(content, this.textIndent, { shownLines: fittedLineCount });
|
|
227
|
+
// The piece is cut from already-covered text, so it would find nothing missing itself.
|
|
228
|
+
fitted.lostBefore = [...this.lostBefore, ...this.lostGlyphs];
|
|
229
|
+
return {
|
|
230
|
+
fitted,
|
|
231
|
+
remainder: this.cloneWithContent(rest, 0, {
|
|
232
|
+
maxLines: this.maxLines === undefined ? undefined : this.maxLines - fittedLineCount,
|
|
233
|
+
}),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/** How the text is broken: the knobs `textHeight` and the renderer pass too. */
|
|
237
|
+
segmentDefaults() {
|
|
243
238
|
return {
|
|
244
|
-
|
|
245
|
-
|
|
239
|
+
fontFamily: this.fontFamily,
|
|
240
|
+
fontSize: this.fontSize,
|
|
241
|
+
fontStyle: this.fontStyle,
|
|
242
|
+
letterSpacing: this.letterSpacing,
|
|
243
|
+
...this.lineOptions(),
|
|
246
244
|
};
|
|
247
245
|
}
|
|
248
246
|
/** The orphan/widow minimums for this paragraph; unset means the CSS default of 2 lines each. */
|
|
@@ -254,15 +252,16 @@ export class TextElement extends SizedPDFElement {
|
|
|
254
252
|
}
|
|
255
253
|
// A copy carrying the same style but different (already-wrapped) content. Re-wrapping at
|
|
256
254
|
// the same width reproduces exactly those lines (greedy is deterministic).
|
|
257
|
-
cloneWithContent(content, indent = this.textIndent) {
|
|
255
|
+
cloneWithContent(content, indent = this.textIndent, piece = {}) {
|
|
258
256
|
return new TextElement({
|
|
259
|
-
|
|
257
|
+
// Cut from `display()`, already recased - a span's own transform too.
|
|
258
|
+
content: content.map((seg) => (seg.textTransform ?? "none") === "none" ? seg : { ...seg, textTransform: "none" }),
|
|
260
259
|
fontSize: this.fontSize,
|
|
261
260
|
fontFamily: this.fontFamily,
|
|
262
261
|
fontStyle: this.fontStyle,
|
|
263
262
|
color: this.color,
|
|
264
263
|
textAlignment: this.textAlignment,
|
|
265
|
-
maxLines: this.maxLines,
|
|
264
|
+
maxLines: piece.maxLines ?? this.maxLines,
|
|
266
265
|
orphans: this.orphans,
|
|
267
266
|
widows: this.widows,
|
|
268
267
|
overflow: this.overflow,
|
|
@@ -275,46 +274,92 @@ export class TextElement extends SizedPDFElement {
|
|
|
275
274
|
wordSpacing: this.wordSpacing,
|
|
276
275
|
textIndent: indent,
|
|
277
276
|
role: this.role,
|
|
278
|
-
|
|
277
|
+
fontFallback: this.fontFallback,
|
|
278
|
+
// Already recased: `capitalize` again would capitalise the half of a word continued on the next
|
|
279
|
+
// page.
|
|
280
|
+
textTransform: "none",
|
|
281
|
+
breakWord: this.breakWord,
|
|
282
|
+
hyphenate: this.hyphenate,
|
|
283
|
+
ligatures: this.ligatures,
|
|
284
|
+
shownLines: piece.shownLines,
|
|
285
|
+
}).asPieceOf(this); // a wrapped remainder is the SAME logical paragraph (one P across pages)
|
|
279
286
|
}
|
|
280
287
|
calculateLayout(constraints, offset, ctx) {
|
|
281
|
-
this.
|
|
288
|
+
const { size, lines } = this.sizeWith(constraints, ctx);
|
|
282
289
|
this.x = offset.x;
|
|
283
290
|
this.y = offset.y;
|
|
284
|
-
// Bounded width (e.g. inside a Column) wraps to that width; an unbounded width
|
|
285
|
-
// (e.g. inside a Row) means the text takes its natural single-line width and does
|
|
286
|
-
// not wrap. Columns always bound the width, so this leaves their layout untouched.
|
|
287
291
|
this.resolved = this.display(ctx.metrics);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
this.height =
|
|
292
|
+
const lost = [...new Set([...this.lostBefore, ...this.lostGlyphs])];
|
|
293
|
+
if (lost.length > 0)
|
|
294
|
+
ctx.metrics.reportMissingGlyph?.(lost, this, pageOf(ctx));
|
|
295
|
+
this.width = size.width;
|
|
296
|
+
this.height = size.height;
|
|
297
|
+
// A line with a piece that could not break is drawn past the edge - say so.
|
|
298
|
+
const overrun = Math.max(0, ...lines.map((line) => line.overrun));
|
|
299
|
+
if (constraints.hasBoundedWidth && overrun > 0.01) {
|
|
300
|
+
diagnose(ctx.diagnostics, {
|
|
301
|
+
code: "overflow-wide",
|
|
302
|
+
severity: "warning",
|
|
303
|
+
message: `A line of this Text runs ${Math.round(overrun)}pt past its ${Math.round(size.width)}pt; ` +
|
|
304
|
+
"a word in it cannot break. Let it break (`breakWord`, `hyphenate`), make room, or clip " +
|
|
305
|
+
'with a Box\'s `overflow: "hidden"`.',
|
|
306
|
+
about: this,
|
|
307
|
+
page: pageOf(ctx),
|
|
308
|
+
fallback: "silent",
|
|
309
|
+
});
|
|
310
|
+
}
|
|
293
311
|
// Top-left coordinates (y = top of the text box). The baseline offset and the
|
|
294
312
|
// Y-flip are applied downstream (the line-builder positions baselines, the seam
|
|
295
313
|
// flips to PDF), so the element stays coordinate-system-blind.
|
|
296
|
-
return
|
|
314
|
+
return size;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* The size `calculateLayout` would take, without placing anything. Resolving the inherited style
|
|
318
|
+
* and filling the memos below are its only writes, and both are a pure function of the context:
|
|
319
|
+
* the same inputs leave the same state, which a later layout reads back unchanged.
|
|
320
|
+
*/
|
|
321
|
+
/** A text's size reads only the width it may take: it wraps to it, and a height bound never changes
|
|
322
|
+
* the lines (a box that is too short cuts them, it does not reflow them). */
|
|
323
|
+
sizeInputs(constraints) {
|
|
324
|
+
return BoxConstraints.loose(constraints.maxWidth, Infinity);
|
|
325
|
+
}
|
|
326
|
+
computeSize(constraints, ctx) {
|
|
327
|
+
return this.sizeWith(constraints, ctx).size;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* The one sizing path, for measuring and layout alike: a bounded width wraps to it (in a Column), an
|
|
331
|
+
* unbounded one takes the natural single-line width (in a Row). The lines are what layout checks.
|
|
332
|
+
* The style is resolved here, as drawing reads it.
|
|
333
|
+
*/
|
|
334
|
+
/** The text broken at `width`: once per context and width, for measuring, layout and splitting. */
|
|
335
|
+
linesAt(width, ctx) {
|
|
336
|
+
return this.broken.get(ctx, width, () => breakSegmentsIntoLines(this.display(ctx.metrics), this.segmentDefaults(), width, ctx.metrics, this.maxLines, this.overflow));
|
|
337
|
+
}
|
|
338
|
+
sizeWith(constraints, ctx) {
|
|
339
|
+
this.resolveStyle(ctx);
|
|
340
|
+
const width = constraints.hasBoundedWidth
|
|
341
|
+
? constraints.maxWidth
|
|
342
|
+
: this.naturalWidth(ctx.metrics);
|
|
343
|
+
const defaults = this.segmentDefaults();
|
|
344
|
+
const lines = this.linesAt(width, ctx);
|
|
345
|
+
return {
|
|
346
|
+
size: { width, height: linesHeight(lines, defaults, ctx.metrics, this.lineHeight) },
|
|
347
|
+
lines,
|
|
348
|
+
};
|
|
297
349
|
}
|
|
298
350
|
/** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
|
|
299
|
-
* Must match the
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* into one term; we must group the same way. Adding word and space as two separate steps is
|
|
303
|
-
* algebraically equal but, because floating-point addition is not associative, drifts by a sub-ULP
|
|
304
|
-
* - enough to tip a borderline string (e.g. "20 Jun 2026", wider than "04 Jul 2026" only because
|
|
305
|
-
* 'n' beats 'l') one bit over its own width, dropping the last word onto a second line. */
|
|
351
|
+
* Must match the breaker's one-line sum BIT for bit - it adds `width + (space + word)`, and the same
|
|
352
|
+
* numbers grouped differently drift by a sub-ULP, enough to wrap a text inside its own natural-width
|
|
353
|
+
* box. `singleLineWidth` is that sum. */
|
|
306
354
|
naturalWidth(metrics) {
|
|
307
|
-
|
|
308
|
-
const oneLine = (text, family, size, style, letterSpacing) => singleLineWidth(text, { fontFamily: family, fontSize: size, fontStyle: style }, metrics, letterSpacing, this.wordSpacing);
|
|
355
|
+
const defaults = this.segmentDefaults();
|
|
309
356
|
// The FIRST line starts `textIndent` in, so an unbounded box has to be that much wider.
|
|
310
357
|
const indent = Math.max(0, this.textIndent);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
return content.reduce((sum, seg) => sum +
|
|
317
|
-
oneLine(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontSize ?? this.fontSize, seg.fontStyle ?? this.fontStyle, seg.letterSpacing ?? this.letterSpacing), indent);
|
|
358
|
+
return this.display(metrics).reduce((sum, seg) => {
|
|
359
|
+
// The breaker's own sum, to the last bit - see `singleLineWidth`.
|
|
360
|
+
const run = runStyleOf(seg, defaults);
|
|
361
|
+
return (sum + singleLineWidth(seg.content, run.font, metrics, run.letterSpacing, run.wordSpacing));
|
|
362
|
+
}, indent);
|
|
318
363
|
}
|
|
319
364
|
/**
|
|
320
365
|
* The widest piece of this text that cannot be broken - CSS's `min-content` width. A shrinking flex
|
|
@@ -331,27 +376,22 @@ export class TextElement extends SizedPDFElement {
|
|
|
331
376
|
return 0;
|
|
332
377
|
this.resolveStyle(ctx);
|
|
333
378
|
const metrics = ctx.metrics;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
// Split on the same boundaries the breaker uses - a hard break ends a line too.
|
|
344
|
-
const pieces = this.breakWord || this.hyphenate ? [...text] : text.split(/[ \n]/);
|
|
345
|
-
return pieces.reduce((max, piece) => Math.max(max, runAdvance(metrics, piece, font, letterSpacing)), 0);
|
|
379
|
+
// The run style is the FONT a piece is measured in: `ligatures` decides which glyphs are drawn and
|
|
380
|
+
// therefore how wide it is. Leaving it out would measure a floor the drawing never matches.
|
|
381
|
+
const widest = (text, run) => {
|
|
382
|
+
// Split on the same boundaries the breaker uses - a hard break ends a line too, and so may a
|
|
383
|
+
// hyphen inside a word. A run that may split its words may split them anywhere.
|
|
384
|
+
const pieces = run.splitting.breakWord || run.splitting.hyphenate
|
|
385
|
+
? [...text]
|
|
386
|
+
: text.split(/[ \n]/).flatMap(hyphenPieces);
|
|
387
|
+
return pieces.reduce((max, piece) => Math.max(max, runAdvance(metrics, piece, run.font, run.letterSpacing)), 0);
|
|
346
388
|
};
|
|
389
|
+
const defaults = this.segmentDefaults();
|
|
347
390
|
const content = this.display(metrics);
|
|
348
|
-
if (typeof content === "string") {
|
|
349
|
-
return widest(content, this.fontFamily, this.fontSize, this.fontStyle, this.letterSpacing);
|
|
350
|
-
}
|
|
351
391
|
// Segments join without a space, so a word may straddle two of them; taking the widest piece per
|
|
352
392
|
// segment can therefore UNDER-estimate. That errs towards squeezing a little too far, which lands
|
|
353
393
|
// back on the old overflow behaviour rather than on something new.
|
|
354
|
-
return content.reduce((max, seg) => Math.max(max, widest(seg.content, seg
|
|
394
|
+
return content.reduce((max, seg) => Math.max(max, widest(seg.content, runStyleOf(seg, defaults))), 0);
|
|
355
395
|
}
|
|
356
396
|
getProps() {
|
|
357
397
|
return {
|
|
@@ -363,7 +403,11 @@ export class TextElement extends SizedPDFElement {
|
|
|
363
403
|
fontFamily: this.fontFamily,
|
|
364
404
|
fontStyle: this.fontStyle,
|
|
365
405
|
color: this.color,
|
|
366
|
-
content: this.
|
|
406
|
+
content: this.content,
|
|
407
|
+
/** The content as it is measured and drawn: spans, recased, split by font, drawable glyphs only. */
|
|
408
|
+
spans: this.resolved ?? this.display(),
|
|
409
|
+
/** How the text is broken - the settings it is measured with, so drawing breaks it the same. */
|
|
410
|
+
breaking: this.segmentDefaults(),
|
|
367
411
|
textAlignment: this.textAlignment,
|
|
368
412
|
maxLines: this.maxLines,
|
|
369
413
|
orphans: this.orphans,
|
|
@@ -381,6 +425,7 @@ export class TextElement extends SizedPDFElement {
|
|
|
381
425
|
ligatures: this.ligatures,
|
|
382
426
|
hyphenate: this.hyphenate,
|
|
383
427
|
role: this.role,
|
|
428
|
+
shownLines: this.shownLines,
|
|
384
429
|
};
|
|
385
430
|
}
|
|
386
431
|
}
|
package/dist/forms/acroform.d.ts
CHANGED
|
@@ -19,6 +19,14 @@ export declare class AcroFormCollector {
|
|
|
19
19
|
/** The size to draw a value at. A field may ask for `0` - the PDF convention for "auto-size"; we then
|
|
20
20
|
* fit the capitals comfortably inside the box, which is what a viewer's auto-size does. */
|
|
21
21
|
private drawSize;
|
|
22
|
+
/**
|
|
23
|
+
* The font a field's baked appearance draws its text in: the font the field inherits when it is
|
|
24
|
+
* EMBEDDED, else the standard Helvetica. PDF/A and PDF/UA require every font that draws to be
|
|
25
|
+
* embedded, and a document that embeds its fonts means that. The /DA keeps naming Helvetica - it is
|
|
26
|
+
* what a viewer redraws with once someone types, and a subset font could not draw the new characters.
|
|
27
|
+
* Measuring and drawing both take the font from here.
|
|
28
|
+
*/
|
|
29
|
+
private faceFont;
|
|
22
30
|
/** Measure one line for the appearance generator. */
|
|
23
31
|
private line;
|
|
24
32
|
/** Bake a text field's value into an appearance stream; returns the XObject number. */
|
|
@@ -47,7 +55,7 @@ export declare class AcroFormCollector {
|
|
|
47
55
|
private claimName;
|
|
48
56
|
/** Emit the widget object for one form-field IR node, register it as a field, and return its object
|
|
49
57
|
* number so the PageRenderer can add it to that page's /Annots. */
|
|
50
|
-
addField(node: FormFieldNode, om: PDFObjectManager): number;
|
|
58
|
+
addField(node: FormFieldNode, om: PDFObjectManager, structParent?: number): number;
|
|
51
59
|
/** A push button (/Btn, Pushbutton flag): no value, an optional action, and a baked caption. The
|
|
52
60
|
* caption is measured here (the appearance module stays free of font metrics) and drawn into an
|
|
53
61
|
* appearance stream that carries its own font resource. */
|