@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.
- package/dist/browser/modules/pdf/builder/document-builder.js +4 -23
- package/dist/browser/modules/pdf/core/pdf-stream.d.ts +15 -0
- package/dist/browser/modules/pdf/core/pdf-stream.js +47 -3
- package/dist/browser/modules/pdf/font/font-manager.d.ts +37 -6
- package/dist/browser/modules/pdf/font/font-manager.js +129 -17
- package/dist/browser/modules/pdf/font/system-fonts.d.ts +41 -0
- package/dist/browser/modules/pdf/font/system-fonts.js +188 -0
- package/dist/browser/modules/pdf/font/ttf-parser.js +29 -1
- package/dist/browser/modules/pdf/font/type3-font.d.ts +35 -0
- package/dist/browser/modules/pdf/font/type3-font.js +228 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-extended.d.ts +33 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-extended.js +4164 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-extended2.d.ts +16 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-extended2.js +9649 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-fill.d.ts +17 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-fill.js +5438 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-quality.d.ts +28 -0
- package/dist/browser/modules/pdf/font/type3-glyphs-quality.js +5345 -0
- package/dist/browser/modules/pdf/font/type3-glyphs.d.ts +79 -0
- package/dist/browser/modules/pdf/font/type3-glyphs.js +2567 -0
- package/dist/browser/modules/pdf/render/layout-engine.js +36 -23
- package/dist/browser/modules/pdf/render/page-renderer.d.ts +9 -0
- package/dist/browser/modules/pdf/render/page-renderer.js +110 -78
- package/dist/browser/modules/pdf/render/pdf-exporter.js +73 -5
- package/dist/cjs/modules/pdf/builder/document-builder.js +3 -22
- package/dist/cjs/modules/pdf/core/pdf-stream.js +49 -3
- package/dist/cjs/modules/pdf/font/font-manager.js +129 -17
- package/dist/cjs/modules/pdf/font/system-fonts.js +194 -0
- package/dist/cjs/modules/pdf/font/ttf-parser.js +29 -1
- package/dist/cjs/modules/pdf/font/type3-font.js +231 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs-extended.js +4167 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs-extended2.js +9652 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs-fill.js +5441 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs-quality.js +5348 -0
- package/dist/cjs/modules/pdf/font/type3-glyphs.js +2573 -0
- package/dist/cjs/modules/pdf/render/layout-engine.js +36 -23
- package/dist/cjs/modules/pdf/render/page-renderer.js +111 -78
- package/dist/cjs/modules/pdf/render/pdf-exporter.js +71 -3
- package/dist/esm/modules/pdf/builder/document-builder.js +4 -23
- package/dist/esm/modules/pdf/core/pdf-stream.js +47 -3
- package/dist/esm/modules/pdf/font/font-manager.js +129 -17
- package/dist/esm/modules/pdf/font/system-fonts.js +188 -0
- package/dist/esm/modules/pdf/font/ttf-parser.js +29 -1
- package/dist/esm/modules/pdf/font/type3-font.js +228 -0
- package/dist/esm/modules/pdf/font/type3-glyphs-extended.js +4164 -0
- package/dist/esm/modules/pdf/font/type3-glyphs-extended2.js +9649 -0
- package/dist/esm/modules/pdf/font/type3-glyphs-fill.js +5438 -0
- package/dist/esm/modules/pdf/font/type3-glyphs-quality.js +5345 -0
- package/dist/esm/modules/pdf/font/type3-glyphs.js +2567 -0
- package/dist/esm/modules/pdf/render/layout-engine.js +36 -23
- package/dist/esm/modules/pdf/render/page-renderer.js +110 -78
- package/dist/esm/modules/pdf/render/pdf-exporter.js +73 -5
- package/dist/iife/excelts.iife.js +25445 -344
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +48 -46
- package/dist/types/modules/pdf/core/pdf-stream.d.ts +15 -0
- package/dist/types/modules/pdf/font/font-manager.d.ts +37 -6
- package/dist/types/modules/pdf/font/system-fonts.d.ts +41 -0
- package/dist/types/modules/pdf/font/type3-font.d.ts +35 -0
- package/dist/types/modules/pdf/font/type3-glyphs-extended.d.ts +33 -0
- package/dist/types/modules/pdf/font/type3-glyphs-extended2.d.ts +16 -0
- package/dist/types/modules/pdf/font/type3-glyphs-fill.d.ts +17 -0
- package/dist/types/modules/pdf/font/type3-glyphs-quality.d.ts +28 -0
- package/dist/types/modules/pdf/font/type3-glyphs.d.ts +79 -0
- package/dist/types/modules/pdf/render/page-renderer.d.ts +9 -0
- package/package.json +1 -1
|
@@ -2,26 +2,30 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Font manager for PDF generation.
|
|
4
4
|
*
|
|
5
|
-
* Manages
|
|
5
|
+
* Manages three kinds of fonts:
|
|
6
6
|
* 1. **Standard Type1 fonts** (Helvetica, Times, Courier) — always available,
|
|
7
|
-
* used
|
|
7
|
+
* used for Latin text (WinAnsi repertoire) when no embedded font is provided.
|
|
8
8
|
* 2. **Embedded TrueType fonts** — user-provided .ttf files for full
|
|
9
9
|
* Unicode support (CJK, Arabic, Hindi, etc.)
|
|
10
|
+
* 3. **Type3 fallback fonts** — auto-generated vector-drawn glyphs for
|
|
11
|
+
* Unicode characters outside WinAnsi when no embedded font is provided.
|
|
10
12
|
*
|
|
11
13
|
* When an embedded font is registered, ALL text uses the embedded font.
|
|
12
|
-
* When no embedded font is provided, the system
|
|
13
|
-
*
|
|
14
|
+
* When no embedded font is provided, the system uses Type1 for WinAnsi
|
|
15
|
+
* characters and Type3 for everything else.
|
|
14
16
|
*
|
|
15
17
|
* The manager tracks which Unicode code points are used so the font embedder
|
|
16
|
-
* can create
|
|
18
|
+
* and Type3 builder can create minimal subsets when writing the PDF.
|
|
17
19
|
*/
|
|
18
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
21
|
exports.FontManager = void 0;
|
|
20
22
|
exports.resolvePdfFontName = resolvePdfFontName;
|
|
21
23
|
const pdf_object_1 = require("../core/pdf-object");
|
|
24
|
+
const pdf_stream_1 = require("../core/pdf-stream");
|
|
22
25
|
const errors_1 = require("../errors");
|
|
23
26
|
const font_embedder_1 = require("./font-embedder");
|
|
24
27
|
const metrics_1 = require("./metrics");
|
|
28
|
+
const type3_font_1 = require("./type3-font");
|
|
25
29
|
// =============================================================================
|
|
26
30
|
// Font Name Mapping (Type1 fallback)
|
|
27
31
|
// =============================================================================
|
|
@@ -108,7 +112,8 @@ function resolvePdfFontName(fontFamily, bold, italic) {
|
|
|
108
112
|
// =============================================================================
|
|
109
113
|
/**
|
|
110
114
|
* Manages PDF font resources for a document.
|
|
111
|
-
* Supports
|
|
115
|
+
* Supports standard Type1 fonts, embedded TrueType fonts, and auto-generated
|
|
116
|
+
* Type3 fallback fonts for non-WinAnsi Unicode characters.
|
|
112
117
|
*/
|
|
113
118
|
class FontManager {
|
|
114
119
|
constructor() {
|
|
@@ -121,6 +126,9 @@ class FontManager {
|
|
|
121
126
|
this.embeddedResourceName = "";
|
|
122
127
|
this.usedCodePoints = new Set();
|
|
123
128
|
this.nextEmbeddedId = 1;
|
|
129
|
+
// --- Type3 fallback font tracking ---
|
|
130
|
+
this.type3CodePoints = new Set();
|
|
131
|
+
this._type3Result = null;
|
|
124
132
|
/** Stored after writeFontResources is called */
|
|
125
133
|
this._embeddedResult = null;
|
|
126
134
|
}
|
|
@@ -153,14 +161,25 @@ class FontManager {
|
|
|
153
161
|
* Must be called for every text string before writing the PDF.
|
|
154
162
|
*/
|
|
155
163
|
trackText(text) {
|
|
156
|
-
if (
|
|
157
|
-
|
|
164
|
+
if (this.embeddedFont) {
|
|
165
|
+
for (let i = 0; i < text.length; i++) {
|
|
166
|
+
const cp = text.codePointAt(i);
|
|
167
|
+
this.usedCodePoints.add(cp);
|
|
168
|
+
if (cp > 0xffff) {
|
|
169
|
+
i++; // skip low surrogate
|
|
170
|
+
}
|
|
171
|
+
}
|
|
158
172
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
173
|
+
else {
|
|
174
|
+
// No embedded font — track non-WinAnsi chars for Type3 fallback
|
|
175
|
+
for (let i = 0; i < text.length; i++) {
|
|
176
|
+
const cp = text.codePointAt(i);
|
|
177
|
+
if (cp > 0xffff) {
|
|
178
|
+
i++;
|
|
179
|
+
}
|
|
180
|
+
if (!(0, pdf_stream_1.isWinAnsiCodePoint)(cp)) {
|
|
181
|
+
this.type3CodePoints.add(cp);
|
|
182
|
+
}
|
|
164
183
|
}
|
|
165
184
|
}
|
|
166
185
|
}
|
|
@@ -198,17 +217,72 @@ class FontManager {
|
|
|
198
217
|
return this.resourceToType1.get(resourceName) ?? "Helvetica";
|
|
199
218
|
}
|
|
200
219
|
// ==========================================================================
|
|
220
|
+
// Type3 Fallback Font
|
|
221
|
+
// ==========================================================================
|
|
222
|
+
/**
|
|
223
|
+
* Check if Type3 fallback fonts are available (after writeFontResources).
|
|
224
|
+
*/
|
|
225
|
+
hasType3Fonts() {
|
|
226
|
+
return this._type3Result !== null && this._type3Result.fontObjects.size > 0;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Resolve the Type3 font resource name and char code for a code point.
|
|
230
|
+
* Returns null if the code point is not in the Type3 encoding.
|
|
231
|
+
*/
|
|
232
|
+
resolveType3(codePoint) {
|
|
233
|
+
if (!this._type3Result) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
return this._type3Result.encoding.get(codePoint) ?? null;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Check if a code point needs Type3 rendering (non-WinAnsi, no embedded font).
|
|
240
|
+
*/
|
|
241
|
+
needsType3(codePoint) {
|
|
242
|
+
return !this.embeddedFont && !(0, pdf_stream_1.isWinAnsiCodePoint)(codePoint);
|
|
243
|
+
}
|
|
244
|
+
// ==========================================================================
|
|
201
245
|
// Text Measurement
|
|
202
246
|
// ==========================================================================
|
|
203
247
|
/**
|
|
204
248
|
* Measure text width using the correct font metrics.
|
|
249
|
+
* For mixed Type1/Type3 text, measures each character with the right font.
|
|
205
250
|
*/
|
|
206
251
|
measureText(text, resourceName, fontSize) {
|
|
207
252
|
if (this.embeddedFont && resourceName === this.embeddedResourceName) {
|
|
208
253
|
return measureEmbeddedText(text, this.embeddedFont, fontSize);
|
|
209
254
|
}
|
|
255
|
+
// If no Type3 fonts or text has no non-WinAnsi chars, use Type1 directly
|
|
256
|
+
if (!this._type3Result || !(0, pdf_stream_1.hasNonWinAnsiChars)(text)) {
|
|
257
|
+
const pdfFontName = this.getPdfFontName(resourceName);
|
|
258
|
+
return (0, metrics_1.measureText)(text, pdfFontName, fontSize);
|
|
259
|
+
}
|
|
260
|
+
// Mixed text: measure char by char
|
|
261
|
+
let totalWidth = 0;
|
|
210
262
|
const pdfFontName = this.getPdfFontName(resourceName);
|
|
211
|
-
|
|
263
|
+
for (let i = 0; i < text.length; i++) {
|
|
264
|
+
const cp = text.codePointAt(i);
|
|
265
|
+
if (cp > 0xffff) {
|
|
266
|
+
i++;
|
|
267
|
+
}
|
|
268
|
+
if ((0, pdf_stream_1.isWinAnsiCodePoint)(cp)) {
|
|
269
|
+
totalWidth += (0, metrics_1.measureText)(String.fromCodePoint(cp), pdfFontName, fontSize);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// Type3 character width
|
|
273
|
+
const t3 = this._type3Result.encoding.get(cp);
|
|
274
|
+
if (t3) {
|
|
275
|
+
const widthMap = this._type3Result.widths.get(t3.resourceName);
|
|
276
|
+
const glyphWidth = widthMap?.get(t3.charCode) ?? 600;
|
|
277
|
+
totalWidth += (glyphWidth / 1000) * fontSize;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Notdef width
|
|
281
|
+
totalWidth += (600 / 1000) * fontSize;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return totalWidth;
|
|
212
286
|
}
|
|
213
287
|
/**
|
|
214
288
|
* Get the font ascent in points.
|
|
@@ -217,7 +291,11 @@ class FontManager {
|
|
|
217
291
|
if (this.embeddedFont && resourceName === this.embeddedResourceName) {
|
|
218
292
|
return (this.embeddedFont.ascent / this.embeddedFont.unitsPerEm) * fontSize;
|
|
219
293
|
}
|
|
220
|
-
|
|
294
|
+
// Type3 fonts use the same metrics as the base Type1 font
|
|
295
|
+
const base = this.isType3Resource(resourceName)
|
|
296
|
+
? "Helvetica"
|
|
297
|
+
: this.getPdfFontName(resourceName);
|
|
298
|
+
return (0, metrics_1.getFontAscent)(base, fontSize);
|
|
221
299
|
}
|
|
222
300
|
/**
|
|
223
301
|
* Get the font descent in points (negative value).
|
|
@@ -226,7 +304,10 @@ class FontManager {
|
|
|
226
304
|
if (this.embeddedFont && resourceName === this.embeddedResourceName) {
|
|
227
305
|
return (this.embeddedFont.descent / this.embeddedFont.unitsPerEm) * fontSize;
|
|
228
306
|
}
|
|
229
|
-
|
|
307
|
+
const base = this.isType3Resource(resourceName)
|
|
308
|
+
? "Helvetica"
|
|
309
|
+
: this.getPdfFontName(resourceName);
|
|
310
|
+
return (0, metrics_1.getFontDescent)(base, fontSize);
|
|
230
311
|
}
|
|
231
312
|
/**
|
|
232
313
|
* Get the line height in points.
|
|
@@ -236,7 +317,10 @@ class FontManager {
|
|
|
236
317
|
const f = this.embeddedFont;
|
|
237
318
|
return ((f.ascent - f.descent) / f.unitsPerEm) * fontSize;
|
|
238
319
|
}
|
|
239
|
-
|
|
320
|
+
const base = this.isType3Resource(resourceName)
|
|
321
|
+
? "Helvetica"
|
|
322
|
+
: this.getPdfFontName(resourceName);
|
|
323
|
+
return (0, metrics_1.getLineHeight)(base, fontSize);
|
|
240
324
|
}
|
|
241
325
|
// ==========================================================================
|
|
242
326
|
// Text Encoding
|
|
@@ -247,6 +331,12 @@ class FontManager {
|
|
|
247
331
|
isEmbeddedFont(resourceName) {
|
|
248
332
|
return this.embeddedFont !== null && resourceName === this.embeddedResourceName;
|
|
249
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Check if a resource name refers to a Type3 fallback font.
|
|
336
|
+
*/
|
|
337
|
+
isType3Resource(resourceName) {
|
|
338
|
+
return this._type3Result?.fontObjects.has(resourceName) ?? false;
|
|
339
|
+
}
|
|
250
340
|
/**
|
|
251
341
|
* Encode text for the given font resource.
|
|
252
342
|
* For embedded fonts, returns a hex string `<0012003A...>`.
|
|
@@ -267,6 +357,21 @@ class FontManager {
|
|
|
267
357
|
// writeFontResources not called yet — this is a programming error
|
|
268
358
|
throw new errors_1.PdfFontError("encodeText called before writeFontResources — subset mapping not available");
|
|
269
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Encode a single character for a Type3 font.
|
|
362
|
+
* Returns a hex string `<XX>` suitable for the Tj operator.
|
|
363
|
+
*/
|
|
364
|
+
encodeType3Char(codePoint) {
|
|
365
|
+
if (!this._type3Result) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const entry = this._type3Result.encoding.get(codePoint);
|
|
369
|
+
if (!entry) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const hex = entry.charCode.toString(16).toUpperCase().padStart(2, "0");
|
|
373
|
+
return `<${hex}>`;
|
|
374
|
+
}
|
|
270
375
|
// ==========================================================================
|
|
271
376
|
// PDF Object Writing
|
|
272
377
|
// ==========================================================================
|
|
@@ -294,6 +399,13 @@ class FontManager {
|
|
|
294
399
|
// Store the embedding result for text re-encoding
|
|
295
400
|
this._embeddedResult = embedded;
|
|
296
401
|
}
|
|
402
|
+
// Write Type3 fallback fonts (only when no embedded font)
|
|
403
|
+
if (!this.embeddedFont && this.type3CodePoints.size > 0) {
|
|
404
|
+
this._type3Result = (0, type3_font_1.writeType3Fonts)(writer, this.type3CodePoints);
|
|
405
|
+
for (const [resourceName, objNum] of this._type3Result.fontObjects) {
|
|
406
|
+
fontObjectMap.set(resourceName, objNum);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
297
409
|
return fontObjectMap;
|
|
298
410
|
}
|
|
299
411
|
/**
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* System font discovery for PDF generation.
|
|
4
|
+
*
|
|
5
|
+
* When no embedded font is provided and the document contains non-WinAnsi
|
|
6
|
+
* characters, this module searches standard system font directories for a
|
|
7
|
+
* TrueType font (.ttf or .ttc) with broad Unicode coverage.
|
|
8
|
+
*
|
|
9
|
+
* This is a Node.js-only feature — browser environments do not have
|
|
10
|
+
* file system access and must always provide fonts explicitly.
|
|
11
|
+
*
|
|
12
|
+
* .ttc (TrueType Collection) files are supported — parseTtf() extracts
|
|
13
|
+
* the first font from the collection automatically.
|
|
14
|
+
*
|
|
15
|
+
* Results are cached: the filesystem search runs only once per process.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.discoverSystemFontCandidates = discoverSystemFontCandidates;
|
|
19
|
+
exports.discoverSystemFont = discoverSystemFont;
|
|
20
|
+
exports.resetFontDiscoveryCache = resetFontDiscoveryCache;
|
|
21
|
+
exports._setCandidatesForTest = _setCandidatesForTest;
|
|
22
|
+
const fs_1 = require("../../../utils/fs.js");
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Platform Font Directories
|
|
25
|
+
// =============================================================================
|
|
26
|
+
function getSystemFontDirs() {
|
|
27
|
+
const platform = typeof process !== "undefined" ? process.platform : "";
|
|
28
|
+
const home = typeof process !== "undefined" ? (process.env.HOME ?? process.env.USERPROFILE ?? "") : "";
|
|
29
|
+
const dirs = [];
|
|
30
|
+
switch (platform) {
|
|
31
|
+
case "darwin":
|
|
32
|
+
dirs.push("/System/Library/Fonts", "/System/Library/Fonts/Supplemental", "/Library/Fonts", `${home}/Library/Fonts`);
|
|
33
|
+
break;
|
|
34
|
+
case "win32": {
|
|
35
|
+
const winDir = process.env.WINDIR ?? process.env.SystemRoot ?? "C:\\Windows";
|
|
36
|
+
dirs.push(`${winDir}\\Fonts`, `${process.env.LOCALAPPDATA ?? ""}\\Microsoft\\Windows\\Fonts`);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "linux":
|
|
40
|
+
default:
|
|
41
|
+
dirs.push("/usr/share/fonts", "/usr/local/share/fonts", "/usr/share/fonts/truetype", "/usr/share/fonts/opentype", "/usr/share/fonts/TTF", "/usr/share/fonts/noto", "/usr/share/fonts/noto-cjk", "/usr/share/fonts/google-noto", "/usr/share/fonts/google-noto-cjk", "/usr/share/fonts/truetype/noto", "/usr/share/fonts/truetype/dejavu", "/usr/share/fonts/truetype/liberation", "/usr/share/fonts/truetype/droid", "/usr/share/fonts/wqy", `${home}/.local/share/fonts`, `${home}/.fonts`);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
return dirs;
|
|
45
|
+
}
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Preferred Font Names (ordered by preference — first match wins)
|
|
48
|
+
// =============================================================================
|
|
49
|
+
const PREFERRED_FONTS = [
|
|
50
|
+
// Noto Sans CJK — broadest open-source coverage
|
|
51
|
+
"NotoSansCJKsc-Regular.ttf",
|
|
52
|
+
"NotoSansCJK-Regular.ttc",
|
|
53
|
+
"NotoSansCJKSC-Regular.otf",
|
|
54
|
+
"NotoSansSC-Regular.ttf",
|
|
55
|
+
"NotoSansTC-Regular.ttf",
|
|
56
|
+
"NotoSansJP-Regular.ttf",
|
|
57
|
+
"NotoSansKR-Regular.ttf",
|
|
58
|
+
"NotoSans-Regular.ttf",
|
|
59
|
+
// Arial Unicode MS
|
|
60
|
+
"Arial Unicode.ttf",
|
|
61
|
+
"Arial Unicode MS.ttf",
|
|
62
|
+
"ArialUnicode.ttf",
|
|
63
|
+
"arialuni.ttf",
|
|
64
|
+
// macOS
|
|
65
|
+
"PingFang.ttc",
|
|
66
|
+
"Hiragino Sans GB.ttc",
|
|
67
|
+
"STHeiti Light.ttc",
|
|
68
|
+
"STHeiti Medium.ttc",
|
|
69
|
+
"Songti.ttc",
|
|
70
|
+
"AppleSDGothicNeo.ttc",
|
|
71
|
+
// Windows
|
|
72
|
+
"msyh.ttc",
|
|
73
|
+
"msyhbd.ttc",
|
|
74
|
+
"msjh.ttc",
|
|
75
|
+
"simsun.ttc",
|
|
76
|
+
"simhei.ttf",
|
|
77
|
+
"malgun.ttf",
|
|
78
|
+
"meiryo.ttc",
|
|
79
|
+
"yugothic.ttf",
|
|
80
|
+
"segoeui.ttf",
|
|
81
|
+
"arial.ttf",
|
|
82
|
+
// Linux
|
|
83
|
+
"DejaVuSans.ttf",
|
|
84
|
+
"LiberationSans-Regular.ttf",
|
|
85
|
+
"FreeSans.ttf",
|
|
86
|
+
"DroidSansFallbackFull.ttf",
|
|
87
|
+
"DroidSansFallback.ttf",
|
|
88
|
+
"wqy-microhei.ttc",
|
|
89
|
+
"wqy-zenhei.ttc",
|
|
90
|
+
"uming.ttc",
|
|
91
|
+
"NanumGothic.ttf",
|
|
92
|
+
"IPAexGothic.ttf"
|
|
93
|
+
];
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// Font Discovery
|
|
96
|
+
// =============================================================================
|
|
97
|
+
let _cachedCandidates;
|
|
98
|
+
/**
|
|
99
|
+
* Return all discoverable system font candidates, ordered by preference.
|
|
100
|
+
*
|
|
101
|
+
* Each entry is the raw font file bytes of a `.ttf` or `.ttc` file.
|
|
102
|
+
* The caller decides which candidate to use (e.g. by checking cmap coverage).
|
|
103
|
+
*
|
|
104
|
+
* Results are cached — the filesystem scan runs only once per process.
|
|
105
|
+
*/
|
|
106
|
+
function discoverSystemFontCandidates() {
|
|
107
|
+
if (_cachedCandidates !== undefined) {
|
|
108
|
+
return _cachedCandidates;
|
|
109
|
+
}
|
|
110
|
+
if (typeof process === "undefined" || !process.platform) {
|
|
111
|
+
_cachedCandidates = [];
|
|
112
|
+
return _cachedCandidates;
|
|
113
|
+
}
|
|
114
|
+
const candidates = [];
|
|
115
|
+
const seen = new Set(); // dedupe by path
|
|
116
|
+
const dirs = getSystemFontDirs();
|
|
117
|
+
// Strategy 1: Check preferred font filenames (in order)
|
|
118
|
+
for (const fontName of PREFERRED_FONTS) {
|
|
119
|
+
for (const dir of dirs) {
|
|
120
|
+
const fontPath = `${dir}/${fontName}`;
|
|
121
|
+
if (seen.has(fontPath)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if ((0, fs_1.fileExistsSync)(fontPath)) {
|
|
125
|
+
const data = tryReadFont(fontPath);
|
|
126
|
+
if (data) {
|
|
127
|
+
candidates.push(data);
|
|
128
|
+
seen.add(fontPath);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Strategy 2: Scan directories for any .ttf/.ttc not already found
|
|
134
|
+
const broadRe = /noto|unicode|cjk|yahei|heiti|gothic|sans|serif|ming|song|dejavu|liberation|droid|wqy/i;
|
|
135
|
+
for (const dir of dirs) {
|
|
136
|
+
try {
|
|
137
|
+
const entries = (0, fs_1.traverseDirectorySync)(dir, { recursive: true, filter: e => !e.isDirectory });
|
|
138
|
+
const fonts = entries.filter(e => /\.tt[cf]$/i.test(e.absolutePath) && !seen.has(e.absolutePath));
|
|
139
|
+
// Broad-coverage names first, then large files
|
|
140
|
+
const broad = fonts.filter(e => broadRe.test(e.absolutePath));
|
|
141
|
+
const rest = fonts.filter(e => !broadRe.test(e.absolutePath) && e.size > 50000);
|
|
142
|
+
for (const entry of [...broad, ...rest]) {
|
|
143
|
+
if (seen.has(entry.absolutePath)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const data = tryReadFont(entry.absolutePath);
|
|
147
|
+
if (data) {
|
|
148
|
+
candidates.push(data);
|
|
149
|
+
seen.add(entry.absolutePath);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Directory doesn't exist or not readable
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
_cachedCandidates = candidates;
|
|
158
|
+
return candidates;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Search for a system font suitable for Unicode rendering.
|
|
162
|
+
*
|
|
163
|
+
* Returns the raw font file bytes of the highest-priority candidate,
|
|
164
|
+
* or `null` if no font was found. This is a convenience wrapper around
|
|
165
|
+
* {@link discoverSystemFontCandidates}.
|
|
166
|
+
*/
|
|
167
|
+
function discoverSystemFont() {
|
|
168
|
+
const candidates = discoverSystemFontCandidates();
|
|
169
|
+
return candidates.length > 0 ? candidates[0] : null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Reset the cached font discovery result (for testing).
|
|
173
|
+
*/
|
|
174
|
+
function resetFontDiscoveryCache() {
|
|
175
|
+
_cachedCandidates = undefined;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Override the cached candidates with a custom list (for testing).
|
|
179
|
+
* Call {@link resetFontDiscoveryCache} to clear the override.
|
|
180
|
+
*/
|
|
181
|
+
function _setCandidatesForTest(candidates) {
|
|
182
|
+
_cachedCandidates = candidates;
|
|
183
|
+
}
|
|
184
|
+
// =============================================================================
|
|
185
|
+
// Internal
|
|
186
|
+
// =============================================================================
|
|
187
|
+
function tryReadFont(fontPath) {
|
|
188
|
+
try {
|
|
189
|
+
return (0, fs_1.readFileBytesSync)(fontPath);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -114,8 +114,36 @@ class BEReader {
|
|
|
114
114
|
*/
|
|
115
115
|
function parseTtf(data) {
|
|
116
116
|
const r = new BEReader(data);
|
|
117
|
+
// --- Check for TTC (TrueType Collection) ---
|
|
118
|
+
const magic = r.u32();
|
|
119
|
+
if (magic === 0x74746366) {
|
|
120
|
+
// 'ttcf' — TrueType Collection
|
|
121
|
+
// Read the offset of the first font and restart parsing from there
|
|
122
|
+
r.skip(4); // majorVersion, minorVersion
|
|
123
|
+
const numFonts = r.u32();
|
|
124
|
+
if (numFonts === 0) {
|
|
125
|
+
throw new errors_1.PdfFontError("TrueType Collection is empty (0 fonts)");
|
|
126
|
+
}
|
|
127
|
+
const firstFontOffset = r.u32();
|
|
128
|
+
return parseTtfAtOffset(data, firstFontOffset);
|
|
129
|
+
}
|
|
130
|
+
// Not TTC — parse as regular TTF starting from the magic we already read
|
|
131
|
+
return parseTtfFromMagic(data, r, magic);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse a TTF font starting at a given byte offset within the data.
|
|
135
|
+
* Used for TTC collections where each font starts at a different offset.
|
|
136
|
+
*/
|
|
137
|
+
function parseTtfAtOffset(data, offset) {
|
|
138
|
+
const r = new BEReader(data, offset);
|
|
139
|
+
const magic = r.u32();
|
|
140
|
+
return parseTtfFromMagic(data, r, magic);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Core TTF parsing after the first 4 bytes (sfVersion) have been read.
|
|
144
|
+
*/
|
|
145
|
+
function parseTtfFromMagic(data, r, sfVersion) {
|
|
117
146
|
// --- Offset table ---
|
|
118
|
-
const sfVersion = r.u32();
|
|
119
147
|
// TrueType: 0x00010000 or 'true' (0x74727565)
|
|
120
148
|
// OpenType with CFF: 'OTTO' (0x4F54544F) — not supported for subsetting
|
|
121
149
|
if (sfVersion !== 0x00010000 && sfVersion !== 0x74727565) {
|