pdf-lite 1.7.0 → 1.7.2
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/acroform/appearance/pdf-graphics.d.ts +47 -0
- package/dist/acroform/appearance/pdf-graphics.js +214 -4
- package/dist/acroform/appearance/pdf-text-appearance-stream.d.ts +3 -0
- package/dist/acroform/appearance/pdf-text-appearance-stream.js +27 -15
- package/dist/acroform/fields/pdf-form-field.d.ts +26 -0
- package/dist/acroform/fields/pdf-form-field.js +140 -19
- package/dist/acroform/fields/pdf-text-form-field.js +21 -1
- package/dist/acroform/pdf-acro-form.js +14 -4
- package/dist/fonts/font-family.d.ts +12 -0
- package/dist/fonts/font-family.js +1 -0
- package/dist/fonts/index.d.ts +1 -0
- package/dist/fonts/index.js +1 -0
- package/dist/fonts/parsers/otf-parser.d.ts +2 -2
- package/dist/fonts/parsers/ttf-parser.d.ts +2 -2
- package/dist/fonts/parsers/woff-parser.d.ts +2 -0
- package/dist/fonts/parsers/woff-parser.js +6 -0
- package/dist/fonts/pdf-font.js +94 -8
- package/dist/fonts/types.d.ts +10 -0
- package/dist/utils/parse-markdown-segments.d.ts +13 -0
- package/dist/utils/parse-markdown-segments.js +71 -0
- package/package.json +1 -1
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { PdfDefaultAppearance } from '../fields/pdf-default-appearance.js';
|
|
2
2
|
import { PdfFont } from '../../fonts/pdf-font.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resource names of font variants to use when rendering styled markdown segments.
|
|
5
|
+
* When a variant is provided, the appearance stream switches fonts instead of
|
|
6
|
+
* simulating bold/italic via stroke width or matrix shear.
|
|
7
|
+
*/
|
|
8
|
+
export interface FontVariantNames {
|
|
9
|
+
bold?: string;
|
|
10
|
+
italic?: string;
|
|
11
|
+
boldItalic?: string;
|
|
12
|
+
}
|
|
3
13
|
/**
|
|
4
14
|
* Lightweight builder for PDF content streams.
|
|
5
15
|
* Chains PDF operators via a fluent API and emits the final stream with build().
|
|
@@ -9,9 +19,11 @@ export declare class PdfGraphics {
|
|
|
9
19
|
private lines;
|
|
10
20
|
private resolvedFonts?;
|
|
11
21
|
private defaultAppearance?;
|
|
22
|
+
private fontVariantNames?;
|
|
12
23
|
constructor(options?: {
|
|
13
24
|
resolvedFonts?: Map<string, PdfFont>;
|
|
14
25
|
defaultAppearance?: PdfDefaultAppearance;
|
|
26
|
+
fontVariantNames?: FontVariantNames;
|
|
15
27
|
});
|
|
16
28
|
save(): this;
|
|
17
29
|
restore(): this;
|
|
@@ -33,6 +45,39 @@ export declare class PdfGraphics {
|
|
|
33
45
|
fill(): this;
|
|
34
46
|
stroke(): this;
|
|
35
47
|
closePath(): this;
|
|
48
|
+
static readonly ITALIC_SHEAR = 0.267;
|
|
49
|
+
static readonly BOLD_STROKE_RATIO = 0.04;
|
|
50
|
+
/**
|
|
51
|
+
* Re-attributes styled segments to wrapped lines by tracking character
|
|
52
|
+
* positions in the flat plain-text. One whitespace character is consumed
|
|
53
|
+
* at each line boundary (space from word-wrap or newline from paragraph).
|
|
54
|
+
*/
|
|
55
|
+
private static splitSegmentsToLines;
|
|
56
|
+
/**
|
|
57
|
+
* Returns the resource name of the best-matching font variant for the given
|
|
58
|
+
* bold/italic flags, or undefined if no variant fonts are configured.
|
|
59
|
+
*/
|
|
60
|
+
private resolveVariantFontName;
|
|
61
|
+
/**
|
|
62
|
+
* Measures text width using a specific font from resolvedFonts by resource
|
|
63
|
+
* name, falling back to measureTextWidth (regular font) if not found.
|
|
64
|
+
*/
|
|
65
|
+
measureTextWidthWithFont(text: string, fontName: string | undefined, fontSize: number): number;
|
|
66
|
+
/**
|
|
67
|
+
* Emits styled text segments into the current BT…ET block.
|
|
68
|
+
* Returns an array of rects for any strikethrough segments so the caller
|
|
69
|
+
* can draw the lines after closing the text object.
|
|
70
|
+
*/
|
|
71
|
+
private showSegments;
|
|
72
|
+
/**
|
|
73
|
+
* Parses a markdown string, renders the styled segments inside a BT…ET
|
|
74
|
+
* block, then draws strikethrough lines (if any) as path operations after
|
|
75
|
+
* the text object. Pass `multiline` to wrap across multiple lines.
|
|
76
|
+
*/
|
|
77
|
+
showMarkdown(markdown: string, isUnicode: boolean, reverseEncodingMap: Map<string, number> | undefined, x: number, y: number, fontSize: number, multiline?: {
|
|
78
|
+
availableWidth: number;
|
|
79
|
+
lineHeight: number;
|
|
80
|
+
}): this;
|
|
36
81
|
build(): string;
|
|
37
82
|
private get currentFont();
|
|
38
83
|
/**
|
|
@@ -41,6 +86,8 @@ export declare class PdfGraphics {
|
|
|
41
86
|
measureTextWidth(text: string, fontSize?: number): number;
|
|
42
87
|
/**
|
|
43
88
|
* Wrap text to fit within the specified width, breaking at word boundaries.
|
|
89
|
+
* When a bold font variant is configured, uses its metrics conservatively
|
|
90
|
+
* to prevent bold glyphs from overflowing field bounds.
|
|
44
91
|
*/
|
|
45
92
|
wrapTextToLines(text: string, maxWidth: number, fontSize?: number): string[];
|
|
46
93
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { encodePdfText } from '../../utils/encodePdfText.js';
|
|
2
2
|
import { PdfFont } from '../../fonts/pdf-font.js';
|
|
3
|
+
import { parseMarkdownSegments, } from '../../utils/parse-markdown-segments.js';
|
|
3
4
|
/**
|
|
4
5
|
* Lightweight builder for PDF content streams.
|
|
5
6
|
* Chains PDF operators via a fluent API and emits the final stream with build().
|
|
@@ -9,9 +10,11 @@ export class PdfGraphics {
|
|
|
9
10
|
lines = [];
|
|
10
11
|
resolvedFonts;
|
|
11
12
|
defaultAppearance;
|
|
13
|
+
fontVariantNames;
|
|
12
14
|
constructor(options) {
|
|
13
15
|
this.resolvedFonts = options?.resolvedFonts;
|
|
14
16
|
this.defaultAppearance = options?.defaultAppearance;
|
|
17
|
+
this.fontVariantNames = options?.fontVariantNames;
|
|
15
18
|
}
|
|
16
19
|
save() {
|
|
17
20
|
this.lines.push('q');
|
|
@@ -94,6 +97,197 @@ export class PdfGraphics {
|
|
|
94
97
|
this.lines.push('h');
|
|
95
98
|
return this;
|
|
96
99
|
}
|
|
100
|
+
// Italic shear: ≈ tan(15°), applied as the c component of the Tm matrix.
|
|
101
|
+
static ITALIC_SHEAR = 0.267;
|
|
102
|
+
// Bold stroke width as a fraction of font size (0.04 × 12pt = 0.48pt stroke).
|
|
103
|
+
static BOLD_STROKE_RATIO = 0.04;
|
|
104
|
+
/**
|
|
105
|
+
* Re-attributes styled segments to wrapped lines by tracking character
|
|
106
|
+
* positions in the flat plain-text. One whitespace character is consumed
|
|
107
|
+
* at each line boundary (space from word-wrap or newline from paragraph).
|
|
108
|
+
*/
|
|
109
|
+
static splitSegmentsToLines(segments, lines) {
|
|
110
|
+
const chars = [];
|
|
111
|
+
for (const seg of segments) {
|
|
112
|
+
for (const char of seg.text) {
|
|
113
|
+
chars.push({
|
|
114
|
+
char,
|
|
115
|
+
bold: seg.bold,
|
|
116
|
+
italic: seg.italic,
|
|
117
|
+
strikethrough: seg.strikethrough,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const result = [];
|
|
122
|
+
let pos = 0;
|
|
123
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
124
|
+
if (lineIdx > 0 && pos < chars.length) {
|
|
125
|
+
const c = chars[pos].char;
|
|
126
|
+
if (c === ' ' || c === '\n' || c === '\r')
|
|
127
|
+
pos++;
|
|
128
|
+
}
|
|
129
|
+
const lineSegs = [];
|
|
130
|
+
let curText = '';
|
|
131
|
+
let curBold = false;
|
|
132
|
+
let curItalic = false;
|
|
133
|
+
let curStrikethrough = false;
|
|
134
|
+
const lineLen = lines[lineIdx].replace(/\r/g, '').length;
|
|
135
|
+
for (let j = 0; j < lineLen && pos < chars.length; j++, pos++) {
|
|
136
|
+
const { char, bold, italic, strikethrough } = chars[pos];
|
|
137
|
+
if (curText === '') {
|
|
138
|
+
curText = char;
|
|
139
|
+
curBold = bold;
|
|
140
|
+
curItalic = italic;
|
|
141
|
+
curStrikethrough = strikethrough;
|
|
142
|
+
}
|
|
143
|
+
else if (bold !== curBold ||
|
|
144
|
+
italic !== curItalic ||
|
|
145
|
+
strikethrough !== curStrikethrough) {
|
|
146
|
+
lineSegs.push({
|
|
147
|
+
text: curText,
|
|
148
|
+
bold: curBold,
|
|
149
|
+
italic: curItalic,
|
|
150
|
+
strikethrough: curStrikethrough,
|
|
151
|
+
});
|
|
152
|
+
curText = char;
|
|
153
|
+
curBold = bold;
|
|
154
|
+
curItalic = italic;
|
|
155
|
+
curStrikethrough = strikethrough;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
curText += char;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (curText)
|
|
162
|
+
lineSegs.push({
|
|
163
|
+
text: curText,
|
|
164
|
+
bold: curBold,
|
|
165
|
+
italic: curItalic,
|
|
166
|
+
strikethrough: curStrikethrough,
|
|
167
|
+
});
|
|
168
|
+
result.push(lineSegs);
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Returns the resource name of the best-matching font variant for the given
|
|
174
|
+
* bold/italic flags, or undefined if no variant fonts are configured.
|
|
175
|
+
*/
|
|
176
|
+
resolveVariantFontName(bold, italic) {
|
|
177
|
+
if (bold && italic && this.fontVariantNames?.boldItalic)
|
|
178
|
+
return this.fontVariantNames.boldItalic;
|
|
179
|
+
if (bold && this.fontVariantNames?.bold)
|
|
180
|
+
return this.fontVariantNames.bold;
|
|
181
|
+
if (italic && this.fontVariantNames?.italic)
|
|
182
|
+
return this.fontVariantNames.italic;
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Measures text width using a specific font from resolvedFonts by resource
|
|
187
|
+
* name, falling back to measureTextWidth (regular font) if not found.
|
|
188
|
+
*/
|
|
189
|
+
measureTextWidthWithFont(text, fontName, fontSize) {
|
|
190
|
+
if (!fontName)
|
|
191
|
+
return this.measureTextWidth(text, fontSize);
|
|
192
|
+
const font = this.resolvedFonts?.get(fontName);
|
|
193
|
+
if (!font)
|
|
194
|
+
return this.measureTextWidth(text, fontSize);
|
|
195
|
+
let width = 0;
|
|
196
|
+
for (const char of text) {
|
|
197
|
+
const w = font.getCharacterWidth(char.charCodeAt(0), fontSize);
|
|
198
|
+
width += w !== null ? w : fontSize * 0.6;
|
|
199
|
+
}
|
|
200
|
+
return width;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Emits styled text segments into the current BT…ET block.
|
|
204
|
+
* Returns an array of rects for any strikethrough segments so the caller
|
|
205
|
+
* can draw the lines after closing the text object.
|
|
206
|
+
*/
|
|
207
|
+
showSegments(lineSegs, isUnicode, reverseEncodingMap, startX, startY, fontSize) {
|
|
208
|
+
const regularFontName = this.defaultAppearance.fontName;
|
|
209
|
+
let x = startX;
|
|
210
|
+
const strikethroughRects = [];
|
|
211
|
+
for (const seg of lineSegs) {
|
|
212
|
+
const variantName = this.resolveVariantFontName(seg.bold, seg.italic);
|
|
213
|
+
if (variantName) {
|
|
214
|
+
// True font variant — switch font, no simulation needed
|
|
215
|
+
const variantFont = this.resolvedFonts?.get(variantName);
|
|
216
|
+
const segIsUnicode = variantFont?.isUnicode ?? isUnicode;
|
|
217
|
+
const segEncMap = variantFont?.reverseEncodingMap ?? reverseEncodingMap;
|
|
218
|
+
this.raw(`1 0 0 1 ${x.toFixed(3)} ${startY.toFixed(3)} Tm`);
|
|
219
|
+
this.raw(`/${variantName} ${fontSize} Tf`);
|
|
220
|
+
this.raw(`0 Tr`);
|
|
221
|
+
this.showText(seg.text, segIsUnicode, segEncMap);
|
|
222
|
+
const segWidth = this.measureTextWidthWithFont(seg.text, variantName, fontSize);
|
|
223
|
+
if (seg.strikethrough) {
|
|
224
|
+
strikethroughRects.push({ x, y: startY, width: segWidth });
|
|
225
|
+
}
|
|
226
|
+
x += segWidth;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// Fallback simulation (no variant font provided)
|
|
230
|
+
// Always restore regular font in case a variant was active
|
|
231
|
+
this.raw(`/${regularFontName} ${fontSize} Tf`);
|
|
232
|
+
const shear = seg.italic ? PdfGraphics.ITALIC_SHEAR : 0;
|
|
233
|
+
this.raw(`1 0 ${shear} 1 ${x.toFixed(3)} ${startY.toFixed(3)} Tm`);
|
|
234
|
+
if (seg.bold) {
|
|
235
|
+
const sw = (fontSize * PdfGraphics.BOLD_STROKE_RATIO).toFixed(3);
|
|
236
|
+
this.raw(`${sw} w 2 Tr`);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
this.raw(`0 Tr`);
|
|
240
|
+
}
|
|
241
|
+
this.showText(seg.text, isUnicode, reverseEncodingMap);
|
|
242
|
+
const segWidth = this.measureTextWidth(seg.text, fontSize);
|
|
243
|
+
if (seg.strikethrough) {
|
|
244
|
+
strikethroughRects.push({ x, y: startY, width: segWidth });
|
|
245
|
+
}
|
|
246
|
+
x += segWidth;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Restore regular font and fill-only rendering mode
|
|
250
|
+
this.raw(`/${regularFontName} ${fontSize} Tf`);
|
|
251
|
+
this.raw(`0 Tr`);
|
|
252
|
+
return strikethroughRects;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Parses a markdown string, renders the styled segments inside a BT…ET
|
|
256
|
+
* block, then draws strikethrough lines (if any) as path operations after
|
|
257
|
+
* the text object. Pass `multiline` to wrap across multiple lines.
|
|
258
|
+
*/
|
|
259
|
+
showMarkdown(markdown, isUnicode, reverseEncodingMap, x, y, fontSize, multiline) {
|
|
260
|
+
const segments = parseMarkdownSegments(markdown);
|
|
261
|
+
const allStrikethroughRects = [];
|
|
262
|
+
this.beginText();
|
|
263
|
+
if (multiline) {
|
|
264
|
+
const plainText = segments.map((s) => s.text).join('');
|
|
265
|
+
const lines = this.wrapTextToLines(plainText, multiline.availableWidth);
|
|
266
|
+
const styledLines = PdfGraphics.splitSegmentsToLines(segments, lines);
|
|
267
|
+
for (let i = 0; i < styledLines.length; i++) {
|
|
268
|
+
const rects = this.showSegments(styledLines[i], isUnicode, reverseEncodingMap, x, y - i * multiline.lineHeight, fontSize);
|
|
269
|
+
allStrikethroughRects.push(...rects);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const rects = this.showSegments(segments, isUnicode, reverseEncodingMap, x, y, fontSize);
|
|
274
|
+
allStrikethroughRects.push(...rects);
|
|
275
|
+
}
|
|
276
|
+
this.endText();
|
|
277
|
+
// Draw strikethrough lines outside the text object
|
|
278
|
+
if (allStrikethroughRects.length > 0) {
|
|
279
|
+
const lineY = fontSize * 0.35;
|
|
280
|
+
const lineWidth = Math.max(0.5, fontSize * 0.06);
|
|
281
|
+
this.raw(`q`);
|
|
282
|
+
this.raw(`${lineWidth.toFixed(3)} w`);
|
|
283
|
+
for (const rect of allStrikethroughRects) {
|
|
284
|
+
const sy = (rect.y + lineY).toFixed(3);
|
|
285
|
+
this.raw(`${rect.x.toFixed(3)} ${sy} m ${(rect.x + rect.width).toFixed(3)} ${sy} l S`);
|
|
286
|
+
}
|
|
287
|
+
this.raw(`Q`);
|
|
288
|
+
}
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
97
291
|
build() {
|
|
98
292
|
return this.lines.join('\n') + '\n';
|
|
99
293
|
}
|
|
@@ -142,16 +336,23 @@ export class PdfGraphics {
|
|
|
142
336
|
}
|
|
143
337
|
/**
|
|
144
338
|
* Wrap text to fit within the specified width, breaking at word boundaries.
|
|
339
|
+
* When a bold font variant is configured, uses its metrics conservatively
|
|
340
|
+
* to prevent bold glyphs from overflowing field bounds.
|
|
145
341
|
*/
|
|
146
342
|
wrapTextToLines(text, maxWidth, fontSize) {
|
|
147
343
|
if (!this.currentFont) {
|
|
148
344
|
throw new Error('No font set - call setDefaultAppearance() first');
|
|
149
345
|
}
|
|
346
|
+
const boldFontName = this.fontVariantNames?.bold;
|
|
347
|
+
const size = fontSize ?? this.currentFont.size;
|
|
348
|
+
const measure = (t) => boldFontName
|
|
349
|
+
? this.measureTextWidthWithFont(t, boldFontName, size)
|
|
350
|
+
: this.measureTextWidth(t, fontSize);
|
|
150
351
|
// Handle explicit line breaks first
|
|
151
352
|
const paragraphs = text.split('\n');
|
|
152
353
|
const wrappedLines = [];
|
|
153
354
|
for (const paragraph of paragraphs) {
|
|
154
|
-
if (
|
|
355
|
+
if (measure(paragraph) <= maxWidth) {
|
|
155
356
|
wrappedLines.push(paragraph);
|
|
156
357
|
continue;
|
|
157
358
|
}
|
|
@@ -160,7 +361,7 @@ export class PdfGraphics {
|
|
|
160
361
|
let currentLine = '';
|
|
161
362
|
for (const word of words) {
|
|
162
363
|
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
163
|
-
if (
|
|
364
|
+
if (measure(testLine) <= maxWidth) {
|
|
164
365
|
currentLine = testLine;
|
|
165
366
|
}
|
|
166
367
|
else {
|
|
@@ -189,6 +390,7 @@ export class PdfGraphics {
|
|
|
189
390
|
if (!this.currentFont) {
|
|
190
391
|
throw new Error('No font set - call setDefaultAppearance() first');
|
|
191
392
|
}
|
|
393
|
+
const boldFontName = this.fontVariantNames?.bold;
|
|
192
394
|
const startSize = this.currentFont.size;
|
|
193
395
|
const minSize = 0.5;
|
|
194
396
|
const fits = (size) => {
|
|
@@ -199,7 +401,10 @@ export class PdfGraphics {
|
|
|
199
401
|
return lines.length * size * lineHeight <= maxHeight;
|
|
200
402
|
}
|
|
201
403
|
// Single-line mode: text must fit within maxWidth.
|
|
202
|
-
return
|
|
404
|
+
return boldFontName
|
|
405
|
+
? this.measureTextWidthWithFont(text, boldFontName, size) <=
|
|
406
|
+
maxWidth
|
|
407
|
+
: this.measureTextWidth(text, size) <= maxWidth;
|
|
203
408
|
};
|
|
204
409
|
if (fits(startSize))
|
|
205
410
|
return startSize;
|
|
@@ -218,11 +423,16 @@ export class PdfGraphics {
|
|
|
218
423
|
return lo;
|
|
219
424
|
}
|
|
220
425
|
breakLongWord(word, maxWidth, fontSize) {
|
|
426
|
+
const boldFontName = this.fontVariantNames?.bold;
|
|
427
|
+
const size = fontSize ?? this.currentFont?.size ?? 12;
|
|
428
|
+
const measure = (t) => boldFontName
|
|
429
|
+
? this.measureTextWidthWithFont(t, boldFontName, size)
|
|
430
|
+
: this.measureTextWidth(t, fontSize);
|
|
221
431
|
const lines = [];
|
|
222
432
|
let currentLine = '';
|
|
223
433
|
for (const char of word) {
|
|
224
434
|
const testLine = currentLine + char;
|
|
225
|
-
if (
|
|
435
|
+
if (measure(testLine) <= maxWidth) {
|
|
226
436
|
currentLine = testLine;
|
|
227
437
|
}
|
|
228
438
|
else {
|
|
@@ -2,6 +2,7 @@ import { PdfDefaultAppearance } from '../fields/pdf-default-appearance.js';
|
|
|
2
2
|
import { PdfAppearanceStream } from './pdf-appearance-stream.js';
|
|
3
3
|
import type { PdfDictionary } from '../../core/objects/pdf-dictionary.js';
|
|
4
4
|
import type { PdfFont } from '../../fonts/pdf-font.js';
|
|
5
|
+
import { type FontVariantNames } from './pdf-graphics.js';
|
|
5
6
|
/**
|
|
6
7
|
* Appearance stream for text fields (single-line, multiline, comb).
|
|
7
8
|
* Enhanced with word wrapping and automatic font scaling.
|
|
@@ -18,5 +19,7 @@ export declare class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
18
19
|
resolvedFonts?: Map<string, PdfFont>;
|
|
19
20
|
isUnicode?: boolean;
|
|
20
21
|
reverseEncodingMap?: Map<string, number>;
|
|
22
|
+
markdown?: string;
|
|
23
|
+
fontVariantNames?: FontVariantNames;
|
|
21
24
|
});
|
|
22
25
|
}
|
|
@@ -21,6 +21,7 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
21
21
|
// Create graphics with font context for text measurement
|
|
22
22
|
const g = new PdfGraphics({
|
|
23
23
|
resolvedFonts: ctx.resolvedFonts,
|
|
24
|
+
fontVariantNames: ctx.fontVariantNames,
|
|
24
25
|
});
|
|
25
26
|
g.beginMarkedContent();
|
|
26
27
|
g.save();
|
|
@@ -43,8 +44,7 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
43
44
|
// shrink further if text overflows the width.
|
|
44
45
|
finalFontSize = Math.min(DEFAULT_FONT_SIZE, availableHeight);
|
|
45
46
|
g.setDefaultAppearance(new PdfDefaultAppearance(ctx.da.fontName, finalFontSize, ctx.da.colorOp));
|
|
46
|
-
|
|
47
|
-
if (textWidth > availableWidth) {
|
|
47
|
+
if (g.measureTextWidth(value) > availableWidth) {
|
|
48
48
|
finalFontSize = g.calculateFittingFontSize(value, availableWidth);
|
|
49
49
|
}
|
|
50
50
|
finalFontSize = Math.max(finalFontSize, 0.5);
|
|
@@ -100,29 +100,41 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
100
100
|
lines = g.wrapTextToLines(value, availableWidth);
|
|
101
101
|
const renderLineHeight = finalFontSize * 1.2;
|
|
102
102
|
const startY = height - padding - finalFontSize;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
if (ctx.markdown) {
|
|
104
|
+
g.showMarkdown(ctx.markdown, isUnicode, reverseEncodingMap, padding, startY, finalFontSize, {
|
|
105
|
+
availableWidth,
|
|
106
|
+
lineHeight: renderLineHeight,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
g.beginText();
|
|
111
|
+
g.moveTo(padding, startY);
|
|
112
|
+
for (let i = 0; i < lines.length; i++) {
|
|
113
|
+
if (i > 0)
|
|
114
|
+
g.moveTo(0, -renderLineHeight);
|
|
115
|
+
g.showText(lines[i].replace(/\r/g, ''), isUnicode, reverseEncodingMap);
|
|
116
|
+
}
|
|
117
|
+
g.endText();
|
|
109
118
|
}
|
|
110
|
-
g.endText();
|
|
111
119
|
}
|
|
112
120
|
else {
|
|
113
121
|
// Single line — for non-auto-size, shrink if text overflows
|
|
114
122
|
if (!autoSize) {
|
|
115
|
-
|
|
116
|
-
if (textWidth > availableWidth) {
|
|
123
|
+
if (g.measureTextWidth(value) > availableWidth) {
|
|
117
124
|
finalFontSize = g.calculateFittingFontSize(value, availableWidth);
|
|
118
125
|
g.setDefaultAppearance(new PdfDefaultAppearance(ctx.da.fontName, finalFontSize, ctx.da.colorOp));
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
128
|
const textY = (height - finalFontSize) / 2 + finalFontSize * 0.2;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
if (ctx.markdown) {
|
|
130
|
+
g.showMarkdown(ctx.markdown, isUnicode, reverseEncodingMap, padding, textY, finalFontSize);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
g.beginText();
|
|
134
|
+
g.moveTo(padding, textY);
|
|
135
|
+
g.showText(value, isUnicode, reverseEncodingMap);
|
|
136
|
+
g.endText();
|
|
137
|
+
}
|
|
126
138
|
}
|
|
127
139
|
g.restore();
|
|
128
140
|
g.endMarkedContent();
|
|
@@ -4,6 +4,7 @@ import { PdfString } from '../../core/objects/pdf-string.js';
|
|
|
4
4
|
import { PdfObjectReference } from '../../core/objects/pdf-object-reference.js';
|
|
5
5
|
import { PdfIndirectObject } from '../../core/objects/pdf-indirect-object.js';
|
|
6
6
|
import { PdfFont } from '../../fonts/pdf-font.js';
|
|
7
|
+
import type { FontFamily } from '../../fonts/font-family.js';
|
|
7
8
|
import { PdfStream } from '../../core/objects/pdf-stream.js';
|
|
8
9
|
import { PdfWidgetAnnotation } from '../../annotations/pdf-widget-annotation.js';
|
|
9
10
|
import type { PdfFieldType } from './types.js';
|
|
@@ -19,6 +20,9 @@ import { PdfJavaScriptAction } from '../js/pdf-javascript-action.js';
|
|
|
19
20
|
*/
|
|
20
21
|
export declare abstract class PdfFormField extends PdfWidgetAnnotation {
|
|
21
22
|
defaultGenerateAppearance: boolean;
|
|
23
|
+
/** Raw markdown string set by markdownValue; cleared by setRawValue. */
|
|
24
|
+
protected _markdownValue?: string;
|
|
25
|
+
private _fontFamily?;
|
|
22
26
|
/** @internal */
|
|
23
27
|
_form?: PdfAcroForm;
|
|
24
28
|
constructor(other?: PdfIndirectObject | {
|
|
@@ -41,6 +45,12 @@ export declare abstract class PdfFormField extends PdfWidgetAnnotation {
|
|
|
41
45
|
* Returns undefined if neither source provides the font.
|
|
42
46
|
*/
|
|
43
47
|
buildFontResources(fontName: string): PdfDictionary | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Like buildFontResources but includes multiple font names (regular +
|
|
50
|
+
* variant fonts) in a single Resources/Font dictionary. Falls through to
|
|
51
|
+
* buildFontResources when only one name is provided.
|
|
52
|
+
*/
|
|
53
|
+
buildAllFontResources(fontNames: string[]): PdfDictionary | undefined;
|
|
44
54
|
get fieldType(): PdfFieldType | null;
|
|
45
55
|
set fieldType(type: PdfFieldType | null);
|
|
46
56
|
get name(): string;
|
|
@@ -52,12 +62,28 @@ export declare abstract class PdfFormField extends PdfWidgetAnnotation {
|
|
|
52
62
|
set onState(state: string);
|
|
53
63
|
get value(): string;
|
|
54
64
|
protected setRawValue(val: string | PdfString): void;
|
|
65
|
+
updateAppearance(cache?: Set<PdfIndirectObject>): void;
|
|
55
66
|
set value(val: string | PdfString);
|
|
67
|
+
get markdownValue(): string | undefined;
|
|
68
|
+
set markdownValue(val: string);
|
|
56
69
|
get fontSize(): number | null;
|
|
57
70
|
set fontSize(size: number);
|
|
58
71
|
get fontName(): string | null;
|
|
59
72
|
set fontName(fontName: string);
|
|
60
73
|
set font(font: PdfFont | null);
|
|
74
|
+
private _embedFontInDR;
|
|
75
|
+
get fontFamily(): FontFamily | null;
|
|
76
|
+
set fontFamily(family: FontFamily | null);
|
|
77
|
+
get fontVariantNames(): {
|
|
78
|
+
bold?: string;
|
|
79
|
+
italic?: string;
|
|
80
|
+
boldItalic?: string;
|
|
81
|
+
};
|
|
82
|
+
protected get resolvedVariantFonts(): {
|
|
83
|
+
bold?: PdfFont;
|
|
84
|
+
italic?: PdfFont;
|
|
85
|
+
boldItalic?: PdfFont;
|
|
86
|
+
};
|
|
61
87
|
get flags(): PdfFormFieldFlags;
|
|
62
88
|
set flags(v: PdfFormFieldFlags);
|
|
63
89
|
get readOnly(): boolean;
|
|
@@ -14,6 +14,7 @@ import { PdfFieldType as PdfFieldTypeConst } from './types.js';
|
|
|
14
14
|
import { PdfFormFieldFlags } from './pdf-form-field-flags.js';
|
|
15
15
|
import { PdfFieldActions } from '../js/pdf-field-actions.js';
|
|
16
16
|
import { PdfJavaScriptAction } from '../js/pdf-javascript-action.js';
|
|
17
|
+
import { parseMarkdownSegments } from '../../utils/parse-markdown-segments.js';
|
|
17
18
|
/**
|
|
18
19
|
* Abstract base form field class. Extends PdfWidgetAnnotation with form-specific properties:
|
|
19
20
|
* FT, V, DA, Ff, T (name), field hierarchy (parent/children/siblings).
|
|
@@ -21,6 +22,9 @@ import { PdfJavaScriptAction } from '../js/pdf-javascript-action.js';
|
|
|
21
22
|
*/
|
|
22
23
|
export class PdfFormField extends PdfWidgetAnnotation {
|
|
23
24
|
defaultGenerateAppearance = true;
|
|
25
|
+
/** Raw markdown string set by markdownValue; cleared by setRawValue. */
|
|
26
|
+
_markdownValue;
|
|
27
|
+
_fontFamily;
|
|
24
28
|
/** @internal */
|
|
25
29
|
_form;
|
|
26
30
|
constructor(other) {
|
|
@@ -79,11 +83,19 @@ export class PdfFormField extends PdfWidgetAnnotation {
|
|
|
79
83
|
const page = this.page;
|
|
80
84
|
if (page) {
|
|
81
85
|
const annots = page.annotations;
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
86
|
+
const widget = this;
|
|
87
|
+
const alreadyPresent = annots.items.some((r) => {
|
|
88
|
+
if (!(r instanceof PdfObjectReference))
|
|
89
|
+
return false;
|
|
90
|
+
try {
|
|
91
|
+
return r.resolve() === widget;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
85
97
|
if (!alreadyPresent) {
|
|
86
|
-
annots.items.push(
|
|
98
|
+
annots.items.push(this.reference);
|
|
87
99
|
}
|
|
88
100
|
}
|
|
89
101
|
}
|
|
@@ -107,7 +119,7 @@ export class PdfFormField extends PdfWidgetAnnotation {
|
|
|
107
119
|
}
|
|
108
120
|
}
|
|
109
121
|
get siblings() {
|
|
110
|
-
return this.parent?.children ?? [];
|
|
122
|
+
return (this.parent?.children ?? []).filter((sib) => sib !== this);
|
|
111
123
|
}
|
|
112
124
|
get font() {
|
|
113
125
|
const fontName = this.fontName;
|
|
@@ -180,6 +192,39 @@ export class PdfFormField extends PdfWidgetAnnotation {
|
|
|
180
192
|
}
|
|
181
193
|
return undefined;
|
|
182
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Like buildFontResources but includes multiple font names (regular +
|
|
197
|
+
* variant fonts) in a single Resources/Font dictionary. Falls through to
|
|
198
|
+
* buildFontResources when only one name is provided.
|
|
199
|
+
*/
|
|
200
|
+
buildAllFontResources(fontNames) {
|
|
201
|
+
if (fontNames.length <= 1)
|
|
202
|
+
return this.buildFontResources(fontNames[0]);
|
|
203
|
+
const dr = this.defaultResources;
|
|
204
|
+
const fontRaw = dr?.get('Font');
|
|
205
|
+
let drFontDict;
|
|
206
|
+
if (fontRaw instanceof PdfObjectReference) {
|
|
207
|
+
const resolved = fontRaw.resolve()?.content;
|
|
208
|
+
if (resolved instanceof PdfDictionary)
|
|
209
|
+
drFontDict = resolved;
|
|
210
|
+
}
|
|
211
|
+
else if (fontRaw instanceof PdfDictionary) {
|
|
212
|
+
drFontDict = fontRaw;
|
|
213
|
+
}
|
|
214
|
+
const resFontDict = new PdfDictionary();
|
|
215
|
+
for (const name of fontNames) {
|
|
216
|
+
if (drFontDict?.get(name)) {
|
|
217
|
+
resFontDict.set(name, drFontDict.get(name));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (resFontDict.keys().length > 0) {
|
|
221
|
+
const resources = new PdfDictionary();
|
|
222
|
+
resources.set('Font', resFontDict);
|
|
223
|
+
return resources;
|
|
224
|
+
}
|
|
225
|
+
// Fallback to single-font path for the primary font name
|
|
226
|
+
return this.buildFontResources(fontNames[0]);
|
|
227
|
+
}
|
|
183
228
|
get fieldType() {
|
|
184
229
|
const ft = PdfFormField.getFieldType(this);
|
|
185
230
|
switch (ft) {
|
|
@@ -266,6 +311,7 @@ export class PdfFormField extends PdfWidgetAnnotation {
|
|
|
266
311
|
return '';
|
|
267
312
|
}
|
|
268
313
|
setRawValue(val) {
|
|
314
|
+
this._markdownValue = undefined;
|
|
269
315
|
const targets = [this];
|
|
270
316
|
const parent = this.parent;
|
|
271
317
|
if (parent?.fieldType) {
|
|
@@ -282,35 +328,52 @@ export class PdfFormField extends PdfWidgetAnnotation {
|
|
|
282
328
|
target.content.set('V', pdfVal);
|
|
283
329
|
}
|
|
284
330
|
}
|
|
285
|
-
|
|
286
|
-
|
|
331
|
+
for (const child of this.children) {
|
|
332
|
+
if (child.content.has('V')) {
|
|
333
|
+
child.content.delete('V');
|
|
334
|
+
}
|
|
287
335
|
}
|
|
336
|
+
this._form?.xfa?.datasets?.updateField(this.name, this.value);
|
|
337
|
+
this.updateAppearance();
|
|
338
|
+
}
|
|
339
|
+
updateAppearance(cache = new Set()) {
|
|
340
|
+
if (cache.has(this))
|
|
341
|
+
return;
|
|
342
|
+
cache.add(this);
|
|
288
343
|
if (this.defaultGenerateAppearance) {
|
|
289
344
|
this.generateAppearance();
|
|
290
345
|
}
|
|
291
346
|
for (const sibling of this.siblings) {
|
|
292
|
-
|
|
293
|
-
sibling.generateAppearance();
|
|
294
|
-
}
|
|
347
|
+
sibling.updateAppearance(cache);
|
|
295
348
|
}
|
|
296
349
|
// Separated field/widget structure: field has no Rect but its Kids
|
|
297
350
|
// are widget annotations that do. Clear stale V entries on children
|
|
298
351
|
// so they inherit the parent's value, then generate appearances.
|
|
299
352
|
for (const child of this.children) {
|
|
300
|
-
if (
|
|
301
|
-
child.
|
|
302
|
-
|
|
303
|
-
if (child.defaultGenerateAppearance) {
|
|
304
|
-
if (this._form)
|
|
305
|
-
child.form = this._form;
|
|
306
|
-
child.generateAppearance();
|
|
307
|
-
}
|
|
353
|
+
if (this._form)
|
|
354
|
+
child.form = this._form;
|
|
355
|
+
child.updateAppearance(cache);
|
|
308
356
|
}
|
|
309
|
-
this._form?.xfa?.datasets?.updateField(this.name, this.value);
|
|
310
357
|
}
|
|
311
358
|
set value(val) {
|
|
312
359
|
this.setRawValue(val);
|
|
313
360
|
}
|
|
361
|
+
get markdownValue() {
|
|
362
|
+
return this._markdownValue ?? this.parent?._markdownValue;
|
|
363
|
+
}
|
|
364
|
+
set markdownValue(val) {
|
|
365
|
+
const plainText = parseMarkdownSegments(val)
|
|
366
|
+
.map((s) => s.text)
|
|
367
|
+
.join('');
|
|
368
|
+
// Store plain text as V without triggering appearance generation yet
|
|
369
|
+
const saved = this.defaultGenerateAppearance;
|
|
370
|
+
this.defaultGenerateAppearance = false;
|
|
371
|
+
this.setRawValue(plainText);
|
|
372
|
+
this.defaultGenerateAppearance = saved;
|
|
373
|
+
// setRawValue cleared _markdownValue; store the markdown string now
|
|
374
|
+
this._markdownValue = val;
|
|
375
|
+
this.updateAppearance();
|
|
376
|
+
}
|
|
314
377
|
get fontSize() {
|
|
315
378
|
const da = this.defaultAppearance || '';
|
|
316
379
|
const parsed = PdfDefaultAppearance.parse(da);
|
|
@@ -352,6 +415,8 @@ export class PdfFormField extends PdfWidgetAnnotation {
|
|
|
352
415
|
}
|
|
353
416
|
const resourceName = font.resourceName;
|
|
354
417
|
const currentSize = this.fontSize ?? 12;
|
|
418
|
+
this._embedFontInDR(font);
|
|
419
|
+
// Update the DA string to use the font
|
|
355
420
|
const da = this.defaultAppearance || '';
|
|
356
421
|
if (!da) {
|
|
357
422
|
this.content.set('DA', new PdfDefaultAppearance(resourceName, currentSize, '0 g'));
|
|
@@ -363,6 +428,62 @@ export class PdfFormField extends PdfWidgetAnnotation {
|
|
|
363
428
|
this.content.set('DA', parsed);
|
|
364
429
|
}
|
|
365
430
|
}
|
|
431
|
+
_embedFontInDR(font) {
|
|
432
|
+
const resourceName = font.resourceName;
|
|
433
|
+
// Add font to field's default resources
|
|
434
|
+
const dr = this.content.get('DR') || new PdfDictionary();
|
|
435
|
+
let fontDict = dr.get('Font');
|
|
436
|
+
if (!fontDict) {
|
|
437
|
+
fontDict = new PdfDictionary();
|
|
438
|
+
dr.set('Font', fontDict);
|
|
439
|
+
}
|
|
440
|
+
fontDict.set(resourceName, font.reference);
|
|
441
|
+
this.content.set('DR', dr);
|
|
442
|
+
// Also add to form's default resources if available
|
|
443
|
+
if (this._form) {
|
|
444
|
+
const formDr = this._form.defaultResources ||
|
|
445
|
+
new PdfDictionary();
|
|
446
|
+
let formFontDict = formDr.get('Font');
|
|
447
|
+
if (!formFontDict) {
|
|
448
|
+
formFontDict = new PdfDictionary();
|
|
449
|
+
formDr.set('Font', formFontDict);
|
|
450
|
+
}
|
|
451
|
+
formFontDict.set(resourceName, font.reference);
|
|
452
|
+
this._form.defaultResources = formDr;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
get fontFamily() {
|
|
456
|
+
return this._fontFamily ?? null;
|
|
457
|
+
}
|
|
458
|
+
set fontFamily(family) {
|
|
459
|
+
if (family === null) {
|
|
460
|
+
this._fontFamily = undefined;
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
this._fontFamily = family;
|
|
464
|
+
// Setting regular via the existing setter keeps DA string in sync
|
|
465
|
+
this.font = family.regular;
|
|
466
|
+
if (family.bold)
|
|
467
|
+
this._embedFontInDR(family.bold);
|
|
468
|
+
if (family.italic)
|
|
469
|
+
this._embedFontInDR(family.italic);
|
|
470
|
+
if (family.boldItalic)
|
|
471
|
+
this._embedFontInDR(family.boldItalic);
|
|
472
|
+
}
|
|
473
|
+
get fontVariantNames() {
|
|
474
|
+
return {
|
|
475
|
+
bold: this._fontFamily?.bold?.resourceName,
|
|
476
|
+
italic: this._fontFamily?.italic?.resourceName,
|
|
477
|
+
boldItalic: this._fontFamily?.boldItalic?.resourceName,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
get resolvedVariantFonts() {
|
|
481
|
+
return {
|
|
482
|
+
bold: this._fontFamily?.bold,
|
|
483
|
+
italic: this._fontFamily?.italic,
|
|
484
|
+
boldItalic: this._fontFamily?.boldItalic,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
366
487
|
get flags() {
|
|
367
488
|
const flags = new PdfFormFieldFlags(this.content.get('Ff') ?? this.parent?.content.get('Ff') ?? 0);
|
|
368
489
|
flags.onChange(() => {
|
|
@@ -21,7 +21,24 @@ export class PdfTextFormField extends PdfFormField {
|
|
|
21
21
|
if (!parsed)
|
|
22
22
|
return false;
|
|
23
23
|
const font = this.font;
|
|
24
|
-
const
|
|
24
|
+
const variantNames = this.fontVariantNames;
|
|
25
|
+
const variantFonts = this.resolvedVariantFonts;
|
|
26
|
+
const allFontNames = [
|
|
27
|
+
parsed.fontName,
|
|
28
|
+
variantNames.bold,
|
|
29
|
+
variantNames.italic,
|
|
30
|
+
variantNames.boldItalic,
|
|
31
|
+
].filter((n) => !!n);
|
|
32
|
+
const fontResources = this.buildAllFontResources(allFontNames);
|
|
33
|
+
const resolvedFonts = new Map();
|
|
34
|
+
if (font)
|
|
35
|
+
resolvedFonts.set(parsed.fontName, font);
|
|
36
|
+
if (variantFonts.bold)
|
|
37
|
+
resolvedFonts.set(variantNames.bold, variantFonts.bold);
|
|
38
|
+
if (variantFonts.italic)
|
|
39
|
+
resolvedFonts.set(variantNames.italic, variantFonts.italic);
|
|
40
|
+
if (variantFonts.boldItalic)
|
|
41
|
+
resolvedFonts.set(variantNames.boldItalic, variantFonts.boldItalic);
|
|
25
42
|
const isUnicode = font?.isUnicode ?? false;
|
|
26
43
|
const reverseEncodingMap = font?.reverseEncodingMap;
|
|
27
44
|
this.appearanceStream = new PdfTextAppearanceStream({
|
|
@@ -32,8 +49,11 @@ export class PdfTextFormField extends PdfFormField {
|
|
|
32
49
|
comb: this.comb,
|
|
33
50
|
maxLen: this.maxLen,
|
|
34
51
|
fontResources,
|
|
52
|
+
resolvedFonts,
|
|
35
53
|
isUnicode,
|
|
36
54
|
reverseEncodingMap,
|
|
55
|
+
markdown: this.markdownValue,
|
|
56
|
+
fontVariantNames: variantNames,
|
|
37
57
|
});
|
|
38
58
|
if (options?.makeReadOnly) {
|
|
39
59
|
this.readOnly = true;
|
|
@@ -119,11 +119,21 @@ export class PdfAcroForm extends PdfIndirectObject {
|
|
|
119
119
|
const page = field.page;
|
|
120
120
|
if (!page)
|
|
121
121
|
return;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
const annots = page.annotations;
|
|
123
|
+
// Unregistered fields all share key "-1/0", so compare by object
|
|
124
|
+
// identity (via resolve()) rather than by key.
|
|
125
|
+
const alreadyPresent = annots.items.some((r) => {
|
|
126
|
+
if (!(r instanceof PdfObjectReference))
|
|
127
|
+
return false;
|
|
128
|
+
try {
|
|
129
|
+
return r.resolve() === field;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
125
135
|
if (!alreadyPresent) {
|
|
126
|
-
|
|
136
|
+
annots.items.push(field.reference);
|
|
127
137
|
}
|
|
128
138
|
}
|
|
129
139
|
set fields(newFields) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PdfFont } from './pdf-font.js';
|
|
2
|
+
/**
|
|
3
|
+
* A group of font variants for a single typeface family.
|
|
4
|
+
* Set on a form field via `field.fontFamily` to enable true bold/italic
|
|
5
|
+
* rendering in markdown appearance streams rather than stroke simulation.
|
|
6
|
+
*/
|
|
7
|
+
export interface FontFamily {
|
|
8
|
+
regular: PdfFont;
|
|
9
|
+
bold?: PdfFont;
|
|
10
|
+
italic?: PdfFont;
|
|
11
|
+
boldItalic?: PdfFont;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/fonts/index.d.ts
CHANGED
package/dist/fonts/index.js
CHANGED
|
@@ -41,10 +41,10 @@ export declare class OtfParser implements FontParser {
|
|
|
41
41
|
private parseOS2;
|
|
42
42
|
private parsePost;
|
|
43
43
|
private parseName;
|
|
44
|
-
|
|
44
|
+
parseCmap(): Map<number, number>;
|
|
45
45
|
private parseCmapFormat4;
|
|
46
46
|
private parseCmapFormat12;
|
|
47
|
-
|
|
47
|
+
parseHmtx(): Map<number, number>;
|
|
48
48
|
private parseMaxp;
|
|
49
49
|
private estimateStemV;
|
|
50
50
|
}
|
|
@@ -33,10 +33,10 @@ export declare class TtfParser implements FontParser {
|
|
|
33
33
|
private parseOS2;
|
|
34
34
|
private parsePost;
|
|
35
35
|
private parseName;
|
|
36
|
-
|
|
36
|
+
parseCmap(): Map<number, number>;
|
|
37
37
|
private parseCmapFormat4;
|
|
38
38
|
private parseCmapFormat12;
|
|
39
|
-
|
|
39
|
+
parseHmtx(): Map<number, number>;
|
|
40
40
|
private parseMaxp;
|
|
41
41
|
private estimateStemV;
|
|
42
42
|
}
|
|
@@ -24,6 +24,8 @@ export declare class WoffParser implements FontParser {
|
|
|
24
24
|
* Gets character widths for a range of characters.
|
|
25
25
|
*/
|
|
26
26
|
getCharWidths(firstChar: number, lastChar: number): number[];
|
|
27
|
+
parseCmap(): Map<number, number>;
|
|
28
|
+
parseHmtx(): Map<number, number>;
|
|
27
29
|
private decompressWoff;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
@@ -35,6 +35,12 @@ export class WoffParser {
|
|
|
35
35
|
getCharWidths(firstChar, lastChar) {
|
|
36
36
|
return this.ttfParser.getCharWidths(firstChar, lastChar);
|
|
37
37
|
}
|
|
38
|
+
parseCmap() {
|
|
39
|
+
return this.ttfParser.parseCmap();
|
|
40
|
+
}
|
|
41
|
+
parseHmtx() {
|
|
42
|
+
return this.ttfParser.parseHmtx();
|
|
43
|
+
}
|
|
38
44
|
decompressWoff(woffData) {
|
|
39
45
|
const data = new DataView(woffData.buffer, woffData.byteOffset, woffData.byteLength);
|
|
40
46
|
// Verify WOFF signature
|
package/dist/fonts/pdf-font.js
CHANGED
|
@@ -213,6 +213,27 @@ export class PdfFont extends PdfIndirectObject {
|
|
|
213
213
|
* @returns The raw character width or null if not found
|
|
214
214
|
*/
|
|
215
215
|
getRawCharacterWidth(charCode) {
|
|
216
|
+
// For Type0 fonts, check CID widths in descriptor
|
|
217
|
+
if (this.isUnicode && this._descriptor) {
|
|
218
|
+
const unicodeDesc = this._descriptor;
|
|
219
|
+
if (unicodeDesc.cidWidths) {
|
|
220
|
+
// Find width for this CID (which equals Unicode code point for Identity-H)
|
|
221
|
+
for (const entry of unicodeDesc.cidWidths) {
|
|
222
|
+
if ('width' in entry && entry.cid === charCode) {
|
|
223
|
+
return entry.width;
|
|
224
|
+
}
|
|
225
|
+
else if ('widths' in entry) {
|
|
226
|
+
const offset = charCode - entry.startCid;
|
|
227
|
+
if (offset >= 0 && offset < entry.widths.length) {
|
|
228
|
+
return entry.widths[offset];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Not found, use default width
|
|
233
|
+
return unicodeDesc.defaultWidth ?? 1000;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// For Type1/TrueType fonts, use simple widths array
|
|
216
237
|
if (this.widths === undefined || this.firstChar === undefined) {
|
|
217
238
|
return null;
|
|
218
239
|
}
|
|
@@ -285,8 +306,7 @@ export class PdfFont extends PdfIndirectObject {
|
|
|
285
306
|
* @returns A PdfFont instance ready to be written to the PDF
|
|
286
307
|
*/
|
|
287
308
|
static fromBytes(data) {
|
|
288
|
-
|
|
289
|
-
return PdfFont.fromParser(parser);
|
|
309
|
+
return PdfFont.fromFile(data);
|
|
290
310
|
}
|
|
291
311
|
/**
|
|
292
312
|
* Creates a PdfFont from a FontParser instance.
|
|
@@ -582,8 +602,37 @@ export class PdfFont extends PdfIndirectObject {
|
|
|
582
602
|
if (descriptor.cidWidths && descriptor.cidWidths.length > 0) {
|
|
583
603
|
cidFontDict.set('W', PdfFont.buildCIDWidthArray(descriptor.cidWidths));
|
|
584
604
|
}
|
|
585
|
-
// CIDToGIDMap
|
|
586
|
-
|
|
605
|
+
// CIDToGIDMap - Generate binary stream if unicodeMappings provided
|
|
606
|
+
if (unicodeMappings && unicodeMappings.size > 0) {
|
|
607
|
+
// Create a binary CIDToGIDMap stream
|
|
608
|
+
// For Identity-H encoding, CID = Unicode value
|
|
609
|
+
// We need to map each CID to its GID
|
|
610
|
+
const maxCid = Math.max(...Array.from(unicodeMappings.keys()));
|
|
611
|
+
const cidToGidData = new Uint8Array((maxCid + 1) * 2);
|
|
612
|
+
// Initialize all mappings to 0 (missing glyph)
|
|
613
|
+
for (let i = 0; i < cidToGidData.length; i++) {
|
|
614
|
+
cidToGidData[i] = 0;
|
|
615
|
+
}
|
|
616
|
+
// Fill in the mappings
|
|
617
|
+
for (const [unicode, glyphId] of unicodeMappings.entries()) {
|
|
618
|
+
const offset = unicode * 2;
|
|
619
|
+
// Big-endian 16-bit glyph ID
|
|
620
|
+
cidToGidData[offset] = (glyphId >> 8) & 0xff;
|
|
621
|
+
cidToGidData[offset + 1] = glyphId & 0xff;
|
|
622
|
+
}
|
|
623
|
+
const cidToGidStream = new PdfStream({
|
|
624
|
+
header: new PdfDictionary(),
|
|
625
|
+
original: cidToGidData,
|
|
626
|
+
});
|
|
627
|
+
cidToGidStream.addFilter('FlateDecode');
|
|
628
|
+
const cidToGidObject = new PdfIndirectObject({
|
|
629
|
+
content: cidToGidStream,
|
|
630
|
+
});
|
|
631
|
+
cidFontDict.set('CIDToGIDMap', cidToGidObject.reference);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
cidFontDict.set('CIDToGIDMap', new PdfName(descriptor.cidToGidMap ?? 'Identity'));
|
|
635
|
+
}
|
|
587
636
|
const cidFontObject = new PdfIndirectObject({
|
|
588
637
|
content: cidFontDict,
|
|
589
638
|
});
|
|
@@ -611,21 +660,58 @@ export class PdfFont extends PdfIndirectObject {
|
|
|
611
660
|
static fromFile(fontData, options) {
|
|
612
661
|
// Parse the font to extract metadata
|
|
613
662
|
const parser = parseFont(fontData);
|
|
663
|
+
// Check if CFF-based OTF (not supported)
|
|
664
|
+
if (parser instanceof OtfParser && parser.isCFFBased()) {
|
|
665
|
+
throw new Error('CFF-based OTF fonts are not supported yet');
|
|
666
|
+
}
|
|
614
667
|
const info = parser.getFontInfo();
|
|
615
668
|
// Auto-generate font name from metadata if not provided
|
|
616
669
|
const fontName = options?.fontName ?? info.postScriptName ?? info.fullName;
|
|
617
670
|
// Get the appropriate descriptor based on unicode option
|
|
618
671
|
const descriptor = parser.getFontDescriptor(fontName);
|
|
672
|
+
// Auto-detect if Unicode font is needed if not explicitly specified
|
|
673
|
+
let useUnicode = options?.unicode;
|
|
674
|
+
if (useUnicode === undefined) {
|
|
675
|
+
// Check if font has glyphs beyond WinAnsiEncoding range (0-255)
|
|
676
|
+
const cmap = parser.parseCmap();
|
|
677
|
+
useUnicode = Array.from(cmap.keys()).some((unicode) => unicode > 0xff);
|
|
678
|
+
}
|
|
619
679
|
// Embed using the appropriate method and return PdfFont
|
|
620
|
-
if (
|
|
680
|
+
if (useUnicode) {
|
|
681
|
+
// Auto-extract cmap if not provided
|
|
682
|
+
const unicodeMappings = options?.unicodeMappings ?? parser.parseCmap();
|
|
683
|
+
// Extract glyph widths for Unicode characters
|
|
684
|
+
const hmtx = parser.parseHmtx();
|
|
685
|
+
const unitsPerEm = info.unitsPerEm;
|
|
686
|
+
const cidWidths = [];
|
|
687
|
+
// Build CID widths array from cmap + hmtx
|
|
688
|
+
for (const [unicode, glyphId] of unicodeMappings.entries()) {
|
|
689
|
+
const glyphWidth = hmtx.get(glyphId) ?? 0;
|
|
690
|
+
// Scale to 1000-unit em square
|
|
691
|
+
const scaledWidth = Math.round((glyphWidth * 1000) / unitsPerEm);
|
|
692
|
+
cidWidths.push({ cid: unicode, width: scaledWidth });
|
|
693
|
+
}
|
|
694
|
+
// Compute average width for default
|
|
695
|
+
const avgWidth = cidWidths.length > 0
|
|
696
|
+
? Math.round(cidWidths.reduce((sum, entry) => {
|
|
697
|
+
if ('width' in entry) {
|
|
698
|
+
return sum + entry.width;
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// Average the widths in the range
|
|
702
|
+
const rangeSum = entry.widths.reduce((a, b) => a + b, 0);
|
|
703
|
+
return sum + rangeSum / entry.widths.length;
|
|
704
|
+
}
|
|
705
|
+
}, 0) / cidWidths.length)
|
|
706
|
+
: 1000;
|
|
621
707
|
// For Unicode fonts, we need a UnicodeFontDescriptor
|
|
622
|
-
// Create one by extending the base descriptor
|
|
623
708
|
const unicodeDescriptor = {
|
|
624
709
|
...descriptor,
|
|
625
|
-
defaultWidth:
|
|
710
|
+
defaultWidth: avgWidth,
|
|
711
|
+
cidWidths,
|
|
626
712
|
cidToGidMap: 'Identity',
|
|
627
713
|
};
|
|
628
|
-
return PdfFont.fromType0Data(fontData, fontName, unicodeDescriptor,
|
|
714
|
+
return PdfFont.fromType0Data(fontData, fontName, unicodeDescriptor, unicodeMappings);
|
|
629
715
|
}
|
|
630
716
|
else {
|
|
631
717
|
// Use standard TrueType embedding
|
package/dist/fonts/types.d.ts
CHANGED
|
@@ -8,6 +8,16 @@ export interface FontParser {
|
|
|
8
8
|
getFontDescriptor(fontName?: string): FontDescriptor;
|
|
9
9
|
getCharWidths(firstChar: number, lastChar: number): number[];
|
|
10
10
|
getFontData(): ByteArray;
|
|
11
|
+
/**
|
|
12
|
+
* Parses the font's cmap table to extract Unicode to glyph ID mappings.
|
|
13
|
+
* @returns A map from Unicode code points to glyph IDs
|
|
14
|
+
*/
|
|
15
|
+
parseCmap(): Map<number, number>;
|
|
16
|
+
/**
|
|
17
|
+
* Parses the font's hmtx table to extract glyph advance widths.
|
|
18
|
+
* @returns A map from glyph IDs to advance widths (in font units)
|
|
19
|
+
*/
|
|
20
|
+
parseHmtx(): Map<number, number>;
|
|
11
21
|
}
|
|
12
22
|
/**
|
|
13
23
|
* Parsed TrueType font information.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type StyledSegment = {
|
|
2
|
+
text: string;
|
|
3
|
+
bold: boolean;
|
|
4
|
+
italic: boolean;
|
|
5
|
+
strikethrough: boolean;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Parses a markdown string with **bold**, __bold__, *italic*, ***bold+italic***,
|
|
9
|
+
* and ~~strikethrough~~ syntax into an array of styled text segments.
|
|
10
|
+
*
|
|
11
|
+
* Unrecognized or unmatched markers are emitted as plain text.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseMarkdownSegments(text: string): StyledSegment[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a markdown string with **bold**, __bold__, *italic*, ***bold+italic***,
|
|
3
|
+
* and ~~strikethrough~~ syntax into an array of styled text segments.
|
|
4
|
+
*
|
|
5
|
+
* Unrecognized or unmatched markers are emitted as plain text.
|
|
6
|
+
*/
|
|
7
|
+
export function parseMarkdownSegments(text) {
|
|
8
|
+
const segments = [];
|
|
9
|
+
// Match longest tokens first: ***…***, **…**, __…__, ~~…~~, *…*, _…_
|
|
10
|
+
const re = /\*\*\*(.+?)\*\*\*|\*\*(.+?)\*\*|__(.+?)__|~~(.+?)~~|\*(.+?)\*|_(.+?)_|([^*_~]+|[*_~])/gs;
|
|
11
|
+
let match;
|
|
12
|
+
while ((match = re.exec(text)) !== null) {
|
|
13
|
+
if (match[1] !== undefined) {
|
|
14
|
+
segments.push({
|
|
15
|
+
text: match[1],
|
|
16
|
+
bold: true,
|
|
17
|
+
italic: true,
|
|
18
|
+
strikethrough: false,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else if (match[2] !== undefined) {
|
|
22
|
+
segments.push({
|
|
23
|
+
text: match[2],
|
|
24
|
+
bold: true,
|
|
25
|
+
italic: false,
|
|
26
|
+
strikethrough: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
else if (match[3] !== undefined) {
|
|
30
|
+
segments.push({
|
|
31
|
+
text: match[3],
|
|
32
|
+
bold: true,
|
|
33
|
+
italic: false,
|
|
34
|
+
strikethrough: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
else if (match[4] !== undefined) {
|
|
38
|
+
segments.push({
|
|
39
|
+
text: match[4],
|
|
40
|
+
bold: false,
|
|
41
|
+
italic: false,
|
|
42
|
+
strikethrough: true,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else if (match[5] !== undefined) {
|
|
46
|
+
segments.push({
|
|
47
|
+
text: match[5],
|
|
48
|
+
bold: false,
|
|
49
|
+
italic: true,
|
|
50
|
+
strikethrough: false,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else if (match[6] !== undefined) {
|
|
54
|
+
segments.push({
|
|
55
|
+
text: match[6],
|
|
56
|
+
bold: false,
|
|
57
|
+
italic: true,
|
|
58
|
+
strikethrough: false,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else if (match[7] !== undefined && match[7].length > 0) {
|
|
62
|
+
segments.push({
|
|
63
|
+
text: match[7],
|
|
64
|
+
bold: false,
|
|
65
|
+
italic: false,
|
|
66
|
+
strikethrough: false,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return segments;
|
|
71
|
+
}
|