pdf-lite 1.7.1 → 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.
@@ -65,15 +65,14 @@ export declare class PdfGraphics {
65
65
  measureTextWidthWithFont(text: string, fontName: string | undefined, fontSize: number): number;
66
66
  /**
67
67
  * Emits styled text segments into the current BT…ET block.
68
- * When font variants are configured, switches fonts mid-stream for true
69
- * bold/italic rendering. Falls back to stroke simulation / Tm shear when
70
- * no variant fonts are provided (backward compatible).
71
- * Each segment is positioned with an absolute Tm so no prior Td is needed.
68
+ * Returns an array of rects for any strikethrough segments so the caller
69
+ * can draw the lines after closing the text object.
72
70
  */
73
71
  private showSegments;
74
72
  /**
75
- * Parses a markdown string and renders the styled segments into the current
76
- * BT…ET block. Pass `multiline` to wrap across multiple lines.
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.
77
76
  */
78
77
  showMarkdown(markdown: string, isUnicode: boolean, reverseEncodingMap: Map<string, number> | undefined, x: number, y: number, fontSize: number, multiline?: {
79
78
  availableWidth: number;
@@ -110,7 +110,12 @@ export class PdfGraphics {
110
110
  const chars = [];
111
111
  for (const seg of segments) {
112
112
  for (const char of seg.text) {
113
- chars.push({ char, bold: seg.bold, italic: seg.italic });
113
+ chars.push({
114
+ char,
115
+ bold: seg.bold,
116
+ italic: seg.italic,
117
+ strikethrough: seg.strikethrough,
118
+ });
114
119
  }
115
120
  }
116
121
  const result = [];
@@ -125,23 +130,29 @@ export class PdfGraphics {
125
130
  let curText = '';
126
131
  let curBold = false;
127
132
  let curItalic = false;
133
+ let curStrikethrough = false;
128
134
  const lineLen = lines[lineIdx].replace(/\r/g, '').length;
129
135
  for (let j = 0; j < lineLen && pos < chars.length; j++, pos++) {
130
- const { char, bold, italic } = chars[pos];
136
+ const { char, bold, italic, strikethrough } = chars[pos];
131
137
  if (curText === '') {
132
138
  curText = char;
133
139
  curBold = bold;
134
140
  curItalic = italic;
141
+ curStrikethrough = strikethrough;
135
142
  }
136
- else if (bold !== curBold || italic !== curItalic) {
143
+ else if (bold !== curBold ||
144
+ italic !== curItalic ||
145
+ strikethrough !== curStrikethrough) {
137
146
  lineSegs.push({
138
147
  text: curText,
139
148
  bold: curBold,
140
149
  italic: curItalic,
150
+ strikethrough: curStrikethrough,
141
151
  });
142
152
  curText = char;
143
153
  curBold = bold;
144
154
  curItalic = italic;
155
+ curStrikethrough = strikethrough;
145
156
  }
146
157
  else {
147
158
  curText += char;
@@ -152,6 +163,7 @@ export class PdfGraphics {
152
163
  text: curText,
153
164
  bold: curBold,
154
165
  italic: curItalic,
166
+ strikethrough: curStrikethrough,
155
167
  });
156
168
  result.push(lineSegs);
157
169
  }
@@ -189,14 +201,13 @@ export class PdfGraphics {
189
201
  }
190
202
  /**
191
203
  * Emits styled text segments into the current BT…ET block.
192
- * When font variants are configured, switches fonts mid-stream for true
193
- * bold/italic rendering. Falls back to stroke simulation / Tm shear when
194
- * no variant fonts are provided (backward compatible).
195
- * Each segment is positioned with an absolute Tm so no prior Td is needed.
204
+ * Returns an array of rects for any strikethrough segments so the caller
205
+ * can draw the lines after closing the text object.
196
206
  */
197
207
  showSegments(lineSegs, isUnicode, reverseEncodingMap, startX, startY, fontSize) {
198
208
  const regularFontName = this.defaultAppearance.fontName;
199
209
  let x = startX;
210
+ const strikethroughRects = [];
200
211
  for (const seg of lineSegs) {
201
212
  const variantName = this.resolveVariantFontName(seg.bold, seg.italic);
202
213
  if (variantName) {
@@ -208,7 +219,11 @@ export class PdfGraphics {
208
219
  this.raw(`/${variantName} ${fontSize} Tf`);
209
220
  this.raw(`0 Tr`);
210
221
  this.showText(seg.text, segIsUnicode, segEncMap);
211
- x += this.measureTextWidthWithFont(seg.text, variantName, fontSize);
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;
212
227
  }
213
228
  else {
214
229
  // Fallback simulation (no variant font provided)
@@ -224,30 +239,52 @@ export class PdfGraphics {
224
239
  this.raw(`0 Tr`);
225
240
  }
226
241
  this.showText(seg.text, isUnicode, reverseEncodingMap);
227
- x += this.measureTextWidth(seg.text, fontSize);
242
+ const segWidth = this.measureTextWidth(seg.text, fontSize);
243
+ if (seg.strikethrough) {
244
+ strikethroughRects.push({ x, y: startY, width: segWidth });
245
+ }
246
+ x += segWidth;
228
247
  }
229
248
  }
230
249
  // Restore regular font and fill-only rendering mode
231
250
  this.raw(`/${regularFontName} ${fontSize} Tf`);
232
251
  this.raw(`0 Tr`);
233
- return this;
252
+ return strikethroughRects;
234
253
  }
235
254
  /**
236
- * Parses a markdown string and renders the styled segments into the current
237
- * BT…ET block. Pass `multiline` to wrap across multiple lines.
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.
238
258
  */
239
259
  showMarkdown(markdown, isUnicode, reverseEncodingMap, x, y, fontSize, multiline) {
240
260
  const segments = parseMarkdownSegments(markdown);
261
+ const allStrikethroughRects = [];
262
+ this.beginText();
241
263
  if (multiline) {
242
264
  const plainText = segments.map((s) => s.text).join('');
243
265
  const lines = this.wrapTextToLines(plainText, multiline.availableWidth);
244
266
  const styledLines = PdfGraphics.splitSegmentsToLines(segments, lines);
245
267
  for (let i = 0; i < styledLines.length; i++) {
246
- this.showSegments(styledLines[i], isUnicode, reverseEncodingMap, x, y - i * multiline.lineHeight, fontSize);
268
+ const rects = this.showSegments(styledLines[i], isUnicode, reverseEncodingMap, x, y - i * multiline.lineHeight, fontSize);
269
+ allStrikethroughRects.push(...rects);
247
270
  }
248
271
  }
249
272
  else {
250
- this.showSegments(segments, isUnicode, reverseEncodingMap, x, y, fontSize);
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`);
251
288
  }
252
289
  return this;
253
290
  }
@@ -100,7 +100,6 @@ 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
- g.beginText();
104
103
  if (ctx.markdown) {
105
104
  g.showMarkdown(ctx.markdown, isUnicode, reverseEncodingMap, padding, startY, finalFontSize, {
106
105
  availableWidth,
@@ -108,14 +107,15 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
108
107
  });
109
108
  }
110
109
  else {
110
+ g.beginText();
111
111
  g.moveTo(padding, startY);
112
112
  for (let i = 0; i < lines.length; i++) {
113
113
  if (i > 0)
114
114
  g.moveTo(0, -renderLineHeight);
115
115
  g.showText(lines[i].replace(/\r/g, ''), isUnicode, reverseEncodingMap);
116
116
  }
117
+ g.endText();
117
118
  }
118
- g.endText();
119
119
  }
120
120
  else {
121
121
  // Single line — for non-auto-size, shrink if text overflows
@@ -126,15 +126,15 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
126
126
  }
127
127
  }
128
128
  const textY = (height - finalFontSize) / 2 + finalFontSize * 0.2;
129
- g.beginText();
130
129
  if (ctx.markdown) {
131
130
  g.showMarkdown(ctx.markdown, isUnicode, reverseEncodingMap, padding, textY, finalFontSize);
132
131
  }
133
132
  else {
133
+ g.beginText();
134
134
  g.moveTo(padding, textY);
135
135
  g.showText(value, isUnicode, reverseEncodingMap);
136
+ g.endText();
136
137
  }
137
- g.endText();
138
138
  }
139
139
  g.restore();
140
140
  g.endMarkedContent();
@@ -62,7 +62,9 @@ export declare abstract class PdfFormField extends PdfWidgetAnnotation {
62
62
  set onState(state: string);
63
63
  get value(): string;
64
64
  protected setRawValue(val: string | PdfString): void;
65
+ updateAppearance(cache?: Set<PdfIndirectObject>): void;
65
66
  set value(val: string | PdfString);
67
+ get markdownValue(): string | undefined;
66
68
  set markdownValue(val: string);
67
69
  get fontSize(): number | null;
68
70
  set fontSize(size: number);
@@ -83,11 +83,19 @@ export class PdfFormField extends PdfWidgetAnnotation {
83
83
  const page = this.page;
84
84
  if (page) {
85
85
  const annots = page.annotations;
86
- const ref = this.reference;
87
- const key = ref.key;
88
- const alreadyPresent = annots.items.some((r) => r instanceof PdfObjectReference && r.key === key);
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
+ });
89
97
  if (!alreadyPresent) {
90
- annots.items.push(ref);
98
+ annots.items.push(this.reference);
91
99
  }
92
100
  }
93
101
  }
@@ -111,7 +119,7 @@ export class PdfFormField extends PdfWidgetAnnotation {
111
119
  }
112
120
  }
113
121
  get siblings() {
114
- return this.parent?.children ?? [];
122
+ return (this.parent?.children ?? []).filter((sib) => sib !== this);
115
123
  }
116
124
  get font() {
117
125
  const fontName = this.fontName;
@@ -320,35 +328,39 @@ export class PdfFormField extends PdfWidgetAnnotation {
320
328
  target.content.set('V', pdfVal);
321
329
  }
322
330
  }
323
- if (isEmpty) {
324
- this._form?.xfa?.datasets?.updateField(this.name, '');
331
+ for (const child of this.children) {
332
+ if (child.content.has('V')) {
333
+ child.content.delete('V');
334
+ }
325
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);
326
343
  if (this.defaultGenerateAppearance) {
327
344
  this.generateAppearance();
328
345
  }
329
346
  for (const sibling of this.siblings) {
330
- if (sibling !== this && sibling.defaultGenerateAppearance) {
331
- sibling.generateAppearance();
332
- }
347
+ sibling.updateAppearance(cache);
333
348
  }
334
349
  // Separated field/widget structure: field has no Rect but its Kids
335
350
  // are widget annotations that do. Clear stale V entries on children
336
351
  // so they inherit the parent's value, then generate appearances.
337
352
  for (const child of this.children) {
338
- if (child.content.has('V')) {
339
- child.content.delete('V');
340
- }
341
- if (child.defaultGenerateAppearance) {
342
- if (this._form)
343
- child.form = this._form;
344
- child.generateAppearance();
345
- }
353
+ if (this._form)
354
+ child.form = this._form;
355
+ child.updateAppearance(cache);
346
356
  }
347
- this._form?.xfa?.datasets?.updateField(this.name, this.value);
348
357
  }
349
358
  set value(val) {
350
359
  this.setRawValue(val);
351
360
  }
361
+ get markdownValue() {
362
+ return this._markdownValue ?? this.parent?._markdownValue;
363
+ }
352
364
  set markdownValue(val) {
353
365
  const plainText = parseMarkdownSegments(val)
354
366
  .map((s) => s.text)
@@ -360,9 +372,7 @@ export class PdfFormField extends PdfWidgetAnnotation {
360
372
  this.defaultGenerateAppearance = saved;
361
373
  // setRawValue cleared _markdownValue; store the markdown string now
362
374
  this._markdownValue = val;
363
- if (this.defaultGenerateAppearance) {
364
- this.generateAppearance();
365
- }
375
+ this.updateAppearance();
366
376
  }
367
377
  get fontSize() {
368
378
  const da = this.defaultAppearance || '';
@@ -52,7 +52,7 @@ export class PdfTextFormField extends PdfFormField {
52
52
  resolvedFonts,
53
53
  isUnicode,
54
54
  reverseEncodingMap,
55
- markdown: this._markdownValue,
55
+ markdown: this.markdownValue,
56
56
  fontVariantNames: variantNames,
57
57
  });
58
58
  if (options?.makeReadOnly) {
@@ -119,11 +119,21 @@ export class PdfAcroForm extends PdfIndirectObject {
119
119
  const page = field.page;
120
120
  if (!page)
121
121
  return;
122
- const ref = field.reference;
123
- const key = ref.key;
124
- const alreadyPresent = page.annotations.items.some((r) => r instanceof PdfObjectReference && r.key === key);
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
- page.annotations.items.push(ref);
136
+ annots.items.push(field.reference);
127
137
  }
128
138
  }
129
139
  set fields(newFields) {
@@ -2,11 +2,12 @@ export type StyledSegment = {
2
2
  text: string;
3
3
  bold: boolean;
4
4
  italic: boolean;
5
+ strikethrough: boolean;
5
6
  };
6
7
  /**
7
- * Parses a markdown string with **bold**, *italic*, and ***bold+italic***
8
- * syntax into an array of styled text segments.
8
+ * Parses a markdown string with **bold**, __bold__, *italic*, ***bold+italic***,
9
+ * and ~~strikethrough~~ syntax into an array of styled text segments.
9
10
  *
10
- * Unrecognized or unmatched asterisks are emitted as plain text.
11
+ * Unrecognized or unmatched markers are emitted as plain text.
11
12
  */
12
13
  export declare function parseMarkdownSegments(text: string): StyledSegment[];
@@ -1,26 +1,70 @@
1
1
  /**
2
- * Parses a markdown string with **bold**, *italic*, and ***bold+italic***
3
- * syntax into an array of styled text segments.
2
+ * Parses a markdown string with **bold**, __bold__, *italic*, ***bold+italic***,
3
+ * and ~~strikethrough~~ syntax into an array of styled text segments.
4
4
  *
5
- * Unrecognized or unmatched asterisks are emitted as plain text.
5
+ * Unrecognized or unmatched markers are emitted as plain text.
6
6
  */
7
7
  export function parseMarkdownSegments(text) {
8
8
  const segments = [];
9
- // Match *** before ** before * to handle longest-match first
10
- const re = /\*\*\*(.+?)\*\*\*|\*\*(.+?)\*\*|\*(.+?)\*|([^*]+|\*)/gs;
9
+ // Match longest tokens first: ***…***, **…**, __…__, ~~…~~, *…*, _…_
10
+ const re = /\*\*\*(.+?)\*\*\*|\*\*(.+?)\*\*|__(.+?)__|~~(.+?)~~|\*(.+?)\*|_(.+?)_|([^*_~]+|[*_~])/gs;
11
11
  let match;
12
12
  while ((match = re.exec(text)) !== null) {
13
13
  if (match[1] !== undefined) {
14
- segments.push({ text: match[1], bold: true, italic: true });
14
+ segments.push({
15
+ text: match[1],
16
+ bold: true,
17
+ italic: true,
18
+ strikethrough: false,
19
+ });
15
20
  }
16
21
  else if (match[2] !== undefined) {
17
- segments.push({ text: match[2], bold: true, italic: false });
22
+ segments.push({
23
+ text: match[2],
24
+ bold: true,
25
+ italic: false,
26
+ strikethrough: false,
27
+ });
18
28
  }
19
29
  else if (match[3] !== undefined) {
20
- segments.push({ text: match[3], bold: false, italic: true });
30
+ segments.push({
31
+ text: match[3],
32
+ bold: true,
33
+ italic: false,
34
+ strikethrough: false,
35
+ });
21
36
  }
22
- else if (match[4] !== undefined && match[4].length > 0) {
23
- segments.push({ text: match[4], bold: false, italic: false });
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
+ });
24
68
  }
25
69
  }
26
70
  return segments;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-lite",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "exports": {