@microlee666/dom-to-pptx 1.1.4 → 1.1.6

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/src/utils.js CHANGED
@@ -109,6 +109,7 @@ export function extractTableData(node, scale, options = {}) {
109
109
  bold: textStyle.bold,
110
110
  italic: textStyle.italic,
111
111
  underline: textStyle.underline,
112
+ lang: 'zh-CN',
112
113
 
113
114
  fill: fill,
114
115
  align: align,
@@ -183,11 +184,11 @@ export function getGradientFallbackColor(bgImage) {
183
184
  // 3. Find first part that is a color (skip angle/direction)
184
185
  for (const part of parts) {
185
186
  // Ignore directions (to right) or angles (90deg, 0.5turn)
186
- if (/^(to\s|[\d\.]+(deg|rad|turn|grad))/.test(part)) continue;
187
+ if (/^(to\s|[\d.]+(deg|rad|turn|grad))/.test(part)) continue;
187
188
 
188
189
  // Extract color: Remove trailing position (e.g. "red 50%" -> "red")
189
190
  // Regex matches whitespace + number + unit at end of string
190
- const colorPart = part.replace(/\s+(-?[\d\.]+(%|px|em|rem|ch|vh|vw)?)$/, '');
191
+ const colorPart = part.replace(/\s+(-?[\d.]+(%|px|em|rem|ch|vh|vw)?)$/, '');
191
192
 
192
193
  // Check if it's not just a number (some gradients might have bare numbers? unlikely in standard syntax)
193
194
  if (colorPart) return colorPart;
@@ -458,6 +459,214 @@ export function parseColor(str) {
458
459
  return { hex, opacity: a };
459
460
  }
460
461
 
462
+ const SUPPORTED_CHART_TYPES = new Set(['area', 'bar', 'line', 'pie', 'doughnut', 'radar', 'scatter']);
463
+ const CHART_TYPE_ALIASES = {
464
+ column: 'bar',
465
+ columnbar: 'bar',
466
+ column3d: 'bar',
467
+ bar3d: 'bar',
468
+ donut: 'doughnut',
469
+ ring: 'doughnut',
470
+ };
471
+
472
+ function normalizeLegendPos(value) {
473
+ if (!value) return null;
474
+ const normalized = value.toString().toLowerCase().replace(/[^a-z]/g, '');
475
+ if (normalized === 'tr' || normalized === 'topright') return 'tr';
476
+ if (normalized === 'top') return 't';
477
+ if (normalized === 'bottom') return 'b';
478
+ if (normalized === 'left') return 'l';
479
+ if (normalized === 'right') return 'r';
480
+ return null;
481
+ }
482
+
483
+ function normalizeChartType(value) {
484
+ if (!value) return null;
485
+ const raw = value.toString().trim().toLowerCase();
486
+ const alias = CHART_TYPE_ALIASES[raw];
487
+ const candidate = alias || raw;
488
+ return SUPPORTED_CHART_TYPES.has(candidate) ? candidate : null;
489
+ }
490
+
491
+ function colorToHex(value) {
492
+ const parsed = parseColor(value);
493
+ return parsed.hex || null;
494
+ }
495
+
496
+ function resolveChartElement(root, config) {
497
+ if (!config) return null;
498
+ if (config.element instanceof HTMLElement) return config.element;
499
+ if (typeof config.element === 'function') return config.element(root);
500
+ if (typeof config.selector === 'string') {
501
+ return root.querySelector(config.selector);
502
+ }
503
+ return null;
504
+ }
505
+
506
+ function buildSeriesFromSource(source = {}) {
507
+ const fallbackLabels = Array.isArray(source.labels) ? source.labels : [];
508
+ const candidateDatasets = Array.isArray(source.datasets) ? source.datasets : [];
509
+ const series = [];
510
+
511
+ candidateDatasets.forEach((dataset, index) => {
512
+ if (!dataset) return;
513
+ const values = Array.isArray(dataset.values)
514
+ ? dataset.values
515
+ : Array.isArray(dataset.data)
516
+ ? dataset.data
517
+ : [];
518
+
519
+ if (!values.length) return;
520
+
521
+ const record = {
522
+ name: dataset.name || dataset.label || `Series ${index + 1}`,
523
+ values,
524
+ };
525
+
526
+ if (Array.isArray(dataset.labels) && dataset.labels.length === values.length) {
527
+ record.labels = dataset.labels;
528
+ } else if (fallbackLabels.length === values.length) {
529
+ record.labels = fallbackLabels;
530
+ }
531
+
532
+ if (Array.isArray(dataset.sizes) && dataset.sizes.length === values.length) {
533
+ record.sizes = dataset.sizes;
534
+ }
535
+
536
+ // Chart.js often provides strings/arrays in backgroundColor
537
+ const candidateColor =
538
+ dataset.color || dataset.backgroundColor || dataset.borderColor || dataset.fillColor;
539
+ if (candidateColor) {
540
+ if (Array.isArray(candidateColor)) {
541
+ record.color = candidateColor[0];
542
+ } else {
543
+ record.color = candidateColor;
544
+ }
545
+ }
546
+
547
+ series.push(record);
548
+ });
549
+
550
+ if (!series.length) {
551
+ const fallbackValues = Array.isArray(source.values)
552
+ ? source.values
553
+ : Array.isArray(source.data)
554
+ ? source.data
555
+ : [];
556
+
557
+ if (fallbackValues.length) {
558
+ const fallbackRecord = {
559
+ name: source.name || source.label || 'Series 1',
560
+ values: fallbackValues,
561
+ };
562
+ if (Array.isArray(source.labels) && source.labels.length === fallbackValues.length) {
563
+ fallbackRecord.labels = source.labels;
564
+ } else if (fallbackLabels.length === fallbackValues.length) {
565
+ fallbackRecord.labels = fallbackLabels;
566
+ }
567
+ if (Array.isArray(source.sizes) && source.sizes.length === fallbackValues.length) {
568
+ fallbackRecord.sizes = source.sizes;
569
+ }
570
+ if (source.color) {
571
+ fallbackRecord.color = source.color;
572
+ }
573
+ series.push(fallbackRecord);
574
+ }
575
+ }
576
+
577
+ return series;
578
+ }
579
+
580
+ function buildChartOptions(raw, derivedColors) {
581
+ const opts = { ...((raw && raw.chartOptions) || {}) };
582
+ if (raw && raw.showLegend !== undefined) opts.showLegend = raw.showLegend;
583
+ if (raw) {
584
+ const legendTarget = raw.legendPos || raw.legendPosition;
585
+ const normalizedLegend = normalizeLegendPos(legendTarget);
586
+ if (normalizedLegend) opts.legendPos = normalizedLegend;
587
+ }
588
+ if (raw && raw.title) opts.title = raw.title;
589
+ if (raw && raw.altText) opts.altText = raw.altText;
590
+ if (raw && raw.showDataTable !== undefined) opts.showDataTable = raw.showDataTable;
591
+ if (raw && raw.chartColorsOpacity !== undefined) opts.chartColorsOpacity = raw.chartColorsOpacity;
592
+ if (raw && raw.showLabel !== undefined) opts.showLabel = raw.showLabel;
593
+
594
+ const paletteFromConfig =
595
+ raw && Array.isArray(raw.chartColors) ? raw.chartColors.map(colorToHex).filter(Boolean) : [];
596
+ const palette = paletteFromConfig.length ? paletteFromConfig : derivedColors;
597
+ if (palette.length && !opts.chartColors) {
598
+ opts.chartColors = palette;
599
+ }
600
+
601
+ return opts;
602
+ }
603
+
604
+ function normalizeChartConfig(raw) {
605
+ if (!raw) return null;
606
+ const chartType = normalizeChartType(raw.chartType || raw.type || raw.chart);
607
+ if (!chartType) return null;
608
+
609
+ let seriesWithColor = [];
610
+ if (Array.isArray(raw.chartData) && raw.chartData.length) {
611
+ seriesWithColor = raw.chartData
612
+ .map((entry, index) => {
613
+ if (!entry || !Array.isArray(entry.values)) return null;
614
+ return {
615
+ ...entry,
616
+ name: entry.name || entry.label || `Series ${index + 1}`,
617
+ };
618
+ })
619
+ .filter((entry) => entry && entry.values && entry.values.length);
620
+ } else {
621
+ const source = {
622
+ labels: raw.data?.labels || raw.labels,
623
+ datasets: Array.isArray(raw.data?.datasets)
624
+ ? raw.data.datasets
625
+ : Array.isArray(raw.datasets)
626
+ ? raw.datasets
627
+ : [],
628
+ values: raw.data?.values || raw.values,
629
+ data: raw.data?.data,
630
+ name: raw.name,
631
+ label: raw.label,
632
+ color: raw.color,
633
+ sizes: raw.data?.sizes || raw.sizes,
634
+ };
635
+ seriesWithColor = buildSeriesFromSource(source);
636
+ }
637
+
638
+ if (!seriesWithColor.length) return null;
639
+
640
+ const derivedColors = seriesWithColor
641
+ .map((dataset) => dataset.color)
642
+ .map(colorToHex)
643
+ .filter(Boolean);
644
+
645
+ const chartOptions = buildChartOptions(raw, derivedColors);
646
+ const chartData = seriesWithColor.map(({ color, ...rest }) => rest);
647
+
648
+ return {
649
+ chartType,
650
+ chartData,
651
+ chartOptions,
652
+ };
653
+ }
654
+
655
+ export function buildChartRegistry(root, chartConfigs = []) {
656
+ const registry = new Map();
657
+ if (!root || !Array.isArray(chartConfigs)) return registry;
658
+
659
+ chartConfigs.forEach((config) => {
660
+ const node = resolveChartElement(root, config);
661
+ if (!node) return;
662
+ const normalized = normalizeChartConfig(config);
663
+ if (!normalized || !normalized.chartData || !normalized.chartData.length) return;
664
+ registry.set(node, normalized);
665
+ });
666
+
667
+ return registry;
668
+ }
669
+
461
670
  export function getPadding(style, scale) {
462
671
  const pxToInch = 1 / 96;
463
672
  return [
@@ -476,6 +685,8 @@ export function getSoftEdges(filterStr, scale) {
476
685
  }
477
686
 
478
687
  const DEFAULT_CJK_FONTS = [
688
+ 'Heiti TC',
689
+ 'Heiti SC',
479
690
  'PingFang SC',
480
691
  'Hiragino Sans GB',
481
692
  'Microsoft YaHei',
@@ -527,7 +738,7 @@ function pickFontFace(fontFamily, text, options) {
527
738
  }
528
739
 
529
740
  const autoMatch = DEFAULT_CJK_FONTS.find((f) => lowered.includes(f.toLowerCase()));
530
- return autoMatch || primary;
741
+ return autoMatch || 'Heiti TC';
531
742
  }
532
743
 
533
744
  export function getTextStyle(style, scale, text = '', options = {}) {
@@ -558,6 +769,9 @@ export function getTextStyle(style, scale, text = '', options = {}) {
558
769
  // And apply the global layout scale.
559
770
  lineSpacing = lhPx * 0.75 * scale;
560
771
  }
772
+ } else {
773
+ // Default line height when 'normal' - use 1.2 multiplier to match browser default
774
+ lineSpacing = fontSizePx * 1.2 * 0.75 * scale;
561
775
  }
562
776
 
563
777
  // --- Spacing (Margins) ---
@@ -576,10 +790,11 @@ export function getTextStyle(style, scale, text = '', options = {}) {
576
790
  return {
577
791
  color: colorObj.hex || '000000',
578
792
  fontFace: fontFace,
579
- fontSize: Math.floor(fontSizePx * 0.75 * scale),
793
+ fontSize: Math.max(8, Math.floor(fontSizePx * 0.75 * scale)),
580
794
  bold: parseInt(style.fontWeight) >= 600,
581
795
  italic: style.fontStyle === 'italic',
582
796
  underline: style.textDecoration.includes('underline'),
797
+ lang: 'zh-CN',
583
798
  // Only add if we have a valid value
584
799
  ...(lineSpacing && { lineSpacing }),
585
800
  ...(paraSpaceBefore > 0 && { paraSpaceBefore }),
@@ -592,6 +807,7 @@ export function getTextStyle(style, scale, text = '', options = {}) {
592
807
  /**
593
808
  * Determines if a given DOM node is primarily a text container.
594
809
  * Updated to correctly reject Icon elements so they are rendered as images.
810
+ * Also rejects flex/grid containers with distributed children (space-between/around/evenly).
595
811
  */
596
812
  export function isTextContainer(node) {
597
813
  const hasText = node.textContent.trim().length > 0;
@@ -600,6 +816,26 @@ export function isTextContainer(node) {
600
816
  const children = Array.from(node.children);
601
817
  if (children.length === 0) return true;
602
818
 
819
+ // Check if parent is a flex/grid container with special layout
820
+ // In such cases, children should be treated as separate elements
821
+ const parentStyle = window.getComputedStyle(node);
822
+ const display = parentStyle.display;
823
+ const justifyContent = parentStyle.justifyContent || '';
824
+ const alignItems = parentStyle.alignItems || '';
825
+
826
+ // If parent uses flex/grid with space distribution, don't treat as text container
827
+ if ((display.includes('flex') || display.includes('grid')) &&
828
+ (justifyContent.includes('space-between') ||
829
+ justifyContent.includes('space-around') ||
830
+ justifyContent.includes('space-evenly'))) {
831
+ return false;
832
+ }
833
+
834
+ // Note: We don't skip text container for align-items: center here.
835
+ // The valign inheritance is handled in prepareRenderItem with single-child check.
836
+ // This allows elements like "密钥状态流转" to properly inherit center alignment
837
+ // while preventing STEP 1/2/3 from being incorrectly centered.
838
+
603
839
  const isSafeInline = (el) => {
604
840
  // 1. Reject Web Components / Custom Elements
605
841
  if (el.tagName.includes('-')) return false;