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.cjs CHANGED
@@ -1144,6 +1144,14 @@ function buildMarkerDef(id, endpoint, color, alpha) {
1144
1144
  return `<marker id="${id}" markerWidth="${mw}" markerHeight="${mh}" refX="${mw}" refY="${mh / 2}" orient="auto" markerUnits="userSpaceOnUse"><path d="${path}" ${fillAttr2}${alphaAttr}/></marker>`;
1145
1145
  }
1146
1146
 
1147
+ // src/utils/unit-types.ts
1148
+ function asEmu(value) {
1149
+ return value;
1150
+ }
1151
+ function asHundredthPt(value) {
1152
+ return value;
1153
+ }
1154
+
1147
1155
  // src/data/font-metrics.ts
1148
1156
  var metricsData = {
1149
1157
  Carlito: {
@@ -3020,7 +3028,7 @@ function computeSpAutofitHeight(textBody, transform) {
3020
3028
  const requiredHeightPx = textHeight + marginTopPx + marginBottomPx;
3021
3029
  if (requiredHeightPx <= height) return null;
3022
3030
  const DEFAULT_DPI2 = 96;
3023
- return requiredHeightPx / DEFAULT_DPI2 * EMU_PER_INCH;
3031
+ return asEmu(requiredHeightPx / DEFAULT_DPI2 * EMU_PER_INCH);
3024
3032
  }
3025
3033
  function computeShrinkToFitScale(paragraphs, defaultFontSize, fontScale, lnSpcReduction, textWidth, availableHeight) {
3026
3034
  if (availableHeight <= 0) return fontScale;
@@ -3127,7 +3135,7 @@ function renderTextDecorations(x, y, segmentWidth, fontSizePx, props) {
3127
3135
  }
3128
3136
  return lines;
3129
3137
  }
3130
- function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, fontResolver) {
3138
+ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, fontResolver, vert) {
3131
3139
  const fontSize = (props.fontSize ?? defaultFontSize) * fontScale;
3132
3140
  const fontSizePx = fontSize * PX_PER_PT2;
3133
3141
  const parts = [];
@@ -3160,7 +3168,50 @@ function renderSegmentAsPath(text, props, x, y, fontScale, defaultFontSize, font
3160
3168
  }
3161
3169
  totalWidth += segWidth;
3162
3170
  };
3163
- if (needsScriptSplit(props)) {
3171
+ const processCjkUpright = (segText, fontFamily, fontFamilyEa) => {
3172
+ if (segText.length === 0) return;
3173
+ const font = fontResolver.resolveFont(fontFamily, fontFamilyEa);
3174
+ const fillAttrs = buildPathFillAttrs(props);
3175
+ for (const char of segText) {
3176
+ const charWidth = getTextMeasurer().measureTextWidth(
3177
+ char,
3178
+ fontSize,
3179
+ props.bold,
3180
+ fontFamily,
3181
+ fontFamilyEa
3182
+ );
3183
+ if (font) {
3184
+ const charX = x + totalWidth;
3185
+ const path = font.getPath(char, charX, effectiveY, fontSizePx);
3186
+ const pathData = path.toPathData(2);
3187
+ if (pathData && pathData.length > 0) {
3188
+ const cx = charX + charWidth / 2;
3189
+ const cy = effectiveY - fontSizePx * (font.ascender + font.descender) / 2 / font.unitsPerEm;
3190
+ parts.push(
3191
+ `<g transform="rotate(-90, ${cx.toFixed(2)}, ${cy.toFixed(2)})"><path d="${pathData}" ${fillAttrs}/></g>`
3192
+ );
3193
+ }
3194
+ }
3195
+ if (props.underline || props.strikethrough) {
3196
+ parts.push(
3197
+ ...renderTextDecorations(x + totalWidth, effectiveY, charWidth, fontSizePx, props)
3198
+ );
3199
+ }
3200
+ totalWidth += charWidth;
3201
+ }
3202
+ };
3203
+ if (vert === "eaVert") {
3204
+ const scriptParts = splitByScript(processedText);
3205
+ for (const part of scriptParts) {
3206
+ const ff = part.isEa ? props.fontFamilyEa ?? props.fontFamily : props.fontFamily;
3207
+ const ffEa = part.isEa ? props.fontFamilyEa : props.fontFamilyEa;
3208
+ if (part.isEa) {
3209
+ processCjkUpright(part.text, ff, ffEa);
3210
+ } else {
3211
+ processSegment(part.text, ff, ffEa);
3212
+ }
3213
+ }
3214
+ } else if (needsScriptSplit(props)) {
3164
3215
  const scriptParts = splitByScript(processedText);
3165
3216
  for (const part of scriptParts) {
3166
3217
  const ff = part.isEa ? props.fontFamilyEa : props.fontFamily;
@@ -3326,7 +3377,8 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
3326
3377
  currentY,
3327
3378
  fontScale,
3328
3379
  defaultFontSize,
3329
- fontResolver
3380
+ fontResolver,
3381
+ bodyProperties.vert
3330
3382
  );
3331
3383
  if (result.svg) elements.push(result.svg);
3332
3384
  currentX += result.width;
@@ -3373,7 +3425,8 @@ function renderTextBodyAsPath(textBody, transform, fontResolver) {
3373
3425
  currentY,
3374
3426
  fontScale,
3375
3427
  defaultFontSize,
3376
- fontResolver
3428
+ fontResolver,
3429
+ bodyProperties.vert
3377
3430
  );
3378
3431
  if (result.svg) elements.push(result.svg);
3379
3432
  currentX += result.width;
@@ -3816,6 +3869,15 @@ function renderChart(element) {
3816
3869
  case "radar":
3817
3870
  parts.push(renderRadarChart(chart, plotX, plotY, plotW, plotH));
3818
3871
  break;
3872
+ case "stock":
3873
+ parts.push(renderStockChart(chart, plotX, plotY, plotW, plotH));
3874
+ break;
3875
+ case "surface":
3876
+ parts.push(renderSurfaceChart(chart, plotX, plotY, plotW, plotH));
3877
+ break;
3878
+ case "ofPie":
3879
+ parts.push(renderOfPieChart(chart, plotX, plotY, plotW, plotH));
3880
+ break;
3819
3881
  }
3820
3882
  }
3821
3883
  if (chart.legend && chart.series.length > 0) {
@@ -4187,9 +4249,237 @@ function renderRadarChart(chart, x, y, w, h) {
4187
4249
  }
4188
4250
  return parts.join("");
4189
4251
  }
4252
+ function renderStockChart(chart, x, y, w, h) {
4253
+ const parts = [];
4254
+ const { series, categories } = chart;
4255
+ if (series.length < 3) return "";
4256
+ const highSeries = series[0];
4257
+ const lowSeries = series[1];
4258
+ const closeSeries = series[2];
4259
+ const catCount = categories.length || highSeries.values.length;
4260
+ if (catCount === 0) return "";
4261
+ let maxVal = 0;
4262
+ let minVal = Infinity;
4263
+ for (const s of [highSeries, lowSeries, closeSeries]) {
4264
+ for (const v of s.values) {
4265
+ maxVal = Math.max(maxVal, v);
4266
+ minVal = Math.min(minVal, v);
4267
+ }
4268
+ }
4269
+ if (maxVal === minVal) return "";
4270
+ parts.push(
4271
+ `<line x1="${round3(x)}" y1="${round3(y + h)}" x2="${round3(x + w)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4272
+ );
4273
+ parts.push(
4274
+ `<line x1="${round3(x)}" y1="${round3(y)}" x2="${round3(x)}" y2="${round3(y + h)}" stroke="#D9D9D9" stroke-width="1"/>`
4275
+ );
4276
+ for (let c = 0; c < catCount; c++) {
4277
+ const label = categories[c] ?? "";
4278
+ const labelX = x + (c + 0.5) * (w / catCount);
4279
+ parts.push(
4280
+ `<text x="${round3(labelX)}" y="${round3(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
4281
+ );
4282
+ }
4283
+ const range = maxVal - minVal;
4284
+ for (let c = 0; c < catCount; c++) {
4285
+ const cx = x + (c + 0.5) * (w / catCount);
4286
+ const highVal = highSeries.values[c] ?? 0;
4287
+ const lowVal = lowSeries.values[c] ?? 0;
4288
+ const closeVal = closeSeries.values[c] ?? 0;
4289
+ const highY = y + h - (highVal - minVal) / range * h;
4290
+ const lowY = y + h - (lowVal - minVal) / range * h;
4291
+ const closeY = y + h - (closeVal - minVal) / range * h;
4292
+ parts.push(
4293
+ `<line x1="${round3(cx)}" y1="${round3(highY)}" x2="${round3(cx)}" y2="${round3(lowY)}" stroke="#404040" stroke-width="2"/>`
4294
+ );
4295
+ const tickW = w / catCount * 0.2;
4296
+ parts.push(
4297
+ `<line x1="${round3(cx)}" y1="${round3(closeY)}" x2="${round3(cx + tickW)}" y2="${round3(closeY)}" stroke="#404040" stroke-width="2"/>`
4298
+ );
4299
+ }
4300
+ return parts.join("");
4301
+ }
4302
+ function renderSurfaceChart(chart, x, y, w, h) {
4303
+ const parts = [];
4304
+ const { series, categories } = chart;
4305
+ if (series.length === 0) return "";
4306
+ const rows = series.length;
4307
+ const cols = categories.length || Math.max(...series.map((s) => s.values.length));
4308
+ if (cols === 0) return "";
4309
+ let minVal = Infinity;
4310
+ let maxVal = -Infinity;
4311
+ for (const s of series) {
4312
+ for (const v of s.values) {
4313
+ minVal = Math.min(minVal, v);
4314
+ maxVal = Math.max(maxVal, v);
4315
+ }
4316
+ }
4317
+ if (minVal === maxVal) maxVal = minVal + 1;
4318
+ const cellW = w / cols;
4319
+ const cellH = h / rows;
4320
+ for (let r = 0; r < rows; r++) {
4321
+ for (let c = 0; c < cols; c++) {
4322
+ const val = series[r].values[c] ?? 0;
4323
+ const t = (val - minVal) / (maxVal - minVal);
4324
+ const color = heatmapColor(t);
4325
+ const cx = x + c * cellW;
4326
+ const cy = y + r * cellH;
4327
+ parts.push(
4328
+ `<rect x="${round3(cx)}" y="${round3(cy)}" width="${round3(cellW)}" height="${round3(cellH)}" fill="${color}" stroke="#FFFFFF" stroke-width="0.5"/>`
4329
+ );
4330
+ }
4331
+ }
4332
+ for (let c = 0; c < cols; c++) {
4333
+ const label = categories[c] ?? "";
4334
+ if (label) {
4335
+ const labelX = x + (c + 0.5) * cellW;
4336
+ parts.push(
4337
+ `<text x="${round3(labelX)}" y="${round3(y + h + 15)}" text-anchor="middle" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
4338
+ );
4339
+ }
4340
+ }
4341
+ for (let r = 0; r < rows; r++) {
4342
+ const label = series[r].name ?? "";
4343
+ if (label) {
4344
+ const labelY = y + (r + 0.5) * cellH;
4345
+ parts.push(
4346
+ `<text x="${round3(x - 5)}" y="${round3(labelY + 4)}" text-anchor="end" font-size="10" fill="#595959">${escapeXml2(label)}</text>`
4347
+ );
4348
+ }
4349
+ }
4350
+ return parts.join("");
4351
+ }
4352
+ function heatmapColor(t) {
4353
+ const clamped = Math.max(0, Math.min(1, t));
4354
+ let r, g, b;
4355
+ if (clamped < 0.25) {
4356
+ const s = clamped / 0.25;
4357
+ r = 0;
4358
+ g = Math.round(s * 255);
4359
+ b = 255;
4360
+ } else if (clamped < 0.5) {
4361
+ const s = (clamped - 0.25) / 0.25;
4362
+ r = 0;
4363
+ g = 255;
4364
+ b = Math.round((1 - s) * 255);
4365
+ } else if (clamped < 0.75) {
4366
+ const s = (clamped - 0.5) / 0.25;
4367
+ r = Math.round(s * 255);
4368
+ g = 255;
4369
+ b = 0;
4370
+ } else {
4371
+ const s = (clamped - 0.75) / 0.25;
4372
+ r = 255;
4373
+ g = Math.round((1 - s) * 255);
4374
+ b = 0;
4375
+ }
4376
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
4377
+ }
4378
+ function renderOfPieChart(chart, x, y, w, h) {
4379
+ const parts = [];
4380
+ const series = chart.series[0];
4381
+ if (!series || series.values.length === 0) return "";
4382
+ const total = series.values.reduce((sum, v) => sum + v, 0);
4383
+ if (total === 0) return "";
4384
+ const splitPos = chart.splitPos ?? 2;
4385
+ const secondPieSize = chart.secondPieSize ?? 75;
4386
+ const isBarOfPie = chart.ofPieType === "bar";
4387
+ const splitIdx = Math.max(0, series.values.length - splitPos);
4388
+ const primaryValues = series.values.slice(0, splitIdx);
4389
+ const secondaryValues = series.values.slice(splitIdx);
4390
+ const secondaryTotal = secondaryValues.reduce((sum, v) => sum + v, 0);
4391
+ const pieW = w * 0.45;
4392
+ const pieCx = x + pieW / 2;
4393
+ const pieCy = y + h / 2;
4394
+ const pieR = Math.min(pieW, h) / 2 * 0.85;
4395
+ let currentAngle = -Math.PI / 2;
4396
+ for (let i = 0; i < primaryValues.length; i++) {
4397
+ const val = primaryValues[i];
4398
+ const sliceAngle = val / total * 2 * Math.PI;
4399
+ const color = getPieSliceColor(i, chart);
4400
+ const x1 = pieCx + pieR * Math.cos(currentAngle);
4401
+ const y1 = pieCy + pieR * Math.sin(currentAngle);
4402
+ const x2 = pieCx + pieR * Math.cos(currentAngle + sliceAngle);
4403
+ const y2 = pieCy + pieR * Math.sin(currentAngle + sliceAngle);
4404
+ const largeArc = sliceAngle > Math.PI ? 1 : 0;
4405
+ parts.push(
4406
+ `<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)}/>`
4407
+ );
4408
+ currentAngle += sliceAngle;
4409
+ }
4410
+ const otherAngleStart = currentAngle;
4411
+ const otherSliceAngle = secondaryTotal / total * 2 * Math.PI;
4412
+ const otherColor = { hex: "#D9D9D9", alpha: 1 };
4413
+ if (primaryValues.length === 0 && secondaryValues.length > 0) {
4414
+ parts.push(
4415
+ `<circle cx="${round3(pieCx)}" cy="${round3(pieCy)}" r="${round3(pieR)}" ${fillAttr(otherColor)}/>`
4416
+ );
4417
+ } else if (secondaryTotal > 0) {
4418
+ const x1 = pieCx + pieR * Math.cos(otherAngleStart);
4419
+ const y1 = pieCy + pieR * Math.sin(otherAngleStart);
4420
+ const x2 = pieCx + pieR * Math.cos(otherAngleStart + otherSliceAngle);
4421
+ const y2 = pieCy + pieR * Math.sin(otherAngleStart + otherSliceAngle);
4422
+ const largeArc = otherSliceAngle > Math.PI ? 1 : 0;
4423
+ parts.push(
4424
+ `<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)}/>`
4425
+ );
4426
+ }
4427
+ const secW = w * 0.25;
4428
+ const secH = h * (secondPieSize / 100) * 0.85;
4429
+ const secX = x + w * 0.65;
4430
+ const secCy = y + h / 2;
4431
+ const lineStartX = pieCx + pieR * Math.cos(otherAngleStart);
4432
+ const lineStartY = pieCy + pieR * Math.sin(otherAngleStart);
4433
+ const lineEndStartX = pieCx + pieR * Math.cos(otherAngleStart + otherSliceAngle);
4434
+ const lineEndStartY = pieCy + pieR * Math.sin(otherAngleStart + otherSliceAngle);
4435
+ parts.push(
4436
+ `<line x1="${round3(lineStartX)}" y1="${round3(lineStartY)}" x2="${round3(secX)}" y2="${round3(secCy - secH / 2)}" stroke="#A6A6A6" stroke-width="1"/>`
4437
+ );
4438
+ parts.push(
4439
+ `<line x1="${round3(lineEndStartX)}" y1="${round3(lineEndStartY)}" x2="${round3(secX)}" y2="${round3(secCy + secH / 2)}" stroke="#A6A6A6" stroke-width="1"/>`
4440
+ );
4441
+ if (isBarOfPie) {
4442
+ let barY = secCy - secH / 2;
4443
+ for (let i = 0; i < secondaryValues.length; i++) {
4444
+ const val = secondaryValues[i];
4445
+ const barH = secondaryTotal > 0 ? val / secondaryTotal * secH : 0;
4446
+ const color = getPieSliceColor(splitIdx + i, chart);
4447
+ parts.push(
4448
+ `<rect x="${round3(secX)}" y="${round3(barY)}" width="${round3(secW)}" height="${round3(barH)}" ${fillAttr(color)}/>`
4449
+ );
4450
+ barY += barH;
4451
+ }
4452
+ } else {
4453
+ const secPieCx = secX + secW / 2;
4454
+ const secR = Math.min(secW, secH) / 2;
4455
+ let secAngle = -Math.PI / 2;
4456
+ if (secondaryValues.length === 1) {
4457
+ const color = getPieSliceColor(splitIdx, chart);
4458
+ parts.push(
4459
+ `<circle cx="${round3(secPieCx)}" cy="${round3(secCy)}" r="${round3(secR)}" ${fillAttr(color)}/>`
4460
+ );
4461
+ } else {
4462
+ for (let i = 0; i < secondaryValues.length; i++) {
4463
+ const val = secondaryValues[i];
4464
+ const sliceAngle = secondaryTotal > 0 ? val / secondaryTotal * 2 * Math.PI : 0;
4465
+ const color = getPieSliceColor(splitIdx + i, chart);
4466
+ const sx1 = secPieCx + secR * Math.cos(secAngle);
4467
+ const sy1 = secCy + secR * Math.sin(secAngle);
4468
+ const sx2 = secPieCx + secR * Math.cos(secAngle + sliceAngle);
4469
+ const sy2 = secCy + secR * Math.sin(secAngle + sliceAngle);
4470
+ const largeArc = sliceAngle > Math.PI ? 1 : 0;
4471
+ parts.push(
4472
+ `<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)}/>`
4473
+ );
4474
+ secAngle += sliceAngle;
4475
+ }
4476
+ }
4477
+ }
4478
+ return parts.join("");
4479
+ }
4190
4480
  function renderLegend(chart, chartW, chartH, position) {
4191
4481
  const parts = [];
4192
- const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" ? chart.categories.map((cat, i) => ({
4482
+ const entries2 = chart.chartType === "pie" || chart.chartType === "doughnut" || chart.chartType === "ofPie" ? chart.categories.map((cat, i) => ({
4193
4483
  label: cat,
4194
4484
  color: getPieSliceColor(i, chart)
4195
4485
  })) : chart.series.map((s, i) => ({
@@ -4295,8 +4585,8 @@ function renderTable(element) {
4295
4585
  }
4296
4586
  if (cell.textBody) {
4297
4587
  const cellTransform = {
4298
- offsetX: 0,
4299
- offsetY: 0,
4588
+ offsetX: asEmu(0),
4589
+ offsetY: asEmu(0),
4300
4590
  extentWidth: pixelsToEmu(cellW),
4301
4591
  extentHeight: pixelsToEmu(cellH),
4302
4592
  rotation: 0,
@@ -4324,7 +4614,7 @@ function computeSpannedSize(sizes, startIdx, span) {
4324
4614
  return total;
4325
4615
  }
4326
4616
  function pixelsToEmu(px) {
4327
- return px / 96 * 914400;
4617
+ return asEmu(px / 96 * 914400);
4328
4618
  }
4329
4619
 
4330
4620
  // src/renderer/svg-renderer.ts
@@ -4628,7 +4918,7 @@ function parseDefaultRunProperties(defRPr, colorResolver) {
4628
4918
  if (!defRPr) return void 0;
4629
4919
  const result = {};
4630
4920
  if (defRPr["@_sz"] !== void 0) {
4631
- result.fontSize = hundredthPointToPoint(Number(defRPr["@_sz"]));
4921
+ result.fontSize = hundredthPointToPoint(asHundredthPt(Number(defRPr["@_sz"])));
4632
4922
  }
4633
4923
  const latin = defRPr.latin;
4634
4924
  if (latin?.["@_typeface"] !== void 0) {
@@ -4672,10 +4962,10 @@ function parseParagraphLevelProperties(node, colorResolver) {
4672
4962
  result.alignment = node["@_algn"];
4673
4963
  }
4674
4964
  if (node["@_marL"] !== void 0) {
4675
- result.marginLeft = Number(node["@_marL"]);
4965
+ result.marginLeft = asEmu(Number(node["@_marL"]));
4676
4966
  }
4677
4967
  if (node["@_indent"] !== void 0) {
4678
- result.indent = Number(node["@_indent"]);
4968
+ result.indent = asEmu(Number(node["@_indent"]));
4679
4969
  }
4680
4970
  const defRPr = parseDefaultRunProperties(node.defRPr, colorResolver);
4681
4971
  if (defRPr) {
@@ -4716,8 +5006,8 @@ function resolveThemeFont(typeface, fontScheme) {
4716
5006
  }
4717
5007
 
4718
5008
  // src/parser/presentation-parser.ts
4719
- var DEFAULT_SLIDE_WIDTH = 9144e3;
4720
- var DEFAULT_SLIDE_HEIGHT = 5143500;
5009
+ var DEFAULT_SLIDE_WIDTH = asEmu(9144e3);
5010
+ var DEFAULT_SLIDE_HEIGHT = asEmu(5143500);
4721
5011
  function parsePresentation(xml) {
4722
5012
  const parsed = parseXml(xml);
4723
5013
  const pres = parsed.presentation;
@@ -4738,8 +5028,8 @@ function parsePresentation(xml) {
4738
5028
  slideSize = { width: DEFAULT_SLIDE_WIDTH, height: DEFAULT_SLIDE_HEIGHT };
4739
5029
  } else {
4740
5030
  slideSize = {
4741
- width: Number(sldSz["@_cx"]),
4742
- height: Number(sldSz["@_cy"])
5031
+ width: asEmu(Number(sldSz["@_cx"])),
5032
+ height: asEmu(Number(sldSz["@_cy"]))
4743
5033
  };
4744
5034
  }
4745
5035
  const sldIdLst = pres.sldIdLst;
@@ -4897,8 +5187,8 @@ function parseBlipFill(blipFillNode, context) {
4897
5187
  const imageData = uint8ArrayToBase64(mediaData);
4898
5188
  const tileNode = blipFillNode.tile;
4899
5189
  const tile = tileNode ? {
4900
- tx: Number(tileNode["@_tx"] ?? 0),
4901
- ty: Number(tileNode["@_ty"] ?? 0),
5190
+ tx: asEmu(Number(tileNode["@_tx"] ?? 0)),
5191
+ ty: asEmu(Number(tileNode["@_ty"] ?? 0)),
4902
5192
  sx: Number(tileNode["@_sx"] ?? 1e5) / 1e5,
4903
5193
  sy: Number(tileNode["@_sy"] ?? 1e5) / 1e5,
4904
5194
  flip: tileNode["@_flip"] ?? "none",
@@ -4955,7 +5245,7 @@ function parseOutline(lnNode, colorResolver) {
4955
5245
  if (lnNode.pattFill) {
4956
5246
  warn("ln.pattFill", "pattern line fill not implemented");
4957
5247
  }
4958
- const width = Number(lnNode["@_w"] ?? 12700);
5248
+ const width = asEmu(Number(lnNode["@_w"] ?? 12700));
4959
5249
  let fill = null;
4960
5250
  if (lnNode.solidFill) {
4961
5251
  const color = colorResolver.resolve(lnNode.solidFill);
@@ -5036,8 +5326,8 @@ function parseOuterShadow(node, colorResolver) {
5036
5326
  const color = colorResolver.resolve(node);
5037
5327
  if (!color) return null;
5038
5328
  return {
5039
- blurRadius: Number(node["@_blurRad"] ?? 0),
5040
- distance: Number(node["@_dist"] ?? 0),
5329
+ blurRadius: asEmu(Number(node["@_blurRad"] ?? 0)),
5330
+ distance: asEmu(Number(node["@_dist"] ?? 0)),
5041
5331
  direction: Number(node["@_dir"] ?? 0) / 6e4,
5042
5332
  color,
5043
5333
  alignment: node["@_algn"] ?? "b",
@@ -5049,8 +5339,8 @@ function parseInnerShadow(node, colorResolver) {
5049
5339
  const color = colorResolver.resolve(node);
5050
5340
  if (!color) return null;
5051
5341
  return {
5052
- blurRadius: Number(node["@_blurRad"] ?? 0),
5053
- distance: Number(node["@_dist"] ?? 0),
5342
+ blurRadius: asEmu(Number(node["@_blurRad"] ?? 0)),
5343
+ distance: asEmu(Number(node["@_dist"] ?? 0)),
5054
5344
  direction: Number(node["@_dir"] ?? 0) / 6e4,
5055
5345
  color
5056
5346
  };
@@ -5060,14 +5350,14 @@ function parseGlow(node, colorResolver) {
5060
5350
  const color = colorResolver.resolve(node);
5061
5351
  if (!color) return null;
5062
5352
  return {
5063
- radius: Number(node["@_rad"] ?? 0),
5353
+ radius: asEmu(Number(node["@_rad"] ?? 0)),
5064
5354
  color
5065
5355
  };
5066
5356
  }
5067
5357
  function parseSoftEdge(node) {
5068
5358
  if (!node) return null;
5069
5359
  return {
5070
- radius: Number(node["@_rad"] ?? 0)
5360
+ radius: asEmu(Number(node["@_rad"] ?? 0))
5071
5361
  };
5072
5362
  }
5073
5363
 
@@ -5536,7 +5826,7 @@ function parseBiLevel(node) {
5536
5826
  function parseBlur(node) {
5537
5827
  if (!node) return null;
5538
5828
  return {
5539
- radius: Number(node["@_rad"] ?? 0),
5829
+ radius: asEmu(Number(node["@_rad"] ?? 0)),
5540
5830
  grow: node["@_grow"] !== "0"
5541
5831
  };
5542
5832
  }
@@ -5586,7 +5876,11 @@ var CHART_TYPE_MAP = [
5586
5876
  ["bubbleChart", "bubble"],
5587
5877
  ["areaChart", "area"],
5588
5878
  ["area3DChart", "area"],
5589
- ["radarChart", "radar"]
5879
+ ["radarChart", "radar"],
5880
+ ["stockChart", "stock"],
5881
+ ["surfaceChart", "surface"],
5882
+ ["surface3DChart", "surface"],
5883
+ ["ofPieChart", "ofPie"]
5590
5884
  ];
5591
5885
  function parseChart(chartXml, colorResolver) {
5592
5886
  const parsed = parseXml(chartXml);
@@ -5597,7 +5891,17 @@ function parseChart(chartXml, colorResolver) {
5597
5891
  const plotArea = chart.plotArea;
5598
5892
  if (!plotArea) return null;
5599
5893
  const title = parseChartTitle(chart.title);
5600
- const { chartType, series, categories, barDirection, holeSize, radarStyle } = parseChartTypeAndData(plotArea, colorResolver);
5894
+ const {
5895
+ chartType,
5896
+ series,
5897
+ categories,
5898
+ barDirection,
5899
+ holeSize,
5900
+ radarStyle,
5901
+ ofPieType,
5902
+ secondPieSize,
5903
+ splitPos
5904
+ } = parseChartTypeAndData(plotArea, colorResolver);
5601
5905
  if (!chartType) return null;
5602
5906
  const legend = parseLegend(chart.legend);
5603
5907
  return {
@@ -5608,6 +5912,9 @@ function parseChart(chartXml, colorResolver) {
5608
5912
  ...barDirection !== void 0 && { barDirection },
5609
5913
  ...holeSize !== void 0 && { holeSize },
5610
5914
  ...radarStyle !== void 0 && { radarStyle },
5915
+ ...ofPieType !== void 0 && { ofPieType },
5916
+ ...secondPieSize !== void 0 && { secondPieSize },
5917
+ ...splitPos !== void 0 && { splitPos },
5611
5918
  legend
5612
5919
  };
5613
5920
  }
@@ -5626,15 +5933,23 @@ function parseChartTypeAndData(plotArea, colorResolver) {
5626
5933
  const holeSize = chartType === "doughnut" ? Number(holeSizeNode?.["@_val"] ?? 50) : void 0;
5627
5934
  const radarStyleNode = chartNode.radarStyle;
5628
5935
  const radarStyle = chartType === "radar" ? radarStyleNode?.["@_val"] ?? "standard" : void 0;
5629
- return { chartType, series, categories, barDirection, holeSize, radarStyle };
5630
- }
5631
- const knownTags = new Set(CHART_TYPE_MAP.map(([tag]) => tag));
5632
- const chartTags = ["surfaceChart", "surface3DChart", "stockChart", "ofPieChart"];
5633
- for (const tag of chartTags) {
5634
- if (!knownTags.has(tag) && plotArea[tag]) {
5635
- warn(`chart.${tag}`, `chart type "${tag}" not implemented`);
5636
- break;
5637
- }
5936
+ const ofPieTypeNode = chartNode.ofPieType;
5937
+ const ofPieType = chartType === "ofPie" ? ofPieTypeNode?.["@_val"] ?? "pie" : void 0;
5938
+ const secondPieSizeNode = chartNode.secondPieSize;
5939
+ const secondPieSize = chartType === "ofPie" ? Number(secondPieSizeNode?.["@_val"] ?? 75) : void 0;
5940
+ const splitPosNode = chartNode.splitPos;
5941
+ const splitPos = chartType === "ofPie" ? Number(splitPosNode?.["@_val"] ?? 2) : void 0;
5942
+ return {
5943
+ chartType,
5944
+ series,
5945
+ categories,
5946
+ barDirection,
5947
+ holeSize,
5948
+ radarStyle,
5949
+ ofPieType,
5950
+ secondPieSize,
5951
+ splitPos
5952
+ };
5638
5953
  }
5639
5954
  return { chartType: null, series: [], categories: [] };
5640
5955
  }
@@ -6002,28 +6317,31 @@ function parseTable(tblNode, colorResolver, fontScheme) {
6002
6317
  if (!tblNode) return null;
6003
6318
  const columns = parseColumns(tblNode.tblGrid);
6004
6319
  if (columns.length === 0) return null;
6005
- const rows = parseRows(tblNode.tr, colorResolver, fontScheme);
6320
+ const tblPr = tblNode.tblPr;
6321
+ const hasTableStyle = tblPr?.tableStyleId !== void 0;
6322
+ const defaultBorders = hasTableStyle ? createDefaultBorders() : null;
6323
+ const rows = parseRows(tblNode.tr, colorResolver, fontScheme, defaultBorders);
6006
6324
  return { rows, columns };
6007
6325
  }
6008
6326
  function parseColumns(tblGrid) {
6009
6327
  if (!tblGrid) return [];
6010
6328
  const gridCols = tblGrid.gridCol ?? [];
6011
6329
  return gridCols.map((col) => ({
6012
- width: Number(col["@_w"] ?? 0)
6330
+ width: asEmu(Number(col["@_w"] ?? 0))
6013
6331
  }));
6014
6332
  }
6015
- function parseRows(trList, colorResolver, fontScheme) {
6333
+ function parseRows(trList, colorResolver, fontScheme, defaultBorders) {
6016
6334
  if (!trList) return [];
6017
6335
  const trArr = Array.isArray(trList) ? trList : [trList];
6018
6336
  const rows = [];
6019
6337
  for (const tr of trArr) {
6020
- const height = Number(tr["@_h"] ?? 0);
6021
- const cells = parseCells(tr.tc, colorResolver, fontScheme);
6338
+ const height = asEmu(Number(tr["@_h"] ?? 0));
6339
+ const cells = parseCells(tr.tc, colorResolver, fontScheme, defaultBorders);
6022
6340
  rows.push({ height, cells });
6023
6341
  }
6024
6342
  return rows;
6025
6343
  }
6026
- function parseCells(tcList, colorResolver, fontScheme) {
6344
+ function parseCells(tcList, colorResolver, fontScheme, defaultBorders) {
6027
6345
  if (!tcList) return [];
6028
6346
  const tcArr = Array.isArray(tcList) ? tcList : [tcList];
6029
6347
  const cells = [];
@@ -6031,7 +6349,8 @@ function parseCells(tcList, colorResolver, fontScheme) {
6031
6349
  const textBody = parseTextBody(tc.txBody, colorResolver, void 0, fontScheme);
6032
6350
  const tcPr = tc.tcPr;
6033
6351
  const fill = tcPr ? parseFillFromNode(tcPr, colorResolver) : null;
6034
- const borders = tcPr ? parseCellBorders(tcPr, colorResolver) : null;
6352
+ const inlineBorders = tcPr ? parseCellBorders(tcPr, colorResolver) : null;
6353
+ const borders = inlineBorders ?? defaultBorders ?? null;
6035
6354
  const gridSpan = Number(tc["@_gridSpan"] ?? 1);
6036
6355
  const rowSpan = Number(tc["@_rowSpan"] ?? 1);
6037
6356
  const hMerge = tcPr?.["@_hMerge"] === "1" || tcPr?.["@_hMerge"] === "true";
@@ -6045,9 +6364,29 @@ function parseCellBorders(tcPr, colorResolver) {
6045
6364
  const bottom = parseOutline(tcPr.lnB, colorResolver);
6046
6365
  const left = parseOutline(tcPr.lnL, colorResolver);
6047
6366
  const right = parseOutline(tcPr.lnR, colorResolver);
6367
+ for (const border of [top, bottom, left, right]) {
6368
+ if (border && !border.fill) {
6369
+ border.fill = { type: "solid", color: { hex: "#000000", alpha: 1 } };
6370
+ }
6371
+ }
6048
6372
  if (!top && !bottom && !left && !right) return null;
6049
6373
  return { top, bottom, left, right };
6050
6374
  }
6375
+ function createDefaultBorders() {
6376
+ const defaultOutline = {
6377
+ width: asEmu(12700),
6378
+ fill: { type: "solid", color: { hex: "#000000", alpha: 1 } },
6379
+ dashStyle: "solid",
6380
+ headEnd: null,
6381
+ tailEnd: null
6382
+ };
6383
+ return {
6384
+ top: { ...defaultOutline },
6385
+ bottom: { ...defaultOutline },
6386
+ left: { ...defaultOutline },
6387
+ right: { ...defaultOutline }
6388
+ };
6389
+ }
6051
6390
 
6052
6391
  // src/parser/slide-parser.ts
6053
6392
  var SHAPE_TAGS = /* @__PURE__ */ new Set(["sp", "pic", "cxnSp", "grpSp", "graphicFrame"]);
@@ -6475,8 +6814,8 @@ function parseStretchFillRect(stretchNode) {
6475
6814
  function parseTileInfo(node) {
6476
6815
  if (!node) return null;
6477
6816
  return {
6478
- tx: Number(node["@_tx"] ?? 0),
6479
- ty: Number(node["@_ty"] ?? 0),
6817
+ tx: asEmu(Number(node["@_tx"] ?? 0)),
6818
+ ty: asEmu(Number(node["@_ty"] ?? 0)),
6480
6819
  sx: Number(node["@_sx"] ?? 1e5) / 1e5,
6481
6820
  sy: Number(node["@_sy"] ?? 1e5) / 1e5,
6482
6821
  flip: node["@_flip"] ?? "none",
@@ -6508,10 +6847,10 @@ function parseGroup(grp, rels, slidePath, archive, colorResolver, parentFillCont
6508
6847
  const childOff = xfrm?.chOff;
6509
6848
  const childExt = xfrm?.chExt;
6510
6849
  const childTransform = {
6511
- offsetX: Number(childOff?.["@_x"] ?? 0),
6512
- offsetY: Number(childOff?.["@_y"] ?? 0),
6513
- extentWidth: Number(childExt?.["@_cx"] ?? transform.extentWidth),
6514
- extentHeight: Number(childExt?.["@_cy"] ?? transform.extentHeight),
6850
+ offsetX: asEmu(Number(childOff?.["@_x"] ?? 0)),
6851
+ offsetY: asEmu(Number(childOff?.["@_y"] ?? 0)),
6852
+ extentWidth: asEmu(Number(childExt?.["@_cx"] ?? transform.extentWidth)),
6853
+ extentHeight: asEmu(Number(childExt?.["@_cy"] ?? transform.extentHeight)),
6515
6854
  rotation: 0,
6516
6855
  flipH: false,
6517
6856
  flipV: false
@@ -6632,10 +6971,10 @@ function parseSmartArt(graphicData, transform, rels, slidePath, archive, colorRe
6632
6971
  const childOff = grpXfrm?.chOff;
6633
6972
  const childExt = grpXfrm?.chExt;
6634
6973
  const childTransform = {
6635
- offsetX: Number(childOff?.["@_x"] ?? 0),
6636
- offsetY: Number(childOff?.["@_y"] ?? 0),
6637
- extentWidth: Number(childExt?.["@_cx"] ?? transform.extentWidth),
6638
- extentHeight: Number(childExt?.["@_cy"] ?? transform.extentHeight),
6974
+ offsetX: asEmu(Number(childOff?.["@_x"] ?? 0)),
6975
+ offsetY: asEmu(Number(childOff?.["@_y"] ?? 0)),
6976
+ extentWidth: asEmu(Number(childExt?.["@_cx"] ?? transform.extentWidth)),
6977
+ extentHeight: asEmu(Number(childExt?.["@_cy"] ?? transform.extentHeight)),
6639
6978
  rotation: 0,
6640
6979
  flipH: false,
6641
6980
  flipV: false
@@ -6672,26 +7011,26 @@ function parseTransform(xfrm) {
6672
7011
  const off = xfrm.off;
6673
7012
  const ext = xfrm.ext;
6674
7013
  if (!off || !ext) return null;
6675
- let offsetX = Number(off["@_x"] ?? 0);
6676
- let offsetY = Number(off["@_y"] ?? 0);
6677
- let extentWidth = Number(ext["@_cx"] ?? 0);
6678
- let extentHeight = Number(ext["@_cy"] ?? 0);
7014
+ let offsetX = asEmu(Number(off["@_x"] ?? 0));
7015
+ let offsetY = asEmu(Number(off["@_y"] ?? 0));
7016
+ let extentWidth = asEmu(Number(ext["@_cx"] ?? 0));
7017
+ let extentHeight = asEmu(Number(ext["@_cy"] ?? 0));
6679
7018
  let rotation = Number(xfrm["@_rot"] ?? 0);
6680
7019
  if (Number.isNaN(offsetX)) {
6681
7020
  debug("transform.nan", "NaN detected in transform offsetX, defaulting to 0");
6682
- offsetX = 0;
7021
+ offsetX = asEmu(0);
6683
7022
  }
6684
7023
  if (Number.isNaN(offsetY)) {
6685
7024
  debug("transform.nan", "NaN detected in transform offsetY, defaulting to 0");
6686
- offsetY = 0;
7025
+ offsetY = asEmu(0);
6687
7026
  }
6688
7027
  if (Number.isNaN(extentWidth)) {
6689
7028
  debug("transform.nan", "NaN detected in transform extentWidth, defaulting to 0");
6690
- extentWidth = 0;
7029
+ extentWidth = asEmu(0);
6691
7030
  }
6692
7031
  if (Number.isNaN(extentHeight)) {
6693
7032
  debug("transform.nan", "NaN detected in transform extentHeight, defaulting to 0");
6694
- extentHeight = 0;
7033
+ extentHeight = asEmu(0);
6695
7034
  }
6696
7035
  if (Number.isNaN(rotation)) {
6697
7036
  debug("transform.nan", "NaN detected in transform rotation, defaulting to 0");
@@ -6756,10 +7095,10 @@ function parseTextBody(txBody, colorResolver, rels, fontScheme, lstStyleOverride
6756
7095
  }
6757
7096
  const bodyProperties = {
6758
7097
  anchor: bodyPr?.["@_anchor"] ?? "t",
6759
- marginLeft: Number(bodyPr?.["@_lIns"] ?? 91440),
6760
- marginRight: Number(bodyPr?.["@_rIns"] ?? 91440),
6761
- marginTop: Number(bodyPr?.["@_tIns"] ?? 45720),
6762
- marginBottom: Number(bodyPr?.["@_bIns"] ?? 45720),
7098
+ marginLeft: asEmu(Number(bodyPr?.["@_lIns"] ?? 91440)),
7099
+ marginRight: asEmu(Number(bodyPr?.["@_rIns"] ?? 91440)),
7100
+ marginTop: asEmu(Number(bodyPr?.["@_tIns"] ?? 45720)),
7101
+ marginBottom: asEmu(Number(bodyPr?.["@_bIns"] ?? 45720)),
6763
7102
  wrap: bodyPr?.["@_wrap"] ?? "square",
6764
7103
  autoFit,
6765
7104
  fontScale,
@@ -6828,13 +7167,13 @@ function parseBullet(pPr, colorResolver) {
6828
7167
  function parseSpacing(spc) {
6829
7168
  if (spc?.spcPts) {
6830
7169
  const spcPts = spc.spcPts;
6831
- return { type: "pts", value: Number(spcPts["@_val"]) };
7170
+ return { type: "pts", value: asHundredthPt(Number(spcPts["@_val"])) };
6832
7171
  }
6833
7172
  if (spc?.spcPct) {
6834
7173
  const spcPct = spc.spcPct;
6835
7174
  return { type: "pct", value: Number(spcPct["@_val"]) };
6836
7175
  }
6837
- return { type: "pts", value: 0 };
7176
+ return { type: "pts", value: asHundredthPt(0) };
6838
7177
  }
6839
7178
  function parseTabStops(pPr) {
6840
7179
  const tabLst = pPr?.tabLst;
@@ -6843,7 +7182,7 @@ function parseTabStops(pPr) {
6843
7182
  if (!tabs) return [];
6844
7183
  const tabArr = Array.isArray(tabs) ? tabs : [tabs];
6845
7184
  return tabArr.map((tab) => ({
6846
- position: Number(tab["@_pos"] ?? 0),
7185
+ position: asEmu(Number(tab["@_pos"] ?? 0)),
6847
7186
  alignment: tab["@_algn"] ?? "l"
6848
7187
  }));
6849
7188
  }
@@ -6893,8 +7232,8 @@ function parseParagraph(p, colorResolver, rels, fontScheme, lstStyle, orderedPCh
6893
7232
  bulletFont,
6894
7233
  bulletColor,
6895
7234
  bulletSizePct,
6896
- marginLeft: pPr?.["@_marL"] !== void 0 ? Number(pPr["@_marL"]) : lstLevelProps?.marginLeft ?? 0,
6897
- indent: pPr?.["@_indent"] !== void 0 ? Number(pPr["@_indent"]) : lstLevelProps?.indent ?? 0,
7235
+ marginLeft: pPr?.["@_marL"] !== void 0 ? asEmu(Number(pPr["@_marL"])) : lstLevelProps?.marginLeft ?? asEmu(0),
7236
+ indent: pPr?.["@_indent"] !== void 0 ? asEmu(Number(pPr["@_indent"])) : lstLevelProps?.indent ?? asEmu(0),
6898
7237
  tabStops
6899
7238
  };
6900
7239
  const pPrDefRPr = parseDefaultRunProperties(pPr?.defRPr);
@@ -7036,7 +7375,7 @@ function parseRunProperties(rPr, colorResolver, rels, fontScheme, defaults) {
7036
7375
  const latin = rPr.latin;
7037
7376
  const ea = rPr.ea;
7038
7377
  const cs = rPr.cs;
7039
- const fontSize = rPr["@_sz"] ? hundredthPointToPoint(Number(rPr["@_sz"])) : defaults?.fontSize ?? null;
7378
+ const fontSize = rPr["@_sz"] ? hundredthPointToPoint(asHundredthPt(Number(rPr["@_sz"]))) : defaults?.fontSize ?? null;
7040
7379
  const fontFamily = resolveThemeFont(
7041
7380
  latin?.["@_typeface"] ?? defaults?.fontFamily ?? null,
7042
7381
  fontScheme
@@ -7063,7 +7402,7 @@ function parseRunProperties(rPr, colorResolver, rels, fontScheme, defaults) {
7063
7402
  const ln = rPr.ln;
7064
7403
  let outline = null;
7065
7404
  if (ln) {
7066
- const lnWidth = Number(ln["@_w"] ?? 12700);
7405
+ const lnWidth = asEmu(Number(ln["@_w"] ?? 12700));
7067
7406
  const lnFill = ln.solidFill;
7068
7407
  const lnColor = lnFill ? colorResolver.resolve(lnFill) : null;
7069
7408
  if (lnColor) {
@@ -7353,7 +7692,23 @@ function findMatchingPlaceholderStyle(placeholderType, placeholderIdx, styles) {
7353
7692
  if (byIdx?.lstStyle) return byIdx.lstStyle;
7354
7693
  }
7355
7694
  const byType = styles.find((s) => s.placeholderType === placeholderType);
7356
- return byType?.lstStyle;
7695
+ if (byType?.lstStyle) return byType.lstStyle;
7696
+ const fallbackType = getPlaceholderFallbackType(placeholderType);
7697
+ if (fallbackType) {
7698
+ const byFallback = styles.find((s) => s.placeholderType === fallbackType);
7699
+ return byFallback?.lstStyle;
7700
+ }
7701
+ return void 0;
7702
+ }
7703
+ function getPlaceholderFallbackType(type) {
7704
+ switch (type) {
7705
+ case "ctrTitle":
7706
+ return "title";
7707
+ case "subTitle":
7708
+ return "body";
7709
+ default:
7710
+ return void 0;
7711
+ }
7357
7712
  }
7358
7713
  function getTxStyleForPlaceholder(placeholderType, txStyles) {
7359
7714
  if (!txStyles) return void 0;