pptx-glimpse 0.11.0 → 0.11.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/index.cjs CHANGED
@@ -1163,16 +1163,35 @@ var OpentypeTextMeasurer = class {
1163
1163
  return measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa);
1164
1164
  }
1165
1165
  const fontSizePx = fontSizePt * PX_PER_PT2;
1166
+ const latinFontResolved = latinFont ?? fallbackFont;
1167
+ const eaFontResolved = eaFont ?? fallbackFont;
1168
+ const boldLatinFont = bold ? this.resolveBoldFont(fontFamily) : null;
1169
+ const latinGlyphCache = /* @__PURE__ */ new Map();
1170
+ const eaGlyphCache = eaFontResolved !== latinFontResolved ? /* @__PURE__ */ new Map() : latinGlyphCache;
1171
+ const boldGlyphCache = /* @__PURE__ */ new Map();
1166
1172
  let totalWidth = 0;
1167
1173
  for (const char of text) {
1168
1174
  const codePoint = char.codePointAt(0);
1169
1175
  const isEa = isCjkCodePoint(codePoint);
1170
- const font = isEa ? eaFont ?? fallbackFont : latinFont ?? fallbackFont;
1176
+ const font = isEa ? eaFontResolved : latinFontResolved;
1177
+ const cache = isEa ? eaGlyphCache : latinGlyphCache;
1178
+ if (!cache.has(char)) {
1179
+ cache.set(char, font.stringToGlyphs(char)[0]);
1180
+ }
1171
1181
  const scale = fontSizePx / font.unitsPerEm;
1172
- const glyphs = font.stringToGlyphs(char);
1173
- let charWidth = (glyphs[0]?.advanceWidth ?? font.unitsPerEm * 0.6) * scale;
1182
+ const glyph = cache.get(char);
1183
+ let charWidth = (glyph?.advanceWidth ?? font.unitsPerEm * 0.6) * scale;
1174
1184
  if (bold && !isEa) {
1175
- charWidth *= BOLD_FACTOR2;
1185
+ if (boldLatinFont) {
1186
+ if (!boldGlyphCache.has(char)) {
1187
+ boldGlyphCache.set(char, boldLatinFont.stringToGlyphs(char)[0]);
1188
+ }
1189
+ const boldGlyph = boldGlyphCache.get(char);
1190
+ const boldScale = fontSizePx / boldLatinFont.unitsPerEm;
1191
+ charWidth = (boldGlyph?.advanceWidth ?? boldLatinFont.unitsPerEm * 0.6) * boldScale;
1192
+ } else {
1193
+ charWidth *= BOLD_FACTOR2;
1194
+ }
1176
1195
  }
1177
1196
  totalWidth += charWidth;
1178
1197
  }
@@ -1188,6 +1207,24 @@ var OpentypeTextMeasurer = class {
1188
1207
  if (!font) return 1;
1189
1208
  return font.ascender / font.unitsPerEm;
1190
1209
  }
1210
+ resolveBoldFont(name) {
1211
+ if (!name) return null;
1212
+ const bases = [name];
1213
+ const mappedBase = getCurrentMappedFont(name);
1214
+ if (mappedBase && mappedBase !== name) bases.push(mappedBase);
1215
+ for (const base of bases) {
1216
+ for (const boldName of [`${base} Bold`, `${base}-Bold`]) {
1217
+ const direct = this.fonts.get(boldName);
1218
+ if (direct) return direct;
1219
+ const mapped = getCurrentMappedFont(boldName);
1220
+ if (mapped) {
1221
+ const mappedFont = this.fonts.get(mapped);
1222
+ if (mappedFont) return mappedFont;
1223
+ }
1224
+ }
1225
+ }
1226
+ return null;
1227
+ }
1191
1228
  resolveFont(name) {
1192
1229
  if (!name) return null;
1193
1230
  const direct = this.fonts.get(name);
@@ -1883,6 +1920,8 @@ var ColorResolver = class {
1883
1920
  this.colorScheme = colorScheme;
1884
1921
  this.colorMap = colorMap;
1885
1922
  }
1923
+ colorScheme;
1924
+ colorMap;
1886
1925
  resolve(colorNode) {
1887
1926
  if (!colorNode) return null;
1888
1927
  if (colorNode.srgbClr) {
@@ -1962,25 +2001,48 @@ var LazyMediaMap = class {
1962
2001
  return data;
1963
2002
  }
1964
2003
  };
2004
+ var LazyXmlMap = class {
2005
+ rawInput;
2006
+ cache = /* @__PURE__ */ new Map();
2007
+ entryIndex;
2008
+ constructor(rawInput, xmlEntryNames) {
2009
+ this.rawInput = rawInput;
2010
+ this.entryIndex = xmlEntryNames;
2011
+ }
2012
+ has(path) {
2013
+ return this.entryIndex.has(path);
2014
+ }
2015
+ get(path) {
2016
+ if (!this.entryIndex.has(path)) return void 0;
2017
+ const cached = this.cache.get(path);
2018
+ if (cached !== void 0) return cached;
2019
+ const result = (0, import_fflate.unzipSync)(this.rawInput, {
2020
+ filter: (file) => file.name === path
2021
+ });
2022
+ const data = result[path];
2023
+ if (data) {
2024
+ const str = (0, import_fflate.strFromU8)(data);
2025
+ this.cache.set(path, str);
2026
+ return str;
2027
+ }
2028
+ return void 0;
2029
+ }
2030
+ };
1965
2031
  function readPptx(input) {
1966
2032
  const rawInput = new Uint8Array(input);
2033
+ const xmlEntryNames = /* @__PURE__ */ new Set();
1967
2034
  const mediaEntryNames = /* @__PURE__ */ new Set();
1968
- const unzipped = (0, import_fflate.unzipSync)(rawInput, {
2035
+ (0, import_fflate.unzipSync)(rawInput, {
1969
2036
  filter: (file) => {
1970
2037
  if (file.name.startsWith("ppt/media/")) {
1971
2038
  mediaEntryNames.add(file.name);
1972
- return false;
2039
+ } else if (file.name.endsWith(".xml") || file.name.endsWith(".rels") || file.name === "[Content_Types].xml") {
2040
+ xmlEntryNames.add(file.name);
1973
2041
  }
1974
- return true;
2042
+ return false;
1975
2043
  }
1976
2044
  });
1977
- const files = /* @__PURE__ */ new Map();
1978
- for (const [relativePath, data] of Object.entries(unzipped)) {
1979
- if (relativePath.endsWith("/")) continue;
1980
- if (relativePath.endsWith(".xml") || relativePath.endsWith(".rels") || relativePath === "[Content_Types].xml") {
1981
- files.set(relativePath, (0, import_fflate.strFromU8)(data));
1982
- }
1983
- }
2045
+ const files = new LazyXmlMap(rawInput, xmlEntryNames);
1984
2046
  const media = new LazyMediaMap(rawInput, mediaEntryNames);
1985
2047
  return { files, media };
1986
2048
  }
@@ -3176,6 +3238,10 @@ function createDefaultBorders() {
3176
3238
 
3177
3239
  // src/parser/slide-parser.ts
3178
3240
  var SHAPE_TAGS = /* @__PURE__ */ new Set(["sp", "pic", "cxnSp", "grpSp", "graphicFrame"]);
3241
+ var SMARTART_DIAGRAM_URIS = /* @__PURE__ */ new Set([
3242
+ "http://schemas.openxmlformats.org/drawingml/2006/diagram",
3243
+ "http://purl.oclc.org/ooxml/drawingml/diagram"
3244
+ ]);
3179
3245
  function navigateOrdered(ordered, path) {
3180
3246
  let current = ordered;
3181
3247
  for (const key of path) {
@@ -3723,7 +3789,7 @@ function parseGraphicFrame(gf, rels, slidePath, archive, colorResolver, fontSche
3723
3789
  if (!tableData) return null;
3724
3790
  return { type: "table", transform, table: tableData };
3725
3791
  }
3726
- if (graphicData["@_uri"] === "http://schemas.openxmlformats.org/drawingml/2006/diagram") {
3792
+ if (SMARTART_DIAGRAM_URIS.has(graphicData["@_uri"])) {
3727
3793
  return parseSmartArt(
3728
3794
  graphicData,
3729
3795
  transform,
@@ -4156,7 +4222,7 @@ function extractMathText(node) {
4156
4222
  if (key === "t") {
4157
4223
  if (typeof value === "object" && value !== null) {
4158
4224
  text += value["#text"] ?? "";
4159
- } else if (value !== void 0 && value !== null) {
4225
+ } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4160
4226
  text += String(value);
4161
4227
  }
4162
4228
  } else if (typeof value === "object" && value !== null) {
@@ -4175,7 +4241,7 @@ function extractTextContent(node) {
4175
4241
  if (typeof text === "object") {
4176
4242
  return text["#text"] ?? "";
4177
4243
  }
4178
- return text !== void 0 && text !== null ? String(text) : "";
4244
+ return typeof text === "string" || typeof text === "number" || typeof text === "boolean" ? String(text) : "";
4179
4245
  }
4180
4246
  function parseParagraph(p, colorResolver, rels, fontScheme, lstStyle, orderedPChildren) {
4181
4247
  const pPr = p.pPr;
@@ -6864,7 +6930,7 @@ var presetGeometries = {
6864
6930
  const shaftEnd = h - headLength;
6865
6931
  return `<polygon points="${bodyLeft},0 ${bodyRight},0 ${bodyRight},${shaftEnd} ${w},${shaftEnd} ${w / 2},${h} 0,${shaftEnd} ${bodyLeft},${shaftEnd}"/>`;
6866
6932
  },
6867
- line: () => "",
6933
+ line: (w, h) => `<line x1="0" y1="0" x2="${w}" y2="${h}"/>`,
6868
6934
  // =====================
6869
6935
  // Connector shapes
6870
6936
  // =====================
@@ -8475,10 +8541,23 @@ function computePathLineX(alignment, textStartX, effectiveTextWidth, width, marg
8475
8541
  return textStartX;
8476
8542
  }
8477
8543
  }
8478
- function measureLineWidth(segments, defaultFontSize, fontScale) {
8544
+ function measureLineWidth(segments, defaultFontSize, fontScale, fontResolver) {
8479
8545
  let totalWidth = 0;
8546
+ const jpanFallback = fontResolver ? getJpanFallbackFont() : null;
8480
8547
  for (const seg of segments) {
8481
8548
  const fontSize = (seg.properties.fontSize ?? defaultFontSize) * fontScale;
8549
+ if (fontResolver) {
8550
+ const fontSizePx = fontSize * PX_PER_PT3;
8551
+ const font = fontResolver.resolveFont(
8552
+ seg.properties.fontFamily,
8553
+ seg.properties.fontFamilyEa,
8554
+ jpanFallback
8555
+ );
8556
+ if (font) {
8557
+ totalWidth += font.getAdvanceWidth(seg.text, fontSizePx);
8558
+ continue;
8559
+ }
8560
+ }
8482
8561
  totalWidth += getTextMeasurer().measureTextWidth(
8483
8562
  seg.text,
8484
8563
  fontSize,
@@ -8534,7 +8613,7 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
8534
8613
  const processSegment = (segText, fontFamily, fontFamilyEa) => {
8535
8614
  if (segText.length === 0) return;
8536
8615
  const font = fontResolver.resolveFont(fontFamily, fontFamilyEa, jpanFallback);
8537
- const segWidth = getTextMeasurer().measureTextWidth(
8616
+ const segWidth = font ? font.getAdvanceWidth(segText, fontSizePx) : getTextMeasurer().measureTextWidth(
8538
8617
  segText,
8539
8618
  fontSize,
8540
8619
  props.bold,
@@ -8559,7 +8638,7 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
8559
8638
  const font = fontResolver.resolveFont(fontFamily, fontFamilyEa, jpanFallback);
8560
8639
  const fillAttrs = buildPathFillAttrs(props);
8561
8640
  for (const char of segText) {
8562
- const charWidth = getTextMeasurer().measureTextWidth(
8641
+ const charWidth = font ? font.getAdvanceWidth(char, fontSizePx) : getTextMeasurer().measureTextWidth(
8563
8642
  char,
8564
8643
  fontSize,
8565
8644
  props.bold,
@@ -8731,7 +8810,7 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
8731
8810
  if (!isFirstLine) {
8732
8811
  currentY += lineNaturalHeightPt * PX_PER_PT3 * getLineSpacing(para, lnSpcReduction) + lineGapPx;
8733
8812
  }
8734
- const lineWidth = measureLineWidth(line.segments, defaultFontSize, fontScale);
8813
+ const lineWidth = measureLineWidth(line.segments, defaultFontSize, fontScale, fontResolver);
8735
8814
  const lineStartX = computePathLineX(
8736
8815
  para.properties.alignment,
8737
8816
  textStartX,
@@ -8780,7 +8859,7 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
8780
8859
  currentY += naturalHeightPt * PX_PER_PT3 * getLineSpacing(para, lnSpcReduction) + paragraphGapPx;
8781
8860
  }
8782
8861
  const runsAsSegments = para.runs.filter((r) => r.text.length > 0).map((r) => ({ text: r.text, properties: r.properties }));
8783
- const lineWidth = measureLineWidth(runsAsSegments, defaultFontSize, fontScale);
8862
+ const lineWidth = measureLineWidth(runsAsSegments, defaultFontSize, fontScale, fontResolver);
8784
8863
  const lineStartX = computePathLineX(
8785
8864
  para.properties.alignment,
8786
8865
  textStartX,
package/dist/index.d.cts CHANGED
@@ -136,6 +136,7 @@ declare class OpentypeTextMeasurer implements TextMeasurer {
136
136
  measureTextWidth(text: string, fontSizePt: number, bold: boolean, fontFamily?: string | null, fontFamilyEa?: string | null): number;
137
137
  getLineHeightRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
138
138
  getAscenderRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
139
+ private resolveBoldFont;
139
140
  private resolveFont;
140
141
  }
141
142
 
@@ -151,6 +152,7 @@ interface OpentypeFullFont {
151
152
  ascender: number;
152
153
  descender: number;
153
154
  getPath(text: string, x: number, y: number, fontSize: number): OpentypePath;
155
+ getAdvanceWidth(text: string, fontSize: number): number;
154
156
  }
155
157
  interface TextPathFontResolver {
156
158
  resolveFont(fontFamily: string | null | undefined, fontFamilyEa: string | null | undefined, jpanFallback?: string | null): OpentypeFullFont | null;
package/dist/index.d.ts CHANGED
@@ -136,6 +136,7 @@ declare class OpentypeTextMeasurer implements TextMeasurer {
136
136
  measureTextWidth(text: string, fontSizePt: number, bold: boolean, fontFamily?: string | null, fontFamilyEa?: string | null): number;
137
137
  getLineHeightRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
138
138
  getAscenderRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
139
+ private resolveBoldFont;
139
140
  private resolveFont;
140
141
  }
141
142
 
@@ -151,6 +152,7 @@ interface OpentypeFullFont {
151
152
  ascender: number;
152
153
  descender: number;
153
154
  getPath(text: string, x: number, y: number, fontSize: number): OpentypePath;
155
+ getAdvanceWidth(text: string, fontSize: number): number;
154
156
  }
155
157
  interface TextPathFontResolver {
156
158
  resolveFont(fontFamily: string | null | undefined, fontFamilyEa: string | null | undefined, jpanFallback?: string | null): OpentypeFullFont | null;
package/dist/index.js CHANGED
@@ -1126,16 +1126,35 @@ var OpentypeTextMeasurer = class {
1126
1126
  return measureTextWidth(text, fontSizePt, bold, fontFamily, fontFamilyEa);
1127
1127
  }
1128
1128
  const fontSizePx = fontSizePt * PX_PER_PT2;
1129
+ const latinFontResolved = latinFont ?? fallbackFont;
1130
+ const eaFontResolved = eaFont ?? fallbackFont;
1131
+ const boldLatinFont = bold ? this.resolveBoldFont(fontFamily) : null;
1132
+ const latinGlyphCache = /* @__PURE__ */ new Map();
1133
+ const eaGlyphCache = eaFontResolved !== latinFontResolved ? /* @__PURE__ */ new Map() : latinGlyphCache;
1134
+ const boldGlyphCache = /* @__PURE__ */ new Map();
1129
1135
  let totalWidth = 0;
1130
1136
  for (const char of text) {
1131
1137
  const codePoint = char.codePointAt(0);
1132
1138
  const isEa = isCjkCodePoint(codePoint);
1133
- const font = isEa ? eaFont ?? fallbackFont : latinFont ?? fallbackFont;
1139
+ const font = isEa ? eaFontResolved : latinFontResolved;
1140
+ const cache = isEa ? eaGlyphCache : latinGlyphCache;
1141
+ if (!cache.has(char)) {
1142
+ cache.set(char, font.stringToGlyphs(char)[0]);
1143
+ }
1134
1144
  const scale = fontSizePx / font.unitsPerEm;
1135
- const glyphs = font.stringToGlyphs(char);
1136
- let charWidth = (glyphs[0]?.advanceWidth ?? font.unitsPerEm * 0.6) * scale;
1145
+ const glyph = cache.get(char);
1146
+ let charWidth = (glyph?.advanceWidth ?? font.unitsPerEm * 0.6) * scale;
1137
1147
  if (bold && !isEa) {
1138
- charWidth *= BOLD_FACTOR2;
1148
+ if (boldLatinFont) {
1149
+ if (!boldGlyphCache.has(char)) {
1150
+ boldGlyphCache.set(char, boldLatinFont.stringToGlyphs(char)[0]);
1151
+ }
1152
+ const boldGlyph = boldGlyphCache.get(char);
1153
+ const boldScale = fontSizePx / boldLatinFont.unitsPerEm;
1154
+ charWidth = (boldGlyph?.advanceWidth ?? boldLatinFont.unitsPerEm * 0.6) * boldScale;
1155
+ } else {
1156
+ charWidth *= BOLD_FACTOR2;
1157
+ }
1139
1158
  }
1140
1159
  totalWidth += charWidth;
1141
1160
  }
@@ -1151,6 +1170,24 @@ var OpentypeTextMeasurer = class {
1151
1170
  if (!font) return 1;
1152
1171
  return font.ascender / font.unitsPerEm;
1153
1172
  }
1173
+ resolveBoldFont(name) {
1174
+ if (!name) return null;
1175
+ const bases = [name];
1176
+ const mappedBase = getCurrentMappedFont(name);
1177
+ if (mappedBase && mappedBase !== name) bases.push(mappedBase);
1178
+ for (const base of bases) {
1179
+ for (const boldName of [`${base} Bold`, `${base}-Bold`]) {
1180
+ const direct = this.fonts.get(boldName);
1181
+ if (direct) return direct;
1182
+ const mapped = getCurrentMappedFont(boldName);
1183
+ if (mapped) {
1184
+ const mappedFont = this.fonts.get(mapped);
1185
+ if (mappedFont) return mappedFont;
1186
+ }
1187
+ }
1188
+ }
1189
+ return null;
1190
+ }
1154
1191
  resolveFont(name) {
1155
1192
  if (!name) return null;
1156
1193
  const direct = this.fonts.get(name);
@@ -1845,6 +1882,8 @@ var ColorResolver = class {
1845
1882
  this.colorScheme = colorScheme;
1846
1883
  this.colorMap = colorMap;
1847
1884
  }
1885
+ colorScheme;
1886
+ colorMap;
1848
1887
  resolve(colorNode) {
1849
1888
  if (!colorNode) return null;
1850
1889
  if (colorNode.srgbClr) {
@@ -1924,25 +1963,48 @@ var LazyMediaMap = class {
1924
1963
  return data;
1925
1964
  }
1926
1965
  };
1966
+ var LazyXmlMap = class {
1967
+ rawInput;
1968
+ cache = /* @__PURE__ */ new Map();
1969
+ entryIndex;
1970
+ constructor(rawInput, xmlEntryNames) {
1971
+ this.rawInput = rawInput;
1972
+ this.entryIndex = xmlEntryNames;
1973
+ }
1974
+ has(path) {
1975
+ return this.entryIndex.has(path);
1976
+ }
1977
+ get(path) {
1978
+ if (!this.entryIndex.has(path)) return void 0;
1979
+ const cached = this.cache.get(path);
1980
+ if (cached !== void 0) return cached;
1981
+ const result = unzipSync(this.rawInput, {
1982
+ filter: (file) => file.name === path
1983
+ });
1984
+ const data = result[path];
1985
+ if (data) {
1986
+ const str = strFromU8(data);
1987
+ this.cache.set(path, str);
1988
+ return str;
1989
+ }
1990
+ return void 0;
1991
+ }
1992
+ };
1927
1993
  function readPptx(input) {
1928
1994
  const rawInput = new Uint8Array(input);
1995
+ const xmlEntryNames = /* @__PURE__ */ new Set();
1929
1996
  const mediaEntryNames = /* @__PURE__ */ new Set();
1930
- const unzipped = unzipSync(rawInput, {
1997
+ unzipSync(rawInput, {
1931
1998
  filter: (file) => {
1932
1999
  if (file.name.startsWith("ppt/media/")) {
1933
2000
  mediaEntryNames.add(file.name);
1934
- return false;
2001
+ } else if (file.name.endsWith(".xml") || file.name.endsWith(".rels") || file.name === "[Content_Types].xml") {
2002
+ xmlEntryNames.add(file.name);
1935
2003
  }
1936
- return true;
2004
+ return false;
1937
2005
  }
1938
2006
  });
1939
- const files = /* @__PURE__ */ new Map();
1940
- for (const [relativePath, data] of Object.entries(unzipped)) {
1941
- if (relativePath.endsWith("/")) continue;
1942
- if (relativePath.endsWith(".xml") || relativePath.endsWith(".rels") || relativePath === "[Content_Types].xml") {
1943
- files.set(relativePath, strFromU8(data));
1944
- }
1945
- }
2007
+ const files = new LazyXmlMap(rawInput, xmlEntryNames);
1946
2008
  const media = new LazyMediaMap(rawInput, mediaEntryNames);
1947
2009
  return { files, media };
1948
2010
  }
@@ -3138,6 +3200,10 @@ function createDefaultBorders() {
3138
3200
 
3139
3201
  // src/parser/slide-parser.ts
3140
3202
  var SHAPE_TAGS = /* @__PURE__ */ new Set(["sp", "pic", "cxnSp", "grpSp", "graphicFrame"]);
3203
+ var SMARTART_DIAGRAM_URIS = /* @__PURE__ */ new Set([
3204
+ "http://schemas.openxmlformats.org/drawingml/2006/diagram",
3205
+ "http://purl.oclc.org/ooxml/drawingml/diagram"
3206
+ ]);
3141
3207
  function navigateOrdered(ordered, path) {
3142
3208
  let current = ordered;
3143
3209
  for (const key of path) {
@@ -3685,7 +3751,7 @@ function parseGraphicFrame(gf, rels, slidePath, archive, colorResolver, fontSche
3685
3751
  if (!tableData) return null;
3686
3752
  return { type: "table", transform, table: tableData };
3687
3753
  }
3688
- if (graphicData["@_uri"] === "http://schemas.openxmlformats.org/drawingml/2006/diagram") {
3754
+ if (SMARTART_DIAGRAM_URIS.has(graphicData["@_uri"])) {
3689
3755
  return parseSmartArt(
3690
3756
  graphicData,
3691
3757
  transform,
@@ -4118,7 +4184,7 @@ function extractMathText(node) {
4118
4184
  if (key === "t") {
4119
4185
  if (typeof value === "object" && value !== null) {
4120
4186
  text += value["#text"] ?? "";
4121
- } else if (value !== void 0 && value !== null) {
4187
+ } else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4122
4188
  text += String(value);
4123
4189
  }
4124
4190
  } else if (typeof value === "object" && value !== null) {
@@ -4137,7 +4203,7 @@ function extractTextContent(node) {
4137
4203
  if (typeof text === "object") {
4138
4204
  return text["#text"] ?? "";
4139
4205
  }
4140
- return text !== void 0 && text !== null ? String(text) : "";
4206
+ return typeof text === "string" || typeof text === "number" || typeof text === "boolean" ? String(text) : "";
4141
4207
  }
4142
4208
  function parseParagraph(p, colorResolver, rels, fontScheme, lstStyle, orderedPChildren) {
4143
4209
  const pPr = p.pPr;
@@ -6826,7 +6892,7 @@ var presetGeometries = {
6826
6892
  const shaftEnd = h - headLength;
6827
6893
  return `<polygon points="${bodyLeft},0 ${bodyRight},0 ${bodyRight},${shaftEnd} ${w},${shaftEnd} ${w / 2},${h} 0,${shaftEnd} ${bodyLeft},${shaftEnd}"/>`;
6828
6894
  },
6829
- line: () => "",
6895
+ line: (w, h) => `<line x1="0" y1="0" x2="${w}" y2="${h}"/>`,
6830
6896
  // =====================
6831
6897
  // Connector shapes
6832
6898
  // =====================
@@ -8437,10 +8503,23 @@ function computePathLineX(alignment, textStartX, effectiveTextWidth, width, marg
8437
8503
  return textStartX;
8438
8504
  }
8439
8505
  }
8440
- function measureLineWidth(segments, defaultFontSize, fontScale) {
8506
+ function measureLineWidth(segments, defaultFontSize, fontScale, fontResolver) {
8441
8507
  let totalWidth = 0;
8508
+ const jpanFallback = fontResolver ? getJpanFallbackFont() : null;
8442
8509
  for (const seg of segments) {
8443
8510
  const fontSize = (seg.properties.fontSize ?? defaultFontSize) * fontScale;
8511
+ if (fontResolver) {
8512
+ const fontSizePx = fontSize * PX_PER_PT3;
8513
+ const font = fontResolver.resolveFont(
8514
+ seg.properties.fontFamily,
8515
+ seg.properties.fontFamilyEa,
8516
+ jpanFallback
8517
+ );
8518
+ if (font) {
8519
+ totalWidth += font.getAdvanceWidth(seg.text, fontSizePx);
8520
+ continue;
8521
+ }
8522
+ }
8444
8523
  totalWidth += getTextMeasurer().measureTextWidth(
8445
8524
  seg.text,
8446
8525
  fontSize,
@@ -8496,7 +8575,7 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
8496
8575
  const processSegment = (segText, fontFamily, fontFamilyEa) => {
8497
8576
  if (segText.length === 0) return;
8498
8577
  const font = fontResolver.resolveFont(fontFamily, fontFamilyEa, jpanFallback);
8499
- const segWidth = getTextMeasurer().measureTextWidth(
8578
+ const segWidth = font ? font.getAdvanceWidth(segText, fontSizePx) : getTextMeasurer().measureTextWidth(
8500
8579
  segText,
8501
8580
  fontSize,
8502
8581
  props.bold,
@@ -8521,7 +8600,7 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
8521
8600
  const font = fontResolver.resolveFont(fontFamily, fontFamilyEa, jpanFallback);
8522
8601
  const fillAttrs = buildPathFillAttrs(props);
8523
8602
  for (const char of segText) {
8524
- const charWidth = getTextMeasurer().measureTextWidth(
8603
+ const charWidth = font ? font.getAdvanceWidth(char, fontSizePx) : getTextMeasurer().measureTextWidth(
8525
8604
  char,
8526
8605
  fontSize,
8527
8606
  props.bold,
@@ -8693,7 +8772,7 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
8693
8772
  if (!isFirstLine) {
8694
8773
  currentY += lineNaturalHeightPt * PX_PER_PT3 * getLineSpacing(para, lnSpcReduction) + lineGapPx;
8695
8774
  }
8696
- const lineWidth = measureLineWidth(line.segments, defaultFontSize, fontScale);
8775
+ const lineWidth = measureLineWidth(line.segments, defaultFontSize, fontScale, fontResolver);
8697
8776
  const lineStartX = computePathLineX(
8698
8777
  para.properties.alignment,
8699
8778
  textStartX,
@@ -8742,7 +8821,7 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
8742
8821
  currentY += naturalHeightPt * PX_PER_PT3 * getLineSpacing(para, lnSpcReduction) + paragraphGapPx;
8743
8822
  }
8744
8823
  const runsAsSegments = para.runs.filter((r) => r.text.length > 0).map((r) => ({ text: r.text, properties: r.properties }));
8745
- const lineWidth = measureLineWidth(runsAsSegments, defaultFontSize, fontScale);
8824
+ const lineWidth = measureLineWidth(runsAsSegments, defaultFontSize, fontScale, fontResolver);
8746
8825
  const lineStartX = computePathLineX(
8747
8826
  para.properties.alignment,
8748
8827
  textStartX,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptx-glimpse",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
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",
@@ -13,36 +13,9 @@
13
13
  "require": "./dist/index.cjs"
14
14
  }
15
15
  },
16
- "scripts": {
17
- "build": "tsup src/index.ts --format cjs,esm --dts",
18
- "bench": "vitest bench",
19
- "lint": "eslint src/ vrt/ scripts/ bench/ e2e/",
20
- "lint:fix": "eslint src/ vrt/ scripts/ bench/ e2e/ --fix",
21
- "format": "prettier --write 'src/**/*.ts' 'vrt/**/*.ts' 'scripts/**/*.ts' 'bench/**/*.ts' 'e2e/**/*.ts'",
22
- "format:check": "prettier --check 'src/**/*.ts' 'vrt/**/*.ts' 'scripts/**/*.ts' 'bench/**/*.ts' 'e2e/**/*.ts'",
23
- "test": "vitest run",
24
- "test:coverage": "vitest run --coverage",
25
- "test:watch": "vitest",
26
- "typecheck": "tsc --noEmit",
27
- "knip": "knip",
28
- "dev": "tsx scripts/dev-server.ts",
29
- "render": "tsx scripts/test-render.ts",
30
- "inspect": "tsx scripts/inspect-pptx.ts",
31
- "vrt:snapshot:docker-build": "docker build -t pptx-glimpse-snapshot-vrt docker/snapshot-vrt",
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 bash -c \"npx tsx vrt/snapshot/create-fixtures.ts && npx tsx vrt/snapshot/update-snapshots.ts\"",
34
- "vrt:lo:docker-build": "docker build -t pptx-glimpse-vrt docker/libreoffice-vrt",
35
- "vrt:lo:fixtures": "docker run --rm -v \"$(pwd)\":/workspace pptx-glimpse-vrt python3 /workspace/vrt/libreoffice/create_fixtures.py",
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",
37
- "test:package": "npm run build && bash scripts/test-package.sh",
38
- "changeset": "changeset",
39
- "demo:dev": "cd demo && npm run dev",
40
- "demo:build": "npm run build && cd demo && npm install && npm run build"
41
- },
42
16
  "files": [
43
17
  "dist"
44
18
  ],
45
- "packageManager": "npm@10.9.6",
46
19
  "engines": {
47
20
  "node": ">=20"
48
21
  },
@@ -67,7 +40,7 @@
67
40
  "@resvg/resvg-wasm": "^2.6.2",
68
41
  "fast-xml-parser": "^5.7.3",
69
42
  "fflate": "^0.8.2",
70
- "opentype.js": "^1.3.4"
43
+ "opentype.js": "1.3.4"
71
44
  },
72
45
  "devDependencies": {
73
46
  "@changesets/cli": "^2.29.8",
@@ -88,5 +61,31 @@
88
61
  "typescript-eslint": "^8.55.0",
89
62
  "vitest": "^3.0.0",
90
63
  "ws": "^8.19.0"
64
+ },
65
+ "scripts": {
66
+ "build": "tsup src/index.ts --format cjs,esm --dts",
67
+ "bench": "vitest bench",
68
+ "lint": "eslint src/ vrt/ scripts/ bench/ e2e/",
69
+ "lint:fix": "eslint src/ vrt/ scripts/ bench/ e2e/ --fix",
70
+ "format": "prettier --write 'src/**/*.ts' 'vrt/**/*.ts' 'scripts/**/*.ts' 'bench/**/*.ts' 'e2e/**/*.ts'",
71
+ "format:check": "prettier --check 'src/**/*.ts' 'vrt/**/*.ts' 'scripts/**/*.ts' 'bench/**/*.ts' 'e2e/**/*.ts'",
72
+ "test": "vitest run",
73
+ "test:coverage": "vitest run --coverage",
74
+ "test:watch": "vitest",
75
+ "typecheck": "tsc --noEmit",
76
+ "knip": "knip",
77
+ "dev": "tsx scripts/dev-server.ts",
78
+ "render": "tsx scripts/test-render.ts",
79
+ "inspect": "tsx scripts/inspect-pptx.ts",
80
+ "vrt:snapshot:docker-build": "docker build -t pptx-glimpse-snapshot-vrt docker/snapshot-vrt",
81
+ "vrt:snapshot:fixtures": "tsx vrt/snapshot/create-fixtures.ts",
82
+ "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\"",
83
+ "vrt:lo:docker-build": "docker build -t pptx-glimpse-vrt docker/libreoffice-vrt",
84
+ "vrt:lo:fixtures": "docker run --rm -v \"$(pwd)\":/workspace pptx-glimpse-vrt python3 /workspace/vrt/libreoffice/create_fixtures.py",
85
+ "vrt:lo:update": "pnpm run vrt:lo:docker-build && pnpm run vrt:lo:fixtures && docker run --rm -v \"$(pwd)\":/workspace pptx-glimpse-vrt bash /workspace/vrt/libreoffice/update_snapshots.sh",
86
+ "test:package": "pnpm run build && bash scripts/test-package.sh",
87
+ "changeset": "changeset",
88
+ "demo:dev": "cd demo && npm run dev",
89
+ "demo:build": "pnpm run build && cd demo && npm install && npm run build"
91
90
  }
92
- }
91
+ }