@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.
@@ -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
@@ -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 isUnknown = !isKnownWebFont(fontObj.name);
36
- // Collect font binary data for non-Google/non-standard fonts
37
- if (isUnknown && fontObj.data && fontObj.data.length > 0) {
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(mappedFamily) || [];
40
- arr.push({ mime, data: new Uint8Array(fontObj.data) });
41
- this.fontDataMap.set(mappedFamily, arr);
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 (isUnknown && !this.fontMetricsMap.has(mappedFamily)) {
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(mappedFamily, {
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
- let fontData;
85
- let mime;
86
- if (blobs.length === 1) {
87
- fontData = blobs[0].data;
88
- mime = blobs[0].mime;
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
- else {
91
- fontData = mergeSubsetFonts(blobs.map((b) => b.data));
92
- mime = blobs[0].mime;
93
- }
94
- let b64;
95
- if (typeof Buffer !== 'undefined') {
96
- b64 = Buffer.from(fontData).toString('base64');
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
- else {
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 < fontData.length; bi++) {
101
- binary += String.fromCharCode(fontData[bi]);
135
+ for (let bi = 0; bi < data.length; bi++) {
136
+ binary += String.fromCharCode(data[bi]);
102
137
  }
103
- b64 = btoa(binary);
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
  }