markdownfly 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +444 -289
- package/dist/cli.js +2840 -659
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +529 -12
- package/dist/index.js +2894 -662
- package/dist/index.js.map +1 -1
- package/package.json +78 -75
package/dist/cli.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import chalk2 from "chalk";
|
|
6
|
-
import { readFileSync as
|
|
6
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
import { readFileSync as
|
|
9
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
10
10
|
import { resolve as resolve3 } from "path";
|
|
11
11
|
|
|
12
12
|
// src/parser/index.ts
|
|
@@ -18,9 +18,12 @@ import remarkGfm from "remark-gfm";
|
|
|
18
18
|
// src/parser/frontmatter.ts
|
|
19
19
|
import { parse as parseYaml } from "yaml";
|
|
20
20
|
|
|
21
|
+
// src/models/theme-preset.ts
|
|
22
|
+
var DEFAULT_THEME_NAME = "blue";
|
|
23
|
+
|
|
21
24
|
// src/config/defaults.ts
|
|
22
25
|
var DEFAULT_CONFIG = {
|
|
23
|
-
theme:
|
|
26
|
+
theme: DEFAULT_THEME_NAME
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
// src/utils/progress.ts
|
|
@@ -71,7 +74,7 @@ function extractFrontmatter(tree) {
|
|
|
71
74
|
}
|
|
72
75
|
try {
|
|
73
76
|
const parsed = parseYaml(yamlNode.value);
|
|
74
|
-
const { resource_dir, ...parsedOnly } = parsed;
|
|
77
|
+
const { resource_dir, color_scheme, text_scheme, layout_scheme, ...parsedOnly } = parsed;
|
|
75
78
|
const merged = {
|
|
76
79
|
...DEFAULT_CONFIG,
|
|
77
80
|
...parsedOnly
|
|
@@ -79,6 +82,15 @@ function extractFrontmatter(tree) {
|
|
|
79
82
|
if (resource_dir && !merged.resourceDir) {
|
|
80
83
|
merged.resourceDir = resource_dir;
|
|
81
84
|
}
|
|
85
|
+
if (color_scheme && !merged.colorScheme) {
|
|
86
|
+
merged.colorScheme = color_scheme;
|
|
87
|
+
}
|
|
88
|
+
if (text_scheme && !merged.textScheme) {
|
|
89
|
+
merged.textScheme = text_scheme;
|
|
90
|
+
}
|
|
91
|
+
if (layout_scheme && !merged.layoutScheme) {
|
|
92
|
+
merged.layoutScheme = layout_scheme;
|
|
93
|
+
}
|
|
82
94
|
return merged;
|
|
83
95
|
} catch {
|
|
84
96
|
log.warn("Failed to parse frontmatter YAML, using defaults");
|
|
@@ -86,8 +98,19 @@ function extractFrontmatter(tree) {
|
|
|
86
98
|
}
|
|
87
99
|
}
|
|
88
100
|
|
|
101
|
+
// src/diagrams/languages.ts
|
|
102
|
+
var ALIASES = {
|
|
103
|
+
graphviz: "dot",
|
|
104
|
+
puml: "plantuml"
|
|
105
|
+
};
|
|
106
|
+
var CANONICAL = /* @__PURE__ */ new Set(["mermaid", "dot", "echarts", "plantuml"]);
|
|
107
|
+
function normalizeDiagramLanguage(language) {
|
|
108
|
+
const lang = language.toLowerCase();
|
|
109
|
+
const canonical = ALIASES[lang] ?? lang;
|
|
110
|
+
return CANONICAL.has(canonical) ? canonical : void 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
89
113
|
// src/parser/splitter.ts
|
|
90
|
-
var DIAGRAM_LANGUAGES = /* @__PURE__ */ new Set(["mermaid", "dot", "graphviz", "echarts"]);
|
|
91
114
|
var CALLOUT_VARIANTS = /* @__PURE__ */ new Set([
|
|
92
115
|
"note",
|
|
93
116
|
"info",
|
|
@@ -271,10 +294,11 @@ function nodeToElement(node) {
|
|
|
271
294
|
}
|
|
272
295
|
case "code": {
|
|
273
296
|
const lang = node.lang?.toLowerCase() ?? "";
|
|
274
|
-
|
|
297
|
+
const diagramType = normalizeDiagramLanguage(lang);
|
|
298
|
+
if (diagramType) {
|
|
275
299
|
return {
|
|
276
300
|
type: "diagram",
|
|
277
|
-
diagramType
|
|
301
|
+
diagramType,
|
|
278
302
|
content: node.value
|
|
279
303
|
};
|
|
280
304
|
}
|
|
@@ -341,6 +365,19 @@ function detectLayout(title, elements, isFirstSlide, headingLevel) {
|
|
|
341
365
|
if (nonEmptyElements.length > 0 && nonEmptyElements.every((e) => e.type === "blockquote")) {
|
|
342
366
|
return "quote";
|
|
343
367
|
}
|
|
368
|
+
if (nonEmptyElements.length > 0) {
|
|
369
|
+
const images = nonEmptyElements.filter((e) => e.type === "image");
|
|
370
|
+
const others = nonEmptyElements.filter(
|
|
371
|
+
(e) => e.type !== "image" && e.type !== "text"
|
|
372
|
+
);
|
|
373
|
+
const texts = nonEmptyElements.filter((e) => e.type === "text");
|
|
374
|
+
const substantialText = texts.some((e) => e.type === "text" && e.content.trim().length > 40);
|
|
375
|
+
if (others.length === 0 && !substantialText && images.length >= 1 && images.length <= 3) {
|
|
376
|
+
if (images.length === 1) return "image-single";
|
|
377
|
+
if (images.length === 2) return "image-double";
|
|
378
|
+
return "image-triple";
|
|
379
|
+
}
|
|
380
|
+
}
|
|
344
381
|
return "content";
|
|
345
382
|
}
|
|
346
383
|
function splitIntoSlides(tree, defaultLayout) {
|
|
@@ -351,6 +388,8 @@ function splitIntoSlides(tree, defaultLayout) {
|
|
|
351
388
|
let currentDirectives = {};
|
|
352
389
|
let currentHeadingLevel = 0;
|
|
353
390
|
let isFirstSlide = true;
|
|
391
|
+
let currentAuthor;
|
|
392
|
+
let currentDate;
|
|
354
393
|
function flushSlide() {
|
|
355
394
|
if (!currentTitle && currentElements.length === 0 && Object.keys(currentDirectives).length === 0) {
|
|
356
395
|
return;
|
|
@@ -371,19 +410,23 @@ function splitIntoSlides(tree, defaultLayout) {
|
|
|
371
410
|
currentHeadingLevel
|
|
372
411
|
);
|
|
373
412
|
const layout = currentDirectives.layout ?? (autoLayout === "content" ? defaultLayout : autoLayout) ?? "content";
|
|
413
|
+
const metadata = currentAuthor || currentDate ? { author: currentAuthor, date: currentDate } : void 0;
|
|
374
414
|
slides.push({
|
|
375
415
|
layout,
|
|
376
416
|
title: currentTitle,
|
|
377
417
|
subtitle: currentSubtitle,
|
|
378
418
|
elements: currentElements,
|
|
379
419
|
notes: currentDirectives.notes,
|
|
380
|
-
directives: toDirectives(currentDirectives)
|
|
420
|
+
directives: toDirectives(currentDirectives),
|
|
421
|
+
...metadata ? { metadata } : {}
|
|
381
422
|
});
|
|
382
423
|
currentTitle = void 0;
|
|
383
424
|
currentSubtitle = void 0;
|
|
384
425
|
currentElements = [];
|
|
385
426
|
currentDirectives = {};
|
|
386
427
|
currentHeadingLevel = 0;
|
|
428
|
+
currentAuthor = void 0;
|
|
429
|
+
currentDate = void 0;
|
|
387
430
|
isFirstSlide = false;
|
|
388
431
|
}
|
|
389
432
|
for (const node of tree.children) {
|
|
@@ -409,6 +452,31 @@ function splitIntoSlides(tree, defaultLayout) {
|
|
|
409
452
|
}
|
|
410
453
|
continue;
|
|
411
454
|
}
|
|
455
|
+
if (node.type === "paragraph" && isFirstSlide && slides.length === 0) {
|
|
456
|
+
const lines = node.children.map(extractText).join("").trim().split("\n");
|
|
457
|
+
let metaAuthor;
|
|
458
|
+
let metaDate;
|
|
459
|
+
const metaOnly = lines.every((raw) => {
|
|
460
|
+
const line = raw.trim();
|
|
461
|
+
if (!line) return true;
|
|
462
|
+
const authorMatch = line.match(/^作者\s*[::]\s*(.+)$/);
|
|
463
|
+
if (authorMatch) {
|
|
464
|
+
metaAuthor = authorMatch[1].trim();
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
const dateMatch = line.match(/^日期\s*[::]\s*(.+)$/);
|
|
468
|
+
if (dateMatch) {
|
|
469
|
+
metaDate = dateMatch[1].trim();
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
return false;
|
|
473
|
+
});
|
|
474
|
+
if (metaOnly && (metaAuthor || metaDate)) {
|
|
475
|
+
if (metaAuthor) currentAuthor = metaAuthor;
|
|
476
|
+
if (metaDate) currentDate = metaDate;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
412
480
|
const element = nodeToElement(node);
|
|
413
481
|
if (element) {
|
|
414
482
|
currentElements.push(element);
|
|
@@ -477,8 +545,69 @@ function parseMarkdown(markdown) {
|
|
|
477
545
|
// src/renderer/pptx-renderer.ts
|
|
478
546
|
import PptxGenJS from "pptxgenjs";
|
|
479
547
|
|
|
548
|
+
// src/renderer/layouts/layout-spec.ts
|
|
549
|
+
function layoutSpec(theme, key) {
|
|
550
|
+
return theme.layouts?.[key] ?? {};
|
|
551
|
+
}
|
|
552
|
+
function specNumber(value, fallback) {
|
|
553
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
554
|
+
}
|
|
555
|
+
function specAlign(value, fallback) {
|
|
556
|
+
return value ?? fallback;
|
|
557
|
+
}
|
|
558
|
+
function extraNumber(spec, key, fallback) {
|
|
559
|
+
const v = spec.extra?.[key];
|
|
560
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
561
|
+
}
|
|
562
|
+
function extraBoolean(spec, key, fallback) {
|
|
563
|
+
const v = spec.extra?.[key];
|
|
564
|
+
return typeof v === "boolean" ? v : fallback;
|
|
565
|
+
}
|
|
566
|
+
function extraString(spec, key, fallback) {
|
|
567
|
+
const v = spec.extra?.[key];
|
|
568
|
+
return typeof v === "string" && v ? v : fallback;
|
|
569
|
+
}
|
|
570
|
+
function resolveSideMargins(spec, fallback) {
|
|
571
|
+
const base = specNumber(spec.margin, fallback);
|
|
572
|
+
return {
|
|
573
|
+
left: specNumber(spec.extra?.marginLeft, base),
|
|
574
|
+
right: specNumber(spec.extra?.marginRight, base)
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function titleRuleHeight(spec, fallback) {
|
|
578
|
+
return extraNumber(spec, "titleRuleHeight", fallback);
|
|
579
|
+
}
|
|
580
|
+
function titleRuleRole(spec) {
|
|
581
|
+
const v = spec.extra?.titleRuleColor;
|
|
582
|
+
return v === "secondary" || v === "divider" ? v : "primary";
|
|
583
|
+
}
|
|
584
|
+
function ruleColor(theme, role) {
|
|
585
|
+
if (role === "secondary") return theme.colors.secondary;
|
|
586
|
+
if (role === "divider") return theme.colors.divider ?? theme.colors.secondary;
|
|
587
|
+
return theme.colors.primary;
|
|
588
|
+
}
|
|
589
|
+
function addHRuler(slide, opts) {
|
|
590
|
+
slide.addShape("rect", {
|
|
591
|
+
x: opts.x,
|
|
592
|
+
y: opts.y,
|
|
593
|
+
w: opts.w,
|
|
594
|
+
h: opts.h,
|
|
595
|
+
fill: { color: opts.color }
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
480
599
|
// src/renderer/layouts/title.ts
|
|
481
600
|
function renderTitleSlide(slide, node, theme) {
|
|
601
|
+
const spec = layoutSpec(theme, "title");
|
|
602
|
+
if (extraBoolean(spec, "minimalTitle", false)) {
|
|
603
|
+
renderMinimalTitle(slide, node, theme);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const align = specAlign(spec.titleAlign, "center");
|
|
607
|
+
const { left, right } = resolveSideMargins(spec, 0.8);
|
|
608
|
+
const spineW = extraNumber(spec, "spineWidth", 0);
|
|
609
|
+
const boxX = left + spineW;
|
|
610
|
+
const contentW = Math.max(2, 13.33 - boxX - right);
|
|
482
611
|
const textColor = theme.colors.titleText ?? "FFFFFF";
|
|
483
612
|
const subtitleColor = textColor === "FFFFFF" ? "FFFFFFCC" : theme.colors.secondary;
|
|
484
613
|
const metaColor = textColor === "FFFFFF" ? "FFFFFF99" : theme.colors.secondary;
|
|
@@ -486,65 +615,358 @@ function renderTitleSlide(slide, node, theme) {
|
|
|
486
615
|
if (bgColor) {
|
|
487
616
|
slide.background = { color: bgColor };
|
|
488
617
|
}
|
|
618
|
+
if (spineW > 0) {
|
|
619
|
+
slide.addShape("rect", {
|
|
620
|
+
x: 0,
|
|
621
|
+
y: 0,
|
|
622
|
+
w: spineW,
|
|
623
|
+
h: 7.5,
|
|
624
|
+
fill: { color: theme.colors.primary }
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
const titleY = extraNumber(spec, "titleY", 2);
|
|
628
|
+
const subtitleY = extraNumber(spec, "subtitleY", 3.8);
|
|
629
|
+
const metaBottom = extraBoolean(spec, "metaBottom", false);
|
|
630
|
+
const metaY = extraNumber(spec, "metaY", metaBottom ? 6.55 : 5.5);
|
|
489
631
|
if (node.title) {
|
|
632
|
+
const titleScale = extraNumber(spec, "titleFontSizeScale", 1);
|
|
490
633
|
slide.addText(node.title, {
|
|
491
|
-
x:
|
|
492
|
-
y:
|
|
493
|
-
w:
|
|
634
|
+
x: boxX,
|
|
635
|
+
y: titleY,
|
|
636
|
+
w: contentW,
|
|
494
637
|
h: 1.5,
|
|
495
|
-
fontSize: theme.fontSize.title,
|
|
638
|
+
fontSize: Math.round(theme.fontSize.title * titleScale),
|
|
496
639
|
fontFace: theme.fonts.heading,
|
|
497
640
|
color: textColor,
|
|
498
641
|
bold: true,
|
|
499
|
-
align
|
|
642
|
+
align,
|
|
500
643
|
valign: "middle"
|
|
501
644
|
});
|
|
645
|
+
if (extraBoolean(spec, "titleRule", false)) {
|
|
646
|
+
const ruleH = extraNumber(spec, "titleRuleHeight", 0.03);
|
|
647
|
+
const ruleY = extraNumber(spec, "titleRuleY", titleY + 1.55);
|
|
648
|
+
const ruleW = extraNumber(spec, "titleRuleWidth", Math.min(contentW, 4.2));
|
|
649
|
+
let ruleX = boxX;
|
|
650
|
+
if (align === "center") ruleX = boxX + (contentW - ruleW) / 2;
|
|
651
|
+
else if (align === "right") ruleX = boxX + contentW - ruleW;
|
|
652
|
+
addHRuler(slide, { x: ruleX, y: ruleY, w: ruleW, h: ruleH, color: ruleColor(theme, titleRuleRole(spec)) });
|
|
653
|
+
}
|
|
502
654
|
}
|
|
503
655
|
if (node.subtitle) {
|
|
504
656
|
slide.addText(node.subtitle, {
|
|
505
|
-
x:
|
|
506
|
-
y:
|
|
507
|
-
w:
|
|
657
|
+
x: boxX,
|
|
658
|
+
y: subtitleY,
|
|
659
|
+
w: contentW,
|
|
508
660
|
h: 0.8,
|
|
509
661
|
fontSize: theme.fontSize.body,
|
|
510
662
|
fontFace: theme.fonts.body,
|
|
511
663
|
color: subtitleColor,
|
|
512
|
-
align
|
|
664
|
+
align,
|
|
513
665
|
valign: "middle"
|
|
514
666
|
});
|
|
515
667
|
}
|
|
668
|
+
const meta = node.metadata ?? {};
|
|
669
|
+
const author = typeof meta.author === "string" ? meta.author : void 0;
|
|
670
|
+
const date = typeof meta.date === "string" ? meta.date : void 0;
|
|
516
671
|
const textElements = node.elements.filter((e) => e.type === "text");
|
|
517
|
-
if (
|
|
672
|
+
if (author || date) {
|
|
673
|
+
const lineH = 0.31;
|
|
674
|
+
if (author) {
|
|
675
|
+
slide.addText(author, {
|
|
676
|
+
x: boxX,
|
|
677
|
+
y: metaY,
|
|
678
|
+
w: contentW,
|
|
679
|
+
h: lineH,
|
|
680
|
+
fontSize: theme.fontSize.small,
|
|
681
|
+
fontFace: theme.fonts.body,
|
|
682
|
+
color: metaColor,
|
|
683
|
+
align,
|
|
684
|
+
valign: "middle"
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
if (date) {
|
|
688
|
+
slide.addText(date, {
|
|
689
|
+
x: boxX,
|
|
690
|
+
y: metaY + lineH + 0.03,
|
|
691
|
+
w: contentW,
|
|
692
|
+
h: lineH,
|
|
693
|
+
fontSize: theme.fontSize.small,
|
|
694
|
+
fontFace: theme.fonts.body,
|
|
695
|
+
color: metaColor,
|
|
696
|
+
align,
|
|
697
|
+
valign: "middle"
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
} else if (textElements.length > 0) {
|
|
518
701
|
const metaText = textElements.map((e) => e.content).join(" \xB7 ");
|
|
519
702
|
slide.addText(metaText, {
|
|
520
|
-
x:
|
|
521
|
-
y:
|
|
522
|
-
w:
|
|
703
|
+
x: boxX,
|
|
704
|
+
y: metaY,
|
|
705
|
+
w: contentW,
|
|
523
706
|
h: 0.6,
|
|
524
707
|
fontSize: theme.fontSize.small,
|
|
525
708
|
fontFace: theme.fonts.body,
|
|
526
709
|
color: metaColor,
|
|
527
|
-
align
|
|
710
|
+
align
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function renderMinimalTitle(slide, node, theme) {
|
|
715
|
+
const spec = layoutSpec(theme, "title");
|
|
716
|
+
const bgColor = theme.colors.backgroundGradient ? void 0 : theme.colors.titleBackground ?? theme.colors.primary;
|
|
717
|
+
if (bgColor) {
|
|
718
|
+
slide.background = { color: bgColor };
|
|
719
|
+
}
|
|
720
|
+
const textColor = theme.colors.titleText ?? "FFFFFF";
|
|
721
|
+
const softColor = textColor === "FFFFFF" ? "FFFFFFCC" : theme.colors.secondary;
|
|
722
|
+
const lineX = extraNumber(spec, "minimalLineX", 0.83);
|
|
723
|
+
const lineY1 = extraNumber(spec, "minimalLineY1", 2.64);
|
|
724
|
+
const lineH = extraNumber(spec, "minimalLineH", 1.94);
|
|
725
|
+
const lineW = extraNumber(spec, "minimalLineW", 0.04);
|
|
726
|
+
slide.addShape("rect", {
|
|
727
|
+
x: lineX,
|
|
728
|
+
y: lineY1,
|
|
729
|
+
w: lineW,
|
|
730
|
+
h: lineH,
|
|
731
|
+
fill: { color: textColor }
|
|
732
|
+
});
|
|
733
|
+
const boxX = extraNumber(spec, "minimalBoxX", 1.11);
|
|
734
|
+
const contentW = extraNumber(spec, "minimalBoxW", 8.89);
|
|
735
|
+
if (node.title) {
|
|
736
|
+
slide.addText(node.title, {
|
|
737
|
+
x: boxX,
|
|
738
|
+
y: extraNumber(spec, "minimalTitleY", 2.64),
|
|
739
|
+
w: contentW,
|
|
740
|
+
h: 0.89,
|
|
741
|
+
fontSize: theme.fontSize.title,
|
|
742
|
+
fontFace: theme.fonts.heading,
|
|
743
|
+
color: textColor,
|
|
744
|
+
bold: true,
|
|
745
|
+
align: "left",
|
|
746
|
+
valign: "middle"
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
if (node.subtitle) {
|
|
750
|
+
slide.addText(node.subtitle, {
|
|
751
|
+
x: boxX,
|
|
752
|
+
y: extraNumber(spec, "minimalSubtitleY", 3.64),
|
|
753
|
+
w: contentW,
|
|
754
|
+
h: 0.44,
|
|
755
|
+
fontSize: theme.fontSize.body,
|
|
756
|
+
fontFace: theme.fonts.body,
|
|
757
|
+
color: softColor,
|
|
758
|
+
align: "left",
|
|
759
|
+
valign: "middle"
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
const meta = node.metadata ?? {};
|
|
763
|
+
const author = typeof meta.author === "string" ? meta.author : void 0;
|
|
764
|
+
const date = typeof meta.date === "string" ? meta.date : void 0;
|
|
765
|
+
const metaX = boxX;
|
|
766
|
+
const metaW = extraNumber(spec, "minimalMetaW", 4.17);
|
|
767
|
+
const metaH = 0.31;
|
|
768
|
+
if (author) {
|
|
769
|
+
slide.addText(author, {
|
|
770
|
+
x: metaX,
|
|
771
|
+
y: extraNumber(spec, "minimalAuthorY", 5.28),
|
|
772
|
+
w: metaW,
|
|
773
|
+
h: metaH,
|
|
774
|
+
fontSize: theme.fontSize.small,
|
|
775
|
+
fontFace: theme.fonts.body,
|
|
776
|
+
color: softColor,
|
|
777
|
+
align: "left",
|
|
778
|
+
valign: "middle"
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
if (date) {
|
|
782
|
+
slide.addText(date, {
|
|
783
|
+
x: metaX,
|
|
784
|
+
y: extraNumber(spec, "minimalDateY", 5.64),
|
|
785
|
+
w: metaW,
|
|
786
|
+
h: metaH,
|
|
787
|
+
fontSize: theme.fontSize.small,
|
|
788
|
+
fontFace: theme.fonts.body,
|
|
789
|
+
color: softColor,
|
|
790
|
+
align: "left",
|
|
791
|
+
valign: "middle"
|
|
528
792
|
});
|
|
529
793
|
}
|
|
530
794
|
}
|
|
531
795
|
|
|
532
796
|
// src/renderer/layouts/section.ts
|
|
533
|
-
function
|
|
797
|
+
function formatSectionNumber(n) {
|
|
798
|
+
return String(n).padStart(2, "0");
|
|
799
|
+
}
|
|
800
|
+
function renderSectionSlide(slide, node, theme, ctx) {
|
|
801
|
+
const spec = layoutSpec(theme, "section");
|
|
802
|
+
if (extraBoolean(spec, "minimalNumber", false)) {
|
|
803
|
+
renderMinimalSection(slide, node, theme, ctx);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const barW = theme.styles?.sectionBarWidth ?? extraNumber(spec, "sectionBarWidth", 0.15);
|
|
807
|
+
const accentBar = spec.accentBar ?? "left";
|
|
808
|
+
const titleAlign = specAlign(spec.titleAlign, "left");
|
|
809
|
+
const { left, right } = resolveSideMargins(spec, accentBar === "left" ? 1 : 0.9);
|
|
810
|
+
if (accentBar !== "none") {
|
|
811
|
+
if (accentBar === "left") {
|
|
812
|
+
slide.addShape("rect", {
|
|
813
|
+
x: 0,
|
|
814
|
+
y: 0,
|
|
815
|
+
w: barW,
|
|
816
|
+
h: 7.5,
|
|
817
|
+
fill: { color: theme.colors.accent }
|
|
818
|
+
});
|
|
819
|
+
} else if (accentBar === "top") {
|
|
820
|
+
slide.addShape("rect", {
|
|
821
|
+
x: 0,
|
|
822
|
+
y: 0,
|
|
823
|
+
w: 13.33,
|
|
824
|
+
h: barW,
|
|
825
|
+
fill: { color: theme.colors.accent }
|
|
826
|
+
});
|
|
827
|
+
} else if (accentBar === "bottom") {
|
|
828
|
+
slide.addShape("rect", {
|
|
829
|
+
x: 0,
|
|
830
|
+
y: 7.5 - barW,
|
|
831
|
+
w: 13.33,
|
|
832
|
+
h: barW,
|
|
833
|
+
fill: { color: theme.colors.accent }
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const leftPad = accentBar === "left" ? Math.max(left, barW + 0.85) : left;
|
|
838
|
+
const textW = Math.max(3, 13.33 - leftPad - right);
|
|
839
|
+
if (extraBoolean(spec, "doubleRules", false)) {
|
|
840
|
+
const weight = extraNumber(spec, "ruleWeight", 0.035);
|
|
841
|
+
const bandY = extraNumber(spec, "bandY", 2.35);
|
|
842
|
+
const bandH = extraNumber(spec, "bandH", 1.7);
|
|
843
|
+
const color = ruleColor(theme, titleRuleRole(spec) === "primary" ? "primary" : titleRuleRole(spec));
|
|
844
|
+
const ruleX = leftPad;
|
|
845
|
+
const ruleW = textW;
|
|
846
|
+
addHRuler(slide, { x: ruleX, y: bandY, w: ruleW, h: weight, color });
|
|
847
|
+
addHRuler(slide, { x: ruleX, y: bandY + bandH, w: ruleW, h: weight, color });
|
|
848
|
+
if (node.title) {
|
|
849
|
+
slide.addText(node.title, {
|
|
850
|
+
x: leftPad,
|
|
851
|
+
y: bandY + weight + 0.08,
|
|
852
|
+
w: textW,
|
|
853
|
+
h: bandH - weight * 2 - 0.16,
|
|
854
|
+
fontSize: theme.fontSize.heading + 6,
|
|
855
|
+
fontFace: theme.fonts.heading,
|
|
856
|
+
color: theme.colors.text,
|
|
857
|
+
bold: true,
|
|
858
|
+
align: titleAlign,
|
|
859
|
+
valign: "middle"
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
const sub = node.subtitle?.trim();
|
|
863
|
+
if (sub) {
|
|
864
|
+
slide.addText(sub, {
|
|
865
|
+
x: leftPad,
|
|
866
|
+
y: bandY + bandH + 0.35,
|
|
867
|
+
w: textW,
|
|
868
|
+
h: 0.45,
|
|
869
|
+
fontSize: theme.fontSize.small,
|
|
870
|
+
fontFace: theme.fonts.body,
|
|
871
|
+
color: theme.colors.secondary,
|
|
872
|
+
align: titleAlign
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (extraBoolean(spec, "sectionRule", false)) {
|
|
878
|
+
const secRuleY = extraNumber(spec, "sectionRuleY", 4.64);
|
|
879
|
+
const secRuleW = extraNumber(spec, "sectionRuleWidth", 5);
|
|
880
|
+
const secRuleH = extraNumber(spec, "sectionRuleHeight", 0.04);
|
|
881
|
+
const secRuleX = extraNumber(
|
|
882
|
+
spec,
|
|
883
|
+
"sectionRuleX",
|
|
884
|
+
titleAlign === "right" ? 13.33 - right - secRuleW : leftPad
|
|
885
|
+
);
|
|
886
|
+
addHRuler(slide, {
|
|
887
|
+
x: secRuleX,
|
|
888
|
+
y: secRuleY,
|
|
889
|
+
w: secRuleW,
|
|
890
|
+
h: secRuleH,
|
|
891
|
+
color: ruleColor(
|
|
892
|
+
theme,
|
|
893
|
+
extraString(spec, "sectionRuleColor", "primary")
|
|
894
|
+
)
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
if (node.subtitle) {
|
|
898
|
+
const subY = extraNumber(spec, "subtitleY", 0.85);
|
|
899
|
+
const subH = extraNumber(spec, "subtitleH", 0.45);
|
|
900
|
+
slide.addText(node.subtitle, {
|
|
901
|
+
x: leftPad,
|
|
902
|
+
y: subY,
|
|
903
|
+
w: textW,
|
|
904
|
+
h: subH,
|
|
905
|
+
fontSize: theme.fontSize.body,
|
|
906
|
+
fontFace: theme.fonts.body,
|
|
907
|
+
color: theme.colors.secondary,
|
|
908
|
+
align: titleAlign,
|
|
909
|
+
valign: "middle"
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
if (node.title) {
|
|
913
|
+
const tY = extraNumber(spec, "titleY", 2.5);
|
|
914
|
+
const tH = extraNumber(spec, "titleH", 2);
|
|
915
|
+
slide.addText(node.title, {
|
|
916
|
+
x: leftPad,
|
|
917
|
+
y: tY,
|
|
918
|
+
w: textW,
|
|
919
|
+
h: tH,
|
|
920
|
+
fontSize: theme.fontSize.heading + 4,
|
|
921
|
+
fontFace: theme.fonts.heading,
|
|
922
|
+
color: theme.colors.text,
|
|
923
|
+
bold: true,
|
|
924
|
+
align: titleAlign,
|
|
925
|
+
valign: "middle"
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
function renderMinimalSection(slide, node, theme, ctx) {
|
|
930
|
+
const spec = layoutSpec(theme, "section");
|
|
931
|
+
const num = formatSectionNumber(ctx?.sectionNumber ?? 1);
|
|
932
|
+
const nX = extraNumber(spec, "minimalNumberX", 1.11);
|
|
933
|
+
const nY = extraNumber(spec, "minimalNumberY", 2.43);
|
|
934
|
+
const nW = extraNumber(spec, "minimalNumberW", 2.5);
|
|
935
|
+
const nH = extraNumber(spec, "minimalNumberH", 1.53);
|
|
936
|
+
slide.addText(num, {
|
|
937
|
+
x: nX,
|
|
938
|
+
y: nY,
|
|
939
|
+
w: nW,
|
|
940
|
+
h: nH,
|
|
941
|
+
fontSize: theme.fontSize.title,
|
|
942
|
+
fontFace: theme.fonts.heading,
|
|
943
|
+
color: theme.colors.primary,
|
|
944
|
+
bold: true,
|
|
945
|
+
align: "left",
|
|
946
|
+
valign: "middle"
|
|
947
|
+
});
|
|
948
|
+
const rX = extraNumber(spec, "minimalRuleX", 3.89);
|
|
949
|
+
const rY1 = extraNumber(spec, "minimalRuleY1", 2.71);
|
|
950
|
+
const rH = extraNumber(spec, "minimalRuleH", 1.11);
|
|
951
|
+
const rW = extraNumber(spec, "minimalRuleW", 0.03);
|
|
534
952
|
slide.addShape("rect", {
|
|
535
|
-
x:
|
|
536
|
-
y:
|
|
537
|
-
w:
|
|
538
|
-
h:
|
|
539
|
-
fill: { color: theme.colors.
|
|
953
|
+
x: rX,
|
|
954
|
+
y: rY1,
|
|
955
|
+
w: rW,
|
|
956
|
+
h: rH,
|
|
957
|
+
fill: { color: theme.colors.divider ?? theme.colors.secondary }
|
|
540
958
|
});
|
|
541
959
|
if (node.title) {
|
|
960
|
+
const tX = extraNumber(spec, "minimalTitleX", 4.31);
|
|
961
|
+
const tY = extraNumber(spec, "minimalTitleY", rY1);
|
|
962
|
+
const tW = extraNumber(spec, "minimalTitleW", 6.94);
|
|
963
|
+
const tH = extraNumber(spec, "minimalTitleH", 0.69);
|
|
542
964
|
slide.addText(node.title, {
|
|
543
|
-
x:
|
|
544
|
-
y:
|
|
545
|
-
w:
|
|
546
|
-
h:
|
|
547
|
-
fontSize: theme.fontSize.heading
|
|
965
|
+
x: tX,
|
|
966
|
+
y: tY,
|
|
967
|
+
w: tW,
|
|
968
|
+
h: tH,
|
|
969
|
+
fontSize: theme.fontSize.heading,
|
|
548
970
|
fontFace: theme.fonts.heading,
|
|
549
971
|
color: theme.colors.text,
|
|
550
972
|
bold: true,
|
|
@@ -554,15 +976,135 @@ function renderSectionSlide(slide, node, theme) {
|
|
|
554
976
|
}
|
|
555
977
|
}
|
|
556
978
|
|
|
557
|
-
// src/utils/
|
|
558
|
-
function
|
|
559
|
-
if (data.length < 24) return null;
|
|
560
|
-
if (data.readUInt32BE(0) !== 2303741511) return null;
|
|
561
|
-
const width = data.readUInt32BE(16);
|
|
562
|
-
const height = data.readUInt32BE(20);
|
|
979
|
+
// src/utils/image-size.ts
|
|
980
|
+
function positive(width, height) {
|
|
563
981
|
if (!(width > 0 && height > 0)) return null;
|
|
564
982
|
return { width, height };
|
|
565
983
|
}
|
|
984
|
+
function pngSize(data) {
|
|
985
|
+
if (data.length < 24) return null;
|
|
986
|
+
if (data.readUInt32BE(0) !== 2303741511) return null;
|
|
987
|
+
return positive(data.readUInt32BE(16), data.readUInt32BE(20));
|
|
988
|
+
}
|
|
989
|
+
function isFrameMarker(marker) {
|
|
990
|
+
return marker >= 192 && marker <= 207 && marker !== 196 && // DHT
|
|
991
|
+
marker !== 200 && // JPG
|
|
992
|
+
marker !== 204;
|
|
993
|
+
}
|
|
994
|
+
function jpegSize(data) {
|
|
995
|
+
if (data.length < 4 || data[0] !== 255 || data[1] !== 216) return null;
|
|
996
|
+
let offset = 2;
|
|
997
|
+
while (offset + 3 < data.length) {
|
|
998
|
+
if (data[offset] !== 255) {
|
|
999
|
+
offset++;
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
let marker = data[offset + 1];
|
|
1003
|
+
while (marker === 255 && offset + 2 < data.length) {
|
|
1004
|
+
offset++;
|
|
1005
|
+
marker = data[offset + 1];
|
|
1006
|
+
}
|
|
1007
|
+
offset += 2;
|
|
1008
|
+
if (marker === 1 || marker >= 208 && marker <= 216) continue;
|
|
1009
|
+
if (marker === 218 || marker === 217) return null;
|
|
1010
|
+
if (offset + 2 > data.length) return null;
|
|
1011
|
+
const length = data.readUInt16BE(offset);
|
|
1012
|
+
if (length < 2) return null;
|
|
1013
|
+
if (isFrameMarker(marker)) {
|
|
1014
|
+
if (offset + 7 > data.length) return null;
|
|
1015
|
+
return positive(data.readUInt16BE(offset + 5), data.readUInt16BE(offset + 3));
|
|
1016
|
+
}
|
|
1017
|
+
offset += length;
|
|
1018
|
+
}
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
function gifSize(data) {
|
|
1022
|
+
if (data.length < 10) return null;
|
|
1023
|
+
const signature = data.toString("latin1", 0, 6);
|
|
1024
|
+
if (signature !== "GIF87a" && signature !== "GIF89a") return null;
|
|
1025
|
+
return positive(data.readUInt16LE(6), data.readUInt16LE(8));
|
|
1026
|
+
}
|
|
1027
|
+
function webpSize(data) {
|
|
1028
|
+
if (data.length < 30) return null;
|
|
1029
|
+
if (data.toString("latin1", 0, 4) !== "RIFF") return null;
|
|
1030
|
+
if (data.toString("latin1", 8, 12) !== "WEBP") return null;
|
|
1031
|
+
const chunk = data.toString("latin1", 12, 16);
|
|
1032
|
+
if (chunk === "VP8X") {
|
|
1033
|
+
return positive(data.readUIntLE(24, 3) + 1, data.readUIntLE(27, 3) + 1);
|
|
1034
|
+
}
|
|
1035
|
+
if (chunk === "VP8 ") {
|
|
1036
|
+
if (data[23] !== 157 || data[24] !== 1 || data[25] !== 42) return null;
|
|
1037
|
+
return positive(data.readUInt16LE(26) & 16383, data.readUInt16LE(28) & 16383);
|
|
1038
|
+
}
|
|
1039
|
+
if (chunk === "VP8L") {
|
|
1040
|
+
if (data[20] !== 47) return null;
|
|
1041
|
+
const bits = data.readUInt32LE(21);
|
|
1042
|
+
return positive((bits & 16383) + 1, (bits >>> 14 & 16383) + 1);
|
|
1043
|
+
}
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
function bmpSize(data) {
|
|
1047
|
+
if (data.length < 26) return null;
|
|
1048
|
+
if (data[0] !== 66 || data[1] !== 77) return null;
|
|
1049
|
+
const headerSize = data.readUInt32LE(14);
|
|
1050
|
+
if (headerSize === 12) {
|
|
1051
|
+
return positive(data.readUInt16LE(18), data.readUInt16LE(20));
|
|
1052
|
+
}
|
|
1053
|
+
if (headerSize >= 40) {
|
|
1054
|
+
return positive(Math.abs(data.readInt32LE(18)), Math.abs(data.readInt32LE(22)));
|
|
1055
|
+
}
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
var SVG_UNITS = {
|
|
1059
|
+
px: 1,
|
|
1060
|
+
pt: 96 / 72,
|
|
1061
|
+
pc: 16,
|
|
1062
|
+
mm: 96 / 25.4,
|
|
1063
|
+
cm: 96 / 2.54,
|
|
1064
|
+
in: 96,
|
|
1065
|
+
q: 96 / 101.6
|
|
1066
|
+
};
|
|
1067
|
+
function parseSvgLength(value) {
|
|
1068
|
+
if (!value) return void 0;
|
|
1069
|
+
const match = value.match(/^([\d.]+)\s*([a-z%]*)$/i);
|
|
1070
|
+
if (!match) return void 0;
|
|
1071
|
+
const n = parseFloat(match[1]);
|
|
1072
|
+
if (!(n > 0)) return void 0;
|
|
1073
|
+
const unit = match[2].toLowerCase();
|
|
1074
|
+
if (unit === "%") return void 0;
|
|
1075
|
+
const scale = SVG_UNITS[unit || "px"];
|
|
1076
|
+
return scale === void 0 ? void 0 : n * scale;
|
|
1077
|
+
}
|
|
1078
|
+
function findSvgRoot(data) {
|
|
1079
|
+
const head = data.toString("utf8", 0, Math.min(data.length, 4096));
|
|
1080
|
+
return head.match(/<svg\b[^>]*>/i);
|
|
1081
|
+
}
|
|
1082
|
+
function isSvgImage(data) {
|
|
1083
|
+
return findSvgRoot(data) !== null;
|
|
1084
|
+
}
|
|
1085
|
+
function svgSize(data) {
|
|
1086
|
+
const root = findSvgRoot(data);
|
|
1087
|
+
if (!root) return null;
|
|
1088
|
+
const attribute = (name) => {
|
|
1089
|
+
const match = root[0].match(new RegExp(`\\b${name}\\s*=\\s*["']([^"']*)["']`, "i"));
|
|
1090
|
+
return match?.[1]?.trim();
|
|
1091
|
+
};
|
|
1092
|
+
const viewBox = attribute("viewBox");
|
|
1093
|
+
if (viewBox) {
|
|
1094
|
+
const parts = viewBox.split(/[\s,]+/).map(Number);
|
|
1095
|
+
if (parts.length === 4 && parts[2] > 0 && parts[3] > 0) {
|
|
1096
|
+
return { width: parts[2], height: parts[3] };
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
const width = parseSvgLength(attribute("width"));
|
|
1100
|
+
const height = parseSvgLength(attribute("height"));
|
|
1101
|
+
if (width === void 0 || height === void 0) return null;
|
|
1102
|
+
return positive(width, height);
|
|
1103
|
+
}
|
|
1104
|
+
function getImageSize(data) {
|
|
1105
|
+
if (data.length < 10) return null;
|
|
1106
|
+
return pngSize(data) ?? jpegSize(data) ?? gifSize(data) ?? webpSize(data) ?? bmpSize(data) ?? svgSize(data);
|
|
1107
|
+
}
|
|
566
1108
|
|
|
567
1109
|
// src/utils/image-fit.ts
|
|
568
1110
|
function fitInBox(img, maxWidth, maxHeight) {
|
|
@@ -655,11 +1197,31 @@ function tableToChartOption(table, type) {
|
|
|
655
1197
|
import { readFileSync } from "fs";
|
|
656
1198
|
var SLIDE_W = 13.33;
|
|
657
1199
|
var SLIDE_H = 7.5;
|
|
658
|
-
var
|
|
659
|
-
var
|
|
660
|
-
var
|
|
661
|
-
var
|
|
662
|
-
|
|
1200
|
+
var DEFAULT_MARGIN = 0.6;
|
|
1201
|
+
var DEFAULT_TITLE_H = 0.9;
|
|
1202
|
+
var DEFAULT_BOTTOM_MARGIN = 0.45;
|
|
1203
|
+
var DEFAULT_GRID_GAP = 0.25;
|
|
1204
|
+
function contentMetrics(theme) {
|
|
1205
|
+
const spec = layoutSpec(theme, "content");
|
|
1206
|
+
const { left, right } = resolveSideMargins(spec, DEFAULT_MARGIN);
|
|
1207
|
+
const titleH = specNumber(spec.titleHeight, DEFAULT_TITLE_H);
|
|
1208
|
+
const contentPadding = specNumber(spec.contentPadding, 0);
|
|
1209
|
+
const contentW = Math.max(2, SLIDE_W - left - right);
|
|
1210
|
+
return {
|
|
1211
|
+
marginLeft: left,
|
|
1212
|
+
marginRight: right,
|
|
1213
|
+
titleH,
|
|
1214
|
+
titleY: extraNumber(spec, "titleY", 0.3),
|
|
1215
|
+
contentW,
|
|
1216
|
+
bottom: DEFAULT_BOTTOM_MARGIN,
|
|
1217
|
+
gridGap: DEFAULT_GRID_GAP,
|
|
1218
|
+
contentPadding,
|
|
1219
|
+
titleRule: extraBoolean(spec, "titleRule", true),
|
|
1220
|
+
titleRuleH: titleRuleHeight(spec, 0.04),
|
|
1221
|
+
titleRuleWidth: extraNumber(spec, "titleRuleWidth", contentW),
|
|
1222
|
+
titleRuleColor: titleRuleRole(spec)
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
663
1225
|
var IMAGE_MIME = {
|
|
664
1226
|
png: "image/png",
|
|
665
1227
|
jpg: "image/jpeg",
|
|
@@ -669,10 +1231,10 @@ var IMAGE_MIME = {
|
|
|
669
1231
|
bmp: "image/bmp",
|
|
670
1232
|
svg: "image/svg+xml"
|
|
671
1233
|
};
|
|
672
|
-
function contentAreaInsets(node) {
|
|
1234
|
+
function contentAreaInsets(node, metrics) {
|
|
673
1235
|
return {
|
|
674
|
-
top: node.title ?
|
|
675
|
-
bottom:
|
|
1236
|
+
top: node.title ? metrics.titleY + metrics.titleH + 0.2 : metrics.titleY,
|
|
1237
|
+
bottom: metrics.bottom
|
|
676
1238
|
};
|
|
677
1239
|
}
|
|
678
1240
|
function splitGrid(elements) {
|
|
@@ -703,20 +1265,32 @@ function splitGrid(elements) {
|
|
|
703
1265
|
});
|
|
704
1266
|
}
|
|
705
1267
|
async function renderContentSlide(slide, node, theme, ctx) {
|
|
706
|
-
const
|
|
1268
|
+
const spec = layoutSpec(theme, "content");
|
|
1269
|
+
if (extraBoolean(spec, "minimalBox", false)) {
|
|
1270
|
+
await renderMinimalContent(slide, node, theme, ctx);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
const metrics = contentMetrics(theme);
|
|
1274
|
+
const { marginLeft, titleH, titleY, contentW, gridGap, contentPadding, titleRuleWidth } = metrics;
|
|
1275
|
+
const { top } = contentAreaInsets(node, metrics);
|
|
1276
|
+
const pad = contentPadding > 0 ? contentPadding : 0;
|
|
1277
|
+
const drawX = marginLeft + pad;
|
|
1278
|
+
const drawW = contentW - pad * 2;
|
|
707
1279
|
if (node.title) {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1280
|
+
if (metrics.titleRule && metrics.titleRuleH > 0) {
|
|
1281
|
+
addHRuler(slide, {
|
|
1282
|
+
x: marginLeft,
|
|
1283
|
+
y: titleY + titleH - metrics.titleRuleH - 0.01,
|
|
1284
|
+
w: titleRuleWidth,
|
|
1285
|
+
h: metrics.titleRuleH,
|
|
1286
|
+
color: ruleColor(theme, metrics.titleRuleColor)
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
715
1289
|
slide.addText(node.title, {
|
|
716
|
-
x:
|
|
717
|
-
y:
|
|
718
|
-
w:
|
|
719
|
-
h:
|
|
1290
|
+
x: marginLeft,
|
|
1291
|
+
y: titleY,
|
|
1292
|
+
w: contentW,
|
|
1293
|
+
h: titleH,
|
|
720
1294
|
fontSize: theme.fontSize.heading,
|
|
721
1295
|
fontFace: theme.fonts.heading,
|
|
722
1296
|
color: theme.colors.primary,
|
|
@@ -726,11 +1300,11 @@ async function renderContentSlide(slide, node, theme, ctx) {
|
|
|
726
1300
|
}
|
|
727
1301
|
const grid = splitGrid(prepareElements(node));
|
|
728
1302
|
const rows = grid.length;
|
|
729
|
-
const availH = SLIDE_H - top -
|
|
1303
|
+
const availH = SLIDE_H - top - metrics.bottom - gridGap * (rows - 1);
|
|
730
1304
|
const ests = grid.map(
|
|
731
1305
|
(columns) => Math.max(
|
|
732
1306
|
0.8,
|
|
733
|
-
...columns.map((c) => estimateColumnHeight(c, (
|
|
1307
|
+
...columns.map((c) => estimateColumnHeight(c, (drawW - gridGap * (columns.length - 1)) / columns.length, theme))
|
|
734
1308
|
)
|
|
735
1309
|
);
|
|
736
1310
|
const totalEst = ests.reduce((a, b) => a + b, 0);
|
|
@@ -739,19 +1313,19 @@ async function renderContentSlide(slide, node, theme, ctx) {
|
|
|
739
1313
|
for (let r = 0; r < rows; r++) {
|
|
740
1314
|
const columns = grid[r];
|
|
741
1315
|
const cols = columns.length;
|
|
742
|
-
const colW = (
|
|
1316
|
+
const colW = (drawW - gridGap * (cols - 1)) / cols;
|
|
743
1317
|
const rowH = ests[r] * scale;
|
|
744
1318
|
for (let c = 0; c < cols; c++) {
|
|
745
1319
|
await renderColumn(
|
|
746
1320
|
slide,
|
|
747
1321
|
columns[c],
|
|
748
|
-
{ x:
|
|
1322
|
+
{ x: drawX + c * (colW + gridGap), y, w: colW, h: rowH },
|
|
749
1323
|
node,
|
|
750
1324
|
theme,
|
|
751
1325
|
ctx
|
|
752
1326
|
);
|
|
753
1327
|
}
|
|
754
|
-
y += rowH +
|
|
1328
|
+
y += rowH + gridGap;
|
|
755
1329
|
}
|
|
756
1330
|
}
|
|
757
1331
|
function prepareElements(node) {
|
|
@@ -771,16 +1345,30 @@ function prepareElements(node) {
|
|
|
771
1345
|
}
|
|
772
1346
|
return elements;
|
|
773
1347
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1348
|
+
var CODE_INSET_IN = 16 / 72;
|
|
1349
|
+
var CODE_LINE_FACTOR = 1.3;
|
|
1350
|
+
function codeBlockHeight(lines, fontSizePt) {
|
|
1351
|
+
return Math.max(1, lines * fontSizePt * CODE_LINE_FACTOR / 72 + CODE_INSET_IN);
|
|
777
1352
|
}
|
|
778
|
-
function
|
|
1353
|
+
function displayWidth(text) {
|
|
1354
|
+
let units = 0;
|
|
1355
|
+
for (const char of text) {
|
|
1356
|
+
units += (char.codePointAt(0) ?? 0) > 11904 ? 2 : 1;
|
|
1357
|
+
}
|
|
1358
|
+
return units;
|
|
1359
|
+
}
|
|
1360
|
+
function charsPerLine(widthIn, fontSizePt) {
|
|
1361
|
+
return Math.max(8, Math.floor(widthIn * 72 / (fontSizePt * 0.5)));
|
|
1362
|
+
}
|
|
1363
|
+
function estimateTextLines(content, w, fontSizePt) {
|
|
1364
|
+
return Math.max(1, Math.ceil(displayWidth(content) / charsPerLine(w, fontSizePt)));
|
|
1365
|
+
}
|
|
1366
|
+
function estimateColumnHeight(elements, w, theme) {
|
|
779
1367
|
let h = 0;
|
|
780
1368
|
for (const el of elements) {
|
|
781
1369
|
switch (el.type) {
|
|
782
1370
|
case "text":
|
|
783
|
-
h += Math.max(0.5,
|
|
1371
|
+
h += Math.max(0.5, estimateTextLines(el.content, w, theme.fontSize.body) * 0.35);
|
|
784
1372
|
break;
|
|
785
1373
|
case "heading":
|
|
786
1374
|
h += 0.7;
|
|
@@ -789,7 +1377,7 @@ function estimateColumnHeight(elements, w) {
|
|
|
789
1377
|
h += el.items.length * 0.5;
|
|
790
1378
|
break;
|
|
791
1379
|
case "code":
|
|
792
|
-
h +=
|
|
1380
|
+
h += codeBlockHeight(el.content.split("\n").length, theme.fontSize.code);
|
|
793
1381
|
break;
|
|
794
1382
|
case "diagram":
|
|
795
1383
|
case "image":
|
|
@@ -805,7 +1393,7 @@ function estimateColumnHeight(elements, w) {
|
|
|
805
1393
|
h += (el.rows.length + 1) * 0.4;
|
|
806
1394
|
break;
|
|
807
1395
|
case "callout":
|
|
808
|
-
h += Math.min(4.5, 0.72 + estimateTextLines(el.content, w) * 0.3);
|
|
1396
|
+
h += Math.min(4.5, 0.72 + estimateTextLines(el.content, w, theme.fontSize.body) * 0.3);
|
|
809
1397
|
break;
|
|
810
1398
|
case "blockquote":
|
|
811
1399
|
h += 1;
|
|
@@ -819,7 +1407,7 @@ function estimateColumnHeight(elements, w) {
|
|
|
819
1407
|
return h;
|
|
820
1408
|
}
|
|
821
1409
|
async function renderColumn(slide, elements, box, node, theme, ctx) {
|
|
822
|
-
const est = estimateColumnHeight(elements, box.w);
|
|
1410
|
+
const est = estimateColumnHeight(elements, box.w, theme);
|
|
823
1411
|
const yPos = box.h > est ? box.y + (box.h - est) / 2 : box.y;
|
|
824
1412
|
let cursor = yPos;
|
|
825
1413
|
for (const element of elements) {
|
|
@@ -833,11 +1421,13 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
833
1421
|
switch (element.type) {
|
|
834
1422
|
case "text": {
|
|
835
1423
|
if (!element.content.trim()) return 0;
|
|
1424
|
+
const lines = estimateTextLines(element.content, w, theme.fontSize.body);
|
|
1425
|
+
const height = Math.max(0.5, lines * 0.35);
|
|
836
1426
|
slide.addText(element.content, {
|
|
837
1427
|
x,
|
|
838
1428
|
y: yPos,
|
|
839
1429
|
w,
|
|
840
|
-
h:
|
|
1430
|
+
h: height,
|
|
841
1431
|
fontSize: theme.fontSize.body,
|
|
842
1432
|
fontFace: theme.fonts.body,
|
|
843
1433
|
color: theme.colors.text,
|
|
@@ -846,9 +1436,7 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
846
1436
|
valign: "top",
|
|
847
1437
|
wrap: true
|
|
848
1438
|
});
|
|
849
|
-
|
|
850
|
-
const lines = Math.ceil(element.content.length / charsPerLine);
|
|
851
|
-
return Math.max(0.5, lines * 0.35);
|
|
1439
|
+
return height;
|
|
852
1440
|
}
|
|
853
1441
|
case "heading": {
|
|
854
1442
|
slide.addText(element.content, {
|
|
@@ -902,7 +1490,7 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
902
1490
|
element.highlightLines
|
|
903
1491
|
);
|
|
904
1492
|
const lines = element.content.split("\n").length;
|
|
905
|
-
const height = Math.min(maxH,
|
|
1493
|
+
const height = Math.min(maxH, codeBlockHeight(lines, theme.fontSize.code));
|
|
906
1494
|
slide.addText(runs, {
|
|
907
1495
|
x,
|
|
908
1496
|
y: yPos,
|
|
@@ -920,12 +1508,16 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
920
1508
|
case "diagram": {
|
|
921
1509
|
try {
|
|
922
1510
|
const pngBuffer = await ctx.renderDiagram(element.diagramType, element.content);
|
|
923
|
-
const imgSize =
|
|
1511
|
+
const imgSize = getImageSize(pngBuffer) ?? { width: 8, height: 3.5 };
|
|
924
1512
|
const boxH = Math.max(1.2, maxH);
|
|
925
1513
|
const fitted = fitInBox(imgSize, w, boxH);
|
|
926
1514
|
const base64 = pngBuffer.toString("base64");
|
|
927
1515
|
slide.addImage({
|
|
928
1516
|
data: `image/png;base64,${base64}`,
|
|
1517
|
+
// A diagram has no alt text of its own, so the slide title describes
|
|
1518
|
+
// it best; without one, name the notation at least. Passing nothing
|
|
1519
|
+
// would leave pptxgenjs' internal placeholder filename as the alt text.
|
|
1520
|
+
altText: node.title || `${element.diagramType} diagram`,
|
|
929
1521
|
x: x + (w - fitted.width) / 2,
|
|
930
1522
|
y: yPos,
|
|
931
1523
|
w: fitted.width,
|
|
@@ -933,7 +1525,10 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
933
1525
|
});
|
|
934
1526
|
return fitted.height + 0.2;
|
|
935
1527
|
} catch (err) {
|
|
936
|
-
|
|
1528
|
+
const reason = err instanceof Error ? err.message : "unknown";
|
|
1529
|
+
const message = `[Diagram render error: ${reason}]`;
|
|
1530
|
+
log.warn(`[${node.title ?? "slide"}] ${message}`);
|
|
1531
|
+
slide.addText(message, {
|
|
937
1532
|
x,
|
|
938
1533
|
y: yPos,
|
|
939
1534
|
w,
|
|
@@ -952,7 +1547,8 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
952
1547
|
log.warn(`[${node.title ?? "slide"}] ${resolved.error}`);
|
|
953
1548
|
return 0;
|
|
954
1549
|
}
|
|
955
|
-
const
|
|
1550
|
+
const fileData = readFileSync(resolved.path);
|
|
1551
|
+
const imgSize = getImageSize(fileData) ?? { width: 6, height: 3 };
|
|
956
1552
|
const boxH = Math.max(1.2, maxH);
|
|
957
1553
|
const placed = fitImageWithOptions(
|
|
958
1554
|
imgSize,
|
|
@@ -961,10 +1557,19 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
961
1557
|
);
|
|
962
1558
|
const ext = resolved.path.split(".").pop()?.toLowerCase() ?? "png";
|
|
963
1559
|
const mime = IMAGE_MIME[ext] ?? "image/png";
|
|
964
|
-
const data = `${mime};base64,${
|
|
1560
|
+
const data = `${mime};base64,${fileData.toString("base64")}`;
|
|
1561
|
+
if (ext === "webp") {
|
|
1562
|
+
log.warn(
|
|
1563
|
+
`[${node.title ?? "slide"}] WebP does not render in PowerPoint for the web or Office 2019 and earlier \u2014 some recipients may see a broken image (${element.src})`
|
|
1564
|
+
);
|
|
1565
|
+
}
|
|
965
1566
|
slide.addImage({
|
|
966
1567
|
data,
|
|
967
1568
|
path: "preencoded.png",
|
|
1569
|
+
// The author's alt text when they wrote one; otherwise the source they
|
|
1570
|
+
// referenced, which at least beats pptxgenjs' internal placeholder name.
|
|
1571
|
+
// Falsy, not nullish: `` parses to an empty alt string.
|
|
1572
|
+
altText: element.alt || element.src,
|
|
968
1573
|
x: x + placed.x,
|
|
969
1574
|
y: yPos,
|
|
970
1575
|
w: placed.width,
|
|
@@ -1049,7 +1654,7 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
1049
1654
|
};
|
|
1050
1655
|
const color = palette[element.variant] ?? theme.colors.primary;
|
|
1051
1656
|
const label = element.title ?? element.variant.toUpperCase();
|
|
1052
|
-
const textLines = estimateTextLines(element.content, w);
|
|
1657
|
+
const textLines = estimateTextLines(element.content, w, theme.fontSize.body);
|
|
1053
1658
|
const cardH = Math.min(maxH, 0.72 + textLines * 0.3);
|
|
1054
1659
|
const textH = cardH - 0.44;
|
|
1055
1660
|
slide.addShape("roundRect", {
|
|
@@ -1095,22 +1700,120 @@ async function renderElement(slide, element, yPos, box, node, theme, ctx) {
|
|
|
1095
1700
|
return 0;
|
|
1096
1701
|
}
|
|
1097
1702
|
}
|
|
1703
|
+
async function renderMinimalContent(slide, node, theme, ctx) {
|
|
1704
|
+
const spec = layoutSpec(theme, "content");
|
|
1705
|
+
const margin = specNumber(spec.margin, 0.83);
|
|
1706
|
+
const boxX = extraNumber(spec, "minimalBoxX", margin);
|
|
1707
|
+
const boxY = extraNumber(spec, "minimalBoxY", 1.33);
|
|
1708
|
+
const boxW = extraNumber(spec, "minimalBoxW", SLIDE_W - margin * 2);
|
|
1709
|
+
const boxH = extraNumber(spec, "minimalBoxH", 5.28);
|
|
1710
|
+
const lineColor = theme.colors.divider ?? theme.colors.secondary;
|
|
1711
|
+
if (node.title) {
|
|
1712
|
+
slide.addText(node.title, {
|
|
1713
|
+
x: boxX,
|
|
1714
|
+
y: extraNumber(spec, "minimalTitleY", 0.5),
|
|
1715
|
+
w: boxW,
|
|
1716
|
+
h: 0.44,
|
|
1717
|
+
fontSize: theme.fontSize.heading,
|
|
1718
|
+
fontFace: theme.fonts.heading,
|
|
1719
|
+
color: theme.colors.primary,
|
|
1720
|
+
bold: true,
|
|
1721
|
+
align: "left",
|
|
1722
|
+
valign: "middle"
|
|
1723
|
+
});
|
|
1724
|
+
addHRuler(slide, {
|
|
1725
|
+
x: boxX,
|
|
1726
|
+
y: extraNumber(spec, "minimalTitleRuleY", 1.03),
|
|
1727
|
+
w: extraNumber(spec, "minimalTitleRuleW", 0.56),
|
|
1728
|
+
h: extraNumber(spec, "minimalTitleRuleH", 0.03),
|
|
1729
|
+
color: lineColor
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
slide.addShape("rect", {
|
|
1733
|
+
x: boxX,
|
|
1734
|
+
y: boxY,
|
|
1735
|
+
w: boxW,
|
|
1736
|
+
h: boxH,
|
|
1737
|
+
fill: { transparency: 100 },
|
|
1738
|
+
line: { color: lineColor, width: 0.01 }
|
|
1739
|
+
});
|
|
1740
|
+
const gap = 0.25;
|
|
1741
|
+
const pad = 0.3;
|
|
1742
|
+
const inner = { x: boxX + pad, y: boxY + pad, w: boxW - pad * 2, h: boxH - pad * 2 };
|
|
1743
|
+
const innerBox = {
|
|
1744
|
+
x: inner.x,
|
|
1745
|
+
y: inner.y,
|
|
1746
|
+
w: inner.w,
|
|
1747
|
+
h: inner.h
|
|
1748
|
+
};
|
|
1749
|
+
const grid = splitGrid(prepareElements(node));
|
|
1750
|
+
const rows = grid.length;
|
|
1751
|
+
const availH = innerBox.h - gap * (rows - 1);
|
|
1752
|
+
const ests = grid.map(
|
|
1753
|
+
(columns) => Math.max(
|
|
1754
|
+
0.8,
|
|
1755
|
+
...columns.map((c) => estimateColumnHeight(c, (innerBox.w - gap * (columns.length - 1)) / columns.length, theme))
|
|
1756
|
+
)
|
|
1757
|
+
);
|
|
1758
|
+
const totalEst = ests.reduce((a, b) => a + b, 0);
|
|
1759
|
+
const scale = totalEst > availH ? availH / totalEst : 1;
|
|
1760
|
+
let y = innerBox.y;
|
|
1761
|
+
for (let r = 0; r < rows; r++) {
|
|
1762
|
+
const columns = grid[r];
|
|
1763
|
+
const cols = columns.length;
|
|
1764
|
+
const colW = (innerBox.w - gap * (cols - 1)) / cols;
|
|
1765
|
+
const rowH = ests[r] * scale;
|
|
1766
|
+
for (let c = 0; c < cols; c++) {
|
|
1767
|
+
await renderColumn(
|
|
1768
|
+
slide,
|
|
1769
|
+
columns[c],
|
|
1770
|
+
{ x: innerBox.x + c * (colW + gap), y, w: colW, h: rowH },
|
|
1771
|
+
node,
|
|
1772
|
+
theme,
|
|
1773
|
+
ctx
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
y += rowH + gap;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1098
1779
|
|
|
1099
1780
|
// src/renderer/layouts/code.ts
|
|
1100
1781
|
async function renderCodeSlide(slide, node, theme, ctx) {
|
|
1101
|
-
|
|
1782
|
+
const spec = layoutSpec(theme, "code");
|
|
1783
|
+
if (extraBoolean(spec, "minimalCard", false)) {
|
|
1784
|
+
await renderMinimalCode(slide, node, theme, ctx);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const { left, right } = resolveSideMargins(spec, 0.5);
|
|
1788
|
+
const titleH = specNumber(spec.titleHeight, 0.7);
|
|
1789
|
+
const titleAlign = specAlign(spec.titleAlign, "left");
|
|
1790
|
+
const contentW = Math.max(3, 13.33 - left - right);
|
|
1791
|
+
const titleGap = 0.2;
|
|
1792
|
+
let yPos = extraNumber(spec, "titleY", 0.3);
|
|
1102
1793
|
if (node.title) {
|
|
1103
1794
|
slide.addText(node.title, {
|
|
1104
|
-
x:
|
|
1795
|
+
x: left,
|
|
1105
1796
|
y: yPos,
|
|
1106
|
-
w:
|
|
1107
|
-
h:
|
|
1797
|
+
w: contentW,
|
|
1798
|
+
h: titleH,
|
|
1108
1799
|
fontSize: theme.fontSize.heading,
|
|
1109
1800
|
fontFace: theme.fonts.heading,
|
|
1110
1801
|
color: theme.colors.primary,
|
|
1111
|
-
bold: true
|
|
1802
|
+
bold: true,
|
|
1803
|
+
align: titleAlign === "center" ? "center" : "left"
|
|
1112
1804
|
});
|
|
1113
|
-
|
|
1805
|
+
if (extraBoolean(spec, "titleRule", false)) {
|
|
1806
|
+
const rh = titleRuleHeight(spec, 0.03);
|
|
1807
|
+
const ruleW = extraNumber(spec, "titleRuleWidth", contentW);
|
|
1808
|
+
addHRuler(slide, {
|
|
1809
|
+
x: left,
|
|
1810
|
+
y: yPos + titleH + 0.02,
|
|
1811
|
+
w: ruleW,
|
|
1812
|
+
h: rh,
|
|
1813
|
+
color: ruleColor(theme, titleRuleRole(spec))
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
yPos += titleH + titleGap;
|
|
1114
1817
|
}
|
|
1115
1818
|
for (const element of node.elements) {
|
|
1116
1819
|
if (element.type === "code") {
|
|
@@ -1120,10 +1823,10 @@ async function renderCodeSlide(slide, node, theme, ctx) {
|
|
|
1120
1823
|
element.highlightLines
|
|
1121
1824
|
);
|
|
1122
1825
|
slide.addText(runs, {
|
|
1123
|
-
x:
|
|
1826
|
+
x: left,
|
|
1124
1827
|
y: yPos,
|
|
1125
|
-
w:
|
|
1126
|
-
h: 7.5 - yPos - 0.
|
|
1828
|
+
w: contentW,
|
|
1829
|
+
h: 7.5 - yPos - 0.45,
|
|
1127
1830
|
fill: { color: theme.colors.codeBackground },
|
|
1128
1831
|
color: theme.colors.codeText,
|
|
1129
1832
|
fontFace: theme.fonts.code,
|
|
@@ -1136,54 +1839,521 @@ async function renderCodeSlide(slide, node, theme, ctx) {
|
|
|
1136
1839
|
}
|
|
1137
1840
|
}
|
|
1138
1841
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
let quoteText = quoteElement.content;
|
|
1155
|
-
let attribution = "";
|
|
1156
|
-
const attrMatch = quoteText.match(/\n?\s*[—\u2014-]\s*(.+)$/);
|
|
1157
|
-
if (attrMatch) {
|
|
1158
|
-
attribution = attrMatch[1].trim();
|
|
1159
|
-
quoteText = quoteText.slice(0, attrMatch.index).trim();
|
|
1160
|
-
}
|
|
1161
|
-
slide.addText(quoteText, {
|
|
1162
|
-
x: 1.8,
|
|
1163
|
-
y: 2.5,
|
|
1164
|
-
w: 9.7,
|
|
1165
|
-
h: 2.5,
|
|
1842
|
+
async function renderMinimalCode(slide, node, theme, ctx) {
|
|
1843
|
+
const spec = layoutSpec(theme, "code");
|
|
1844
|
+
const margin = specNumber(spec.margin, 0.83);
|
|
1845
|
+
const boxX = extraNumber(spec, "minimalCardX", margin);
|
|
1846
|
+
const boxY = extraNumber(spec, "minimalCardY", 1.33);
|
|
1847
|
+
const boxW = extraNumber(spec, "minimalCardW", 13.33 - margin * 2);
|
|
1848
|
+
const boxH = extraNumber(spec, "minimalCardH", 5.28);
|
|
1849
|
+
const lineColor = theme.colors.divider ?? theme.colors.secondary;
|
|
1850
|
+
const radius = extraNumber(spec, "minimalCardRadius", 0.08);
|
|
1851
|
+
if (node.title) {
|
|
1852
|
+
slide.addText(node.title, {
|
|
1853
|
+
x: boxX,
|
|
1854
|
+
y: extraNumber(spec, "minimalTitleY", 0.5),
|
|
1855
|
+
w: boxW,
|
|
1856
|
+
h: 0.44,
|
|
1166
1857
|
fontSize: theme.fontSize.heading,
|
|
1167
|
-
fontFace: theme.fonts.
|
|
1168
|
-
color: theme.colors.
|
|
1169
|
-
|
|
1170
|
-
align: "
|
|
1858
|
+
fontFace: theme.fonts.heading,
|
|
1859
|
+
color: theme.colors.primary,
|
|
1860
|
+
bold: true,
|
|
1861
|
+
align: "left",
|
|
1171
1862
|
valign: "middle"
|
|
1172
1863
|
});
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
fontFace: theme.fonts.body,
|
|
1181
|
-
color: theme.colors.secondary,
|
|
1182
|
-
align: "right"
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1864
|
+
addHRuler(slide, {
|
|
1865
|
+
x: boxX,
|
|
1866
|
+
y: extraNumber(spec, "minimalTitleRuleY", 1.03),
|
|
1867
|
+
w: extraNumber(spec, "minimalTitleRuleW", 0.56),
|
|
1868
|
+
h: extraNumber(spec, "minimalTitleRuleH", 0.03),
|
|
1869
|
+
color: lineColor
|
|
1870
|
+
});
|
|
1185
1871
|
}
|
|
1186
|
-
|
|
1872
|
+
slide.addShape("roundRect", {
|
|
1873
|
+
x: boxX,
|
|
1874
|
+
y: boxY,
|
|
1875
|
+
w: boxW,
|
|
1876
|
+
h: boxH,
|
|
1877
|
+
rectRadius: radius,
|
|
1878
|
+
fill: { color: theme.colors.codeBackground },
|
|
1879
|
+
line: { color: lineColor, width: 0.01 }
|
|
1880
|
+
});
|
|
1881
|
+
const pad = 0.3;
|
|
1882
|
+
for (const element of node.elements) {
|
|
1883
|
+
if (element.type !== "code") continue;
|
|
1884
|
+
const runs = await ctx.highlightCode(
|
|
1885
|
+
element.content,
|
|
1886
|
+
element.language ?? "text",
|
|
1887
|
+
element.highlightLines
|
|
1888
|
+
);
|
|
1889
|
+
slide.addText(runs, {
|
|
1890
|
+
x: boxX + pad,
|
|
1891
|
+
y: boxY + pad,
|
|
1892
|
+
w: boxW - pad * 2,
|
|
1893
|
+
h: boxH - pad * 2,
|
|
1894
|
+
fill: { color: theme.colors.codeBackground },
|
|
1895
|
+
color: theme.colors.codeText,
|
|
1896
|
+
fontFace: theme.fonts.code,
|
|
1897
|
+
fontSize: theme.fontSize.code,
|
|
1898
|
+
valign: "top",
|
|
1899
|
+
paraSpaceAfter: 2,
|
|
1900
|
+
margin: [8, 12, 8, 12]
|
|
1901
|
+
});
|
|
1902
|
+
break;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
// src/renderer/layouts/quote.ts
|
|
1907
|
+
function renderQuoteSlide(slide, node, theme) {
|
|
1908
|
+
const spec = layoutSpec(theme, "quote");
|
|
1909
|
+
if (extraBoolean(spec, "minimalQuote", false)) {
|
|
1910
|
+
renderMinimalQuote(slide, node, theme);
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
const showMark = extraBoolean(spec, "quoteMark", true);
|
|
1914
|
+
const markSize = extraNumber(spec, "quoteMarkSize", 72);
|
|
1915
|
+
const align = specAlign(spec.titleAlign, "center");
|
|
1916
|
+
const { left, right } = resolveSideMargins(spec, 1.5);
|
|
1917
|
+
const quoteBar = extraBoolean(spec, "quoteBar", false);
|
|
1918
|
+
const barW = extraNumber(spec, "quoteBarWidth", 0.09);
|
|
1919
|
+
const attrAlign = extraString(spec, "attributionAlign", "right");
|
|
1920
|
+
if (showMark && !quoteBar) {
|
|
1921
|
+
slide.addText("\u201C", {
|
|
1922
|
+
x: left,
|
|
1923
|
+
y: 1,
|
|
1924
|
+
w: 2,
|
|
1925
|
+
h: 1.5,
|
|
1926
|
+
fontSize: markSize,
|
|
1927
|
+
fontFace: "Georgia",
|
|
1928
|
+
color: theme.colors.accent,
|
|
1929
|
+
bold: true
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
const quoteElement = node.elements.find((e) => e.type === "blockquote");
|
|
1933
|
+
if (!quoteElement || quoteElement.type !== "blockquote") return;
|
|
1934
|
+
let quoteText = quoteElement.content;
|
|
1935
|
+
let attribution = "";
|
|
1936
|
+
const attrMatch = quoteText.match(/\n?\s*[—–-]\s*(.+)$/);
|
|
1937
|
+
if (attrMatch) {
|
|
1938
|
+
attribution = attrMatch[1].trim();
|
|
1939
|
+
quoteText = quoteText.slice(0, attrMatch.index).trim();
|
|
1940
|
+
}
|
|
1941
|
+
const textX = quoteBar ? left + barW + 0.35 : Math.max(left, showMark && !quoteBar ? 1.8 : left);
|
|
1942
|
+
const textW = Math.max(4, 13.33 - textX - right);
|
|
1943
|
+
const textY = extraNumber(spec, "textY", quoteBar ? 2.2 : 2.5);
|
|
1944
|
+
const textH = extraNumber(spec, "textH", quoteBar ? 2.8 : 2.5);
|
|
1945
|
+
if (quoteBar) {
|
|
1946
|
+
addHRuler(slide, {
|
|
1947
|
+
// vertical bar via tall thin rect
|
|
1948
|
+
x: left,
|
|
1949
|
+
y: textY,
|
|
1950
|
+
w: barW,
|
|
1951
|
+
h: textH,
|
|
1952
|
+
color: theme.colors.accent
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
const quoteRule = extraBoolean(spec, "quoteRule", false);
|
|
1956
|
+
if (quoteRule) {
|
|
1957
|
+
const qRuleY = extraNumber(spec, "quoteRuleY", 2.19);
|
|
1958
|
+
const qRuleW = extraNumber(spec, "quoteRuleWidth", 1.33);
|
|
1959
|
+
const qRuleH = extraNumber(spec, "quoteRuleHeight", 0.04);
|
|
1960
|
+
const qRuleColor = ruleColor(
|
|
1961
|
+
theme,
|
|
1962
|
+
extraString(spec, "quoteRuleColor", "primary")
|
|
1963
|
+
);
|
|
1964
|
+
const qRuleX = (13.33 - qRuleW) / 2;
|
|
1965
|
+
addHRuler(slide, {
|
|
1966
|
+
x: qRuleX,
|
|
1967
|
+
y: qRuleY,
|
|
1968
|
+
w: qRuleW,
|
|
1969
|
+
h: qRuleH,
|
|
1970
|
+
color: qRuleColor
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
slide.addText(quoteText, {
|
|
1974
|
+
x: textX,
|
|
1975
|
+
y: textY,
|
|
1976
|
+
w: textW,
|
|
1977
|
+
h: textH,
|
|
1978
|
+
fontSize: theme.fontSize.heading,
|
|
1979
|
+
fontFace: theme.fonts.body,
|
|
1980
|
+
color: theme.colors.text,
|
|
1981
|
+
italic: true,
|
|
1982
|
+
align,
|
|
1983
|
+
valign: "middle"
|
|
1984
|
+
});
|
|
1985
|
+
if (attribution) {
|
|
1986
|
+
slide.addText(`\u2014 ${attribution}`, {
|
|
1987
|
+
x: textX,
|
|
1988
|
+
y: textY + textH + 0.25,
|
|
1989
|
+
w: textW,
|
|
1990
|
+
h: 0.55,
|
|
1991
|
+
fontSize: theme.fontSize.body,
|
|
1992
|
+
fontFace: theme.fonts.body,
|
|
1993
|
+
color: theme.colors.secondary,
|
|
1994
|
+
align: attrAlign === "left" ? "left" : attrAlign === "center" ? "center" : "right"
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
function renderMinimalQuote(slide, node, theme) {
|
|
1999
|
+
const spec = layoutSpec(theme, "quote");
|
|
2000
|
+
const lineX = extraNumber(spec, "minimalRuleX", 1.67);
|
|
2001
|
+
const lineY = extraNumber(spec, "minimalRuleY", 2.78);
|
|
2002
|
+
const lineH = extraNumber(spec, "minimalRuleH", 1.39);
|
|
2003
|
+
const lineW = extraNumber(spec, "minimalRuleW", 0.04);
|
|
2004
|
+
const color = theme.colors.divider ?? theme.colors.secondary;
|
|
2005
|
+
const textX = extraNumber(spec, "minimalTextX", 2.08);
|
|
2006
|
+
const textY = lineY;
|
|
2007
|
+
const textW = extraNumber(spec, "minimalTextW", 9.58);
|
|
2008
|
+
const textH = extraNumber(spec, "minimalTextH", 1.39);
|
|
2009
|
+
const quoteElement = node.elements.find((e) => e.type === "blockquote");
|
|
2010
|
+
if (!quoteElement || quoteElement.type !== "blockquote") return;
|
|
2011
|
+
let quoteText = quoteElement.content;
|
|
2012
|
+
let attribution = "";
|
|
2013
|
+
const attrMatch = quoteText.match(/\n?\s*[—–-]\s*(.+)$/);
|
|
2014
|
+
if (attrMatch) {
|
|
2015
|
+
attribution = attrMatch[1].trim();
|
|
2016
|
+
quoteText = quoteText.slice(0, attrMatch.index).trim();
|
|
2017
|
+
}
|
|
2018
|
+
slide.addShape("rect", {
|
|
2019
|
+
x: lineX,
|
|
2020
|
+
y: lineY,
|
|
2021
|
+
w: lineW,
|
|
2022
|
+
h: lineH,
|
|
2023
|
+
fill: { color }
|
|
2024
|
+
});
|
|
2025
|
+
slide.addText(quoteText, {
|
|
2026
|
+
x: textX,
|
|
2027
|
+
y: textY,
|
|
2028
|
+
w: textW,
|
|
2029
|
+
h: textH,
|
|
2030
|
+
fontSize: theme.fontSize.heading,
|
|
2031
|
+
fontFace: theme.fonts.body,
|
|
2032
|
+
color: theme.colors.text,
|
|
2033
|
+
italic: true,
|
|
2034
|
+
align: "left",
|
|
2035
|
+
valign: "middle",
|
|
2036
|
+
wrap: true
|
|
2037
|
+
});
|
|
2038
|
+
if (attribution) {
|
|
2039
|
+
slide.addText(`\u2014 ${attribution}`, {
|
|
2040
|
+
x: textX,
|
|
2041
|
+
y: textY + textH + 0.3,
|
|
2042
|
+
w: textW,
|
|
2043
|
+
h: 0.4,
|
|
2044
|
+
fontSize: theme.fontSize.body,
|
|
2045
|
+
fontFace: theme.fonts.body,
|
|
2046
|
+
color: theme.colors.secondary,
|
|
2047
|
+
align: "left"
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
// src/renderer/layouts/closing.ts
|
|
2053
|
+
function renderClosingSlide(slide, node, theme) {
|
|
2054
|
+
const spec = layoutSpec(theme, "closing");
|
|
2055
|
+
if (extraBoolean(spec, "minimalClosing", false)) {
|
|
2056
|
+
renderMinimalClosing(slide, node, theme);
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
const align = specAlign(spec.titleAlign, "center");
|
|
2060
|
+
const { left, right } = resolveSideMargins(spec, 0.8);
|
|
2061
|
+
const contentW = Math.max(3, 13.33 - left - right);
|
|
2062
|
+
const thankText = extraString(spec, "thankText", "Thank you");
|
|
2063
|
+
const primary = node.title?.trim() || thankText;
|
|
2064
|
+
const textY = extraNumber(spec, "textY", 2.7);
|
|
2065
|
+
const showRules = extraBoolean(spec, "titleRule", false);
|
|
2066
|
+
const color = ruleColor(theme, titleRuleRole(spec) === "primary" ? "primary" : titleRuleRole(spec));
|
|
2067
|
+
const ruleW = extraNumber(spec, "ruleWidth", Math.min(contentW, 3.2));
|
|
2068
|
+
const closingRule = extraString(spec, "closingRule", "both");
|
|
2069
|
+
const ruleH = extraNumber(spec, "ruleHeight", 0.03);
|
|
2070
|
+
const ruleY = extraNumber(spec, "ruleY", textY + 1.5);
|
|
2071
|
+
const ruleX = align === "center" ? left + (contentW - ruleW) / 2 : align === "right" ? left + contentW - ruleW : left;
|
|
2072
|
+
const drawAbove = showRules && (closingRule === "both" || closingRule === "above");
|
|
2073
|
+
const drawBelow = showRules && (closingRule === "both" || closingRule === "below");
|
|
2074
|
+
if (drawAbove) {
|
|
2075
|
+
addHRuler(slide, { x: ruleX, y: textY - 0.35, w: ruleW, h: ruleH, color });
|
|
2076
|
+
}
|
|
2077
|
+
slide.addText(primary, {
|
|
2078
|
+
x: left,
|
|
2079
|
+
y: textY,
|
|
2080
|
+
w: contentW,
|
|
2081
|
+
h: 1.3,
|
|
2082
|
+
fontSize: theme.fontSize.title,
|
|
2083
|
+
fontFace: theme.fonts.heading,
|
|
2084
|
+
color: theme.colors.primary,
|
|
2085
|
+
bold: true,
|
|
2086
|
+
align,
|
|
2087
|
+
valign: "middle"
|
|
2088
|
+
});
|
|
2089
|
+
if (drawBelow) {
|
|
2090
|
+
addHRuler(slide, { x: ruleX, y: ruleY, w: ruleW, h: ruleH, color });
|
|
2091
|
+
}
|
|
2092
|
+
const secondary = node.subtitle?.trim();
|
|
2093
|
+
if (secondary) {
|
|
2094
|
+
slide.addText(secondary, {
|
|
2095
|
+
x: left,
|
|
2096
|
+
y: textY + 1.7,
|
|
2097
|
+
w: contentW,
|
|
2098
|
+
h: 0.6,
|
|
2099
|
+
fontSize: theme.fontSize.body,
|
|
2100
|
+
fontFace: theme.fonts.body,
|
|
2101
|
+
color: theme.colors.secondary,
|
|
2102
|
+
align,
|
|
2103
|
+
valign: "middle"
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
function renderMinimalClosing(slide, node, theme) {
|
|
2108
|
+
const spec = layoutSpec(theme, "closing");
|
|
2109
|
+
const color = theme.colors.divider ?? theme.colors.secondary;
|
|
2110
|
+
const thankText = extraString(spec, "thankText", "Thank you");
|
|
2111
|
+
const primary = node.title?.trim() || thankText;
|
|
2112
|
+
const ruleW = extraNumber(spec, "minimalRuleW", 1.11);
|
|
2113
|
+
const ruleH = extraNumber(spec, "minimalRuleH", 0.03);
|
|
2114
|
+
const ruleY = extraNumber(spec, "minimalRuleY", 2.78);
|
|
2115
|
+
addHRuler(slide, {
|
|
2116
|
+
x: (13.33 - ruleW) / 2,
|
|
2117
|
+
y: ruleY,
|
|
2118
|
+
w: ruleW,
|
|
2119
|
+
h: ruleH,
|
|
2120
|
+
color
|
|
2121
|
+
});
|
|
2122
|
+
slide.addText(primary, {
|
|
2123
|
+
x: extraNumber(spec, "minimalThankX", 2.5),
|
|
2124
|
+
y: extraNumber(spec, "minimalThankY", 3.06),
|
|
2125
|
+
w: extraNumber(spec, "minimalThankW", 8.33),
|
|
2126
|
+
h: 0.83,
|
|
2127
|
+
fontSize: theme.fontSize.title,
|
|
2128
|
+
fontFace: theme.fonts.heading,
|
|
2129
|
+
color: theme.colors.primary,
|
|
2130
|
+
bold: true,
|
|
2131
|
+
align: "center",
|
|
2132
|
+
valign: "middle"
|
|
2133
|
+
});
|
|
2134
|
+
const contact = node.subtitle?.trim() ?? "";
|
|
2135
|
+
if (contact) {
|
|
2136
|
+
slide.addText(contact, {
|
|
2137
|
+
x: extraNumber(spec, "minimalContactX", 3.89),
|
|
2138
|
+
y: extraNumber(spec, "minimalContactY", 4.31),
|
|
2139
|
+
w: extraNumber(spec, "minimalContactW", 5.56),
|
|
2140
|
+
h: 0.33,
|
|
2141
|
+
fontSize: theme.fontSize.body,
|
|
2142
|
+
fontFace: theme.fonts.body,
|
|
2143
|
+
color: theme.colors.secondary,
|
|
2144
|
+
align: "center",
|
|
2145
|
+
valign: "middle"
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// src/renderer/layouts/image-pages.ts
|
|
2151
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2152
|
+
var SLIDE_W2 = 13.33;
|
|
2153
|
+
var SLIDE_H2 = 7.5;
|
|
2154
|
+
var IMAGE_MIME2 = {
|
|
2155
|
+
png: "image/png",
|
|
2156
|
+
jpg: "image/jpeg",
|
|
2157
|
+
jpeg: "image/jpeg",
|
|
2158
|
+
gif: "image/gif",
|
|
2159
|
+
webp: "image/webp",
|
|
2160
|
+
bmp: "image/bmp",
|
|
2161
|
+
svg: "image/svg+xml"
|
|
2162
|
+
};
|
|
2163
|
+
function specFor(theme, slots) {
|
|
2164
|
+
if (slots === 1) return layoutSpec(theme, "imageSingle");
|
|
2165
|
+
if (slots === 2) return layoutSpec(theme, "imageDouble");
|
|
2166
|
+
return layoutSpec(theme, "imageTriple");
|
|
2167
|
+
}
|
|
2168
|
+
function pickImages(node, slots) {
|
|
2169
|
+
const images = node.elements.filter((e) => e.type === "image").slice(0, slots);
|
|
2170
|
+
const captions = node.elements.filter((e) => e.type === "text").map((e) => e.type === "text" ? e.content.trim() : "").filter((t) => t.length > 0 && t.length <= 40);
|
|
2171
|
+
return { images, captions };
|
|
2172
|
+
}
|
|
2173
|
+
async function placeImage(slide, element, box, node, ctx) {
|
|
2174
|
+
try {
|
|
2175
|
+
const resolved = await ctx.resolveImage(element.src);
|
|
2176
|
+
if (!resolved.ok) {
|
|
2177
|
+
log.warn(`[${node.title ?? "slide"}] ${resolved.error}`);
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
const fileData = readFileSync2(resolved.path);
|
|
2181
|
+
const imgSize = getImageSize(fileData) ?? { width: 6, height: 3 };
|
|
2182
|
+
const fitted = fitInBox(imgSize, box.w, box.h);
|
|
2183
|
+
const ext = resolved.path.split(".").pop()?.toLowerCase() ?? "png";
|
|
2184
|
+
const mime = IMAGE_MIME2[ext] ?? "image/png";
|
|
2185
|
+
const data = `${mime};base64,${fileData.toString("base64")}`;
|
|
2186
|
+
if (ext === "webp") {
|
|
2187
|
+
log.warn(
|
|
2188
|
+
`[${node.title ?? "slide"}] WebP does not render in PowerPoint for the web or Office 2019 and earlier \u2014 some recipients may see a broken image (${element.src})`
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
const x = box.x + (box.w - fitted.width) / 2;
|
|
2192
|
+
const y = box.y + (box.h - fitted.height) / 2;
|
|
2193
|
+
slide.addImage({
|
|
2194
|
+
data,
|
|
2195
|
+
altText: element.alt || element.src,
|
|
2196
|
+
x,
|
|
2197
|
+
y,
|
|
2198
|
+
w: fitted.width,
|
|
2199
|
+
h: fitted.height
|
|
2200
|
+
});
|
|
2201
|
+
} catch (err) {
|
|
2202
|
+
const reason = err instanceof Error ? err.message : "unknown";
|
|
2203
|
+
log.warn(`[${node.title ?? "slide"}] image error: ${reason}`);
|
|
2204
|
+
slide.addText(`[Image error: ${reason}]`, {
|
|
2205
|
+
x: box.x,
|
|
2206
|
+
y: box.y,
|
|
2207
|
+
w: box.w,
|
|
2208
|
+
h: 0.4,
|
|
2209
|
+
fontSize: 12,
|
|
2210
|
+
color: "FF0000"
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
async function renderImageSlide(slide, node, theme, ctx, slots) {
|
|
2215
|
+
const spec = specFor(theme, slots);
|
|
2216
|
+
if (extraBoolean(spec, "minimalImage", false)) {
|
|
2217
|
+
await renderMinimalImage(slide, node, theme, ctx, slots, spec);
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
const { left, right } = resolveSideMargins(spec, 0.6);
|
|
2221
|
+
const titleH = specNumber(spec.titleHeight, 0.9);
|
|
2222
|
+
const titleAlign = specAlign(spec.titleAlign, "left");
|
|
2223
|
+
const contentW = Math.max(2, SLIDE_W2 - left - right);
|
|
2224
|
+
let top = extraNumber(spec, "titleY", 0.3);
|
|
2225
|
+
if (node.title) {
|
|
2226
|
+
if (extraBoolean(spec, "titleRule", true)) {
|
|
2227
|
+
const rh = titleRuleHeight(spec, 0.04);
|
|
2228
|
+
const ruleW = extraNumber(spec, "titleRuleWidth", contentW);
|
|
2229
|
+
addHRuler(slide, {
|
|
2230
|
+
x: left,
|
|
2231
|
+
y: top + titleH - rh - 0.01,
|
|
2232
|
+
w: ruleW,
|
|
2233
|
+
h: rh,
|
|
2234
|
+
color: ruleColor(theme, titleRuleRole(spec))
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
slide.addText(node.title, {
|
|
2238
|
+
x: left,
|
|
2239
|
+
y: top,
|
|
2240
|
+
w: contentW,
|
|
2241
|
+
h: titleH,
|
|
2242
|
+
fontSize: theme.fontSize.heading,
|
|
2243
|
+
fontFace: theme.fonts.heading,
|
|
2244
|
+
color: theme.colors.primary,
|
|
2245
|
+
bold: true,
|
|
2246
|
+
align: titleAlign === "center" ? "center" : "left",
|
|
2247
|
+
valign: "bottom"
|
|
2248
|
+
});
|
|
2249
|
+
top = top + titleH + 0.2;
|
|
2250
|
+
}
|
|
2251
|
+
const bottom = 0.45;
|
|
2252
|
+
const zoneH = SLIDE_H2 - top - bottom;
|
|
2253
|
+
const { images, captions } = pickImages(node, slots);
|
|
2254
|
+
if (images.length === 0) {
|
|
2255
|
+
const fallback = captions[0] ?? node.title ?? "No image";
|
|
2256
|
+
slide.addText(fallback, {
|
|
2257
|
+
x: left,
|
|
2258
|
+
y: top,
|
|
2259
|
+
w: contentW,
|
|
2260
|
+
h: zoneH,
|
|
2261
|
+
fontSize: theme.fontSize.body,
|
|
2262
|
+
fontFace: theme.fonts.body,
|
|
2263
|
+
color: theme.colors.secondary,
|
|
2264
|
+
align: "center",
|
|
2265
|
+
valign: "middle"
|
|
2266
|
+
});
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
const gap = 0.25;
|
|
2270
|
+
const captionH = 0.35;
|
|
2271
|
+
const cellW = (contentW - gap * (slots - 1)) / slots;
|
|
2272
|
+
const cellH = zoneH - captionH;
|
|
2273
|
+
for (let i = 0; i < images.length; i++) {
|
|
2274
|
+
const x = left + i * (cellW + gap);
|
|
2275
|
+
await placeImage(
|
|
2276
|
+
slide,
|
|
2277
|
+
images[i],
|
|
2278
|
+
{ x, y: top, w: cellW, h: cellH },
|
|
2279
|
+
node,
|
|
2280
|
+
ctx
|
|
2281
|
+
);
|
|
2282
|
+
const caption = images[i].alt?.trim() || captions[i];
|
|
2283
|
+
if (caption) {
|
|
2284
|
+
slide.addText(caption, {
|
|
2285
|
+
x,
|
|
2286
|
+
y: top + cellH + 0.05,
|
|
2287
|
+
w: cellW,
|
|
2288
|
+
h: captionH,
|
|
2289
|
+
fontSize: theme.fontSize.small,
|
|
2290
|
+
fontFace: theme.fonts.body,
|
|
2291
|
+
color: theme.colors.secondary,
|
|
2292
|
+
align: "center",
|
|
2293
|
+
valign: "top"
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
async function renderMinimalImage(slide, node, theme, ctx, slots, spec) {
|
|
2299
|
+
const margin = specNumber(spec.margin, 0.83);
|
|
2300
|
+
const boxX = margin;
|
|
2301
|
+
const boxY = extraNumber(spec, "minimalImageY", 1.33);
|
|
2302
|
+
const boxH = extraNumber(spec, "minimalImageH", 5.28);
|
|
2303
|
+
const contentW = 13.33 - margin * 2;
|
|
2304
|
+
const lineColor = theme.colors.divider ?? theme.colors.secondary;
|
|
2305
|
+
if (node.title) {
|
|
2306
|
+
slide.addText(node.title, {
|
|
2307
|
+
x: boxX,
|
|
2308
|
+
y: extraNumber(spec, "minimalTitleY", 0.5),
|
|
2309
|
+
w: contentW,
|
|
2310
|
+
h: 0.44,
|
|
2311
|
+
fontSize: theme.fontSize.heading,
|
|
2312
|
+
fontFace: theme.fonts.heading,
|
|
2313
|
+
color: theme.colors.primary,
|
|
2314
|
+
bold: true,
|
|
2315
|
+
align: "left",
|
|
2316
|
+
valign: "middle"
|
|
2317
|
+
});
|
|
2318
|
+
addHRuler(slide, {
|
|
2319
|
+
x: boxX,
|
|
2320
|
+
y: extraNumber(spec, "minimalTitleRuleY", 1.03),
|
|
2321
|
+
w: extraNumber(spec, "minimalTitleRuleW", 0.56),
|
|
2322
|
+
h: extraNumber(spec, "minimalTitleRuleH", 0.03),
|
|
2323
|
+
color: lineColor
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
const gaps = { 1: 0, 2: 0.28, 3: 0.21 };
|
|
2327
|
+
const gap = gaps[slots];
|
|
2328
|
+
const cellW = (contentW - gap * (slots - 1)) / slots;
|
|
2329
|
+
const { images } = pickImages(node, slots);
|
|
2330
|
+
for (let i = 0; i < slots; i++) {
|
|
2331
|
+
const x = boxX + i * (cellW + gap);
|
|
2332
|
+
slide.addShape("rect", {
|
|
2333
|
+
x,
|
|
2334
|
+
y: boxY,
|
|
2335
|
+
w: cellW,
|
|
2336
|
+
h: boxH,
|
|
2337
|
+
fill: { transparency: 100 },
|
|
2338
|
+
line: { color: lineColor, width: 0.01, dashType: "dash" }
|
|
2339
|
+
});
|
|
2340
|
+
if (images[i]) {
|
|
2341
|
+
await placeImage(slide, images[i], { x, y: boxY, w: cellW, h: boxH }, node, ctx);
|
|
2342
|
+
} else {
|
|
2343
|
+
slide.addText("Image", {
|
|
2344
|
+
x: x + (cellW - 2.22) / 2,
|
|
2345
|
+
y: boxY + (boxH - 0.56) / 2,
|
|
2346
|
+
w: 2.22,
|
|
2347
|
+
h: 0.56,
|
|
2348
|
+
fontSize: theme.fontSize.small,
|
|
2349
|
+
fontFace: theme.fonts.body,
|
|
2350
|
+
color: lineColor,
|
|
2351
|
+
align: "center",
|
|
2352
|
+
valign: "middle"
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
1187
2357
|
|
|
1188
2358
|
// src/renderer/layouts/index.ts
|
|
1189
2359
|
async function renderSlideLayout(slide, node, theme, ctx) {
|
|
@@ -1202,7 +2372,7 @@ async function renderSlideLayout(slide, node, theme, ctx) {
|
|
|
1202
2372
|
renderTitleSlide(slide, node, theme);
|
|
1203
2373
|
break;
|
|
1204
2374
|
case "section":
|
|
1205
|
-
renderSectionSlide(slide, node, theme);
|
|
2375
|
+
renderSectionSlide(slide, node, theme, ctx);
|
|
1206
2376
|
break;
|
|
1207
2377
|
case "code":
|
|
1208
2378
|
await renderCodeSlide(slide, node, theme, ctx);
|
|
@@ -1210,6 +2380,18 @@ async function renderSlideLayout(slide, node, theme, ctx) {
|
|
|
1210
2380
|
case "quote":
|
|
1211
2381
|
renderQuoteSlide(slide, node, theme);
|
|
1212
2382
|
break;
|
|
2383
|
+
case "closing":
|
|
2384
|
+
renderClosingSlide(slide, node, theme);
|
|
2385
|
+
break;
|
|
2386
|
+
case "image-single":
|
|
2387
|
+
await renderImageSlide(slide, node, theme, ctx, 1);
|
|
2388
|
+
break;
|
|
2389
|
+
case "image-double":
|
|
2390
|
+
await renderImageSlide(slide, node, theme, ctx, 2);
|
|
2391
|
+
break;
|
|
2392
|
+
case "image-triple":
|
|
2393
|
+
await renderImageSlide(slide, node, theme, ctx, 3);
|
|
2394
|
+
break;
|
|
1213
2395
|
case "content":
|
|
1214
2396
|
default:
|
|
1215
2397
|
await renderContentSlide(slide, node, theme, ctx);
|
|
@@ -1218,18 +2400,45 @@ async function renderSlideLayout(slide, node, theme, ctx) {
|
|
|
1218
2400
|
if (node.notes) {
|
|
1219
2401
|
slide.addNotes(node.notes);
|
|
1220
2402
|
}
|
|
1221
|
-
|
|
2403
|
+
const sectionSpec = layoutSpec(theme, "section");
|
|
2404
|
+
const sectionNoFooter = extraString(sectionSpec, "footer", "on") === "off";
|
|
2405
|
+
if (node.layout !== "title" && node.layout !== "closing" && node.layout !== "blank" && !(node.layout === "section" && sectionNoFooter)) {
|
|
1222
2406
|
const footerText = renderFooter(node, theme, ctx);
|
|
2407
|
+
const footerSpec = layoutSpec(theme, "footer");
|
|
2408
|
+
const sides = resolveSideMargins(
|
|
2409
|
+
footerSpec,
|
|
2410
|
+
specNumber(theme.layouts?.content?.margin, 0.6)
|
|
2411
|
+
);
|
|
2412
|
+
const marginLeft = footerSpec.extra?.marginLeft !== void 0 ? sides.left : specNumber(
|
|
2413
|
+
theme.layouts?.content?.extra?.marginLeft,
|
|
2414
|
+
specNumber(theme.layouts?.content?.margin, sides.left)
|
|
2415
|
+
);
|
|
2416
|
+
const marginRight = footerSpec.extra?.marginRight !== void 0 ? sides.right : specNumber(
|
|
2417
|
+
theme.layouts?.content?.extra?.marginRight,
|
|
2418
|
+
specNumber(theme.layouts?.content?.margin, sides.right)
|
|
2419
|
+
);
|
|
2420
|
+
const contentW = Math.max(2, 13.33 - marginLeft - marginRight);
|
|
2421
|
+
const showDivider = theme.styles?.footerDivider ?? footerSpec.footerDivider ?? false;
|
|
2422
|
+
const footerAlign = extraString(footerSpec, "footerAlign", "right");
|
|
2423
|
+
if (showDivider) {
|
|
2424
|
+
slide.addShape("rect", {
|
|
2425
|
+
x: marginLeft,
|
|
2426
|
+
y: extraNumber(footerSpec, "dividerY", 7.08),
|
|
2427
|
+
w: contentW,
|
|
2428
|
+
h: 0.015,
|
|
2429
|
+
fill: { color: theme.colors.divider ?? theme.colors.secondary }
|
|
2430
|
+
});
|
|
2431
|
+
}
|
|
1223
2432
|
if (footerText) {
|
|
1224
2433
|
slide.addText(footerText, {
|
|
1225
|
-
x:
|
|
1226
|
-
y: 7.16,
|
|
1227
|
-
w:
|
|
2434
|
+
x: marginLeft,
|
|
2435
|
+
y: extraNumber(footerSpec, "footerY", 7.16),
|
|
2436
|
+
w: contentW,
|
|
1228
2437
|
h: 0.28,
|
|
1229
2438
|
fontSize: theme.fontSize.small - 1,
|
|
1230
2439
|
fontFace: theme.fonts.body,
|
|
1231
2440
|
color: theme.colors.secondary,
|
|
1232
|
-
align:
|
|
2441
|
+
align: footerAlign,
|
|
1233
2442
|
valign: "middle"
|
|
1234
2443
|
});
|
|
1235
2444
|
}
|
|
@@ -1243,6 +2452,93 @@ function renderFooter(node, theme, ctx) {
|
|
|
1243
2452
|
|
|
1244
2453
|
// src/renderer/code-highlighter.ts
|
|
1245
2454
|
import { createHighlighter } from "shiki";
|
|
2455
|
+
|
|
2456
|
+
// src/utils/gradient.ts
|
|
2457
|
+
import { deflateSync } from "zlib";
|
|
2458
|
+
var CRC_TABLE = (() => {
|
|
2459
|
+
const table = new Uint32Array(256);
|
|
2460
|
+
for (let n = 0; n < 256; n++) {
|
|
2461
|
+
let c = n;
|
|
2462
|
+
for (let k = 0; k < 8; k++) {
|
|
2463
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
2464
|
+
}
|
|
2465
|
+
table[n] = c >>> 0;
|
|
2466
|
+
}
|
|
2467
|
+
return table;
|
|
2468
|
+
})();
|
|
2469
|
+
function crc32(bytes) {
|
|
2470
|
+
let crc = 4294967295;
|
|
2471
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2472
|
+
crc = CRC_TABLE[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
|
|
2473
|
+
}
|
|
2474
|
+
return (crc ^ 4294967295) >>> 0;
|
|
2475
|
+
}
|
|
2476
|
+
function pngChunk(type, data) {
|
|
2477
|
+
const length = Buffer.alloc(4);
|
|
2478
|
+
length.writeUInt32BE(data.length);
|
|
2479
|
+
const payload = Buffer.concat([Buffer.from(type, "latin1"), Buffer.from(data)]);
|
|
2480
|
+
const crc = Buffer.alloc(4);
|
|
2481
|
+
crc.writeUInt32BE(crc32(payload));
|
|
2482
|
+
return Buffer.concat([length, payload, crc]);
|
|
2483
|
+
}
|
|
2484
|
+
function encodePngRgb(width, height, pixelAt) {
|
|
2485
|
+
const ihdr = Buffer.alloc(13);
|
|
2486
|
+
ihdr.writeUInt32BE(width, 0);
|
|
2487
|
+
ihdr.writeUInt32BE(height, 4);
|
|
2488
|
+
ihdr[8] = 8;
|
|
2489
|
+
ihdr[9] = 2;
|
|
2490
|
+
const raw = Buffer.alloc(height * (1 + width * 3));
|
|
2491
|
+
let offset = 0;
|
|
2492
|
+
for (let y = 0; y < height; y++) {
|
|
2493
|
+
raw[offset++] = 0;
|
|
2494
|
+
for (let x = 0; x < width; x++) {
|
|
2495
|
+
const [r, g, b] = pixelAt(x, y);
|
|
2496
|
+
raw[offset++] = r;
|
|
2497
|
+
raw[offset++] = g;
|
|
2498
|
+
raw[offset++] = b;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
const idat = deflateSync(raw, { level: 9 });
|
|
2502
|
+
return Buffer.concat([
|
|
2503
|
+
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
2504
|
+
pngChunk("IHDR", ihdr),
|
|
2505
|
+
pngChunk("IDAT", new Uint8Array(idat)),
|
|
2506
|
+
pngChunk("IEND", new Uint8Array(0))
|
|
2507
|
+
]);
|
|
2508
|
+
}
|
|
2509
|
+
function hexToRgb(hex) {
|
|
2510
|
+
let s = hex.replace(/^#/, "");
|
|
2511
|
+
if (s.length === 3) s = s.split("").map((ch) => ch + ch).join("");
|
|
2512
|
+
if (!/^[0-9a-fA-F]{6}$/.test(s)) throw new Error(`Invalid hex color: ${hex}`);
|
|
2513
|
+
return [
|
|
2514
|
+
parseInt(s.slice(0, 2), 16),
|
|
2515
|
+
parseInt(s.slice(2, 4), 16),
|
|
2516
|
+
parseInt(s.slice(4, 6), 16)
|
|
2517
|
+
];
|
|
2518
|
+
}
|
|
2519
|
+
function createLinearGradientPng(from, to, angleDeg = 180, options = {}) {
|
|
2520
|
+
const width = options.width ?? 192;
|
|
2521
|
+
const height = options.height ?? 108;
|
|
2522
|
+
const angle = angleDeg * Math.PI / 180;
|
|
2523
|
+
const dx = Math.sin(angle);
|
|
2524
|
+
const dy = -Math.cos(angle);
|
|
2525
|
+
const extent = (Math.abs(dx) + Math.abs(dy)) / 2;
|
|
2526
|
+
const c1 = hexToRgb(from);
|
|
2527
|
+
const c2 = hexToRgb(to);
|
|
2528
|
+
return encodePngRgb(width, height, (x, y) => {
|
|
2529
|
+
const px = x / (width - 1) - 0.5;
|
|
2530
|
+
const py = y / (height - 1) - 0.5;
|
|
2531
|
+
let t = 0.5 + (px * dx + py * dy) / (2 * extent);
|
|
2532
|
+
t = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
2533
|
+
return [
|
|
2534
|
+
Math.round(c1[0] + (c2[0] - c1[0]) * t),
|
|
2535
|
+
Math.round(c1[1] + (c2[1] - c1[1]) * t),
|
|
2536
|
+
Math.round(c1[2] + (c2[2] - c1[2]) * t)
|
|
2537
|
+
];
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
// src/renderer/code-highlighter.ts
|
|
1246
2542
|
var highlighterInstance = null;
|
|
1247
2543
|
async function getHighlighter() {
|
|
1248
2544
|
if (!highlighterInstance) {
|
|
@@ -1281,11 +2577,21 @@ function cleanColor(color) {
|
|
|
1281
2577
|
if (!color) return "E2E8F0";
|
|
1282
2578
|
return color.replace(/^#/, "");
|
|
1283
2579
|
}
|
|
2580
|
+
function tint(hex, amount) {
|
|
2581
|
+
const mix = (channel) => Math.round(channel + (255 - channel) * amount).toString(16).padStart(2, "0");
|
|
2582
|
+
const [r, g, b] = hexToRgb(hex);
|
|
2583
|
+
return `${mix(r)}${mix(g)}${mix(b)}`;
|
|
2584
|
+
}
|
|
2585
|
+
function highlightBackgroundFor(theme) {
|
|
2586
|
+
const explicit = theme.colors.highlightBackground;
|
|
2587
|
+
if (explicit) return explicit;
|
|
2588
|
+
return tint(theme.colors.codeBackground, 0.25);
|
|
2589
|
+
}
|
|
1284
2590
|
async function highlightCode(code, language, theme, highlightLines = []) {
|
|
1285
2591
|
const highlighter = await getHighlighter();
|
|
1286
2592
|
const runs = [];
|
|
1287
2593
|
const shikiThemeName = theme.shikiTheme ?? "github-dark";
|
|
1288
|
-
const highlightColor = theme
|
|
2594
|
+
const highlightColor = highlightBackgroundFor(theme);
|
|
1289
2595
|
try {
|
|
1290
2596
|
const loadedLangs = highlighter.getLoadedLanguages();
|
|
1291
2597
|
if (!loadedLangs.includes(language)) {
|
|
@@ -1355,27 +2661,113 @@ async function highlightCode(code, language, theme, highlightLines = []) {
|
|
|
1355
2661
|
}
|
|
1356
2662
|
|
|
1357
2663
|
// src/renderer/image-handler.ts
|
|
1358
|
-
import { existsSync, writeFileSync } from "fs";
|
|
2664
|
+
import { existsSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1359
2665
|
import { resolve } from "path";
|
|
1360
2666
|
import { tmpdir } from "os";
|
|
1361
2667
|
import { join } from "path";
|
|
1362
2668
|
import { randomUUID } from "crypto";
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
2669
|
+
|
|
2670
|
+
// src/diagrams/svg-to-png.ts
|
|
2671
|
+
function ensureSvgNamespace(svg) {
|
|
2672
|
+
if (svg.includes("xmlns=")) return svg;
|
|
2673
|
+
return svg.replace(/<svg\b([^>]*)>/i, '<svg xmlns="http://www.w3.org/2000/svg" $1>');
|
|
2674
|
+
}
|
|
2675
|
+
function normalizeSvg(svg) {
|
|
2676
|
+
let clean = ensureSvgNamespace(svg.trim());
|
|
2677
|
+
clean = clean.replace(/(<svg\b[^>]*)\s+width=["'][^"']*["']/gi, "$1");
|
|
2678
|
+
clean = clean.replace(/(<svg\b[^>]*)\s+height=["'][^"']*["']/gi, "$1");
|
|
2679
|
+
const viewBoxMatch = clean.match(/viewBox\s*=\s*["']([^"']+)["']/i);
|
|
2680
|
+
let frameUsable = true;
|
|
2681
|
+
if (viewBoxMatch) {
|
|
2682
|
+
const parts = viewBoxMatch[1].trim().split(/[\s,]+/).map(Number);
|
|
2683
|
+
if (parts.length === 4 && parts[2] > 0 && parts[3] > 0) {
|
|
2684
|
+
const w = parts[2];
|
|
2685
|
+
const h = parts[3];
|
|
2686
|
+
clean = clean.replace(/<svg\b([^>]*)>/i, `<svg width="${w}" height="${h}" $1>`);
|
|
2687
|
+
} else {
|
|
2688
|
+
frameUsable = false;
|
|
1376
2689
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
2690
|
+
} else {
|
|
2691
|
+
clean = clean.replace(/<svg\b([^>]*)>/i, `<svg width="800" height="600" viewBox="0 0 800 600" $1>`);
|
|
2692
|
+
}
|
|
2693
|
+
return { svg: clean, frameUsable };
|
|
2694
|
+
}
|
|
2695
|
+
async function svgToPng(svg, width = 800) {
|
|
2696
|
+
const { svg: normalized, frameUsable } = normalizeSvg(svg);
|
|
2697
|
+
const { Resvg } = await import("@resvg/resvg-js");
|
|
2698
|
+
const resvg = new Resvg(normalized, {
|
|
2699
|
+
fitTo: { mode: "width", value: width }
|
|
2700
|
+
});
|
|
2701
|
+
if (frameUsable) reframeToContent(resvg);
|
|
2702
|
+
const pngData = resvg.render();
|
|
2703
|
+
return Buffer.from(pngData.asPng());
|
|
2704
|
+
}
|
|
2705
|
+
var SVG_EMBED_WIDTH = 1200;
|
|
2706
|
+
async function rasterizeSvg(svg, width) {
|
|
2707
|
+
const { Resvg } = await import("@resvg/resvg-js");
|
|
2708
|
+
const resvg = new Resvg(ensureSvgNamespace(svg.trim()), {
|
|
2709
|
+
fitTo: { mode: "width", value: width }
|
|
2710
|
+
});
|
|
2711
|
+
return Buffer.from(resvg.render().asPng());
|
|
2712
|
+
}
|
|
2713
|
+
var CONTENT_PADDING = 4;
|
|
2714
|
+
var MAX_ASPECT_SHIFT = 3;
|
|
2715
|
+
function reframeToContent(resvg, padding = CONTENT_PADDING) {
|
|
2716
|
+
const bbox = resvg.getBBox();
|
|
2717
|
+
if (!bbox || !(bbox.width > 0) || !(bbox.height > 0)) return;
|
|
2718
|
+
const frameAspect = resvg.width / resvg.height;
|
|
2719
|
+
if (!Number.isFinite(frameAspect) || frameAspect <= 0) return;
|
|
2720
|
+
const contentAspect = bbox.width / bbox.height;
|
|
2721
|
+
const shift = Math.max(contentAspect / frameAspect, frameAspect / contentAspect);
|
|
2722
|
+
if (shift > MAX_ASPECT_SHIFT) return;
|
|
2723
|
+
try {
|
|
2724
|
+
bbox.x -= padding;
|
|
2725
|
+
bbox.y -= padding;
|
|
2726
|
+
bbox.width += padding * 2;
|
|
2727
|
+
bbox.height += padding * 2;
|
|
2728
|
+
} catch {
|
|
2729
|
+
}
|
|
2730
|
+
resvg.cropByBBox(bbox);
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// src/renderer/image-handler.ts
|
|
2734
|
+
var REMOTE_FETCH_TIMEOUT_MS = 1e4;
|
|
2735
|
+
async function resolveImage(src, basePath) {
|
|
2736
|
+
const located = await locateImage(src, basePath);
|
|
2737
|
+
if (!located.ok) return located;
|
|
2738
|
+
return rasterizeIfSvg(located);
|
|
2739
|
+
}
|
|
2740
|
+
async function rasterizeIfSvg(resolved) {
|
|
2741
|
+
if (!resolved.ok) return resolved;
|
|
2742
|
+
try {
|
|
2743
|
+
const data = readFileSync3(resolved.path);
|
|
2744
|
+
if (!isSvgImage(data)) return resolved;
|
|
2745
|
+
const png = await rasterizeSvg(data.toString("utf8"), SVG_EMBED_WIDTH);
|
|
2746
|
+
const tmpPath = join(tmpdir(), `mfly-${randomUUID().slice(0, 8)}.png`);
|
|
2747
|
+
writeFileSync(tmpPath, png);
|
|
2748
|
+
return { ok: true, path: tmpPath };
|
|
2749
|
+
} catch (err) {
|
|
2750
|
+
log.warn(
|
|
2751
|
+
`Could not rasterize ${resolved.path}: ${err instanceof Error ? err.message : String(err)} \u2014 embedding the SVG as-is`
|
|
2752
|
+
);
|
|
2753
|
+
return resolved;
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
async function locateImage(src, basePath) {
|
|
2757
|
+
try {
|
|
2758
|
+
if (src.startsWith("data:")) {
|
|
2759
|
+
const match = src.match(/^data:image\/([\w.+-]+);base64,(.+)/);
|
|
2760
|
+
if (match) {
|
|
2761
|
+
const ext = match[1].split("+")[0];
|
|
2762
|
+
const base64 = match[2];
|
|
2763
|
+
const tmpPath = join(tmpdir(), `mfly-${randomUUID().slice(0, 8)}.${ext}`);
|
|
2764
|
+
writeFileSync(tmpPath, Buffer.from(base64, "base64"));
|
|
2765
|
+
return { ok: true, path: tmpPath };
|
|
2766
|
+
}
|
|
2767
|
+
return { ok: false, error: `Unsupported data URI: ${src.slice(0, 48)}` };
|
|
2768
|
+
}
|
|
2769
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
2770
|
+
let response;
|
|
1379
2771
|
try {
|
|
1380
2772
|
response = await fetch(src, { signal: AbortSignal.timeout(REMOTE_FETCH_TIMEOUT_MS) });
|
|
1381
2773
|
} catch (err) {
|
|
@@ -1405,39 +2797,6 @@ async function resolveImage(src, basePath) {
|
|
|
1405
2797
|
}
|
|
1406
2798
|
}
|
|
1407
2799
|
|
|
1408
|
-
// src/diagrams/svg-to-png.ts
|
|
1409
|
-
function normalizeSvg(svg) {
|
|
1410
|
-
let clean = svg.trim();
|
|
1411
|
-
if (!clean.includes("xmlns=")) {
|
|
1412
|
-
clean = clean.replace(/<svg\b([^>]*)>/i, '<svg xmlns="http://www.w3.org/2000/svg" $1>');
|
|
1413
|
-
}
|
|
1414
|
-
clean = clean.replace(/(<svg\b[^>]*)\s+width=["'][^"']*["']/gi, "$1");
|
|
1415
|
-
clean = clean.replace(/(<svg\b[^>]*)\s+height=["'][^"']*["']/gi, "$1");
|
|
1416
|
-
clean = clean.replace(/<marker\b[\s\S]*?<\/marker>/gi, "");
|
|
1417
|
-
clean = clean.replace(/\s+marker-(end|start|mid)=["'][^"']*["']/gi, "");
|
|
1418
|
-
const viewBoxMatch = clean.match(/viewBox\s*=\s*["']([^"']+)["']/i);
|
|
1419
|
-
if (viewBoxMatch) {
|
|
1420
|
-
const parts = viewBoxMatch[1].trim().split(/[\s,]+/).map(Number);
|
|
1421
|
-
if (parts.length === 4 && parts[2] > 0 && parts[3] > 0) {
|
|
1422
|
-
const w = parts[2];
|
|
1423
|
-
const h = parts[3];
|
|
1424
|
-
clean = clean.replace(/<svg\b([^>]*)>/i, `<svg width="${w}" height="${h}" $1>`);
|
|
1425
|
-
}
|
|
1426
|
-
} else {
|
|
1427
|
-
clean = clean.replace(/<svg\b([^>]*)>/i, `<svg width="800" height="600" viewBox="0 0 800 600" $1>`);
|
|
1428
|
-
}
|
|
1429
|
-
return clean;
|
|
1430
|
-
}
|
|
1431
|
-
async function svgToPng(svg, width = 800) {
|
|
1432
|
-
const normalized = normalizeSvg(svg);
|
|
1433
|
-
const { Resvg } = await import("@resvg/resvg-js");
|
|
1434
|
-
const resvg = new Resvg(normalized, {
|
|
1435
|
-
fitTo: { mode: "width", value: width }
|
|
1436
|
-
});
|
|
1437
|
-
const pngData = resvg.render();
|
|
1438
|
-
return Buffer.from(pngData.asPng());
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
2800
|
// src/diagrams/theme.ts
|
|
1442
2801
|
function isDarkColor(hex) {
|
|
1443
2802
|
const clean = hex.replace(/^#/, "");
|
|
@@ -1774,25 +3133,55 @@ function contentBBox(root) {
|
|
|
1774
3133
|
if (!Number.isFinite(minX)) return null;
|
|
1775
3134
|
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
1776
3135
|
}
|
|
3136
|
+
var browserGlobals = [];
|
|
3137
|
+
function installBrowserGlobals() {
|
|
3138
|
+
const saved = browserGlobals.map(
|
|
3139
|
+
([key]) => [key, Object.getOwnPropertyDescriptor(globalThis, key)]
|
|
3140
|
+
);
|
|
3141
|
+
for (const [key, value] of browserGlobals) {
|
|
3142
|
+
try {
|
|
3143
|
+
Object.defineProperty(globalThis, key, {
|
|
3144
|
+
value,
|
|
3145
|
+
configurable: true,
|
|
3146
|
+
writable: true
|
|
3147
|
+
});
|
|
3148
|
+
} catch {
|
|
3149
|
+
globalThis[key] = value;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
return () => {
|
|
3153
|
+
for (const [key, descriptor] of saved) {
|
|
3154
|
+
if (descriptor) {
|
|
3155
|
+
Object.defineProperty(globalThis, key, descriptor);
|
|
3156
|
+
} else {
|
|
3157
|
+
delete globalThis[key];
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
}
|
|
1777
3162
|
var MermaidDiagramRenderer = class {
|
|
1778
3163
|
type = "mermaid";
|
|
1779
3164
|
renderCounter = 0;
|
|
1780
3165
|
async initialize() {
|
|
1781
3166
|
if (initialized) return;
|
|
3167
|
+
browserGlobals = [];
|
|
1782
3168
|
const { JSDOM } = await import("jsdom");
|
|
1783
3169
|
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>", {
|
|
1784
3170
|
pretendToBeVisual: true
|
|
1785
3171
|
});
|
|
3172
|
+
const jsdomGetComputedStyle = dom.window.getComputedStyle.bind(dom.window);
|
|
3173
|
+
dom.window.getComputedStyle = ((element, pseudo) => {
|
|
3174
|
+
const style = jsdomGetComputedStyle(element, pseudo);
|
|
3175
|
+
const read = style.getPropertyValue.bind(style);
|
|
3176
|
+
style.getPropertyValue = (property) => {
|
|
3177
|
+
const value = read(property);
|
|
3178
|
+
if (value !== "" && value != null) return value;
|
|
3179
|
+
return property.startsWith("padding") ? "0px" : value;
|
|
3180
|
+
};
|
|
3181
|
+
return style;
|
|
3182
|
+
});
|
|
1786
3183
|
const assignGlobal = (key, value) => {
|
|
1787
|
-
|
|
1788
|
-
Object.defineProperty(globalThis, key, {
|
|
1789
|
-
value,
|
|
1790
|
-
configurable: true,
|
|
1791
|
-
writable: true
|
|
1792
|
-
});
|
|
1793
|
-
} catch {
|
|
1794
|
-
globalThis[key] = value;
|
|
1795
|
-
}
|
|
3184
|
+
browserGlobals.push([key, value]);
|
|
1796
3185
|
};
|
|
1797
3186
|
assignGlobal("window", dom.window);
|
|
1798
3187
|
assignGlobal("document", dom.window.document);
|
|
@@ -1850,25 +3239,36 @@ var MermaidDiagramRenderer = class {
|
|
|
1850
3239
|
y: b.y,
|
|
1851
3240
|
width: b.width,
|
|
1852
3241
|
height: b.height,
|
|
3242
|
+
toJSON: () => ({})
|
|
3243
|
+
});
|
|
3244
|
+
const toDomRect = (b) => ({
|
|
3245
|
+
...toRect(b),
|
|
1853
3246
|
top: b.y,
|
|
1854
3247
|
right: b.x + b.width,
|
|
1855
3248
|
bottom: b.y + b.height,
|
|
1856
|
-
left: b.x
|
|
1857
|
-
toJSON: () => ({})
|
|
3249
|
+
left: b.x
|
|
1858
3250
|
});
|
|
3251
|
+
const isMeasuredNode = (el) => {
|
|
3252
|
+
const parent = el.parentElement;
|
|
3253
|
+
if (!parent) return false;
|
|
3254
|
+
return (parent.getAttribute("class") ?? "").split(/\s+/).includes("nodes");
|
|
3255
|
+
};
|
|
1859
3256
|
const polyfillBBox = function() {
|
|
1860
3257
|
const tag = this.tagName ? this.tagName.toLowerCase() : "";
|
|
1861
3258
|
if (tag === "style" || tag === "script" || tag === "defs" || tag === "clippath" || tag === "marker") {
|
|
1862
3259
|
return toRect({ x: 0, y: 0, width: 0, height: 0 });
|
|
1863
3260
|
}
|
|
1864
|
-
if (tag === "svg") {
|
|
3261
|
+
if (tag === "svg" || isMeasuredNode(this)) {
|
|
1865
3262
|
const box = contentBBox(this);
|
|
1866
3263
|
if (box) return toRect(box);
|
|
1867
3264
|
}
|
|
1868
3265
|
const text = getDirectText(this);
|
|
1869
3266
|
const width = text ? Math.max(30, text.length * 9 + 20) : 80;
|
|
1870
3267
|
const height = text ? 28 : 40;
|
|
1871
|
-
|
|
3268
|
+
const isText = tag === "text" || tag === "tspan";
|
|
3269
|
+
const anchor = parseFloat(this.getAttribute("x") ?? "");
|
|
3270
|
+
const x = isText ? (Number.isFinite(anchor) ? anchor : 0) - width / 2 : 0;
|
|
3271
|
+
return toRect({ x, y: 0, width, height });
|
|
1872
3272
|
};
|
|
1873
3273
|
if (!dom.window.SVGElement.prototype.getBBox) {
|
|
1874
3274
|
dom.window.SVGElement.prototype.getBBox = polyfillBBox;
|
|
@@ -1877,7 +3277,10 @@ var MermaidDiagramRenderer = class {
|
|
|
1877
3277
|
dom.window.Element.prototype.getBBox = polyfillBBox;
|
|
1878
3278
|
}
|
|
1879
3279
|
if (!dom.window.Element.prototype.getBoundingClientRect) {
|
|
1880
|
-
dom.window.Element.prototype.getBoundingClientRect =
|
|
3280
|
+
dom.window.Element.prototype.getBoundingClientRect = (function() {
|
|
3281
|
+
const box = polyfillBBox.call(this);
|
|
3282
|
+
return toDomRect(box);
|
|
3283
|
+
});
|
|
1881
3284
|
}
|
|
1882
3285
|
const computeTextLength = function() {
|
|
1883
3286
|
const text = getDirectText(this);
|
|
@@ -1981,8 +3384,13 @@ var MermaidDiagramRenderer = class {
|
|
|
1981
3384
|
dispatchEvent: () => false
|
|
1982
3385
|
});
|
|
1983
3386
|
}
|
|
1984
|
-
const
|
|
1985
|
-
|
|
3387
|
+
const restore = installBrowserGlobals();
|
|
3388
|
+
try {
|
|
3389
|
+
const mermaid = (await import("mermaid")).default;
|
|
3390
|
+
mermaid.initialize(this.buildConfig());
|
|
3391
|
+
} finally {
|
|
3392
|
+
restore();
|
|
3393
|
+
}
|
|
1986
3394
|
initialized = true;
|
|
1987
3395
|
}
|
|
1988
3396
|
buildConfig(theme) {
|
|
@@ -2000,22 +3408,30 @@ var MermaidDiagramRenderer = class {
|
|
|
2000
3408
|
background: "transparent"
|
|
2001
3409
|
},
|
|
2002
3410
|
flowchart: { htmlLabels: false, curve: "basis", useMaxWidth: false },
|
|
2003
|
-
sequence: { useMaxWidth: false }
|
|
3411
|
+
sequence: { useMaxWidth: false },
|
|
3412
|
+
// jsdom has no layout engine: offsetWidth is 0 rather than undefined, so
|
|
3413
|
+
// mermaid's own "no parent width" fallback never fires and the gantt chart
|
|
3414
|
+
// ends up with a zero-width viewBox. Pin the viewport width instead.
|
|
3415
|
+
gantt: { useWidth: 1200 }
|
|
2004
3416
|
};
|
|
2005
3417
|
}
|
|
2006
3418
|
async render(code, theme) {
|
|
2007
3419
|
await this.initialize();
|
|
2008
3420
|
const mermaid = (await import("mermaid")).default;
|
|
2009
3421
|
const id = `mfly-mermaid-${Date.now()}-${this.renderCounter++}`;
|
|
2010
|
-
|
|
3422
|
+
let svg;
|
|
3423
|
+
const restore = installBrowserGlobals();
|
|
2011
3424
|
try {
|
|
2012
|
-
|
|
2013
|
-
|
|
3425
|
+
mermaid.initialize(this.buildConfig(theme));
|
|
3426
|
+
({ svg } = await mermaid.render(id, code.trim()));
|
|
2014
3427
|
} catch (err) {
|
|
2015
3428
|
throw new Error(
|
|
2016
3429
|
`Mermaid render failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2017
3430
|
);
|
|
3431
|
+
} finally {
|
|
3432
|
+
restore();
|
|
2018
3433
|
}
|
|
3434
|
+
return svgToPng(svg, 1200);
|
|
2019
3435
|
}
|
|
2020
3436
|
};
|
|
2021
3437
|
|
|
@@ -2095,25 +3511,213 @@ var EChartsDiagramRenderer = class {
|
|
|
2095
3511
|
}
|
|
2096
3512
|
};
|
|
2097
3513
|
|
|
3514
|
+
// src/diagrams/plantuml-renderer.ts
|
|
3515
|
+
var RENDER_WIDTH = 1200;
|
|
3516
|
+
var RENDER_TIMEOUT_MS = 3e4;
|
|
3517
|
+
function originalLine(reported, prepared) {
|
|
3518
|
+
if (reported === void 0) return void 0;
|
|
3519
|
+
return reported > prepared.shiftAfter ? reported - prepared.lineShift : reported;
|
|
3520
|
+
}
|
|
3521
|
+
async function withEngineLogsMuted(fn) {
|
|
3522
|
+
const originals = { log: console.log, info: console.info, debug: console.debug };
|
|
3523
|
+
const sink = process.env.MFLY_DEBUG ? (...args) => console.error(...args) : () => {
|
|
3524
|
+
};
|
|
3525
|
+
console.log = sink;
|
|
3526
|
+
console.info = sink;
|
|
3527
|
+
console.debug = sink;
|
|
3528
|
+
try {
|
|
3529
|
+
return await fn();
|
|
3530
|
+
} finally {
|
|
3531
|
+
console.log = originals.log;
|
|
3532
|
+
console.info = originals.info;
|
|
3533
|
+
console.debug = originals.debug;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
function fontValue(theme) {
|
|
3537
|
+
return `"${theme.fonts.cjk.replace(/"/g, "")}"`;
|
|
3538
|
+
}
|
|
3539
|
+
var LINE_STROKE = "stroke:#181818;stroke-width:0.5;";
|
|
3540
|
+
var LIFELINE_STROKE = `${LINE_STROKE}stroke-dasharray:5,5;`;
|
|
3541
|
+
function restoreLineStrokes(svg, diagramType) {
|
|
3542
|
+
const dashed = /sequence/i.test(diagramType ?? "");
|
|
3543
|
+
return svg.replace(
|
|
3544
|
+
/<line\b([^>]*?)\/>/g,
|
|
3545
|
+
(match, attributes) => /\b(stroke|style)=/.test(attributes) ? match : `<line${attributes} style="${dashed ? LIFELINE_STROKE : LINE_STROKE}"/>`
|
|
3546
|
+
);
|
|
3547
|
+
}
|
|
3548
|
+
var PlantUmlDiagramRenderer = class {
|
|
3549
|
+
type = "plantuml";
|
|
3550
|
+
engine = null;
|
|
3551
|
+
async initialize() {
|
|
3552
|
+
if (this.engine) return;
|
|
3553
|
+
this.engine = await withEngineLogsMuted(async () => {
|
|
3554
|
+
const globalWithViz = globalThis;
|
|
3555
|
+
if (!globalWithViz.Viz) {
|
|
3556
|
+
const vizModule = await import("@viz-js/viz");
|
|
3557
|
+
let instancePromise = null;
|
|
3558
|
+
globalWithViz.Viz = {
|
|
3559
|
+
instance: () => instancePromise ??= vizModule.instance()
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
return import("@plantuml/mcp-js/engine.js");
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
/**
|
|
3566
|
+
* PlantUML has no dark-mode switch in this build, so the palette is injected
|
|
3567
|
+
* as skinparams — the same shape as the DOT renderer's attribute prelude.
|
|
3568
|
+
* Anything the author writes after the prelude still wins.
|
|
3569
|
+
*
|
|
3570
|
+
* `backgroundColor transparent` is stated explicitly rather than left to the
|
|
3571
|
+
* default: PlantUML otherwise writes `background:#FFFFFF` into the SVG style,
|
|
3572
|
+
* which only stays invisible because the rasterizer happens to ignore it.
|
|
3573
|
+
*/
|
|
3574
|
+
skinparams(theme) {
|
|
3575
|
+
const lines = ["skinparam backgroundColor transparent"];
|
|
3576
|
+
if (theme) {
|
|
3577
|
+
lines.push(`skinparam defaultFontName ${fontValue(theme)}`);
|
|
3578
|
+
}
|
|
3579
|
+
if (!isDarkTheme(theme)) {
|
|
3580
|
+
lines.push("skinparam defaultFontColor #1F2328");
|
|
3581
|
+
return lines.join("\n");
|
|
3582
|
+
}
|
|
3583
|
+
lines.push(
|
|
3584
|
+
"skinparam defaultFontColor #C9D1D9",
|
|
3585
|
+
"skinparam shadowing false",
|
|
3586
|
+
// Boxes are light by default, so the fills have to move with the text or
|
|
3587
|
+
// labels end up dark-on-dark.
|
|
3588
|
+
"skinparam classBackgroundColor #1F2937",
|
|
3589
|
+
"skinparam classBorderColor #6B7280",
|
|
3590
|
+
"skinparam classFontColor #E5E7EB",
|
|
3591
|
+
"skinparam classAttributeFontColor #CBD5E1",
|
|
3592
|
+
"skinparam participantBackgroundColor #1F2937",
|
|
3593
|
+
"skinparam participantBorderColor #6B7280",
|
|
3594
|
+
"skinparam actorBackgroundColor #1F2937",
|
|
3595
|
+
"skinparam actorBorderColor #6B7280",
|
|
3596
|
+
"skinparam sequenceArrowColor #9CA3AF",
|
|
3597
|
+
"skinparam sequenceLifeLineBorderColor #6B7280",
|
|
3598
|
+
"skinparam noteBackgroundColor #374151",
|
|
3599
|
+
"skinparam noteBorderColor #6B7280",
|
|
3600
|
+
"skinparam activityBackgroundColor #1F2937",
|
|
3601
|
+
"skinparam activityBorderColor #6B7280",
|
|
3602
|
+
"skinparam activityDiamondBackgroundColor #1F2937",
|
|
3603
|
+
"skinparam activityDiamondBorderColor #6B7280",
|
|
3604
|
+
"skinparam stateBackgroundColor #1F2937",
|
|
3605
|
+
"skinparam stateBorderColor #6B7280",
|
|
3606
|
+
"skinparam componentBackgroundColor #1F2937",
|
|
3607
|
+
"skinparam componentBorderColor #6B7280",
|
|
3608
|
+
"skinparam componentFontColor #E5E7EB"
|
|
3609
|
+
);
|
|
3610
|
+
return lines.join("\n");
|
|
3611
|
+
}
|
|
3612
|
+
/**
|
|
3613
|
+
* Drop `!theme` directives and inject the theme prelude.
|
|
3614
|
+
*
|
|
3615
|
+
* `!theme` needs themes.js, which the headless build does not ship — the
|
|
3616
|
+
* directive is ignored with a warning anyway. Dropping it up front also avoids
|
|
3617
|
+
* the theme loader entirely, which is what can leave renderSvg without ever
|
|
3618
|
+
* calling back (a second *distinct* theme in one process is enough).
|
|
3619
|
+
*/
|
|
3620
|
+
prepare(code, theme) {
|
|
3621
|
+
const warnings = [];
|
|
3622
|
+
const kept = [];
|
|
3623
|
+
for (const line of code.trim().split("\n")) {
|
|
3624
|
+
if (/^\s*!theme\b/i.test(line)) {
|
|
3625
|
+
warnings.push(
|
|
3626
|
+
`"${line.trim()}" ignored \u2014 the bundled PlantUML engine ships no themes; use skinparam instead`
|
|
3627
|
+
);
|
|
3628
|
+
continue;
|
|
3629
|
+
}
|
|
3630
|
+
kept.push(line);
|
|
3631
|
+
}
|
|
3632
|
+
const prelude = this.skinparams(theme);
|
|
3633
|
+
const preludeLines = prelude.split("\n").length;
|
|
3634
|
+
const startIndex = kept.findIndex((line) => /^\s*@start\w+/i.test(line));
|
|
3635
|
+
if (startIndex === -1) {
|
|
3636
|
+
return {
|
|
3637
|
+
source: `@startuml
|
|
3638
|
+
${prelude}
|
|
3639
|
+
${kept.join("\n")}
|
|
3640
|
+
@enduml`,
|
|
3641
|
+
warnings,
|
|
3642
|
+
lineShift: preludeLines + 1,
|
|
3643
|
+
shiftAfter: 1
|
|
3644
|
+
};
|
|
3645
|
+
}
|
|
3646
|
+
kept.splice(startIndex + 1, 0, prelude);
|
|
3647
|
+
if (!kept.some((line) => /^\s*@end\w+/i.test(line))) {
|
|
3648
|
+
const kind = kept[startIndex].match(/^\s*@start(\w+)/i)?.[1] ?? "uml";
|
|
3649
|
+
kept.push(`@end${kind}`);
|
|
3650
|
+
}
|
|
3651
|
+
return {
|
|
3652
|
+
source: kept.join("\n"),
|
|
3653
|
+
warnings,
|
|
3654
|
+
lineShift: preludeLines,
|
|
3655
|
+
shiftAfter: startIndex + 1
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
async render(code, theme) {
|
|
3659
|
+
await this.initialize();
|
|
3660
|
+
const prepared = this.prepare(code, theme);
|
|
3661
|
+
for (const warning of prepared.warnings) log.warn(`[PlantUML] ${warning}`);
|
|
3662
|
+
try {
|
|
3663
|
+
const result = await this.renderToSvg(prepared.source);
|
|
3664
|
+
if (!result.valid || !result.svg) {
|
|
3665
|
+
const line = originalLine(result.errorLineNumber, prepared);
|
|
3666
|
+
const at = line ? ` (line ${line})` : "";
|
|
3667
|
+
throw new Error(`${result.errorMessage ?? "invalid diagram"}${at}`);
|
|
3668
|
+
}
|
|
3669
|
+
return svgToPng(restoreLineStrokes(result.svg, result.diagramType), RENDER_WIDTH);
|
|
3670
|
+
} catch (err) {
|
|
3671
|
+
throw new Error(
|
|
3672
|
+
`PlantUML render failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
async renderToSvg(source) {
|
|
3677
|
+
const engine = this.engine;
|
|
3678
|
+
const json = await withEngineLogsMuted(
|
|
3679
|
+
() => new Promise((resolve4, reject) => {
|
|
3680
|
+
let settled = false;
|
|
3681
|
+
const timer = setTimeout(() => {
|
|
3682
|
+
if (settled) return;
|
|
3683
|
+
settled = true;
|
|
3684
|
+
reject(new Error(`timed out after ${RENDER_TIMEOUT_MS / 1e3}s`));
|
|
3685
|
+
}, RENDER_TIMEOUT_MS);
|
|
3686
|
+
engine.renderSvg(source, (result) => {
|
|
3687
|
+
if (settled) return;
|
|
3688
|
+
settled = true;
|
|
3689
|
+
clearTimeout(timer);
|
|
3690
|
+
resolve4(result);
|
|
3691
|
+
});
|
|
3692
|
+
})
|
|
3693
|
+
);
|
|
3694
|
+
try {
|
|
3695
|
+
return JSON.parse(json);
|
|
3696
|
+
} catch {
|
|
3697
|
+
throw new Error("engine returned malformed JSON");
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
};
|
|
3701
|
+
|
|
2098
3702
|
// src/diagrams/index.ts
|
|
2099
|
-
var
|
|
3703
|
+
var RENDERERS = {
|
|
2100
3704
|
mermaid: () => new MermaidDiagramRenderer(),
|
|
2101
3705
|
dot: () => new GraphvizDiagramRenderer(),
|
|
2102
|
-
|
|
2103
|
-
|
|
3706
|
+
echarts: () => new EChartsDiagramRenderer(),
|
|
3707
|
+
plantuml: () => new PlantUmlDiagramRenderer()
|
|
2104
3708
|
};
|
|
2105
3709
|
var instances = /* @__PURE__ */ new Map();
|
|
2106
3710
|
function getOrCreate(language) {
|
|
2107
|
-
const
|
|
2108
|
-
if (
|
|
2109
|
-
const
|
|
2110
|
-
if (
|
|
2111
|
-
const renderer =
|
|
2112
|
-
instances.set(
|
|
3711
|
+
const canonical = normalizeDiagramLanguage(language);
|
|
3712
|
+
if (!canonical) return void 0;
|
|
3713
|
+
const cached = instances.get(canonical);
|
|
3714
|
+
if (cached) return cached;
|
|
3715
|
+
const renderer = RENDERERS[canonical]();
|
|
3716
|
+
instances.set(canonical, renderer);
|
|
2113
3717
|
return renderer;
|
|
2114
3718
|
}
|
|
2115
3719
|
async function renderDiagram(language, code, theme) {
|
|
2116
|
-
const renderer = getOrCreate(language
|
|
3720
|
+
const renderer = getOrCreate(language);
|
|
2117
3721
|
if (!renderer) {
|
|
2118
3722
|
throw new Error(`No diagram renderer for language: ${language}`);
|
|
2119
3723
|
}
|
|
@@ -2121,91 +3725,6 @@ async function renderDiagram(language, code, theme) {
|
|
|
2121
3725
|
return renderer.render(code, theme);
|
|
2122
3726
|
}
|
|
2123
3727
|
|
|
2124
|
-
// src/utils/gradient.ts
|
|
2125
|
-
import { deflateSync } from "zlib";
|
|
2126
|
-
var CRC_TABLE = (() => {
|
|
2127
|
-
const table = new Uint32Array(256);
|
|
2128
|
-
for (let n = 0; n < 256; n++) {
|
|
2129
|
-
let c = n;
|
|
2130
|
-
for (let k = 0; k < 8; k++) {
|
|
2131
|
-
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
2132
|
-
}
|
|
2133
|
-
table[n] = c >>> 0;
|
|
2134
|
-
}
|
|
2135
|
-
return table;
|
|
2136
|
-
})();
|
|
2137
|
-
function crc32(bytes) {
|
|
2138
|
-
let crc = 4294967295;
|
|
2139
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
2140
|
-
crc = CRC_TABLE[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
|
|
2141
|
-
}
|
|
2142
|
-
return (crc ^ 4294967295) >>> 0;
|
|
2143
|
-
}
|
|
2144
|
-
function pngChunk(type, data) {
|
|
2145
|
-
const length = Buffer.alloc(4);
|
|
2146
|
-
length.writeUInt32BE(data.length);
|
|
2147
|
-
const payload = Buffer.concat([Buffer.from(type, "latin1"), Buffer.from(data)]);
|
|
2148
|
-
const crc = Buffer.alloc(4);
|
|
2149
|
-
crc.writeUInt32BE(crc32(payload));
|
|
2150
|
-
return Buffer.concat([length, payload, crc]);
|
|
2151
|
-
}
|
|
2152
|
-
function encodePngRgb(width, height, pixelAt) {
|
|
2153
|
-
const ihdr = Buffer.alloc(13);
|
|
2154
|
-
ihdr.writeUInt32BE(width, 0);
|
|
2155
|
-
ihdr.writeUInt32BE(height, 4);
|
|
2156
|
-
ihdr[8] = 8;
|
|
2157
|
-
ihdr[9] = 2;
|
|
2158
|
-
const raw = Buffer.alloc(height * (1 + width * 3));
|
|
2159
|
-
let offset = 0;
|
|
2160
|
-
for (let y = 0; y < height; y++) {
|
|
2161
|
-
raw[offset++] = 0;
|
|
2162
|
-
for (let x = 0; x < width; x++) {
|
|
2163
|
-
const [r, g, b] = pixelAt(x, y);
|
|
2164
|
-
raw[offset++] = r;
|
|
2165
|
-
raw[offset++] = g;
|
|
2166
|
-
raw[offset++] = b;
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
const idat = deflateSync(raw, { level: 9 });
|
|
2170
|
-
return Buffer.concat([
|
|
2171
|
-
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
2172
|
-
pngChunk("IHDR", ihdr),
|
|
2173
|
-
pngChunk("IDAT", new Uint8Array(idat)),
|
|
2174
|
-
pngChunk("IEND", new Uint8Array(0))
|
|
2175
|
-
]);
|
|
2176
|
-
}
|
|
2177
|
-
function hexToRgb(hex) {
|
|
2178
|
-
let s = hex.replace(/^#/, "");
|
|
2179
|
-
if (s.length === 3) s = s.split("").map((ch) => ch + ch).join("");
|
|
2180
|
-
if (!/^[0-9a-fA-F]{6}$/.test(s)) throw new Error(`Invalid hex color: ${hex}`);
|
|
2181
|
-
return [
|
|
2182
|
-
parseInt(s.slice(0, 2), 16),
|
|
2183
|
-
parseInt(s.slice(2, 4), 16),
|
|
2184
|
-
parseInt(s.slice(4, 6), 16)
|
|
2185
|
-
];
|
|
2186
|
-
}
|
|
2187
|
-
function createLinearGradientPng(from, to, angleDeg = 180, options = {}) {
|
|
2188
|
-
const width = options.width ?? 192;
|
|
2189
|
-
const height = options.height ?? 108;
|
|
2190
|
-
const angle = angleDeg * Math.PI / 180;
|
|
2191
|
-
const dx = Math.sin(angle);
|
|
2192
|
-
const dy = -Math.cos(angle);
|
|
2193
|
-
const extent = (Math.abs(dx) + Math.abs(dy)) / 2;
|
|
2194
|
-
const c1 = hexToRgb(from);
|
|
2195
|
-
const c2 = hexToRgb(to);
|
|
2196
|
-
return encodePngRgb(width, height, (x, y) => {
|
|
2197
|
-
const px = x / (width - 1) - 0.5;
|
|
2198
|
-
const py = y / (height - 1) - 0.5;
|
|
2199
|
-
let t = 0.5 + (px * dx + py * dy) / (2 * extent);
|
|
2200
|
-
t = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
2201
|
-
return [
|
|
2202
|
-
Math.round(c1[0] + (c2[0] - c1[0]) * t),
|
|
2203
|
-
Math.round(c1[1] + (c2[1] - c1[1]) * t),
|
|
2204
|
-
Math.round(c1[2] + (c2[2] - c1[2]) * t)
|
|
2205
|
-
];
|
|
2206
|
-
});
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
3728
|
// src/renderer/background.ts
|
|
2210
3729
|
var cache = /* @__PURE__ */ new Map();
|
|
2211
3730
|
function slideBackground(theme) {
|
|
@@ -2248,15 +3767,17 @@ async function renderPresentation(presentation, theme, outputPath, inputPath = "
|
|
|
2248
3767
|
footerTemplate: presentation.config.footer,
|
|
2249
3768
|
pageNumber: 0,
|
|
2250
3769
|
totalSlides: presentation.slides.length,
|
|
2251
|
-
currentSection: ""
|
|
3770
|
+
currentSection: "",
|
|
3771
|
+
sectionNumber: 0
|
|
2252
3772
|
};
|
|
2253
3773
|
for (let i = 0; i < presentation.slides.length; i++) {
|
|
2254
3774
|
const node = presentation.slides[i];
|
|
2255
3775
|
const slide = pptx.addSlide();
|
|
2256
3776
|
slide.background = backgroundProps;
|
|
2257
3777
|
ctx.pageNumber = i + 1;
|
|
2258
|
-
if (node.layout === "section"
|
|
2259
|
-
ctx.currentSection = node.title;
|
|
3778
|
+
if (node.layout === "section") {
|
|
3779
|
+
if (node.title) ctx.currentSection = node.title;
|
|
3780
|
+
ctx.sectionNumber = (ctx.sectionNumber ?? 0) + 1;
|
|
2260
3781
|
}
|
|
2261
3782
|
await renderSlideLayout(slide, node, theme, ctx);
|
|
2262
3783
|
}
|
|
@@ -2264,381 +3785,1023 @@ async function renderPresentation(presentation, theme, outputPath, inputPath = "
|
|
|
2264
3785
|
writeFileSync2(outputPath, Buffer.from(buffer));
|
|
2265
3786
|
}
|
|
2266
3787
|
|
|
2267
|
-
// src/
|
|
2268
|
-
var
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
3788
|
+
// src/utils/color-mix.ts
|
|
3789
|
+
var HEX6 = /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/;
|
|
3790
|
+
function parseHex(hex) {
|
|
3791
|
+
const raw = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
3792
|
+
const m = HEX6.exec(raw);
|
|
3793
|
+
if (!m) {
|
|
3794
|
+
throw new Error(`Invalid hex color (expected RRGGBB): ${hex}`);
|
|
3795
|
+
}
|
|
3796
|
+
return [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)];
|
|
3797
|
+
}
|
|
3798
|
+
function toHex(r, g, b) {
|
|
3799
|
+
const c = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0").toUpperCase();
|
|
3800
|
+
return `${c(r)}${c(g)}${c(b)}`;
|
|
3801
|
+
}
|
|
3802
|
+
function mixHex(a, b, t) {
|
|
3803
|
+
const [ar, ag, ab] = parseHex(a);
|
|
3804
|
+
const [br, bg, bb] = parseHex(b);
|
|
3805
|
+
const k = Math.max(0, Math.min(1, t));
|
|
3806
|
+
return toHex(ar + (br - ar) * k, ag + (bg - ag) * k, ab + (bb - ab) * k);
|
|
3807
|
+
}
|
|
3808
|
+
function relativeLuminance(hex) {
|
|
3809
|
+
const [r, g, b] = parseHex(hex).map((v) => {
|
|
3810
|
+
const s = v / 255;
|
|
3811
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
3812
|
+
});
|
|
3813
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
3814
|
+
}
|
|
3815
|
+
function isDarkColor2(hex) {
|
|
3816
|
+
return relativeLuminance(hex) < 0.4;
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
// src/models/color-scheme.ts
|
|
3820
|
+
function resolveSchemeMode(scheme) {
|
|
3821
|
+
if (scheme.mode) return scheme.mode;
|
|
3822
|
+
return isDarkColor2(scheme.paper) ? "dark" : "light";
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
// src/themes/color-schemes/ocean.ts
|
|
3826
|
+
var oceanScheme = {
|
|
3827
|
+
name: "ocean",
|
|
3828
|
+
mode: "light",
|
|
3829
|
+
ink: "1E4A6F",
|
|
3830
|
+
paper: "F0F8FF",
|
|
3831
|
+
primary: "4F9FD9",
|
|
3832
|
+
secondary: "2D6A9F"
|
|
2295
3833
|
};
|
|
2296
3834
|
|
|
2297
|
-
// src/themes/
|
|
2298
|
-
var
|
|
2299
|
-
name: "
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
accent: "991B1B",
|
|
2306
|
-
codeBackground: "1E293B",
|
|
2307
|
-
codeText: "E2E8F0",
|
|
2308
|
-
titleBackground: "003366",
|
|
2309
|
-
titleText: "FFFFFF"
|
|
2310
|
-
},
|
|
2311
|
-
fonts: {
|
|
2312
|
-
heading: "Times New Roman",
|
|
2313
|
-
body: "Segoe UI",
|
|
2314
|
-
code: "Consolas",
|
|
2315
|
-
cjk: "\u5B8B\u4F53"
|
|
2316
|
-
},
|
|
2317
|
-
fontSize: {
|
|
2318
|
-
title: 36,
|
|
2319
|
-
heading: 28,
|
|
2320
|
-
body: 18,
|
|
2321
|
-
code: 14,
|
|
2322
|
-
small: 12
|
|
2323
|
-
},
|
|
2324
|
-
shikiTheme: "github-dark"
|
|
3835
|
+
// src/themes/color-schemes/ocean-dark.ts
|
|
3836
|
+
var oceanDarkScheme = {
|
|
3837
|
+
name: "ocean-dark",
|
|
3838
|
+
mode: "dark",
|
|
3839
|
+
ink: "D6E7F5",
|
|
3840
|
+
paper: "0B1C2E",
|
|
3841
|
+
primary: "5BAAE8",
|
|
3842
|
+
secondary: "8BBCDD"
|
|
2325
3843
|
};
|
|
2326
3844
|
|
|
2327
|
-
// src/themes/
|
|
2328
|
-
var
|
|
2329
|
-
name: "
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
accent: "A855F7",
|
|
2336
|
-
codeBackground: "020617",
|
|
2337
|
-
codeText: "38BDF8",
|
|
2338
|
-
titleBackground: "0F172A",
|
|
2339
|
-
titleText: "38BDF8",
|
|
2340
|
-
highlightBackground: "334155",
|
|
2341
|
-
backgroundGradient: { from: "0F172A", to: "1E293B", angle: 135 }
|
|
2342
|
-
},
|
|
2343
|
-
fonts: {
|
|
2344
|
-
heading: "Segoe UI",
|
|
2345
|
-
body: "Segoe UI",
|
|
2346
|
-
code: "Consolas",
|
|
2347
|
-
cjk: "\u5FAE\u8F6F\u96C5\u9ED1"
|
|
2348
|
-
},
|
|
2349
|
-
fontSize: {
|
|
2350
|
-
title: 36,
|
|
2351
|
-
heading: 28,
|
|
2352
|
-
body: 18,
|
|
2353
|
-
code: 14,
|
|
2354
|
-
small: 12
|
|
2355
|
-
},
|
|
2356
|
-
shikiTheme: "dracula"
|
|
3845
|
+
// src/themes/color-schemes/forest.ts
|
|
3846
|
+
var forestScheme = {
|
|
3847
|
+
name: "forest",
|
|
3848
|
+
mode: "light",
|
|
3849
|
+
ink: "2A4A3F",
|
|
3850
|
+
paper: "F0FFF5",
|
|
3851
|
+
primary: "5F9A8A",
|
|
3852
|
+
secondary: "3F6A5A"
|
|
2357
3853
|
};
|
|
2358
3854
|
|
|
2359
|
-
// src/themes/
|
|
2360
|
-
var
|
|
2361
|
-
name: "
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
accent: "D97706",
|
|
2368
|
-
codeBackground: "0F172A",
|
|
2369
|
-
codeText: "E2E8F0",
|
|
2370
|
-
titleBackground: "1E3A8A",
|
|
2371
|
-
titleText: "FFFFFF"
|
|
2372
|
-
},
|
|
2373
|
-
fonts: {
|
|
2374
|
-
heading: "Segoe UI",
|
|
2375
|
-
body: "Segoe UI",
|
|
2376
|
-
code: "Consolas",
|
|
2377
|
-
cjk: "\u5FAE\u8F6F\u96C5\u9ED1"
|
|
2378
|
-
},
|
|
2379
|
-
fontSize: {
|
|
2380
|
-
title: 36,
|
|
2381
|
-
heading: 28,
|
|
2382
|
-
body: 18,
|
|
2383
|
-
code: 14,
|
|
2384
|
-
small: 12
|
|
2385
|
-
},
|
|
2386
|
-
shikiTheme: "github-dark"
|
|
3855
|
+
// src/themes/color-schemes/champagne.ts
|
|
3856
|
+
var champagneScheme = {
|
|
3857
|
+
name: "champagne",
|
|
3858
|
+
mode: "light",
|
|
3859
|
+
ink: "CFB53B",
|
|
3860
|
+
paper: "FFFCE6",
|
|
3861
|
+
primary: "E5CD5F",
|
|
3862
|
+
secondary: "F5E08A"
|
|
2387
3863
|
};
|
|
2388
3864
|
|
|
2389
|
-
// src/themes/
|
|
2390
|
-
var
|
|
2391
|
-
name: "
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
accent: "C2410C",
|
|
2398
|
-
codeBackground: "292524",
|
|
2399
|
-
codeText: "FDFBF7",
|
|
2400
|
-
titleBackground: "065F46",
|
|
2401
|
-
titleText: "292524",
|
|
2402
|
-
backgroundGradient: { from: "FDFBF7", to: "F5EEDF", angle: 180 }
|
|
2403
|
-
},
|
|
2404
|
-
fonts: {
|
|
2405
|
-
heading: "Georgia",
|
|
2406
|
-
body: "Segoe UI",
|
|
2407
|
-
code: "Consolas",
|
|
2408
|
-
cjk: "\u5FAE\u8F6F\u96C5\u9ED1"
|
|
2409
|
-
},
|
|
2410
|
-
fontSize: {
|
|
2411
|
-
title: 36,
|
|
2412
|
-
heading: 28,
|
|
2413
|
-
body: 18,
|
|
2414
|
-
code: 14,
|
|
2415
|
-
small: 12
|
|
2416
|
-
},
|
|
2417
|
-
shikiTheme: "github-dark"
|
|
3865
|
+
// src/themes/color-schemes/graphite.ts
|
|
3866
|
+
var graphiteScheme = {
|
|
3867
|
+
name: "graphite",
|
|
3868
|
+
mode: "light",
|
|
3869
|
+
ink: "4D4D4D",
|
|
3870
|
+
paper: "F8F8F8",
|
|
3871
|
+
primary: "D9D9D9",
|
|
3872
|
+
secondary: "A6A6A6"
|
|
2418
3873
|
};
|
|
2419
3874
|
|
|
2420
|
-
// src/themes/
|
|
2421
|
-
var
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
shikiTheme: "dracula"
|
|
3875
|
+
// src/themes/color-schemes/index.ts
|
|
3876
|
+
var builtInSchemes = [
|
|
3877
|
+
oceanScheme,
|
|
3878
|
+
oceanDarkScheme,
|
|
3879
|
+
forestScheme,
|
|
3880
|
+
champagneScheme,
|
|
3881
|
+
graphiteScheme
|
|
3882
|
+
];
|
|
3883
|
+
var colorSchemes = Object.fromEntries(
|
|
3884
|
+
builtInSchemes.map((s) => [s.name.toLowerCase(), s])
|
|
3885
|
+
);
|
|
3886
|
+
function getColorScheme(name) {
|
|
3887
|
+
if (!name) return void 0;
|
|
3888
|
+
return colorSchemes[name.toLowerCase()];
|
|
3889
|
+
}
|
|
3890
|
+
function listColorSchemes() {
|
|
3891
|
+
return Object.values(colorSchemes);
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
// src/models/text-set.ts
|
|
3895
|
+
var DEFAULT_TEXT_SCHEME_NAME = "system";
|
|
3896
|
+
var DEFAULT_UNIFORM_SIZES = {
|
|
3897
|
+
coverTitle: 36,
|
|
3898
|
+
slideTitle: 28,
|
|
3899
|
+
body: 18,
|
|
3900
|
+
quote: 20,
|
|
3901
|
+
code: 14,
|
|
3902
|
+
small: 12
|
|
2449
3903
|
};
|
|
3904
|
+
function defineUniformTextScheme(options) {
|
|
3905
|
+
const sizes = { ...DEFAULT_UNIFORM_SIZES, ...options.sizes };
|
|
3906
|
+
const boldOn = {
|
|
3907
|
+
coverTitle: true,
|
|
3908
|
+
slideTitle: true,
|
|
3909
|
+
...options.bold
|
|
3910
|
+
};
|
|
3911
|
+
const italicOn = {
|
|
3912
|
+
quote: true,
|
|
3913
|
+
...options.italic
|
|
3914
|
+
};
|
|
3915
|
+
const make = (key, face) => {
|
|
3916
|
+
const entry = { face, size: sizes[key] };
|
|
3917
|
+
if (boldOn[key]) entry.bold = true;
|
|
3918
|
+
if (italicOn[key]) entry.italic = true;
|
|
3919
|
+
return entry;
|
|
3920
|
+
};
|
|
3921
|
+
return {
|
|
3922
|
+
name: options.name,
|
|
3923
|
+
note: options.note ?? `Uniform typeface: ${options.face}`,
|
|
3924
|
+
positions: {
|
|
3925
|
+
coverTitle: make("coverTitle", options.face),
|
|
3926
|
+
slideTitle: make("slideTitle", options.face),
|
|
3927
|
+
body: make("body", options.face),
|
|
3928
|
+
quote: make("quote", options.face),
|
|
3929
|
+
code: options.codeEntry ?? make("code", options.codeFace ?? "Consolas"),
|
|
3930
|
+
small: make("small", options.face)
|
|
3931
|
+
}
|
|
3932
|
+
};
|
|
3933
|
+
}
|
|
2450
3934
|
|
|
2451
|
-
// src/themes/
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
3935
|
+
// src/themes/resolve-scheme.ts
|
|
3936
|
+
function resolveColorScheme(scheme) {
|
|
3937
|
+
const { primary, secondary, ink, paper } = scheme;
|
|
3938
|
+
const dark = resolveSchemeMode(scheme) === "dark";
|
|
3939
|
+
const background = paper;
|
|
3940
|
+
const text = ink;
|
|
3941
|
+
const accent = mixHex(primary, ink, 0.45);
|
|
3942
|
+
const codeBackground = dark ? mixHex(paper, ink, 0.35) : "FFFFFF";
|
|
3943
|
+
const codeText = dark ? mixHex(ink, paper, 0.15) : ink;
|
|
3944
|
+
const titleBackground = mixHex(paper, primary, 0.12);
|
|
3945
|
+
const titleText = ink;
|
|
3946
|
+
const highlightBackground = mixHex(codeBackground, primary, 0.28);
|
|
3947
|
+
const secondaryText = mixHex(secondary, ink, 0.25);
|
|
3948
|
+
return {
|
|
3949
|
+
primary,
|
|
3950
|
+
secondary: secondaryText,
|
|
3951
|
+
background,
|
|
3952
|
+
text,
|
|
3953
|
+
accent,
|
|
3954
|
+
codeBackground,
|
|
3955
|
+
codeText,
|
|
3956
|
+
titleBackground,
|
|
3957
|
+
titleText,
|
|
3958
|
+
highlightBackground,
|
|
3959
|
+
accents: [primary, secondary],
|
|
3960
|
+
subtitle: secondaryText,
|
|
3961
|
+
divider: mixHex(ink, paper, 0.45),
|
|
3962
|
+
muted: mixHex(ink, paper, 0.35),
|
|
3963
|
+
tableHeader: mixHex(paper, primary, 0.12),
|
|
3964
|
+
tableZebra: mixHex(paper, ink, 0.04)
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
// src/themes/layout-schemes/folio/title.ts
|
|
3969
|
+
var titleLayout = {
|
|
3970
|
+
titleAlign: "left",
|
|
3971
|
+
margin: 0.9,
|
|
3972
|
+
extra: {
|
|
3973
|
+
spineWidth: 0.2,
|
|
3974
|
+
marginLeft: 0.55,
|
|
3975
|
+
marginRight: 0.7,
|
|
3976
|
+
titleY: 2.35,
|
|
3977
|
+
subtitleY: 4.05,
|
|
3978
|
+
titleRule: true,
|
|
3979
|
+
titleRuleHeight: 0.035,
|
|
3980
|
+
titleRuleWidth: 3.6,
|
|
3981
|
+
titleRuleColor: "primary",
|
|
3982
|
+
metaBottom: true
|
|
3983
|
+
}
|
|
2480
3984
|
};
|
|
2481
3985
|
|
|
2482
|
-
// src/themes/
|
|
2483
|
-
var
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
},
|
|
2497
|
-
fonts: {
|
|
2498
|
-
heading: "Segoe UI",
|
|
2499
|
-
body: "Segoe UI",
|
|
2500
|
-
code: "Consolas",
|
|
2501
|
-
cjk: "\u5FAE\u8F6F\u96C5\u9ED1"
|
|
2502
|
-
},
|
|
2503
|
-
fontSize: {
|
|
2504
|
-
title: 36,
|
|
2505
|
-
heading: 28,
|
|
2506
|
-
body: 18,
|
|
2507
|
-
code: 14,
|
|
2508
|
-
small: 12
|
|
2509
|
-
},
|
|
2510
|
-
shikiTheme: "nord"
|
|
3986
|
+
// src/themes/layout-schemes/folio/section.ts
|
|
3987
|
+
var sectionLayout = {
|
|
3988
|
+
accentBar: "none",
|
|
3989
|
+
titleAlign: "left",
|
|
3990
|
+
margin: 1,
|
|
3991
|
+
extra: {
|
|
3992
|
+
doubleRules: true,
|
|
3993
|
+
ruleWeight: 0.04,
|
|
3994
|
+
bandY: 2.55,
|
|
3995
|
+
bandH: 1.55,
|
|
3996
|
+
titleRuleColor: "primary",
|
|
3997
|
+
marginLeft: 1,
|
|
3998
|
+
marginRight: 0.7
|
|
3999
|
+
}
|
|
2511
4000
|
};
|
|
2512
4001
|
|
|
2513
|
-
// src/themes/
|
|
2514
|
-
var
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
highlightBackground: "44475A"
|
|
2527
|
-
},
|
|
2528
|
-
fonts: {
|
|
2529
|
-
heading: "Segoe UI",
|
|
2530
|
-
body: "Segoe UI",
|
|
2531
|
-
code: "Consolas",
|
|
2532
|
-
cjk: "\u5FAE\u8F6F\u96C5\u9ED1"
|
|
2533
|
-
},
|
|
2534
|
-
fontSize: {
|
|
2535
|
-
title: 36,
|
|
2536
|
-
heading: 28,
|
|
2537
|
-
body: 18,
|
|
2538
|
-
code: 14,
|
|
2539
|
-
small: 12
|
|
2540
|
-
},
|
|
2541
|
-
shikiTheme: "dracula"
|
|
4002
|
+
// src/themes/layout-schemes/folio/content.ts
|
|
4003
|
+
var contentLayout = {
|
|
4004
|
+
titleAlign: "left",
|
|
4005
|
+
margin: 0.55,
|
|
4006
|
+
titleHeight: 0.82,
|
|
4007
|
+
contentPadding: 0,
|
|
4008
|
+
extra: {
|
|
4009
|
+
marginLeft: 0.72,
|
|
4010
|
+
marginRight: 0.38,
|
|
4011
|
+
titleRule: true,
|
|
4012
|
+
titleRuleHeight: 0.07,
|
|
4013
|
+
titleRuleColor: "secondary"
|
|
4014
|
+
}
|
|
2542
4015
|
};
|
|
2543
4016
|
|
|
2544
|
-
// src/themes/
|
|
2545
|
-
var
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
4017
|
+
// src/themes/layout-schemes/folio/code.ts
|
|
4018
|
+
var codeLayout = {
|
|
4019
|
+
titleAlign: "left",
|
|
4020
|
+
margin: 0.35,
|
|
4021
|
+
titleHeight: 0.55,
|
|
4022
|
+
extra: {
|
|
4023
|
+
marginLeft: 0.4,
|
|
4024
|
+
marginRight: 0.4,
|
|
4025
|
+
titleRule: true,
|
|
4026
|
+
titleRuleHeight: 0.025,
|
|
4027
|
+
titleRuleColor: "secondary"
|
|
4028
|
+
}
|
|
4029
|
+
};
|
|
4030
|
+
|
|
4031
|
+
// src/themes/layout-schemes/folio/quote.ts
|
|
4032
|
+
var quoteLayout = {
|
|
4033
|
+
titleAlign: "left",
|
|
4034
|
+
accentBar: "none",
|
|
4035
|
+
margin: 0.9,
|
|
4036
|
+
extra: {
|
|
4037
|
+
quoteMark: false,
|
|
4038
|
+
quoteBar: true,
|
|
4039
|
+
quoteBarWidth: 0.1,
|
|
4040
|
+
marginLeft: 0.9,
|
|
4041
|
+
marginRight: 0.7,
|
|
4042
|
+
attributionAlign: "left"
|
|
4043
|
+
}
|
|
4044
|
+
};
|
|
4045
|
+
|
|
4046
|
+
// src/themes/layout-schemes/folio/closing.ts
|
|
4047
|
+
var closingLayout = {
|
|
4048
|
+
titleAlign: "center",
|
|
4049
|
+
margin: 0.7,
|
|
4050
|
+
extra: {
|
|
4051
|
+
thankText: "Thank you",
|
|
4052
|
+
titleRule: true,
|
|
4053
|
+
ruleWidth: 2.8,
|
|
4054
|
+
textY: 2.85
|
|
4055
|
+
}
|
|
4056
|
+
};
|
|
4057
|
+
|
|
4058
|
+
// src/themes/layout-schemes/folio/images.ts
|
|
4059
|
+
var imageBase = {
|
|
4060
|
+
titleAlign: "left",
|
|
4061
|
+
margin: 0.55,
|
|
4062
|
+
titleHeight: 0.75,
|
|
4063
|
+
extra: {
|
|
4064
|
+
marginLeft: 0.72,
|
|
4065
|
+
marginRight: 0.38,
|
|
4066
|
+
titleRule: true,
|
|
4067
|
+
titleRuleHeight: 0.055,
|
|
4068
|
+
titleRuleColor: "secondary"
|
|
4069
|
+
}
|
|
4070
|
+
};
|
|
4071
|
+
var imageSingleLayout = { ...imageBase };
|
|
4072
|
+
var imageDoubleLayout = { ...imageBase };
|
|
4073
|
+
var imageTripleLayout = { ...imageBase };
|
|
4074
|
+
|
|
4075
|
+
// src/themes/layout-schemes/folio/footer.ts
|
|
4076
|
+
var footerLayout = {
|
|
4077
|
+
margin: 0.55,
|
|
4078
|
+
footerDivider: true,
|
|
4079
|
+
extra: {
|
|
4080
|
+
footerAlign: "center"
|
|
4081
|
+
}
|
|
4082
|
+
};
|
|
4083
|
+
|
|
4084
|
+
// src/themes/layout-schemes/folio/styles.ts
|
|
4085
|
+
var folioLayoutStyles = {
|
|
4086
|
+
sectionBarWidth: 0.04,
|
|
4087
|
+
footerDivider: true,
|
|
4088
|
+
coverBadge: false
|
|
4089
|
+
};
|
|
4090
|
+
|
|
4091
|
+
// src/themes/layout-schemes/folio/index.ts
|
|
4092
|
+
var folioLayoutScheme = {
|
|
4093
|
+
name: "folio",
|
|
4094
|
+
layouts: {
|
|
4095
|
+
title: titleLayout,
|
|
4096
|
+
section: sectionLayout,
|
|
4097
|
+
content: contentLayout,
|
|
4098
|
+
code: codeLayout,
|
|
4099
|
+
quote: quoteLayout,
|
|
4100
|
+
closing: closingLayout,
|
|
4101
|
+
imageSingle: imageSingleLayout,
|
|
4102
|
+
imageDouble: imageDoubleLayout,
|
|
4103
|
+
imageTriple: imageTripleLayout,
|
|
4104
|
+
footer: footerLayout
|
|
2572
4105
|
},
|
|
2573
|
-
|
|
4106
|
+
styles: folioLayoutStyles,
|
|
4107
|
+
note: "\u7F16\u8F91\u518C\u9875\uFF1A\u4E66\u810A\u5C01\u9762\u3001\u53CC\u7EBF\u7AE0\u8282\u3001\u4E0D\u5BF9\u79F0\u6B63\u6587\u3001pull-quote\u3001\u9875\u811A\u5C45\u4E2D\u3002"
|
|
2574
4108
|
};
|
|
2575
4109
|
|
|
2576
|
-
// src/themes/
|
|
2577
|
-
var
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
4110
|
+
// src/themes/layout-schemes/legacy/title.ts
|
|
4111
|
+
var titleLayout2 = {
|
|
4112
|
+
titleAlign: "center",
|
|
4113
|
+
margin: 0.8
|
|
4114
|
+
};
|
|
4115
|
+
|
|
4116
|
+
// src/themes/layout-schemes/legacy/section.ts
|
|
4117
|
+
var sectionLayout2 = {
|
|
4118
|
+
accentBar: "left",
|
|
4119
|
+
titleAlign: "left",
|
|
4120
|
+
margin: 1
|
|
4121
|
+
};
|
|
4122
|
+
|
|
4123
|
+
// src/themes/layout-schemes/legacy/content.ts
|
|
4124
|
+
var contentLayout2 = {
|
|
4125
|
+
titleAlign: "left",
|
|
4126
|
+
margin: 0.6,
|
|
4127
|
+
titleHeight: 0.9,
|
|
4128
|
+
contentPadding: 0
|
|
4129
|
+
};
|
|
4130
|
+
|
|
4131
|
+
// src/themes/layout-schemes/legacy/code.ts
|
|
4132
|
+
var codeLayout2 = {
|
|
4133
|
+
titleAlign: "left",
|
|
4134
|
+
margin: 0.5,
|
|
4135
|
+
titleHeight: 0.7
|
|
4136
|
+
};
|
|
4137
|
+
|
|
4138
|
+
// src/themes/layout-schemes/legacy/quote.ts
|
|
4139
|
+
var quoteLayout2 = {
|
|
4140
|
+
titleAlign: "center",
|
|
4141
|
+
accentBar: "none",
|
|
4142
|
+
extra: {
|
|
4143
|
+
quoteMark: true,
|
|
4144
|
+
quoteMarkSize: 72
|
|
4145
|
+
}
|
|
4146
|
+
};
|
|
4147
|
+
|
|
4148
|
+
// src/themes/layout-schemes/legacy/footer.ts
|
|
4149
|
+
var footerLayout2 = {
|
|
4150
|
+
margin: 0.6,
|
|
4151
|
+
footerDivider: false
|
|
4152
|
+
};
|
|
4153
|
+
|
|
4154
|
+
// src/themes/layout-schemes/legacy/styles.ts
|
|
4155
|
+
var legacyLayoutStyles = {
|
|
4156
|
+
sectionBarWidth: 0.15,
|
|
4157
|
+
footerDivider: false,
|
|
4158
|
+
coverBadge: false
|
|
4159
|
+
};
|
|
4160
|
+
|
|
4161
|
+
// src/themes/layout-schemes/legacy/index.ts
|
|
4162
|
+
var legacyLayoutScheme = {
|
|
4163
|
+
name: "legacy",
|
|
4164
|
+
layouts: {
|
|
4165
|
+
title: titleLayout2,
|
|
4166
|
+
section: sectionLayout2,
|
|
4167
|
+
content: contentLayout2,
|
|
4168
|
+
code: codeLayout2,
|
|
4169
|
+
quote: quoteLayout2,
|
|
4170
|
+
footer: footerLayout2
|
|
2591
4171
|
},
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
4172
|
+
styles: legacyLayoutStyles,
|
|
4173
|
+
note: "\u65E7\u7248\u7248\u5F0F\uFF1A\u73B0\u7F51\u4E94\u79CD\u57FA\u7840\u9875 + \u9875\u811A\uFF1B\u65E0\u7ED3\u5C3E/\u56FE\u6587\u6761\u76EE\u3002"
|
|
4174
|
+
};
|
|
4175
|
+
|
|
4176
|
+
// src/themes/layout-schemes/golden/title.ts
|
|
4177
|
+
var titleLayout3 = {
|
|
4178
|
+
titleAlign: "right",
|
|
4179
|
+
margin: 0.83,
|
|
4180
|
+
extra: {
|
|
4181
|
+
marginLeft: 6.94,
|
|
4182
|
+
marginRight: 0.83,
|
|
4183
|
+
titleY: 3,
|
|
4184
|
+
subtitleY: 4,
|
|
4185
|
+
titleRule: true,
|
|
4186
|
+
titleRuleHeight: 0.04,
|
|
4187
|
+
titleRuleWidth: 8.22,
|
|
4188
|
+
titleRuleY: 4.64,
|
|
4189
|
+
titleRuleColor: "primary",
|
|
4190
|
+
metaBottom: true,
|
|
4191
|
+
metaY: 5
|
|
4192
|
+
}
|
|
4193
|
+
};
|
|
4194
|
+
|
|
4195
|
+
// src/themes/layout-schemes/golden/section.ts
|
|
4196
|
+
var sectionLayout3 = {
|
|
4197
|
+
accentBar: "none",
|
|
4198
|
+
titleAlign: "right",
|
|
4199
|
+
margin: 0.83,
|
|
4200
|
+
extra: {
|
|
4201
|
+
marginLeft: 6.67,
|
|
4202
|
+
marginRight: 0.83,
|
|
4203
|
+
// 章节序号占位(node.subtitle)+ 标题
|
|
4204
|
+
subtitleY: 3.03,
|
|
4205
|
+
subtitleH: 0.36,
|
|
4206
|
+
titleY: 3.5,
|
|
4207
|
+
titleH: 0.89,
|
|
4208
|
+
// 黄金分割横线
|
|
4209
|
+
sectionRule: true,
|
|
4210
|
+
sectionRuleY: 4.64,
|
|
4211
|
+
sectionRuleWidth: 8.22,
|
|
4212
|
+
sectionRuleHeight: 0.04,
|
|
4213
|
+
sectionRuleColor: "primary"
|
|
4214
|
+
}
|
|
4215
|
+
};
|
|
4216
|
+
|
|
4217
|
+
// src/themes/layout-schemes/golden/content.ts
|
|
4218
|
+
var contentLayout3 = {
|
|
4219
|
+
titleAlign: "left",
|
|
4220
|
+
margin: 1.11,
|
|
4221
|
+
titleHeight: 1.3,
|
|
4222
|
+
contentPadding: 0,
|
|
4223
|
+
extra: {
|
|
4224
|
+
marginLeft: 1.11,
|
|
4225
|
+
marginRight: 1.11,
|
|
4226
|
+
titleY: 0.72,
|
|
4227
|
+
titleRule: true,
|
|
4228
|
+
titleRuleWidth: 0.89,
|
|
4229
|
+
titleRuleHeight: 0.04,
|
|
4230
|
+
titleRuleColor: "primary"
|
|
4231
|
+
}
|
|
4232
|
+
};
|
|
4233
|
+
|
|
4234
|
+
// src/themes/layout-schemes/golden/code.ts
|
|
4235
|
+
var codeLayout3 = {
|
|
4236
|
+
titleAlign: "left",
|
|
4237
|
+
margin: 1.11,
|
|
4238
|
+
titleHeight: 0.6,
|
|
4239
|
+
extra: {
|
|
4240
|
+
marginLeft: 1.11,
|
|
4241
|
+
marginRight: 1.11,
|
|
4242
|
+
titleY: 0.72,
|
|
4243
|
+
titleRule: true,
|
|
4244
|
+
titleRuleWidth: 0.89,
|
|
4245
|
+
titleRuleHeight: 0.04,
|
|
4246
|
+
titleRuleColor: "primary"
|
|
4247
|
+
}
|
|
4248
|
+
};
|
|
4249
|
+
|
|
4250
|
+
// src/themes/layout-schemes/golden/quote.ts
|
|
4251
|
+
var quoteLayout3 = {
|
|
4252
|
+
titleAlign: "center",
|
|
4253
|
+
accentBar: "none",
|
|
4254
|
+
margin: 2.22,
|
|
4255
|
+
extra: {
|
|
4256
|
+
quoteMark: false,
|
|
4257
|
+
quoteBar: false,
|
|
4258
|
+
marginLeft: 2.22,
|
|
4259
|
+
marginRight: 2.22,
|
|
4260
|
+
quoteRule: true,
|
|
4261
|
+
quoteRuleY: 2.19,
|
|
4262
|
+
quoteRuleWidth: 1.33,
|
|
4263
|
+
quoteRuleHeight: 0.04,
|
|
4264
|
+
quoteRuleColor: "primary",
|
|
4265
|
+
textY: 2.58,
|
|
4266
|
+
textH: 1.94,
|
|
4267
|
+
attributionAlign: "center"
|
|
4268
|
+
}
|
|
4269
|
+
};
|
|
4270
|
+
|
|
4271
|
+
// src/themes/layout-schemes/golden/closing.ts
|
|
4272
|
+
var closingLayout2 = {
|
|
4273
|
+
titleAlign: "right",
|
|
4274
|
+
margin: 0.83,
|
|
4275
|
+
extra: {
|
|
4276
|
+
thankText: "Thank you",
|
|
4277
|
+
marginLeft: 5.83,
|
|
4278
|
+
marginRight: 0.83,
|
|
4279
|
+
textY: 3.22,
|
|
4280
|
+
titleRule: true,
|
|
4281
|
+
closingRule: "below",
|
|
4282
|
+
ruleWidth: 8.22,
|
|
4283
|
+
ruleHeight: 0.04,
|
|
4284
|
+
ruleY: 4.64
|
|
4285
|
+
}
|
|
4286
|
+
};
|
|
4287
|
+
|
|
4288
|
+
// src/themes/layout-schemes/golden/images.ts
|
|
4289
|
+
var imageBase2 = {
|
|
4290
|
+
titleAlign: "left",
|
|
4291
|
+
margin: 1.11,
|
|
4292
|
+
titleHeight: 1.3,
|
|
4293
|
+
extra: {
|
|
4294
|
+
marginLeft: 1.11,
|
|
4295
|
+
marginRight: 1.11,
|
|
4296
|
+
titleY: 0.72,
|
|
4297
|
+
titleRule: true,
|
|
4298
|
+
titleRuleWidth: 0.89,
|
|
4299
|
+
titleRuleHeight: 0.04,
|
|
4300
|
+
titleRuleColor: "primary"
|
|
4301
|
+
}
|
|
4302
|
+
};
|
|
4303
|
+
var imageSingleLayout2 = { ...imageBase2 };
|
|
4304
|
+
var imageDoubleLayout2 = { ...imageBase2 };
|
|
4305
|
+
var imageTripleLayout2 = { ...imageBase2 };
|
|
4306
|
+
|
|
4307
|
+
// src/themes/layout-schemes/golden/footer.ts
|
|
4308
|
+
var footerLayout3 = {
|
|
4309
|
+
margin: 1.11,
|
|
4310
|
+
footerDivider: true,
|
|
4311
|
+
extra: {
|
|
4312
|
+
footerAlign: "left",
|
|
4313
|
+
marginLeft: 1.11,
|
|
4314
|
+
marginRight: 1.11
|
|
4315
|
+
}
|
|
4316
|
+
};
|
|
4317
|
+
|
|
4318
|
+
// src/themes/layout-schemes/golden/styles.ts
|
|
4319
|
+
var goldenLayoutStyles = {
|
|
4320
|
+
sectionBarWidth: 0.04,
|
|
4321
|
+
footerDivider: true,
|
|
4322
|
+
coverBadge: false,
|
|
4323
|
+
codeRadius: 16
|
|
4324
|
+
};
|
|
4325
|
+
|
|
4326
|
+
// src/themes/layout-schemes/golden/index.ts
|
|
4327
|
+
var goldenLayoutScheme = {
|
|
4328
|
+
name: "golden",
|
|
4329
|
+
layouts: {
|
|
4330
|
+
title: titleLayout3,
|
|
4331
|
+
section: sectionLayout3,
|
|
4332
|
+
content: contentLayout3,
|
|
4333
|
+
code: codeLayout3,
|
|
4334
|
+
quote: quoteLayout3,
|
|
4335
|
+
closing: closingLayout2,
|
|
4336
|
+
imageSingle: imageSingleLayout2,
|
|
4337
|
+
imageDouble: imageDoubleLayout2,
|
|
4338
|
+
imageTriple: imageTripleLayout2,
|
|
4339
|
+
footer: footerLayout3
|
|
2597
4340
|
},
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
4341
|
+
styles: goldenLayoutStyles,
|
|
4342
|
+
note: "\u9EC4\u91D1\u6BD4\u4F8B\uFF1A\u53F3\u5BF9\u9F50\u7559\u767D\u5C01\u9762/\u7AE0\u8282/\u7ED3\u5C3E + \u9EC4\u91D1\u5206\u5272\u6A2A\u7EBF\uFF0C\u6B63\u6587/\u4EE3\u7801/\u5F15\u7528/\u56FE\u6587\u5171\u7528\u9AA8\u67B6\u8BED\u8A00\u3002"
|
|
4343
|
+
};
|
|
4344
|
+
|
|
4345
|
+
// src/themes/layout-schemes/minimal/title.ts
|
|
4346
|
+
var titleLayout4 = {
|
|
4347
|
+
titleAlign: "left",
|
|
4348
|
+
margin: 0.83,
|
|
4349
|
+
extra: {
|
|
4350
|
+
minimalTitle: true,
|
|
4351
|
+
minimalLineX: 0.83,
|
|
4352
|
+
minimalLineY1: 2.64,
|
|
4353
|
+
minimalLineH: 1.94,
|
|
4354
|
+
minimalLineW: 0.04,
|
|
4355
|
+
minimalBoxX: 1.11,
|
|
4356
|
+
minimalBoxW: 8.89,
|
|
4357
|
+
minimalTitleY: 2.64,
|
|
4358
|
+
minimalSubtitleY: 3.64,
|
|
4359
|
+
minimalAuthorY: 5.28,
|
|
4360
|
+
minimalDateY: 5.64,
|
|
4361
|
+
minimalMetaW: 4.17
|
|
4362
|
+
}
|
|
4363
|
+
};
|
|
4364
|
+
|
|
4365
|
+
// src/themes/layout-schemes/minimal/section.ts
|
|
4366
|
+
var sectionLayout4 = {
|
|
4367
|
+
titleAlign: "left",
|
|
4368
|
+
margin: 0.83,
|
|
4369
|
+
extra: {
|
|
4370
|
+
minimalNumber: true,
|
|
4371
|
+
footer: "off",
|
|
4372
|
+
minimalNumberX: 1.11,
|
|
4373
|
+
minimalNumberY: 2.43,
|
|
4374
|
+
minimalNumberW: 2.5,
|
|
4375
|
+
minimalNumberH: 1.53,
|
|
4376
|
+
minimalRuleX: 3.89,
|
|
4377
|
+
minimalRuleY1: 2.71,
|
|
4378
|
+
minimalRuleW: 0.03,
|
|
4379
|
+
minimalRuleH: 1.11,
|
|
4380
|
+
minimalTitleX: 4.31,
|
|
4381
|
+
minimalTitleY: 2.71,
|
|
4382
|
+
minimalTitleW: 6.94,
|
|
4383
|
+
minimalTitleH: 0.69
|
|
4384
|
+
}
|
|
4385
|
+
};
|
|
4386
|
+
|
|
4387
|
+
// src/themes/layout-schemes/minimal/content.ts
|
|
4388
|
+
var contentLayout4 = {
|
|
4389
|
+
titleAlign: "left",
|
|
4390
|
+
margin: 0.83,
|
|
4391
|
+
titleHeight: 0.44,
|
|
4392
|
+
extra: {
|
|
4393
|
+
minimalBox: true,
|
|
4394
|
+
minimalBoxX: 0.83,
|
|
4395
|
+
minimalBoxY: 1.33,
|
|
4396
|
+
minimalBoxW: 11.67,
|
|
4397
|
+
minimalBoxH: 5.28,
|
|
4398
|
+
minimalTitleY: 0.5,
|
|
4399
|
+
minimalTitleRuleY: 1.03,
|
|
4400
|
+
minimalTitleRuleW: 0.56,
|
|
4401
|
+
minimalTitleRuleH: 0.03
|
|
4402
|
+
}
|
|
4403
|
+
};
|
|
4404
|
+
|
|
4405
|
+
// src/themes/layout-schemes/minimal/code.ts
|
|
4406
|
+
var codeLayout4 = {
|
|
4407
|
+
titleAlign: "left",
|
|
4408
|
+
margin: 0.83,
|
|
4409
|
+
extra: {
|
|
4410
|
+
minimalCard: true,
|
|
4411
|
+
minimalCardX: 0.83,
|
|
4412
|
+
minimalCardY: 1.33,
|
|
4413
|
+
minimalCardW: 11.67,
|
|
4414
|
+
minimalCardH: 5.28,
|
|
4415
|
+
minimalCardRadius: 0.08,
|
|
4416
|
+
minimalTitleY: 0.5,
|
|
4417
|
+
minimalTitleRuleY: 1.03,
|
|
4418
|
+
minimalTitleRuleW: 0.56,
|
|
4419
|
+
minimalTitleRuleH: 0.03
|
|
4420
|
+
}
|
|
4421
|
+
};
|
|
4422
|
+
|
|
4423
|
+
// src/themes/layout-schemes/minimal/quote.ts
|
|
4424
|
+
var quoteLayout4 = {
|
|
4425
|
+
titleAlign: "left",
|
|
4426
|
+
margin: 0.83,
|
|
4427
|
+
extra: {
|
|
4428
|
+
minimalQuote: true,
|
|
4429
|
+
minimalRuleX: 1.67,
|
|
4430
|
+
minimalRuleY: 2.78,
|
|
4431
|
+
minimalRuleH: 1.39,
|
|
4432
|
+
minimalRuleW: 0.04,
|
|
4433
|
+
minimalTextX: 2.08,
|
|
4434
|
+
minimalTextW: 9.58,
|
|
4435
|
+
minimalTextH: 1.39
|
|
4436
|
+
}
|
|
4437
|
+
};
|
|
4438
|
+
|
|
4439
|
+
// src/themes/layout-schemes/minimal/closing.ts
|
|
4440
|
+
var closingLayout3 = {
|
|
4441
|
+
titleAlign: "center",
|
|
4442
|
+
margin: 0.83,
|
|
4443
|
+
extra: {
|
|
4444
|
+
minimalClosing: true,
|
|
4445
|
+
thankText: "Thank you",
|
|
4446
|
+
minimalRuleW: 1.11,
|
|
4447
|
+
minimalRuleH: 0.03,
|
|
4448
|
+
minimalRuleY: 2.78,
|
|
4449
|
+
minimalThankX: 2.5,
|
|
4450
|
+
minimalThankY: 3.06,
|
|
4451
|
+
minimalThankW: 8.33,
|
|
4452
|
+
minimalContactX: 3.89,
|
|
4453
|
+
minimalContactY: 4.31,
|
|
4454
|
+
minimalContactW: 5.56
|
|
4455
|
+
}
|
|
4456
|
+
};
|
|
4457
|
+
|
|
4458
|
+
// src/themes/layout-schemes/minimal/images.ts
|
|
4459
|
+
var imageBase3 = {
|
|
4460
|
+
titleAlign: "left",
|
|
4461
|
+
margin: 0.83,
|
|
4462
|
+
extra: {
|
|
4463
|
+
minimalImage: true,
|
|
4464
|
+
minimalImageY: 1.33,
|
|
4465
|
+
minimalImageH: 5.28,
|
|
4466
|
+
minimalTitleY: 0.5,
|
|
4467
|
+
minimalTitleRuleY: 1.03,
|
|
4468
|
+
minimalTitleRuleW: 0.56,
|
|
4469
|
+
minimalTitleRuleH: 0.03
|
|
4470
|
+
}
|
|
4471
|
+
};
|
|
4472
|
+
var imageSingleLayout3 = { ...imageBase3 };
|
|
4473
|
+
var imageDoubleLayout3 = { ...imageBase3 };
|
|
4474
|
+
var imageTripleLayout3 = { ...imageBase3 };
|
|
4475
|
+
|
|
4476
|
+
// src/themes/layout-schemes/minimal/footer.ts
|
|
4477
|
+
var footerLayout4 = {
|
|
4478
|
+
margin: 0.83,
|
|
4479
|
+
footerDivider: true,
|
|
4480
|
+
extra: {
|
|
4481
|
+
footerAlign: "left",
|
|
4482
|
+
dividerY: 6.94,
|
|
4483
|
+
footerY: 7.03
|
|
4484
|
+
}
|
|
4485
|
+
};
|
|
4486
|
+
|
|
4487
|
+
// src/themes/layout-schemes/minimal/styles.ts
|
|
4488
|
+
var minimalLayoutStyles = {
|
|
4489
|
+
footerDivider: true,
|
|
4490
|
+
coverBadge: false
|
|
4491
|
+
};
|
|
4492
|
+
|
|
4493
|
+
// src/themes/layout-schemes/minimal/index.ts
|
|
4494
|
+
var minimalLayoutScheme = {
|
|
4495
|
+
name: "minimal",
|
|
4496
|
+
layouts: {
|
|
4497
|
+
title: titleLayout4,
|
|
4498
|
+
section: sectionLayout4,
|
|
4499
|
+
content: contentLayout4,
|
|
4500
|
+
code: codeLayout4,
|
|
4501
|
+
quote: quoteLayout4,
|
|
4502
|
+
closing: closingLayout3,
|
|
4503
|
+
imageSingle: imageSingleLayout3,
|
|
4504
|
+
imageDouble: imageDoubleLayout3,
|
|
4505
|
+
imageTriple: imageTripleLayout3,
|
|
4506
|
+
footer: footerLayout4
|
|
2604
4507
|
},
|
|
2605
|
-
|
|
4508
|
+
styles: minimalLayoutStyles,
|
|
4509
|
+
note: "\u6781\u7B80\u6863\u6848\uFF1A\u7EDF\u4E000.83\u8FB9\u8DDD\u3001\u6A2A/\u7AD6\u7EBF\u6027\u88C5\u9970\u3001\u8D85\u5927\u7AE0\u5E8F\u53F7\u3001\u5706\u89D2\u4EE3\u7801\u5361\u7247\u3001\u865A\u7EBF\u56FE\u7247\u5360\u4F4D\u3002"
|
|
2606
4510
|
};
|
|
2607
4511
|
|
|
2608
|
-
// src/themes/
|
|
2609
|
-
var
|
|
2610
|
-
|
|
2611
|
-
|
|
4512
|
+
// src/themes/layout-schemes/index.ts
|
|
4513
|
+
var builtInLayoutSchemes = [
|
|
4514
|
+
folioLayoutScheme,
|
|
4515
|
+
legacyLayoutScheme,
|
|
4516
|
+
goldenLayoutScheme,
|
|
4517
|
+
minimalLayoutScheme
|
|
4518
|
+
];
|
|
4519
|
+
var layoutSchemes = Object.fromEntries(
|
|
4520
|
+
builtInLayoutSchemes.map((s) => [s.name.toLowerCase(), s])
|
|
4521
|
+
);
|
|
4522
|
+
function getLayoutScheme(name) {
|
|
4523
|
+
if (!name) return void 0;
|
|
4524
|
+
return layoutSchemes[name.toLowerCase()];
|
|
4525
|
+
}
|
|
4526
|
+
function listLayoutSchemes() {
|
|
4527
|
+
return Object.values(layoutSchemes);
|
|
4528
|
+
}
|
|
4529
|
+
|
|
4530
|
+
// src/themes/presets/blue.ts
|
|
4531
|
+
var bluePreset = {
|
|
4532
|
+
name: "blue",
|
|
4533
|
+
colorScheme: "ocean",
|
|
4534
|
+
textScheme: "academic",
|
|
4535
|
+
layoutScheme: "legacy"
|
|
2612
4536
|
};
|
|
2613
4537
|
|
|
2614
|
-
// src/themes/
|
|
2615
|
-
var
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
4538
|
+
// src/themes/presets/emerald.ts
|
|
4539
|
+
var emeraldPreset = {
|
|
4540
|
+
name: "emerald",
|
|
4541
|
+
colorScheme: "forest",
|
|
4542
|
+
textScheme: "system",
|
|
4543
|
+
layoutScheme: "folio"
|
|
4544
|
+
};
|
|
4545
|
+
|
|
4546
|
+
// src/themes/presets/gold.ts
|
|
4547
|
+
var goldPreset = {
|
|
4548
|
+
name: "gold",
|
|
4549
|
+
colorScheme: "champagne",
|
|
4550
|
+
textScheme: "kai",
|
|
4551
|
+
layoutScheme: "golden"
|
|
4552
|
+
};
|
|
4553
|
+
|
|
4554
|
+
// src/themes/presets/slate.ts
|
|
4555
|
+
var slatePreset = {
|
|
4556
|
+
name: "slate",
|
|
4557
|
+
colorScheme: "graphite",
|
|
4558
|
+
textScheme: "source-han-serif",
|
|
4559
|
+
layoutScheme: "minimal"
|
|
4560
|
+
};
|
|
4561
|
+
|
|
4562
|
+
// src/themes/presets/index.ts
|
|
4563
|
+
var builtInThemePresets = [
|
|
4564
|
+
bluePreset,
|
|
4565
|
+
emeraldPreset,
|
|
4566
|
+
goldPreset,
|
|
4567
|
+
slatePreset
|
|
4568
|
+
];
|
|
4569
|
+
var themePresets = Object.fromEntries(
|
|
4570
|
+
builtInThemePresets.map((p) => [p.name.toLowerCase(), p])
|
|
4571
|
+
);
|
|
4572
|
+
function getThemePreset(name) {
|
|
4573
|
+
if (!name) return void 0;
|
|
4574
|
+
return themePresets[name.toLowerCase()];
|
|
4575
|
+
}
|
|
4576
|
+
function listThemePresets() {
|
|
4577
|
+
return Object.values(themePresets);
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
// src/themes/text-entries.ts
|
|
4581
|
+
var sharedCode = {
|
|
4582
|
+
face: "Consolas",
|
|
4583
|
+
cjkFace: "Consolas",
|
|
4584
|
+
size: 14
|
|
2628
4585
|
};
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
4586
|
+
|
|
4587
|
+
// src/themes/text-schemes/academic.ts
|
|
4588
|
+
var academicTextScheme = defineUniformTextScheme({
|
|
4589
|
+
name: "academic",
|
|
4590
|
+
face: "\u5B8B\u4F53",
|
|
4591
|
+
codeEntry: sharedCode,
|
|
4592
|
+
sizes: {
|
|
4593
|
+
coverTitle: 42,
|
|
4594
|
+
slideTitle: 32
|
|
4595
|
+
},
|
|
4596
|
+
note: "\u5B66\u672F\uFF1A\u6B63\u6587/\u6807\u9898\u7EDF\u4E00\u5B8B\u4F53\uFF0C\u4EE3\u7801 Consolas\uFF1B\u6807\u9898\u52A0\u5927\uFF08\u5C01\u9762 42 / \u9875\u6807\u9898 32\uFF09\u3002"
|
|
4597
|
+
});
|
|
4598
|
+
|
|
4599
|
+
// src/themes/text-schemes/system.ts
|
|
4600
|
+
var systemTextScheme = defineUniformTextScheme({
|
|
4601
|
+
name: "system",
|
|
4602
|
+
face: "\u5FAE\u8F6F\u96C5\u9ED1",
|
|
4603
|
+
codeEntry: sharedCode,
|
|
4604
|
+
note: "\u7CFB\u7EDF\u9ED8\u8BA4\uFF1A\u6B63\u6587/\u6807\u9898\u7EDF\u4E00\u5FAE\u8F6F\u96C5\u9ED1\uFF0C\u4EE3\u7801 Consolas\u3002"
|
|
4605
|
+
});
|
|
4606
|
+
|
|
4607
|
+
// src/themes/text-schemes/kai.ts
|
|
4608
|
+
var kaiTextScheme = defineUniformTextScheme({
|
|
4609
|
+
name: "kai",
|
|
4610
|
+
face: "KaiTi",
|
|
4611
|
+
codeEntry: sharedCode,
|
|
4612
|
+
note: "\u6977\u4F53\uFF1A\u6B63\u6587/\u6807\u9898\u7EDF\u4E00 KaiTi\uFF0C\u4EE3\u7801 Consolas\u3002"
|
|
4613
|
+
});
|
|
4614
|
+
|
|
4615
|
+
// src/themes/text-schemes/source-han-serif.ts
|
|
4616
|
+
var sourceHanSerifTextScheme = defineUniformTextScheme({
|
|
4617
|
+
name: "source-han-serif",
|
|
4618
|
+
face: "\u601D\u6E90\u5B8B\u4F53",
|
|
4619
|
+
codeEntry: sharedCode,
|
|
4620
|
+
note: "\u601D\u6E90\u5B8B\u4F53\uFF1A\u6B63\u6587/\u6807\u9898\u7EDF\u4E00\u601D\u6E90\u5B8B\u4F53\uFF0C\u4EE3\u7801 Consolas\u3002"
|
|
4621
|
+
});
|
|
4622
|
+
|
|
4623
|
+
// src/themes/text-schemes/text-index.ts
|
|
4624
|
+
var builtInTextSchemes = [
|
|
4625
|
+
systemTextScheme,
|
|
4626
|
+
academicTextScheme,
|
|
4627
|
+
kaiTextScheme,
|
|
4628
|
+
sourceHanSerifTextScheme
|
|
4629
|
+
];
|
|
4630
|
+
var textSchemes = Object.fromEntries(
|
|
4631
|
+
builtInTextSchemes.map((s) => [s.name.toLowerCase(), s])
|
|
4632
|
+
);
|
|
4633
|
+
function getTextScheme(name) {
|
|
4634
|
+
if (!name) return void 0;
|
|
4635
|
+
return textSchemes[name.toLowerCase()];
|
|
4636
|
+
}
|
|
4637
|
+
function listTextSchemes() {
|
|
4638
|
+
return Object.values(textSchemes);
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4641
|
+
// src/themes/from-scheme.ts
|
|
4642
|
+
function resolveThemePresetOption(input) {
|
|
4643
|
+
if (input === void 0 || input === null) return void 0;
|
|
4644
|
+
if (typeof input === "object") return input;
|
|
4645
|
+
const name = input.trim();
|
|
4646
|
+
if (!name) return void 0;
|
|
4647
|
+
const preset = getThemePreset(name);
|
|
4648
|
+
if (!preset) {
|
|
4649
|
+
console.warn(`Theme preset "${name}" not found; using scheme defaults only`);
|
|
4650
|
+
return void 0;
|
|
4651
|
+
}
|
|
4652
|
+
return preset;
|
|
4653
|
+
}
|
|
4654
|
+
function resolveColorSchemeOption(input) {
|
|
4655
|
+
if (input && typeof input === "object") return input;
|
|
4656
|
+
if (typeof input !== "string" || !input.trim()) return void 0;
|
|
4657
|
+
const name = input.trim();
|
|
4658
|
+
const scheme = getColorScheme(name);
|
|
4659
|
+
if (!scheme) {
|
|
4660
|
+
console.warn(`Color scheme "${name}" not found`);
|
|
4661
|
+
return void 0;
|
|
4662
|
+
}
|
|
4663
|
+
return scheme;
|
|
4664
|
+
}
|
|
4665
|
+
function resolveTextSchemeOption(input) {
|
|
4666
|
+
if (input && typeof input === "object") return input;
|
|
4667
|
+
const name = typeof input === "string" && input.trim() ? input.trim() : DEFAULT_TEXT_SCHEME_NAME;
|
|
4668
|
+
const scheme = getTextScheme(name);
|
|
4669
|
+
if (!scheme) {
|
|
4670
|
+
if (typeof input === "string" && input.trim()) {
|
|
4671
|
+
console.warn(`Text scheme "${name}" not found, using "${DEFAULT_TEXT_SCHEME_NAME}"`);
|
|
4672
|
+
}
|
|
4673
|
+
const fallback = getTextScheme(DEFAULT_TEXT_SCHEME_NAME);
|
|
4674
|
+
if (!fallback) {
|
|
4675
|
+
throw new Error(`Default text scheme "${DEFAULT_TEXT_SCHEME_NAME}" is not registered`);
|
|
4676
|
+
}
|
|
4677
|
+
return fallback;
|
|
2632
4678
|
}
|
|
2633
|
-
|
|
4679
|
+
return scheme;
|
|
4680
|
+
}
|
|
4681
|
+
function bridgeFontsFromTextScheme(scheme) {
|
|
4682
|
+
const p = scheme.positions;
|
|
4683
|
+
const body = p.body;
|
|
4684
|
+
const headingSource = p.slideTitle ?? p.coverTitle;
|
|
4685
|
+
return {
|
|
4686
|
+
body: body.face,
|
|
4687
|
+
cjk: body.cjkFace ?? body.face,
|
|
4688
|
+
heading: headingSource.face ?? body.face,
|
|
4689
|
+
code: p.code.face
|
|
4690
|
+
};
|
|
4691
|
+
}
|
|
4692
|
+
function bridgeFontSizesFromTextScheme(scheme) {
|
|
4693
|
+
const p = scheme.positions;
|
|
4694
|
+
return {
|
|
4695
|
+
title: p.coverTitle.size,
|
|
4696
|
+
heading: p.slideTitle.size,
|
|
4697
|
+
body: p.body.size,
|
|
4698
|
+
code: p.code.size,
|
|
4699
|
+
small: p.small.size
|
|
4700
|
+
};
|
|
4701
|
+
}
|
|
4702
|
+
function textStylesFromScheme(scheme) {
|
|
4703
|
+
return { ...scheme.positions };
|
|
4704
|
+
}
|
|
4705
|
+
function resolveLayoutSchemeOption(input) {
|
|
4706
|
+
if (input === void 0) return {};
|
|
4707
|
+
if (typeof input === "object" && input !== null) {
|
|
4708
|
+
return {
|
|
4709
|
+
layouts: input.layouts,
|
|
4710
|
+
styles: input.styles,
|
|
4711
|
+
layoutSet: input.name,
|
|
4712
|
+
scheme: input
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
const name = input.trim();
|
|
4716
|
+
if (!name) return {};
|
|
4717
|
+
const scheme = getLayoutScheme(name);
|
|
4718
|
+
if (!scheme) {
|
|
4719
|
+
console.warn(`Layout scheme "${name}" not found, using built-in layout defaults`);
|
|
4720
|
+
return {};
|
|
4721
|
+
}
|
|
4722
|
+
return {
|
|
4723
|
+
layouts: scheme.layouts,
|
|
4724
|
+
styles: scheme.styles,
|
|
4725
|
+
layoutSet: scheme.name,
|
|
4726
|
+
scheme
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
function createThemeFromScheme(scheme, options = {}) {
|
|
4730
|
+
const colorScheme = resolveColorSchemeOption(options.colorScheme) ?? scheme;
|
|
4731
|
+
const textScheme = resolveTextSchemeOption(options.textScheme);
|
|
4732
|
+
const fonts = options.fonts ?? bridgeFontsFromTextScheme(textScheme);
|
|
4733
|
+
const fontSize = options.fontSize ?? bridgeFontSizesFromTextScheme(textScheme);
|
|
4734
|
+
const fromLayout = resolveLayoutSchemeOption(options.layoutScheme);
|
|
4735
|
+
return {
|
|
4736
|
+
name: scheme.name,
|
|
4737
|
+
colors: resolveColorScheme(colorScheme),
|
|
4738
|
+
fonts,
|
|
4739
|
+
fontSize,
|
|
4740
|
+
shikiTheme: options.shikiTheme ?? (resolveSchemeMode(colorScheme) === "dark" ? "github-dark" : "github-light"),
|
|
4741
|
+
layouts: options.layouts ?? fromLayout.layouts,
|
|
4742
|
+
styles: options.styles ?? fromLayout.styles,
|
|
4743
|
+
typography: options.typography,
|
|
4744
|
+
avoid: options.avoid,
|
|
4745
|
+
textSet: textScheme.name,
|
|
4746
|
+
textScheme,
|
|
4747
|
+
textStyles: textStylesFromScheme(textScheme),
|
|
4748
|
+
layoutSet: fromLayout.layoutSet,
|
|
4749
|
+
presetSet: options.presetSet
|
|
4750
|
+
};
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
// src/themes/index.ts
|
|
4754
|
+
function resolveThemeName(name) {
|
|
4755
|
+
const trimmed = typeof name === "string" ? name.trim() : "";
|
|
4756
|
+
const key = trimmed || DEFAULT_THEME_NAME;
|
|
4757
|
+
const preset = getThemePreset(key);
|
|
4758
|
+
if (preset) return { kind: "preset", preset };
|
|
4759
|
+
if (getColorScheme(key)) return { kind: "color", colorName: key };
|
|
4760
|
+
return { kind: "default" };
|
|
4761
|
+
}
|
|
4762
|
+
function getTheme(name, options = {}) {
|
|
4763
|
+
if (name !== void 0 && name !== null && typeof name !== "string") {
|
|
2634
4764
|
throw new Error(`Invalid theme value: expected a string, got ${typeof name}`);
|
|
2635
4765
|
}
|
|
2636
|
-
const
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
4766
|
+
const explicitPreset = resolveThemePresetOption(options.preset);
|
|
4767
|
+
const resolved = resolveThemeName(name);
|
|
4768
|
+
if (resolved.kind === "preset" || resolved.kind === "default" && !explicitPreset) {
|
|
4769
|
+
const preset = resolved.kind === "preset" ? resolved.preset : getThemePreset(DEFAULT_THEME_NAME);
|
|
4770
|
+
if (resolved.kind === "default" && name?.trim()) {
|
|
4771
|
+
console.warn(`Theme "${name}" not found, using "${preset.name}"`);
|
|
4772
|
+
}
|
|
4773
|
+
return createThemeFromScheme(getColorScheme(preset.colorScheme), {
|
|
4774
|
+
colorScheme: options.colorScheme,
|
|
4775
|
+
textScheme: options.textScheme ?? preset.textScheme,
|
|
4776
|
+
layoutScheme: options.layoutScheme ?? preset.layoutScheme,
|
|
4777
|
+
presetSet: preset.name
|
|
4778
|
+
});
|
|
4779
|
+
}
|
|
4780
|
+
if (resolved.kind === "default" && explicitPreset) {
|
|
4781
|
+
console.warn(`Theme "${name}" not found, using preset "${explicitPreset.name}"`);
|
|
4782
|
+
return createThemeFromScheme(getColorScheme(explicitPreset.colorScheme), {
|
|
4783
|
+
colorScheme: options.colorScheme,
|
|
4784
|
+
textScheme: options.textScheme ?? explicitPreset.textScheme,
|
|
4785
|
+
layoutScheme: options.layoutScheme ?? explicitPreset.layoutScheme,
|
|
4786
|
+
presetSet: explicitPreset.name
|
|
4787
|
+
});
|
|
2640
4788
|
}
|
|
2641
|
-
|
|
4789
|
+
const colorName = resolved.kind === "color" ? resolved.colorName : explicitPreset.colorScheme;
|
|
4790
|
+
return createThemeFromScheme(getColorScheme(colorName), {
|
|
4791
|
+
colorScheme: options.colorScheme,
|
|
4792
|
+
textScheme: options.textScheme ?? explicitPreset?.textScheme,
|
|
4793
|
+
layoutScheme: options.layoutScheme ?? explicitPreset?.layoutScheme,
|
|
4794
|
+
presetSet: explicitPreset?.name
|
|
4795
|
+
});
|
|
4796
|
+
}
|
|
4797
|
+
function themeNames() {
|
|
4798
|
+
const presets = listThemePresets().map((p) => p.name.toLowerCase());
|
|
4799
|
+
const colors = listColorSchemes().map((s) => s.name.toLowerCase());
|
|
4800
|
+
return [...presets, ...colors.filter((c) => !presets.includes(c))];
|
|
4801
|
+
}
|
|
4802
|
+
function hasTheme(name) {
|
|
4803
|
+
if (!name) return false;
|
|
4804
|
+
return Boolean(getThemePreset(name) || getColorScheme(name));
|
|
2642
4805
|
}
|
|
2643
4806
|
|
|
2644
4807
|
// src/utils/output-namer.ts
|
|
@@ -2662,10 +4825,13 @@ function getOutputPath(inputPath, specifiedOutput) {
|
|
|
2662
4825
|
// src/index.ts
|
|
2663
4826
|
async function convert(inputPath, options = {}) {
|
|
2664
4827
|
const absInput = resolve3(inputPath);
|
|
2665
|
-
const markdown =
|
|
4828
|
+
const markdown = readFileSync4(absInput, "utf-8");
|
|
2666
4829
|
const presentation = parseMarkdown(markdown);
|
|
2667
|
-
|
|
2668
|
-
const
|
|
4830
|
+
const baseTheme = options.theme ?? presentation.config.theme;
|
|
4831
|
+
const colorScheme = options.colorScheme ?? presentation.config.colorScheme;
|
|
4832
|
+
const textScheme = options.textScheme ?? presentation.config.textScheme;
|
|
4833
|
+
const layoutScheme = options.layoutScheme ?? presentation.config.layoutScheme;
|
|
4834
|
+
const theme = getTheme(baseTheme, { colorScheme, textScheme, layoutScheme });
|
|
2669
4835
|
const outputPath = resolve3(getOutputPath(absInput, options.output));
|
|
2670
4836
|
await renderPresentation(presentation, theme, outputPath, absInput);
|
|
2671
4837
|
return outputPath;
|
|
@@ -2684,12 +4850,15 @@ async function expandGlob(patterns) {
|
|
|
2684
4850
|
|
|
2685
4851
|
// src/cli.ts
|
|
2686
4852
|
var pkg = JSON.parse(
|
|
2687
|
-
|
|
4853
|
+
readFileSync5(new URL("../package.json", import.meta.url), "utf-8")
|
|
2688
4854
|
);
|
|
2689
4855
|
var program = new Command();
|
|
2690
4856
|
program.name("mfly").description("Markdown to PowerPoint (PPTX)").version(pkg.version ?? "0.0.0");
|
|
2691
|
-
var themeChoices =
|
|
2692
|
-
|
|
4857
|
+
var themeChoices = themeNames();
|
|
4858
|
+
var colorChoices = listColorSchemes().map((s) => s.name).join(", ");
|
|
4859
|
+
var textChoices = listTextSchemes().map((s) => s.name).join(", ");
|
|
4860
|
+
var layoutChoices = listLayoutSchemes().map((s) => s.name).join(", ");
|
|
4861
|
+
program.argument("<files...>", "Markdown files to convert (supports glob)").option("-o, --output <path>", "Output file path (single file only)").option("-t, --theme <name>", `Theme name (${themeChoices.join(", ")})`).option("--color <name>", `Color scheme override (${colorChoices})`).option("--text <name>", `Text scheme override (${textChoices})`).option("--layout <name>", `Layout scheme override (${layoutChoices})`).option("--quiet", "Suppress per-file progress output").option("--json", "Print machine-readable JSON result to stdout").action(async (filePatterns, options) => {
|
|
2693
4862
|
const jsonMode = Boolean(options.json);
|
|
2694
4863
|
if (jsonMode || options.quiet) setQuiet(true);
|
|
2695
4864
|
const usageError = (msg) => {
|
|
@@ -2705,9 +4874,18 @@ program.argument("<files...>", "Markdown files to convert (supports glob)").opti
|
|
|
2705
4874
|
if (options.output && files.length > 1) {
|
|
2706
4875
|
usageError("--output can only be used with a single input file");
|
|
2707
4876
|
}
|
|
2708
|
-
if (options.theme && !
|
|
4877
|
+
if (options.theme && !hasTheme(options.theme)) {
|
|
2709
4878
|
usageError(`Unknown theme "${options.theme}". Available themes: ${themeChoices.join(", ")}`);
|
|
2710
4879
|
}
|
|
4880
|
+
if (options.color && !getColorScheme(options.color)) {
|
|
4881
|
+
usageError(`Unknown color scheme "${options.color}". Available: ${colorChoices}`);
|
|
4882
|
+
}
|
|
4883
|
+
if (options.text && !getTextScheme(options.text)) {
|
|
4884
|
+
usageError(`Unknown text scheme "${options.text}". Available: ${textChoices}`);
|
|
4885
|
+
}
|
|
4886
|
+
if (options.layout && !getLayoutScheme(options.layout)) {
|
|
4887
|
+
usageError(`Unknown layout scheme "${options.layout}". Available: ${layoutChoices}`);
|
|
4888
|
+
}
|
|
2711
4889
|
const startTime = Date.now();
|
|
2712
4890
|
const results = [];
|
|
2713
4891
|
for (const file of files) {
|
|
@@ -2717,7 +4895,10 @@ program.argument("<files...>", "Markdown files to convert (supports glob)").opti
|
|
|
2717
4895
|
try {
|
|
2718
4896
|
const outputPath = await convert(file, {
|
|
2719
4897
|
output: options.output,
|
|
2720
|
-
theme: options.theme
|
|
4898
|
+
theme: options.theme,
|
|
4899
|
+
colorScheme: options.color,
|
|
4900
|
+
textScheme: options.text,
|
|
4901
|
+
layoutScheme: options.layout
|
|
2721
4902
|
});
|
|
2722
4903
|
const outName = outputPath.split(/[\\/]/).pop() ?? outputPath;
|
|
2723
4904
|
progress.succeed(`${chalk2.cyan(fileName)} \u2192 ${chalk2.green(outName)}`);
|