pptx-glimpse 0.7.0 → 0.8.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
@@ -43,15 +43,18 @@ var DEFAULT_FONT_MAPPING = {
43
43
  "Times New Roman": "Tinos",
44
44
  "Courier New": "Cousine",
45
45
  Cambria: "Caladea",
46
- // 日本語ゴシック系 → Noto Sans CJK JP
47
- \u30E1\u30A4\u30EA\u30AA: "Noto Sans CJK JP",
48
- Meiryo: "Noto Sans CJK JP",
49
- \u6E38\u30B4\u30B7\u30C3\u30AF: "Noto Sans CJK JP",
50
- "Yu Gothic": "Noto Sans CJK JP",
51
- "MS \u30B4\u30B7\u30C3\u30AF": "Noto Sans CJK JP",
52
- "MS Gothic": "Noto Sans CJK JP",
53
- "MS P\u30B4\u30B7\u30C3\u30AF": "Noto Sans CJK JP",
54
- "MS PGothic": "Noto Sans CJK JP",
46
+ // 日本語ゴシック系 → Noto Sans JP
47
+ // "Noto Sans CJK JP" ではなく "Noto Sans JP" を使用する。
48
+ // NotoSansCJK TTC は最初の1フォントのみ抽出するため JP バリアントが取れるとは限らず、
49
+ // Docker 環境でダウンロードする standalone NotoSansJP.ttf のフォント名に合わせている。
50
+ \u30E1\u30A4\u30EA\u30AA: "Noto Sans JP",
51
+ Meiryo: "Noto Sans JP",
52
+ \u6E38\u30B4\u30B7\u30C3\u30AF: "Noto Sans JP",
53
+ "Yu Gothic": "Noto Sans JP",
54
+ "MS \u30B4\u30B7\u30C3\u30AF": "Noto Sans JP",
55
+ "MS Gothic": "Noto Sans JP",
56
+ "MS P\u30B4\u30B7\u30C3\u30AF": "Noto Sans JP",
57
+ "MS PGothic": "Noto Sans JP",
55
58
  // 日本語明朝系 → Noto Serif CJK JP
56
59
  "MS \u660E\u671D": "Noto Serif CJK JP",
57
60
  "MS Mincho": "Noto Serif CJK JP",
@@ -1055,19 +1058,22 @@ var OpentypeTextMeasurer = class {
1055
1058
  this.defaultFont = defaultFont ?? null;
1056
1059
  }
1057
1060
  measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
1058
- const font = this.resolveFont(fontFamily) ?? this.resolveFont(fontFamilyEa) ?? this.defaultFont;
1059
- if (!font) {
1061
+ const latinFont = this.resolveFont(fontFamily);
1062
+ const eaFont = this.resolveFont(fontFamilyEa);
1063
+ const fallbackFont = latinFont ?? eaFont ?? this.defaultFont;
1064
+ if (!fallbackFont) {
1060
1065
  return measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa);
1061
1066
  }
1062
1067
  const fontSizePx = fontSizePt * PX_PER_PT2;
1063
- const scale = fontSizePx / font.unitsPerEm;
1064
1068
  let totalWidth = 0;
1065
- const chars = [...text];
1066
- const glyphs = font.stringToGlyphs(text);
1067
- for (let i = 0; i < glyphs.length; i++) {
1068
- let charWidth = (glyphs[i].advanceWidth ?? font.unitsPerEm * 0.6) * scale;
1069
- const codePoint = chars[i]?.codePointAt(0);
1070
- if (bold && (codePoint === void 0 || !isCjkCodePoint(codePoint))) {
1069
+ for (const char of text) {
1070
+ const codePoint = char.codePointAt(0);
1071
+ const isEa = isCjkCodePoint(codePoint);
1072
+ const font = isEa ? eaFont ?? fallbackFont : latinFont ?? fallbackFont;
1073
+ const scale = fontSizePx / font.unitsPerEm;
1074
+ const glyphs = font.stringToGlyphs(char);
1075
+ let charWidth = (glyphs[0]?.advanceWidth ?? font.unitsPerEm * 0.6) * scale;
1076
+ if (bold && !isEa) {
1071
1077
  charWidth *= BOLD_FACTOR2;
1072
1078
  }
1073
1079
  totalWidth += charWidth;
@@ -1086,7 +1092,14 @@ var OpentypeTextMeasurer = class {
1086
1092
  }
1087
1093
  resolveFont(name) {
1088
1094
  if (!name) return null;
1089
- return this.fonts.get(name) ?? null;
1095
+ const direct = this.fonts.get(name);
1096
+ if (direct) return direct;
1097
+ const mapped = getCurrentMappedFont(name);
1098
+ if (mapped) {
1099
+ const mappedFont = this.fonts.get(mapped);
1100
+ if (mappedFont) return mappedFont;
1101
+ }
1102
+ return null;
1090
1103
  }
1091
1104
  };
1092
1105
 
@@ -3917,7 +3930,7 @@ function parseParagraph(p, colorResolver, rels, fontScheme, lstStyle, orderedPCh
3917
3930
  const lnSpcSpcPct = lnSpc?.spcPct;
3918
3931
  const tabStops = parseTabStops(pPr);
3919
3932
  const properties = {
3920
- alignment: pPr?.["@_algn"] ?? lstLevelProps?.alignment ?? "l",
3933
+ alignment: pPr?.["@_algn"] ?? lstLevelProps?.alignment ?? null,
3921
3934
  lineSpacing: lnSpcSpcPct ? Number(lnSpcSpcPct["@_val"]) : null,
3922
3935
  spaceBefore: parseSpacing(pPr?.spcBef),
3923
3936
  spaceAfter: parseSpacing(pPr?.spcAft),
@@ -4601,6 +4614,19 @@ function resolveShapeTextInheritance(shape, context) {
4601
4614
  const chainSources = [layoutLstStyle, masterLstStyle, txStyle, context.defaultTextStyle];
4602
4615
  for (const paragraph of shape.textBody.paragraphs) {
4603
4616
  const level = paragraph.properties.level;
4617
+ if (paragraph.properties.alignment === null) {
4618
+ for (const source of chainSources) {
4619
+ if (!source) continue;
4620
+ const alignment = source.levels[level]?.alignment ?? source.defaultParagraph?.alignment;
4621
+ if (alignment) {
4622
+ paragraph.properties.alignment = alignment;
4623
+ break;
4624
+ }
4625
+ }
4626
+ if (paragraph.properties.alignment === null) {
4627
+ paragraph.properties.alignment = "l";
4628
+ }
4629
+ }
4604
4630
  for (const run of paragraph.runs) {
4605
4631
  const props = run.properties;
4606
4632
  for (const source of chainSources) {
@@ -8202,8 +8228,8 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
8202
8228
  segText,
8203
8229
  fontSize,
8204
8230
  props.bold,
8205
- fontFamily,
8206
- fontFamilyEa
8231
+ props.fontFamily,
8232
+ props.fontFamilyEa
8207
8233
  );
8208
8234
  if (font) {
8209
8235
  const path = font.getPath(segText, x + totalWidth, effectiveY, fontSizePx);
@@ -8227,8 +8253,8 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
8227
8253
  char,
8228
8254
  fontSize,
8229
8255
  props.bold,
8230
- fontFamily,
8231
- fontFamilyEa
8256
+ props.fontFamily,
8257
+ props.fontFamilyEa
8232
8258
  );
8233
8259
  if (font) {
8234
8260
  const charX = x + totalWidth;
@@ -8787,6 +8813,9 @@ async function convertPptxToSvg(input, options) {
8787
8813
  const data = parsePptxData(input);
8788
8814
  setScriptFonts(data.theme.fontScheme.majorFontJpan, data.theme.fontScheme.minorFontJpan);
8789
8815
  const targetSlides = options?.slides ? data.slidePaths.filter((s) => options.slides.includes(s.slideNumber)) : data.slidePaths;
8816
+ if (data.slidePaths.length === 0) {
8817
+ warn("presentation.noSlides", "No slides found in the PPTX file");
8818
+ }
8790
8819
  const results = [];
8791
8820
  for (const { slideNumber, path } of targetSlides) {
8792
8821
  const parsed = parseSlideWithLayout(slideNumber, path, data);
package/dist/index.js CHANGED
@@ -7,15 +7,18 @@ var DEFAULT_FONT_MAPPING = {
7
7
  "Times New Roman": "Tinos",
8
8
  "Courier New": "Cousine",
9
9
  Cambria: "Caladea",
10
- // 日本語ゴシック系 → Noto Sans CJK JP
11
- \u30E1\u30A4\u30EA\u30AA: "Noto Sans CJK JP",
12
- Meiryo: "Noto Sans CJK JP",
13
- \u6E38\u30B4\u30B7\u30C3\u30AF: "Noto Sans CJK JP",
14
- "Yu Gothic": "Noto Sans CJK JP",
15
- "MS \u30B4\u30B7\u30C3\u30AF": "Noto Sans CJK JP",
16
- "MS Gothic": "Noto Sans CJK JP",
17
- "MS P\u30B4\u30B7\u30C3\u30AF": "Noto Sans CJK JP",
18
- "MS PGothic": "Noto Sans CJK JP",
10
+ // 日本語ゴシック系 → Noto Sans JP
11
+ // "Noto Sans CJK JP" ではなく "Noto Sans JP" を使用する。
12
+ // NotoSansCJK TTC は最初の1フォントのみ抽出するため JP バリアントが取れるとは限らず、
13
+ // Docker 環境でダウンロードする standalone NotoSansJP.ttf のフォント名に合わせている。
14
+ \u30E1\u30A4\u30EA\u30AA: "Noto Sans JP",
15
+ Meiryo: "Noto Sans JP",
16
+ \u6E38\u30B4\u30B7\u30C3\u30AF: "Noto Sans JP",
17
+ "Yu Gothic": "Noto Sans JP",
18
+ "MS \u30B4\u30B7\u30C3\u30AF": "Noto Sans JP",
19
+ "MS Gothic": "Noto Sans JP",
20
+ "MS P\u30B4\u30B7\u30C3\u30AF": "Noto Sans JP",
21
+ "MS PGothic": "Noto Sans JP",
19
22
  // 日本語明朝系 → Noto Serif CJK JP
20
23
  "MS \u660E\u671D": "Noto Serif CJK JP",
21
24
  "MS Mincho": "Noto Serif CJK JP",
@@ -1019,19 +1022,22 @@ var OpentypeTextMeasurer = class {
1019
1022
  this.defaultFont = defaultFont ?? null;
1020
1023
  }
1021
1024
  measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa) {
1022
- const font = this.resolveFont(fontFamily) ?? this.resolveFont(fontFamilyEa) ?? this.defaultFont;
1023
- if (!font) {
1025
+ const latinFont = this.resolveFont(fontFamily);
1026
+ const eaFont = this.resolveFont(fontFamilyEa);
1027
+ const fallbackFont = latinFont ?? eaFont ?? this.defaultFont;
1028
+ if (!fallbackFont) {
1024
1029
  return measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa);
1025
1030
  }
1026
1031
  const fontSizePx = fontSizePt * PX_PER_PT2;
1027
- const scale = fontSizePx / font.unitsPerEm;
1028
1032
  let totalWidth = 0;
1029
- const chars = [...text];
1030
- const glyphs = font.stringToGlyphs(text);
1031
- for (let i = 0; i < glyphs.length; i++) {
1032
- let charWidth = (glyphs[i].advanceWidth ?? font.unitsPerEm * 0.6) * scale;
1033
- const codePoint = chars[i]?.codePointAt(0);
1034
- if (bold && (codePoint === void 0 || !isCjkCodePoint(codePoint))) {
1033
+ for (const char of text) {
1034
+ const codePoint = char.codePointAt(0);
1035
+ const isEa = isCjkCodePoint(codePoint);
1036
+ const font = isEa ? eaFont ?? fallbackFont : latinFont ?? fallbackFont;
1037
+ const scale = fontSizePx / font.unitsPerEm;
1038
+ const glyphs = font.stringToGlyphs(char);
1039
+ let charWidth = (glyphs[0]?.advanceWidth ?? font.unitsPerEm * 0.6) * scale;
1040
+ if (bold && !isEa) {
1035
1041
  charWidth *= BOLD_FACTOR2;
1036
1042
  }
1037
1043
  totalWidth += charWidth;
@@ -1050,7 +1056,14 @@ var OpentypeTextMeasurer = class {
1050
1056
  }
1051
1057
  resolveFont(name) {
1052
1058
  if (!name) return null;
1053
- return this.fonts.get(name) ?? null;
1059
+ const direct = this.fonts.get(name);
1060
+ if (direct) return direct;
1061
+ const mapped = getCurrentMappedFont(name);
1062
+ if (mapped) {
1063
+ const mappedFont = this.fonts.get(mapped);
1064
+ if (mappedFont) return mappedFont;
1065
+ }
1066
+ return null;
1054
1067
  }
1055
1068
  };
1056
1069
 
@@ -3880,7 +3893,7 @@ function parseParagraph(p, colorResolver, rels, fontScheme, lstStyle, orderedPCh
3880
3893
  const lnSpcSpcPct = lnSpc?.spcPct;
3881
3894
  const tabStops = parseTabStops(pPr);
3882
3895
  const properties = {
3883
- alignment: pPr?.["@_algn"] ?? lstLevelProps?.alignment ?? "l",
3896
+ alignment: pPr?.["@_algn"] ?? lstLevelProps?.alignment ?? null,
3884
3897
  lineSpacing: lnSpcSpcPct ? Number(lnSpcSpcPct["@_val"]) : null,
3885
3898
  spaceBefore: parseSpacing(pPr?.spcBef),
3886
3899
  spaceAfter: parseSpacing(pPr?.spcAft),
@@ -4564,6 +4577,19 @@ function resolveShapeTextInheritance(shape, context) {
4564
4577
  const chainSources = [layoutLstStyle, masterLstStyle, txStyle, context.defaultTextStyle];
4565
4578
  for (const paragraph of shape.textBody.paragraphs) {
4566
4579
  const level = paragraph.properties.level;
4580
+ if (paragraph.properties.alignment === null) {
4581
+ for (const source of chainSources) {
4582
+ if (!source) continue;
4583
+ const alignment = source.levels[level]?.alignment ?? source.defaultParagraph?.alignment;
4584
+ if (alignment) {
4585
+ paragraph.properties.alignment = alignment;
4586
+ break;
4587
+ }
4588
+ }
4589
+ if (paragraph.properties.alignment === null) {
4590
+ paragraph.properties.alignment = "l";
4591
+ }
4592
+ }
4567
4593
  for (const run of paragraph.runs) {
4568
4594
  const props = run.properties;
4569
4595
  for (const source of chainSources) {
@@ -8165,8 +8191,8 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
8165
8191
  segText,
8166
8192
  fontSize,
8167
8193
  props.bold,
8168
- fontFamily,
8169
- fontFamilyEa
8194
+ props.fontFamily,
8195
+ props.fontFamilyEa
8170
8196
  );
8171
8197
  if (font) {
8172
8198
  const path = font.getPath(segText, x + totalWidth, effectiveY, fontSizePx);
@@ -8190,8 +8216,8 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
8190
8216
  char,
8191
8217
  fontSize,
8192
8218
  props.bold,
8193
- fontFamily,
8194
- fontFamilyEa
8219
+ props.fontFamily,
8220
+ props.fontFamilyEa
8195
8221
  );
8196
8222
  if (font) {
8197
8223
  const charX = x + totalWidth;
@@ -8750,6 +8776,9 @@ async function convertPptxToSvg(input, options) {
8750
8776
  const data = parsePptxData(input);
8751
8777
  setScriptFonts(data.theme.fontScheme.majorFontJpan, data.theme.fontScheme.minorFontJpan);
8752
8778
  const targetSlides = options?.slides ? data.slidePaths.filter((s) => options.slides.includes(s.slideNumber)) : data.slidePaths;
8779
+ if (data.slidePaths.length === 0) {
8780
+ warn("presentation.noSlides", "No slides found in the PPTX file");
8781
+ }
8753
8782
  const results = [];
8754
8783
  for (const { slideNumber, path } of targetSlides) {
8755
8784
  const parsed = parseSlideWithLayout(slideNumber, path, data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptx-glimpse",
3
- "version": "0.7.0",
3
+ "version": "0.8.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",
@@ -30,7 +30,7 @@
30
30
  "inspect": "tsx scripts/inspect-pptx.ts",
31
31
  "vrt:snapshot:docker-build": "docker build -t pptx-glimpse-snapshot-vrt docker/snapshot-vrt",
32
32
  "vrt:snapshot:fixtures": "tsx vrt/snapshot/create-fixtures.ts",
33
- "vrt:snapshot:update": "docker run --rm -v \"$(pwd)\":/workspace -v pptx-glimpse-snapshot-vrt-nm:/workspace/node_modules pptx-glimpse-snapshot-vrt bash /workspace/vrt/snapshot/docker-run.sh npx tsx vrt/snapshot/create-fixtures.ts && docker run --rm -v \"$(pwd)\":/workspace -v pptx-glimpse-snapshot-vrt-nm:/workspace/node_modules pptx-glimpse-snapshot-vrt bash /workspace/vrt/snapshot/docker-run.sh npx tsx vrt/snapshot/update-snapshots.ts",
33
+ "vrt:snapshot:update": "docker run --rm -v \"$(pwd)\":/workspace -v pptx-glimpse-snapshot-vrt-nm:/workspace/node_modules pptx-glimpse-snapshot-vrt bash /workspace/vrt/snapshot/docker-run.sh bash -c \"npx tsx vrt/snapshot/create-fixtures.ts && npx tsx vrt/snapshot/update-snapshots.ts\"",
34
34
  "vrt:lo:docker-build": "docker build -t pptx-glimpse-vrt docker/libreoffice-vrt",
35
35
  "vrt:lo:fixtures": "docker run --rm -v \"$(pwd)\":/workspace pptx-glimpse-vrt python3 /workspace/vrt/libreoffice/create_fixtures.py",
36
36
  "vrt:lo:update": "npm run vrt:lo:docker-build && npm run vrt:lo:fixtures && docker run --rm -v \"$(pwd)\":/workspace pptx-glimpse-vrt bash /workspace/vrt/libreoffice/update_snapshots.sh",