docgen-utils 1.0.19 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +65 -18
  2. package/dist/bundle.js +27905 -26887
  3. package/dist/bundle.min.js +253 -263
  4. package/dist/cli.js +3696 -2584
  5. package/dist/packages/cli/commands/export-docs.d.ts.map +1 -1
  6. package/dist/packages/cli/commands/export-docs.js +161 -12
  7. package/dist/packages/cli/commands/export-docs.js.map +1 -1
  8. package/dist/packages/cli/commands/export-slides.d.ts.map +1 -1
  9. package/dist/packages/cli/commands/export-slides.js +11 -7
  10. package/dist/packages/cli/commands/export-slides.js.map +1 -1
  11. package/dist/packages/cli/index.js.map +1 -1
  12. package/dist/packages/docs/common.d.ts +2 -0
  13. package/dist/packages/docs/common.d.ts.map +1 -1
  14. package/dist/packages/docs/convert.d.ts.map +1 -1
  15. package/dist/packages/docs/convert.js +6 -15
  16. package/dist/packages/docs/convert.js.map +1 -1
  17. package/dist/packages/docs/create-document.d.ts.map +1 -1
  18. package/dist/packages/docs/create-document.js +8 -2
  19. package/dist/packages/docs/create-document.js.map +1 -1
  20. package/dist/packages/docs/import-docx.d.ts.map +1 -1
  21. package/dist/packages/docs/import-docx.js +170 -83
  22. package/dist/packages/docs/import-docx.js.map +1 -1
  23. package/dist/packages/docs/parse-colors.d.ts +0 -5
  24. package/dist/packages/docs/parse-colors.d.ts.map +1 -1
  25. package/dist/packages/docs/parse-colors.js +2 -2
  26. package/dist/packages/docs/parse-colors.js.map +1 -1
  27. package/dist/packages/docs/parse-css.d.ts +0 -9
  28. package/dist/packages/docs/parse-css.d.ts.map +1 -1
  29. package/dist/packages/docs/parse-css.js +4 -6
  30. package/dist/packages/docs/parse-css.js.map +1 -1
  31. package/dist/packages/docs/parse-helpers.d.ts +0 -1
  32. package/dist/packages/docs/parse-helpers.d.ts.map +1 -1
  33. package/dist/packages/docs/parse-helpers.js +1 -1
  34. package/dist/packages/docs/parse-helpers.js.map +1 -1
  35. package/dist/packages/docs/parse-inline.d.ts +0 -13
  36. package/dist/packages/docs/parse-inline.d.ts.map +1 -1
  37. package/dist/packages/docs/parse-inline.js +7 -7
  38. package/dist/packages/docs/parse-inline.js.map +1 -1
  39. package/dist/packages/docs/parse-layout.d.ts.map +1 -1
  40. package/dist/packages/docs/parse-layout.js +1 -14
  41. package/dist/packages/docs/parse-layout.js.map +1 -1
  42. package/dist/packages/docs/parse-special.js +1 -1
  43. package/dist/packages/docs/parse-special.js.map +1 -1
  44. package/dist/packages/docs/parse.d.ts.map +1 -1
  45. package/dist/packages/docs/parse.js +120 -130
  46. package/dist/packages/docs/parse.js.map +1 -1
  47. package/dist/packages/shared/zip-guard.d.ts +37 -0
  48. package/dist/packages/shared/zip-guard.d.ts.map +1 -0
  49. package/dist/packages/shared/zip-guard.js +101 -0
  50. package/dist/packages/shared/zip-guard.js.map +1 -0
  51. package/dist/packages/slides/convert.d.ts +1 -3
  52. package/dist/packages/slides/convert.d.ts.map +1 -1
  53. package/dist/packages/slides/convert.js +8 -74
  54. package/dist/packages/slides/convert.js.map +1 -1
  55. package/dist/packages/slides/createPresentation.d.ts +1 -1
  56. package/dist/packages/slides/createPresentation.d.ts.map +1 -1
  57. package/dist/packages/slides/createPresentation.js +1 -10
  58. package/dist/packages/slides/createPresentation.js.map +1 -1
  59. package/dist/packages/slides/import-pptx.d.ts.map +1 -1
  60. package/dist/packages/slides/import-pptx.js +78 -9
  61. package/dist/packages/slides/import-pptx.js.map +1 -1
  62. package/dist/packages/slides/parse.d.ts +0 -22
  63. package/dist/packages/slides/parse.d.ts.map +1 -1
  64. package/dist/packages/slides/parse.js +106 -44
  65. package/dist/packages/slides/parse.js.map +1 -1
  66. package/dist/packages/slides/transform.d.ts.map +1 -1
  67. package/dist/packages/slides/transform.js +1 -5
  68. package/dist/packages/slides/transform.js.map +1 -1
  69. package/dist/packages/slides/vendor/VENDORING.md +2 -2
  70. package/package.json +15 -8
  71. package/dist/packages/cli/commands/common.d.ts +0 -2
  72. package/dist/packages/cli/commands/common.d.ts.map +0 -1
  73. package/dist/packages/cli/commands/common.js +0 -22
  74. package/dist/packages/cli/commands/common.js.map +0 -1
@@ -19,21 +19,21 @@ const PX_PER_IN = 96;
19
19
  // ---------------------------------------------------------------------------
20
20
  const SINGLE_WEIGHT_FONTS = ['impact'];
21
21
  // ---------------------------------------------------------------------------
22
- // Exported helper functions
22
+ // Internal helper functions
23
23
  // ---------------------------------------------------------------------------
24
24
  /** Convert pixel value to inches. */
25
- export function pxToInch(px) {
25
+ function pxToInch(px) {
26
26
  return px / PX_PER_IN;
27
27
  }
28
28
  /** Convert a CSS pixel string (e.g. "16px") to points. */
29
- export function pxToPoints(pxStr) {
29
+ function pxToPoints(pxStr) {
30
30
  return parseFloat(pxStr) * PT_PER_PX;
31
31
  }
32
32
  /**
33
33
  * Convert an `rgb()` / `rgba()` color string to a 6-char hex string.
34
34
  * Returns `'FFFFFF'` for transparent or unparseable values.
35
35
  */
36
- export function rgbToHex(rgbStr) {
36
+ function rgbToHex(rgbStr) {
37
37
  // Remove !important suffix if present
38
38
  rgbStr = rgbStr.replace(/\s*!important\s*$/i, '').trim();
39
39
  if (rgbStr === 'rgba(0, 0, 0, 0)' || rgbStr === 'transparent')
@@ -64,7 +64,7 @@ function isFullyTransparent(colorStr) {
64
64
  * Extract transparency percentage from an `rgba()` string.
65
65
  * Returns `null` for opaque or non-rgba values, otherwise 0-100.
66
66
  */
67
- export function extractAlpha(rgbStr) {
67
+ function extractAlpha(rgbStr) {
68
68
  const match = rgbStr.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/);
69
69
  if (!match || !match[4])
70
70
  return null;
@@ -354,7 +354,7 @@ function renderRepeatingGradientPatternAsImage(bgImage, bgSize, bgPosition, widt
354
354
  }
355
355
  }
356
356
  // ---------------------------------------------------------------------------
357
- // parseCssGradient (exported, used by other modules)
357
+ // parseCssGradient (internal, used within this module)
358
358
  // ---------------------------------------------------------------------------
359
359
  /**
360
360
  * Parse a CSS gradient string (`linear-gradient(...)` or `radial-gradient(...)`)
@@ -362,7 +362,7 @@ function renderRepeatingGradientPatternAsImage(bgImage, bgSize, bgPosition, widt
362
362
  *
363
363
  * Returns `null` if the string does not contain a recognized gradient.
364
364
  */
365
- export function parseCssGradient(gradientStr) {
365
+ function parseCssGradient(gradientStr) {
366
366
  const colorToHex = (colorStr) => {
367
367
  colorStr = colorStr.trim().toLowerCase();
368
368
  // Remove !important suffix if present
@@ -444,10 +444,30 @@ export function parseCssGradient(gradientStr) {
444
444
  }
445
445
  return null;
446
446
  };
447
+ // Shared helper: parse color stops from a gradient parts array.
448
+ // Prevents divide-by-zero when there is only one color stop.
449
+ const parseColorStops = (colorStops) => {
450
+ return colorStops.map((stop, idx) => {
451
+ const posMatch = stop.match(/([\d.]+)%\s*$/);
452
+ const position = posMatch
453
+ ? parseFloat(posMatch[1])
454
+ : (idx / Math.max(colorStops.length - 1, 1)) * 100;
455
+ const colorPart = posMatch
456
+ ? stop.replace(/([\d.]+)%\s*$/, '').trim()
457
+ : stop.trim();
458
+ const stopData = { color: colorToHex(colorPart), position };
459
+ const transparency = extractTransparency(colorPart);
460
+ if (transparency !== null)
461
+ stopData.transparency = transparency;
462
+ return stopData;
463
+ });
464
+ };
447
465
  // Try linear-gradient
448
466
  const linearContent = extractGradientContent(gradientStr, 'linear-gradient(');
449
467
  if (linearContent) {
450
468
  const parts = splitGradientParts(linearContent);
469
+ if (parts.length === 0)
470
+ return null;
451
471
  let cssAngle = 180;
452
472
  let colorStops = parts;
453
473
  const angleMatch = parts[0].match(/^([\d.]+)deg$/);
@@ -474,26 +494,15 @@ export function parseCssGradient(gradientStr) {
474
494
  cssAngle = dirMap[dir] ?? 180;
475
495
  colorStops = parts.slice(1);
476
496
  }
477
- const stops = colorStops.map((stop, idx) => {
478
- const posMatch = stop.match(/([\d.]+)%\s*$/);
479
- const position = posMatch
480
- ? parseFloat(posMatch[1])
481
- : (idx / (colorStops.length - 1)) * 100;
482
- const colorPart = posMatch
483
- ? stop.replace(/([\d.]+)%\s*$/, '').trim()
484
- : stop.trim();
485
- const stopData = { color: colorToHex(colorPart), position };
486
- const transparency = extractTransparency(colorPart);
487
- if (transparency !== null)
488
- stopData.transparency = transparency;
489
- return stopData;
490
- });
497
+ const stops = parseColorStops(colorStops);
491
498
  return { type: 'linear', angle: cssAngle, stops };
492
499
  }
493
500
  // Try radial-gradient
494
501
  const radialContent = extractGradientContent(gradientStr, 'radial-gradient(');
495
502
  if (radialContent) {
496
503
  const parts = splitGradientParts(radialContent);
504
+ if (parts.length === 0)
505
+ return null;
497
506
  let centerX = 50;
498
507
  let centerY = 50;
499
508
  let colorStops = parts;
@@ -506,20 +515,7 @@ export function parseCssGradient(gradientStr) {
506
515
  else if (parts[0].includes('circle') || parts[0].includes('ellipse')) {
507
516
  colorStops = parts.slice(1);
508
517
  }
509
- const stops = colorStops.map((stop, idx) => {
510
- const posMatch2 = stop.match(/([\d.]+)%\s*$/);
511
- const position = posMatch2
512
- ? parseFloat(posMatch2[1])
513
- : (idx / (colorStops.length - 1)) * 100;
514
- const colorPart = posMatch2
515
- ? stop.replace(/([\d.]+)%\s*$/, '').trim()
516
- : stop.trim();
517
- const stopData = { color: colorToHex(colorPart), position };
518
- const transparency = extractTransparency(colorPart);
519
- if (transparency !== null)
520
- stopData.transparency = transparency;
521
- return stopData;
522
- });
518
+ const stops = parseColorStops(colorStops);
523
519
  return { type: 'radial', centerX, centerY, stops };
524
520
  }
525
521
  return null;
@@ -1599,8 +1595,18 @@ function extractPseudoElements(el, win) {
1599
1595
  // the element's content area.
1600
1596
  }
1601
1597
  // Convert to absolute page coordinates
1602
- const absLeft = parentRect.left + pLeft;
1603
- const absTop = parentRect.top + pTop;
1598
+ // For absolutely positioned pseudo-elements, CSS left/top values are relative
1599
+ // to the padding box (inside the border), but getBoundingClientRect() returns
1600
+ // the border box. We need to add the parent's border widths to get correct positioning.
1601
+ const parentComputedForBorder = win.getComputedStyle(el);
1602
+ let borderOffsetLeft = 0;
1603
+ let borderOffsetTop = 0;
1604
+ if (pseudoPos === 'absolute' || pseudoPos === 'fixed') {
1605
+ borderOffsetLeft = parseFloat(parentComputedForBorder.borderLeftWidth) || 0;
1606
+ borderOffsetTop = parseFloat(parentComputedForBorder.borderTopWidth) || 0;
1607
+ }
1608
+ const absLeft = parentRect.left + pLeft + borderOffsetLeft;
1609
+ const absTop = parentRect.top + pTop + borderOffsetTop;
1604
1610
  // Check for visual content: background color, gradient, or border
1605
1611
  const hasBg = pComputed.backgroundColor && pComputed.backgroundColor !== 'rgba(0, 0, 0, 0)';
1606
1612
  const bgImage = pComputed.backgroundImage;
@@ -1611,7 +1617,7 @@ function extractPseudoElements(el, win) {
1611
1617
  continue;
1612
1618
  // Parse gradient(s) — a single background-image property may contain multiple gradients
1613
1619
  let gradient = null;
1614
- let extraPseudoGradients = [];
1620
+ const extraPseudoGradients = [];
1615
1621
  if (hasGradient) {
1616
1622
  const gradParts = splitCssBackgroundGradients(bgImage);
1617
1623
  if (gradParts.length > 0)
@@ -2597,7 +2603,7 @@ export function parseSlideHtml(doc) {
2597
2603
  }
2598
2604
  const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)';
2599
2605
  // Check for clip-path: polygon() — store for custom geometry
2600
- const clipPathValue = computed.clipPath || computed.webkitClipPath;
2606
+ const clipPathValue = computed.clipPath || computed.webkitClipPath || '';
2601
2607
  const clipPathPolygon = parseClipPathPolygon(clipPathValue);
2602
2608
  // Check for background images or gradients
2603
2609
  const elBgImage = computed.backgroundImage;
@@ -2605,7 +2611,7 @@ export function parseSlideHtml(doc) {
2605
2611
  let bgImageSize = null;
2606
2612
  let bgImagePosition = null;
2607
2613
  let bgGradient = null;
2608
- let extraDivGradients = [];
2614
+ const extraDivGradients = [];
2609
2615
  if (elBgImage && elBgImage !== 'none') {
2610
2616
  if (elBgImage.includes('linear-gradient') ||
2611
2617
  elBgImage.includes('radial-gradient')) {
@@ -2848,7 +2854,7 @@ export function parseSlideHtml(doc) {
2848
2854
  const alignItems = computed.alignItems;
2849
2855
  const justifyContent = computed.justifyContent;
2850
2856
  let valign = 'top';
2851
- let align = 'left';
2857
+ let align;
2852
2858
  if (isFlexContainer) {
2853
2859
  const flexDirection = computed.flexDirection || 'row';
2854
2860
  if (flexDirection === 'row' || flexDirection === 'row-reverse') {
@@ -3583,11 +3589,67 @@ export function parseSlideHtml(doc) {
3583
3589
  // and process each child element (SVGs, spans, text nodes) individually
3584
3590
  return;
3585
3591
  }
3592
+ // For lists with custom bullets via ::before pseudo-elements (list-style: none
3593
+ // with padding-left on li), process each li as its own text element.
3594
+ // This ensures each li's ::before renders at the correct position relative
3595
+ // to its own li bounding box, avoiding text/bullet overlap.
3596
+ const hasCustomPseudoBullets = !hasNativeBullets && liElements.some((li) => {
3597
+ const liComputed = win.getComputedStyle(li);
3598
+ const liPadding = parseFloat(liComputed.paddingLeft) || 0;
3599
+ // Check if li has position:relative (needed for absolute ::before positioning)
3600
+ // and has padding-left (space for the bullet)
3601
+ return liComputed.position === 'relative' && liPadding > 0;
3602
+ });
3603
+ if (hasCustomPseudoBullets) {
3604
+ // Process each li as a separate text element
3605
+ // The ::before pseudo-elements will be extracted in the second pass
3606
+ liElements.forEach((li) => {
3607
+ const liEl = li;
3608
+ const liRect = liEl.getBoundingClientRect();
3609
+ if (liRect.width === 0 || liRect.height === 0)
3610
+ return;
3611
+ const liComputed = win.getComputedStyle(liEl);
3612
+ const liPaddingLeft = parseFloat(liComputed.paddingLeft) || 0;
3613
+ // Get the text content, offset by padding-left
3614
+ const textLeft = liRect.left + liPaddingLeft;
3615
+ const textWidth = liRect.width - liPaddingLeft;
3616
+ const runs = parseInlineFormatting(liEl, { breakLine: false }, [], (x) => x, win);
3617
+ if (runs.length === 0)
3618
+ return;
3619
+ // Strip any leading bullet characters that might have been left over
3620
+ runs[0].text = runs[0].text.replace(/^[•\-*\u25AA\u25B8]\s*/, '');
3621
+ const textElement = {
3622
+ type: 'p',
3623
+ text: runs,
3624
+ position: {
3625
+ x: pxToInch(textLeft),
3626
+ y: pxToInch(liRect.top),
3627
+ w: pxToInch(textWidth),
3628
+ h: pxToInch(liRect.height),
3629
+ },
3630
+ style: {
3631
+ fontSize: pxToPoints(liComputed.fontSize),
3632
+ fontFace: extractFontFace(liComputed.fontFamily),
3633
+ color: rgbToHex(liComputed.color),
3634
+ transparency: extractAlpha(liComputed.color),
3635
+ align: liComputed.textAlign === 'start'
3636
+ ? 'left'
3637
+ : liComputed.textAlign,
3638
+ valign: 'top',
3639
+ lineSpacing: pxToPoints(liComputed.lineHeight),
3640
+ },
3641
+ };
3642
+ elements.push(textElement);
3643
+ processed.add(liEl);
3644
+ });
3645
+ processed.add(el);
3646
+ return;
3647
+ }
3586
3648
  liElements.forEach((li, idx) => {
3587
3649
  const isLast = idx === liElements.length - 1;
3588
3650
  const runs = parseInlineFormatting(li, { breakLine: false }, [], (x) => x, win);
3589
3651
  if (runs.length > 0) {
3590
- runs[0].text = runs[0].text.replace(/^[•\-\*\u25AA\u25B8]\s*/, '');
3652
+ runs[0].text = runs[0].text.replace(/^[•\-*\u25AA\u25B8]\s*/, '');
3591
3653
  if (hasNativeBullets) {
3592
3654
  runs[0].options.bullet = { indent: textIndent };
3593
3655
  }
@@ -3720,7 +3782,7 @@ export function parseSlideHtml(doc) {
3720
3782
  }
3721
3783
  }
3722
3784
  if (el.tagName !== 'LI' &&
3723
- /^[•\-\*\u25AA\u25B8\u25CB\u25CF\u25C6\u25C7\u25A0\u25A1]\s/.test(text.trimStart())) {
3785
+ /^[•\-*\u25AA\u25B8\u25CB\u25CF\u25C6\u25C7\u25A0\u25A1]\s/.test(text.trimStart())) {
3724
3786
  errors.push(`Text element <${el.tagName.toLowerCase()}> starts with bullet symbol "${text.substring(0, 20)}...". ` +
3725
3787
  'Use <ul> or <ol> lists instead of manual bullet symbols.');
3726
3788
  return;
@@ -3730,7 +3792,7 @@ export function parseSlideHtml(doc) {
3730
3792
  const fontSizePx = parseFloat(computed.fontSize);
3731
3793
  const lineHeightPx = parseFloat(computed.lineHeight);
3732
3794
  const lineHeightMultiplier = fontSizePx > 0 && !isNaN(lineHeightPx) ? lineHeightPx / fontSizePx : 1.0;
3733
- let textAlign = computed.textAlign === 'start' ? 'left' : computed.textAlign;
3795
+ const textAlign = computed.textAlign === 'start' ? 'left' : computed.textAlign;
3734
3796
  let valign = null;
3735
3797
  const checkFlexParent = (parent) => {
3736
3798
  if (!parent)