@json-to-office/core-pptx 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/image.d.ts +3 -3
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/shape.d.ts +1 -0
- package/dist/components/shape.d.ts.map +1 -1
- package/dist/components/table.d.ts +1 -0
- package/dist/components/table.d.ts.map +1 -1
- package/dist/components/text.d.ts +1 -0
- package/dist/components/text.d.ts.map +1 -1
- package/dist/core/fontResolution.d.ts +14 -0
- package/dist/core/fontResolution.d.ts.map +1 -0
- package/dist/core/generator.d.ts +2 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/render.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +337 -58
- package/dist/index.js.map +1 -1
- package/dist/plugin/createPresentationGenerator.d.ts +3 -1
- package/dist/plugin/createPresentationGenerator.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/fontAliasContext.d.ts +21 -0
- package/dist/utils/fontAliasContext.d.ts.map +1 -0
- package/dist/utils/warn.d.ts +3 -1
- package/dist/utils/warn.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -25,7 +25,9 @@ var W = {
|
|
|
25
25
|
PLACEHOLDER_NO_POSITION: "PLACEHOLDER_NO_POSITION",
|
|
26
26
|
THEME_COLOR_FALLBACK: "THEME_COLOR_FALLBACK",
|
|
27
27
|
UNKNOWN_COLOR: "UNKNOWN_COLOR",
|
|
28
|
-
GRID_POSITION_CLAMPED: "GRID_POSITION_CLAMPED"
|
|
28
|
+
GRID_POSITION_CLAMPED: "GRID_POSITION_CLAMPED",
|
|
29
|
+
IMAGE_ZERO_BOX: "IMAGE_ZERO_BOX",
|
|
30
|
+
FONT_UNRESOLVED: "FONT_UNRESOLVED"
|
|
29
31
|
};
|
|
30
32
|
function warn(warnings, code, message, extra) {
|
|
31
33
|
if (warnings) {
|
|
@@ -428,6 +430,25 @@ function resolveColor(color, theme, warnings) {
|
|
|
428
430
|
return bare;
|
|
429
431
|
}
|
|
430
432
|
|
|
433
|
+
// src/utils/fontAliasContext.ts
|
|
434
|
+
import { synthesizeFamilyName } from "@json-to-office/shared";
|
|
435
|
+
function applyFontWeight(params) {
|
|
436
|
+
if (!params.family) {
|
|
437
|
+
const weight2 = params.fontWeight ?? (params.bold === true ? 700 : void 0);
|
|
438
|
+
return {
|
|
439
|
+
bold: weight2 != null ? weight2 >= 600 : params.bold,
|
|
440
|
+
italic: params.italic
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
const weight = params.fontWeight ?? (params.bold === true ? 700 : void 0);
|
|
444
|
+
const synth = synthesizeFamilyName(
|
|
445
|
+
params.family,
|
|
446
|
+
weight,
|
|
447
|
+
params.italic === true
|
|
448
|
+
);
|
|
449
|
+
return { fontFace: synth.family, bold: synth.bold, italic: synth.italic };
|
|
450
|
+
}
|
|
451
|
+
|
|
431
452
|
// src/components/text.ts
|
|
432
453
|
function resolvePagePlaceholders(text, ctx) {
|
|
433
454
|
const { slideNumber, totalSlides, pageNumberFormat } = ctx;
|
|
@@ -450,11 +471,27 @@ function renderTextComponent(slide, props, theme, warnings, slideCtx) {
|
|
|
450
471
|
}
|
|
451
472
|
opts.fontSize = props.fontSize ?? style?.fontSize ?? theme.defaults.fontSize;
|
|
452
473
|
opts.fontFace = props.fontFace ?? style?.fontFace ?? (isHeadingStyle ? theme.fonts.heading : theme.fonts.body);
|
|
453
|
-
opts.color = resolveColor(
|
|
474
|
+
opts.color = resolveColor(
|
|
475
|
+
props.color ?? style?.fontColor ?? theme.defaults.fontColor,
|
|
476
|
+
theme,
|
|
477
|
+
warnings
|
|
478
|
+
);
|
|
454
479
|
const bold = props.bold ?? style?.bold;
|
|
455
480
|
const italic = props.italic ?? style?.italic;
|
|
481
|
+
const fontWeight = props.fontWeight ?? style?.fontWeight;
|
|
456
482
|
if (bold != null) opts.bold = bold;
|
|
457
483
|
if (italic != null) opts.italic = italic;
|
|
484
|
+
if (fontWeight != null || bold === true) {
|
|
485
|
+
const w = applyFontWeight({
|
|
486
|
+
family: opts.fontFace,
|
|
487
|
+
fontWeight,
|
|
488
|
+
italic,
|
|
489
|
+
bold
|
|
490
|
+
});
|
|
491
|
+
if (w.fontFace !== void 0) opts.fontFace = w.fontFace;
|
|
492
|
+
if (w.bold !== void 0) opts.bold = w.bold;
|
|
493
|
+
if (w.italic !== void 0) opts.italic = w.italic;
|
|
494
|
+
}
|
|
458
495
|
if (props.strike) opts.strike = true;
|
|
459
496
|
if (props.underline !== void 0) {
|
|
460
497
|
if (typeof props.underline === "boolean") {
|
|
@@ -502,7 +539,8 @@ function renderTextComponent(slide, props, theme, warnings, slideCtx) {
|
|
|
502
539
|
if (lineSpacing !== void 0) opts.lineSpacing = lineSpacing;
|
|
503
540
|
const charSpacing = props.charSpacing ?? style?.charSpacing;
|
|
504
541
|
if (charSpacing !== void 0) opts.charSpacing = charSpacing;
|
|
505
|
-
if (props.paraSpaceBefore !== void 0)
|
|
542
|
+
if (props.paraSpaceBefore !== void 0)
|
|
543
|
+
opts.paraSpaceBefore = props.paraSpaceBefore;
|
|
506
544
|
const paraSpaceAfter = props.paraSpaceAfter ?? style?.paraSpaceAfter;
|
|
507
545
|
if (paraSpaceAfter !== void 0) opts.paraSpaceAfter = paraSpaceAfter;
|
|
508
546
|
if (props.breakLine) opts.breakLine = true;
|
|
@@ -516,7 +554,8 @@ import probe from "probe-image-size";
|
|
|
516
554
|
function isPrivateUrl(urlStr) {
|
|
517
555
|
try {
|
|
518
556
|
const { hostname } = new URL(urlStr);
|
|
519
|
-
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]" || hostname.startsWith("10.") || hostname.startsWith("192.168.") || hostname.startsWith("169.254.") || hostname.endsWith(".local") || hostname.endsWith(".internal"))
|
|
557
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]" || hostname.startsWith("10.") || hostname.startsWith("192.168.") || hostname.startsWith("169.254.") || hostname.endsWith(".local") || hostname.endsWith(".internal"))
|
|
558
|
+
return true;
|
|
520
559
|
if (hostname.startsWith("172.")) {
|
|
521
560
|
const second = parseInt(hostname.split(".")[1], 10);
|
|
522
561
|
if (second >= 16 && second <= 31) return true;
|
|
@@ -546,30 +585,75 @@ async function probeImageSize(imagePath, warnings) {
|
|
|
546
585
|
const result = await probe(createReadStream(resolved));
|
|
547
586
|
return result ? { width: result.width, height: result.height } : void 0;
|
|
548
587
|
} catch (err) {
|
|
549
|
-
warn(
|
|
588
|
+
warn(
|
|
589
|
+
warnings,
|
|
590
|
+
W.IMAGE_PROBE_FAILED,
|
|
591
|
+
`Image probe failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
592
|
+
{ component: "image" }
|
|
593
|
+
);
|
|
550
594
|
return void 0;
|
|
551
595
|
}
|
|
552
596
|
}
|
|
553
|
-
|
|
597
|
+
function resolveDimension(value, axisLength) {
|
|
598
|
+
if (typeof value === "number") return value;
|
|
599
|
+
if (value.endsWith("%")) {
|
|
600
|
+
const pct = parseFloat(value);
|
|
601
|
+
return !Number.isNaN(pct) && pct >= 0 ? pct / 100 * axisLength : 0;
|
|
602
|
+
}
|
|
603
|
+
const n = Number(value);
|
|
604
|
+
return Number.isNaN(n) ? 0 : n;
|
|
605
|
+
}
|
|
606
|
+
async function renderImageComponent(slide, props, theme, warnings, slideWidth = 10, slideHeight = 7.5) {
|
|
554
607
|
const opts = {};
|
|
608
|
+
const source = props.path || props.base64;
|
|
609
|
+
if (!source) {
|
|
610
|
+
warn(
|
|
611
|
+
warnings,
|
|
612
|
+
W.IMAGE_NO_SOURCE,
|
|
613
|
+
"Image component missing both path and base64",
|
|
614
|
+
{ component: "image" }
|
|
615
|
+
);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
555
618
|
if (props.path) {
|
|
556
619
|
opts.path = props.path;
|
|
557
|
-
} else if (props.base64) {
|
|
558
|
-
opts.data = props.base64;
|
|
559
620
|
} else {
|
|
560
|
-
|
|
561
|
-
return;
|
|
621
|
+
opts.data = props.base64;
|
|
562
622
|
}
|
|
563
623
|
if (props.x !== void 0) opts.x = props.x;
|
|
564
624
|
if (props.y !== void 0) opts.y = props.y;
|
|
565
625
|
if (props.w !== void 0) opts.w = props.w;
|
|
566
626
|
if (props.h !== void 0) opts.h = props.h;
|
|
627
|
+
const hasW = props.w !== void 0;
|
|
628
|
+
const hasH = props.h !== void 0;
|
|
629
|
+
const needsProbe = hasW !== hasH && !props.sizing || props.sizing?.type === "contain" || props.sizing?.type === "cover";
|
|
630
|
+
const intrinsic = needsProbe ? await probeImageSize(source, warnings) : void 0;
|
|
631
|
+
if (hasW !== hasH && !props.sizing) {
|
|
632
|
+
if (intrinsic && intrinsic.width > 0 && intrinsic.height > 0) {
|
|
633
|
+
const aspect = intrinsic.width / intrinsic.height;
|
|
634
|
+
if (hasW && !hasH) {
|
|
635
|
+
const wInches = resolveDimension(props.w, slideWidth);
|
|
636
|
+
opts.w = wInches;
|
|
637
|
+
opts.h = wInches / aspect;
|
|
638
|
+
} else {
|
|
639
|
+
const hInches = resolveDimension(props.h, slideHeight);
|
|
640
|
+
opts.h = hInches;
|
|
641
|
+
opts.w = hInches * aspect;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
567
645
|
if (props.sizing && (props.sizing.type === "contain" || props.sizing.type === "cover")) {
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
646
|
+
const boxW = resolveDimension(props.sizing.w ?? props.w ?? 0, slideWidth);
|
|
647
|
+
const boxH = resolveDimension(props.sizing.h ?? props.h ?? 0, slideHeight);
|
|
648
|
+
if (boxW <= 0 || boxH <= 0) {
|
|
649
|
+
warn(
|
|
650
|
+
warnings,
|
|
651
|
+
W.IMAGE_ZERO_BOX,
|
|
652
|
+
`Image sizing box resolved to zero (${boxW}x${boxH})`,
|
|
653
|
+
{ component: "image" }
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
if (intrinsic && intrinsic.width > 0 && intrinsic.height > 0 && boxW > 0 && boxH > 0) {
|
|
573
657
|
const imgAspect = intrinsic.width / intrinsic.height;
|
|
574
658
|
if (props.sizing.type === "contain") {
|
|
575
659
|
const boxAspect = boxW / boxH;
|
|
@@ -581,8 +665,8 @@ async function renderImageComponent(slide, props, theme, warnings) {
|
|
|
581
665
|
fitH = boxH;
|
|
582
666
|
fitW = boxH * imgAspect;
|
|
583
667
|
}
|
|
584
|
-
const baseX =
|
|
585
|
-
const baseY =
|
|
668
|
+
const baseX = resolveDimension(props.x ?? 0, slideWidth);
|
|
669
|
+
const baseY = resolveDimension(props.y ?? 0, slideHeight);
|
|
586
670
|
opts.x = baseX + (boxW - fitW) / 2;
|
|
587
671
|
opts.y = baseY + (boxH - fitH) / 2;
|
|
588
672
|
opts.w = fitW;
|
|
@@ -598,8 +682,8 @@ async function renderImageComponent(slide, props, theme, warnings) {
|
|
|
598
682
|
} else if (props.sizing) {
|
|
599
683
|
opts.sizing = {
|
|
600
684
|
...props.sizing,
|
|
601
|
-
w: props.sizing.w ?? props.w,
|
|
602
|
-
h: props.sizing.h ?? props.h
|
|
685
|
+
w: resolveDimension(props.sizing.w ?? props.w ?? 0, slideWidth),
|
|
686
|
+
h: resolveDimension(props.sizing.h ?? props.h ?? 0, slideHeight)
|
|
603
687
|
};
|
|
604
688
|
}
|
|
605
689
|
if (props.rotate !== void 0) opts.rotate = props.rotate;
|
|
@@ -663,9 +747,16 @@ function buildShapeOpts(props, theme, warnings) {
|
|
|
663
747
|
}
|
|
664
748
|
if (props.line) {
|
|
665
749
|
opts.line = {};
|
|
666
|
-
if (props.line.color)
|
|
667
|
-
|
|
668
|
-
|
|
750
|
+
if (props.line.color)
|
|
751
|
+
opts.line.color = resolveColor(
|
|
752
|
+
props.line.color,
|
|
753
|
+
theme,
|
|
754
|
+
warnings
|
|
755
|
+
);
|
|
756
|
+
if (props.line.width)
|
|
757
|
+
opts.line.width = props.line.width;
|
|
758
|
+
if (props.line.dashType)
|
|
759
|
+
opts.line.dashType = props.line.dashType;
|
|
669
760
|
}
|
|
670
761
|
if (props.rotate !== void 0) opts.rotate = props.rotate;
|
|
671
762
|
if (props.rectRadius !== void 0) opts.rectRadius = props.rectRadius;
|
|
@@ -685,7 +776,9 @@ function renderShapeComponent(slide, props, theme, pptx, warnings) {
|
|
|
685
776
|
const shapeTypeName = SHAPE_TYPE_MAP[props.type] || props.type;
|
|
686
777
|
const shapeType = pptx.ShapeType[shapeTypeName];
|
|
687
778
|
if (!shapeType) {
|
|
688
|
-
warn(warnings, W.UNKNOWN_SHAPE, `Unknown shape type: ${props.type}`, {
|
|
779
|
+
warn(warnings, W.UNKNOWN_SHAPE, `Unknown shape type: ${props.type}`, {
|
|
780
|
+
component: "shape"
|
|
781
|
+
});
|
|
689
782
|
return;
|
|
690
783
|
}
|
|
691
784
|
const style = props.style ? theme.styles?.[props.style] : void 0;
|
|
@@ -695,32 +788,69 @@ function renderShapeComponent(slide, props, theme, pptx, warnings) {
|
|
|
695
788
|
opts.shape = shapeType;
|
|
696
789
|
opts.fontSize = props.fontSize ?? style?.fontSize ?? theme.defaults.fontSize;
|
|
697
790
|
opts.fontFace = props.fontFace ?? style?.fontFace ?? (isHeadingStyle ? theme.fonts.heading : theme.fonts.body);
|
|
698
|
-
|
|
791
|
+
const preAliasFamily = opts.fontFace;
|
|
792
|
+
opts.color = resolveColor(
|
|
793
|
+
props.fontColor ?? style?.fontColor ?? theme.defaults.fontColor,
|
|
794
|
+
theme,
|
|
795
|
+
warnings
|
|
796
|
+
);
|
|
699
797
|
const bold = props.bold ?? style?.bold;
|
|
700
798
|
const italic = props.italic ?? style?.italic;
|
|
701
|
-
|
|
702
|
-
if (italic != null) opts.italic = italic;
|
|
799
|
+
const fontWeight = props.fontWeight ?? style?.fontWeight;
|
|
703
800
|
const charSpacing = props.charSpacing ?? style?.charSpacing;
|
|
704
801
|
if (charSpacing !== void 0) opts.charSpacing = charSpacing;
|
|
705
802
|
const align = props.align ?? style?.align;
|
|
706
803
|
if (align) opts.align = align;
|
|
707
804
|
opts.valign = props.valign ?? "top";
|
|
708
805
|
if (Array.isArray(props.text)) {
|
|
806
|
+
if (bold != null) opts.bold = bold;
|
|
807
|
+
if (italic != null) opts.italic = italic;
|
|
709
808
|
const textSegments = props.text.map((seg) => {
|
|
710
809
|
const segOpts = {};
|
|
711
810
|
if (seg.fontSize != null) segOpts.fontSize = seg.fontSize;
|
|
712
811
|
if (seg.fontFace != null) segOpts.fontFace = seg.fontFace;
|
|
713
|
-
if (seg.color != null)
|
|
714
|
-
|
|
715
|
-
if (seg.italic != null) segOpts.italic = seg.italic;
|
|
812
|
+
if (seg.color != null)
|
|
813
|
+
segOpts.color = resolveColor(seg.color, theme, warnings);
|
|
716
814
|
if (seg.breakLine != null) segOpts.breakLine = seg.breakLine;
|
|
717
815
|
if (seg.charSpacing != null) segOpts.charSpacing = seg.charSpacing;
|
|
718
816
|
if (seg.spaceBefore != null) segOpts.paraSpaceBefore = seg.spaceBefore;
|
|
719
817
|
if (seg.spaceAfter != null) segOpts.paraSpaceAfter = seg.spaceAfter;
|
|
818
|
+
const segWeight = seg.fontWeight;
|
|
819
|
+
const effWeight = segWeight ?? fontWeight;
|
|
820
|
+
const effBold = seg.bold ?? bold;
|
|
821
|
+
const effItalic = seg.italic ?? italic;
|
|
822
|
+
if (effBold != null) segOpts.bold = effBold;
|
|
823
|
+
if (effItalic != null) segOpts.italic = effItalic;
|
|
824
|
+
if (effWeight != null || effBold === true) {
|
|
825
|
+
if (seg.fontFace == null) {
|
|
826
|
+
const w = applyFontWeight({
|
|
827
|
+
family: preAliasFamily,
|
|
828
|
+
fontWeight: effWeight,
|
|
829
|
+
italic: effItalic,
|
|
830
|
+
bold: effBold
|
|
831
|
+
});
|
|
832
|
+
if (w.fontFace !== void 0) segOpts.fontFace = w.fontFace;
|
|
833
|
+
if (w.bold !== void 0) segOpts.bold = w.bold;
|
|
834
|
+
if (w.italic !== void 0) segOpts.italic = w.italic;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
720
837
|
return { text: seg.text, options: segOpts };
|
|
721
838
|
});
|
|
722
839
|
slide.addText(textSegments, opts);
|
|
723
840
|
} else {
|
|
841
|
+
if (bold != null) opts.bold = bold;
|
|
842
|
+
if (italic != null) opts.italic = italic;
|
|
843
|
+
if (fontWeight != null || bold === true) {
|
|
844
|
+
const w = applyFontWeight({
|
|
845
|
+
family: preAliasFamily,
|
|
846
|
+
fontWeight,
|
|
847
|
+
italic,
|
|
848
|
+
bold
|
|
849
|
+
});
|
|
850
|
+
if (w.fontFace !== void 0) opts.fontFace = w.fontFace;
|
|
851
|
+
if (w.bold !== void 0) opts.bold = w.bold;
|
|
852
|
+
if (w.italic !== void 0) opts.italic = w.italic;
|
|
853
|
+
}
|
|
724
854
|
slide.addText(props.text, opts);
|
|
725
855
|
}
|
|
726
856
|
} else {
|
|
@@ -783,14 +913,19 @@ function renderTableComponent(slide, props, theme, pptx, warnings) {
|
|
|
783
913
|
return { text: applyTextVariationSelector(cell), options: opts2 };
|
|
784
914
|
}
|
|
785
915
|
const cellOpts = {};
|
|
786
|
-
if (cell.color)
|
|
916
|
+
if (cell.color)
|
|
917
|
+
cellOpts.color = resolveColor(cell.color, theme, warnings);
|
|
787
918
|
if (bgFill) {
|
|
788
919
|
const isHeader = rowIndex === 0;
|
|
789
920
|
if (!isCorner) {
|
|
790
921
|
const resolvedFill = cell.fill ? resolveColor(cell.fill, theme, warnings) : isHeader ? headerFill : bgFill;
|
|
791
922
|
cellOpts.fill = { color: resolvedFill };
|
|
792
923
|
}
|
|
793
|
-
cellOpts.border = buildBorderRadiusBorders(
|
|
924
|
+
cellOpts.border = buildBorderRadiusBorders(
|
|
925
|
+
rowIndex,
|
|
926
|
+
colIndex,
|
|
927
|
+
row.length
|
|
928
|
+
);
|
|
794
929
|
} else if (cell.fill) {
|
|
795
930
|
cellOpts.fill = { color: resolveColor(cell.fill, theme, warnings) };
|
|
796
931
|
}
|
|
@@ -798,6 +933,17 @@ function renderTableComponent(slide, props, theme, pptx, warnings) {
|
|
|
798
933
|
if (cell.fontFace) cellOpts.fontFace = cell.fontFace;
|
|
799
934
|
if (cell.bold) cellOpts.bold = true;
|
|
800
935
|
if (cell.italic) cellOpts.italic = true;
|
|
936
|
+
if (cell.fontWeight != null || cell.bold === true) {
|
|
937
|
+
const w = applyFontWeight({
|
|
938
|
+
family: cellOpts.fontFace ?? props.fontFace ?? theme.fonts.body,
|
|
939
|
+
fontWeight: cell.fontWeight,
|
|
940
|
+
italic: cell.italic,
|
|
941
|
+
bold: cell.bold
|
|
942
|
+
});
|
|
943
|
+
if (w.fontFace !== void 0) cellOpts.fontFace = w.fontFace;
|
|
944
|
+
if (w.bold !== void 0) cellOpts.bold = w.bold;
|
|
945
|
+
if (w.italic !== void 0) cellOpts.italic = w.italic;
|
|
946
|
+
}
|
|
801
947
|
if (cell.align) cellOpts.align = cell.align;
|
|
802
948
|
if (cell.valign) cellOpts.valign = cell.valign;
|
|
803
949
|
if (cell.colspan) cellOpts.colspan = cell.colspan;
|
|
@@ -820,7 +966,8 @@ function renderTableComponent(slide, props, theme, pptx, warnings) {
|
|
|
820
966
|
color: resolveColor(props.border.color ?? "000000", theme, warnings)
|
|
821
967
|
};
|
|
822
968
|
}
|
|
823
|
-
if (props.fill)
|
|
969
|
+
if (props.fill)
|
|
970
|
+
opts.fill = { color: resolveColor(props.fill, theme, warnings) };
|
|
824
971
|
opts.fontSize = props.fontSize ?? theme.defaults.fontSize;
|
|
825
972
|
opts.fontFace = props.fontFace ?? theme.fonts.body;
|
|
826
973
|
if (props.color) opts.color = resolveColor(props.color, theme, warnings);
|
|
@@ -881,7 +1028,12 @@ function renderTableComponent(slide, props, theme, pptx, warnings) {
|
|
|
881
1028
|
}
|
|
882
1029
|
if (bgFill && borderRadiusTableW !== void 0) {
|
|
883
1030
|
opts.w = borderRadiusTableW;
|
|
884
|
-
opts.border = [
|
|
1031
|
+
opts.border = [
|
|
1032
|
+
{ type: "none" },
|
|
1033
|
+
{ type: "none" },
|
|
1034
|
+
{ type: "none" },
|
|
1035
|
+
{ type: "none" }
|
|
1036
|
+
];
|
|
885
1037
|
}
|
|
886
1038
|
slide.addTable(tableRows, opts);
|
|
887
1039
|
}
|
|
@@ -1050,16 +1202,23 @@ function renderChartComponent(slide, props, theme, _pptx, warnings) {
|
|
|
1050
1202
|
}
|
|
1051
1203
|
|
|
1052
1204
|
// src/components/index.ts
|
|
1053
|
-
async function renderComponent(slide, component, theme, pptx, warnings,
|
|
1205
|
+
async function renderComponent(slide, component, theme, pptx, warnings, ctx) {
|
|
1054
1206
|
if (component.enabled === false) return;
|
|
1055
1207
|
const { name, props } = component;
|
|
1056
1208
|
const p = props;
|
|
1057
1209
|
switch (name) {
|
|
1058
1210
|
case "text":
|
|
1059
|
-
renderTextComponent(slide, p, theme, warnings, slideCtx);
|
|
1211
|
+
renderTextComponent(slide, p, theme, warnings, ctx?.slideCtx);
|
|
1060
1212
|
break;
|
|
1061
1213
|
case "image":
|
|
1062
|
-
await renderImageComponent(
|
|
1214
|
+
await renderImageComponent(
|
|
1215
|
+
slide,
|
|
1216
|
+
p,
|
|
1217
|
+
theme,
|
|
1218
|
+
warnings,
|
|
1219
|
+
ctx?.slideWidth,
|
|
1220
|
+
ctx?.slideHeight
|
|
1221
|
+
);
|
|
1063
1222
|
break;
|
|
1064
1223
|
case "shape":
|
|
1065
1224
|
renderShapeComponent(slide, p, theme, pptx, warnings);
|
|
@@ -1073,7 +1232,7 @@ async function renderComponent(slide, component, theme, pptx, warnings, slideCtx
|
|
|
1073
1232
|
p,
|
|
1074
1233
|
theme,
|
|
1075
1234
|
warnings,
|
|
1076
|
-
services?.highcharts
|
|
1235
|
+
ctx?.services?.highcharts
|
|
1077
1236
|
);
|
|
1078
1237
|
break;
|
|
1079
1238
|
case "chart":
|
|
@@ -1159,6 +1318,12 @@ async function renderPresentation(processed, warnings) {
|
|
|
1159
1318
|
totalSlides,
|
|
1160
1319
|
pageNumberFormat: processed.pageNumberFormat
|
|
1161
1320
|
};
|
|
1321
|
+
const renderCtx = {
|
|
1322
|
+
slideCtx,
|
|
1323
|
+
services: processed.services,
|
|
1324
|
+
slideWidth: processed.slideWidth,
|
|
1325
|
+
slideHeight: processed.slideHeight
|
|
1326
|
+
};
|
|
1162
1327
|
const slide = slideData.template ? pptx.addSlide({ masterName: slideData.template }) : pptx.addSlide();
|
|
1163
1328
|
if (slideData.background) {
|
|
1164
1329
|
if (slideData.background.color) {
|
|
@@ -1198,8 +1363,7 @@ async function renderPresentation(processed, warnings) {
|
|
|
1198
1363
|
processed.theme,
|
|
1199
1364
|
pptx,
|
|
1200
1365
|
warnings,
|
|
1201
|
-
|
|
1202
|
-
processed.services
|
|
1366
|
+
renderCtx
|
|
1203
1367
|
);
|
|
1204
1368
|
}
|
|
1205
1369
|
}
|
|
@@ -1217,8 +1381,7 @@ async function renderPresentation(processed, warnings) {
|
|
|
1217
1381
|
processed.theme,
|
|
1218
1382
|
pptx,
|
|
1219
1383
|
warnings,
|
|
1220
|
-
|
|
1221
|
-
processed.services
|
|
1384
|
+
renderCtx
|
|
1222
1385
|
);
|
|
1223
1386
|
}
|
|
1224
1387
|
if (slideData.placeholders) {
|
|
@@ -1264,8 +1427,7 @@ async function renderPresentation(processed, warnings) {
|
|
|
1264
1427
|
processed.theme,
|
|
1265
1428
|
pptx,
|
|
1266
1429
|
warnings,
|
|
1267
|
-
|
|
1268
|
-
processed.services
|
|
1430
|
+
renderCtx
|
|
1269
1431
|
);
|
|
1270
1432
|
}
|
|
1271
1433
|
} else {
|
|
@@ -1291,8 +1453,7 @@ async function renderPresentation(processed, warnings) {
|
|
|
1291
1453
|
processed.theme,
|
|
1292
1454
|
pptx,
|
|
1293
1455
|
warnings,
|
|
1294
|
-
|
|
1295
|
-
processed.services
|
|
1456
|
+
renderCtx
|
|
1296
1457
|
);
|
|
1297
1458
|
} else {
|
|
1298
1459
|
warn(
|
|
@@ -1312,7 +1473,60 @@ async function renderPresentation(processed, warnings) {
|
|
|
1312
1473
|
return pptx;
|
|
1313
1474
|
}
|
|
1314
1475
|
|
|
1476
|
+
// src/core/fontResolution.ts
|
|
1477
|
+
import {
|
|
1478
|
+
collectFontNamesFromPptx,
|
|
1479
|
+
validateFontReferences,
|
|
1480
|
+
FontRegistry
|
|
1481
|
+
} from "@json-to-office/shared";
|
|
1482
|
+
import {
|
|
1483
|
+
loadFileFontSource,
|
|
1484
|
+
FontDiskCache,
|
|
1485
|
+
fetchVariableFontSource
|
|
1486
|
+
} from "@json-to-office/shared/fonts/node";
|
|
1487
|
+
async function resolveDocumentFonts(document, theme, warnings, fonts) {
|
|
1488
|
+
const names = /* @__PURE__ */ new Set();
|
|
1489
|
+
for (const n of collectFontNamesFromPptx(document)) names.add(n);
|
|
1490
|
+
for (const n of collectFontNamesFromPptx(theme)) names.add(n);
|
|
1491
|
+
if (names.size === 0) return [];
|
|
1492
|
+
const validation = validateFontReferences({
|
|
1493
|
+
referencedNames: names,
|
|
1494
|
+
registeredEntries: fonts?.extraEntries
|
|
1495
|
+
});
|
|
1496
|
+
if (validation.warnings.length > 0) {
|
|
1497
|
+
if (fonts?.strict) {
|
|
1498
|
+
throw new Error(
|
|
1499
|
+
`Unresolved font references (strict mode):
|
|
1500
|
+
` + validation.warnings.map((w) => ` - ${w.message}`).join("\n")
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
for (const w of validation.warnings) {
|
|
1504
|
+
warn(warnings, W.FONT_UNRESOLVED, w.message, {
|
|
1505
|
+
component: "fontRegistry"
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (!fonts?.onResolved) return [];
|
|
1510
|
+
const registry = new FontRegistry({
|
|
1511
|
+
opts: fonts,
|
|
1512
|
+
fileLoader: loadFileFontSource,
|
|
1513
|
+
variableLoader: fetchVariableFontSource,
|
|
1514
|
+
diskCache: fonts?.googleFonts?.cacheDir ? new FontDiskCache(fonts.googleFonts.cacheDir) : void 0
|
|
1515
|
+
});
|
|
1516
|
+
const resolved = await registry.resolveMany(names);
|
|
1517
|
+
for (const r of resolved) {
|
|
1518
|
+
for (const msg of r.warnings) {
|
|
1519
|
+
warn(warnings, W.FONT_UNRESOLVED, msg, {
|
|
1520
|
+
component: "fontRegistry"
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
fonts.onResolved(resolved);
|
|
1525
|
+
return resolved;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1315
1528
|
// src/core/generator.ts
|
|
1529
|
+
import { applyExportMode, scopedThemeName } from "@json-to-office/shared";
|
|
1316
1530
|
function isPresentationComponentDefinition(definition) {
|
|
1317
1531
|
if (typeof definition !== "object" || definition === null) return false;
|
|
1318
1532
|
const def = definition;
|
|
@@ -1341,7 +1555,47 @@ async function generateBufferWithWarnings(jsonConfig, options) {
|
|
|
1341
1555
|
component = jsonConfig;
|
|
1342
1556
|
}
|
|
1343
1557
|
const warnings = [];
|
|
1344
|
-
const
|
|
1558
|
+
const baseThemeName = component.props?.theme ?? "default";
|
|
1559
|
+
let resolvedTheme = options?.customThemes?.[baseThemeName] ?? getPptxTheme(baseThemeName);
|
|
1560
|
+
const mode = applyExportMode({
|
|
1561
|
+
doc: component,
|
|
1562
|
+
theme: resolvedTheme,
|
|
1563
|
+
fonts: options?.fonts
|
|
1564
|
+
});
|
|
1565
|
+
component = mode.doc;
|
|
1566
|
+
resolvedTheme = mode.theme;
|
|
1567
|
+
for (const w of mode.warnings) {
|
|
1568
|
+
warnings.push({
|
|
1569
|
+
code: w.code,
|
|
1570
|
+
message: w.message,
|
|
1571
|
+
component: "fontRegistry"
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
await resolveDocumentFonts(
|
|
1575
|
+
component,
|
|
1576
|
+
resolvedTheme,
|
|
1577
|
+
warnings,
|
|
1578
|
+
options?.fonts
|
|
1579
|
+
);
|
|
1580
|
+
const themeName = scopedThemeName(baseThemeName, options?.fonts?.mode);
|
|
1581
|
+
if (themeName !== baseThemeName) {
|
|
1582
|
+
component = {
|
|
1583
|
+
...component,
|
|
1584
|
+
props: { ...component.props, theme: themeName }
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
const effectiveOptions = {
|
|
1588
|
+
...options,
|
|
1589
|
+
customThemes: {
|
|
1590
|
+
...options?.customThemes ?? {},
|
|
1591
|
+
[themeName]: resolvedTheme
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
const pptx = await generatePresentation(
|
|
1595
|
+
component,
|
|
1596
|
+
effectiveOptions,
|
|
1597
|
+
warnings
|
|
1598
|
+
);
|
|
1345
1599
|
const data = await pptx.write({ outputType: "nodebuffer" });
|
|
1346
1600
|
const buffer = await neutralizeTableStyle(data);
|
|
1347
1601
|
return { buffer, warnings };
|
|
@@ -1501,6 +1755,7 @@ async function exportPluginSchema(customComponents, outputPath, options = {}) {
|
|
|
1501
1755
|
}
|
|
1502
1756
|
|
|
1503
1757
|
// src/plugin/createPresentationGenerator.ts
|
|
1758
|
+
import { applyExportMode as applyExportMode2, scopedThemeName as scopedThemeName2 } from "@json-to-office/shared";
|
|
1504
1759
|
var MEDIUM_STYLE_2_ACCENT_12 = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
|
|
1505
1760
|
var NO_STYLE_NO_GRID2 = "{2D5ABB26-0587-4C30-8999-92F81FD0307C}";
|
|
1506
1761
|
async function neutralizeTableStyle2(buffer) {
|
|
@@ -1624,7 +1879,8 @@ function createBuilderImpl(state) {
|
|
|
1624
1879
|
theme: state.theme,
|
|
1625
1880
|
customThemes: state.customThemes,
|
|
1626
1881
|
debug: state.debug,
|
|
1627
|
-
services: state.services
|
|
1882
|
+
services: state.services,
|
|
1883
|
+
fonts: state.fonts
|
|
1628
1884
|
};
|
|
1629
1885
|
return createBuilderImpl(
|
|
1630
1886
|
newState
|
|
@@ -1636,20 +1892,42 @@ function createBuilderImpl(state) {
|
|
|
1636
1892
|
if (!internalDocument || internalDocument.name !== "pptx") {
|
|
1637
1893
|
throw new Error("Top-level component must be a pptx component");
|
|
1638
1894
|
}
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1895
|
+
const baseThemeName = typeof state.theme === "string" ? state.theme : internalDocument.props.theme ?? "default";
|
|
1896
|
+
let resolvedTheme = typeof state.theme === "object" ? state.theme : state.customThemes?.[baseThemeName] ?? getPptxTheme(baseThemeName);
|
|
1641
1897
|
const warnings = [];
|
|
1642
|
-
const
|
|
1643
|
-
internalDocument
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
)
|
|
1647
|
-
|
|
1648
|
-
|
|
1898
|
+
const mode = applyExportMode2({
|
|
1899
|
+
doc: internalDocument,
|
|
1900
|
+
theme: resolvedTheme,
|
|
1901
|
+
fonts: state.fonts
|
|
1902
|
+
});
|
|
1903
|
+
resolvedTheme = mode.theme;
|
|
1904
|
+
for (const w of mode.warnings) {
|
|
1905
|
+
warnings.push({
|
|
1906
|
+
code: w.code,
|
|
1907
|
+
message: w.message,
|
|
1908
|
+
component: "fontRegistry"
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
const processedChildren = mode.doc.children ? await processAllSlides(mode.doc.children, warnings, resolvedTheme) : [];
|
|
1912
|
+
const themeName = scopedThemeName2(baseThemeName, state.fonts?.mode);
|
|
1913
|
+
const docWithScopedTheme = themeName !== baseThemeName ? {
|
|
1914
|
+
...mode.doc,
|
|
1915
|
+
props: { ...mode.doc.props, theme: themeName },
|
|
1649
1916
|
children: processedChildren
|
|
1917
|
+
} : { ...mode.doc, children: processedChildren };
|
|
1918
|
+
const processedDocument = docWithScopedTheme;
|
|
1919
|
+
await resolveDocumentFonts(
|
|
1920
|
+
processedDocument,
|
|
1921
|
+
resolvedTheme,
|
|
1922
|
+
warnings,
|
|
1923
|
+
state.fonts
|
|
1924
|
+
);
|
|
1925
|
+
const effectiveCustomThemes = {
|
|
1926
|
+
...state.customThemes ?? {},
|
|
1927
|
+
[themeName]: resolvedTheme
|
|
1650
1928
|
};
|
|
1651
1929
|
const processed = processPresentation(processedDocument, {
|
|
1652
|
-
customThemes:
|
|
1930
|
+
customThemes: effectiveCustomThemes,
|
|
1653
1931
|
services: state.services
|
|
1654
1932
|
});
|
|
1655
1933
|
const pptx = await renderPresentation(processed, warnings);
|
|
@@ -1761,7 +2039,8 @@ function createPresentationGenerator(options = {}) {
|
|
|
1761
2039
|
theme: options.theme,
|
|
1762
2040
|
customThemes: options.customThemes,
|
|
1763
2041
|
debug: options.debug ?? false,
|
|
1764
|
-
services: options.services
|
|
2042
|
+
services: options.services,
|
|
2043
|
+
fonts: options.fonts
|
|
1765
2044
|
};
|
|
1766
2045
|
return createBuilderImpl(initialState);
|
|
1767
2046
|
}
|