docgen-utils 1.0.9 → 1.0.11

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/bundle.js CHANGED
@@ -25609,7 +25609,7 @@ var docgen = (() => {
25609
25609
  };
25610
25610
  return new TableRow({
25611
25611
  tableHeader: isHeaderRow,
25612
- children: cells.map((cell) => {
25612
+ children: cells.map((cell, cellIndex) => {
25613
25613
  const textRuns = typeof cell === "string" ? [new TextRun({
25614
25614
  text: cell,
25615
25615
  bold: isHeaderRow,
@@ -25649,6 +25649,10 @@ var docgen = (() => {
25649
25649
  fill: "F9FAFB"
25650
25650
  };
25651
25651
  }
25652
+ const isLastCell = cellIndex === cells.length - 1;
25653
+ const remainingColumns = columnCount - cells.length;
25654
+ const needsSpan = isLastCell && remainingColumns > 0;
25655
+ const cellColumnSpan = needsSpan ? remainingColumns + 1 : void 0;
25652
25656
  return new TableCell({
25653
25657
  children: [
25654
25658
  new Paragraph({
@@ -25659,6 +25663,7 @@ var docgen = (() => {
25659
25663
  size: 100 / columnCount,
25660
25664
  type: WidthType.PERCENTAGE
25661
25665
  },
25666
+ columnSpan: cellColumnSpan,
25662
25667
  shading,
25663
25668
  // Apply cell padding from CSS
25664
25669
  margins: cellPadding ? {
@@ -25801,10 +25806,11 @@ var docgen = (() => {
25801
25806
  }
25802
25807
  case "table": {
25803
25808
  const useHeaderStyling = element.noBorders ? false : element.hasHeader !== false;
25809
+ const maxColumnCount = Math.max(...element.rows.map((row) => row.length));
25804
25810
  const tableRows = element.rows.map((row, rowIndex) => createTableRow(
25805
25811
  row,
25806
25812
  useHeaderStyling && rowIndex === 0,
25807
- row.length,
25813
+ maxColumnCount,
25808
25814
  element.cellPadding,
25809
25815
  element.headerBackgroundColor,
25810
25816
  element.headerTextColor,
@@ -28627,6 +28633,45 @@ ${generateStylesCss(styleMap, themeFonts)}
28627
28633
  var PT_PER_PX = 0.75;
28628
28634
  var PX_PER_IN = 96;
28629
28635
  var SINGLE_WEIGHT_FONTS = ["impact"];
28636
+ var FONT_FAMILY_MAP = {
28637
+ // Common sans-serif web fonts → Calibri (default Office sans-serif)
28638
+ // Fonts that are commonly available via Google Fonts CDN are NOT mapped here,
28639
+ // to preserve original font names for systems that have them installed.
28640
+ "roboto": "Calibri",
28641
+ "open sans": "Calibri",
28642
+ "lato": "Calibri",
28643
+ "montserrat": "Calibri",
28644
+ "poppins": "Calibri",
28645
+ "source sans pro": "Calibri",
28646
+ "nunito": "Calibri",
28647
+ "raleway": "Calibri",
28648
+ "ubuntu": "Calibri",
28649
+ "pt sans": "Calibri",
28650
+ "noto sans": "Calibri",
28651
+ "fira sans": "Calibri",
28652
+ "work sans": "Calibri",
28653
+ // Serif fonts → Georgia (closest web-safe serif)
28654
+ "playfair display": "Georgia",
28655
+ "merriweather": "Georgia",
28656
+ "libre baskerville": "Georgia",
28657
+ "pt serif": "Georgia",
28658
+ "noto serif": "Georgia",
28659
+ "lora": "Georgia",
28660
+ // Monospace fonts → Courier New (standard monospace)
28661
+ "fira code": "Courier New",
28662
+ "source code pro": "Courier New",
28663
+ "jetbrains mono": "Courier New"
28664
+ };
28665
+ function mapFontFamily(fontFamily) {
28666
+ const normalized = fontFamily.toLowerCase().trim();
28667
+ return FONT_FAMILY_MAP[normalized] || fontFamily;
28668
+ }
28669
+ function extractFontFace(fontFamily) {
28670
+ if (!fontFamily)
28671
+ return void 0;
28672
+ const firstFont = fontFamily.split(",")[0].replace(/['"]/g, "").trim();
28673
+ return mapFontFamily(firstFont);
28674
+ }
28630
28675
  function pxToInch(px) {
28631
28676
  return px / PX_PER_IN;
28632
28677
  }
@@ -28634,6 +28679,7 @@ ${generateStylesCss(styleMap, themeFonts)}
28634
28679
  return parseFloat(pxStr) * PT_PER_PX;
28635
28680
  }
28636
28681
  function rgbToHex(rgbStr) {
28682
+ rgbStr = rgbStr.replace(/\s*!important\s*$/i, "").trim();
28637
28683
  if (rgbStr === "rgba(0, 0, 0, 0)" || rgbStr === "transparent")
28638
28684
  return "FFFFFF";
28639
28685
  const match = rgbStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
@@ -28658,14 +28704,27 @@ ${generateStylesCss(styleMap, themeFonts)}
28658
28704
  const alpha = parseFloat(match[4]);
28659
28705
  return Math.round((1 - alpha) * 100);
28660
28706
  }
28707
+ function extractDashType(borderStyle) {
28708
+ switch (borderStyle.toLowerCase()) {
28709
+ case "dashed":
28710
+ return "dash";
28711
+ case "dotted":
28712
+ return "sysDot";
28713
+ case "solid":
28714
+ default:
28715
+ return void 0;
28716
+ }
28717
+ }
28661
28718
  function parseCssGradient(gradientStr) {
28662
28719
  const colorToHex = (colorStr) => {
28663
28720
  colorStr = colorStr.trim().toLowerCase();
28721
+ colorStr = colorStr.replace(/\s*!important\s*$/i, "");
28664
28722
  if (colorStr === "transparent") {
28665
28723
  return "000000";
28666
28724
  }
28667
28725
  if (colorStr.startsWith("#")) {
28668
28726
  let hex = colorStr.slice(1);
28727
+ hex = hex.replace(/[^0-9a-f]/gi, "");
28669
28728
  if (hex.length === 3)
28670
28729
  hex = hex.split("").map((c) => c + c).join("");
28671
28730
  return hex.toUpperCase();
@@ -28678,6 +28737,7 @@ ${generateStylesCss(styleMap, themeFonts)}
28678
28737
  };
28679
28738
  const extractTransparency = (colorStr) => {
28680
28739
  colorStr = colorStr.trim().toLowerCase();
28740
+ colorStr = colorStr.replace(/\s*!important\s*$/i, "");
28681
28741
  if (colorStr === "transparent") {
28682
28742
  return 100;
28683
28743
  }
@@ -28894,6 +28954,14 @@ ${generateStylesCss(styleMap, themeFonts)}
28894
28954
  }
28895
28955
  return text;
28896
28956
  }
28957
+ function getTransformedText(element, computed) {
28958
+ const rawText = element.textContent?.trim() || "";
28959
+ const textTransform = computed.textTransform;
28960
+ if (textTransform && textTransform !== "none") {
28961
+ return applyTextTransform(rawText, textTransform);
28962
+ }
28963
+ return rawText;
28964
+ }
28897
28965
  function getRotation(transform, writingMode) {
28898
28966
  let angle = 0;
28899
28967
  if (writingMode === "vertical-rl") {
@@ -29107,6 +29175,332 @@ ${generateStylesCss(styleMap, themeFonts)}
29107
29175
  opacity
29108
29176
  };
29109
29177
  }
29178
+ function parseClipPathPolygon(clipPath) {
29179
+ if (!clipPath || clipPath === "none")
29180
+ return null;
29181
+ const polygonMatch = clipPath.match(/polygon\(([^)]+)\)/);
29182
+ if (!polygonMatch)
29183
+ return null;
29184
+ const points = [];
29185
+ const pairs = polygonMatch[1].split(",");
29186
+ for (const pair of pairs) {
29187
+ const coords = pair.trim().split(/\s+/);
29188
+ if (coords.length < 2)
29189
+ continue;
29190
+ const x = parseFloat(coords[0]) / 100;
29191
+ const y = parseFloat(coords[1]) / 100;
29192
+ if (isNaN(x) || isNaN(y))
29193
+ continue;
29194
+ points.push({ x, y });
29195
+ }
29196
+ return points.length >= 3 ? points : null;
29197
+ }
29198
+ function renderConicGradientAsImage(conicGradientCSS, width, height, isCircle, doc) {
29199
+ try {
29200
+ const canvas = doc.createElement("canvas");
29201
+ const scale = 2;
29202
+ canvas.width = Math.round(width * scale);
29203
+ canvas.height = Math.round(height * scale);
29204
+ const ctx = canvas.getContext("2d");
29205
+ if (!ctx)
29206
+ return null;
29207
+ const conicIdx = conicGradientCSS.indexOf("conic-gradient(");
29208
+ if (conicIdx === -1)
29209
+ return null;
29210
+ let depth = 0;
29211
+ let startIdx = -1;
29212
+ let endIdx = -1;
29213
+ for (let i = conicIdx + "conic-gradient".length; i < conicGradientCSS.length; i++) {
29214
+ if (conicGradientCSS[i] === "(") {
29215
+ if (depth === 0)
29216
+ startIdx = i + 1;
29217
+ depth++;
29218
+ } else if (conicGradientCSS[i] === ")") {
29219
+ depth--;
29220
+ if (depth === 0) {
29221
+ endIdx = i;
29222
+ break;
29223
+ }
29224
+ }
29225
+ }
29226
+ if (startIdx === -1 || endIdx === -1)
29227
+ return null;
29228
+ let innerContent = conicGradientCSS.substring(startIdx, endIdx).trim();
29229
+ let startAngleDeg = 0;
29230
+ const fromMatch = innerContent.match(/^from\s+([\d.]+)(deg|rad|turn|grad)/i);
29231
+ if (fromMatch) {
29232
+ const val = parseFloat(fromMatch[1]);
29233
+ const unit = fromMatch[2].toLowerCase();
29234
+ if (unit === "deg")
29235
+ startAngleDeg = val;
29236
+ else if (unit === "rad")
29237
+ startAngleDeg = val * (180 / Math.PI);
29238
+ else if (unit === "turn")
29239
+ startAngleDeg = val * 360;
29240
+ else if (unit === "grad")
29241
+ startAngleDeg = val * 0.9;
29242
+ innerContent = innerContent.substring(fromMatch[0].length).replace(/^\s*,\s*/, "");
29243
+ }
29244
+ let centerXFrac = 0.5;
29245
+ let centerYFrac = 0.5;
29246
+ const atMatch = innerContent.match(/^at\s+([\d.]+)(%|px)?\s+([\d.]+)(%|px)?/i);
29247
+ if (atMatch) {
29248
+ const xVal = parseFloat(atMatch[1]);
29249
+ const xUnit = atMatch[2] || "%";
29250
+ const yVal = parseFloat(atMatch[3]);
29251
+ const yUnit = atMatch[4] || "%";
29252
+ centerXFrac = xUnit === "%" ? xVal / 100 : xVal / width;
29253
+ centerYFrac = yUnit === "%" ? yVal / 100 : yVal / height;
29254
+ innerContent = innerContent.substring(atMatch[0].length).replace(/^\s*,\s*/, "");
29255
+ }
29256
+ const stopParts = [];
29257
+ let current = "";
29258
+ let parenDepth = 0;
29259
+ for (let i = 0; i < innerContent.length; i++) {
29260
+ const ch = innerContent[i];
29261
+ if (ch === "(")
29262
+ parenDepth++;
29263
+ else if (ch === ")")
29264
+ parenDepth--;
29265
+ else if (ch === "," && parenDepth === 0) {
29266
+ stopParts.push(current.trim());
29267
+ current = "";
29268
+ continue;
29269
+ }
29270
+ current += ch;
29271
+ }
29272
+ if (current.trim())
29273
+ stopParts.push(current.trim());
29274
+ if (stopParts.length === 0)
29275
+ return null;
29276
+ const rawStops = [];
29277
+ for (const part of stopParts) {
29278
+ const trimmed = part.trim();
29279
+ const posPattern = /([\d.]+)(deg|%|turn|rad|grad)/gi;
29280
+ const positions = [];
29281
+ let m;
29282
+ while ((m = posPattern.exec(trimmed)) !== null) {
29283
+ positions.push({ value: parseFloat(m[1]), unit: m[2].toLowerCase(), index: m.index });
29284
+ }
29285
+ const trailingPositions = [];
29286
+ if (positions.length > 0) {
29287
+ const lastPos = positions[positions.length - 1];
29288
+ const lastEnd = lastPos.index + `${lastPos.value}${lastPos.unit}`.length;
29289
+ if (Math.abs(lastEnd - trimmed.length) <= 1) {
29290
+ for (let i = positions.length - 1; i >= 0; i--) {
29291
+ const p = positions[i];
29292
+ const lastParen = trimmed.lastIndexOf(")");
29293
+ if (p.index > lastParen) {
29294
+ trailingPositions.unshift({ value: p.value, unit: p.unit });
29295
+ }
29296
+ }
29297
+ }
29298
+ }
29299
+ let color;
29300
+ if (trailingPositions.length > 0) {
29301
+ const firstPosIdx = positions.find((p) => trailingPositions.some((tp) => tp.value === p.value && tp.unit === p.unit))?.index ?? trimmed.length;
29302
+ color = trimmed.substring(0, firstPosIdx).trim();
29303
+ } else {
29304
+ color = trimmed;
29305
+ }
29306
+ const toFraction = (val, unit) => {
29307
+ switch (unit) {
29308
+ case "%":
29309
+ return val / 100;
29310
+ case "deg":
29311
+ return val / 360;
29312
+ case "turn":
29313
+ return val;
29314
+ case "rad":
29315
+ return val / (Math.PI * 2);
29316
+ case "grad":
29317
+ return val / 400;
29318
+ default:
29319
+ return val / 100;
29320
+ }
29321
+ };
29322
+ if (trailingPositions.length >= 2) {
29323
+ rawStops.push({ color, position: toFraction(trailingPositions[0].value, trailingPositions[0].unit) });
29324
+ rawStops.push({ color, position: toFraction(trailingPositions[1].value, trailingPositions[1].unit) });
29325
+ } else if (trailingPositions.length === 1) {
29326
+ rawStops.push({ color, position: toFraction(trailingPositions[0].value, trailingPositions[0].unit) });
29327
+ } else {
29328
+ rawStops.push({ color, position: null });
29329
+ }
29330
+ }
29331
+ if (rawStops.length === 0)
29332
+ return null;
29333
+ if (rawStops[0].position === null)
29334
+ rawStops[0].position = 0;
29335
+ if (rawStops[rawStops.length - 1].position === null)
29336
+ rawStops[rawStops.length - 1].position = 1;
29337
+ let lastKnownIdx = 0;
29338
+ for (let i = 1; i < rawStops.length; i++) {
29339
+ if (rawStops[i].position !== null) {
29340
+ const gapCount = i - lastKnownIdx;
29341
+ if (gapCount > 1) {
29342
+ const startVal = rawStops[lastKnownIdx].position;
29343
+ const endVal = rawStops[i].position;
29344
+ for (let j = lastKnownIdx + 1; j < i; j++) {
29345
+ rawStops[j].position = startVal + (endVal - startVal) * ((j - lastKnownIdx) / gapCount);
29346
+ }
29347
+ }
29348
+ lastKnownIdx = i;
29349
+ }
29350
+ }
29351
+ ctx.scale(scale, scale);
29352
+ const cx = width * centerXFrac;
29353
+ const cy = height * centerYFrac;
29354
+ const startAngleRad = startAngleDeg * Math.PI / 180;
29355
+ if (typeof ctx.createConicGradient === "function") {
29356
+ const grad = ctx.createConicGradient(startAngleRad, cx, cy);
29357
+ for (const stop of rawStops) {
29358
+ try {
29359
+ grad.addColorStop(Math.max(0, Math.min(1, stop.position)), stop.color);
29360
+ } catch {
29361
+ }
29362
+ }
29363
+ ctx.fillStyle = grad;
29364
+ ctx.fillRect(0, 0, width, height);
29365
+ } else {
29366
+ const scaledCx = cx;
29367
+ const scaledCy = cy;
29368
+ const radius = Math.max(width, height) * 1.5;
29369
+ const angleOffset = startAngleRad - Math.PI / 2;
29370
+ for (let i = 0; i < rawStops.length - 1; i++) {
29371
+ const startFrac = rawStops[i].position;
29372
+ const endFrac = rawStops[i + 1].position;
29373
+ if (endFrac <= startFrac)
29374
+ continue;
29375
+ const slices = Math.max(1, Math.ceil((endFrac - startFrac) * 360));
29376
+ for (let s = 0; s < slices; s++) {
29377
+ const t = s / slices;
29378
+ const fracStart = startFrac + (endFrac - startFrac) * t;
29379
+ const fracEnd = startFrac + (endFrac - startFrac) * ((s + 1) / slices);
29380
+ const a1 = fracStart * Math.PI * 2 + angleOffset;
29381
+ const a2 = fracEnd * Math.PI * 2 + angleOffset;
29382
+ const c1 = rawStops[i].color;
29383
+ const c2 = rawStops[i + 1].color;
29384
+ const blendColor = t < 0.5 ? c1 : c2;
29385
+ ctx.beginPath();
29386
+ ctx.moveTo(scaledCx, scaledCy);
29387
+ ctx.arc(scaledCx, scaledCy, radius, a1, a2);
29388
+ ctx.closePath();
29389
+ ctx.fillStyle = blendColor;
29390
+ ctx.fill();
29391
+ }
29392
+ }
29393
+ }
29394
+ if (isCircle) {
29395
+ ctx.globalCompositeOperation = "destination-in";
29396
+ ctx.beginPath();
29397
+ ctx.arc(cx, cy, Math.min(cx, cy), 0, Math.PI * 2);
29398
+ ctx.fill();
29399
+ ctx.globalCompositeOperation = "source-over";
29400
+ }
29401
+ return canvas.toDataURL("image/png");
29402
+ } catch {
29403
+ return null;
29404
+ }
29405
+ }
29406
+ function renderBlurredElementAsImage(el, blurPx, win) {
29407
+ try {
29408
+ const rect = el.getBoundingClientRect();
29409
+ if (rect.width <= 0 || rect.height <= 0)
29410
+ return null;
29411
+ const doc = win.document;
29412
+ const computed = win.getComputedStyle(el);
29413
+ const spread = blurPx * 3;
29414
+ const canvasW = rect.width + spread * 2;
29415
+ const canvasH = rect.height + spread * 2;
29416
+ const scale = 2;
29417
+ const canvas = doc.createElement("canvas");
29418
+ canvas.width = Math.round(canvasW * scale);
29419
+ canvas.height = Math.round(canvasH * scale);
29420
+ const ctx = canvas.getContext("2d");
29421
+ if (!ctx)
29422
+ return null;
29423
+ ctx.scale(scale, scale);
29424
+ const opacity = parseFloat(computed.opacity);
29425
+ if (!isNaN(opacity) && opacity < 1) {
29426
+ ctx.globalAlpha = opacity;
29427
+ }
29428
+ ctx.filter = `blur(${blurPx}px)`;
29429
+ const bgImage = computed.backgroundImage;
29430
+ const bgColor = computed.backgroundColor;
29431
+ const borderRadius = parseFloat(computed.borderRadius) || 0;
29432
+ const isCircle = borderRadius >= Math.min(rect.width, rect.height) / 2 - 1;
29433
+ const offsetX = spread;
29434
+ const offsetY = spread;
29435
+ if (bgImage && bgImage !== "none" && bgImage.includes("gradient")) {
29436
+ if (bgImage.includes("radial-gradient")) {
29437
+ const centerX = offsetX + rect.width / 2;
29438
+ const centerY = offsetY + rect.height / 2;
29439
+ const gradientRadius = Math.max(rect.width, rect.height) / 2;
29440
+ const canvasGrad = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, gradientRadius);
29441
+ const colorRegex = /rgba?\([^)]+\)|transparent|#[0-9a-fA-F]+/g;
29442
+ const pctRegex = /([\d.]+)%/g;
29443
+ const colors = Array.from(bgImage.matchAll(colorRegex)).map((m) => m[0]);
29444
+ const percents = Array.from(bgImage.matchAll(pctRegex)).map((m) => parseFloat(m[1]) / 100);
29445
+ for (let i = 0; i < colors.length; i++) {
29446
+ const pct = percents[i] !== void 0 ? percents[i] : i / Math.max(colors.length - 1, 1);
29447
+ try {
29448
+ canvasGrad.addColorStop(Math.min(1, Math.max(0, pct)), colors[i]);
29449
+ } catch {
29450
+ }
29451
+ }
29452
+ ctx.fillStyle = canvasGrad;
29453
+ } else if (bgImage.includes("linear-gradient")) {
29454
+ const canvasGrad = ctx.createLinearGradient(offsetX, offsetY, offsetX + rect.width, offsetY + rect.height);
29455
+ const colorRegex = /rgba?\([^)]+\)|transparent|#[0-9a-fA-F]+/g;
29456
+ const pctRegex = /([\d.]+)%/g;
29457
+ const colors = Array.from(bgImage.matchAll(colorRegex)).map((m) => m[0]);
29458
+ const percents = Array.from(bgImage.matchAll(pctRegex)).map((m) => parseFloat(m[1]) / 100);
29459
+ for (let i = 0; i < colors.length; i++) {
29460
+ const pct = percents[i] !== void 0 ? percents[i] : i / Math.max(colors.length - 1, 1);
29461
+ try {
29462
+ canvasGrad.addColorStop(Math.min(1, Math.max(0, pct)), colors[i]);
29463
+ } catch {
29464
+ }
29465
+ }
29466
+ ctx.fillStyle = canvasGrad;
29467
+ }
29468
+ } else if (bgColor && bgColor !== "rgba(0, 0, 0, 0)") {
29469
+ ctx.fillStyle = bgColor;
29470
+ }
29471
+ if (isCircle) {
29472
+ ctx.beginPath();
29473
+ ctx.arc(offsetX + rect.width / 2, offsetY + rect.height / 2, Math.min(rect.width, rect.height) / 2, 0, Math.PI * 2);
29474
+ ctx.fill();
29475
+ } else if (borderRadius > 0) {
29476
+ const r = Math.min(borderRadius, rect.width / 2, rect.height / 2);
29477
+ ctx.beginPath();
29478
+ ctx.moveTo(offsetX + r, offsetY);
29479
+ ctx.lineTo(offsetX + rect.width - r, offsetY);
29480
+ ctx.quadraticCurveTo(offsetX + rect.width, offsetY, offsetX + rect.width, offsetY + r);
29481
+ ctx.lineTo(offsetX + rect.width, offsetY + rect.height - r);
29482
+ ctx.quadraticCurveTo(offsetX + rect.width, offsetY + rect.height, offsetX + rect.width - r, offsetY + rect.height);
29483
+ ctx.lineTo(offsetX + r, offsetY + rect.height);
29484
+ ctx.quadraticCurveTo(offsetX, offsetY + rect.height, offsetX, offsetY + rect.height - r);
29485
+ ctx.lineTo(offsetX, offsetY + r);
29486
+ ctx.quadraticCurveTo(offsetX, offsetY, offsetX + r, offsetY);
29487
+ ctx.closePath();
29488
+ ctx.fill();
29489
+ } else {
29490
+ ctx.fillRect(offsetX, offsetY, rect.width, rect.height);
29491
+ }
29492
+ const dataUri = canvas.toDataURL("image/png");
29493
+ return {
29494
+ dataUri,
29495
+ x: rect.left - spread,
29496
+ y: rect.top - spread,
29497
+ w: canvasW,
29498
+ h: canvasH
29499
+ };
29500
+ } catch {
29501
+ return null;
29502
+ }
29503
+ }
29110
29504
  function extractPseudoElements(el, win) {
29111
29505
  const results = [];
29112
29506
  const parentRect = el.getBoundingClientRect();
@@ -29164,14 +29558,13 @@ ${generateStylesCss(styleMap, themeFonts)}
29164
29558
  let rectRadius = 0;
29165
29559
  const borderRadius = pComputed.borderRadius;
29166
29560
  const radiusValue = parseFloat(borderRadius);
29167
- if (radiusValue > 0) {
29561
+ const isCircularRadius = borderRadius.includes("%") ? radiusValue >= 50 : radiusValue > 0 && radiusValue >= Math.min(pWidth, pHeight) / 2 - 1;
29562
+ const aspectRatio = pWidth / pHeight;
29563
+ const isEllipse = isCircularRadius && aspectRatio > 0.5 && aspectRatio < 2;
29564
+ if (radiusValue > 0 && !isEllipse) {
29168
29565
  if (borderRadius.includes("%")) {
29169
- if (radiusValue >= 50) {
29170
- rectRadius = 1;
29171
- } else {
29172
- const minDim = Math.min(pWidth, pHeight);
29173
- rectRadius = radiusValue / 100 * pxToInch(minDim);
29174
- }
29566
+ const minDim = Math.min(pWidth, pHeight);
29567
+ rectRadius = radiusValue / 100 * pxToInch(minDim);
29175
29568
  } else {
29176
29569
  rectRadius = pxToInch(radiusValue);
29177
29570
  }
@@ -29193,11 +29586,12 @@ ${generateStylesCss(styleMap, themeFonts)}
29193
29586
  gradient,
29194
29587
  transparency: hasBg ? extractAlpha(pComputed.backgroundColor) : null,
29195
29588
  line: null,
29196
- rectRadius,
29589
+ rectRadius: isEllipse ? 0 : rectRadius,
29197
29590
  shadow,
29198
29591
  opacity: hasOpacity ? elementOpacity : null,
29199
- isEllipse: false,
29200
- softEdge: null
29592
+ isEllipse,
29593
+ softEdge: null,
29594
+ customGeometry: null
29201
29595
  }
29202
29596
  };
29203
29597
  results.push(shapeElement);
@@ -29269,10 +29663,23 @@ ${generateStylesCss(styleMap, themeFonts)}
29269
29663
  if (transparency !== null)
29270
29664
  options.transparency = transparency;
29271
29665
  }
29666
+ const bgClip = computed.webkitBackgroundClip || computed.backgroundClip;
29667
+ const textFillColor = computed.webkitTextFillColor;
29668
+ const isTextFillTransparent = textFillColor === "transparent" || textFillColor === "rgba(0, 0, 0, 0)" || textFillColor && textFillColor.includes("rgba") && textFillColor.endsWith(", 0)");
29669
+ if (bgClip === "text" && isTextFillTransparent) {
29670
+ const bgImage = computed.backgroundImage;
29671
+ if (bgImage && bgImage !== "none" && (bgImage.includes("linear-gradient") || bgImage.includes("radial-gradient"))) {
29672
+ const textGradient = parseCssGradient(bgImage);
29673
+ if (textGradient) {
29674
+ options.fontFill = { type: "gradient", gradient: textGradient };
29675
+ delete options.color;
29676
+ }
29677
+ }
29678
+ }
29272
29679
  if (computed.fontSize)
29273
29680
  options.fontSize = pxToPoints(computed.fontSize);
29274
29681
  if (computed.fontFamily) {
29275
- options.fontFace = computed.fontFamily.split(",")[0].replace(/['"]/g, "").trim();
29682
+ options.fontFace = extractFontFace(computed.fontFamily);
29276
29683
  }
29277
29684
  const runLetterSpacing = extractLetterSpacing(computed);
29278
29685
  if (runLetterSpacing !== null)
@@ -29345,10 +29752,14 @@ ${generateStylesCss(styleMap, themeFonts)}
29345
29752
  const hasBorder = computed2.borderWidth && parseFloat(computed2.borderWidth) > 0 || computed2.borderTopWidth && parseFloat(computed2.borderTopWidth) > 0 || computed2.borderRightWidth && parseFloat(computed2.borderRightWidth) > 0 || computed2.borderBottomWidth && parseFloat(computed2.borderBottomWidth) > 0 || computed2.borderLeftWidth && parseFloat(computed2.borderLeftWidth) > 0;
29346
29753
  const spanBgImage = computed2.backgroundImage;
29347
29754
  const hasGradientBg = spanBgImage && spanBgImage !== "none" && (spanBgImage.includes("linear-gradient") || spanBgImage.includes("radial-gradient"));
29348
- if (hasBg || hasBorder || hasGradientBg) {
29755
+ const spanBgClip = computed2.webkitBackgroundClip || computed2.backgroundClip;
29756
+ const spanTextFillColor = computed2.webkitTextFillColor;
29757
+ const isGradientTextFill = hasGradientBg && spanBgClip === "text" && (spanTextFillColor === "transparent" || spanTextFillColor === "rgba(0, 0, 0, 0)" || spanTextFillColor && spanTextFillColor.includes("rgba") && spanTextFillColor.endsWith(", 0)"));
29758
+ if (isGradientTextFill) {
29759
+ } else if (hasBg || hasBorder || hasGradientBg) {
29349
29760
  const rect2 = htmlEl.getBoundingClientRect();
29350
29761
  if (rect2.width > 0 && rect2.height > 0) {
29351
- const text2 = el.textContent.trim();
29762
+ const text2 = getTransformedText(htmlEl, computed2);
29352
29763
  const bgGradient = parseCssGradient(computed2.backgroundImage);
29353
29764
  const borderRadius = computed2.borderRadius;
29354
29765
  const radiusValue = parseFloat(borderRadius);
@@ -29372,6 +29783,8 @@ ${generateStylesCss(styleMap, themeFonts)}
29372
29783
  const spanOpacity = parseFloat(computed2.opacity);
29373
29784
  const hasSpanOpacity = !isNaN(spanOpacity) && spanOpacity < 1;
29374
29785
  const spanShadow = parseBoxShadow(computed2.boxShadow);
29786
+ const spanWhiteSpace = computed2.whiteSpace;
29787
+ const spanShouldNotWrap = spanWhiteSpace === "nowrap" || spanWhiteSpace === "pre" || rect2.width < 100 || rect2.height < 50;
29375
29788
  const shapeElement = {
29376
29789
  type: "shape",
29377
29790
  position: {
@@ -29384,11 +29797,12 @@ ${generateStylesCss(styleMap, themeFonts)}
29384
29797
  textRuns: null,
29385
29798
  style: text2 ? {
29386
29799
  fontSize: pxToPoints(computed2.fontSize),
29387
- fontFace: computed2.fontFamily.split(",")[0].replace(/['"]/g, "").trim(),
29800
+ fontFace: extractFontFace(computed2.fontFamily),
29388
29801
  color: rgbToHex(computed2.color),
29389
29802
  bold: parseInt(computed2.fontWeight) >= 600,
29390
29803
  align: "center",
29391
- valign: "middle"
29804
+ valign: "middle",
29805
+ wrap: !spanShouldNotWrap
29392
29806
  } : null,
29393
29807
  shape: {
29394
29808
  fill: hasBg ? rgbToHex(computed2.backgroundColor) : null,
@@ -29397,13 +29811,15 @@ ${generateStylesCss(styleMap, themeFonts)}
29397
29811
  line: hasUniformBorder ? {
29398
29812
  color: rgbToHex(computed2.borderColor),
29399
29813
  width: pxToPoints(borderTop),
29400
- transparency: extractAlpha(computed2.borderColor)
29814
+ transparency: extractAlpha(computed2.borderColor),
29815
+ dashType: extractDashType(computed2.borderStyle)
29401
29816
  } : null,
29402
29817
  rectRadius: spanIsEllipse ? 0 : rectRadius,
29403
29818
  shadow: spanShadow,
29404
29819
  opacity: hasSpanOpacity ? spanOpacity : null,
29405
29820
  isEllipse: spanIsEllipse,
29406
- softEdge: null
29821
+ softEdge: null,
29822
+ customGeometry: null
29407
29823
  }
29408
29824
  };
29409
29825
  elements.push(shapeElement);
@@ -29414,15 +29830,29 @@ ${generateStylesCss(styleMap, themeFonts)}
29414
29830
  const parent = el.parentElement;
29415
29831
  if (parent && parent.tagName === "DIV") {
29416
29832
  const rect2 = htmlEl.getBoundingClientRect();
29417
- const text2 = el.textContent.trim();
29833
+ const computed22 = win.getComputedStyle(el);
29834
+ const text2 = getTransformedText(htmlEl, computed22);
29418
29835
  if (rect2.width > 0 && rect2.height > 0 && text2) {
29419
- const computed22 = win.getComputedStyle(el);
29420
29836
  const fontSizePx2 = parseFloat(computed22.fontSize);
29421
29837
  const lineHeightPx2 = parseFloat(computed22.lineHeight);
29422
29838
  const lineHeightMultiplier2 = fontSizePx2 > 0 && !isNaN(lineHeightPx2) ? lineHeightPx2 / fontSizePx2 : 1;
29839
+ const span2BgClip = computed22.webkitBackgroundClip || computed22.backgroundClip;
29840
+ const span2TextFillColor = computed22.webkitTextFillColor;
29841
+ const span2IsGradientText = span2BgClip === "text" && (span2TextFillColor === "transparent" || span2TextFillColor === "rgba(0, 0, 0, 0)" || span2TextFillColor && span2TextFillColor.includes("rgba") && span2TextFillColor.endsWith(", 0)"));
29842
+ const span2BgImage = computed22.backgroundImage;
29843
+ const span2HasGradientBg = span2BgImage && span2BgImage !== "none" && (span2BgImage.includes("linear-gradient") || span2BgImage.includes("radial-gradient"));
29844
+ let spanFontFill = void 0;
29845
+ let spanTextColor = rgbToHex(computed22.color);
29846
+ if (span2IsGradientText && span2HasGradientBg) {
29847
+ const spanGradient = parseCssGradient(span2BgImage);
29848
+ if (spanGradient) {
29849
+ spanFontFill = { type: "gradient", gradient: spanGradient };
29850
+ spanTextColor = null;
29851
+ }
29852
+ }
29423
29853
  const textElement = {
29424
29854
  type: "p",
29425
- text: [{ text: text2, options: {} }],
29855
+ text: [{ text: text2, options: spanFontFill ? { fontFill: spanFontFill } : {} }],
29426
29856
  position: {
29427
29857
  x: pxToInch(rect2.left),
29428
29858
  y: pxToInch(rect2.top),
@@ -29431,13 +29861,14 @@ ${generateStylesCss(styleMap, themeFonts)}
29431
29861
  },
29432
29862
  style: {
29433
29863
  fontSize: pxToPoints(computed22.fontSize),
29434
- fontFace: computed22.fontFamily.split(",")[0].replace(/['"]/g, "").trim(),
29435
- color: rgbToHex(computed22.color),
29864
+ fontFace: extractFontFace(computed22.fontFamily),
29865
+ color: spanTextColor,
29436
29866
  bold: parseInt(computed22.fontWeight) >= 600,
29437
29867
  italic: computed22.fontStyle === "italic",
29438
29868
  align: computed22.textAlign === "center" ? "center" : computed22.textAlign === "right" ? "right" : "left",
29439
29869
  valign: "middle",
29440
- lineSpacing: lineHeightMultiplier2 * pxToPoints(computed22.fontSize)
29870
+ lineSpacing: lineHeightMultiplier2 * pxToPoints(computed22.fontSize),
29871
+ fontFill: spanFontFill
29441
29872
  }
29442
29873
  };
29443
29874
  elements.push(textElement);
@@ -29576,7 +30007,7 @@ ${generateStylesCss(styleMap, themeFonts)}
29576
30007
  return;
29577
30008
  }
29578
30009
  }
29579
- if (el.tagName === "svg") {
30010
+ if (el.tagName.toLowerCase() === "svg") {
29580
30011
  const rect2 = htmlEl.getBoundingClientRect();
29581
30012
  if (rect2.width > 0 && rect2.height > 0) {
29582
30013
  const computedStyle = win.getComputedStyle(htmlEl);
@@ -29626,30 +30057,73 @@ ${generateStylesCss(styleMap, themeFonts)}
29626
30057
  }
29627
30058
  return null;
29628
30059
  };
29629
- const resolveDynamicColors = (element) => {
29630
- const fill = element.getAttribute("fill");
30060
+ const originalElements = [el, ...Array.from(el.querySelectorAll("*"))];
30061
+ const computedStylesMap = /* @__PURE__ */ new Map();
30062
+ originalElements.forEach((origEl, index) => {
30063
+ const isSvgElement = origEl.namespaceURI === "http://www.w3.org/2000/svg";
30064
+ if (isSvgElement) {
30065
+ const style = win.getComputedStyle(origEl);
30066
+ computedStylesMap.set(index, {
30067
+ fill: style.fill || "",
30068
+ stroke: style.stroke || "",
30069
+ strokeWidth: style.strokeWidth || ""
30070
+ });
30071
+ }
30072
+ });
30073
+ const cloneElements = [svgClone, ...Array.from(svgClone.querySelectorAll("*"))];
30074
+ cloneElements.forEach((cloneEl, index) => {
30075
+ const isSvgElement = cloneEl.namespaceURI === "http://www.w3.org/2000/svg";
30076
+ const fill = cloneEl.getAttribute("fill");
29631
30077
  const resolvedFill = resolveColorValue(fill);
29632
30078
  if (resolvedFill) {
29633
- element.setAttribute("fill", resolvedFill);
30079
+ cloneEl.setAttribute("fill", resolvedFill);
30080
+ } else if (!fill && isSvgElement) {
30081
+ const computed2 = computedStylesMap.get(index);
30082
+ if (computed2 && computed2.fill) {
30083
+ if (computed2.fill === "none") {
30084
+ cloneEl.setAttribute("fill", "none");
30085
+ } else if (computed2.fill !== "rgb(0, 0, 0)") {
30086
+ cloneEl.setAttribute("fill", computed2.fill);
30087
+ }
30088
+ }
29634
30089
  }
29635
- const stroke = element.getAttribute("stroke");
30090
+ const stroke = cloneEl.getAttribute("stroke");
29636
30091
  const resolvedStroke = resolveColorValue(stroke);
29637
30092
  if (resolvedStroke) {
29638
- element.setAttribute("stroke", resolvedStroke);
30093
+ cloneEl.setAttribute("stroke", resolvedStroke);
30094
+ } else if (!stroke && isSvgElement) {
30095
+ const computed2 = computedStylesMap.get(index);
30096
+ if (computed2 && computed2.stroke && computed2.stroke !== "none") {
30097
+ cloneEl.setAttribute("stroke", computed2.stroke);
30098
+ }
30099
+ if (computed2 && computed2.strokeWidth && computed2.strokeWidth !== "1px" && !cloneEl.getAttribute("stroke-width")) {
30100
+ cloneEl.setAttribute("stroke-width", computed2.strokeWidth);
30101
+ }
29639
30102
  }
29640
- element.querySelectorAll("*").forEach(resolveDynamicColors);
29641
- };
29642
- resolveDynamicColors(svgClone);
30103
+ });
29643
30104
  const serializer = new XMLSerializer();
29644
30105
  const svgString = serializer.serializeToString(svgClone);
29645
30106
  const svgBase64 = btoa(unescape(encodeURIComponent(svgString)));
29646
30107
  const dataUri = `data:image/svg+xml;base64,${svgBase64}`;
30108
+ let svgY = rect2.top;
30109
+ const computedAlign = win.getComputedStyle(htmlEl);
30110
+ const verticalAlign = computedAlign.verticalAlign;
30111
+ if (verticalAlign === "middle" && el.parentElement) {
30112
+ const parentComputed = win.getComputedStyle(el.parentElement);
30113
+ const parentIsFlex = parentComputed.display === "flex" || parentComputed.display === "inline-flex";
30114
+ if (!parentIsFlex) {
30115
+ const parentRect = el.parentElement.getBoundingClientRect();
30116
+ if (parentRect.height > rect2.height) {
30117
+ svgY = parentRect.top + (parentRect.height - rect2.height) / 2;
30118
+ }
30119
+ }
30120
+ }
29647
30121
  const imageElement = {
29648
30122
  type: "image",
29649
30123
  src: dataUri,
29650
30124
  position: {
29651
30125
  x: pxToInch(rect2.left),
29652
- y: pxToInch(rect2.top),
30126
+ y: pxToInch(svgY),
29653
30127
  w: pxToInch(rect2.width),
29654
30128
  h: pxToInch(rect2.height)
29655
30129
  },
@@ -29662,13 +30136,131 @@ ${generateStylesCss(styleMap, themeFonts)}
29662
30136
  }
29663
30137
  }
29664
30138
  if (el.tagName === "I") {
30139
+ const rect2 = htmlEl.getBoundingClientRect();
30140
+ if (rect2.width > 0 && rect2.height > 0) {
30141
+ const computed2 = win.getComputedStyle(htmlEl);
30142
+ const beforeComputed = win.getComputedStyle(htmlEl, "::before");
30143
+ const fontFamily = beforeComputed.fontFamily || computed2.fontFamily;
30144
+ const content = beforeComputed.content;
30145
+ const isFontIcon = fontFamily.toLowerCase().includes("font awesome") || fontFamily.toLowerCase().includes("fontawesome") || fontFamily.toLowerCase().includes("fa ") || fontFamily.toLowerCase().includes("material") || fontFamily.toLowerCase().includes("icon");
30146
+ if (isFontIcon && content && content !== "none" && content !== "normal") {
30147
+ try {
30148
+ const scale = 8;
30149
+ const w2 = Math.ceil(rect2.width);
30150
+ const h2 = Math.ceil(rect2.height);
30151
+ const iframeDoc = win.document;
30152
+ const canvas = iframeDoc.createElement("canvas");
30153
+ canvas.width = w2 * scale;
30154
+ canvas.height = h2 * scale;
30155
+ const ctx = canvas.getContext("2d");
30156
+ if (ctx) {
30157
+ ctx.scale(scale, scale);
30158
+ let iconChar = content.replace(/['"]/g, "");
30159
+ if (iconChar.startsWith("\\")) {
30160
+ const codePoint = parseInt(iconChar.slice(1), 16);
30161
+ if (!isNaN(codePoint)) {
30162
+ iconChar = String.fromCodePoint(codePoint);
30163
+ }
30164
+ }
30165
+ const fontSize = parseFloat(beforeComputed.fontSize) || parseFloat(computed2.fontSize) || 16;
30166
+ const fontWeight = beforeComputed.fontWeight || computed2.fontWeight || "900";
30167
+ const fontFamilies = fontFamily.split(",").map((f) => f.trim().replace(/['"]/g, ""));
30168
+ let fontSet = false;
30169
+ for (const ff of fontFamilies) {
30170
+ const fontSpec = `${fontWeight} ${fontSize}px "${ff}"`;
30171
+ if (iframeDoc.fonts && iframeDoc.fonts.check(fontSpec)) {
30172
+ ctx.font = fontSpec;
30173
+ fontSet = true;
30174
+ break;
30175
+ }
30176
+ }
30177
+ if (!fontSet) {
30178
+ ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;
30179
+ }
30180
+ ctx.fillStyle = computed2.color || "white";
30181
+ ctx.textAlign = "center";
30182
+ ctx.textBaseline = "middle";
30183
+ ctx.fillText(iconChar, w2 / 2, h2 / 2);
30184
+ const dataUri = canvas.toDataURL("image/png");
30185
+ const imageElement = {
30186
+ type: "image",
30187
+ src: dataUri,
30188
+ position: {
30189
+ x: pxToInch(rect2.left),
30190
+ y: pxToInch(rect2.top),
30191
+ w: pxToInch(rect2.width),
30192
+ h: pxToInch(rect2.height)
30193
+ },
30194
+ sizing: null
30195
+ };
30196
+ elements.push(imageElement);
30197
+ }
30198
+ } catch {
30199
+ }
30200
+ }
30201
+ }
29665
30202
  processed.add(el);
29666
30203
  return;
29667
30204
  }
29668
30205
  const isContainer = el.tagName === "DIV" && !textTags.includes(el.tagName);
29669
30206
  if (isContainer) {
29670
30207
  const computed2 = win.getComputedStyle(el);
30208
+ const divFilterStr = computed2.filter;
30209
+ if (divFilterStr && divFilterStr !== "none") {
30210
+ const divBlurMatch = divFilterStr.match(/blur\(([\d.]+)px\)/);
30211
+ if (divBlurMatch) {
30212
+ const divBlurPx = parseFloat(divBlurMatch[1]);
30213
+ if (divBlurPx > 20) {
30214
+ const blurResult = renderBlurredElementAsImage(htmlEl, divBlurPx, win);
30215
+ if (blurResult) {
30216
+ const imgElement = {
30217
+ type: "image",
30218
+ src: blurResult.dataUri,
30219
+ position: {
30220
+ x: pxToInch(blurResult.x),
30221
+ y: pxToInch(blurResult.y),
30222
+ w: pxToInch(blurResult.w),
30223
+ h: pxToInch(blurResult.h)
30224
+ },
30225
+ sizing: null,
30226
+ rectRadius: 0
30227
+ };
30228
+ elements.push(imgElement);
30229
+ processed.add(el);
30230
+ el.querySelectorAll("*").forEach((child) => processed.add(child));
30231
+ return;
30232
+ }
30233
+ }
30234
+ }
30235
+ }
30236
+ const divBgImageForConic = computed2.backgroundImage;
30237
+ if (divBgImageForConic && divBgImageForConic.includes("conic-gradient")) {
30238
+ const rect2 = htmlEl.getBoundingClientRect();
30239
+ if (rect2.width > 0 && rect2.height > 0) {
30240
+ const borderRadiusStr = computed2.borderRadius;
30241
+ const radiusVal = parseFloat(borderRadiusStr);
30242
+ const isCircular = borderRadiusStr.includes("%") ? radiusVal >= 50 : radiusVal > 0 && radiusVal >= Math.min(rect2.width, rect2.height) / 2 - 1;
30243
+ const conicDataUri = renderConicGradientAsImage(divBgImageForConic, rect2.width, rect2.height, isCircular, win.document);
30244
+ if (conicDataUri) {
30245
+ const imgElement = {
30246
+ type: "image",
30247
+ src: conicDataUri,
30248
+ position: {
30249
+ x: pxToInch(rect2.left),
30250
+ y: pxToInch(rect2.top),
30251
+ w: pxToInch(rect2.width),
30252
+ h: pxToInch(rect2.height)
30253
+ },
30254
+ sizing: null,
30255
+ rectRadius: 0
30256
+ };
30257
+ elements.push(imgElement);
30258
+ }
30259
+ }
30260
+ }
29671
30261
  const hasBg = computed2.backgroundColor && computed2.backgroundColor !== "rgba(0, 0, 0, 0)";
30262
+ const clipPathValue = computed2.clipPath || computed2.webkitClipPath;
30263
+ const clipPathPolygon = parseClipPathPolygon(clipPathValue);
29672
30264
  const elBgImage = computed2.backgroundImage;
29673
30265
  let bgImageUrl = null;
29674
30266
  let bgImageSize = null;
@@ -29784,14 +30376,22 @@ ${generateStylesCss(styleMap, themeFonts)}
29784
30376
  if (textTagSet.has(child.tagName))
29785
30377
  return true;
29786
30378
  if (child.tagName === "DIV") {
30379
+ const childComputed = win.getComputedStyle(child);
30380
+ const childDisplay = childComputed.display;
30381
+ const childFlexDir = childComputed.flexDirection || "row";
30382
+ const childChildCount = child.children.length;
30383
+ const isFlexRowLayout = (childDisplay === "flex" || childDisplay === "inline-flex") && (childFlexDir === "row" || childFlexDir === "row-reverse") && childChildCount > 1;
30384
+ const isGridLayout = childDisplay === "grid" || childDisplay === "inline-grid";
30385
+ if (isFlexRowLayout || isGridLayout)
30386
+ return false;
29787
30387
  const childElements = Array.from(child.children);
29788
- const isTextOnlyDiv = childElements.length === 0 || childElements.every((ce) => ce.tagName === "BR") || childElements.length === 1 && childElements[0].tagName === "P" && childElements[0].children.length === 0;
30388
+ const inlineTextTags = /* @__PURE__ */ new Set(["STRONG", "EM", "B", "I", "A", "BR", "SPAN", "MARK", "SMALL", "SUB", "SUP", "CODE", "U", "S", "Q", "CITE", "ABBR", "TIME", "DATA"]);
30389
+ const isTextOnlyDiv = childElements.length === 0 || childElements.every((ce) => inlineTextTags.has(ce.tagName)) || childElements.length === 1 && childElements[0].tagName === "P" && (childElements[0].children.length === 0 || Array.from(childElements[0].children).every((ce) => inlineTextTags.has(ce.tagName)));
29789
30390
  if (!isTextOnlyDiv)
29790
30391
  return false;
29791
30392
  const childText = child.textContent?.trim();
29792
30393
  if (!childText)
29793
30394
  return false;
29794
- const childComputed = win.getComputedStyle(child);
29795
30395
  const childHasBg = childComputed.backgroundColor && childComputed.backgroundColor !== "rgba(0, 0, 0, 0)";
29796
30396
  const childHasBgImg = childComputed.backgroundImage && childComputed.backgroundImage !== "none";
29797
30397
  const childBorders = [
@@ -29807,10 +30407,11 @@ ${generateStylesCss(styleMap, themeFonts)}
29807
30407
  });
29808
30408
  const decorativeTags = /* @__PURE__ */ new Set(["I", "SVG", "CANVAS", "VIDEO", "AUDIO", "IFRAME"]);
29809
30409
  const nonTextChildren = allChildren.filter((child) => !textTagSet.has(child.tagName) && !decorativeTags.has(child.tagName) && !(child.tagName === "DIV" && textChildren.includes(child)));
30410
+ const inlineTextTagsForSimple = /* @__PURE__ */ new Set(["STRONG", "EM", "B", "I", "A", "BR", "SPAN", "MARK", "SMALL", "SUB", "SUP", "CODE", "U", "S", "Q", "CITE", "ABBR", "TIME", "DATA"]);
29810
30411
  const isSingleTextChild = textChildren.length === 1 && (() => {
29811
30412
  const tc = textChildren[0];
29812
30413
  const tcChildren = Array.from(tc.children);
29813
- return tcChildren.length === 0 || tcChildren.every((ce) => ce.tagName === "BR") || tcChildren.length === 1 && tcChildren[0].tagName === "P" && tcChildren[0].children.length === 0;
30414
+ return tcChildren.length === 0 || tcChildren.every((ce) => inlineTextTagsForSimple.has(ce.tagName)) || tcChildren.length === 1 && tcChildren[0].tagName === "P" && (tcChildren[0].children.length === 0 || Array.from(tcChildren[0].children).every((ce) => inlineTextTagsForSimple.has(ce.tagName)));
29814
30415
  })() && nonTextChildren.length === 0;
29815
30416
  const display = computed2.display;
29816
30417
  const isFlexContainer2 = display === "flex" || display === "inline-flex";
@@ -29854,26 +30455,32 @@ ${generateStylesCss(styleMap, themeFonts)}
29854
30455
  } else if (textAlign2 === "right" || textAlign2 === "end") {
29855
30456
  align = "right";
29856
30457
  } else {
29857
- const paddingLeft = parseFloat(computed2.paddingLeft) || 0;
29858
- const paddingRight = parseFloat(computed2.paddingRight) || 0;
29859
- const paddingDiff = Math.abs(paddingLeft - paddingRight);
29860
- if (paddingLeft > 0 && paddingDiff < 2) {
29861
- align = "center";
29862
- } else {
29863
- align = "left";
29864
- }
30458
+ align = "left";
29865
30459
  }
29866
30460
  }
29867
30461
  let shapeText = "";
29868
30462
  let shapeTextRuns = null;
29869
30463
  let shapeStyle = null;
29870
30464
  const hasTextChildren = textChildren.length > 0;
29871
- const shouldMergeText = hasTextChildren && (isSingleTextChild || nonTextChildren.length === 0);
30465
+ let directTextContent = "";
30466
+ if (!hasTextChildren) {
30467
+ for (const node of Array.from(el.childNodes)) {
30468
+ if (node.nodeType === Node.TEXT_NODE) {
30469
+ directTextContent += node.textContent || "";
30470
+ }
30471
+ }
30472
+ directTextContent = directTextContent.trim();
30473
+ if (directTextContent && computed2.textTransform && computed2.textTransform !== "none") {
30474
+ directTextContent = applyTextTransform(directTextContent, computed2.textTransform);
30475
+ }
30476
+ }
30477
+ const hasDirectText = directTextContent.length > 0;
30478
+ const shouldMergeText = hasTextChildren && (isSingleTextChild || nonTextChildren.length === 0) || hasDirectText;
29872
30479
  if (shouldMergeText) {
29873
30480
  if (isSingleTextChild) {
29874
30481
  const textEl = textChildren[0];
29875
30482
  const textComputed = win.getComputedStyle(textEl);
29876
- shapeText = textEl.textContent.trim();
30483
+ shapeText = getTransformedText(textEl, textComputed);
29877
30484
  let fontFill = null;
29878
30485
  const textBgClip = textComputed.webkitBackgroundClip || textComputed.backgroundClip;
29879
30486
  const textFillColor2 = textComputed.webkitTextFillColor;
@@ -29904,7 +30511,7 @@ ${generateStylesCss(styleMap, themeFonts)}
29904
30511
  const shouldNotWrap = whiteSpace === "nowrap" || whiteSpace === "pre" || rect2.width < 100 || rect2.height < 50;
29905
30512
  shapeStyle = {
29906
30513
  fontSize: pxToPoints(textComputed.fontSize || computed2.fontSize),
29907
- fontFace: (textComputed.fontFamily || computed2.fontFamily).split(",")[0].replace(/['"]/g, "").trim(),
30514
+ fontFace: extractFontFace(textComputed.fontFamily || computed2.fontFamily),
29908
30515
  color: fontFill ? null : rgbToHex(effectiveColor),
29909
30516
  fontFill,
29910
30517
  bold: isBold,
@@ -29923,12 +30530,12 @@ ${generateStylesCss(styleMap, themeFonts)}
29923
30530
  shapeStyle.textShadow = shapeTextShadow.shadow;
29924
30531
  processed.add(textEl);
29925
30532
  textEl.querySelectorAll("*").forEach((desc) => processed.add(desc));
29926
- } else {
30533
+ } else if (hasTextChildren) {
29927
30534
  shapeTextRuns = [];
29928
30535
  textChildren.forEach((textChild, idx) => {
29929
30536
  const textEl = textChild;
29930
30537
  const textComputed = win.getComputedStyle(textEl);
29931
- const fullText = textEl.textContent.trim();
30538
+ const fullText = getTransformedText(textEl, textComputed);
29932
30539
  if (!fullText)
29933
30540
  return;
29934
30541
  const isBold = textComputed.fontWeight === "bold" || parseInt(textComputed.fontWeight) >= 600;
@@ -29936,7 +30543,7 @@ ${generateStylesCss(styleMap, themeFonts)}
29936
30543
  const isUnderline = textComputed.textDecoration && textComputed.textDecoration.includes("underline");
29937
30544
  const baseRunOptions = {
29938
30545
  fontSize: pxToPoints(textComputed.fontSize),
29939
- fontFace: textComputed.fontFamily.split(",")[0].replace(/['"]/g, "").trim(),
30546
+ fontFace: extractFontFace(textComputed.fontFamily),
29940
30547
  color: rgbToHex(textComputed.color),
29941
30548
  bold: isBold,
29942
30549
  italic: isItalic,
@@ -29962,6 +30569,9 @@ ${generateStylesCss(styleMap, themeFonts)}
29962
30569
  if (currentSegment.trim())
29963
30570
  segments.push(currentSegment.trim());
29964
30571
  segments = segments.filter((s) => s.length > 0);
30572
+ if (textComputed.textTransform && textComputed.textTransform !== "none") {
30573
+ segments = segments.map((s) => applyTextTransform(s, textComputed.textTransform));
30574
+ }
29965
30575
  segments.forEach((segment, segIdx) => {
29966
30576
  const prefix = segIdx === 0 && idx > 0 && shapeTextRuns.length > 0 ? "\n" : "";
29967
30577
  const runText = prefix + segment;
@@ -29984,6 +30594,26 @@ ${generateStylesCss(styleMap, themeFonts)}
29984
30594
  valign: valign2,
29985
30595
  inset: 0
29986
30596
  };
30597
+ } else if (hasDirectText) {
30598
+ shapeText = directTextContent;
30599
+ const isBold = parseInt(computed2.fontWeight) >= 600;
30600
+ const isItalic = computed2.fontStyle === "italic";
30601
+ const whiteSpace = computed2.whiteSpace;
30602
+ const shouldNotWrap = whiteSpace === "nowrap" || whiteSpace === "pre" || rect2.width < 100 || rect2.height < 50;
30603
+ shapeStyle = {
30604
+ fontSize: pxToPoints(computed2.fontSize),
30605
+ fontFace: extractFontFace(computed2.fontFamily),
30606
+ color: rgbToHex(computed2.color),
30607
+ bold: isBold,
30608
+ italic: isItalic,
30609
+ align,
30610
+ valign: valign2,
30611
+ inset: 0,
30612
+ wrap: !shouldNotWrap
30613
+ };
30614
+ const ls = extractLetterSpacing(computed2);
30615
+ if (ls !== null)
30616
+ shapeStyle.charSpacing = ls;
29987
30617
  }
29988
30618
  }
29989
30619
  if (hasBg || hasUniformBorder || bgGradient) {
@@ -30028,7 +30658,8 @@ ${generateStylesCss(styleMap, themeFonts)}
30028
30658
  line: hasUniformBorder ? {
30029
30659
  color: rgbToHex(computed2.borderColor),
30030
30660
  width: pxToPoints(computed2.borderWidth),
30031
- transparency: extractAlpha(computed2.borderColor)
30661
+ transparency: extractAlpha(computed2.borderColor),
30662
+ dashType: extractDashType(computed2.borderStyle)
30032
30663
  } : null,
30033
30664
  rectRadius: (() => {
30034
30665
  if (isEllipse)
@@ -30050,12 +30681,43 @@ ${generateStylesCss(styleMap, themeFonts)}
30050
30681
  shadow,
30051
30682
  opacity: hasOpacity ? elementOpacity : null,
30052
30683
  isEllipse,
30053
- softEdge: softEdgePt
30684
+ softEdge: softEdgePt,
30685
+ customGeometry: clipPathPolygon ? (() => {
30686
+ const EMU2 = 914400;
30687
+ const pathW = Math.round(rect2.width / PX_PER_IN * EMU2);
30688
+ const pathH = Math.round(rect2.height / PX_PER_IN * EMU2);
30689
+ const points = [];
30690
+ clipPathPolygon.forEach((pt, i) => {
30691
+ points.push({
30692
+ x: Math.round(pt.x * pathW),
30693
+ y: Math.round(pt.y * pathH),
30694
+ ...i === 0 ? { moveTo: true } : {}
30695
+ });
30696
+ });
30697
+ points.push({ x: 0, y: 0, close: true });
30698
+ return points;
30699
+ })() : null
30054
30700
  }
30055
30701
  };
30056
30702
  if (hasPadding && shapeElement.style && (shapeText || shapeTextRuns && shapeTextRuns.length > 0)) {
30703
+ let effectiveLeftPadding = paddingLeft;
30704
+ if (hasDirectText && allChildren.length > 0) {
30705
+ for (const child of allChildren) {
30706
+ if (child.nodeType === Node.ELEMENT_NODE) {
30707
+ const childEl = child;
30708
+ const childRect = childEl.getBoundingClientRect();
30709
+ const childComputed = win.getComputedStyle(childEl);
30710
+ const marginRight = parseFloat(childComputed.marginRight) || 0;
30711
+ effectiveLeftPadding += childRect.width + marginRight;
30712
+ }
30713
+ }
30714
+ const gap = parseFloat(computed2.gap) || 0;
30715
+ if (gap > 0 && allChildren.length > 0) {
30716
+ effectiveLeftPadding += gap;
30717
+ }
30718
+ }
30057
30719
  shapeElement.style.margin = [
30058
- paddingLeft * PT_PER_PX,
30720
+ effectiveLeftPadding * PT_PER_PX,
30059
30721
  // left
30060
30722
  paddingRight * PT_PER_PX,
30061
30723
  // right
@@ -30081,6 +30743,11 @@ ${generateStylesCss(styleMap, themeFonts)}
30081
30743
  elements.push(fontFillTextElement);
30082
30744
  } else if (isSingleTextChild) {
30083
30745
  processed.delete(textChildren[0]);
30746
+ } else if (textChildren.length > 0) {
30747
+ textChildren.forEach((tc) => {
30748
+ processed.delete(tc);
30749
+ tc.querySelectorAll("*").forEach((desc) => processed.delete(desc));
30750
+ });
30084
30751
  }
30085
30752
  elements.push(...borderLines);
30086
30753
  processed.add(el);
@@ -30090,6 +30757,7 @@ ${generateStylesCss(styleMap, themeFonts)}
30090
30757
  if (isContainer) {
30091
30758
  const rect2 = htmlEl.getBoundingClientRect();
30092
30759
  if (rect2.width > 0 && rect2.height > 0) {
30760
+ const inlineTextTagsSet = /* @__PURE__ */ new Set(["STRONG", "EM", "B", "I", "A", "BR", "SPAN", "MARK", "SMALL", "SUB", "SUP", "CODE", "U", "S", "Q", "CITE", "ABBR", "TIME", "DATA"]);
30093
30761
  let directText = "";
30094
30762
  for (const child of Array.from(el.childNodes)) {
30095
30763
  if (child.nodeType === Node.TEXT_NODE) {
@@ -30098,33 +30766,107 @@ ${generateStylesCss(styleMap, themeFonts)}
30098
30766
  }
30099
30767
  directText = directText.trim();
30100
30768
  const childElements = Array.from(el.children);
30101
- if (!directText && childElements.length === 1 && childElements[0].tagName === "P" && childElements[0].children.length === 0) {
30769
+ const singlePInlineCheck = /* @__PURE__ */ new Set(["STRONG", "EM", "B", "I", "A", "BR", "SPAN", "MARK", "SMALL", "SUB", "SUP", "CODE", "U", "S"]);
30770
+ if (!directText && childElements.length === 1 && childElements[0].tagName === "P" && (childElements[0].children.length === 0 || Array.from(childElements[0].children).every((ce) => singlePInlineCheck.has(ce.tagName)))) {
30102
30771
  directText = childElements[0].textContent?.trim() || "";
30103
30772
  }
30104
- const hasStructuralChildren = childElements.length > 0 && !childElements.every((ce) => ce.tagName === "BR") && !(childElements.length === 1 && childElements[0].tagName === "P" && childElements[0].children.length === 0);
30105
- if (directText && !hasStructuralChildren) {
30773
+ if (directText) {
30774
+ const directTextComputed = win.getComputedStyle(el);
30775
+ if (directTextComputed.textTransform && directTextComputed.textTransform !== "none") {
30776
+ directText = applyTextTransform(directText, directTextComputed.textTransform);
30777
+ }
30778
+ }
30779
+ const plainDivComputed = win.getComputedStyle(el);
30780
+ const plainDivDisplay = plainDivComputed.display;
30781
+ const plainDivFlexDir = plainDivComputed.flexDirection || "row";
30782
+ const plainDivChildCount = el.children.length;
30783
+ const isPlainDivFlexRow = (plainDivDisplay === "flex" || plainDivDisplay === "inline-flex") && (plainDivFlexDir === "row" || plainDivFlexDir === "row-reverse") && plainDivChildCount > 1;
30784
+ const isPlainDivGrid = plainDivDisplay === "grid" || plainDivDisplay === "inline-grid";
30785
+ const allChildrenAreInlineText = !isPlainDivFlexRow && !isPlainDivGrid && (childElements.length === 0 || childElements.every((ce) => inlineTextTagsSet.has(ce.tagName.toUpperCase())));
30786
+ const structuralChildElements = childElements.filter((ce) => {
30787
+ const tagName = ce.tagName.toUpperCase();
30788
+ return tagName !== "BR" && tagName !== "SVG" && !inlineTextTagsSet.has(tagName);
30789
+ });
30790
+ const hasStructuralChildren = structuralChildElements.length > 0;
30791
+ let extractedText = "";
30792
+ if (allChildrenAreInlineText) {
30793
+ extractedText = el.textContent?.trim() || "";
30794
+ const inlineTextComputed = win.getComputedStyle(el);
30795
+ if (extractedText && inlineTextComputed.textTransform && inlineTextComputed.textTransform !== "none") {
30796
+ extractedText = applyTextTransform(extractedText, inlineTextComputed.textTransform);
30797
+ }
30798
+ } else if (directText) {
30799
+ extractedText = directText;
30800
+ }
30801
+ if (extractedText && !hasStructuralChildren) {
30106
30802
  const computed22 = win.getComputedStyle(el);
30107
30803
  const fontSizePx2 = parseFloat(computed22.fontSize);
30108
30804
  const lineHeightPx2 = parseFloat(computed22.lineHeight);
30109
30805
  const lineHeightMultiplier2 = fontSizePx2 > 0 && !isNaN(lineHeightPx2) ? lineHeightPx2 / fontSizePx2 : 1;
30806
+ let textLeft = rect2.left;
30807
+ let textWidth = rect2.width;
30808
+ if (directText && !allChildrenAreInlineText) {
30809
+ for (const child of Array.from(el.childNodes)) {
30810
+ if (child.nodeType === Node.TEXT_NODE && (child.textContent || "").trim()) {
30811
+ const range2 = document.createRange();
30812
+ range2.selectNodeContents(child);
30813
+ const textRect = range2.getBoundingClientRect();
30814
+ if (textRect.width > 0) {
30815
+ textLeft = textRect.left;
30816
+ textWidth = textRect.width;
30817
+ }
30818
+ break;
30819
+ }
30820
+ }
30821
+ }
30822
+ const textTop = rect2.top;
30823
+ const textHeight = rect2.height;
30824
+ let textRuns;
30825
+ if (allChildrenAreInlineText && childElements.length > 0) {
30826
+ const baseRunOptions = {
30827
+ fontSize: pxToPoints(computed22.fontSize),
30828
+ fontFace: extractFontFace(computed22.fontFamily),
30829
+ color: rgbToHex(computed22.color)
30830
+ };
30831
+ if (parseInt(computed22.fontWeight) >= 600)
30832
+ baseRunOptions.bold = true;
30833
+ if (computed22.fontStyle === "italic")
30834
+ baseRunOptions.italic = true;
30835
+ let textTransformFn = (s) => s;
30836
+ if (computed22.textTransform && computed22.textTransform !== "none") {
30837
+ textTransformFn = (text2) => applyTextTransform(text2, computed22.textTransform);
30838
+ }
30839
+ textRuns = parseInlineFormatting(el, baseRunOptions, [], textTransformFn, win);
30840
+ if (textRuns.length === 0) {
30841
+ textRuns = [{ text: extractedText, options: {} }];
30842
+ }
30843
+ } else {
30844
+ textRuns = [{ text: extractedText, options: {} }];
30845
+ }
30846
+ const paddingTop = parseFloat(computed22.paddingTop) || 0;
30847
+ const paddingRight = parseFloat(computed22.paddingRight) || 0;
30848
+ const paddingBottom = parseFloat(computed22.paddingBottom) || 0;
30849
+ const paddingLeft = parseFloat(computed22.paddingLeft) || 0;
30850
+ const hasPadding = paddingTop > 0 || paddingRight > 0 || paddingBottom > 0 || paddingLeft > 0;
30110
30851
  const textElement = {
30111
30852
  type: "p",
30112
- text: [{ text: directText, options: {} }],
30853
+ text: textRuns,
30113
30854
  position: {
30114
- x: pxToInch(rect2.left),
30115
- y: pxToInch(rect2.top),
30116
- w: pxToInch(rect2.width),
30117
- h: pxToInch(rect2.height)
30855
+ x: pxToInch(textLeft),
30856
+ y: pxToInch(textTop),
30857
+ w: pxToInch(textWidth),
30858
+ h: pxToInch(textHeight)
30118
30859
  },
30119
30860
  style: {
30120
30861
  fontSize: pxToPoints(computed22.fontSize),
30121
- fontFace: computed22.fontFamily.split(",")[0].replace(/['"]/g, "").trim(),
30862
+ fontFace: extractFontFace(computed22.fontFamily),
30122
30863
  color: rgbToHex(computed22.color),
30123
30864
  bold: parseInt(computed22.fontWeight) >= 600,
30124
30865
  italic: computed22.fontStyle === "italic",
30125
30866
  align: computed22.textAlign === "center" ? "center" : computed22.textAlign === "right" || computed22.textAlign === "end" ? "right" : "left",
30126
- valign: "middle",
30127
- lineSpacing: lineHeightMultiplier2 * pxToPoints(computed22.fontSize)
30867
+ valign: hasPadding ? "top" : "middle",
30868
+ lineSpacing: lineHeightMultiplier2 * pxToPoints(computed22.fontSize),
30869
+ margin: hasPadding ? [paddingLeft * PT_PER_PX, paddingRight * PT_PER_PX, paddingBottom * PT_PER_PX, paddingTop * PT_PER_PX] : void 0
30128
30870
  }
30129
30871
  };
30130
30872
  const textTransparency = extractAlpha(computed22.color);
@@ -30136,7 +30878,11 @@ ${generateStylesCss(styleMap, themeFonts)}
30136
30878
  textElement.style.charSpacing = ls;
30137
30879
  elements.push(textElement);
30138
30880
  processed.add(el);
30139
- el.querySelectorAll("*").forEach((desc) => processed.add(desc));
30881
+ el.querySelectorAll("*").forEach((desc) => {
30882
+ if (desc.tagName.toUpperCase() !== "SVG") {
30883
+ processed.add(desc);
30884
+ }
30885
+ });
30140
30886
  return;
30141
30887
  }
30142
30888
  }
@@ -30190,7 +30936,7 @@ ${generateStylesCss(styleMap, themeFonts)}
30190
30936
  },
30191
30937
  style: {
30192
30938
  fontSize: pxToPoints(liComputed.fontSize),
30193
- fontFace: liComputed.fontFamily.split(",")[0].replace(/['"]/g, "").trim(),
30939
+ fontFace: extractFontFace(liComputed.fontFamily),
30194
30940
  color: rgbToHex(liComputed.color),
30195
30941
  transparency: extractAlpha(liComputed.color),
30196
30942
  align: liComputed.textAlign === "start" ? "left" : liComputed.textAlign,
@@ -30208,10 +30954,10 @@ ${generateStylesCss(styleMap, themeFonts)}
30208
30954
  if (!textTags.includes(el.tagName) || el.tagName === "SPAN")
30209
30955
  return;
30210
30956
  let rect = htmlEl.getBoundingClientRect();
30211
- let text = el.textContent.trim();
30957
+ const computed = win.getComputedStyle(el);
30958
+ let text = getTransformedText(htmlEl, computed);
30212
30959
  if (rect.width === 0 || rect.height === 0 || !text)
30213
30960
  return;
30214
- const computed = win.getComputedStyle(el);
30215
30961
  const isFlexContainer = computed.display === "flex" || computed.display === "inline-flex";
30216
30962
  if (isFlexContainer && el.children.length > 0) {
30217
30963
  const textNodes = [];
@@ -30227,7 +30973,20 @@ ${generateStylesCss(styleMap, themeFonts)}
30227
30973
  const textRect = range2.getBoundingClientRect();
30228
30974
  if (textRect.width > 0 && textRect.height > 0) {
30229
30975
  rect = textRect;
30230
- text = textNodes.map((n) => n.textContent.trim()).join(" ").trim();
30976
+ const rawText = textNodes.map((n) => n.textContent.trim()).join(" ").trim();
30977
+ text = applyTextTransform(rawText, computed.textTransform || "none");
30978
+ }
30979
+ } else {
30980
+ for (const child of Array.from(el.children)) {
30981
+ if (child.tagName === "SPAN" && child.textContent?.trim()) {
30982
+ const childRect = child.getBoundingClientRect();
30983
+ if (childRect.width > 0 && childRect.height > 0) {
30984
+ rect = childRect;
30985
+ const childComputed = win.getComputedStyle(child);
30986
+ text = getTransformedText(child, childComputed);
30987
+ break;
30988
+ }
30989
+ }
30231
30990
  }
30232
30991
  }
30233
30992
  }
@@ -30268,7 +31027,7 @@ ${generateStylesCss(styleMap, themeFonts)}
30268
31027
  }
30269
31028
  const baseStyle = {
30270
31029
  fontSize: pxToPoints(computed.fontSize),
30271
- fontFace: computed.fontFamily.split(",")[0].replace(/['"]/g, "").trim(),
31030
+ fontFace: extractFontFace(computed.fontFamily),
30272
31031
  color: rgbToHex(computed.color),
30273
31032
  align: textAlign,
30274
31033
  valign,
@@ -30396,23 +31155,44 @@ ${generateStylesCss(styleMap, themeFonts)}
30396
31155
  })
30397
31156
  };
30398
31157
  }
30399
- async function fetchImageAsDataUrl(url) {
30400
- try {
30401
- const response = await fetch(url, { mode: "cors" });
30402
- if (!response.ok) {
30403
- throw new Error(`Failed to fetch image: ${response.status}`);
30404
- }
30405
- const blob = await response.blob();
30406
- return new Promise((resolve, reject) => {
30407
- const reader = new FileReader();
30408
- reader.onloadend = () => resolve(reader.result);
30409
- reader.onerror = reject;
30410
- reader.readAsDataURL(blob);
30411
- });
30412
- } catch (error) {
30413
- const message = error instanceof Error ? error.message : String(error);
30414
- throw new Error(`Failed to fetch image ${url}: ${message}`);
31158
+ function sleep(ms) {
31159
+ return new Promise((resolve) => setTimeout(resolve, ms));
31160
+ }
31161
+ async function fetchImageAsDataUrl(url, maxRetries = 3, initialDelayMs = 500) {
31162
+ let lastError = null;
31163
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
31164
+ try {
31165
+ const response = await fetch(url, { mode: "cors" });
31166
+ if (response.status === 429 || response.status >= 500 && response.status < 600) {
31167
+ if (attempt < maxRetries) {
31168
+ const delay = initialDelayMs * Math.pow(2, attempt);
31169
+ console.warn(`Image fetch returned ${response.status}, retrying in ${delay}ms... (${url})`);
31170
+ await sleep(delay);
31171
+ continue;
31172
+ }
31173
+ }
31174
+ if (!response.ok) {
31175
+ throw new Error(`Failed to fetch image: ${response.status}`);
31176
+ }
31177
+ const blob = await response.blob();
31178
+ return new Promise((resolve, reject) => {
31179
+ const reader = new FileReader();
31180
+ reader.onloadend = () => resolve(reader.result);
31181
+ reader.onerror = reject;
31182
+ reader.readAsDataURL(blob);
31183
+ });
31184
+ } catch (error) {
31185
+ lastError = error instanceof Error ? error : new Error(String(error));
31186
+ if (attempt < maxRetries) {
31187
+ const delay = initialDelayMs * Math.pow(2, attempt);
31188
+ console.warn(`Image fetch failed, retrying in ${delay}ms... (${url}): ${lastError.message}`);
31189
+ await sleep(delay);
31190
+ continue;
31191
+ }
31192
+ }
30415
31193
  }
31194
+ const message = lastError?.message ?? "Unknown error";
31195
+ throw new Error(`Failed to fetch image ${url}: ${message}`);
30416
31196
  }
30417
31197
  async function applyBackground(background, slide) {
30418
31198
  if (background.type === "image" && background.path) {
@@ -30556,7 +31336,11 @@ ${generateStylesCss(styleMap, themeFonts)}
30556
31336
  });
30557
31337
  } else if (el.type === "shape") {
30558
31338
  let shapeType = pres.ShapeType.rect;
30559
- if (el.shape.isEllipse) {
31339
+ let customGeometryPoints;
31340
+ if (el.shape.customGeometry && el.shape.customGeometry.length > 0) {
31341
+ shapeType = pres.ShapeType.custGeom;
31342
+ customGeometryPoints = el.shape.customGeometry;
31343
+ } else if (el.shape.isEllipse) {
30560
31344
  shapeType = pres.ShapeType.ellipse;
30561
31345
  } else if (el.shape.rectRadius > 0) {
30562
31346
  shapeType = pres.ShapeType.roundRect;
@@ -30568,8 +31352,11 @@ ${generateStylesCss(styleMap, themeFonts)}
30568
31352
  const hasText = el.text || el.textRuns && el.textRuns.length > 0;
30569
31353
  let adjustedX = el.position.x;
30570
31354
  let adjustedW = el.position.w;
31355
+ let adjustedH = el.position.h;
30571
31356
  if (isSingleLine && hasText) {
30572
- const widthIncrease = el.position.w * 0.02;
31357
+ const fontSize = el.style?.fontSize ?? 12;
31358
+ const bufferPercent = fontSize > 36 ? 0.05 : fontSize > 24 ? 0.04 : fontSize > 16 ? 0.03 : 0.02;
31359
+ const widthIncrease = el.position.w * bufferPercent;
30573
31360
  const align = el.style?.align;
30574
31361
  if (align === "center") {
30575
31362
  adjustedX = el.position.x - widthIncrease / 2;
@@ -30580,16 +31367,25 @@ ${generateStylesCss(styleMap, themeFonts)}
30580
31367
  } else {
30581
31368
  adjustedW = el.position.w + widthIncrease;
30582
31369
  }
31370
+ } else if (hasText && !isSingleLine) {
31371
+ const hasInternalPadding = el.style?.margin && el.style.margin.some((m) => m > 3);
31372
+ const heightBufferPercent = hasInternalPadding ? 0.03 : 0.1;
31373
+ const heightIncrease = el.position.h * heightBufferPercent;
31374
+ adjustedH = el.position.h + heightIncrease;
30583
31375
  }
30584
31376
  const shapeOptions = {
30585
31377
  x: adjustedX,
30586
31378
  y: el.position.y,
30587
31379
  w: adjustedW,
30588
- h: el.position.h,
31380
+ h: adjustedH,
30589
31381
  shape: shapeType,
30590
31382
  // Disable text wrapping for single-line shapes to prevent unwanted line breaks
30591
- wrap: !isSingleLine
31383
+ // Also respect explicit wrap: false from parse.ts (for small badges/labels)
31384
+ wrap: el.style?.wrap === false ? false : !isSingleLine
30592
31385
  };
31386
+ if (customGeometryPoints) {
31387
+ shapeOptions.points = customGeometryPoints;
31388
+ }
30593
31389
  let fillTransparency = el.shape.transparency;
30594
31390
  const elementOpacity = el.shape.opacity !== null && el.shape.opacity < 1 ? el.shape.opacity : 1;
30595
31391
  if (elementOpacity < 1) {
@@ -30620,6 +31416,8 @@ ${generateStylesCss(styleMap, themeFonts)}
30620
31416
  };
30621
31417
  if (el.shape.line.transparency != null)
30622
31418
  lineOpts.transparency = el.shape.line.transparency;
31419
+ if (el.shape.line.dashType)
31420
+ lineOpts.dashType = el.shape.line.dashType;
30623
31421
  shapeOptions.line = lineOpts;
30624
31422
  }
30625
31423
  if (el.shape.rectRadius > 0)
@@ -30643,6 +31441,7 @@ ${generateStylesCss(styleMap, themeFonts)}
30643
31441
  shapeOptions.valign = el.style.valign;
30644
31442
  if (el.style.fontFill)
30645
31443
  shapeOptions.fontFill = el.style.fontFill;
31444
+ shapeOptions.inset = 0;
30646
31445
  if (el.style.margin)
30647
31446
  shapeOptions.margin = el.style.margin;
30648
31447
  if (el.style.inset !== void 0)
@@ -30666,11 +31465,13 @@ ${generateStylesCss(styleMap, themeFonts)}
30666
31465
  slide.addText(el.text || "", shapeOptions);
30667
31466
  }
30668
31467
  } else if (el.type === "list") {
31468
+ const heightIncrease = el.position.h * 0.15;
31469
+ const adjustedH = el.position.h + heightIncrease;
30669
31470
  const listOptions = {
30670
31471
  x: el.position.x,
30671
31472
  y: el.position.y,
30672
31473
  w: el.position.w,
30673
- h: el.position.h,
31474
+ h: adjustedH,
30674
31475
  fontSize: el.style.fontSize,
30675
31476
  fontFace: el.style.fontFace,
30676
31477
  color: el.style.color ?? void 0,
@@ -30679,7 +31480,8 @@ ${generateStylesCss(styleMap, themeFonts)}
30679
31480
  lineSpacing: el.style.lineSpacing,
30680
31481
  paraSpaceBefore: el.style.paraSpaceBefore,
30681
31482
  paraSpaceAfter: el.style.paraSpaceAfter,
30682
- margin: el.style.margin
31483
+ margin: el.style.margin,
31484
+ inset: 0
30683
31485
  };
30684
31486
  if (el.style.margin)
30685
31487
  listOptions.margin = el.style.margin;
@@ -30690,7 +31492,22 @@ ${generateStylesCss(styleMap, themeFonts)}
30690
31492
  const isSingleLine = heightPt <= lineHeightPt * 1.5;
30691
31493
  let adjustedX = el.position.x;
30692
31494
  let adjustedW = el.position.w;
31495
+ let adjustedH = el.position.h;
30693
31496
  if (isSingleLine) {
31497
+ const fontSize = el.style.fontSize ?? 12;
31498
+ const bufferPercent = fontSize > 36 ? 0.02 : fontSize > 24 ? 0.015 : 0.01;
31499
+ const widthIncrease = el.position.w * bufferPercent;
31500
+ const align = el.style.align;
31501
+ if (align === "center") {
31502
+ adjustedX = el.position.x - widthIncrease / 2;
31503
+ adjustedW = el.position.w + widthIncrease;
31504
+ } else if (align === "right") {
31505
+ adjustedX = el.position.x - widthIncrease;
31506
+ adjustedW = el.position.w + widthIncrease;
31507
+ } else {
31508
+ adjustedW = el.position.w + widthIncrease;
31509
+ }
31510
+ } else {
30694
31511
  const widthIncrease = el.position.w * 0.02;
30695
31512
  const align = el.style.align;
30696
31513
  if (align === "center") {
@@ -30702,12 +31519,14 @@ ${generateStylesCss(styleMap, themeFonts)}
30702
31519
  } else {
30703
31520
  adjustedW = el.position.w + widthIncrease;
30704
31521
  }
31522
+ const heightIncrease = el.position.h * 0.05;
31523
+ adjustedH = el.position.h + heightIncrease;
30705
31524
  }
30706
31525
  const textOptions = {
30707
31526
  x: adjustedX,
30708
31527
  y: el.position.y,
30709
31528
  w: adjustedW,
30710
- h: el.position.h,
31529
+ h: adjustedH,
30711
31530
  fontSize: el.style.fontSize,
30712
31531
  fontFace: el.style.fontFace,
30713
31532
  color: el.style.color ?? void 0,
@@ -30850,9 +31669,17 @@ ${generateStylesCss(styleMap, themeFonts)}
30850
31669
  resolve();
30851
31670
  };
30852
31671
  iframe.onload = resolveOnce;
30853
- setTimeout(resolveOnce, 500);
31672
+ setTimeout(resolveOnce, 2e3);
30854
31673
  });
30855
31674
  const doc = iframe.contentDocument || iframe.contentWindow.document;
31675
+ const iframeWin = iframe.contentWindow;
31676
+ if (iframeWin && iframeWin.document.fonts && iframeWin.document.fonts.ready) {
31677
+ await Promise.race([
31678
+ iframeWin.document.fonts.ready,
31679
+ new Promise((r) => setTimeout(r, 3e3))
31680
+ // 3s timeout for slow CDN
31681
+ ]);
31682
+ }
30856
31683
  return { iframe, doc };
30857
31684
  }
30858
31685
  async function addSlideFromHtml(source, pres, options = {}) {
@@ -30871,6 +31698,10 @@ ${generateStylesCss(styleMap, themeFonts)}
30871
31698
  } else {
30872
31699
  throw new Error("Source must be a Document, HTMLIFrameElement, or HTML string");
30873
31700
  }
31701
+ const win = doc.defaultView;
31702
+ if (win && win.document.fonts && win.document.fonts.ready) {
31703
+ await win.document.fonts.ready;
31704
+ }
30874
31705
  const validationErrors = [];
30875
31706
  const bodyDimensions = getBodyDimensions(doc);
30876
31707
  const slideData = parseSlideHtml(doc);
@@ -30928,7 +31759,7 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
30928
31759
  }
30929
31760
 
30930
31761
  // packages/slides/transform.js
30931
- var FONT_FAMILY_MAP = {
31762
+ var FONT_FAMILY_MAP2 = {
30932
31763
  // Serif fonts → Georgia (closest web-safe serif)
30933
31764
  "Playfair Display": "Georgia, serif",
30934
31765
  "Merriweather": "Georgia, serif",
@@ -30937,7 +31768,8 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
30937
31768
  "Noto Serif": "Georgia, serif",
30938
31769
  "Lora": "Georgia, serif",
30939
31770
  // Sans-serif fonts → Calibri (modern sans-serif, widely available in PowerPoint)
30940
- "Inter": "Calibri, sans-serif",
31771
+ // Note: fonts loaded via Google Fonts CDN are NOT mapped here, as they will
31772
+ // be available at render time and should keep their original names in the PPTX.
30941
31773
  "Roboto": "Calibri, sans-serif",
30942
31774
  "Open Sans": "Calibri, sans-serif",
30943
31775
  "Lato": "Calibri, sans-serif",
@@ -30974,7 +31806,7 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
30974
31806
  const scaleFactor = targetWidth / sourceWidth;
30975
31807
  html = html.replace(/(body\s*\{[^}]*?)width:\s*\d+px/s, `$1width: ${targetWidth}pt`);
30976
31808
  html = html.replace(/(body\s*\{[^}]*?)height:\s*\d+px/s, `$1height: ${targetHeight}pt`);
30977
- for (const [font, replacement] of Object.entries(FONT_FAMILY_MAP)) {
31809
+ for (const [font, replacement] of Object.entries(FONT_FAMILY_MAP2)) {
30978
31810
  const fontRegex = new RegExp(`font-family:\\s*["']?${font}["']?,?\\s*(sans-serif|serif|monospace)?`, "gi");
30979
31811
  html = html.replace(fontRegex, `font-family: ${replacement}`);
30980
31812
  }
@@ -31012,7 +31844,43 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31012
31844
  });
31013
31845
  html = html.replace(/-webkit-backdrop-filter:\s*[^;]+;/g, "");
31014
31846
  html = html.replace(/backdrop-filter:\s*[^;]+;/g, "");
31015
- html = html.replace(/animation[\w-]*:\s*[^;]+;/g, "");
31847
+ const keyframeFinalTransforms = {};
31848
+ {
31849
+ const kfPattern = /@keyframes\s+([\w-]+)\s*\{/g;
31850
+ let kfScan;
31851
+ while ((kfScan = kfPattern.exec(html)) !== null) {
31852
+ const name = kfScan[1];
31853
+ const bodyStart = kfScan.index + kfScan[0].length;
31854
+ let depth = 1;
31855
+ let idx = bodyStart;
31856
+ while (idx < html.length && depth > 0) {
31857
+ if (html[idx] === "{")
31858
+ depth++;
31859
+ else if (html[idx] === "}")
31860
+ depth--;
31861
+ idx++;
31862
+ }
31863
+ const kfBody = html.substring(bodyStart, idx - 1);
31864
+ const finalBlock = kfBody.match(/(?:to|100%)\s*\{\s*([^}]*)\}/);
31865
+ if (finalBlock) {
31866
+ const transformInFinal = finalBlock[1].match(/transform:\s*([^;]+)/);
31867
+ if (transformInFinal) {
31868
+ keyframeFinalTransforms[name] = transformInFinal[1].trim();
31869
+ }
31870
+ }
31871
+ }
31872
+ }
31873
+ html = html.replace(/(?<![a-zA-Z-])animation:\s*([^;]+);/g, (_match, value) => {
31874
+ if (/forwards/.test(value)) {
31875
+ for (const [name, finalTransform] of Object.entries(keyframeFinalTransforms)) {
31876
+ if (value.includes(name)) {
31877
+ return `transform: ${finalTransform} !important;`;
31878
+ }
31879
+ }
31880
+ }
31881
+ return "";
31882
+ });
31883
+ html = html.replace(/animation-[\w-]+:\s*[^;]+;/g, "");
31016
31884
  let keyframeMatch;
31017
31885
  while (keyframeMatch = html.match(/@keyframes\s+[\w-]+\s*\{/)) {
31018
31886
  const start = keyframeMatch.index;
@@ -31082,6 +31950,109 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31082
31950
  var TARGET_WIDTH = 1280;
31083
31951
  var TARGET_HEIGHT = 720;
31084
31952
  var EMU_PER_PX2 = 914400 / 96;
31953
+ var FONT_FALLBACK_MAP = {
31954
+ // Microsoft Office fonts → metrically compatible open-source alternatives
31955
+ "Calibri": "'Calibri','Carlito','Helvetica Neue',Helvetica,Arial,sans-serif",
31956
+ "Calibri Light": "'Calibri Light','Carlito','Helvetica Neue Light','Helvetica Neue',Arial,sans-serif",
31957
+ "Cambria": "'Cambria','Caladea','Times New Roman',Georgia,serif",
31958
+ "Cambria Math": "'Cambria Math','Caladea','Times New Roman',serif",
31959
+ "Consolas": "'Consolas','Courier New',monospace",
31960
+ "Aptos": "'Aptos','Carlito','Helvetica Neue',Arial,sans-serif",
31961
+ "Times New Roman": "'Times New Roman','Liberation Serif',Georgia,serif",
31962
+ // Handwriting / decorative fonts
31963
+ "MV Boli": "'MV Boli','Comic Sans MS','Marker Felt',cursive",
31964
+ "Kristen ITC": "'Kristen ITC','Comic Sans MS','Marker Felt',cursive",
31965
+ "Stylus BT": "'Stylus BT','Brush Script MT','Snell Roundhand',cursive",
31966
+ // Japanese sans-serif fonts → Noto Sans CJK JP (commonly installed)
31967
+ "MS Gothic": "'MS Gothic','Noto Sans CJK JP','Hiragino Kaku Gothic ProN','Yu Gothic',sans-serif",
31968
+ "MS PGothic": "'MS PGothic','Noto Sans CJK JP','Hiragino Kaku Gothic ProN','Yu Gothic',sans-serif",
31969
+ "\uFF2D\uFF33 \uFF30\u30B4\u30B7\u30C3\u30AF": "'\uFF2D\uFF33 \uFF30\u30B4\u30B7\u30C3\u30AF','Noto Sans CJK JP','Hiragino Kaku Gothic ProN','Yu Gothic',sans-serif",
31970
+ "\uFF2D\uFF33 \u30B4\u30B7\u30C3\u30AF": "'\uFF2D\uFF33 \u30B4\u30B7\u30C3\u30AF','Noto Sans CJK JP','Hiragino Kaku Gothic ProN','Yu Gothic',sans-serif",
31971
+ "Kozuka Gothic Pro R": "'Kozuka Gothic Pro R','Noto Sans CJK JP','Hiragino Kaku Gothic ProN',sans-serif",
31972
+ "Kozuka Gothic Pro B": "'Kozuka Gothic Pro B','Noto Sans CJK JP Bold','Hiragino Kaku Gothic ProN',sans-serif",
31973
+ "Kozuka Gothic Pro M": "'Kozuka Gothic Pro M','Noto Sans CJK JP Medium','Hiragino Kaku Gothic ProN',sans-serif",
31974
+ "Kozuka Gothic Pro EL": "'Kozuka Gothic Pro EL','Noto Sans CJK JP Light','Hiragino Kaku Gothic ProN',sans-serif",
31975
+ "HGSKyokashotai": "'HGSKyokashotai','Noto Sans CJK JP','Hiragino Kaku Gothic ProN',sans-serif",
31976
+ // Japanese serif fonts → Noto Serif CJK JP
31977
+ "MS Mincho": "'MS Mincho','Noto Serif CJK JP','Hiragino Mincho ProN','Yu Mincho',serif",
31978
+ "MS PMincho": "'MS PMincho','Noto Serif CJK JP','Hiragino Mincho ProN','Yu Mincho',serif",
31979
+ // CJK fonts
31980
+ "\u5B8B\u4F53": "'\u5B8B\u4F53','Noto Serif CJK SC','Songti SC','STSong',serif",
31981
+ "\u65B0\u7D30\u660E\u9AD4": "'\u65B0\u7D30\u660E\u9AD4','Noto Serif CJK TC','Songti TC',serif",
31982
+ "\u9ED1\u4F53": "'\u9ED1\u4F53','Noto Sans CJK SC','Heiti SC','STHeiti',sans-serif",
31983
+ "\u5FAE\u8EDF\u6B63\u9ED1\u9AD4": "'\u5FAE\u8EDF\u6B63\u9ED1\u9AD4','Noto Sans CJK TC','Heiti TC',sans-serif",
31984
+ "\uB9D1\uC740 \uACE0\uB515": "'\uB9D1\uC740 \uACE0\uB515','Apple SD Gothic Neo',sans-serif",
31985
+ // UI / system fonts
31986
+ "Lucida Sans Unicode": "'Lucida Sans Unicode','Lucida Grande','Lucida Sans',sans-serif",
31987
+ "Segoe UI": "'Segoe UI','-apple-system','Helvetica Neue',sans-serif",
31988
+ "Segoe Sans Display": "'Segoe Sans Display','-apple-system','Helvetica Neue',Arial,sans-serif",
31989
+ "Segoe Sans Display Semibold": "'Segoe Sans Display Semibold','-apple-system','Helvetica Neue',Arial,sans-serif",
31990
+ // Google Fonts / web fonts commonly used in presentations
31991
+ "Inter": "'Inter','Helvetica Neue',Helvetica,Arial,sans-serif",
31992
+ "Inter Light": "'Inter Light','Inter','Helvetica Neue',Arial,sans-serif",
31993
+ "Inter ExtraBold": "'Inter ExtraBold','Inter','Helvetica Neue',Arial,sans-serif",
31994
+ "Space Grotesk": "'Space Grotesk','Helvetica Neue',Helvetica,Arial,sans-serif",
31995
+ "Montserrat": "'Montserrat','Helvetica Neue',Helvetica,Arial,sans-serif",
31996
+ "Open Sans": "'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif",
31997
+ "Lato": "'Lato','Helvetica Neue',Helvetica,Arial,sans-serif",
31998
+ "Oswald": "'Oswald','Helvetica Neue',Helvetica,Arial,sans-serif",
31999
+ "Archivo": "'Archivo','Helvetica Neue',Helvetica,Arial,sans-serif",
32000
+ "Economica": "'Economica','Helvetica Neue',Arial,sans-serif",
32001
+ "Libre Baskerville": "'Libre Baskerville','Georgia','Times New Roman',serif",
32002
+ // GitHub Copilot fonts
32003
+ "Ginto Copilot": "'Ginto Copilot','Helvetica Neue',Helvetica,Arial,sans-serif",
32004
+ "Ginto Copilot Light": "'Ginto Copilot Light','Helvetica Neue Light','Helvetica Neue',Arial,sans-serif",
32005
+ "Ginto Copilot 400": "'Ginto Copilot 400','Helvetica Neue',Helvetica,Arial,sans-serif",
32006
+ // Google Fonts / decorative (serif & display)
32007
+ "Playfair Display": "'Playfair Display','Georgia','Times New Roman',serif",
32008
+ "Playfair Display SemiBold": "'Playfair Display SemiBold','Playfair Display','Georgia',serif",
32009
+ "Caveat": "'Caveat','Comic Sans MS','Marker Felt',cursive",
32010
+ "Syncopate": "'Syncopate','Helvetica Neue',Helvetica,Arial,sans-serif",
32011
+ // Montserrat weight variants
32012
+ "Montserrat SemiBold": "'Montserrat SemiBold','Montserrat','Helvetica Neue',Arial,sans-serif",
32013
+ "Montserrat ExtraBold": "'Montserrat ExtraBold','Montserrat','Helvetica Neue',Arial,sans-serif",
32014
+ // GitHub Copilot font variants
32015
+ "Ginto Copilot Medium": "'Ginto Copilot Medium','Ginto Copilot','Helvetica Neue',Arial,sans-serif",
32016
+ "Ginto Copilot Black": "'Ginto Copilot Black','Ginto Copilot','Helvetica Neue',Arial,sans-serif",
32017
+ "Ginto Copilot Thin": "'Ginto Copilot Thin','Ginto Copilot Light','Helvetica Neue',Arial,sans-serif",
32018
+ // Microsoft UI fonts
32019
+ "Segoe Sans Small Regular": "'Segoe Sans Small Regular','Segoe UI','-apple-system','Helvetica Neue',sans-serif",
32020
+ "Segoe Sans Text Regular": "'Segoe Sans Text Regular','Segoe UI','-apple-system','Helvetica Neue',sans-serif",
32021
+ "Grandview": "'Grandview','Helvetica Neue',Arial,sans-serif",
32022
+ "Nirmala UI": "'Nirmala UI','Helvetica Neue',Arial,sans-serif",
32023
+ "Ebrima": "'Ebrima','Helvetica Neue',Arial,sans-serif",
32024
+ // Common system fonts with fallbacks
32025
+ "Arial": "Arial,'Helvetica Neue',Helvetica,sans-serif",
32026
+ "Arial Black": "'Arial Black','Helvetica Neue',Helvetica,Arial,sans-serif",
32027
+ "Georgia": "Georgia,'Times New Roman',serif",
32028
+ "Georgia Regular": "'Georgia Regular',Georgia,'Times New Roman',serif",
32029
+ "Courier New": "'Courier New',Courier,monospace",
32030
+ "Times": "Times,'Times New Roman',serif",
32031
+ // Symbol fonts (preserved as-is with system fallback)
32032
+ "Wingdings": "'Wingdings','Zapf Dingbats',sans-serif",
32033
+ "Wingdings 2": "'Wingdings 2','Zapf Dingbats',sans-serif",
32034
+ "Wingdings 3": "'Wingdings 3','Zapf Dingbats',sans-serif",
32035
+ // Common fallbacks
32036
+ "Tahoma": "'Tahoma','Verdana','Geneva',sans-serif",
32037
+ "Verdana": "'Verdana','Geneva',sans-serif",
32038
+ "Helvetica Neue Light": "'Helvetica Neue Light','Helvetica Neue',Helvetica,Arial,sans-serif"
32039
+ };
32040
+ function cssFontFamily(fontName) {
32041
+ const mapped = FONT_FALLBACK_MAP[fontName];
32042
+ if (mapped)
32043
+ return mapped;
32044
+ if (fontName.includes(",")) {
32045
+ return fontName.split(",").map((f) => {
32046
+ const trimmed = f.trim();
32047
+ const lower = trimmed.toLowerCase();
32048
+ if (lower === "sans-serif" || lower === "serif" || lower === "monospace" || lower === "cursive" || lower === "fantasy") {
32049
+ return lower;
32050
+ }
32051
+ return `'${trimmed}'`;
32052
+ }).join(",");
32053
+ }
32054
+ return `'${fontName}',sans-serif`;
32055
+ }
31085
32056
  function emuToPx2(emu) {
31086
32057
  return emu / EMU_PER_PX2;
31087
32058
  }
@@ -31160,6 +32131,20 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31160
32131
  }
31161
32132
  if (stops.length === 0)
31162
32133
  return void 0;
32134
+ const pathEl = findChild2(gradFill, "path");
32135
+ if (pathEl && pathEl.getAttribute("path") === "circle") {
32136
+ const fillToRect = findChild2(pathEl, "fillToRect");
32137
+ let centerX = 50;
32138
+ let centerY = 50;
32139
+ if (fillToRect) {
32140
+ const l = parseInt(fillToRect.getAttribute("l") ?? "50000", 10) / 1e3;
32141
+ const t = parseInt(fillToRect.getAttribute("t") ?? "50000", 10) / 1e3;
32142
+ centerX = l;
32143
+ centerY = t;
32144
+ }
32145
+ const stopStr2 = stops.map((s) => `${s.color} ${s.pos}%`).join(",");
32146
+ return `radial-gradient(ellipse at ${centerX}% ${centerY}%,${stopStr2})`;
32147
+ }
31163
32148
  const lin = findChild2(gradFill, "lin");
31164
32149
  const angAttr = lin?.getAttribute("ang");
31165
32150
  const cssDeg = angAttr ? ooxmlAngleToCss(parseInt(angAttr, 10)) : 180;
@@ -31191,12 +32176,71 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31191
32176
  if (color)
31192
32177
  result.color = color;
31193
32178
  }
32179
+ if (!result.color) {
32180
+ const gradFill = findChild2(rPr, "gradFill");
32181
+ if (gradFill) {
32182
+ const gsLst = findChild2(gradFill, "gsLst");
32183
+ if (gsLst) {
32184
+ const gsEls = findChildren2(gsLst, "gs");
32185
+ if (gsEls.length > 0) {
32186
+ const stops = [];
32187
+ for (const gs of gsEls) {
32188
+ const pos = parseInt(gs.getAttribute("pos") ?? "0", 10) / 1e3;
32189
+ const color = resolveColor2(gs, themeColors);
32190
+ if (color)
32191
+ stops.push({ pos, color });
32192
+ }
32193
+ if (stops.length >= 2) {
32194
+ const lin = findChild2(gradFill, "lin");
32195
+ const angAttr = lin?.getAttribute("ang");
32196
+ const cssDeg = angAttr ? ooxmlAngleToCss(parseInt(angAttr, 10)) : 135;
32197
+ const stopStr = stops.map((s) => `${s.color} ${s.pos}%`).join(",");
32198
+ result.gradientFill = `linear-gradient(${cssDeg}deg,${stopStr})`;
32199
+ }
32200
+ const firstColor = resolveColor2(gsEls[0], themeColors);
32201
+ if (firstColor)
32202
+ result.color = firstColor;
32203
+ }
32204
+ }
32205
+ }
32206
+ }
31194
32207
  const latin = findChild2(rPr, "latin");
31195
32208
  if (latin) {
31196
32209
  const typeface = latin.getAttribute("typeface");
31197
32210
  if (typeface)
31198
32211
  result.fontFamily = typeface;
31199
32212
  }
32213
+ const effectLst = findChild2(rPr, "effectLst");
32214
+ if (effectLst) {
32215
+ const glow = findChild2(effectLst, "glow");
32216
+ if (glow) {
32217
+ const radAttr = glow.getAttribute("rad");
32218
+ const radiusEmu = radAttr ? parseInt(radAttr, 10) : 0;
32219
+ const radiusPx = Math.round(emuToPx2(radiusEmu) * scale);
32220
+ const glowColor = resolveColor2(glow, themeColors);
32221
+ if (glowColor && radiusPx > 0) {
32222
+ const INVERSE_GLOW_SIZE_SCALE = 0.4;
32223
+ const INVERSE_GLOW_OPACITY_SCALE = 3.33;
32224
+ const cssRadius = Math.round(radiusPx * INVERSE_GLOW_SIZE_SCALE);
32225
+ const rgbaMatch = glowColor.match(/rgba?\((\d+),(\d+),(\d+)(?:,([0-9.]+))?\)/);
32226
+ if (rgbaMatch) {
32227
+ const r = rgbaMatch[1];
32228
+ const g = rgbaMatch[2];
32229
+ const b2 = rgbaMatch[3];
32230
+ const originalAlpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1;
32231
+ const scaledAlpha = Math.min(originalAlpha * INVERSE_GLOW_OPACITY_SCALE, 1);
32232
+ result.textShadow = `0 0 ${cssRadius}px rgba(${r},${g},${b2},${scaledAlpha.toFixed(2)})`;
32233
+ } else if (glowColor.startsWith("#")) {
32234
+ const r = parseInt(glowColor.slice(1, 3), 16);
32235
+ const g = parseInt(glowColor.slice(3, 5), 16);
32236
+ const b2 = parseInt(glowColor.slice(5, 7), 16);
32237
+ result.textShadow = `0 0 ${cssRadius}px rgba(${r},${g},${b2},1)`;
32238
+ } else {
32239
+ result.textShadow = `0 0 ${cssRadius}px ${glowColor}`;
32240
+ }
32241
+ }
32242
+ }
32243
+ }
31200
32244
  return result;
31201
32245
  }
31202
32246
  function parseShape(sp, scale, themeColors) {
@@ -31221,13 +32265,16 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31221
32265
  paragraphs: []
31222
32266
  };
31223
32267
  const prstGeom = findChild2(spPr, "prstGeom");
31224
- if (prstGeom?.getAttribute("prst") === "roundRect") {
32268
+ const prstType = prstGeom?.getAttribute("prst");
32269
+ if (prstType === "roundRect" && prstGeom) {
31225
32270
  const avLst = findChild2(prstGeom, "avLst");
31226
32271
  const gd = avLst ? findChild2(avLst, "gd") : null;
31227
32272
  const adjVal = gd ? parseInt(gd.getAttribute("fmla")?.replace("val ", "") ?? "16667", 10) : 16667;
31228
32273
  const minDim = Math.min(wEmu, hEmu);
31229
32274
  const radiusEmu = minDim * Math.min(adjVal, 5e4) / 1e5;
31230
32275
  shape.borderRadius = Math.round(emuToPx2(radiusEmu) * scale);
32276
+ } else if (prstType === "ellipse") {
32277
+ shape.isEllipse = true;
31231
32278
  }
31232
32279
  const ln = findChild2(spPr, "ln");
31233
32280
  if (ln) {
@@ -31238,6 +32285,13 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31238
32285
  if (lnFill) {
31239
32286
  shape.borderColor = resolveColor2(lnFill, themeColors);
31240
32287
  }
32288
+ const prstDash = findChild2(ln, "prstDash");
32289
+ if (prstDash) {
32290
+ const dashVal = prstDash.getAttribute("val");
32291
+ if (dashVal && dashVal !== "solid") {
32292
+ shape.borderDashType = dashVal;
32293
+ }
32294
+ }
31241
32295
  }
31242
32296
  }
31243
32297
  const txBody = findChild2(sp, "txBody");
@@ -31308,21 +32362,34 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31308
32362
  para.bulletChar = buChar.getAttribute("char") ?? void 0;
31309
32363
  }
31310
32364
  }
31311
- const runs = findChildren2(p, "r");
31312
- for (const r of runs) {
31313
- const rPr = findChild2(r, "rPr");
31314
- const props = extractRunProps(rPr, scale, themeColors);
31315
- const tEls = findChildren2(r, "t");
31316
- const text = tEls.map((t) => t.textContent ?? "").join("");
31317
- if (text) {
31318
- para.runs.push({
31319
- text,
31320
- bold: props.bold ?? defaults?.bold,
31321
- italic: props.italic ?? defaults?.italic,
31322
- fontSize: props.fontSize ?? defaults?.fontSize,
31323
- color: props.color ?? defaults?.color,
31324
- fontFamily: props.fontFamily ?? defaults?.fontFamily
31325
- });
32365
+ for (const child of Array.from(p.childNodes)) {
32366
+ if (child.nodeType !== 1)
32367
+ continue;
32368
+ const el = child;
32369
+ const localName = el.localName || el.nodeName.split(":").pop();
32370
+ if (localName === "r") {
32371
+ const rPr = findChild2(el, "rPr");
32372
+ const props = extractRunProps(rPr, scale, themeColors);
32373
+ const tEls = findChildren2(el, "t");
32374
+ const text = tEls.map((t) => t.textContent ?? "").join("");
32375
+ if (text) {
32376
+ para.runs.push({
32377
+ text,
32378
+ bold: props.bold ?? defaults?.bold,
32379
+ italic: props.italic ?? defaults?.italic,
32380
+ fontSize: props.fontSize ?? defaults?.fontSize,
32381
+ color: props.color ?? defaults?.color,
32382
+ fontFamily: props.fontFamily ?? defaults?.fontFamily,
32383
+ textShadow: props.textShadow ?? defaults?.textShadow,
32384
+ gradientFill: props.gradientFill ?? defaults?.gradientFill
32385
+ });
32386
+ }
32387
+ } else if (localName === "br") {
32388
+ if (para.runs.length > 0) {
32389
+ para.runs[para.runs.length - 1].text += "\n";
32390
+ } else {
32391
+ para.runs.push({ text: "\n" });
32392
+ }
31326
32393
  }
31327
32394
  }
31328
32395
  if (para.runs.length > 0) {
@@ -31349,7 +32416,27 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31349
32416
  const blip = findChild2(blipFill, "blip");
31350
32417
  if (!blip)
31351
32418
  return null;
31352
- const rEmbed = blip.getAttribute("r:embed") ?? blip.getAttributeNS("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "embed");
32419
+ let rEmbed = null;
32420
+ const extLst = findChild2(blip, "extLst");
32421
+ if (extLst) {
32422
+ for (let i = 0; i < extLst.children.length; i++) {
32423
+ const ext2 = extLst.children[i];
32424
+ if (ext2.localName === "ext") {
32425
+ for (let j = 0; j < ext2.children.length; j++) {
32426
+ const child = ext2.children[j];
32427
+ if (child.localName === "svgBlip") {
32428
+ rEmbed = child.getAttribute("r:embed") ?? child.getAttributeNS("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "embed");
32429
+ break;
32430
+ }
32431
+ }
32432
+ if (rEmbed)
32433
+ break;
32434
+ }
32435
+ }
32436
+ }
32437
+ if (!rEmbed) {
32438
+ rEmbed = blip.getAttribute("r:embed") ?? blip.getAttributeNS("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "embed");
32439
+ }
31353
32440
  if (!rEmbed)
31354
32441
  return null;
31355
32442
  const dataUri = imageMap.get(rEmbed);
@@ -31358,13 +32445,36 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31358
32445
  const nvPicPr = findChild2(pic, "nvPicPr");
31359
32446
  const cNvPr = nvPicPr ? findChild2(nvPicPr, "cNvPr") : null;
31360
32447
  const alt = cNvPr?.getAttribute("descr") ?? void 0;
32448
+ const srcRect = findChild2(blipFill, "srcRect");
32449
+ let hasCrop = false;
32450
+ if (srcRect) {
32451
+ const l = parseInt(srcRect.getAttribute("l") ?? "0", 10);
32452
+ const r = parseInt(srcRect.getAttribute("r") ?? "0", 10);
32453
+ const t = parseInt(srcRect.getAttribute("t") ?? "0", 10);
32454
+ const b = parseInt(srcRect.getAttribute("b") ?? "0", 10);
32455
+ hasCrop = l > 0 || r > 0 || t > 0 || b > 0;
32456
+ }
32457
+ let borderRadius;
32458
+ const prstGeom = findChild2(spPr, "prstGeom");
32459
+ if (prstGeom?.getAttribute("prst") === "roundRect") {
32460
+ const avLst = findChild2(prstGeom, "avLst");
32461
+ const gd = avLst ? findChild2(avLst, "gd") : null;
32462
+ const adjVal = gd ? parseInt(gd.getAttribute("fmla")?.replace("val ", "") ?? "16667", 10) : 16667;
32463
+ const wEmu = parseInt(ext.getAttribute("cx") ?? "0", 10);
32464
+ const hEmu = parseInt(ext.getAttribute("cy") ?? "0", 10);
32465
+ const minDim = Math.min(wEmu, hEmu);
32466
+ const radiusEmu = minDim * Math.min(adjVal, 5e4) / 1e5;
32467
+ borderRadius = Math.round(emuToPx2(radiusEmu) * scale);
32468
+ }
31361
32469
  return {
31362
32470
  x: Math.round(emuToPx2(parseInt(off.getAttribute("x") ?? "0", 10)) * scale),
31363
32471
  y: Math.round(emuToPx2(parseInt(off.getAttribute("y") ?? "0", 10)) * scale),
31364
32472
  w: Math.round(emuToPx2(parseInt(ext.getAttribute("cx") ?? "0", 10)) * scale),
31365
32473
  h: Math.round(emuToPx2(parseInt(ext.getAttribute("cy") ?? "0", 10)) * scale),
31366
32474
  dataUri,
31367
- alt
32475
+ alt,
32476
+ borderRadius,
32477
+ hasCrop
31368
32478
  };
31369
32479
  }
31370
32480
  function renderSlideHtml(elements, bgColor) {
@@ -31374,14 +32484,20 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31374
32484
  const elId = `el-${elementIndex++}`;
31375
32485
  if (el.kind === "image") {
31376
32486
  const img = el.data;
31377
- const styles2 = [
32487
+ const isFullbleed = img.x <= 10 && img.y <= 10 && img.w >= TARGET_WIDTH - 20 && img.h >= TARGET_HEIGHT - 20;
32488
+ const objectFit = isFullbleed || img.hasCrop ? "cover" : "contain";
32489
+ const stylesList = [
31378
32490
  "position:absolute",
31379
32491
  `left:${img.x}px`,
31380
32492
  `top:${img.y}px`,
31381
32493
  `width:${img.w}px`,
31382
32494
  `height:${img.h}px`,
31383
- "object-fit:contain"
31384
- ].join(";");
32495
+ `object-fit:${objectFit}`
32496
+ ];
32497
+ if (img.borderRadius && img.borderRadius > 0) {
32498
+ stylesList.push(`border-radius:${img.borderRadius}px`);
32499
+ }
32500
+ const styles2 = stylesList.join(";");
31385
32501
  const altAttr = img.alt ? ` alt="${img.alt.replace(/"/g, "&quot;")}"` : "";
31386
32502
  inner += `<img id="${elId}" data-elementType="image" src="${img.dataUri}"${altAttr} style="${styles2}" />
31387
32503
  `;
@@ -31401,9 +32517,25 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31401
32517
  }
31402
32518
  if (shape.borderRadius) {
31403
32519
  styles.push(`border-radius:${shape.borderRadius}px`);
32520
+ } else if (shape.isEllipse) {
32521
+ styles.push(`border-radius:50%`);
31404
32522
  }
31405
32523
  if (shape.borderWidth && shape.borderColor) {
31406
- styles.push(`border:${shape.borderWidth}px solid ${shape.borderColor}`);
32524
+ let borderStyle = "solid";
32525
+ if (shape.borderDashType) {
32526
+ switch (shape.borderDashType) {
32527
+ case "dash":
32528
+ case "lgDash":
32529
+ case "sysDash":
32530
+ borderStyle = "dashed";
32531
+ break;
32532
+ case "sysDot":
32533
+ case "dot":
32534
+ borderStyle = "dotted";
32535
+ break;
32536
+ }
32537
+ }
32538
+ styles.push(`border:${shape.borderWidth}px ${borderStyle} ${shape.borderColor}`);
31407
32539
  styles.push("box-sizing:border-box");
31408
32540
  }
31409
32541
  if (shape.padding) {
@@ -31444,14 +32576,22 @@ ${validationErrors.map((e, i) => ` ${i + 1}. ${e}`).join("\n")}`;
31444
32576
  const rStyles = [];
31445
32577
  if (run.fontSize)
31446
32578
  rStyles.push(`font-size:${run.fontSize}px`);
31447
- if (run.color)
32579
+ if (run.gradientFill) {
32580
+ rStyles.push(`background:${run.gradientFill}`);
32581
+ rStyles.push("-webkit-background-clip:text");
32582
+ rStyles.push("-webkit-text-fill-color:transparent");
32583
+ rStyles.push("background-clip:text");
32584
+ } else if (run.color) {
31448
32585
  rStyles.push(`color:${run.color}`);
32586
+ }
31449
32587
  if (run.bold)
31450
32588
  rStyles.push("font-weight:bold");
31451
32589
  if (run.italic)
31452
32590
  rStyles.push("font-style:italic");
31453
32591
  if (run.fontFamily)
31454
- rStyles.push(`font-family:'${run.fontFamily}',sans-serif`);
32592
+ rStyles.push(`font-family:${cssFontFamily(run.fontFamily)}`);
32593
+ if (run.textShadow)
32594
+ rStyles.push(`text-shadow:${run.textShadow}`);
31455
32595
  const escapedText = run.text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
31456
32596
  if (rStyles.length > 0) {
31457
32597
  runHtml += `<span style="${rStyles.join(";")}">${escapedText}</span>`;
@@ -31563,6 +32703,21 @@ ${inner}</div>`;
31563
32703
  }
31564
32704
  }
31565
32705
  }
32706
+ const embeddedFontNames = /* @__PURE__ */ new Set();
32707
+ const embeddedFontEls = presDoc.getElementsByTagName("p:embeddedFont");
32708
+ for (let i = 0; i < embeddedFontEls.length; i++) {
32709
+ const fontEl = embeddedFontEls[i].getElementsByTagName("p:font")[0];
32710
+ if (fontEl) {
32711
+ const typeface = fontEl.getAttribute("typeface");
32712
+ if (typeface)
32713
+ embeddedFontNames.add(typeface);
32714
+ }
32715
+ }
32716
+ let fontStyleBlock = "";
32717
+ if (embeddedFontNames.size > 0) {
32718
+ const fontFamilies = Array.from(embeddedFontNames).map((name) => name.replace(/ /g, "+") + ":ital,wght@0,400;0,700;1,400;1,700").join("&family=");
32719
+ fontStyleBlock = `<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=${fontFamilies}&display=swap">`;
32720
+ }
31566
32721
  const slides = [];
31567
32722
  for (const slideFile of slideFiles) {
31568
32723
  const slideXml = await zip.file(slideFile)?.async("text");
@@ -31623,6 +32778,9 @@ ${inner}</div>`;
31623
32778
  }
31624
32779
  slides.push(renderSlideHtml(elements, slideBg));
31625
32780
  }
32781
+ if (fontStyleBlock && slides.length > 0) {
32782
+ slides[0] = fontStyleBlock + slides[0];
32783
+ }
31626
32784
  return slides;
31627
32785
  }
31628
32786