@cj-tech-master/excelts 9.4.1 → 9.4.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.
Files changed (66) hide show
  1. package/dist/browser/modules/pdf/builder/document-builder.js +4 -23
  2. package/dist/browser/modules/pdf/core/pdf-stream.d.ts +15 -0
  3. package/dist/browser/modules/pdf/core/pdf-stream.js +47 -3
  4. package/dist/browser/modules/pdf/font/font-manager.d.ts +37 -6
  5. package/dist/browser/modules/pdf/font/font-manager.js +129 -17
  6. package/dist/browser/modules/pdf/font/system-fonts.d.ts +41 -0
  7. package/dist/browser/modules/pdf/font/system-fonts.js +188 -0
  8. package/dist/browser/modules/pdf/font/ttf-parser.js +29 -1
  9. package/dist/browser/modules/pdf/font/type3-font.d.ts +35 -0
  10. package/dist/browser/modules/pdf/font/type3-font.js +228 -0
  11. package/dist/browser/modules/pdf/font/type3-glyphs-extended.d.ts +33 -0
  12. package/dist/browser/modules/pdf/font/type3-glyphs-extended.js +4164 -0
  13. package/dist/browser/modules/pdf/font/type3-glyphs-extended2.d.ts +16 -0
  14. package/dist/browser/modules/pdf/font/type3-glyphs-extended2.js +9649 -0
  15. package/dist/browser/modules/pdf/font/type3-glyphs-fill.d.ts +17 -0
  16. package/dist/browser/modules/pdf/font/type3-glyphs-fill.js +5438 -0
  17. package/dist/browser/modules/pdf/font/type3-glyphs-quality.d.ts +28 -0
  18. package/dist/browser/modules/pdf/font/type3-glyphs-quality.js +5345 -0
  19. package/dist/browser/modules/pdf/font/type3-glyphs.d.ts +79 -0
  20. package/dist/browser/modules/pdf/font/type3-glyphs.js +2567 -0
  21. package/dist/browser/modules/pdf/render/layout-engine.js +36 -23
  22. package/dist/browser/modules/pdf/render/page-renderer.d.ts +9 -0
  23. package/dist/browser/modules/pdf/render/page-renderer.js +110 -78
  24. package/dist/browser/modules/pdf/render/pdf-exporter.js +73 -5
  25. package/dist/cjs/modules/pdf/builder/document-builder.js +3 -22
  26. package/dist/cjs/modules/pdf/core/pdf-stream.js +49 -3
  27. package/dist/cjs/modules/pdf/font/font-manager.js +129 -17
  28. package/dist/cjs/modules/pdf/font/system-fonts.js +194 -0
  29. package/dist/cjs/modules/pdf/font/ttf-parser.js +29 -1
  30. package/dist/cjs/modules/pdf/font/type3-font.js +231 -0
  31. package/dist/cjs/modules/pdf/font/type3-glyphs-extended.js +4167 -0
  32. package/dist/cjs/modules/pdf/font/type3-glyphs-extended2.js +9652 -0
  33. package/dist/cjs/modules/pdf/font/type3-glyphs-fill.js +5441 -0
  34. package/dist/cjs/modules/pdf/font/type3-glyphs-quality.js +5348 -0
  35. package/dist/cjs/modules/pdf/font/type3-glyphs.js +2573 -0
  36. package/dist/cjs/modules/pdf/render/layout-engine.js +36 -23
  37. package/dist/cjs/modules/pdf/render/page-renderer.js +111 -78
  38. package/dist/cjs/modules/pdf/render/pdf-exporter.js +71 -3
  39. package/dist/esm/modules/pdf/builder/document-builder.js +4 -23
  40. package/dist/esm/modules/pdf/core/pdf-stream.js +47 -3
  41. package/dist/esm/modules/pdf/font/font-manager.js +129 -17
  42. package/dist/esm/modules/pdf/font/system-fonts.js +188 -0
  43. package/dist/esm/modules/pdf/font/ttf-parser.js +29 -1
  44. package/dist/esm/modules/pdf/font/type3-font.js +228 -0
  45. package/dist/esm/modules/pdf/font/type3-glyphs-extended.js +4164 -0
  46. package/dist/esm/modules/pdf/font/type3-glyphs-extended2.js +9649 -0
  47. package/dist/esm/modules/pdf/font/type3-glyphs-fill.js +5438 -0
  48. package/dist/esm/modules/pdf/font/type3-glyphs-quality.js +5345 -0
  49. package/dist/esm/modules/pdf/font/type3-glyphs.js +2567 -0
  50. package/dist/esm/modules/pdf/render/layout-engine.js +36 -23
  51. package/dist/esm/modules/pdf/render/page-renderer.js +110 -78
  52. package/dist/esm/modules/pdf/render/pdf-exporter.js +73 -5
  53. package/dist/iife/excelts.iife.js +25445 -344
  54. package/dist/iife/excelts.iife.js.map +1 -1
  55. package/dist/iife/excelts.iife.min.js +48 -46
  56. package/dist/types/modules/pdf/core/pdf-stream.d.ts +15 -0
  57. package/dist/types/modules/pdf/font/font-manager.d.ts +37 -6
  58. package/dist/types/modules/pdf/font/system-fonts.d.ts +41 -0
  59. package/dist/types/modules/pdf/font/type3-font.d.ts +35 -0
  60. package/dist/types/modules/pdf/font/type3-glyphs-extended.d.ts +33 -0
  61. package/dist/types/modules/pdf/font/type3-glyphs-extended2.d.ts +16 -0
  62. package/dist/types/modules/pdf/font/type3-glyphs-fill.d.ts +17 -0
  63. package/dist/types/modules/pdf/font/type3-glyphs-quality.d.ts +28 -0
  64. package/dist/types/modules/pdf/font/type3-glyphs.d.ts +79 -0
  65. package/dist/types/modules/pdf/render/page-renderer.d.ts +9 -0
  66. package/package.json +1 -1
@@ -25,7 +25,7 @@ import { PdfWriter } from "../core/pdf-writer.js";
25
25
  import { writePdfAMetadata, writePdfAOutputIntent } from "../core/pdfa.js";
26
26
  import { FontManager } from "../font/font-manager.js";
27
27
  import { parseTtf } from "../font/ttf-parser.js";
28
- import { wrapTextLines } from "../render/page-renderer.js";
28
+ import { wrapTextLines, emitTextWithMatrix } from "../render/page-renderer.js";
29
29
  import { writeImageXObject } from "./image-utils.js";
30
30
  // =============================================================================
31
31
  // Constants
@@ -87,8 +87,8 @@ export class PdfPageBuilder {
87
87
  const fontFamily = options.fontFamily ?? "Helvetica";
88
88
  // Resolve font
89
89
  const resourceName = this._fontManager.resolveFont(fontFamily, bold, italic);
90
- const encodedText = this._fontManager.encodeText(text, resourceName);
91
90
  this._fontManager.trackText(text);
91
+ const useType3 = this._fontManager.hasType3Fonts() && !this._fontManager.hasEmbeddedFont();
92
92
  if (options.maxWidth) {
93
93
  // Word-wrap (reuses the shared wrapTextLines from page-renderer)
94
94
  const measure = (s) => this._fontManager.measureText(s, resourceName, fontSize);
@@ -96,36 +96,17 @@ export class PdfPageBuilder {
96
96
  const leading = fontSize * lineHeightFactor;
97
97
  this._stream.save();
98
98
  this._stream.setFillColor(color);
99
- this._stream.beginText();
100
- this._stream.setFont(resourceName, fontSize);
101
99
  for (let i = 0; i < lines.length; i++) {
102
100
  const lineY = options.y - i * leading;
103
- this._stream.setTextMatrix(1, 0, 0, 1, options.x, lineY);
104
- const lineEncoded = this._fontManager.encodeText(lines[i], resourceName);
105
- if (lineEncoded) {
106
- this._stream.showTextHex(lineEncoded);
107
- }
108
- else {
109
- this._stream.showText(lines[i]);
110
- }
101
+ emitTextWithMatrix(this._stream, lines[i], 1, 0, 0, 1, options.x, lineY, resourceName, fontSize, this._fontManager, useType3);
111
102
  }
112
- this._stream.endText();
113
103
  this._stream.restore();
114
104
  }
115
105
  else {
116
106
  // Single line
117
107
  this._stream.save();
118
108
  this._stream.setFillColor(color);
119
- this._stream.beginText();
120
- this._stream.setFont(resourceName, fontSize);
121
- this._stream.setTextMatrix(1, 0, 0, 1, options.x, options.y);
122
- if (encodedText) {
123
- this._stream.showTextHex(encodedText);
124
- }
125
- else {
126
- this._stream.showText(text);
127
- }
128
- this._stream.endText();
109
+ emitTextWithMatrix(this._stream, text, 1, 0, 0, 1, options.x, options.y, resourceName, fontSize, this._fontManager, useType3);
129
110
  this._stream.restore();
130
111
  }
131
112
  return this;
@@ -20,6 +20,11 @@ import type { PdfColor } from "../types.js";
20
20
  */
21
21
  export declare class PdfContentStream {
22
22
  private parts;
23
+ /**
24
+ * Append a raw PDF operator string to the content stream.
25
+ * Use this for operators not covered by the typed API (e.g. `d1` for Type3 glyphs).
26
+ */
27
+ raw(operator: string): this;
23
28
  /**
24
29
  * Save the current graphics state (push onto state stack).
25
30
  * Must be balanced with a corresponding restore().
@@ -225,3 +230,13 @@ export declare class PdfContentStream {
225
230
  */
226
231
  toUint8Array(): Uint8Array;
227
232
  }
233
+ /**
234
+ * Check whether a single code point is representable in WinAnsi encoding.
235
+ */
236
+ export declare function isWinAnsiCodePoint(cp: number): boolean;
237
+ /**
238
+ * Check whether a string contains characters outside the WinAnsi repertoire.
239
+ * When true, standard Type1 fonts cannot render those characters and an
240
+ * embedded TrueType font is required for correct output.
241
+ */
242
+ export declare function hasNonWinAnsiChars(text: string): boolean;
@@ -26,6 +26,17 @@ export class PdfContentStream {
26
26
  this.parts = [];
27
27
  }
28
28
  // ===========================================================================
29
+ // Raw Operator
30
+ // ===========================================================================
31
+ /**
32
+ * Append a raw PDF operator string to the content stream.
33
+ * Use this for operators not covered by the typed API (e.g. `d1` for Type3 glyphs).
34
+ */
35
+ raw(operator) {
36
+ this.parts.push(operator);
37
+ return this;
38
+ }
39
+ // ===========================================================================
29
40
  // Graphics State
30
41
  // ===========================================================================
31
42
  /**
@@ -502,7 +513,9 @@ const UNICODE_TO_WINANSI = new Map([
502
513
  ]);
503
514
  /**
504
515
  * Convert a Unicode code point to a WinAnsi byte value.
505
- * Returns 0x3F ('?') for unmappable characters.
516
+ * Returns 0x20 (space) for unmappable characters — standard Type1 fonts
517
+ * do not contain glyphs outside the WinAnsi repertoire, so a space is
518
+ * less misleading than a literal '?'.
506
519
  */
507
520
  function unicodeToWinAnsi(cp) {
508
521
  // Direct mapping for Latin-1 range (0x00-0xFF), excluding 0x80-0x9F
@@ -517,6 +530,37 @@ function unicodeToWinAnsi(cp) {
517
530
  if (mapped !== undefined) {
518
531
  return mapped;
519
532
  }
520
- // Unmappable — use '?'
521
- return 0x3f;
533
+ // Unmappable — use space instead of '?' to avoid misleading output.
534
+ // Full Unicode support requires an embedded TrueType font (the `font`
535
+ // option in PdfExportOptions).
536
+ return 0x20;
537
+ }
538
+ /**
539
+ * Check whether a single code point is representable in WinAnsi encoding.
540
+ */
541
+ export function isWinAnsiCodePoint(cp) {
542
+ if (cp < 0x80) {
543
+ return true;
544
+ }
545
+ if (cp >= 0xa0 && cp <= 0xff) {
546
+ return true;
547
+ }
548
+ return UNICODE_TO_WINANSI.has(cp);
549
+ }
550
+ /**
551
+ * Check whether a string contains characters outside the WinAnsi repertoire.
552
+ * When true, standard Type1 fonts cannot render those characters and an
553
+ * embedded TrueType font is required for correct output.
554
+ */
555
+ export function hasNonWinAnsiChars(text) {
556
+ for (let i = 0; i < text.length; i++) {
557
+ const cp = text.codePointAt(i);
558
+ if (cp > 0xffff) {
559
+ i++;
560
+ }
561
+ if (!isWinAnsiCodePoint(cp)) {
562
+ return true;
563
+ }
564
+ }
565
+ return false;
522
566
  }
@@ -1,18 +1,20 @@
1
1
  /**
2
2
  * Font manager for PDF generation.
3
3
  *
4
- * Manages two kinds of fonts:
4
+ * Manages three kinds of fonts:
5
5
  * 1. **Standard Type1 fonts** (Helvetica, Times, Courier) — always available,
6
- * used as fallback for Latin text when no embedded font is provided.
6
+ * used for Latin text (WinAnsi repertoire) when no embedded font is provided.
7
7
  * 2. **Embedded TrueType fonts** — user-provided .ttf files for full
8
8
  * Unicode support (CJK, Arabic, Hindi, etc.)
9
+ * 3. **Type3 fallback fonts** — auto-generated vector-drawn glyphs for
10
+ * Unicode characters outside WinAnsi when no embedded font is provided.
9
11
  *
10
12
  * When an embedded font is registered, ALL text uses the embedded font.
11
- * When no embedded font is provided, the system falls back to standard fonts
12
- * exactly as before (mapping Calibri→Helvetica, etc.)
13
+ * When no embedded font is provided, the system uses Type1 for WinAnsi
14
+ * characters and Type3 for everything else.
13
15
  *
14
16
  * The manager tracks which Unicode code points are used so the font embedder
15
- * can create a minimal subset when writing the PDF.
17
+ * and Type3 builder can create minimal subsets when writing the PDF.
16
18
  */
17
19
  import type { PdfWriter } from "../core/pdf-writer.js";
18
20
  import { type EmbeddedFont } from "./font-embedder.js";
@@ -23,7 +25,8 @@ import type { TtfFont } from "./ttf-parser.js";
23
25
  export declare function resolvePdfFontName(fontFamily: string, bold: boolean, italic: boolean): string;
24
26
  /**
25
27
  * Manages PDF font resources for a document.
26
- * Supports both standard Type1 fonts and embedded TrueType fonts.
28
+ * Supports standard Type1 fonts, embedded TrueType fonts, and auto-generated
29
+ * Type3 fallback fonts for non-WinAnsi Unicode characters.
27
30
  */
28
31
  export declare class FontManager {
29
32
  private type1Map;
@@ -33,6 +36,8 @@ export declare class FontManager {
33
36
  private embeddedResourceName;
34
37
  private usedCodePoints;
35
38
  private nextEmbeddedId;
39
+ private type3CodePoints;
40
+ private _type3Result;
36
41
  /**
37
42
  * Register an embedded TrueType font for use.
38
43
  * When set, all text rendering uses this font instead of standard fonts.
@@ -65,8 +70,25 @@ export declare class FontManager {
65
70
  * Get the PDF font name for a given resource name.
66
71
  */
67
72
  getPdfFontName(resourceName: string): string;
73
+ /**
74
+ * Check if Type3 fallback fonts are available (after writeFontResources).
75
+ */
76
+ hasType3Fonts(): boolean;
77
+ /**
78
+ * Resolve the Type3 font resource name and char code for a code point.
79
+ * Returns null if the code point is not in the Type3 encoding.
80
+ */
81
+ resolveType3(codePoint: number): {
82
+ resourceName: string;
83
+ charCode: number;
84
+ } | null;
85
+ /**
86
+ * Check if a code point needs Type3 rendering (non-WinAnsi, no embedded font).
87
+ */
88
+ needsType3(codePoint: number): boolean;
68
89
  /**
69
90
  * Measure text width using the correct font metrics.
91
+ * For mixed Type1/Type3 text, measures each character with the right font.
70
92
  */
71
93
  measureText(text: string, resourceName: string, fontSize: number): number;
72
94
  /**
@@ -85,6 +107,10 @@ export declare class FontManager {
85
107
  * Check if a resource name refers to an embedded font.
86
108
  */
87
109
  isEmbeddedFont(resourceName: string): boolean;
110
+ /**
111
+ * Check if a resource name refers to a Type3 fallback font.
112
+ */
113
+ isType3Resource(resourceName: string): boolean;
88
114
  /**
89
115
  * Encode text for the given font resource.
90
116
  * For embedded fonts, returns a hex string `<0012003A...>`.
@@ -94,6 +120,11 @@ export declare class FontManager {
94
120
  * subset and produces the unicodeToCid mapping.
95
121
  */
96
122
  encodeText(text: string, resourceName: string): string | null;
123
+ /**
124
+ * Encode a single character for a Type3 font.
125
+ * Returns a hex string `<XX>` suitable for the Tj operator.
126
+ */
127
+ encodeType3Char(codePoint: number): string | null;
97
128
  /**
98
129
  * Write all font resource objects to the PDF.
99
130
  * Returns a map from resource name → object number.
@@ -1,23 +1,27 @@
1
1
  /**
2
2
  * Font manager for PDF generation.
3
3
  *
4
- * Manages two kinds of fonts:
4
+ * Manages three kinds of fonts:
5
5
  * 1. **Standard Type1 fonts** (Helvetica, Times, Courier) — always available,
6
- * used as fallback for Latin text when no embedded font is provided.
6
+ * used for Latin text (WinAnsi repertoire) when no embedded font is provided.
7
7
  * 2. **Embedded TrueType fonts** — user-provided .ttf files for full
8
8
  * Unicode support (CJK, Arabic, Hindi, etc.)
9
+ * 3. **Type3 fallback fonts** — auto-generated vector-drawn glyphs for
10
+ * Unicode characters outside WinAnsi when no embedded font is provided.
9
11
  *
10
12
  * When an embedded font is registered, ALL text uses the embedded font.
11
- * When no embedded font is provided, the system falls back to standard fonts
12
- * exactly as before (mapping Calibri→Helvetica, etc.)
13
+ * When no embedded font is provided, the system uses Type1 for WinAnsi
14
+ * characters and Type3 for everything else.
13
15
  *
14
16
  * The manager tracks which Unicode code points are used so the font embedder
15
- * can create a minimal subset when writing the PDF.
17
+ * and Type3 builder can create minimal subsets when writing the PDF.
16
18
  */
17
19
  import { PdfDict, pdfName, pdfRef } from "../core/pdf-object.js";
20
+ import { hasNonWinAnsiChars, isWinAnsiCodePoint } from "../core/pdf-stream.js";
18
21
  import { PdfFontError } from "../errors.js";
19
22
  import { embedTtfFont } from "./font-embedder.js";
20
23
  import { measureText as measureType1Text, getFontAscent as getType1Ascent, getFontDescent as getType1Descent, getLineHeight as getType1LineHeight } from "./metrics.js";
24
+ import { writeType3Fonts } from "./type3-font.js";
21
25
  // =============================================================================
22
26
  // Font Name Mapping (Type1 fallback)
23
27
  // =============================================================================
@@ -104,7 +108,8 @@ export function resolvePdfFontName(fontFamily, bold, italic) {
104
108
  // =============================================================================
105
109
  /**
106
110
  * Manages PDF font resources for a document.
107
- * Supports both standard Type1 fonts and embedded TrueType fonts.
111
+ * Supports standard Type1 fonts, embedded TrueType fonts, and auto-generated
112
+ * Type3 fallback fonts for non-WinAnsi Unicode characters.
108
113
  */
109
114
  export class FontManager {
110
115
  constructor() {
@@ -117,6 +122,9 @@ export class FontManager {
117
122
  this.embeddedResourceName = "";
118
123
  this.usedCodePoints = new Set();
119
124
  this.nextEmbeddedId = 1;
125
+ // --- Type3 fallback font tracking ---
126
+ this.type3CodePoints = new Set();
127
+ this._type3Result = null;
120
128
  /** Stored after writeFontResources is called */
121
129
  this._embeddedResult = null;
122
130
  }
@@ -149,14 +157,25 @@ export class FontManager {
149
157
  * Must be called for every text string before writing the PDF.
150
158
  */
151
159
  trackText(text) {
152
- if (!this.embeddedFont) {
153
- return;
160
+ if (this.embeddedFont) {
161
+ for (let i = 0; i < text.length; i++) {
162
+ const cp = text.codePointAt(i);
163
+ this.usedCodePoints.add(cp);
164
+ if (cp > 0xffff) {
165
+ i++; // skip low surrogate
166
+ }
167
+ }
154
168
  }
155
- for (let i = 0; i < text.length; i++) {
156
- const cp = text.codePointAt(i);
157
- this.usedCodePoints.add(cp);
158
- if (cp > 0xffff) {
159
- i++; // skip low surrogate
169
+ else {
170
+ // No embedded font — track non-WinAnsi chars for Type3 fallback
171
+ for (let i = 0; i < text.length; i++) {
172
+ const cp = text.codePointAt(i);
173
+ if (cp > 0xffff) {
174
+ i++;
175
+ }
176
+ if (!isWinAnsiCodePoint(cp)) {
177
+ this.type3CodePoints.add(cp);
178
+ }
160
179
  }
161
180
  }
162
181
  }
@@ -194,17 +213,72 @@ export class FontManager {
194
213
  return this.resourceToType1.get(resourceName) ?? "Helvetica";
195
214
  }
196
215
  // ==========================================================================
216
+ // Type3 Fallback Font
217
+ // ==========================================================================
218
+ /**
219
+ * Check if Type3 fallback fonts are available (after writeFontResources).
220
+ */
221
+ hasType3Fonts() {
222
+ return this._type3Result !== null && this._type3Result.fontObjects.size > 0;
223
+ }
224
+ /**
225
+ * Resolve the Type3 font resource name and char code for a code point.
226
+ * Returns null if the code point is not in the Type3 encoding.
227
+ */
228
+ resolveType3(codePoint) {
229
+ if (!this._type3Result) {
230
+ return null;
231
+ }
232
+ return this._type3Result.encoding.get(codePoint) ?? null;
233
+ }
234
+ /**
235
+ * Check if a code point needs Type3 rendering (non-WinAnsi, no embedded font).
236
+ */
237
+ needsType3(codePoint) {
238
+ return !this.embeddedFont && !isWinAnsiCodePoint(codePoint);
239
+ }
240
+ // ==========================================================================
197
241
  // Text Measurement
198
242
  // ==========================================================================
199
243
  /**
200
244
  * Measure text width using the correct font metrics.
245
+ * For mixed Type1/Type3 text, measures each character with the right font.
201
246
  */
202
247
  measureText(text, resourceName, fontSize) {
203
248
  if (this.embeddedFont && resourceName === this.embeddedResourceName) {
204
249
  return measureEmbeddedText(text, this.embeddedFont, fontSize);
205
250
  }
251
+ // If no Type3 fonts or text has no non-WinAnsi chars, use Type1 directly
252
+ if (!this._type3Result || !hasNonWinAnsiChars(text)) {
253
+ const pdfFontName = this.getPdfFontName(resourceName);
254
+ return measureType1Text(text, pdfFontName, fontSize);
255
+ }
256
+ // Mixed text: measure char by char
257
+ let totalWidth = 0;
206
258
  const pdfFontName = this.getPdfFontName(resourceName);
207
- return measureType1Text(text, pdfFontName, fontSize);
259
+ for (let i = 0; i < text.length; i++) {
260
+ const cp = text.codePointAt(i);
261
+ if (cp > 0xffff) {
262
+ i++;
263
+ }
264
+ if (isWinAnsiCodePoint(cp)) {
265
+ totalWidth += measureType1Text(String.fromCodePoint(cp), pdfFontName, fontSize);
266
+ }
267
+ else {
268
+ // Type3 character width
269
+ const t3 = this._type3Result.encoding.get(cp);
270
+ if (t3) {
271
+ const widthMap = this._type3Result.widths.get(t3.resourceName);
272
+ const glyphWidth = widthMap?.get(t3.charCode) ?? 600;
273
+ totalWidth += (glyphWidth / 1000) * fontSize;
274
+ }
275
+ else {
276
+ // Notdef width
277
+ totalWidth += (600 / 1000) * fontSize;
278
+ }
279
+ }
280
+ }
281
+ return totalWidth;
208
282
  }
209
283
  /**
210
284
  * Get the font ascent in points.
@@ -213,7 +287,11 @@ export class FontManager {
213
287
  if (this.embeddedFont && resourceName === this.embeddedResourceName) {
214
288
  return (this.embeddedFont.ascent / this.embeddedFont.unitsPerEm) * fontSize;
215
289
  }
216
- return getType1Ascent(this.getPdfFontName(resourceName), fontSize);
290
+ // Type3 fonts use the same metrics as the base Type1 font
291
+ const base = this.isType3Resource(resourceName)
292
+ ? "Helvetica"
293
+ : this.getPdfFontName(resourceName);
294
+ return getType1Ascent(base, fontSize);
217
295
  }
218
296
  /**
219
297
  * Get the font descent in points (negative value).
@@ -222,7 +300,10 @@ export class FontManager {
222
300
  if (this.embeddedFont && resourceName === this.embeddedResourceName) {
223
301
  return (this.embeddedFont.descent / this.embeddedFont.unitsPerEm) * fontSize;
224
302
  }
225
- return getType1Descent(this.getPdfFontName(resourceName), fontSize);
303
+ const base = this.isType3Resource(resourceName)
304
+ ? "Helvetica"
305
+ : this.getPdfFontName(resourceName);
306
+ return getType1Descent(base, fontSize);
226
307
  }
227
308
  /**
228
309
  * Get the line height in points.
@@ -232,7 +313,10 @@ export class FontManager {
232
313
  const f = this.embeddedFont;
233
314
  return ((f.ascent - f.descent) / f.unitsPerEm) * fontSize;
234
315
  }
235
- return getType1LineHeight(this.getPdfFontName(resourceName), fontSize);
316
+ const base = this.isType3Resource(resourceName)
317
+ ? "Helvetica"
318
+ : this.getPdfFontName(resourceName);
319
+ return getType1LineHeight(base, fontSize);
236
320
  }
237
321
  // ==========================================================================
238
322
  // Text Encoding
@@ -243,6 +327,12 @@ export class FontManager {
243
327
  isEmbeddedFont(resourceName) {
244
328
  return this.embeddedFont !== null && resourceName === this.embeddedResourceName;
245
329
  }
330
+ /**
331
+ * Check if a resource name refers to a Type3 fallback font.
332
+ */
333
+ isType3Resource(resourceName) {
334
+ return this._type3Result?.fontObjects.has(resourceName) ?? false;
335
+ }
246
336
  /**
247
337
  * Encode text for the given font resource.
248
338
  * For embedded fonts, returns a hex string `<0012003A...>`.
@@ -263,6 +353,21 @@ export class FontManager {
263
353
  // writeFontResources not called yet — this is a programming error
264
354
  throw new PdfFontError("encodeText called before writeFontResources — subset mapping not available");
265
355
  }
356
+ /**
357
+ * Encode a single character for a Type3 font.
358
+ * Returns a hex string `<XX>` suitable for the Tj operator.
359
+ */
360
+ encodeType3Char(codePoint) {
361
+ if (!this._type3Result) {
362
+ return null;
363
+ }
364
+ const entry = this._type3Result.encoding.get(codePoint);
365
+ if (!entry) {
366
+ return null;
367
+ }
368
+ const hex = entry.charCode.toString(16).toUpperCase().padStart(2, "0");
369
+ return `<${hex}>`;
370
+ }
266
371
  // ==========================================================================
267
372
  // PDF Object Writing
268
373
  // ==========================================================================
@@ -290,6 +395,13 @@ export class FontManager {
290
395
  // Store the embedding result for text re-encoding
291
396
  this._embeddedResult = embedded;
292
397
  }
398
+ // Write Type3 fallback fonts (only when no embedded font)
399
+ if (!this.embeddedFont && this.type3CodePoints.size > 0) {
400
+ this._type3Result = writeType3Fonts(writer, this.type3CodePoints);
401
+ for (const [resourceName, objNum] of this._type3Result.fontObjects) {
402
+ fontObjectMap.set(resourceName, objNum);
403
+ }
404
+ }
293
405
  return fontObjectMap;
294
406
  }
295
407
  /**
@@ -0,0 +1,41 @@
1
+ /**
2
+ * System font discovery for PDF generation.
3
+ *
4
+ * When no embedded font is provided and the document contains non-WinAnsi
5
+ * characters, this module searches standard system font directories for a
6
+ * TrueType font (.ttf or .ttc) with broad Unicode coverage.
7
+ *
8
+ * This is a Node.js-only feature — browser environments do not have
9
+ * file system access and must always provide fonts explicitly.
10
+ *
11
+ * .ttc (TrueType Collection) files are supported — parseTtf() extracts
12
+ * the first font from the collection automatically.
13
+ *
14
+ * Results are cached: the filesystem search runs only once per process.
15
+ */
16
+ /**
17
+ * Return all discoverable system font candidates, ordered by preference.
18
+ *
19
+ * Each entry is the raw font file bytes of a `.ttf` or `.ttc` file.
20
+ * The caller decides which candidate to use (e.g. by checking cmap coverage).
21
+ *
22
+ * Results are cached — the filesystem scan runs only once per process.
23
+ */
24
+ export declare function discoverSystemFontCandidates(): Uint8Array[];
25
+ /**
26
+ * Search for a system font suitable for Unicode rendering.
27
+ *
28
+ * Returns the raw font file bytes of the highest-priority candidate,
29
+ * or `null` if no font was found. This is a convenience wrapper around
30
+ * {@link discoverSystemFontCandidates}.
31
+ */
32
+ export declare function discoverSystemFont(): Uint8Array | null;
33
+ /**
34
+ * Reset the cached font discovery result (for testing).
35
+ */
36
+ export declare function resetFontDiscoveryCache(): void;
37
+ /**
38
+ * Override the cached candidates with a custom list (for testing).
39
+ * Call {@link resetFontDiscoveryCache} to clear the override.
40
+ */
41
+ export declare function _setCandidatesForTest(candidates: Uint8Array[]): void;