@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
|
@@ -0,0 +1,188 @@
|
|
|
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
|
+
import { fileExistsSync, readFileBytesSync, traverseDirectorySync } from "../../../utils/fs.browser.js";
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Platform Font Directories
|
|
19
|
+
// =============================================================================
|
|
20
|
+
function getSystemFontDirs() {
|
|
21
|
+
const platform = typeof process !== "undefined" ? process.platform : "";
|
|
22
|
+
const home = typeof process !== "undefined" ? (process.env.HOME ?? process.env.USERPROFILE ?? "") : "";
|
|
23
|
+
const dirs = [];
|
|
24
|
+
switch (platform) {
|
|
25
|
+
case "darwin":
|
|
26
|
+
dirs.push("/System/Library/Fonts", "/System/Library/Fonts/Supplemental", "/Library/Fonts", `${home}/Library/Fonts`);
|
|
27
|
+
break;
|
|
28
|
+
case "win32": {
|
|
29
|
+
const winDir = process.env.WINDIR ?? process.env.SystemRoot ?? "C:\\Windows";
|
|
30
|
+
dirs.push(`${winDir}\\Fonts`, `${process.env.LOCALAPPDATA ?? ""}\\Microsoft\\Windows\\Fonts`);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "linux":
|
|
34
|
+
default:
|
|
35
|
+
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`);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
return dirs;
|
|
39
|
+
}
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Preferred Font Names (ordered by preference — first match wins)
|
|
42
|
+
// =============================================================================
|
|
43
|
+
const PREFERRED_FONTS = [
|
|
44
|
+
// Noto Sans CJK — broadest open-source coverage
|
|
45
|
+
"NotoSansCJKsc-Regular.ttf",
|
|
46
|
+
"NotoSansCJK-Regular.ttc",
|
|
47
|
+
"NotoSansCJKSC-Regular.otf",
|
|
48
|
+
"NotoSansSC-Regular.ttf",
|
|
49
|
+
"NotoSansTC-Regular.ttf",
|
|
50
|
+
"NotoSansJP-Regular.ttf",
|
|
51
|
+
"NotoSansKR-Regular.ttf",
|
|
52
|
+
"NotoSans-Regular.ttf",
|
|
53
|
+
// Arial Unicode MS
|
|
54
|
+
"Arial Unicode.ttf",
|
|
55
|
+
"Arial Unicode MS.ttf",
|
|
56
|
+
"ArialUnicode.ttf",
|
|
57
|
+
"arialuni.ttf",
|
|
58
|
+
// macOS
|
|
59
|
+
"PingFang.ttc",
|
|
60
|
+
"Hiragino Sans GB.ttc",
|
|
61
|
+
"STHeiti Light.ttc",
|
|
62
|
+
"STHeiti Medium.ttc",
|
|
63
|
+
"Songti.ttc",
|
|
64
|
+
"AppleSDGothicNeo.ttc",
|
|
65
|
+
// Windows
|
|
66
|
+
"msyh.ttc",
|
|
67
|
+
"msyhbd.ttc",
|
|
68
|
+
"msjh.ttc",
|
|
69
|
+
"simsun.ttc",
|
|
70
|
+
"simhei.ttf",
|
|
71
|
+
"malgun.ttf",
|
|
72
|
+
"meiryo.ttc",
|
|
73
|
+
"yugothic.ttf",
|
|
74
|
+
"segoeui.ttf",
|
|
75
|
+
"arial.ttf",
|
|
76
|
+
// Linux
|
|
77
|
+
"DejaVuSans.ttf",
|
|
78
|
+
"LiberationSans-Regular.ttf",
|
|
79
|
+
"FreeSans.ttf",
|
|
80
|
+
"DroidSansFallbackFull.ttf",
|
|
81
|
+
"DroidSansFallback.ttf",
|
|
82
|
+
"wqy-microhei.ttc",
|
|
83
|
+
"wqy-zenhei.ttc",
|
|
84
|
+
"uming.ttc",
|
|
85
|
+
"NanumGothic.ttf",
|
|
86
|
+
"IPAexGothic.ttf"
|
|
87
|
+
];
|
|
88
|
+
// =============================================================================
|
|
89
|
+
// Font Discovery
|
|
90
|
+
// =============================================================================
|
|
91
|
+
let _cachedCandidates;
|
|
92
|
+
/**
|
|
93
|
+
* Return all discoverable system font candidates, ordered by preference.
|
|
94
|
+
*
|
|
95
|
+
* Each entry is the raw font file bytes of a `.ttf` or `.ttc` file.
|
|
96
|
+
* The caller decides which candidate to use (e.g. by checking cmap coverage).
|
|
97
|
+
*
|
|
98
|
+
* Results are cached — the filesystem scan runs only once per process.
|
|
99
|
+
*/
|
|
100
|
+
export function discoverSystemFontCandidates() {
|
|
101
|
+
if (_cachedCandidates !== undefined) {
|
|
102
|
+
return _cachedCandidates;
|
|
103
|
+
}
|
|
104
|
+
if (typeof process === "undefined" || !process.platform) {
|
|
105
|
+
_cachedCandidates = [];
|
|
106
|
+
return _cachedCandidates;
|
|
107
|
+
}
|
|
108
|
+
const candidates = [];
|
|
109
|
+
const seen = new Set(); // dedupe by path
|
|
110
|
+
const dirs = getSystemFontDirs();
|
|
111
|
+
// Strategy 1: Check preferred font filenames (in order)
|
|
112
|
+
for (const fontName of PREFERRED_FONTS) {
|
|
113
|
+
for (const dir of dirs) {
|
|
114
|
+
const fontPath = `${dir}/${fontName}`;
|
|
115
|
+
if (seen.has(fontPath)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (fileExistsSync(fontPath)) {
|
|
119
|
+
const data = tryReadFont(fontPath);
|
|
120
|
+
if (data) {
|
|
121
|
+
candidates.push(data);
|
|
122
|
+
seen.add(fontPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Strategy 2: Scan directories for any .ttf/.ttc not already found
|
|
128
|
+
const broadRe = /noto|unicode|cjk|yahei|heiti|gothic|sans|serif|ming|song|dejavu|liberation|droid|wqy/i;
|
|
129
|
+
for (const dir of dirs) {
|
|
130
|
+
try {
|
|
131
|
+
const entries = traverseDirectorySync(dir, { recursive: true, filter: e => !e.isDirectory });
|
|
132
|
+
const fonts = entries.filter(e => /\.tt[cf]$/i.test(e.absolutePath) && !seen.has(e.absolutePath));
|
|
133
|
+
// Broad-coverage names first, then large files
|
|
134
|
+
const broad = fonts.filter(e => broadRe.test(e.absolutePath));
|
|
135
|
+
const rest = fonts.filter(e => !broadRe.test(e.absolutePath) && e.size > 50000);
|
|
136
|
+
for (const entry of [...broad, ...rest]) {
|
|
137
|
+
if (seen.has(entry.absolutePath)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const data = tryReadFont(entry.absolutePath);
|
|
141
|
+
if (data) {
|
|
142
|
+
candidates.push(data);
|
|
143
|
+
seen.add(entry.absolutePath);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Directory doesn't exist or not readable
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
_cachedCandidates = candidates;
|
|
152
|
+
return candidates;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Search for a system font suitable for Unicode rendering.
|
|
156
|
+
*
|
|
157
|
+
* Returns the raw font file bytes of the highest-priority candidate,
|
|
158
|
+
* or `null` if no font was found. This is a convenience wrapper around
|
|
159
|
+
* {@link discoverSystemFontCandidates}.
|
|
160
|
+
*/
|
|
161
|
+
export function discoverSystemFont() {
|
|
162
|
+
const candidates = discoverSystemFontCandidates();
|
|
163
|
+
return candidates.length > 0 ? candidates[0] : null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Reset the cached font discovery result (for testing).
|
|
167
|
+
*/
|
|
168
|
+
export function resetFontDiscoveryCache() {
|
|
169
|
+
_cachedCandidates = undefined;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Override the cached candidates with a custom list (for testing).
|
|
173
|
+
* Call {@link resetFontDiscoveryCache} to clear the override.
|
|
174
|
+
*/
|
|
175
|
+
export function _setCandidatesForTest(candidates) {
|
|
176
|
+
_cachedCandidates = candidates;
|
|
177
|
+
}
|
|
178
|
+
// =============================================================================
|
|
179
|
+
// Internal
|
|
180
|
+
// =============================================================================
|
|
181
|
+
function tryReadFont(fontPath) {
|
|
182
|
+
try {
|
|
183
|
+
return readFileBytesSync(fontPath);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -111,8 +111,36 @@ class BEReader {
|
|
|
111
111
|
*/
|
|
112
112
|
export function parseTtf(data) {
|
|
113
113
|
const r = new BEReader(data);
|
|
114
|
+
// --- Check for TTC (TrueType Collection) ---
|
|
115
|
+
const magic = r.u32();
|
|
116
|
+
if (magic === 0x74746366) {
|
|
117
|
+
// 'ttcf' — TrueType Collection
|
|
118
|
+
// Read the offset of the first font and restart parsing from there
|
|
119
|
+
r.skip(4); // majorVersion, minorVersion
|
|
120
|
+
const numFonts = r.u32();
|
|
121
|
+
if (numFonts === 0) {
|
|
122
|
+
throw new PdfFontError("TrueType Collection is empty (0 fonts)");
|
|
123
|
+
}
|
|
124
|
+
const firstFontOffset = r.u32();
|
|
125
|
+
return parseTtfAtOffset(data, firstFontOffset);
|
|
126
|
+
}
|
|
127
|
+
// Not TTC — parse as regular TTF starting from the magic we already read
|
|
128
|
+
return parseTtfFromMagic(data, r, magic);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Parse a TTF font starting at a given byte offset within the data.
|
|
132
|
+
* Used for TTC collections where each font starts at a different offset.
|
|
133
|
+
*/
|
|
134
|
+
function parseTtfAtOffset(data, offset) {
|
|
135
|
+
const r = new BEReader(data, offset);
|
|
136
|
+
const magic = r.u32();
|
|
137
|
+
return parseTtfFromMagic(data, r, magic);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Core TTF parsing after the first 4 bytes (sfVersion) have been read.
|
|
141
|
+
*/
|
|
142
|
+
function parseTtfFromMagic(data, r, sfVersion) {
|
|
114
143
|
// --- Offset table ---
|
|
115
|
-
const sfVersion = r.u32();
|
|
116
144
|
// TrueType: 0x00010000 or 'true' (0x74727565)
|
|
117
145
|
// OpenType with CFF: 'OTTO' (0x4F54544F) — not supported for subsetting
|
|
118
146
|
if (sfVersion !== 0x00010000 && sfVersion !== 0x74727565) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type3 fallback font builder.
|
|
3
|
+
*
|
|
4
|
+
* Produces a PDF Type3 font object that contains vector-drawn glyphs for
|
|
5
|
+
* Unicode characters that cannot be represented by standard Type1 fonts
|
|
6
|
+
* (WinAnsi encoding).
|
|
7
|
+
*
|
|
8
|
+
* A single Type3 font supports up to 256 glyphs (single-byte encoding).
|
|
9
|
+
* When more than 256 distinct non-WinAnsi characters appear in a document,
|
|
10
|
+
* multiple Type3 fonts are created automatically.
|
|
11
|
+
*
|
|
12
|
+
* @see PDF Reference 1.7, §5.5.4 — Type 3 Fonts
|
|
13
|
+
*/
|
|
14
|
+
import type { PdfWriter } from "../core/pdf-writer.js";
|
|
15
|
+
/** Result of writing Type3 font(s) to the PDF. */
|
|
16
|
+
export interface Type3FontResult {
|
|
17
|
+
/** Map from Type3 resource name → PDF object number. */
|
|
18
|
+
fontObjects: Map<string, number>;
|
|
19
|
+
/** Map from Unicode code point → { resourceName, charCode }. */
|
|
20
|
+
encoding: Map<number, {
|
|
21
|
+
resourceName: string;
|
|
22
|
+
charCode: number;
|
|
23
|
+
}>;
|
|
24
|
+
/** Advance widths: resourceName → Map<charCode, width in 1/1000 em>. */
|
|
25
|
+
widths: Map<string, Map<number, number>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build and write Type3 fallback fonts to the PDF for the given code points.
|
|
29
|
+
*
|
|
30
|
+
* @param writer - The PdfWriter to add objects to.
|
|
31
|
+
* @param codePoints - Set of Unicode code points that need Type3 rendering.
|
|
32
|
+
* @param resourcePrefix - Prefix for resource names (e.g. "T3F").
|
|
33
|
+
* @returns Encoding and object info for use by FontManager.
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeType3Fonts(writer: PdfWriter, codePoints: Set<number>, resourcePrefix?: string): Type3FontResult;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type3 fallback font builder.
|
|
3
|
+
*
|
|
4
|
+
* Produces a PDF Type3 font object that contains vector-drawn glyphs for
|
|
5
|
+
* Unicode characters that cannot be represented by standard Type1 fonts
|
|
6
|
+
* (WinAnsi encoding).
|
|
7
|
+
*
|
|
8
|
+
* A single Type3 font supports up to 256 glyphs (single-byte encoding).
|
|
9
|
+
* When more than 256 distinct non-WinAnsi characters appear in a document,
|
|
10
|
+
* multiple Type3 fonts are created automatically.
|
|
11
|
+
*
|
|
12
|
+
* @see PDF Reference 1.7, §5.5.4 — Type 3 Fonts
|
|
13
|
+
*/
|
|
14
|
+
import { PdfDict, pdfName, pdfRef, pdfNumber, pdfArray } from "../core/pdf-object.js";
|
|
15
|
+
import { PdfContentStream } from "../core/pdf-stream.js";
|
|
16
|
+
import { lookupGlyph, NOTDEF_GLYPH } from "./type3-glyphs.js";
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Constants
|
|
19
|
+
// =============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Type3 fonts use a FontMatrix to map glyph coordinates to text space.
|
|
22
|
+
* With a 1000-unit em square, FontMatrix = [0.001 0 0 0.001 0 0].
|
|
23
|
+
*/
|
|
24
|
+
const UNITS_PER_EM = 1000;
|
|
25
|
+
/** Maximum glyphs per Type3 font (single-byte encoding limit). */
|
|
26
|
+
const MAX_GLYPHS_PER_FONT = 256;
|
|
27
|
+
// First usable encoding slot (0x00 = .notdef, start user glyphs at 0x01).
|
|
28
|
+
const FIRST_SLOT = 1;
|
|
29
|
+
/**
|
|
30
|
+
* Build and write Type3 fallback fonts to the PDF for the given code points.
|
|
31
|
+
*
|
|
32
|
+
* @param writer - The PdfWriter to add objects to.
|
|
33
|
+
* @param codePoints - Set of Unicode code points that need Type3 rendering.
|
|
34
|
+
* @param resourcePrefix - Prefix for resource names (e.g. "T3F").
|
|
35
|
+
* @returns Encoding and object info for use by FontManager.
|
|
36
|
+
*/
|
|
37
|
+
export function writeType3Fonts(writer, codePoints, resourcePrefix = "T3F") {
|
|
38
|
+
const encoding = new Map();
|
|
39
|
+
const fontObjects = new Map();
|
|
40
|
+
const widths = new Map();
|
|
41
|
+
if (codePoints.size === 0) {
|
|
42
|
+
return { fontObjects, encoding, widths };
|
|
43
|
+
}
|
|
44
|
+
// Sort code points for deterministic output
|
|
45
|
+
const sorted = [...codePoints].sort((a, b) => a - b);
|
|
46
|
+
// Partition into chunks of MAX_GLYPHS_PER_FONT - 1 (slot 0 is .notdef)
|
|
47
|
+
const maxPerFont = MAX_GLYPHS_PER_FONT - FIRST_SLOT;
|
|
48
|
+
let fontIndex = 1;
|
|
49
|
+
for (let offset = 0; offset < sorted.length; offset += maxPerFont) {
|
|
50
|
+
const chunk = sorted.slice(offset, offset + maxPerFont);
|
|
51
|
+
const resourceName = `${resourcePrefix}${fontIndex}`;
|
|
52
|
+
fontIndex++;
|
|
53
|
+
const { objNum, fontWidths } = writeSingleType3Font(writer, chunk, resourceName);
|
|
54
|
+
fontObjects.set(resourceName, objNum);
|
|
55
|
+
widths.set(resourceName, fontWidths);
|
|
56
|
+
// Record encoding for each code point
|
|
57
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
58
|
+
encoding.set(chunk[i], { resourceName, charCode: FIRST_SLOT + i });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { fontObjects, encoding, widths };
|
|
62
|
+
}
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Internal — Single Type3 Font
|
|
65
|
+
// =============================================================================
|
|
66
|
+
function writeSingleType3Font(writer, codePoints, resourceName) {
|
|
67
|
+
// Build CharProcs: each glyph is a content stream
|
|
68
|
+
const charProcRefs = [];
|
|
69
|
+
const encodingNames = [];
|
|
70
|
+
const widthValues = new Array(FIRST_SLOT + codePoints.length).fill(0);
|
|
71
|
+
const fontWidths = new Map();
|
|
72
|
+
// Slot 0: .notdef
|
|
73
|
+
const notdefObj = writeGlyphStream(writer, NOTDEF_GLYPH);
|
|
74
|
+
charProcRefs.push({ name: ".notdef", objNum: notdefObj });
|
|
75
|
+
widthValues[0] = NOTDEF_GLYPH.width;
|
|
76
|
+
for (let i = 0; i < codePoints.length; i++) {
|
|
77
|
+
const cp = codePoints[i];
|
|
78
|
+
const glyphDef = lookupGlyph(cp) ?? NOTDEF_GLYPH;
|
|
79
|
+
const glyphName = `uni${cp.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
80
|
+
const objNum = writeGlyphStream(writer, glyphDef);
|
|
81
|
+
charProcRefs.push({ name: glyphName, objNum });
|
|
82
|
+
encodingNames.push(glyphName);
|
|
83
|
+
const slot = FIRST_SLOT + i;
|
|
84
|
+
widthValues[slot] = glyphDef.width;
|
|
85
|
+
fontWidths.set(slot, glyphDef.width);
|
|
86
|
+
}
|
|
87
|
+
// Build CharProcs dictionary
|
|
88
|
+
const charProcsEntries = [];
|
|
89
|
+
for (const { name, objNum } of charProcRefs) {
|
|
90
|
+
charProcsEntries.push(`${pdfName(name)} ${pdfRef(objNum)}`);
|
|
91
|
+
}
|
|
92
|
+
const charProcsDict = `<<\n${charProcsEntries.join("\n")}\n>>`;
|
|
93
|
+
// Build Encoding dictionary
|
|
94
|
+
const differences = [];
|
|
95
|
+
differences.push(`${FIRST_SLOT}`);
|
|
96
|
+
for (const name of encodingNames) {
|
|
97
|
+
differences.push(pdfName(name));
|
|
98
|
+
}
|
|
99
|
+
const encodingObjNum = writer.allocObject();
|
|
100
|
+
const encodingDict = new PdfDict()
|
|
101
|
+
.set("Type", "/Encoding")
|
|
102
|
+
.set("Differences", `[${differences.join(" ")}]`);
|
|
103
|
+
writer.addObject(encodingObjNum, encodingDict);
|
|
104
|
+
// Build ToUnicode CMap
|
|
105
|
+
const toUnicodeObjNum = writeToUnicodeCMap(writer, codePoints);
|
|
106
|
+
// Build the Type3 font dictionary
|
|
107
|
+
const fontObjNum = writer.allocObject();
|
|
108
|
+
const fontBBox = `[0 0 ${UNITS_PER_EM} ${UNITS_PER_EM}]`;
|
|
109
|
+
const fontMatrix = "[0.001 0 0 0.001 0 0]";
|
|
110
|
+
const fontDict = new PdfDict()
|
|
111
|
+
.set("Type", "/Font")
|
|
112
|
+
.set("Subtype", "/Type3")
|
|
113
|
+
.set("Name", pdfName(resourceName))
|
|
114
|
+
.set("FontBBox", fontBBox)
|
|
115
|
+
.set("FontMatrix", fontMatrix)
|
|
116
|
+
.set("FirstChar", pdfNumber(0))
|
|
117
|
+
.set("LastChar", pdfNumber(widthValues.length - 1))
|
|
118
|
+
.set("Widths", pdfArray(widthValues.map(w => pdfNumber(w))))
|
|
119
|
+
.set("CharProcs", charProcsDict)
|
|
120
|
+
.set("Encoding", pdfRef(encodingObjNum))
|
|
121
|
+
.set("ToUnicode", pdfRef(toUnicodeObjNum));
|
|
122
|
+
writer.addObject(fontObjNum, fontDict);
|
|
123
|
+
return { objNum: fontObjNum, fontWidths };
|
|
124
|
+
}
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// Glyph Stream Writer
|
|
127
|
+
// =============================================================================
|
|
128
|
+
function writeGlyphStream(writer, glyph) {
|
|
129
|
+
const stream = new PdfContentStream();
|
|
130
|
+
// d1 operator: wx wy llx lly urx ury — sets glyph width and bounding box
|
|
131
|
+
// This tells the PDF viewer the advance width and clip region
|
|
132
|
+
stream.raw(`${glyph.width} 0 0 0 ${UNITS_PER_EM} ${UNITS_PER_EM} d1`);
|
|
133
|
+
// Draw the glyph using the pen adapter
|
|
134
|
+
const pen = createPen(stream);
|
|
135
|
+
glyph.draw(pen);
|
|
136
|
+
const objNum = writer.allocObject();
|
|
137
|
+
writer.addStreamObject(objNum, new PdfDict(), stream);
|
|
138
|
+
return objNum;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create a GlyphPen backed by a PdfContentStream.
|
|
142
|
+
*/
|
|
143
|
+
function createPen(stream) {
|
|
144
|
+
return {
|
|
145
|
+
M: (x, y) => {
|
|
146
|
+
stream.moveTo(x, y);
|
|
147
|
+
},
|
|
148
|
+
L: (x, y) => {
|
|
149
|
+
stream.lineTo(x, y);
|
|
150
|
+
},
|
|
151
|
+
C: (x1, y1, x2, y2, x3, y3) => {
|
|
152
|
+
stream.curveTo(x1, y1, x2, y2, x3, y3);
|
|
153
|
+
},
|
|
154
|
+
Z: () => {
|
|
155
|
+
stream.closePath();
|
|
156
|
+
},
|
|
157
|
+
rect: (x, y, w, h) => {
|
|
158
|
+
stream.rect(x, y, w, h);
|
|
159
|
+
},
|
|
160
|
+
circle: (cx, cy, r) => {
|
|
161
|
+
stream.circle(cx, cy, r);
|
|
162
|
+
},
|
|
163
|
+
ellipse: (cx, cy, rx, ry) => {
|
|
164
|
+
stream.ellipse(cx, cy, rx, ry);
|
|
165
|
+
},
|
|
166
|
+
stroke: () => {
|
|
167
|
+
stream.stroke();
|
|
168
|
+
},
|
|
169
|
+
fill: () => {
|
|
170
|
+
stream.fill();
|
|
171
|
+
},
|
|
172
|
+
fillStroke: () => {
|
|
173
|
+
stream.fillAndStroke();
|
|
174
|
+
},
|
|
175
|
+
lineWidth: w => {
|
|
176
|
+
stream.setLineWidth(w);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// =============================================================================
|
|
181
|
+
// ToUnicode CMap
|
|
182
|
+
// =============================================================================
|
|
183
|
+
function writeToUnicodeCMap(writer, codePoints) {
|
|
184
|
+
const lines = [];
|
|
185
|
+
lines.push("/CIDInit /ProcSet findresource begin");
|
|
186
|
+
lines.push("12 dict begin");
|
|
187
|
+
lines.push("begincmap");
|
|
188
|
+
lines.push("/CIDSystemInfo");
|
|
189
|
+
lines.push("<< /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def");
|
|
190
|
+
lines.push("/CMapName /Adobe-Identity-UCS def");
|
|
191
|
+
lines.push("/CMapType 2 def");
|
|
192
|
+
lines.push("1 begincodespacerange");
|
|
193
|
+
lines.push("<00> <FF>");
|
|
194
|
+
lines.push("endcodespacerange");
|
|
195
|
+
// Write mappings in chunks of 100 (PDF limit per beginbfchar block)
|
|
196
|
+
for (let offset = 0; offset < codePoints.length; offset += 100) {
|
|
197
|
+
const chunk = codePoints.slice(offset, offset + 100);
|
|
198
|
+
lines.push(`${chunk.length} beginbfchar`);
|
|
199
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
200
|
+
const slot = FIRST_SLOT + offset + i;
|
|
201
|
+
const cp = chunk[i];
|
|
202
|
+
const slotHex = slot.toString(16).toUpperCase().padStart(2, "0");
|
|
203
|
+
if (cp > 0xffff) {
|
|
204
|
+
// Supplementary character — encode as UTF-16 surrogate pair
|
|
205
|
+
const hi = Math.floor((cp - 0x10000) / 0x400) + 0xd800;
|
|
206
|
+
const lo = ((cp - 0x10000) % 0x400) + 0xdc00;
|
|
207
|
+
const hiHex = hi.toString(16).toUpperCase().padStart(4, "0");
|
|
208
|
+
const loHex = lo.toString(16).toUpperCase().padStart(4, "0");
|
|
209
|
+
lines.push(`<${slotHex}> <${hiHex}${loHex}>`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const cpHex = cp.toString(16).toUpperCase().padStart(4, "0");
|
|
213
|
+
lines.push(`<${slotHex}> <${cpHex}>`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
lines.push("endbfchar");
|
|
217
|
+
}
|
|
218
|
+
lines.push("endcmap");
|
|
219
|
+
lines.push("CMapName currentdict /CMap defineresource pop");
|
|
220
|
+
lines.push("end");
|
|
221
|
+
lines.push("end");
|
|
222
|
+
const cmapStr = lines.join("\n");
|
|
223
|
+
const encoder = new TextEncoder();
|
|
224
|
+
const data = encoder.encode(cmapStr);
|
|
225
|
+
const objNum = writer.allocObject();
|
|
226
|
+
writer.addStreamObject(objNum, new PdfDict(), data);
|
|
227
|
+
return objNum;
|
|
228
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended Type3 glyph definitions — programmatically generated blocks
|
|
3
|
+
* and additional hand-crafted symbols.
|
|
4
|
+
*
|
|
5
|
+
* This file supplements type3-glyphs.ts with:
|
|
6
|
+
* - Box Drawing (U+2500–U+257F) 128 chars — algorithmic
|
|
7
|
+
* - Block Elements (U+2580–U+259F) 32 chars — algorithmic
|
|
8
|
+
* - Braille Patterns (U+2800–U+28FF) 256 chars — algorithmic
|
|
9
|
+
* - Letterlike Symbols (U+2100–U+214F) hand-crafted
|
|
10
|
+
* - Number Forms (U+2150–U+218F) hand-crafted
|
|
11
|
+
* - Enclosed Alphanumerics (U+2460–U+24FF) programmatic
|
|
12
|
+
* - General Punctuation extras (U+2000–U+206F)
|
|
13
|
+
* - Additional Arrows, Math, Dingbats, Misc Symbols, Currency, Technical
|
|
14
|
+
*/
|
|
15
|
+
import type { GlyphDef } from "./type3-glyphs.js";
|
|
16
|
+
export declare const BOX_FULL: Record<number, GlyphDef>;
|
|
17
|
+
export declare const BLOCK_FULL: Record<number, GlyphDef>;
|
|
18
|
+
export declare const BRAILLE: Record<number, GlyphDef>;
|
|
19
|
+
export declare const LETTERLIKE: Record<number, GlyphDef>;
|
|
20
|
+
export declare const NUMBER_FORMS: Record<number, GlyphDef>;
|
|
21
|
+
export declare const ENCLOSED: Record<number, GlyphDef>;
|
|
22
|
+
export declare const PUNCT_EXT: Record<number, GlyphDef>;
|
|
23
|
+
export declare const ARROWS_EXT: Record<number, GlyphDef>;
|
|
24
|
+
export declare const MATH_EXT: Record<number, GlyphDef>;
|
|
25
|
+
export declare const MISC_EXT: Record<number, GlyphDef>;
|
|
26
|
+
export declare const DING_EXT: Record<number, GlyphDef>;
|
|
27
|
+
export declare const TECH_EXT: Record<number, GlyphDef>;
|
|
28
|
+
export declare const CURRENCY_EXT: Record<number, GlyphDef>;
|
|
29
|
+
export declare const MATH_SYM_A: Record<number, GlyphDef>;
|
|
30
|
+
export declare const SUP_ARROWS_A: Record<number, GlyphDef>;
|
|
31
|
+
export declare const GEOMETRIC_EXT: Record<number, GlyphDef>;
|
|
32
|
+
export declare const ROMAN_NUMERALS: Record<number, GlyphDef>;
|
|
33
|
+
export declare const ENCLOSED_EXT: Record<number, GlyphDef>;
|