@jasy/pdf 1.0.0-alpha.7 → 1.0.0-alpha.9
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 +11 -10
- package/dist/api/content.d.ts +5 -4
- package/dist/api/content.js +13 -7
- package/dist/api/descriptor.js +23 -0
- package/dist/api/dimension.d.ts +56 -0
- package/dist/api/dimension.js +27 -0
- package/dist/api/font-url.d.ts +28 -0
- package/dist/api/font-url.js +134 -0
- package/dist/api/forms.d.ts +347 -0
- package/dist/api/forms.js +189 -0
- package/dist/api/gradient.d.ts +53 -0
- package/dist/api/gradient.js +97 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.js +3 -0
- package/dist/api/insets.d.ts +19 -12
- package/dist/api/insets.js +18 -7
- package/dist/api/layout.d.ts +20 -6
- package/dist/api/layout.js +31 -6
- package/dist/api/page-builder.d.ts +8 -0
- package/dist/api/page-builder.js +12 -0
- package/dist/api/structure.d.ts +11 -0
- package/dist/api/structure.js +17 -4
- package/dist/api/text.d.ts +42 -6
- package/dist/api/text.js +29 -5
- package/dist/crypto/legacy-handler.d.ts +43 -0
- package/dist/crypto/legacy-handler.js +145 -0
- package/dist/crypto/security-handler.d.ts +16 -0
- package/dist/crypto/security-handler.js +26 -0
- package/dist/edit/acroform-reader.d.ts +70 -0
- package/dist/edit/acroform-reader.js +126 -0
- package/dist/edit/appearance.d.ts +47 -0
- package/dist/edit/appearance.js +171 -0
- package/dist/edit/document.d.ts +145 -0
- package/dist/edit/document.js +642 -0
- package/dist/edit/fill.d.ts +65 -0
- package/dist/edit/fill.js +424 -0
- package/dist/edit/flatten.d.ts +54 -0
- package/dist/edit/flatten.js +462 -0
- package/dist/edit/index.d.ts +20 -0
- package/dist/edit/index.js +18 -0
- package/dist/edit/lexer.d.ts +30 -0
- package/dist/edit/lexer.js +285 -0
- package/dist/edit/objects.d.ts +68 -0
- package/dist/edit/objects.js +83 -0
- package/dist/edit/writer.d.ts +60 -0
- package/dist/edit/writer.js +283 -0
- package/dist/elements/container-element.d.ts +21 -1
- package/dist/elements/container-element.js +25 -10
- package/dist/elements/forms/checkbox-element.d.ts +51 -0
- package/dist/elements/forms/checkbox-element.js +48 -0
- package/dist/elements/forms/choice-element.d.ts +53 -0
- package/dist/elements/forms/choice-element.js +59 -0
- package/dist/elements/forms/push-button-element.d.ts +56 -0
- package/dist/elements/forms/push-button-element.js +49 -0
- package/dist/elements/forms/radio-element.d.ts +52 -0
- package/dist/elements/forms/radio-element.js +49 -0
- package/dist/elements/forms/signature-element.d.ts +54 -0
- package/dist/elements/forms/signature-element.js +47 -0
- package/dist/elements/forms/text-field-element.d.ts +61 -0
- package/dist/elements/forms/text-field-element.js +54 -0
- package/dist/elements/image-element.d.ts +18 -4
- package/dist/elements/image-element.js +27 -12
- package/dist/elements/index.d.ts +6 -0
- package/dist/elements/index.js +6 -0
- package/dist/elements/layout/expanded-element.d.ts +3 -1
- package/dist/elements/layout/expanded-element.js +12 -3
- package/dist/elements/layout/padding-element.d.ts +4 -2
- package/dist/elements/layout/padding-element.js +4 -2
- package/dist/elements/layout/page-builder-element.js +2 -0
- package/dist/elements/pdf-element.d.ts +59 -1
- package/dist/elements/pdf-element.js +62 -1
- package/dist/elements/rectangle-element.d.ts +20 -6
- package/dist/elements/rectangle-element.js +25 -12
- package/dist/elements/row-element.d.ts +24 -1
- package/dist/elements/row-element.js +25 -10
- package/dist/elements/text-element.d.ts +60 -1
- package/dist/elements/text-element.js +132 -33
- package/dist/forms/acroform.d.ts +63 -0
- package/dist/forms/acroform.js +427 -0
- package/dist/forms/appearance.d.ts +58 -0
- package/dist/forms/appearance.js +204 -0
- package/dist/forms/field.d.ts +164 -0
- package/dist/forms/field.js +1 -0
- package/dist/forms/pdf.d.ts +19 -0
- package/dist/forms/pdf.js +28 -0
- package/dist/ir/display-list.d.ts +40 -7
- package/dist/ir/display-list.js +8 -1
- package/dist/layout/alignment.d.ts +10 -0
- package/dist/layout/alignment.js +6 -0
- package/dist/layout/box-constraints.d.ts +53 -0
- package/dist/layout/box-constraints.js +72 -0
- package/dist/layout/insets.d.ts +28 -0
- package/dist/layout/insets.js +20 -0
- package/dist/renderer/form-field-renderer.d.ts +20 -0
- package/dist/renderer/form-field-renderer.js +12 -0
- package/dist/renderer/image-renderer.js +2 -1
- package/dist/renderer/page-renderer.js +8 -2
- package/dist/renderer/pdf-backend.d.ts +6 -0
- package/dist/renderer/pdf-backend.js +86 -31
- package/dist/renderer/pdf-document-renderer.js +8 -1
- package/dist/renderer/pdf-renderer.js +19 -1
- package/dist/renderer/rectangle-renderer.js +8 -2
- package/dist/renderer/text-renderer.d.ts +2 -2
- package/dist/renderer/text-renderer.js +150 -42
- package/dist/text/advance.js +5 -2
- package/dist/text/arabic.d.ts +20 -0
- package/dist/text/arabic.js +101 -0
- package/dist/text/bidi.d.ts +33 -0
- package/dist/text/bidi.js +94 -0
- package/dist/text/font-fallback.d.ts +25 -0
- package/dist/text/font-fallback.js +38 -0
- package/dist/text/line-breaker.d.ts +35 -1
- package/dist/text/line-breaker.js +76 -16
- package/dist/text/orphans-widows.d.ts +27 -0
- package/dist/text/orphans-widows.js +53 -0
- package/dist/text/shape.d.ts +29 -0
- package/dist/text/shape.js +67 -0
- package/dist/text/text-style.d.ts +25 -0
- package/dist/text/text-style.js +44 -1
- package/dist/utils/dest-registry.d.ts +5 -2
- package/dist/utils/dest-registry.js +5 -5
- package/dist/utils/flex-layout.d.ts +24 -4
- package/dist/utils/flex-layout.js +169 -7
- package/dist/utils/font-metrics.d.ts +5 -0
- package/dist/utils/gsub.d.ts +36 -0
- package/dist/utils/gsub.js +178 -0
- package/dist/utils/inflate.d.ts +16 -0
- package/dist/utils/inflate.js +52 -0
- package/dist/utils/outline.js +1 -2
- package/dist/utils/pdf-object-manager.d.ts +39 -0
- package/dist/utils/pdf-object-manager.js +166 -15
- package/dist/utils/struct-tree.js +2 -3
- package/dist/utils/ttf-parser.d.ts +6 -0
- package/dist/utils/ttf-parser.js +19 -0
- package/dist/utils/utf8-to-windows1252-encoder.d.ts +6 -0
- package/dist/utils/utf8-to-windows1252-encoder.js +10 -0
- package/dist/utils/woff.d.ts +10 -0
- package/dist/utils/woff.js +116 -0
- package/dist/validators/element-validator.js +14 -3
- package/package.json +10 -2
|
@@ -4,12 +4,31 @@
|
|
|
4
4
|
// breaks the constructor-keyed RendererRegistry. A direct import keeps the graph acyclic.
|
|
5
5
|
import { TextRenderer } from "../renderer/text-renderer.js";
|
|
6
6
|
import { DEFAULT_TEXT_STYLE } from "../text/text-style.js";
|
|
7
|
-
import {
|
|
7
|
+
import { applyTextTransform } from "../text/text-style.js";
|
|
8
|
+
import { splitByFont } from "../text/font-fallback.js";
|
|
9
|
+
import { singleLineWidth, MAX_SPACE_SHRINK, wrapStringIntoLines, breakSegmentsIntoLines, segmentLinesToSegments, } from "../text/line-breaker.js";
|
|
10
|
+
import { adjustForOrphansWidows, DEFAULT_ORPHANS, DEFAULT_WIDOWS, } from "../text/orphans-widows.js";
|
|
8
11
|
import { lineBoxForSegmentLine, lineBoxForString } from "../text/line-metrics.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
12
|
+
import { HorizontalAlignment, SizedPDFElement } from "./pdf-element.js";
|
|
13
|
+
/** How far a run sits off the baseline, as a fraction of its font size (up is positive). */
|
|
14
|
+
export const VERTICAL_TEXT_SHIFT = {
|
|
15
|
+
baseline: 0,
|
|
16
|
+
super: 1 / 3,
|
|
17
|
+
sub: -1 / 5,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A measurement in points has to be a real number. A NaN or an Infinity would travel all the way into
|
|
21
|
+
* the content stream as a position - the backend refuses it there, but by then the message names a
|
|
22
|
+
* coordinate rather than the property that was wrong. Rejected here, where the caller can see it.
|
|
23
|
+
*/
|
|
24
|
+
function finitePoints(value, name) {
|
|
25
|
+
if (value !== undefined && !Number.isFinite(value)) {
|
|
26
|
+
throw new Error(`@jasy/pdf: Invalid ${name} ${value}: it must be a finite number of points.`);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
11
30
|
export class TextElement extends SizedPDFElement {
|
|
12
|
-
constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, overflow = "clip", lineHeight, underline, strikethrough, skipInk, letterSpacing, role, }) {
|
|
31
|
+
constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow = "clip", lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, role, }) {
|
|
13
32
|
super({ x: 0, y: 0 });
|
|
14
33
|
this.role = role;
|
|
15
34
|
this.rawFontSize = fontSize;
|
|
@@ -22,8 +41,15 @@ export class TextElement extends SizedPDFElement {
|
|
|
22
41
|
this.rawStrikethrough = strikethrough;
|
|
23
42
|
this.rawSkipInk = skipInk;
|
|
24
43
|
this.rawLetterSpacing = letterSpacing;
|
|
44
|
+
this.rawDirection = direction;
|
|
45
|
+
this.rawFontFallback = fontFallback;
|
|
46
|
+
this.rawTextTransform = textTransform;
|
|
47
|
+
this.rawWordSpacing = finitePoints(wordSpacing, "wordSpacing");
|
|
48
|
+
this.rawTextIndent = finitePoints(textIndent, "textIndent");
|
|
25
49
|
this.content = content;
|
|
26
50
|
this.maxLines = maxLines;
|
|
51
|
+
this.orphans = orphans;
|
|
52
|
+
this.widows = widows;
|
|
27
53
|
this.overflow = overflow;
|
|
28
54
|
this.applyStyle(DEFAULT_TEXT_STYLE);
|
|
29
55
|
}
|
|
@@ -31,6 +57,57 @@ export class TextElement extends SizedPDFElement {
|
|
|
31
57
|
resolveStyle(ctx) {
|
|
32
58
|
this.applyStyle(ctx.textStyle ?? DEFAULT_TEXT_STYLE);
|
|
33
59
|
}
|
|
60
|
+
/** Justified lines may be squeezed to keep a word; every other alignment gets no slack. Read by the
|
|
61
|
+
* breaker in BOTH passes, or a measured line and a drawn one would disagree about where it ends. */
|
|
62
|
+
/**
|
|
63
|
+
* The content as it is measured AND drawn - `text-transform` applied. One place, so a recased
|
|
64
|
+
* paragraph can never be measured in one casing and drawn in another (`ABC` and `abc` are not the
|
|
65
|
+
* same width in most fonts).
|
|
66
|
+
*/
|
|
67
|
+
display(metrics) {
|
|
68
|
+
const cased = this.recased();
|
|
69
|
+
// A SPAN may bring its own stack, so the element's being empty is not enough to skip the pass.
|
|
70
|
+
const anyStack = this.fontFallback.length > 0 ||
|
|
71
|
+
(typeof cased !== "string" && cased.some((seg) => (seg.fontFallback?.length ?? 0) > 0));
|
|
72
|
+
if (!anyStack || !metrics)
|
|
73
|
+
return cased;
|
|
74
|
+
// Font fallback turns the content into spans - one per family - which every later pass already
|
|
75
|
+
// knows how to handle. Nothing new downstream.
|
|
76
|
+
const pieces = typeof cased === "string" ? [{ content: cased }] : cased;
|
|
77
|
+
const out = [];
|
|
78
|
+
for (const seg of pieces) {
|
|
79
|
+
const runs = splitByFont(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontFallback ?? this.fontFallback, seg.fontStyle ?? this.fontStyle, metrics);
|
|
80
|
+
if (!runs)
|
|
81
|
+
out.push(seg);
|
|
82
|
+
else
|
|
83
|
+
for (const run of runs)
|
|
84
|
+
out.push({ ...seg, content: run.text, fontFamily: run.fontFamily });
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
/** The content with `text-transform` applied, and nothing else. */
|
|
89
|
+
recased() {
|
|
90
|
+
if (this.textTransform === "none")
|
|
91
|
+
return this.content;
|
|
92
|
+
if (typeof this.content === "string") {
|
|
93
|
+
return applyTextTransform(this.content, this.textTransform).text;
|
|
94
|
+
}
|
|
95
|
+
// The capitalisation state runs THROUGH the spans: `span("hel") + span("lo")` is one word.
|
|
96
|
+
let atWordStart = true;
|
|
97
|
+
return this.content.map((seg) => {
|
|
98
|
+
const done = applyTextTransform(seg.content, this.textTransform, atWordStart);
|
|
99
|
+
atWordStart = done.atWordStart;
|
|
100
|
+
return { ...seg, content: done.text };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
lineOptions() {
|
|
104
|
+
return {
|
|
105
|
+
wordSpacing: this.wordSpacing,
|
|
106
|
+
indent: this.textIndent,
|
|
107
|
+
// Justified lines may be squeezed to keep a word; every other alignment gets no slack.
|
|
108
|
+
shrink: this.textAlignment === HorizontalAlignment.justify ? MAX_SPACE_SHRINK : 0,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
34
111
|
applyStyle(ts) {
|
|
35
112
|
this.fontSize = this.rawFontSize ?? ts.fontSize;
|
|
36
113
|
this.fontFamily = this.rawFontFamily ?? ts.fontFamily;
|
|
@@ -42,6 +119,11 @@ export class TextElement extends SizedPDFElement {
|
|
|
42
119
|
this.strikethrough = this.rawStrikethrough ?? ts.strikethrough;
|
|
43
120
|
this.skipInk = this.rawSkipInk ?? ts.skipInk;
|
|
44
121
|
this.letterSpacing = this.rawLetterSpacing ?? ts.letterSpacing;
|
|
122
|
+
this.direction = this.rawDirection ?? ts.direction;
|
|
123
|
+
this.fontFallback = this.rawFontFallback ?? ts.fontFallback;
|
|
124
|
+
this.textTransform = this.rawTextTransform ?? ts.textTransform;
|
|
125
|
+
this.wordSpacing = this.rawWordSpacing ?? ts.wordSpacing;
|
|
126
|
+
this.textIndent = this.rawTextIndent ?? ts.textIndent;
|
|
45
127
|
}
|
|
46
128
|
/**
|
|
47
129
|
* Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
|
|
@@ -51,23 +133,25 @@ export class TextElement extends SizedPDFElement {
|
|
|
51
133
|
*/
|
|
52
134
|
fragment(maxHeight, width, ctx) {
|
|
53
135
|
this.resolveStyle(ctx);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
136
|
+
const content = this.display(ctx.metrics);
|
|
137
|
+
return typeof content === "string"
|
|
138
|
+
? this.fragmentString(content, maxHeight, width, ctx)
|
|
139
|
+
: this.fragmentSegments(content, maxHeight, width, ctx);
|
|
57
140
|
}
|
|
58
141
|
// Plain string: every wrapped line gets the same box (the same one calculateTextHeight uses),
|
|
59
142
|
// so `floor(maxHeight / lineBox)` lines fit.
|
|
60
143
|
fragmentString(content, maxHeight, width, ctx) {
|
|
61
|
-
const lines = wrapStringIntoLines(content, this.fontFamily, this.fontSize, this.fontStyle, width, ctx.metrics, this.maxLines, this.overflow, this.letterSpacing);
|
|
144
|
+
const lines = wrapStringIntoLines(content, this.fontFamily, this.fontSize, this.fontStyle, width, ctx.metrics, this.maxLines, this.overflow, this.letterSpacing, this.lineOptions());
|
|
62
145
|
const box = lineBoxForString(ctx.metrics, this.fontFamily, this.fontStyle, this.fontSize, this.lineHeight);
|
|
63
|
-
const fittedLineCount = Math.floor(maxHeight / box.height);
|
|
146
|
+
const fittedLineCount = adjustForOrphansWidows(Math.floor(maxHeight / box.height), lines.length, this.breakRule());
|
|
64
147
|
if (fittedLineCount <= 0)
|
|
65
148
|
return { fitted: null, remainder: this };
|
|
66
149
|
if (fittedLineCount >= lines.length)
|
|
67
150
|
return { fitted: this, remainder: null };
|
|
68
151
|
return {
|
|
69
152
|
fitted: this.cloneWithContent(lines.slice(0, fittedLineCount).join(" ")),
|
|
70
|
-
|
|
153
|
+
// A continuation is no longer the paragraph's FIRST line, so it carries no indent.
|
|
154
|
+
remainder: this.cloneWithContent(lines.slice(fittedLineCount).join(" "), 0),
|
|
71
155
|
};
|
|
72
156
|
}
|
|
73
157
|
// Styled segments: each line's height comes from the fonts on it, so pack lines by cumulative
|
|
@@ -78,29 +162,40 @@ export class TextElement extends SizedPDFElement {
|
|
|
78
162
|
fontSize: this.fontSize,
|
|
79
163
|
fontStyle: this.fontStyle,
|
|
80
164
|
letterSpacing: this.letterSpacing,
|
|
165
|
+
direction: this.direction,
|
|
166
|
+
wordSpacing: this.wordSpacing,
|
|
167
|
+
textIndent: this.textIndent,
|
|
81
168
|
};
|
|
82
169
|
const lines = breakSegmentsIntoLines(content, defaults, width, ctx.metrics, this.maxLines, this.overflow);
|
|
83
170
|
let used = 0;
|
|
84
|
-
let
|
|
171
|
+
let packed = 0;
|
|
85
172
|
for (const line of lines) {
|
|
86
173
|
const lineBox = lineBoxForSegmentLine(line, defaults, ctx.metrics, this.lineHeight).height;
|
|
87
174
|
if (used + lineBox > maxHeight)
|
|
88
175
|
break;
|
|
89
176
|
used += lineBox;
|
|
90
|
-
|
|
177
|
+
packed++;
|
|
91
178
|
}
|
|
179
|
+
const fittedLineCount = adjustForOrphansWidows(packed, lines.length, this.breakRule());
|
|
92
180
|
if (fittedLineCount <= 0)
|
|
93
181
|
return { fitted: null, remainder: this };
|
|
94
182
|
if (fittedLineCount >= lines.length)
|
|
95
183
|
return { fitted: this, remainder: null };
|
|
96
184
|
return {
|
|
97
185
|
fitted: this.cloneWithContent(segmentLinesToSegments(lines.slice(0, fittedLineCount))),
|
|
98
|
-
remainder: this.cloneWithContent(segmentLinesToSegments(lines.slice(fittedLineCount))),
|
|
186
|
+
remainder: this.cloneWithContent(segmentLinesToSegments(lines.slice(fittedLineCount)), 0),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/** The orphan/widow minimums for this paragraph; unset means the CSS default of 2 lines each. */
|
|
190
|
+
breakRule() {
|
|
191
|
+
return {
|
|
192
|
+
orphans: this.orphans ?? DEFAULT_ORPHANS,
|
|
193
|
+
widows: this.widows ?? DEFAULT_WIDOWS,
|
|
99
194
|
};
|
|
100
195
|
}
|
|
101
196
|
// A copy carrying the same style but different (already-wrapped) content. Re-wrapping at
|
|
102
197
|
// the same width reproduces exactly those lines (greedy is deterministic).
|
|
103
|
-
cloneWithContent(content) {
|
|
198
|
+
cloneWithContent(content, indent = this.textIndent) {
|
|
104
199
|
return new TextElement({
|
|
105
200
|
content,
|
|
106
201
|
fontSize: this.fontSize,
|
|
@@ -109,12 +204,17 @@ export class TextElement extends SizedPDFElement {
|
|
|
109
204
|
color: this.color,
|
|
110
205
|
textAlignment: this.textAlignment,
|
|
111
206
|
maxLines: this.maxLines,
|
|
207
|
+
orphans: this.orphans,
|
|
208
|
+
widows: this.widows,
|
|
112
209
|
overflow: this.overflow,
|
|
113
210
|
lineHeight: this.lineHeight,
|
|
114
211
|
underline: this.underline,
|
|
115
212
|
strikethrough: this.strikethrough,
|
|
116
213
|
skipInk: this.skipInk,
|
|
117
214
|
letterSpacing: this.letterSpacing,
|
|
215
|
+
direction: this.direction,
|
|
216
|
+
wordSpacing: this.wordSpacing,
|
|
217
|
+
textIndent: indent,
|
|
118
218
|
role: this.role,
|
|
119
219
|
}).adoptStructId(this); // a wrapped remainder is the SAME logical paragraph (one P across pages)
|
|
120
220
|
}
|
|
@@ -125,11 +225,12 @@ export class TextElement extends SizedPDFElement {
|
|
|
125
225
|
// Bounded width (e.g. inside a Column) wraps to that width; an unbounded width
|
|
126
226
|
// (e.g. inside a Row) means the text takes its natural single-line width and does
|
|
127
227
|
// not wrap. Columns always bound the width, so this leaves their layout untouched.
|
|
228
|
+
this.resolved = this.display(ctx.metrics);
|
|
128
229
|
this.width = constraints.hasBoundedWidth
|
|
129
230
|
? constraints.maxWidth
|
|
130
231
|
: this.naturalWidth(ctx.metrics);
|
|
131
232
|
const wrapWidth = this.width ?? 0;
|
|
132
|
-
this.height = TextRenderer.calculateTextHeight(this.
|
|
233
|
+
this.height = TextRenderer.calculateTextHeight(this.resolved, this.fontSize, this.fontFamily, this.fontStyle, ctx.metrics, wrapWidth, this.maxLines, this.overflow, this.lineHeight, this.letterSpacing, this.lineOptions());
|
|
133
234
|
// Top-left coordinates (y = top of the text box). The baseline offset and the
|
|
134
235
|
// Y-flip are applied downstream (the line-builder positions baselines, the seam
|
|
135
236
|
// flips to PDF), so the element stays coordinate-system-blind.
|
|
@@ -144,24 +245,17 @@ export class TextElement extends SizedPDFElement {
|
|
|
144
245
|
* - enough to tip a borderline string (e.g. "20 Jun 2026", wider than "04 Jul 2026" only because
|
|
145
246
|
* 'n' beats 'l') one bit over its own width, dropping the last word onto a second line. */
|
|
146
247
|
naturalWidth(metrics) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// the same `runAdvance`, so the two agree bit for bit even with letterSpacing.
|
|
156
|
-
width += i < words.length - 1 ? w + space : w;
|
|
157
|
-
});
|
|
158
|
-
return width;
|
|
159
|
-
};
|
|
160
|
-
if (typeof this.content === "string") {
|
|
161
|
-
return oneLine(this.content, this.fontFamily, this.fontSize, this.fontStyle, this.letterSpacing);
|
|
248
|
+
// The breaker's own sum, to the last bit - see `singleLineWidth`.
|
|
249
|
+
const oneLine = (text, family, size, style, letterSpacing) => singleLineWidth(text, { fontFamily: family, fontSize: size, fontStyle: style }, metrics, letterSpacing, this.wordSpacing);
|
|
250
|
+
// The FIRST line starts `textIndent` in, so an unbounded box has to be that much wider.
|
|
251
|
+
const indent = Math.max(0, this.textIndent);
|
|
252
|
+
const content = this.display(metrics);
|
|
253
|
+
if (typeof content === "string") {
|
|
254
|
+
return (indent +
|
|
255
|
+
oneLine(content, this.fontFamily, this.fontSize, this.fontStyle, this.letterSpacing));
|
|
162
256
|
}
|
|
163
|
-
return
|
|
164
|
-
oneLine(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontSize ?? this.fontSize, seg.fontStyle ?? this.fontStyle, seg.letterSpacing ?? this.letterSpacing),
|
|
257
|
+
return content.reduce((sum, seg) => sum +
|
|
258
|
+
oneLine(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontSize ?? this.fontSize, seg.fontStyle ?? this.fontStyle, seg.letterSpacing ?? this.letterSpacing), indent);
|
|
165
259
|
}
|
|
166
260
|
getProps() {
|
|
167
261
|
return {
|
|
@@ -173,15 +267,20 @@ export class TextElement extends SizedPDFElement {
|
|
|
173
267
|
fontFamily: this.fontFamily,
|
|
174
268
|
fontStyle: this.fontStyle,
|
|
175
269
|
color: this.color,
|
|
176
|
-
content: this.content,
|
|
270
|
+
content: this.resolved ?? this.content,
|
|
177
271
|
textAlignment: this.textAlignment,
|
|
178
272
|
maxLines: this.maxLines,
|
|
273
|
+
orphans: this.orphans,
|
|
274
|
+
widows: this.widows,
|
|
179
275
|
overflow: this.overflow,
|
|
180
276
|
lineHeight: this.lineHeight,
|
|
181
277
|
underline: this.underline,
|
|
182
278
|
strikethrough: this.strikethrough,
|
|
183
279
|
skipInk: this.skipInk,
|
|
184
280
|
letterSpacing: this.letterSpacing,
|
|
281
|
+
direction: this.direction,
|
|
282
|
+
wordSpacing: this.wordSpacing,
|
|
283
|
+
textIndent: this.textIndent,
|
|
185
284
|
role: this.role,
|
|
186
285
|
};
|
|
187
286
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PDFObjectManager } from "../utils/pdf-object-manager.js";
|
|
2
|
+
import type { FormFieldNode } from "../ir/display-list.js";
|
|
3
|
+
/**
|
|
4
|
+
* Collects the document's form fields as pages render, then emits the catalog `/AcroForm` dictionary at
|
|
5
|
+
* finalize. Mirrors `OutlineBuilder` / `DestRegistry`: a no-op returning "" when no field was placed, so
|
|
6
|
+
* a document without a form stays byte-identical.
|
|
7
|
+
*/
|
|
8
|
+
export declare class AcroFormCollector {
|
|
9
|
+
private fieldRefs;
|
|
10
|
+
private radioGroups;
|
|
11
|
+
private needAppearances;
|
|
12
|
+
private helvNum?;
|
|
13
|
+
private zadbNum?;
|
|
14
|
+
private hasSignature;
|
|
15
|
+
private usedNames;
|
|
16
|
+
private bake;
|
|
17
|
+
/** Turn appearance baking off (`renderToBytes(doc, { fieldAppearances: false })`). */
|
|
18
|
+
setBakeAppearances(on: boolean): void;
|
|
19
|
+
/** The size to draw a value at. A field may ask for `0` - the PDF convention for "auto-size"; we then
|
|
20
|
+
* fit the capitals comfortably inside the box, which is what a viewer's auto-size does. */
|
|
21
|
+
private drawSize;
|
|
22
|
+
/** Measure one line for the appearance generator. */
|
|
23
|
+
private line;
|
|
24
|
+
/** Bake a text field's value into an appearance stream; returns the XObject number. */
|
|
25
|
+
private bakeText;
|
|
26
|
+
/** Bake a choice field: a combo shows its current value on one line, a list box shows its options
|
|
27
|
+
* with the selected rows highlighted. */
|
|
28
|
+
private bakeChoice;
|
|
29
|
+
get isEmpty(): boolean;
|
|
30
|
+
private helv;
|
|
31
|
+
/**
|
|
32
|
+
* ZapfDingbats, the font a check mark lives in. Needed even though we bake the appearance ourselves:
|
|
33
|
+
* a viewer told to regenerate (`/NeedAppearances`, which filling a form sets) redraws a check box from
|
|
34
|
+
* its `/DA`, and the conventional `/DA` for one names `/ZaDb`. Without the matching `/DR` entry the
|
|
35
|
+
* viewer cannot resolve the font - poppler says "Unknown font tag 'ZaDb'" and draws no check at all.
|
|
36
|
+
* No encoding: ZapfDingbats brings its own.
|
|
37
|
+
*/
|
|
38
|
+
private zadb;
|
|
39
|
+
/**
|
|
40
|
+
* A field name is its identity: two fields sharing one are ONE field to every viewer, showing the same
|
|
41
|
+
* value in both places and overwriting each other as the reader types. The format allows it and says
|
|
42
|
+
* nothing, so the mistake surfaces only when someone fills the form. Named here instead.
|
|
43
|
+
*
|
|
44
|
+
* A radio GROUP is the exception - its buttons deliberately share a name, which is what makes them
|
|
45
|
+
* mutually exclusive - so it never comes through here.
|
|
46
|
+
*/
|
|
47
|
+
private claimName;
|
|
48
|
+
/** Emit the widget object for one form-field IR node, register it as a field, and return its object
|
|
49
|
+
* number so the PageRenderer can add it to that page's /Annots. */
|
|
50
|
+
addField(node: FormFieldNode, om: PDFObjectManager): number;
|
|
51
|
+
/** A push button (/Btn, Pushbutton flag): no value, an optional action, and a baked caption. The
|
|
52
|
+
* caption is measured here (the appearance module stays free of font metrics) and drawn into an
|
|
53
|
+
* appearance stream that carries its own font resource. */
|
|
54
|
+
private buildPushButtonWidget;
|
|
55
|
+
/** A radio button: one KID widget of its group's shared field. The group field is reserved on first
|
|
56
|
+
* sight (and added to /Fields); each button adds itself to that field's /Kids. Returns the kid widget
|
|
57
|
+
* number, which goes into the PAGE /Annots (the parent field does not). */
|
|
58
|
+
private addRadio;
|
|
59
|
+
/** A signature field (/Sig): an EMPTY placeholder with a baked "sign here" face. No /V - an unsigned
|
|
60
|
+
* field has no value; a signing tool fills that in (and replaces this appearance) later. */
|
|
61
|
+
private buildSignatureWidget;
|
|
62
|
+
finalize(om: PDFObjectManager): string;
|
|
63
|
+
}
|