@polotno/pdf-import 0.0.1 → 0.0.3
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/lib/font-registry.d.ts +4 -1
- package/lib/font-registry.js +79 -28
- package/lib/generated/pdf-worker-source.d.ts +2 -0
- package/lib/generated/pdf-worker-source.js +23 -0
- package/lib/index.js +1 -1
- package/lib/page-parser.d.ts +2 -1
- package/lib/page-parser.js +39 -22
- package/lib/text-blocks.d.ts +4 -1
- package/lib/text-blocks.js +20 -4
- package/lib/text-layout.js +1 -1
- package/package.json +2 -2
package/lib/font-registry.d.ts
CHANGED
|
@@ -4,12 +4,15 @@ export declare class FontRegistry {
|
|
|
4
4
|
private fontDataMap;
|
|
5
5
|
private fontMetricsMap;
|
|
6
6
|
private otCache;
|
|
7
|
+
private renameMap;
|
|
7
8
|
/**
|
|
8
9
|
* Parse font data with opentype.js, returning cached result if available.
|
|
9
10
|
* Key is the pdfjs loaded font name (e.g. "g_d0_f1").
|
|
10
11
|
*/
|
|
11
12
|
parseOpentype(loadedName: string, data: Uint8Array): opentype.Font | null;
|
|
12
|
-
recordFont(fontObj: any): void;
|
|
13
|
+
recordFont(fontObj: any, embedAllFonts?: boolean): void;
|
|
14
|
+
/** Get the fontFamily name for a PDF font, accounting for renames. */
|
|
15
|
+
getFontFamily(pdfFontName: string): string;
|
|
13
16
|
finalize(fontStrategy: 'embed' | 'googleFontsMatch', pages: PolotnoPage[]): PolotnoFont[];
|
|
14
17
|
}
|
|
15
18
|
//# sourceMappingURL=font-registry.d.ts.map
|
package/lib/font-registry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import opentype from 'opentype.js';
|
|
2
|
-
import { mapPdfFont, isKnownWebFont } from './font-mapper.js';
|
|
2
|
+
import { mapPdfFont, isKnownWebFont, extractWeightFromName, extractStyleFromName, } from './font-mapper.js';
|
|
3
3
|
import { findClosestGoogleFont } from './font-matcher.js';
|
|
4
4
|
import { mergeSubsetFonts } from './font-merger.js';
|
|
5
5
|
export class FontRegistry {
|
|
@@ -8,6 +8,8 @@ export class FontRegistry {
|
|
|
8
8
|
this.fontMetricsMap = new Map();
|
|
9
9
|
// Cache opentype.js parsed fonts across pages to avoid re-parsing
|
|
10
10
|
this.otCache = new Map();
|
|
11
|
+
// Maps PDF font name → renamed fontFamily (only for embed-all renamed fonts)
|
|
12
|
+
this.renameMap = new Map();
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Parse font data with opentype.js, returning cached result if available.
|
|
@@ -28,25 +30,41 @@ export class FontRegistry {
|
|
|
28
30
|
return null;
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
|
-
recordFont(fontObj) {
|
|
33
|
+
recordFont(fontObj, embedAllFonts = false) {
|
|
32
34
|
if (!fontObj?.name)
|
|
33
35
|
return;
|
|
34
36
|
const mappedFamily = mapPdfFont(fontObj.name);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const isGoogleFont = isKnownWebFont(fontObj.name);
|
|
38
|
+
const shouldEmbed = embedAllFonts || !isGoogleFont;
|
|
39
|
+
// When embedding a known Google Font, rename to avoid Polotno loading
|
|
40
|
+
// the Google version instead of the embedded subset.
|
|
41
|
+
const fontFamily = embedAllFonts && isGoogleFont ? `${mappedFamily} (PDF)` : mappedFamily;
|
|
42
|
+
// Track the rename so text elements can use the correct fontFamily
|
|
43
|
+
if (fontFamily !== mappedFamily) {
|
|
44
|
+
this.renameMap.set(fontObj.name, fontFamily);
|
|
45
|
+
}
|
|
46
|
+
// Detect weight/style from the PDF font name
|
|
47
|
+
const fontWeight = extractWeightFromName(fontObj.name);
|
|
48
|
+
const fontStyle = extractStyleFromName(fontObj.name);
|
|
49
|
+
// Collect font binary data with weight/style info
|
|
50
|
+
if (shouldEmbed && fontObj.data && fontObj.data.length > 0) {
|
|
38
51
|
const mime = fontObj.mimetype || 'font/opentype';
|
|
39
|
-
const arr = this.fontDataMap.get(
|
|
40
|
-
arr.push({
|
|
41
|
-
|
|
52
|
+
const arr = this.fontDataMap.get(fontFamily) || [];
|
|
53
|
+
arr.push({
|
|
54
|
+
mime,
|
|
55
|
+
data: new Uint8Array(fontObj.data),
|
|
56
|
+
fontWeight,
|
|
57
|
+
fontStyle,
|
|
58
|
+
});
|
|
59
|
+
this.fontDataMap.set(fontFamily, arr);
|
|
42
60
|
}
|
|
43
61
|
// Collect font metrics for unknown fonts (for Google Font matching)
|
|
44
|
-
if (
|
|
62
|
+
if (shouldEmbed && !this.fontMetricsMap.has(fontFamily)) {
|
|
45
63
|
const widths = (fontObj.widths || []).filter((w) => w != null && w > 0);
|
|
46
64
|
const avgWidth = widths.length > 0
|
|
47
65
|
? widths.reduce((a, b) => a + b, 0) / widths.length
|
|
48
66
|
: 500;
|
|
49
|
-
this.fontMetricsMap.set(
|
|
67
|
+
this.fontMetricsMap.set(fontFamily, {
|
|
50
68
|
fontName: fontObj.name.replace(/^[A-Z]{6}\+/, ''),
|
|
51
69
|
isSerifFont: fontObj.isSerifFont || false,
|
|
52
70
|
isMonospace: fontObj.isMonospace || false,
|
|
@@ -56,6 +74,10 @@ export class FontRegistry {
|
|
|
56
74
|
});
|
|
57
75
|
}
|
|
58
76
|
}
|
|
77
|
+
/** Get the fontFamily name for a PDF font, accounting for renames. */
|
|
78
|
+
getFontFamily(pdfFontName) {
|
|
79
|
+
return this.renameMap.get(pdfFontName) ?? mapPdfFont(pdfFontName);
|
|
80
|
+
}
|
|
59
81
|
finalize(fontStrategy, pages) {
|
|
60
82
|
const fonts = [];
|
|
61
83
|
if (fontStrategy === 'googleFontsMatch') {
|
|
@@ -79,30 +101,59 @@ export class FontRegistry {
|
|
|
79
101
|
return fonts;
|
|
80
102
|
}
|
|
81
103
|
// 'embed' strategy: embed font data as base64 data URIs.
|
|
82
|
-
// When multiple subsets exist, merge them into a single font.
|
|
83
104
|
for (const [fontFamily, blobs] of this.fontDataMap) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
// Group blobs by weight+style variant
|
|
106
|
+
const variantMap = new Map();
|
|
107
|
+
for (const blob of blobs) {
|
|
108
|
+
const key = `${blob.fontWeight}|${blob.fontStyle}`;
|
|
109
|
+
const arr = variantMap.get(key) || [];
|
|
110
|
+
arr.push(blob);
|
|
111
|
+
variantMap.set(key, arr);
|
|
89
112
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
// When multiple subsets exist for the same variant, merge them.
|
|
114
|
+
const variants = [];
|
|
115
|
+
for (const [, variantBlobs] of variantMap) {
|
|
116
|
+
let fontData;
|
|
117
|
+
if (variantBlobs.length === 1) {
|
|
118
|
+
fontData = variantBlobs[0].data;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
fontData = mergeSubsetFonts(variantBlobs.map((b) => b.data));
|
|
122
|
+
}
|
|
123
|
+
variants.push({
|
|
124
|
+
fontWeight: variantBlobs[0].fontWeight,
|
|
125
|
+
fontStyle: variantBlobs[0].fontStyle,
|
|
126
|
+
data: fontData,
|
|
127
|
+
mime: variantBlobs[0].mime,
|
|
128
|
+
});
|
|
97
129
|
}
|
|
98
|
-
|
|
130
|
+
function toBase64(data) {
|
|
131
|
+
if (typeof Buffer !== 'undefined') {
|
|
132
|
+
return Buffer.from(data).toString('base64');
|
|
133
|
+
}
|
|
99
134
|
let binary = '';
|
|
100
|
-
for (let bi = 0; bi <
|
|
101
|
-
binary += String.fromCharCode(
|
|
135
|
+
for (let bi = 0; bi < data.length; bi++) {
|
|
136
|
+
binary += String.fromCharCode(data[bi]);
|
|
102
137
|
}
|
|
103
|
-
|
|
138
|
+
return btoa(binary);
|
|
139
|
+
}
|
|
140
|
+
if (variants.length === 1 && variants[0].fontWeight === 'normal' && variants[0].fontStyle === 'normal') {
|
|
141
|
+
// Single normal variant — use simple url format
|
|
142
|
+
const b64 = toBase64(variants[0].data);
|
|
143
|
+
fonts.push({ fontFamily, url: `data:${variants[0].mime};base64,${b64}` });
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Multiple variants — use styles array
|
|
147
|
+
const styles = variants.map((v) => {
|
|
148
|
+
const b64 = toBase64(v.data);
|
|
149
|
+
return {
|
|
150
|
+
src: `url("data:${v.mime};base64,${b64}")`,
|
|
151
|
+
fontWeight: v.fontWeight,
|
|
152
|
+
fontStyle: v.fontStyle,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
fonts.push({ fontFamily, styles });
|
|
104
156
|
}
|
|
105
|
-
fonts.push({ fontFamily, url: `data:${mime};base64,${b64}` });
|
|
106
157
|
}
|
|
107
158
|
return fonts;
|
|
108
159
|
}
|