pptx-glimpse 0.8.0 → 0.10.0

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/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  DEFAULT_FONT_MAPPING: () => DEFAULT_FONT_MAPPING,
24
+ clearFontCache: () => clearFontCache,
24
25
  collectUsedFonts: () => collectUsedFonts,
25
26
  convertPptxToPng: () => convertPptxToPng,
26
27
  convertPptxToSvg: () => convertPptxToSvg,
@@ -1043,12 +1044,109 @@ function measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
1043
1044
  return totalWidth;
1044
1045
  }
1045
1046
 
1047
+ // src/warning-logger.ts
1048
+ var PREFIX = "[pptx-glimpse]";
1049
+ var currentLevel = "off";
1050
+ var entries = [];
1051
+ var featureCounts = /* @__PURE__ */ new Map();
1052
+ function initWarningLogger(level) {
1053
+ currentLevel = level;
1054
+ entries = [];
1055
+ featureCounts.clear();
1056
+ }
1057
+ function warn(feature, message, context) {
1058
+ if (currentLevel === "off") return;
1059
+ entries.push({ feature, message, ...context !== void 0 && { context } });
1060
+ const existing = featureCounts.get(feature);
1061
+ if (existing) {
1062
+ existing.count++;
1063
+ } else {
1064
+ featureCounts.set(feature, { message, count: 1 });
1065
+ }
1066
+ if (currentLevel === "debug") {
1067
+ const ctx = context ? ` (${context})` : "";
1068
+ console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
1069
+ }
1070
+ }
1071
+ function debug(feature, message, context) {
1072
+ if (currentLevel !== "debug") return;
1073
+ entries.push({ feature, message, ...context !== void 0 && { context } });
1074
+ const existing = featureCounts.get(feature);
1075
+ if (existing) {
1076
+ existing.count++;
1077
+ } else {
1078
+ featureCounts.set(feature, { message, count: 1 });
1079
+ }
1080
+ const ctx = context ? ` (${context})` : "";
1081
+ console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
1082
+ }
1083
+ function getWarningSummary() {
1084
+ const features = [];
1085
+ for (const [feature, { message, count }] of featureCounts) {
1086
+ features.push({ feature, message, count });
1087
+ }
1088
+ return { totalCount: entries.length, features };
1089
+ }
1090
+ function flushWarnings() {
1091
+ const summary = getWarningSummary();
1092
+ if (currentLevel !== "off" && summary.features.length > 0) {
1093
+ console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
1094
+ for (const { feature, count } of summary.features) {
1095
+ console.warn(` - ${feature}: ${count} occurrence(s)`);
1096
+ }
1097
+ }
1098
+ entries = [];
1099
+ featureCounts.clear();
1100
+ return summary;
1101
+ }
1102
+ function getWarningEntries() {
1103
+ return entries;
1104
+ }
1105
+
1106
+ // src/font/cjk-font-fallback.ts
1107
+ var import_node_os = require("os");
1108
+ var MACOS_FALLBACKS = {
1109
+ // ゴシック系
1110
+ "Noto Sans JP": ["Hiragino Sans", "Hiragino Kaku Gothic ProN"],
1111
+ "Noto Sans CJK JP": ["Hiragino Sans", "Hiragino Kaku Gothic ProN"],
1112
+ // 明朝系
1113
+ "Noto Serif CJK JP": ["Hiragino Mincho ProN"]
1114
+ };
1115
+ var WINDOWS_FALLBACKS = {
1116
+ // ゴシック系
1117
+ "Noto Sans JP": ["Yu Gothic", "Meiryo", "MS Gothic"],
1118
+ "Noto Sans CJK JP": ["Yu Gothic", "Meiryo", "MS Gothic"],
1119
+ // 明朝系
1120
+ "Noto Serif CJK JP": ["Yu Mincho", "MS Mincho"]
1121
+ };
1122
+ var EMPTY = [];
1123
+ var cachedFallbacks = null;
1124
+ function getFallbackMap() {
1125
+ if (cachedFallbacks) return cachedFallbacks;
1126
+ const os = (0, import_node_os.platform)();
1127
+ switch (os) {
1128
+ case "darwin":
1129
+ cachedFallbacks = MACOS_FALLBACKS;
1130
+ break;
1131
+ case "win32":
1132
+ cachedFallbacks = WINDOWS_FALLBACKS;
1133
+ break;
1134
+ default:
1135
+ cachedFallbacks = {};
1136
+ }
1137
+ return cachedFallbacks;
1138
+ }
1139
+ function getCjkFallbackFonts(mappedFontName) {
1140
+ return getFallbackMap()[mappedFontName] ?? EMPTY;
1141
+ }
1142
+
1046
1143
  // src/font/opentype-text-measurer.ts
1047
1144
  var PX_PER_PT2 = 96 / 72;
1048
1145
  var BOLD_FACTOR2 = 1.05;
1049
1146
  var OpentypeTextMeasurer = class {
1050
1147
  fonts;
1051
1148
  defaultFont;
1149
+ warnedFonts = /* @__PURE__ */ new Set();
1052
1150
  /**
1053
1151
  * @param fonts - フォント名 → opentype.js Font オブジェクトのマップ
1054
1152
  * @param defaultFont - フォールバックフォント(省略可)
@@ -1098,6 +1196,14 @@ var OpentypeTextMeasurer = class {
1098
1196
  if (mapped) {
1099
1197
  const mappedFont = this.fonts.get(mapped);
1100
1198
  if (mappedFont) return mappedFont;
1199
+ for (const fallback of getCjkFallbackFonts(mapped)) {
1200
+ const fallbackFont = this.fonts.get(fallback);
1201
+ if (fallbackFont) return fallbackFont;
1202
+ }
1203
+ }
1204
+ if (!this.warnedFonts.has(name)) {
1205
+ this.warnedFonts.add(name);
1206
+ warn("font.notFound", `Font not found: "${name}"`);
1101
1207
  }
1102
1208
  return null;
1103
1209
  }
@@ -1105,19 +1211,31 @@ var OpentypeTextMeasurer = class {
1105
1211
 
1106
1212
  // src/font/system-font-loader.ts
1107
1213
  var import_node_fs = require("fs");
1108
- var import_node_os = require("os");
1214
+ var import_node_os2 = require("os");
1109
1215
  var import_node_path = require("path");
1110
1216
  var FONT_EXTENSIONS = /* @__PURE__ */ new Set([".ttf", ".otf"]);
1111
- var CJK_TTC_PATTERNS = ["NotoSansCJK", "NotoSerifCJK"];
1217
+ var CJK_TTC_PATTERNS = [
1218
+ "NotoSansCJK",
1219
+ "NotoSerifCJK",
1220
+ // macOS
1221
+ "Hiragino",
1222
+ "\u30D2\u30E9\u30AE\u30CE",
1223
+ // Windows
1224
+ "YuGoth",
1225
+ "YuMin",
1226
+ "meiryo",
1227
+ "msgothic",
1228
+ "msmincho"
1229
+ ];
1112
1230
  var cachedPaths = null;
1113
1231
  var cachedAdditionalDirs = null;
1114
1232
  function getSystemFontDirs() {
1115
- const os = (0, import_node_os.platform)();
1233
+ const os = (0, import_node_os2.platform)();
1116
1234
  switch (os) {
1117
1235
  case "linux":
1118
1236
  return ["/usr/share/fonts", "/usr/local/share/fonts"];
1119
1237
  case "darwin":
1120
- return ["/System/Library/Fonts", "/Library/Fonts", (0, import_node_path.join)((0, import_node_os.homedir)(), "Library/Fonts")];
1238
+ return ["/System/Library/Fonts", "/Library/Fonts", (0, import_node_path.join)((0, import_node_os2.homedir)(), "Library/Fonts")];
1121
1239
  case "win32":
1122
1240
  return ["C:\\Windows\\Fonts"];
1123
1241
  default:
@@ -1125,7 +1243,7 @@ function getSystemFontDirs() {
1125
1243
  }
1126
1244
  }
1127
1245
  function isCjkTtc(name) {
1128
- const lower = name.toLowerCase();
1246
+ const lower = name.normalize("NFC").toLowerCase();
1129
1247
  return lower.endsWith(".ttc") && CJK_TTC_PATTERNS.some((p) => lower.includes(p.toLowerCase()));
1130
1248
  }
1131
1249
  function walk(dir, result) {
@@ -1163,6 +1281,7 @@ function collectFontFilePaths(additionalDirs) {
1163
1281
  var DefaultTextPathFontResolver = class {
1164
1282
  fonts;
1165
1283
  defaultFont;
1284
+ warnedFonts = /* @__PURE__ */ new Set();
1166
1285
  constructor(fonts, defaultFont) {
1167
1286
  this.fonts = fonts;
1168
1287
  this.defaultFont = defaultFont ?? null;
@@ -1180,6 +1299,12 @@ var DefaultTextPathFontResolver = class {
1180
1299
  const font = this.findFont(jpanFallback);
1181
1300
  if (font) return font;
1182
1301
  }
1302
+ for (const name of [fontFamily, fontFamilyEa, jpanFallback]) {
1303
+ if (name && !this.warnedFonts.has(name)) {
1304
+ this.warnedFonts.add(name);
1305
+ warn("font.notFound", `Font not found: "${name}"`);
1306
+ }
1307
+ }
1183
1308
  return this.defaultFont;
1184
1309
  }
1185
1310
  findFont(name) {
@@ -1189,6 +1314,10 @@ var DefaultTextPathFontResolver = class {
1189
1314
  if (mapped) {
1190
1315
  const mappedFont = this.fonts.get(mapped);
1191
1316
  if (mappedFont) return mappedFont;
1317
+ for (const fallback of getCjkFallbackFonts(mapped)) {
1318
+ const fallbackFont = this.fonts.get(fallback);
1319
+ if (fallbackFont) return fallbackFont;
1320
+ }
1192
1321
  }
1193
1322
  return null;
1194
1323
  }
@@ -1416,7 +1545,23 @@ function collectFontNames(font) {
1416
1545
  }
1417
1546
  return names;
1418
1547
  }
1548
+ function buildCacheKey(additionalFontDirs, fontMapping) {
1549
+ const dirsKey = additionalFontDirs ? [...additionalFontDirs].sort().join("\0") : "";
1550
+ const mappingKey = fontMapping ? JSON.stringify(fontMapping, Object.keys(fontMapping).sort()) : "";
1551
+ return `${dirsKey}
1552
+ ${mappingKey}`;
1553
+ }
1554
+ var cachedSetup = null;
1555
+ var cachedSetupKey = null;
1556
+ function clearFontCache() {
1557
+ cachedSetup = null;
1558
+ cachedSetupKey = null;
1559
+ }
1419
1560
  async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping) {
1561
+ const key = buildCacheKey(additionalFontDirs, fontMapping);
1562
+ if (cachedSetup && cachedSetupKey === key) {
1563
+ return cachedSetup;
1564
+ }
1420
1565
  const opentype = await tryLoadOpentype();
1421
1566
  if (!opentype) return null;
1422
1567
  const fontFilePaths = collectFontFilePaths(additionalFontDirs);
@@ -1448,7 +1593,10 @@ async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping) {
1448
1593
  resolverFonts,
1449
1594
  firstResolverFont ?? void 0
1450
1595
  );
1451
- return { measurer, fontResolver };
1596
+ const setup = { measurer, fontResolver };
1597
+ cachedSetup = setup;
1598
+ cachedSetupKey = key;
1599
+ return setup;
1452
1600
  }
1453
1601
 
1454
1602
  // src/font/script-font-context.ts
@@ -1621,65 +1769,6 @@ async function svgToPng(svgString, options) {
1621
1769
  };
1622
1770
  }
1623
1771
 
1624
- // src/warning-logger.ts
1625
- var PREFIX = "[pptx-glimpse]";
1626
- var currentLevel = "off";
1627
- var entries = [];
1628
- var featureCounts = /* @__PURE__ */ new Map();
1629
- function initWarningLogger(level) {
1630
- currentLevel = level;
1631
- entries = [];
1632
- featureCounts.clear();
1633
- }
1634
- function warn(feature, message, context) {
1635
- if (currentLevel === "off") return;
1636
- entries.push({ feature, message, ...context !== void 0 && { context } });
1637
- const existing = featureCounts.get(feature);
1638
- if (existing) {
1639
- existing.count++;
1640
- } else {
1641
- featureCounts.set(feature, { message, count: 1 });
1642
- }
1643
- if (currentLevel === "debug") {
1644
- const ctx = context ? ` (${context})` : "";
1645
- console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
1646
- }
1647
- }
1648
- function debug(feature, message, context) {
1649
- if (currentLevel !== "debug") return;
1650
- entries.push({ feature, message, ...context !== void 0 && { context } });
1651
- const existing = featureCounts.get(feature);
1652
- if (existing) {
1653
- existing.count++;
1654
- } else {
1655
- featureCounts.set(feature, { message, count: 1 });
1656
- }
1657
- const ctx = context ? ` (${context})` : "";
1658
- console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
1659
- }
1660
- function getWarningSummary() {
1661
- const features = [];
1662
- for (const [feature, { message, count }] of featureCounts) {
1663
- features.push({ feature, message, count });
1664
- }
1665
- return { totalCount: entries.length, features };
1666
- }
1667
- function flushWarnings() {
1668
- const summary = getWarningSummary();
1669
- if (currentLevel !== "off" && summary.features.length > 0) {
1670
- console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
1671
- for (const { feature, count } of summary.features) {
1672
- console.warn(` - ${feature}: ${count} occurrence(s)`);
1673
- }
1674
- }
1675
- entries = [];
1676
- featureCounts.clear();
1677
- return summary;
1678
- }
1679
- function getWarningEntries() {
1680
- return entries;
1681
- }
1682
-
1683
1772
  // src/color/color-transforms.ts
1684
1773
  function applyColorTransforms(color, node) {
1685
1774
  let { hex, alpha } = color;
@@ -8935,6 +9024,7 @@ function collectFontsFromTextBody(textBody, fonts) {
8935
9024
  // Annotate the CommonJS export names for ESM import in node:
8936
9025
  0 && (module.exports = {
8937
9026
  DEFAULT_FONT_MAPPING,
9027
+ clearFontCache,
8938
9028
  collectUsedFonts,
8939
9029
  convertPptxToPng,
8940
9030
  convertPptxToSvg,
package/dist/index.d.cts CHANGED
@@ -125,6 +125,7 @@ interface OpentypeFont {
125
125
  declare class OpentypeTextMeasurer implements TextMeasurer {
126
126
  private fonts;
127
127
  private defaultFont;
128
+ private warnedFonts;
128
129
  /**
129
130
  * @param fonts - フォント名 → opentype.js Font オブジェクトのマップ
130
131
  * @param defaultFont - フォールバックフォント(省略可)
@@ -176,6 +177,12 @@ interface OpentypeSetup {
176
177
  * 同じ Font オブジェクトを measurer と fontResolver の両方に渡す。
177
178
  */
178
179
  declare function createOpentypeSetupFromBuffers(fontBuffers: FontBuffer[], fontMapping?: FontMapping): Promise<OpentypeSetup | null>;
180
+ /**
181
+ * フォントオブジェクトキャッシュをクリアする。
182
+ * 通常は呼び出す必要はないが、フォントのインストール/アンインストール後に
183
+ * 強制的に再読み込みしたい場合に使用する。
184
+ */
185
+ declare function clearFontCache(): void;
179
186
 
180
187
  /**
181
188
  * resvg-wasm の WASM モジュールを初期化する。
@@ -184,4 +191,4 @@ declare function createOpentypeSetupFromBuffers(fontBuffers: FontBuffer[], fontM
184
191
  */
185
192
  declare function initResvgWasm(): Promise<void>;
186
193
 
187
- export { type ConvertOptions, DEFAULT_FONT_MAPPING, type FontBuffer, type FontMapping, type LogLevel, type OpentypeSetup, type SlideImage, type SlideSvg, type UsedFonts, type WarningEntry, type WarningSummary, collectUsedFonts, convertPptxToPng, convertPptxToSvg, createFontMapping, createOpentypeSetupFromBuffers, createOpentypeTextMeasurerFromBuffers, getMappedFont, getWarningEntries, getWarningSummary, initResvgWasm };
194
+ export { type ConvertOptions, DEFAULT_FONT_MAPPING, type FontBuffer, type FontMapping, type LogLevel, type OpentypeSetup, type SlideImage, type SlideSvg, type UsedFonts, type WarningEntry, type WarningSummary, clearFontCache, collectUsedFonts, convertPptxToPng, convertPptxToSvg, createFontMapping, createOpentypeSetupFromBuffers, createOpentypeTextMeasurerFromBuffers, getMappedFont, getWarningEntries, getWarningSummary, initResvgWasm };
package/dist/index.d.ts CHANGED
@@ -125,6 +125,7 @@ interface OpentypeFont {
125
125
  declare class OpentypeTextMeasurer implements TextMeasurer {
126
126
  private fonts;
127
127
  private defaultFont;
128
+ private warnedFonts;
128
129
  /**
129
130
  * @param fonts - フォント名 → opentype.js Font オブジェクトのマップ
130
131
  * @param defaultFont - フォールバックフォント(省略可)
@@ -176,6 +177,12 @@ interface OpentypeSetup {
176
177
  * 同じ Font オブジェクトを measurer と fontResolver の両方に渡す。
177
178
  */
178
179
  declare function createOpentypeSetupFromBuffers(fontBuffers: FontBuffer[], fontMapping?: FontMapping): Promise<OpentypeSetup | null>;
180
+ /**
181
+ * フォントオブジェクトキャッシュをクリアする。
182
+ * 通常は呼び出す必要はないが、フォントのインストール/アンインストール後に
183
+ * 強制的に再読み込みしたい場合に使用する。
184
+ */
185
+ declare function clearFontCache(): void;
179
186
 
180
187
  /**
181
188
  * resvg-wasm の WASM モジュールを初期化する。
@@ -184,4 +191,4 @@ declare function createOpentypeSetupFromBuffers(fontBuffers: FontBuffer[], fontM
184
191
  */
185
192
  declare function initResvgWasm(): Promise<void>;
186
193
 
187
- export { type ConvertOptions, DEFAULT_FONT_MAPPING, type FontBuffer, type FontMapping, type LogLevel, type OpentypeSetup, type SlideImage, type SlideSvg, type UsedFonts, type WarningEntry, type WarningSummary, collectUsedFonts, convertPptxToPng, convertPptxToSvg, createFontMapping, createOpentypeSetupFromBuffers, createOpentypeTextMeasurerFromBuffers, getMappedFont, getWarningEntries, getWarningSummary, initResvgWasm };
194
+ export { type ConvertOptions, DEFAULT_FONT_MAPPING, type FontBuffer, type FontMapping, type LogLevel, type OpentypeSetup, type SlideImage, type SlideSvg, type UsedFonts, type WarningEntry, type WarningSummary, clearFontCache, collectUsedFonts, convertPptxToPng, convertPptxToSvg, createFontMapping, createOpentypeSetupFromBuffers, createOpentypeTextMeasurerFromBuffers, getMappedFont, getWarningEntries, getWarningSummary, initResvgWasm };
package/dist/index.js CHANGED
@@ -1007,12 +1007,109 @@ function measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
1007
1007
  return totalWidth;
1008
1008
  }
1009
1009
 
1010
+ // src/warning-logger.ts
1011
+ var PREFIX = "[pptx-glimpse]";
1012
+ var currentLevel = "off";
1013
+ var entries = [];
1014
+ var featureCounts = /* @__PURE__ */ new Map();
1015
+ function initWarningLogger(level) {
1016
+ currentLevel = level;
1017
+ entries = [];
1018
+ featureCounts.clear();
1019
+ }
1020
+ function warn(feature, message, context) {
1021
+ if (currentLevel === "off") return;
1022
+ entries.push({ feature, message, ...context !== void 0 && { context } });
1023
+ const existing = featureCounts.get(feature);
1024
+ if (existing) {
1025
+ existing.count++;
1026
+ } else {
1027
+ featureCounts.set(feature, { message, count: 1 });
1028
+ }
1029
+ if (currentLevel === "debug") {
1030
+ const ctx = context ? ` (${context})` : "";
1031
+ console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
1032
+ }
1033
+ }
1034
+ function debug(feature, message, context) {
1035
+ if (currentLevel !== "debug") return;
1036
+ entries.push({ feature, message, ...context !== void 0 && { context } });
1037
+ const existing = featureCounts.get(feature);
1038
+ if (existing) {
1039
+ existing.count++;
1040
+ } else {
1041
+ featureCounts.set(feature, { message, count: 1 });
1042
+ }
1043
+ const ctx = context ? ` (${context})` : "";
1044
+ console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
1045
+ }
1046
+ function getWarningSummary() {
1047
+ const features = [];
1048
+ for (const [feature, { message, count }] of featureCounts) {
1049
+ features.push({ feature, message, count });
1050
+ }
1051
+ return { totalCount: entries.length, features };
1052
+ }
1053
+ function flushWarnings() {
1054
+ const summary = getWarningSummary();
1055
+ if (currentLevel !== "off" && summary.features.length > 0) {
1056
+ console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
1057
+ for (const { feature, count } of summary.features) {
1058
+ console.warn(` - ${feature}: ${count} occurrence(s)`);
1059
+ }
1060
+ }
1061
+ entries = [];
1062
+ featureCounts.clear();
1063
+ return summary;
1064
+ }
1065
+ function getWarningEntries() {
1066
+ return entries;
1067
+ }
1068
+
1069
+ // src/font/cjk-font-fallback.ts
1070
+ import { platform } from "os";
1071
+ var MACOS_FALLBACKS = {
1072
+ // ゴシック系
1073
+ "Noto Sans JP": ["Hiragino Sans", "Hiragino Kaku Gothic ProN"],
1074
+ "Noto Sans CJK JP": ["Hiragino Sans", "Hiragino Kaku Gothic ProN"],
1075
+ // 明朝系
1076
+ "Noto Serif CJK JP": ["Hiragino Mincho ProN"]
1077
+ };
1078
+ var WINDOWS_FALLBACKS = {
1079
+ // ゴシック系
1080
+ "Noto Sans JP": ["Yu Gothic", "Meiryo", "MS Gothic"],
1081
+ "Noto Sans CJK JP": ["Yu Gothic", "Meiryo", "MS Gothic"],
1082
+ // 明朝系
1083
+ "Noto Serif CJK JP": ["Yu Mincho", "MS Mincho"]
1084
+ };
1085
+ var EMPTY = [];
1086
+ var cachedFallbacks = null;
1087
+ function getFallbackMap() {
1088
+ if (cachedFallbacks) return cachedFallbacks;
1089
+ const os = platform();
1090
+ switch (os) {
1091
+ case "darwin":
1092
+ cachedFallbacks = MACOS_FALLBACKS;
1093
+ break;
1094
+ case "win32":
1095
+ cachedFallbacks = WINDOWS_FALLBACKS;
1096
+ break;
1097
+ default:
1098
+ cachedFallbacks = {};
1099
+ }
1100
+ return cachedFallbacks;
1101
+ }
1102
+ function getCjkFallbackFonts(mappedFontName) {
1103
+ return getFallbackMap()[mappedFontName] ?? EMPTY;
1104
+ }
1105
+
1010
1106
  // src/font/opentype-text-measurer.ts
1011
1107
  var PX_PER_PT2 = 96 / 72;
1012
1108
  var BOLD_FACTOR2 = 1.05;
1013
1109
  var OpentypeTextMeasurer = class {
1014
1110
  fonts;
1015
1111
  defaultFont;
1112
+ warnedFonts = /* @__PURE__ */ new Set();
1016
1113
  /**
1017
1114
  * @param fonts - フォント名 → opentype.js Font オブジェクトのマップ
1018
1115
  * @param defaultFont - フォールバックフォント(省略可)
@@ -1062,6 +1159,14 @@ var OpentypeTextMeasurer = class {
1062
1159
  if (mapped) {
1063
1160
  const mappedFont = this.fonts.get(mapped);
1064
1161
  if (mappedFont) return mappedFont;
1162
+ for (const fallback of getCjkFallbackFonts(mapped)) {
1163
+ const fallbackFont = this.fonts.get(fallback);
1164
+ if (fallbackFont) return fallbackFont;
1165
+ }
1166
+ }
1167
+ if (!this.warnedFonts.has(name)) {
1168
+ this.warnedFonts.add(name);
1169
+ warn("font.notFound", `Font not found: "${name}"`);
1065
1170
  }
1066
1171
  return null;
1067
1172
  }
@@ -1069,14 +1174,26 @@ var OpentypeTextMeasurer = class {
1069
1174
 
1070
1175
  // src/font/system-font-loader.ts
1071
1176
  import { existsSync, readdirSync } from "fs";
1072
- import { homedir, platform } from "os";
1177
+ import { homedir, platform as platform2 } from "os";
1073
1178
  import { extname, join } from "path";
1074
1179
  var FONT_EXTENSIONS = /* @__PURE__ */ new Set([".ttf", ".otf"]);
1075
- var CJK_TTC_PATTERNS = ["NotoSansCJK", "NotoSerifCJK"];
1180
+ var CJK_TTC_PATTERNS = [
1181
+ "NotoSansCJK",
1182
+ "NotoSerifCJK",
1183
+ // macOS
1184
+ "Hiragino",
1185
+ "\u30D2\u30E9\u30AE\u30CE",
1186
+ // Windows
1187
+ "YuGoth",
1188
+ "YuMin",
1189
+ "meiryo",
1190
+ "msgothic",
1191
+ "msmincho"
1192
+ ];
1076
1193
  var cachedPaths = null;
1077
1194
  var cachedAdditionalDirs = null;
1078
1195
  function getSystemFontDirs() {
1079
- const os = platform();
1196
+ const os = platform2();
1080
1197
  switch (os) {
1081
1198
  case "linux":
1082
1199
  return ["/usr/share/fonts", "/usr/local/share/fonts"];
@@ -1089,7 +1206,7 @@ function getSystemFontDirs() {
1089
1206
  }
1090
1207
  }
1091
1208
  function isCjkTtc(name) {
1092
- const lower = name.toLowerCase();
1209
+ const lower = name.normalize("NFC").toLowerCase();
1093
1210
  return lower.endsWith(".ttc") && CJK_TTC_PATTERNS.some((p) => lower.includes(p.toLowerCase()));
1094
1211
  }
1095
1212
  function walk(dir, result) {
@@ -1127,6 +1244,7 @@ function collectFontFilePaths(additionalDirs) {
1127
1244
  var DefaultTextPathFontResolver = class {
1128
1245
  fonts;
1129
1246
  defaultFont;
1247
+ warnedFonts = /* @__PURE__ */ new Set();
1130
1248
  constructor(fonts, defaultFont) {
1131
1249
  this.fonts = fonts;
1132
1250
  this.defaultFont = defaultFont ?? null;
@@ -1144,6 +1262,12 @@ var DefaultTextPathFontResolver = class {
1144
1262
  const font = this.findFont(jpanFallback);
1145
1263
  if (font) return font;
1146
1264
  }
1265
+ for (const name of [fontFamily, fontFamilyEa, jpanFallback]) {
1266
+ if (name && !this.warnedFonts.has(name)) {
1267
+ this.warnedFonts.add(name);
1268
+ warn("font.notFound", `Font not found: "${name}"`);
1269
+ }
1270
+ }
1147
1271
  return this.defaultFont;
1148
1272
  }
1149
1273
  findFont(name) {
@@ -1153,6 +1277,10 @@ var DefaultTextPathFontResolver = class {
1153
1277
  if (mapped) {
1154
1278
  const mappedFont = this.fonts.get(mapped);
1155
1279
  if (mappedFont) return mappedFont;
1280
+ for (const fallback of getCjkFallbackFonts(mapped)) {
1281
+ const fallbackFont = this.fonts.get(fallback);
1282
+ if (fallbackFont) return fallbackFont;
1283
+ }
1156
1284
  }
1157
1285
  return null;
1158
1286
  }
@@ -1380,7 +1508,23 @@ function collectFontNames(font) {
1380
1508
  }
1381
1509
  return names;
1382
1510
  }
1511
+ function buildCacheKey(additionalFontDirs, fontMapping) {
1512
+ const dirsKey = additionalFontDirs ? [...additionalFontDirs].sort().join("\0") : "";
1513
+ const mappingKey = fontMapping ? JSON.stringify(fontMapping, Object.keys(fontMapping).sort()) : "";
1514
+ return `${dirsKey}
1515
+ ${mappingKey}`;
1516
+ }
1517
+ var cachedSetup = null;
1518
+ var cachedSetupKey = null;
1519
+ function clearFontCache() {
1520
+ cachedSetup = null;
1521
+ cachedSetupKey = null;
1522
+ }
1383
1523
  async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping) {
1524
+ const key = buildCacheKey(additionalFontDirs, fontMapping);
1525
+ if (cachedSetup && cachedSetupKey === key) {
1526
+ return cachedSetup;
1527
+ }
1384
1528
  const opentype = await tryLoadOpentype();
1385
1529
  if (!opentype) return null;
1386
1530
  const fontFilePaths = collectFontFilePaths(additionalFontDirs);
@@ -1412,7 +1556,10 @@ async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping) {
1412
1556
  resolverFonts,
1413
1557
  firstResolverFont ?? void 0
1414
1558
  );
1415
- return { measurer, fontResolver };
1559
+ const setup = { measurer, fontResolver };
1560
+ cachedSetup = setup;
1561
+ cachedSetupKey = key;
1562
+ return setup;
1416
1563
  }
1417
1564
 
1418
1565
  // src/font/script-font-context.ts
@@ -1584,65 +1731,6 @@ async function svgToPng(svgString, options) {
1584
1731
  };
1585
1732
  }
1586
1733
 
1587
- // src/warning-logger.ts
1588
- var PREFIX = "[pptx-glimpse]";
1589
- var currentLevel = "off";
1590
- var entries = [];
1591
- var featureCounts = /* @__PURE__ */ new Map();
1592
- function initWarningLogger(level) {
1593
- currentLevel = level;
1594
- entries = [];
1595
- featureCounts.clear();
1596
- }
1597
- function warn(feature, message, context) {
1598
- if (currentLevel === "off") return;
1599
- entries.push({ feature, message, ...context !== void 0 && { context } });
1600
- const existing = featureCounts.get(feature);
1601
- if (existing) {
1602
- existing.count++;
1603
- } else {
1604
- featureCounts.set(feature, { message, count: 1 });
1605
- }
1606
- if (currentLevel === "debug") {
1607
- const ctx = context ? ` (${context})` : "";
1608
- console.warn(`${PREFIX} SKIP: ${feature} - ${message}${ctx}`);
1609
- }
1610
- }
1611
- function debug(feature, message, context) {
1612
- if (currentLevel !== "debug") return;
1613
- entries.push({ feature, message, ...context !== void 0 && { context } });
1614
- const existing = featureCounts.get(feature);
1615
- if (existing) {
1616
- existing.count++;
1617
- } else {
1618
- featureCounts.set(feature, { message, count: 1 });
1619
- }
1620
- const ctx = context ? ` (${context})` : "";
1621
- console.warn(`${PREFIX} DEBUG: ${feature} - ${message}${ctx}`);
1622
- }
1623
- function getWarningSummary() {
1624
- const features = [];
1625
- for (const [feature, { message, count }] of featureCounts) {
1626
- features.push({ feature, message, count });
1627
- }
1628
- return { totalCount: entries.length, features };
1629
- }
1630
- function flushWarnings() {
1631
- const summary = getWarningSummary();
1632
- if (currentLevel !== "off" && summary.features.length > 0) {
1633
- console.warn(`${PREFIX} Summary: ${summary.features.length} unsupported feature(s) detected`);
1634
- for (const { feature, count } of summary.features) {
1635
- console.warn(` - ${feature}: ${count} occurrence(s)`);
1636
- }
1637
- }
1638
- entries = [];
1639
- featureCounts.clear();
1640
- return summary;
1641
- }
1642
- function getWarningEntries() {
1643
- return entries;
1644
- }
1645
-
1646
1734
  // src/color/color-transforms.ts
1647
1735
  function applyColorTransforms(color, node) {
1648
1736
  let { hex, alpha } = color;
@@ -8897,6 +8985,7 @@ function collectFontsFromTextBody(textBody, fonts) {
8897
8985
  }
8898
8986
  export {
8899
8987
  DEFAULT_FONT_MAPPING,
8988
+ clearFontCache,
8900
8989
  collectUsedFonts,
8901
8990
  convertPptxToPng,
8902
8991
  convertPptxToSvg,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptx-glimpse",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "A lightweight JavaScript library for rendering PowerPoint (.pptx) files as SVG or PNG in Node.js. No LibreOffice required.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",