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