pptx-glimpse 0.1.3 → 0.2.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.
Files changed (3) hide show
  1. package/dist/index.cjs +429 -74
  2. package/dist/index.js +429 -74
  3. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -1101,6 +1101,14 @@ function buildMarkerDef(id, endpoint, color, alpha) {
1101
1101
  return `<marker id="${id}" markerWidth="${mw}" markerHeight="${mh}" refX="${mw}" refY="${mh / 2}" orient="auto" markerUnits="userSpaceOnUse"><path d="${path}" ${fillAttr2}${alphaAttr}/></marker>`;
1102
1102
  }
1103
1103
 
1104
+ // src/utils/unit-types.ts
1105
+ function asEmu(value) {
1106
+ return value;
1107
+ }
1108
+ function asHundredthPt(value) {
1109
+ return value;
1110
+ }
1111
+
1104
1112
  // src/data/font-metrics.ts
1105
1113
  var metricsData = {
1106
1114
  Carlito: {
@@ -2977,7 +2985,7 @@ function computeSpAutofitHeight(textBody, transform) {
2977
2985
  const requiredHeightPx = textHeight + marginTopPx + marginBottomPx;
2978
2986
  if (requiredHeightPx <= height) return null;
2979
2987
  const DEFAULT_DPI2 = 96;
2980
- return requiredHeightPx / DEFAULT_DPI2 * EMU_PER_INCH;
2988
+ return asEmu(requiredHeightPx / DEFAULT_DPI2 * EMU_PER_INCH);
2981
2989
  }
2982
2990
  function computeShrinkToFitScale(paragraphs, defaultFontSize, fontScale, lnSpcReduction, textWidth, availableHeight) {
2983
2991
  if (availableHeight <= 0) return fontScale;
@@ -3084,7 +3092,7 @@ function renderTextDecorations(x, y, segmentWidth, fontSizePx, props) {
3084
3092
  }
3085
3093
  return lines;
3086
3094
  }
3087
- function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, fontResolver) {
3095
+ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, fontResolver, vert) {
3088
3096
  const fontSize = (props.fontSize ?? defaultFontSize) * fontScale;
3089
3097
  const fontSizePx = fontSize * PX_PER_PT2;
3090
3098
  const parts = [];
@@ -3117,7 +3125,50 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
3117
3125
  }
3118
3126
  totalWidth += segWidth;
3119
3127
  };
3120
- if (needsScriptSplit(props)) {
3128
+ const processCjkUpright = (segText, fontFamily, fontFamilyEa) => {
3129
+ if (segText.length === 0) return;
3130
+ const font = fontResolver.resolveFont(fontFamily, fontFamilyEa);
3131
+ const fillAttrs = buildPathFillAttrs(props);
3132
+ for (const char of segText) {
3133
+ const charWidth = getTextMeasurer().measureTextWidth(
3134
+ char,
3135
+ fontSize,
3136
+ props.bold,
3137
+ fontFamily,
3138
+ fontFamilyEa
3139
+ );
3140
+ if (font) {
3141
+ const charX = x + totalWidth;
3142
+ const path = font.getPath(char, charX, effectiveY, fontSizePx);
3143
+ const pathData = path.toPathData(2);
3144
+ if (pathData && pathData.length > 0) {
3145
+ const cx = charX + charWidth / 2;
3146
+ const cy = effectiveY - fontSizePx * (font.ascender + font.descender) / 2 / font.unitsPerEm;
3147
+ parts.push(
3148
+ `<g transform="rotate(-90, ${cx.toFixed(2)}, ${cy.toFixed(2)})"><path d="${pathData}" ${fillAttrs}/></g>`
3149
+ );
3150
+ }
3151
+ }
3152
+ if (props.underline || props.strikethrough) {
3153
+ parts.push(
3154
+ ...renderTextDecorations(x + totalWidth, effectiveY, charWidth, fontSizePx, props)
3155
+ );
3156
+ }
3157
+ totalWidth += charWidth;
3158
+ }
3159
+ };
3160
+ if (vert === "eaVert") {
3161
+ const scriptParts = splitByScript(processedText);
3162
+ for (const part of scriptParts) {
3163
+ const ff = part.isEa ? props.fontFamilyEa ?? props.fontFamily : props.fontFamily;
3164
+ const ffEa = part.isEa ? props.fontFamilyEa : props.fontFamilyEa;
3165
+ if (part.isEa) {
3166
+ processCjkUpright(part.text, ff, ffEa);
3167
+ } else {
3168
+ processSegment(part.text, ff, ffEa);
3169
+ }
3170
+ }
3171
+ } else if (needsScriptSplit(props)) {
3121
3172
  const scriptParts = splitByScript(processedText);
3122
3173
  for (const part of scriptParts) {
3123
3174
  const ff = part.isEa ? props.fontFamilyEa : props.fontFamily;
@@ -3283,7 +3334,8 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
3283
3334
  currentY,
3284
3335
  fontScale,
3285
3336
  defaultFontSize,
3286
- fontResolver
3337
+ fontResolver,
3338
+ bodyProperties.vert
3287
3339
  );
3288
3340
  if (result.svg) elements.push(result.svg);
3289
3341
  currentX += result.width;
@@ -3330,7 +3382,8 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
3330
3382
  currentY,
3331
3383
  fontScale,
3332
3384
  defaultFontSize,
3333
- fontResolver
3385
+ fontResolver,
3386
+ bodyProperties.vert
3334
3387
  );
3335
3388
  if (result.svg) elements.push(result.svg);
3336
3389
  currentX += result.width;
@@ -3773,6 +3826,15 @@ function renderChart(element) {
3773
3826
  case "radar":
3774
3827
  parts.push(renderRadarChart(chart, plotX, plotY, plotW, plotH));
3775
3828
  break;
3829
+ case "stock":
3830
+ parts.push(renderStockChart(chart, plotX, plotY, plotW, plotH));
3831
+ break;
3832
+ case "surface":
3833
+ parts.push(renderSurfaceChart(chart, plotX, plotY, plotW, plotH));
3834
+ break;
3835
+ case "ofPie":
3836
+ parts.push(renderOfPieChart(chart, plotX, plotY, plotW, plotH));
3837
+ break;
3776
3838
  }
3777
3839
  }
3778
3840
  if (chart.legend && chart.series.length > 0) {
@@ -4144,9 +4206,237 @@ function renderRadarChart(chart, x, y, w, h) {
4144
4206
  }
4145
4207
  return parts.join("");
4146
4208
  }
4209
+ function renderStockChart(chart, x, y, w, h) {
4210
+ const parts = [];
4211
+ const { series, categories } = chart;
4212
+ if (series.length < 3) return "";
4213
+ const highSeries = series[0];
4214
+ const lowSeries = series[1];
4215
+ const closeSeries = series[2];
4216
+ const catCount = categories.length || highSeries.values.length;
4217
+ if (catCount === 0) return "";
4218
+ let maxVal = 0;
4219
+ let minVal = Infinity;
4220
+ for (const s of [highSeries, lowSeries, closeSeries]) {
4221
+ for (const v of s.values) {
4222
+ maxVal = Math.max(maxVal, v);
4223
+ minVal = Math.min(minVal, v);
4224
+ }
4225
+ }
4226
+ if (maxVal === minVal) return "";
4227
+ parts.push(
4228
+ `<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4229
+ );
4230
+ parts.push(
4231
+ `<line x1="${round3(x)}" y1="${round3(y)}" x2="${round3(x)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4232
+ );
4233
+ for (let c = 0; c < catCount; c++) {
4234
+ const label = categories[c] ?? "";
4235
+ const labelX = x + (c + 0.5) * (w / catCount);
4236
+ parts.push(
4237
+ `<text x="${round3(labelX)}" y="${round3(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
4238
+ );
4239
+ }
4240
+ const range = maxVal - minVal;
4241
+ for (let c = 0; c < catCount; c++) {
4242
+ const cx = x + (c + 0.5) * (w / catCount);
4243
+ const highVal = highSeries.values[c] ?? 0;
4244
+ const lowVal = lowSeries.values[c] ?? 0;
4245
+ const closeVal = closeSeries.values[c] ?? 0;
4246
+ const highY = y + h - (highVal - minVal) / range * h;
4247
+ const lowY = y + h - (lowVal - minVal) / range * h;
4248
+ const closeY = y + h - (closeVal - minVal) / range * h;
4249
+ parts.push(
4250
+ `<line x1="${round3(cx)}" y1="${round3(highY)}" x2="${round3(cx)}" y2="${round3(lowY)}" stroke="#404040" stroke-width="2"/>`
4251
+ );
4252
+ const tickW = w / catCount * 0.2;
4253
+ parts.push(
4254
+ `<line x1="${round3(cx)}" y1="${round3(closeY)}" x2="${round3(cx + tickW)}" y2="${round3(closeY)}" stroke="#404040" stroke-width="2"/>`
4255
+ );
4256
+ }
4257
+ return parts.join("");
4258
+ }
4259
+ function renderSurfaceChart(chart, x, y, w, h) {
4260
+ const parts = [];
4261
+ const { series, categories } = chart;
4262
+ if (series.length === 0) return "";
4263
+ const rows = series.length;
4264
+ const cols = categories.length || Math.max(...series.map((s) => s.values.length));
4265
+ if (cols === 0) return "";
4266
+ let minVal = Infinity;
4267
+ let maxVal = -Infinity;
4268
+ for (const s of series) {
4269
+ for (const v of s.values) {
4270
+ minVal = Math.min(minVal, v);
4271
+ maxVal = Math.max(maxVal, v);
4272
+ }
4273
+ }
4274
+ if (minVal === maxVal) maxVal = minVal + 1;
4275
+ const cellW = w / cols;
4276
+ const cellH = h / rows;
4277
+ for (let r = 0; r < rows; r++) {
4278
+ for (let c = 0; c < cols; c++) {
4279
+ const val = series[r].values[c] ?? 0;
4280
+ const t = (val - minVal) / (maxVal - minVal);
4281
+ const color = heatmapColor(t);
4282
+ const cx = x + c * cellW;
4283
+ const cy = y + r * cellH;
4284
+ parts.push(
4285
+ `<rect x="${round3(cx)}" y="${round3(cy)}" width="${round3(cellW)}" height="${round3(cellH)}" fill="${color}" stroke="#FFFFFF" stroke-width="0.5"/>`
4286
+ );
4287
+ }
4288
+ }
4289
+ for (let c = 0; c < cols; c++) {
4290
+ const label = categories[c] ?? "";
4291
+ if (label) {
4292
+ const labelX = x + (c + 0.5) * cellW;
4293
+ parts.push(
4294
+ `<text x="${round3(labelX)}" y="${round3(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
4295
+ );
4296
+ }
4297
+ }
4298
+ for (let r = 0; r < rows; r++) {
4299
+ const label = series[r].name ?? "";
4300
+ if (label) {
4301
+ const labelY = y + (r + 0.5) * cellH;
4302
+ parts.push(
4303
+ `<text x="${round3(x - 5)}" y="${round3(labelY + 4)}" text-anchor="end" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
4304
+ );
4305
+ }
4306
+ }
4307
+ return parts.join("");
4308
+ }
4309
+ function heatmapColor(t) {
4310
+ const clamped = Math.max(0, Math.min(1, t));
4311
+ let r, g, b;
4312
+ if (clamped < 0.25) {
4313
+ const s = clamped / 0.25;
4314
+ r = 0;
4315
+ g = Math.round(s * 255);
4316
+ b = 255;
4317
+ } else if (clamped < 0.5) {
4318
+ const s = (clamped - 0.25) / 0.25;
4319
+ r = 0;
4320
+ g = 255;
4321
+ b = Math.round((1 - s) * 255);
4322
+ } else if (clamped < 0.75) {
4323
+ const s = (clamped - 0.5) / 0.25;
4324
+ r = Math.round(s * 255);
4325
+ g = 255;
4326
+ b = 0;
4327
+ } else {
4328
+ const s = (clamped - 0.75) / 0.25;
4329
+ r = 255;
4330
+ g = Math.round((1 - s) * 255);
4331
+ b = 0;
4332
+ }
4333
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
4334
+ }
4335
+ function renderOfPieChart(chart, x, y, w, h) {
4336
+ const parts = [];
4337
+ const series = chart.series[0];
4338
+ if (!series || series.values.length === 0) return "";
4339
+ const total = series.values.reduce((sum, v) => sum + v, 0);
4340
+ if (total === 0) return "";
4341
+ const splitPos = chart.splitPos ?? 2;
4342
+ const secondPieSize = chart.secondPieSize ?? 75;
4343
+ const isBarOfPie = chart.ofPieType === "bar";
4344
+ const splitIdx = Math.max(0, series.values.length - splitPos);
4345
+ const primaryValues = series.values.slice(0, splitIdx);
4346
+ const secondaryValues = series.values.slice(splitIdx);
4347
+ const secondaryTotal = secondaryValues.reduce((sum, v) => sum + v, 0);
4348
+ const pieW = w * 0.45;
4349
+ const pieCx = x + pieW / 2;
4350
+ const pieCy = y + h / 2;
4351
+ const pieR = Math.min(pieW, h) / 2 * 0.85;
4352
+ let currentAngle = -Math.PI / 2;
4353
+ for (let i = 0; i < primaryValues.length; i++) {
4354
+ const val = primaryValues[i];
4355
+ const sliceAngle = val / total * 2 * Math.PI;
4356
+ const color = getPieSliceColor(i, chart);
4357
+ const x1 = pieCx + pieR * Math.cos(currentAngle);
4358
+ const y1 = pieCy + pieR * Math.sin(currentAngle);
4359
+ const x2 = pieCx + pieR * Math.cos(currentAngle + sliceAngle);
4360
+ const y2 = pieCy + pieR * Math.sin(currentAngle + sliceAngle);
4361
+ const largeArc = sliceAngle > Math.PI ? 1 : 0;
4362
+ parts.push(
4363
+ `<path d="M${round3(pieCx)},${round3(pieCy)} L${round3(x1)},${round3(y1)} A${round3(pieR)},${round3(pieR)} 0 ${largeArc},1 ${round3(x2)},${round3(y2)} Z" ${fillAttr(color)}/>`
4364
+ );
4365
+ currentAngle += sliceAngle;
4366
+ }
4367
+ const otherAngleStart = currentAngle;
4368
+ const otherSliceAngle = secondaryTotal / total * 2 * Math.PI;
4369
+ const otherColor = { hex: "#D9D9D9", alpha: 1 };
4370
+ if (primaryValues.length === 0 && secondaryValues.length > 0) {
4371
+ parts.push(
4372
+ `<circle cx="${round3(pieCx)}" cy="${round3(pieCy)}" r="${round3(pieR)}" ${fillAttr(otherColor)}/>`
4373
+ );
4374
+ } else if (secondaryTotal > 0) {
4375
+ const x1 = pieCx + pieR * Math.cos(otherAngleStart);
4376
+ const y1 = pieCy + pieR * Math.sin(otherAngleStart);
4377
+ const x2 = pieCx + pieR * Math.cos(otherAngleStart + otherSliceAngle);
4378
+ const y2 = pieCy + pieR * Math.sin(otherAngleStart + otherSliceAngle);
4379
+ const largeArc = otherSliceAngle > Math.PI ? 1 : 0;
4380
+ parts.push(
4381
+ `<path d="M${round3(pieCx)},${round3(pieCy)} L${round3(x1)},${round3(y1)} A${round3(pieR)},${round3(pieR)} 0 ${largeArc},1 ${round3(x2)},${round3(y2)} Z" ${fillAttr(otherColor)}/>`
4382
+ );
4383
+ }
4384
+ const secW = w * 0.25;
4385
+ const secH = h * (secondPieSize / 100) * 0.85;
4386
+ const secX = x + w * 0.65;
4387
+ const secCy = y + h / 2;
4388
+ const lineStartX = pieCx + pieR * Math.cos(otherAngleStart);
4389
+ const lineStartY = pieCy + pieR * Math.sin(otherAngleStart);
4390
+ const lineEndStartX = pieCx + pieR * Math.cos(otherAngleStart + otherSliceAngle);
4391
+ const lineEndStartY = pieCy + pieR * Math.sin(otherAngleStart + otherSliceAngle);
4392
+ parts.push(
4393
+ `<line x1="${round3(lineStartX)}" y1="${round3(lineStartY)}" x2="${round3(secX)}" y2="${round3(secCy - secH / 2)}" stroke="#A6A6A6" stroke-width="1"/>`
4394
+ );
4395
+ parts.push(
4396
+ `<line x1="${round3(lineEndStartX)}" y1="${round3(lineEndStartY)}" x2="${round3(secX)}" y2="${round3(secCy + secH / 2)}" stroke="#A6A6A6" stroke-width="1"/>`
4397
+ );
4398
+ if (isBarOfPie) {
4399
+ let barY = secCy - secH / 2;
4400
+ for (let i = 0; i < secondaryValues.length; i++) {
4401
+ const val = secondaryValues[i];
4402
+ const barH = secondaryTotal > 0 ? val / secondaryTotal * secH : 0;
4403
+ const color = getPieSliceColor(splitIdx + i, chart);
4404
+ parts.push(
4405
+ `<rect x="${round3(secX)}" y="${round3(barY)}" width="${round3(secW)}" height="${round3(barH)}" ${fillAttr(color)}/>`
4406
+ );
4407
+ barY += barH;
4408
+ }
4409
+ } else {
4410
+ const secPieCx = secX + secW / 2;
4411
+ const secR = Math.min(secW, secH) / 2;
4412
+ let secAngle = -Math.PI / 2;
4413
+ if (secondaryValues.length === 1) {
4414
+ const color = getPieSliceColor(splitIdx, chart);
4415
+ parts.push(
4416
+ `<circle cx="${round3(secPieCx)}" cy="${round3(secCy)}" r="${round3(secR)}" ${fillAttr(color)}/>`
4417
+ );
4418
+ } else {
4419
+ for (let i = 0; i < secondaryValues.length; i++) {
4420
+ const val = secondaryValues[i];
4421
+ const sliceAngle = secondaryTotal > 0 ? val / secondaryTotal * 2 * Math.PI : 0;
4422
+ const color = getPieSliceColor(splitIdx + i, chart);
4423
+ const sx1 = secPieCx + secR * Math.cos(secAngle);
4424
+ const sy1 = secCy + secR * Math.sin(secAngle);
4425
+ const sx2 = secPieCx + secR * Math.cos(secAngle + sliceAngle);
4426
+ const sy2 = secCy + secR * Math.sin(secAngle + sliceAngle);
4427
+ const largeArc = sliceAngle > Math.PI ? 1 : 0;
4428
+ parts.push(
4429
+ `<path d="M${round3(secPieCx)},${round3(secCy)} L${round3(sx1)},${round3(sy1)} A${round3(secR)},${round3(secR)} 0 ${largeArc},1 ${round3(sx2)},${round3(sy2)} Z" ${fillAttr(color)}/>`
4430
+ );
4431
+ secAngle += sliceAngle;
4432
+ }
4433
+ }
4434
+ }
4435
+ return parts.join("");
4436
+ }
4147
4437
  function renderLegend(chart, chartW, chartH, position) {
4148
4438
  const parts = [];
4149
- const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" ? chart.categories.map((cat, i) => ({
4439
+ const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" || chart.chartType === "ofPie" ? chart.categories.map((cat, i) => ({
4150
4440
  label: cat,
4151
4441
  color: getPieSliceColor(i, chart)
4152
4442
  })) : chart.series.map((s, i) => ({
@@ -4252,8 +4542,8 @@ function renderTable(element) {
4252
4542
  }
4253
4543
  if (cell.textBody) {
4254
4544
  const cellTransform = {
4255
- offsetX: 0,
4256
- offsetY: 0,
4545
+ offsetX: asEmu(0),
4546
+ offsetY: asEmu(0),
4257
4547
  extentWidth: pixelsToEmu(cellW),
4258
4548
  extentHeight: pixelsToEmu(cellH),
4259
4549
  rotation: 0,
@@ -4281,7 +4571,7 @@ function computeSpannedSize(sizes, startIdx, span) {
4281
4571
  return total;
4282
4572
  }
4283
4573
  function pixelsToEmu(px) {
4284
- return px / 96 * 914400;
4574
+ return asEmu(px / 96 * 914400);
4285
4575
  }
4286
4576
 
4287
4577
  // src/renderer/svg-renderer.ts
@@ -4585,7 +4875,7 @@ function parseDefaultRunProperties(defRPr, colorResolver) {
4585
4875
  if (!defRPr) return void 0;
4586
4876
  const result = {};
4587
4877
  if (defRPr["@_sz"] !== void 0) {
4588
- result.fontSize = hundredthPointToPoint(Number(defRPr["@_sz"]));
4878
+ result.fontSize = hundredthPointToPoint(asHundredthPt(Number(defRPr["@_sz"])));
4589
4879
  }
4590
4880
  const latin = defRPr.latin;
4591
4881
  if (latin?.["@_typeface"] !== void 0) {
@@ -4629,10 +4919,10 @@ function parseParagraphLevelProperties(node, colorResolver) {
4629
4919
  result.alignment = node["@_algn"];
4630
4920
  }
4631
4921
  if (node["@_marL"] !== void 0) {
4632
- result.marginLeft = Number(node["@_marL"]);
4922
+ result.marginLeft = asEmu(Number(node["@_marL"]));
4633
4923
  }
4634
4924
  if (node["@_indent"] !== void 0) {
4635
- result.indent = Number(node["@_indent"]);
4925
+ result.indent = asEmu(Number(node["@_indent"]));
4636
4926
  }
4637
4927
  const defRPr = parseDefaultRunProperties(node.defRPr, colorResolver);
4638
4928
  if (defRPr) {
@@ -4673,8 +4963,8 @@ function resolveThemeFont(typeface, fontScheme) {
4673
4963
  }
4674
4964
 
4675
4965
  // src/parser/presentation-parser.ts
4676
- var DEFAULT_SLIDE_WIDTH = 9144e3;
4677
- var DEFAULT_SLIDE_HEIGHT = 5143500;
4966
+ var DEFAULT_SLIDE_WIDTH = asEmu(9144e3);
4967
+ var DEFAULT_SLIDE_HEIGHT = asEmu(5143500);
4678
4968
  function parsePresentation(xml) {
4679
4969
  const parsed = parseXml(xml);
4680
4970
  const pres = parsed.presentation;
@@ -4695,8 +4985,8 @@ function parsePresentation(xml) {
4695
4985
  slideSize = { width: DEFAULT_SLIDE_WIDTH, height: DEFAULT_SLIDE_HEIGHT };
4696
4986
  } else {
4697
4987
  slideSize = {
4698
- width: Number(sldSz["@_cx"]),
4699
- height: Number(sldSz["@_cy"])
4988
+ width: asEmu(Number(sldSz["@_cx"])),
4989
+ height: asEmu(Number(sldSz["@_cy"]))
4700
4990
  };
4701
4991
  }
4702
4992
  const sldIdLst = pres.sldIdLst;
@@ -4854,8 +5144,8 @@ function parseBlipFill(blipFillNode, context) {
4854
5144
  const imageData = uint8ArrayToBase64(mediaData);
4855
5145
  const tileNode = blipFillNode.tile;
4856
5146
  const tile = tileNode ? {
4857
- tx: Number(tileNode["@_tx"] ?? 0),
4858
- ty: Number(tileNode["@_ty"] ?? 0),
5147
+ tx: asEmu(Number(tileNode["@_tx"] ?? 0)),
5148
+ ty: asEmu(Number(tileNode["@_ty"] ?? 0)),
4859
5149
  sx: Number(tileNode["@_sx"] ?? 1e5) / 1e5,
4860
5150
  sy: Number(tileNode["@_sy"] ?? 1e5) / 1e5,
4861
5151
  flip: tileNode["@_flip"] ?? "none",
@@ -4912,7 +5202,7 @@ function parseOutline(lnNode, colorResolver) {
4912
5202
  if (lnNode.pattFill) {
4913
5203
  warn("ln.pattFill", "pattern line fill not implemented");
4914
5204
  }
4915
- const width = Number(lnNode["@_w"] ?? 12700);
5205
+ const width = asEmu(Number(lnNode["@_w"] ?? 12700));
4916
5206
  let fill = null;
4917
5207
  if (lnNode.solidFill) {
4918
5208
  const color = colorResolver.resolve(lnNode.solidFill);
@@ -4993,8 +5283,8 @@ function parseOuterShadow(node, colorResolver) {
4993
5283
  const color = colorResolver.resolve(node);
4994
5284
  if (!color) return null;
4995
5285
  return {
4996
- blurRadius: Number(node["@_blurRad"] ?? 0),
4997
- distance: Number(node["@_dist"] ?? 0),
5286
+ blurRadius: asEmu(Number(node["@_blurRad"] ?? 0)),
5287
+ distance: asEmu(Number(node["@_dist"] ?? 0)),
4998
5288
  direction: Number(node["@_dir"] ?? 0) / 6e4,
4999
5289
  color,
5000
5290
  alignment: node["@_algn"] ?? "b",
@@ -5006,8 +5296,8 @@ function parseInnerShadow(node, colorResolver) {
5006
5296
  const color = colorResolver.resolve(node);
5007
5297
  if (!color) return null;
5008
5298
  return {
5009
- blurRadius: Number(node["@_blurRad"] ?? 0),
5010
- distance: Number(node["@_dist"] ?? 0),
5299
+ blurRadius: asEmu(Number(node["@_blurRad"] ?? 0)),
5300
+ distance: asEmu(Number(node["@_dist"] ?? 0)),
5011
5301
  direction: Number(node["@_dir"] ?? 0) / 6e4,
5012
5302
  color
5013
5303
  };
@@ -5017,14 +5307,14 @@ function parseGlow(node, colorResolver) {
5017
5307
  const color = colorResolver.resolve(node);
5018
5308
  if (!color) return null;
5019
5309
  return {
5020
- radius: Number(node["@_rad"] ?? 0),
5310
+ radius: asEmu(Number(node["@_rad"] ?? 0)),
5021
5311
  color
5022
5312
  };
5023
5313
  }
5024
5314
  function parseSoftEdge(node) {
5025
5315
  if (!node) return null;
5026
5316
  return {
5027
- radius: Number(node["@_rad"] ?? 0)
5317
+ radius: asEmu(Number(node["@_rad"] ?? 0))
5028
5318
  };
5029
5319
  }
5030
5320
 
@@ -5493,7 +5783,7 @@ function parseBiLevel(node) {
5493
5783
  function parseBlur(node) {
5494
5784
  if (!node) return null;
5495
5785
  return {
5496
- radius: Number(node["@_rad"] ?? 0),
5786
+ radius: asEmu(Number(node["@_rad"] ?? 0)),
5497
5787
  grow: node["@_grow"] !== "0"
5498
5788
  };
5499
5789
  }
@@ -5543,7 +5833,11 @@ var CHART_TYPE_MAP = [
5543
5833
  ["bubbleChart", "bubble"],
5544
5834
  ["areaChart", "area"],
5545
5835
  ["area3DChart", "area"],
5546
- ["radarChart", "radar"]
5836
+ ["radarChart", "radar"],
5837
+ ["stockChart", "stock"],
5838
+ ["surfaceChart", "surface"],
5839
+ ["surface3DChart", "surface"],
5840
+ ["ofPieChart", "ofPie"]
5547
5841
  ];
5548
5842
  function parseChart(chartXml, colorResolver) {
5549
5843
  const parsed = parseXml(chartXml);
@@ -5554,7 +5848,17 @@ function parseChart(chartXml, colorResolver) {
5554
5848
  const plotArea = chart.plotArea;
5555
5849
  if (!plotArea) return null;
5556
5850
  const title = parseChartTitle(chart.title);
5557
- const { chartType, series, categories, barDirection, holeSize, radarStyle } = parseChartTypeAndData(plotArea, colorResolver);
5851
+ const {
5852
+ chartType,
5853
+ series,
5854
+ categories,
5855
+ barDirection,
5856
+ holeSize,
5857
+ radarStyle,
5858
+ ofPieType,
5859
+ secondPieSize,
5860
+ splitPos
5861
+ } = parseChartTypeAndData(plotArea, colorResolver);
5558
5862
  if (!chartType) return null;
5559
5863
  const legend = parseLegend(chart.legend);
5560
5864
  return {
@@ -5565,6 +5869,9 @@ function parseChart(chartXml, colorResolver) {
5565
5869
  ...barDirection !== void 0 && { barDirection },
5566
5870
  ...holeSize !== void 0 && { holeSize },
5567
5871
  ...radarStyle !== void 0 && { radarStyle },
5872
+ ...ofPieType !== void 0 && { ofPieType },
5873
+ ...secondPieSize !== void 0 && { secondPieSize },
5874
+ ...splitPos !== void 0 && { splitPos },
5568
5875
  legend
5569
5876
  };
5570
5877
  }
@@ -5583,15 +5890,23 @@ function parseChartTypeAndData(plotArea, colorResolver) {
5583
5890
  const holeSize = chartType === "doughnut" ? Number(holeSizeNode?.["@_val"] ?? 50) : void 0;
5584
5891
  const radarStyleNode = chartNode.radarStyle;
5585
5892
  const radarStyle = chartType === "radar" ? radarStyleNode?.["@_val"] ?? "standard" : void 0;
5586
- return { chartType, series, categories, barDirection, holeSize, radarStyle };
5587
- }
5588
- const knownTags = new Set(CHART_TYPE_MAP.map(([tag]) => tag));
5589
- const chartTags = ["surfaceChart", "surface3DChart", "stockChart", "ofPieChart"];
5590
- for (const tag of chartTags) {
5591
- if (!knownTags.has(tag) && plotArea[tag]) {
5592
- warn(`chart.${tag}`, `chart type "${tag}" not implemented`);
5593
- break;
5594
- }
5893
+ const ofPieTypeNode = chartNode.ofPieType;
5894
+ const ofPieType = chartType === "ofPie" ? ofPieTypeNode?.["@_val"] ?? "pie" : void 0;
5895
+ const secondPieSizeNode = chartNode.secondPieSize;
5896
+ const secondPieSize = chartType === "ofPie" ? Number(secondPieSizeNode?.["@_val"] ?? 75) : void 0;
5897
+ const splitPosNode = chartNode.splitPos;
5898
+ const splitPos = chartType === "ofPie" ? Number(splitPosNode?.["@_val"] ?? 2) : void 0;
5899
+ return {
5900
+ chartType,
5901
+ series,
5902
+ categories,
5903
+ barDirection,
5904
+ holeSize,
5905
+ radarStyle,
5906
+ ofPieType,
5907
+ secondPieSize,
5908
+ splitPos
5909
+ };
5595
5910
  }
5596
5911
  return { chartType: null, series: [], categories: [] };
5597
5912
  }
@@ -5959,28 +6274,31 @@ function parseTable(tblNode, colorResolver, fontScheme) {
5959
6274
  if (!tblNode) return null;
5960
6275
  const columns = parseColumns(tblNode.tblGrid);
5961
6276
  if (columns.length === 0) return null;
5962
- const rows = parseRows(tblNode.tr, colorResolver, fontScheme);
6277
+ const tblPr = tblNode.tblPr;
6278
+ const hasTableStyle = tblPr?.tableStyleId !== void 0;
6279
+ const defaultBorders = hasTableStyle ? createDefaultBorders() : null;
6280
+ const rows = parseRows(tblNode.tr, colorResolver, fontScheme, defaultBorders);
5963
6281
  return { rows, columns };
5964
6282
  }
5965
6283
  function parseColumns(tblGrid) {
5966
6284
  if (!tblGrid) return [];
5967
6285
  const gridCols = tblGrid.gridCol ?? [];
5968
6286
  return gridCols.map((col) => ({
5969
- width: Number(col["@_w"] ?? 0)
6287
+ width: asEmu(Number(col["@_w"] ?? 0))
5970
6288
  }));
5971
6289
  }
5972
- function parseRows(trList, colorResolver, fontScheme) {
6290
+ function parseRows(trList, colorResolver, fontScheme, defaultBorders) {
5973
6291
  if (!trList) return [];
5974
6292
  const trArr = Array.isArray(trList) ? trList : [trList];
5975
6293
  const rows = [];
5976
6294
  for (const tr of trArr) {
5977
- const height = Number(tr["@_h"] ?? 0);
5978
- const cells = parseCells(tr.tc, colorResolver, fontScheme);
6295
+ const height = asEmu(Number(tr["@_h"] ?? 0));
6296
+ const cells = parseCells(tr.tc, colorResolver, fontScheme, defaultBorders);
5979
6297
  rows.push({ height, cells });
5980
6298
  }
5981
6299
  return rows;
5982
6300
  }
5983
- function parseCells(tcList, colorResolver, fontScheme) {
6301
+ function parseCells(tcList, colorResolver, fontScheme, defaultBorders) {
5984
6302
  if (!tcList) return [];
5985
6303
  const tcArr = Array.isArray(tcList) ? tcList : [tcList];
5986
6304
  const cells = [];
@@ -5988,7 +6306,8 @@ function parseCells(tcList, colorResolver, fontScheme) {
5988
6306
  const textBody = parseTextBody(tc.txBody, colorResolver, void 0, fontScheme);
5989
6307
  const tcPr = tc.tcPr;
5990
6308
  const fill = tcPr ? parseFillFromNode(tcPr, colorResolver) : null;
5991
- const borders = tcPr ? parseCellBorders(tcPr, colorResolver) : null;
6309
+ const inlineBorders = tcPr ? parseCellBorders(tcPr, colorResolver) : null;
6310
+ const borders = inlineBorders ?? defaultBorders ?? null;
5992
6311
  const gridSpan = Number(tc["@_gridSpan"] ?? 1);
5993
6312
  const rowSpan = Number(tc["@_rowSpan"] ?? 1);
5994
6313
  const hMerge = tcPr?.["@_hMerge"] === "1" || tcPr?.["@_hMerge"] === "true";
@@ -6002,9 +6321,29 @@ function parseCellBorders(tcPr, colorResolver) {
6002
6321
  const bottom = parseOutline(tcPr.lnB, colorResolver);
6003
6322
  const left = parseOutline(tcPr.lnL, colorResolver);
6004
6323
  const right = parseOutline(tcPr.lnR, colorResolver);
6324
+ for (const border of [top, bottom, left, right]) {
6325
+ if (border && !border.fill) {
6326
+ border.fill = { type: "solid", color: { hex: "#000000", alpha: 1 } };
6327
+ }
6328
+ }
6005
6329
  if (!top && !bottom && !left && !right) return null;
6006
6330
  return { top, bottom, left, right };
6007
6331
  }
6332
+ function createDefaultBorders() {
6333
+ const defaultOutline = {
6334
+ width: asEmu(12700),
6335
+ fill: { type: "solid", color: { hex: "#000000", alpha: 1 } },
6336
+ dashStyle: "solid",
6337
+ headEnd: null,
6338
+ tailEnd: null
6339
+ };
6340
+ return {
6341
+ top: { ...defaultOutline },
6342
+ bottom: { ...defaultOutline },
6343
+ left: { ...defaultOutline },
6344
+ right: { ...defaultOutline }
6345
+ };
6346
+ }
6008
6347
 
6009
6348
  // src/parser/slide-parser.ts
6010
6349
  var SHAPE_TAGS = /* @__PURE__ */ new Set(["sp", "pic", "cxnSp", "grpSp", "graphicFrame"]);
@@ -6432,8 +6771,8 @@ function parseStretchFillRect(stretchNode) {
6432
6771
  function parseTileInfo(node) {
6433
6772
  if (!node) return null;
6434
6773
  return {
6435
- tx: Number(node["@_tx"] ?? 0),
6436
- ty: Number(node["@_ty"] ?? 0),
6774
+ tx: asEmu(Number(node["@_tx"] ?? 0)),
6775
+ ty: asEmu(Number(node["@_ty"] ?? 0)),
6437
6776
  sx: Number(node["@_sx"] ?? 1e5) / 1e5,
6438
6777
  sy: Number(node["@_sy"] ?? 1e5) / 1e5,
6439
6778
  flip: node["@_flip"] ?? "none",
@@ -6465,10 +6804,10 @@ function parseGroup(grp, rels, slidePath, archive, colorResolver, parentFillCont
6465
6804
  const childOff = xfrm?.chOff;
6466
6805
  const childExt = xfrm?.chExt;
6467
6806
  const childTransform = {
6468
- offsetX: Number(childOff?.["@_x"] ?? 0),
6469
- offsetY: Number(childOff?.["@_y"] ?? 0),
6470
- extentWidth: Number(childExt?.["@_cx"] ?? transform.extentWidth),
6471
- extentHeight: Number(childExt?.["@_cy"] ?? transform.extentHeight),
6807
+ offsetX: asEmu(Number(childOff?.["@_x"] ?? 0)),
6808
+ offsetY: asEmu(Number(childOff?.["@_y"] ?? 0)),
6809
+ extentWidth: asEmu(Number(childExt?.["@_cx"] ?? transform.extentWidth)),
6810
+ extentHeight: asEmu(Number(childExt?.["@_cy"] ?? transform.extentHeight)),
6472
6811
  rotation: 0,
6473
6812
  flipH: false,
6474
6813
  flipV: false
@@ -6589,10 +6928,10 @@ function parseSmartArt(graphicData, transform, rels, slidePath, archive, colorRe
6589
6928
  const childOff = grpXfrm?.chOff;
6590
6929
  const childExt = grpXfrm?.chExt;
6591
6930
  const childTransform = {
6592
- offsetX: Number(childOff?.["@_x"] ?? 0),
6593
- offsetY: Number(childOff?.["@_y"] ?? 0),
6594
- extentWidth: Number(childExt?.["@_cx"] ?? transform.extentWidth),
6595
- extentHeight: Number(childExt?.["@_cy"] ?? transform.extentHeight),
6931
+ offsetX: asEmu(Number(childOff?.["@_x"] ?? 0)),
6932
+ offsetY: asEmu(Number(childOff?.["@_y"] ?? 0)),
6933
+ extentWidth: asEmu(Number(childExt?.["@_cx"] ?? transform.extentWidth)),
6934
+ extentHeight: asEmu(Number(childExt?.["@_cy"] ?? transform.extentHeight)),
6596
6935
  rotation: 0,
6597
6936
  flipH: false,
6598
6937
  flipV: false
@@ -6629,26 +6968,26 @@ function parseTransform(xfrm) {
6629
6968
  const off = xfrm.off;
6630
6969
  const ext = xfrm.ext;
6631
6970
  if (!off || !ext) return null;
6632
- let offsetX = Number(off["@_x"] ?? 0);
6633
- let offsetY = Number(off["@_y"] ?? 0);
6634
- let extentWidth = Number(ext["@_cx"] ?? 0);
6635
- let extentHeight = Number(ext["@_cy"] ?? 0);
6971
+ let offsetX = asEmu(Number(off["@_x"] ?? 0));
6972
+ let offsetY = asEmu(Number(off["@_y"] ?? 0));
6973
+ let extentWidth = asEmu(Number(ext["@_cx"] ?? 0));
6974
+ let extentHeight = asEmu(Number(ext["@_cy"] ?? 0));
6636
6975
  let rotation = Number(xfrm["@_rot"] ?? 0);
6637
6976
  if (Number.isNaN(offsetX)) {
6638
6977
  debug("transform.nan", "NaN detected in transform offsetX, defaulting to 0");
6639
- offsetX = 0;
6978
+ offsetX = asEmu(0);
6640
6979
  }
6641
6980
  if (Number.isNaN(offsetY)) {
6642
6981
  debug("transform.nan", "NaN detected in transform offsetY, defaulting to 0");
6643
- offsetY = 0;
6982
+ offsetY = asEmu(0);
6644
6983
  }
6645
6984
  if (Number.isNaN(extentWidth)) {
6646
6985
  debug("transform.nan", "NaN detected in transform extentWidth, defaulting to 0");
6647
- extentWidth = 0;
6986
+ extentWidth = asEmu(0);
6648
6987
  }
6649
6988
  if (Number.isNaN(extentHeight)) {
6650
6989
  debug("transform.nan", "NaN detected in transform extentHeight, defaulting to 0");
6651
- extentHeight = 0;
6990
+ extentHeight = asEmu(0);
6652
6991
  }
6653
6992
  if (Number.isNaN(rotation)) {
6654
6993
  debug("transform.nan", "NaN detected in transform rotation, defaulting to 0");
@@ -6713,10 +7052,10 @@ function parseTextBody(txBody, colorResolver, rels, fontScheme, lstStyleOverride
6713
7052
  }
6714
7053
  const bodyProperties = {
6715
7054
  anchor: bodyPr?.["@_anchor"] ?? "t",
6716
- marginLeft: Number(bodyPr?.["@_lIns"] ?? 91440),
6717
- marginRight: Number(bodyPr?.["@_rIns"] ?? 91440),
6718
- marginTop: Number(bodyPr?.["@_tIns"] ?? 45720),
6719
- marginBottom: Number(bodyPr?.["@_bIns"] ?? 45720),
7055
+ marginLeft: asEmu(Number(bodyPr?.["@_lIns"] ?? 91440)),
7056
+ marginRight: asEmu(Number(bodyPr?.["@_rIns"] ?? 91440)),
7057
+ marginTop: asEmu(Number(bodyPr?.["@_tIns"] ?? 45720)),
7058
+ marginBottom: asEmu(Number(bodyPr?.["@_bIns"] ?? 45720)),
6720
7059
  wrap: bodyPr?.["@_wrap"] ?? "square",
6721
7060
  autoFit,
6722
7061
  fontScale,
@@ -6785,13 +7124,13 @@ function parseBullet(pPr, colorResolver) {
6785
7124
  function parseSpacing(spc) {
6786
7125
  if (spc?.spcPts) {
6787
7126
  const spcPts = spc.spcPts;
6788
- return { type: "pts", value: Number(spcPts["@_val"]) };
7127
+ return { type: "pts", value: asHundredthPt(Number(spcPts["@_val"])) };
6789
7128
  }
6790
7129
  if (spc?.spcPct) {
6791
7130
  const spcPct = spc.spcPct;
6792
7131
  return { type: "pct", value: Number(spcPct["@_val"]) };
6793
7132
  }
6794
- return { type: "pts", value: 0 };
7133
+ return { type: "pts", value: asHundredthPt(0) };
6795
7134
  }
6796
7135
  function parseTabStops(pPr) {
6797
7136
  const tabLst = pPr?.tabLst;
@@ -6800,7 +7139,7 @@ function parseTabStops(pPr) {
6800
7139
  if (!tabs) return [];
6801
7140
  const tabArr = Array.isArray(tabs) ? tabs : [tabs];
6802
7141
  return tabArr.map((tab) => ({
6803
- position: Number(tab["@_pos"] ?? 0),
7142
+ position: asEmu(Number(tab["@_pos"] ?? 0)),
6804
7143
  alignment: tab["@_algn"] ?? "l"
6805
7144
  }));
6806
7145
  }
@@ -6850,8 +7189,8 @@ function parseParagraph(p, colorResolver, rels, fontScheme, lstStyle, orderedPCh
6850
7189
  bulletFont,
6851
7190
  bulletColor,
6852
7191
  bulletSizePct,
6853
- marginLeft: pPr?.["@_marL"] !== void 0 ? Number(pPr["@_marL"]) : lstLevelProps?.marginLeft ?? 0,
6854
- indent: pPr?.["@_indent"] !== void 0 ? Number(pPr["@_indent"]) : lstLevelProps?.indent ?? 0,
7192
+ marginLeft: pPr?.["@_marL"] !== void 0 ? asEmu(Number(pPr["@_marL"])) : lstLevelProps?.marginLeft ?? asEmu(0),
7193
+ indent: pPr?.["@_indent"] !== void 0 ? asEmu(Number(pPr["@_indent"])) : lstLevelProps?.indent ?? asEmu(0),
6855
7194
  tabStops
6856
7195
  };
6857
7196
  const pPrDefRPr = parseDefaultRunProperties(pPr?.defRPr);
@@ -6993,7 +7332,7 @@ function parseRunProperties(rPr, colorResolver, rels, fontScheme, defaults) {
6993
7332
  const latin = rPr.latin;
6994
7333
  const ea = rPr.ea;
6995
7334
  const cs = rPr.cs;
6996
- const fontSize = rPr["@_sz"] ? hundredthPointToPoint(Number(rPr["@_sz"])) : defaults?.fontSize ?? null;
7335
+ const fontSize = rPr["@_sz"] ? hundredthPointToPoint(asHundredthPt(Number(rPr["@_sz"]))) : defaults?.fontSize ?? null;
6997
7336
  const fontFamily = resolveThemeFont(
6998
7337
  latin?.["@_typeface"] ?? defaults?.fontFamily ?? null,
6999
7338
  fontScheme
@@ -7020,7 +7359,7 @@ function parseRunProperties(rPr, colorResolver, rels, fontScheme, defaults) {
7020
7359
  const ln = rPr.ln;
7021
7360
  let outline = null;
7022
7361
  if (ln) {
7023
- const lnWidth = Number(ln["@_w"] ?? 12700);
7362
+ const lnWidth = asEmu(Number(ln["@_w"] ?? 12700));
7024
7363
  const lnFill = ln.solidFill;
7025
7364
  const lnColor = lnFill ? colorResolver.resolve(lnFill) : null;
7026
7365
  if (lnColor) {
@@ -7310,7 +7649,23 @@ function findMatchingPlaceholderStyle(placeholderType, placeholderIdx, styles) {
7310
7649
  if (byIdx?.lstStyle) return byIdx.lstStyle;
7311
7650
  }
7312
7651
  const byType = styles.find((s) => s.placeholderType === placeholderType);
7313
- return byType?.lstStyle;
7652
+ if (byType?.lstStyle) return byType.lstStyle;
7653
+ const fallbackType = getPlaceholderFallbackType(placeholderType);
7654
+ if (fallbackType) {
7655
+ const byFallback = styles.find((s) => s.placeholderType === fallbackType);
7656
+ return byFallback?.lstStyle;
7657
+ }
7658
+ return void 0;
7659
+ }
7660
+ function getPlaceholderFallbackType(type) {
7661
+ switch (type) {
7662
+ case "ctrTitle":
7663
+ return "title";
7664
+ case "subTitle":
7665
+ return "body";
7666
+ default:
7667
+ return void 0;
7668
+ }
7314
7669
  }
7315
7670
  function getTxStyleForPlaceholder(placeholderType, txStyles) {
7316
7671
  if (!txStyles) return void 0;