printdown 1.1.1 → 1.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 +177 -0
- package/dist/{chunk-ZB6NLLOD.js → chunk-BLKOSAAA.js} +861 -59
- package/dist/chunk-BLKOSAAA.js.map +1 -0
- package/dist/{chunk-GEQQR3U7.js → chunk-QYKBYAYW.js} +221 -4
- package/dist/chunk-QYKBYAYW.js.map +1 -0
- package/dist/cli.js +24 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +89 -2
- package/dist/index.js +19 -5
- package/dist/themes/default/index.d.ts +8 -2
- package/dist/themes/default/index.js +5 -1
- package/dist/themes/default/style.css +211 -3
- package/package.json +3 -1
- package/dist/chunk-GEQQR3U7.js.map +0 -1
- package/dist/chunk-ZB6NLLOD.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultThemeCss,
|
|
3
3
|
getFontDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QYKBYAYW.js";
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import fs4 from "fs";
|
|
8
|
+
import path4 from "path";
|
|
9
9
|
|
|
10
10
|
// src/markdown/index.ts
|
|
11
11
|
import MarkdownIt from "markdown-it";
|
|
@@ -595,7 +595,7 @@ function centerPlugin(md) {
|
|
|
595
595
|
}
|
|
596
596
|
|
|
597
597
|
// src/markdown/plugins/heading.ts
|
|
598
|
-
var
|
|
598
|
+
var HEADING_ATTR_REGEX = /\s*(?:\{(?:\.|\s)*(?:no-border|no-underline|no-bottom-border|plain|-|page-break|pagebreak|break-before)(?:\s+(?:\.|\s)*(?:no-border|no-underline|no-bottom-border|plain|-|page-break|pagebreak|break-before))*\s*\}|\[(?:no-border|no-underline|page-break|pagebreak|break-before)\]|<!--\s*(?:no-border|no-underline|page-break|pagebreak|break-before)\s*-->)\s*$/i;
|
|
599
599
|
function addClass2(token, className) {
|
|
600
600
|
const classIndex = token.attrIndex("class");
|
|
601
601
|
if (classIndex < 0) {
|
|
@@ -615,28 +615,35 @@ function addClass2(token, className) {
|
|
|
615
615
|
}
|
|
616
616
|
}
|
|
617
617
|
function headingPlugin(md) {
|
|
618
|
-
md.core.ruler.after("inline", "
|
|
618
|
+
md.core.ruler.after("inline", "heading_attributes", (state) => {
|
|
619
619
|
const tokens = state.tokens;
|
|
620
620
|
for (let i = 0; i < tokens.length; i++) {
|
|
621
621
|
if (tokens[i].type === "heading_open") {
|
|
622
622
|
const inlineToken = tokens[i + 1];
|
|
623
623
|
if (inlineToken && inlineToken.type === "inline") {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
624
|
+
const match = inlineToken.content.match(HEADING_ATTR_REGEX);
|
|
625
|
+
if (match) {
|
|
626
|
+
const attrStr = match[0].toLowerCase();
|
|
627
|
+
if (attrStr.includes("no-border") || attrStr.includes("no-underline") || attrStr.includes("plain") || attrStr.includes("{-}")) {
|
|
628
|
+
addClass2(tokens[i], "printdown-no-border printdown-no-underline");
|
|
629
|
+
}
|
|
630
|
+
if (attrStr.includes("page-break") || attrStr.includes("pagebreak") || attrStr.includes("break-before")) {
|
|
631
|
+
addClass2(tokens[i], "printdown-page-break");
|
|
632
|
+
}
|
|
633
|
+
inlineToken.content = inlineToken.content.replace(HEADING_ATTR_REGEX, "");
|
|
627
634
|
if (inlineToken.children && inlineToken.children.length > 0) {
|
|
628
635
|
for (let j = inlineToken.children.length - 1; j >= 0; j--) {
|
|
629
636
|
const child = inlineToken.children[j];
|
|
630
637
|
if (child.type === "text") {
|
|
631
|
-
if (
|
|
632
|
-
child.content = child.content.replace(
|
|
638
|
+
if (HEADING_ATTR_REGEX.test(child.content)) {
|
|
639
|
+
child.content = child.content.replace(HEADING_ATTR_REGEX, "");
|
|
633
640
|
if (child.content === "") {
|
|
634
641
|
inlineToken.children.splice(j, 1);
|
|
635
642
|
}
|
|
636
643
|
break;
|
|
637
644
|
}
|
|
638
645
|
} else if (child.type === "html_inline") {
|
|
639
|
-
if (child.content.includes("no-border") || child.content.includes("no-underline")) {
|
|
646
|
+
if (child.content.includes("no-border") || child.content.includes("no-underline") || child.content.includes("page-break") || child.content.includes("pagebreak") || child.content.includes("break-before")) {
|
|
640
647
|
inlineToken.children.splice(j, 1);
|
|
641
648
|
break;
|
|
642
649
|
}
|
|
@@ -647,26 +654,441 @@ function headingPlugin(md) {
|
|
|
647
654
|
}
|
|
648
655
|
} else if (tokens[i].type === "html_block") {
|
|
649
656
|
tokens[i].content = tokens[i].content.replace(
|
|
650
|
-
/<(h[1-6])(\s+[^>]*)?(?:\s+(?:no-border|no-underline))\s*([^>]*)>/gi,
|
|
657
|
+
/<(h[1-6])(\s+[^>]*)?(?:\s+(?:no-border|no-underline|page-break|pagebreak|break-before))\s*([^>]*)>/gi,
|
|
651
658
|
(match, tag, before, after) => {
|
|
652
659
|
const combinedAttrs = `${before || ""} ${after || ""}`.trim();
|
|
660
|
+
const classesToAdd = [];
|
|
661
|
+
if (match.includes("no-border") || match.includes("no-underline")) {
|
|
662
|
+
classesToAdd.push("printdown-no-border printdown-no-underline");
|
|
663
|
+
}
|
|
664
|
+
if (match.includes("page-break") || match.includes("pagebreak") || match.includes("break-before")) {
|
|
665
|
+
classesToAdd.push("printdown-page-break");
|
|
666
|
+
}
|
|
667
|
+
const classString = classesToAdd.join(" ");
|
|
653
668
|
if (combinedAttrs.includes('class="') || combinedAttrs.includes("class='")) {
|
|
654
669
|
return `<${tag} ${combinedAttrs.replace(
|
|
655
670
|
/class=(["'])(.*?)\1/,
|
|
656
|
-
|
|
671
|
+
`class=$1$2 ${classString}$1`
|
|
657
672
|
)}>`;
|
|
658
673
|
}
|
|
659
|
-
return `<${tag} class="
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
674
|
+
return `<${tag} class="${classString}" ${combinedAttrs}>`.replace(/\s+>/, ">");
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/markdown/plugins/page-break.ts
|
|
683
|
+
var PAGE_BREAK_LINE_REGEX = /^(?:<!--\s*(?:pagebreak|page-break|newpage|break)\s*-->|\\(?:pagebreak|newpage)|\[(?:pagebreak|page-break|newpage)\]|<(?:pagebreak|page-break|newpage)(?:\s*\/?)>)$/i;
|
|
684
|
+
var PAGE_BREAK_MATCH_REGEX = /(?:<!--\s*(?:pagebreak|page-break|newpage|break)\s*-->|\\(?:pagebreak|newpage)|\[(?:pagebreak|page-break|newpage)\]|<(?:pagebreak|page-break|newpage)(?:\s*\/?)>)/gi;
|
|
685
|
+
function pageBreakPlugin(md) {
|
|
686
|
+
function blockPageBreakRule(state, startLine, endLine, silent) {
|
|
687
|
+
const startPos = state.bMarks[startLine] + state.tShift[startLine];
|
|
688
|
+
const maxPos = state.eMarks[startLine];
|
|
689
|
+
const lineText = state.src.slice(startPos, maxPos).trim();
|
|
690
|
+
if (PAGE_BREAK_LINE_REGEX.test(lineText)) {
|
|
691
|
+
if (silent) return true;
|
|
692
|
+
const token = state.push("html_block", "", 0);
|
|
693
|
+
token.content = '<div class="printdown-page-break"></div>\n';
|
|
694
|
+
token.map = [startLine, startLine + 1];
|
|
695
|
+
state.line = startLine + 1;
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
if (/^:::\s*(?:pagebreak|page-break|newpage)\s*$/i.test(lineText)) {
|
|
699
|
+
if (silent) return true;
|
|
700
|
+
let nextLine = startLine + 1;
|
|
701
|
+
while (nextLine < endLine) {
|
|
702
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
703
|
+
const max = state.eMarks[nextLine];
|
|
704
|
+
const curLine = state.src.slice(pos, max).trim();
|
|
705
|
+
if (/^:::\s*$/.test(curLine)) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
nextLine++;
|
|
709
|
+
}
|
|
710
|
+
const token = state.push("html_block", "", 0);
|
|
711
|
+
token.content = '<div class="printdown-page-break"></div>\n';
|
|
712
|
+
token.map = [startLine, nextLine + 1];
|
|
713
|
+
state.line = nextLine < endLine ? nextLine + 1 : nextLine;
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
md.block.ruler.before("hr", "page_break", blockPageBreakRule);
|
|
719
|
+
md.core.ruler.after("inline", "page_break_tags", (state) => {
|
|
720
|
+
const tokens = state.tokens;
|
|
721
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
722
|
+
const token = tokens[i];
|
|
723
|
+
if (token.type === "html_block") {
|
|
724
|
+
if (PAGE_BREAK_MATCH_REGEX.test(token.content)) {
|
|
725
|
+
token.content = token.content.replace(
|
|
726
|
+
PAGE_BREAK_MATCH_REGEX,
|
|
727
|
+
'<div class="printdown-page-break"></div>'
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
} else if (token.type === "paragraph_open") {
|
|
731
|
+
const inlineToken = tokens[i + 1];
|
|
732
|
+
if (inlineToken && inlineToken.type === "inline") {
|
|
733
|
+
const content = inlineToken.content.trim();
|
|
734
|
+
if (PAGE_BREAK_LINE_REGEX.test(content)) {
|
|
735
|
+
tokens[i].type = "html_block";
|
|
736
|
+
tokens[i].tag = "";
|
|
737
|
+
tokens[i].content = '<div class="printdown-page-break"></div>\n';
|
|
738
|
+
tokens[i].children = null;
|
|
739
|
+
tokens.splice(i + 1, 2);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
} else if (token.type === "inline" && token.children) {
|
|
743
|
+
for (let j = 0; j < token.children.length; j++) {
|
|
744
|
+
const child = token.children[j];
|
|
745
|
+
if (child.type === "html_inline" || child.type === "text") {
|
|
746
|
+
if (PAGE_BREAK_MATCH_REGEX.test(child.content)) {
|
|
747
|
+
child.type = "html_inline";
|
|
748
|
+
child.content = child.content.replace(
|
|
749
|
+
PAGE_BREAK_MATCH_REGEX,
|
|
750
|
+
'<div class="printdown-page-break"></div>'
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/markdown/plugins/cover.ts
|
|
761
|
+
var COVER_CONTAINER_START_REGEX = /^:::\s*cover(?:\s+([a-zA-Z0-9_-]+))?\s*$/i;
|
|
762
|
+
var COVER_TAG_OPEN_REGEX = /^<cover(?:\s+variant=["']?([a-zA-Z0-9_-]+)["']?)?(?:\s+class=["']?([^"'>]*)["']?)?[^>]*>$/i;
|
|
763
|
+
var COVER_TAG_CLOSE_REGEX = /^<\/cover>$/i;
|
|
764
|
+
function coverPlugin(md) {
|
|
765
|
+
function blockCoverRule(state, startLine, endLine, silent) {
|
|
766
|
+
const startPos = state.bMarks[startLine] + state.tShift[startLine];
|
|
767
|
+
const maxPos = state.eMarks[startLine];
|
|
768
|
+
const lineText = state.src.slice(startPos, maxPos).trim();
|
|
769
|
+
if (/^:::\s*(?:cover-meta|cover-footer)\s*$/i.test(lineText)) {
|
|
770
|
+
if (silent) return true;
|
|
771
|
+
let nextLine = startLine + 1;
|
|
772
|
+
let foundEnd = false;
|
|
773
|
+
while (nextLine < endLine) {
|
|
774
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
775
|
+
const max = state.eMarks[nextLine];
|
|
776
|
+
const curLine = state.src.slice(pos, max).trim();
|
|
777
|
+
if (/^:::\s*$/.test(curLine) || /^:::\s*(?:cover-meta|cover-footer)\s*$/i.test(curLine)) {
|
|
778
|
+
foundEnd = true;
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
nextLine++;
|
|
782
|
+
}
|
|
783
|
+
const tokenOpen = state.push("cover_meta_open", "div", 1);
|
|
784
|
+
tokenOpen.attrs = [["class", "printdown-cover-meta"]];
|
|
785
|
+
tokenOpen.block = true;
|
|
786
|
+
const oldParentType = state.parentType;
|
|
787
|
+
state.parentType = "root";
|
|
788
|
+
state.md.block.tokenize(state, startLine + 1, nextLine);
|
|
789
|
+
state.parentType = oldParentType;
|
|
790
|
+
const tokenClose = state.push("cover_meta_close", "div", -1);
|
|
791
|
+
tokenClose.block = true;
|
|
792
|
+
state.line = foundEnd ? nextLine + 1 : nextLine;
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
const coverMatch = lineText.match(COVER_CONTAINER_START_REGEX);
|
|
796
|
+
if (coverMatch) {
|
|
797
|
+
if (silent) return true;
|
|
798
|
+
const variant = coverMatch[1] ? coverMatch[1].toLowerCase() : "default";
|
|
799
|
+
let nextLine = startLine + 1;
|
|
800
|
+
let foundEnd = false;
|
|
801
|
+
let depth = 1;
|
|
802
|
+
while (nextLine < endLine) {
|
|
803
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
804
|
+
const max = state.eMarks[nextLine];
|
|
805
|
+
const curLine = state.src.slice(pos, max).trim();
|
|
806
|
+
if (/^:::\s*[a-zA-Z0-9_-]+\s*$/i.test(curLine)) {
|
|
807
|
+
depth++;
|
|
808
|
+
} else if (/^:::\s*$/i.test(curLine)) {
|
|
809
|
+
depth--;
|
|
810
|
+
if (depth === 0) {
|
|
811
|
+
foundEnd = true;
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
} else if (/^:::\s*cover\s*$/i.test(curLine)) {
|
|
815
|
+
depth--;
|
|
816
|
+
if (depth <= 0) {
|
|
817
|
+
foundEnd = true;
|
|
818
|
+
break;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
nextLine++;
|
|
822
|
+
}
|
|
823
|
+
const tokenOpen = state.push("cover_open", "div", 1);
|
|
824
|
+
const classNames = ["printdown-cover", `printdown-cover-${variant}`];
|
|
825
|
+
tokenOpen.attrs = [["class", classNames.join(" ")]];
|
|
826
|
+
tokenOpen.block = true;
|
|
827
|
+
const oldParentType = state.parentType;
|
|
828
|
+
state.parentType = "root";
|
|
829
|
+
state.md.block.tokenize(state, startLine + 1, nextLine);
|
|
830
|
+
state.parentType = oldParentType;
|
|
831
|
+
const tokenClose = state.push("cover_close", "div", -1);
|
|
832
|
+
tokenClose.block = true;
|
|
833
|
+
state.line = foundEnd ? nextLine + 1 : nextLine;
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
md.block.ruler.before("fence", "cover_block", blockCoverRule);
|
|
839
|
+
md.core.ruler.after("inline", "cover_tags", (state) => {
|
|
840
|
+
const tokens = state.tokens;
|
|
841
|
+
function processToken(token) {
|
|
842
|
+
if (token.type === "html_inline") {
|
|
843
|
+
const trimmed = token.content.trim();
|
|
844
|
+
const openMatch = trimmed.match(COVER_TAG_OPEN_REGEX);
|
|
845
|
+
if (openMatch) {
|
|
846
|
+
const variant = openMatch[1] ? openMatch[1].toLowerCase() : "default";
|
|
847
|
+
const extraClass = openMatch[2] ? ` ${openMatch[2]}` : "";
|
|
848
|
+
token.content = `<div class="printdown-cover printdown-cover-${variant}${extraClass}">`;
|
|
849
|
+
} else if (COVER_TAG_CLOSE_REGEX.test(trimmed)) {
|
|
850
|
+
token.content = "</div>";
|
|
851
|
+
}
|
|
852
|
+
} else if (token.type === "html_block") {
|
|
853
|
+
token.content = token.content.replace(
|
|
854
|
+
/<cover(?:\s+variant=["']?([a-zA-Z0-9_-]+)["']?)?(?:\s+class=["']?([^"'>]*)["']?)?[^>]*>([\s\S]*?)<\/cover>/gi,
|
|
855
|
+
(_, variant, customClass, body) => {
|
|
856
|
+
const v = variant ? variant.toLowerCase() : "default";
|
|
857
|
+
const extra = customClass ? ` ${customClass}` : "";
|
|
858
|
+
const rendered = md.render(body.trim()).trim();
|
|
859
|
+
return `<div class="printdown-cover printdown-cover-${v}${extra}">
|
|
860
|
+
${rendered}
|
|
861
|
+
</div>`;
|
|
663
862
|
}
|
|
664
863
|
);
|
|
665
864
|
}
|
|
666
865
|
}
|
|
866
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
867
|
+
processToken(tokens[i]);
|
|
868
|
+
if (tokens[i].children) {
|
|
869
|
+
for (const child of tokens[i].children) {
|
|
870
|
+
processToken(child);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
667
874
|
});
|
|
668
875
|
}
|
|
669
876
|
|
|
877
|
+
// src/markdown/plugins/math.ts
|
|
878
|
+
import katex from "katex";
|
|
879
|
+
function mathPlugin(md, options = {}) {
|
|
880
|
+
const defaultKatexOptions = {
|
|
881
|
+
throwOnError: false,
|
|
882
|
+
output: "htmlAndMathml",
|
|
883
|
+
...options.katexOptions
|
|
884
|
+
};
|
|
885
|
+
function inlineMathRule(state, silent) {
|
|
886
|
+
const start = state.pos;
|
|
887
|
+
const max = state.posMax;
|
|
888
|
+
const src = state.src;
|
|
889
|
+
if (start > 0 && src.charCodeAt(start - 1) === 92) {
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
const firstChar = src.charCodeAt(start);
|
|
893
|
+
if (firstChar === 92 && src.charCodeAt(start + 1) === 40) {
|
|
894
|
+
let matchEnd = -1;
|
|
895
|
+
let pos = start + 2;
|
|
896
|
+
while (pos < max - 1) {
|
|
897
|
+
if (src.charCodeAt(pos) === 92 && src.charCodeAt(pos + 1) === 41) {
|
|
898
|
+
matchEnd = pos;
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
pos++;
|
|
902
|
+
}
|
|
903
|
+
if (matchEnd === -1) return false;
|
|
904
|
+
if (!silent) {
|
|
905
|
+
const content = src.slice(start + 2, matchEnd).trim();
|
|
906
|
+
const token = state.push("math_inline", "math", 0);
|
|
907
|
+
token.content = content;
|
|
908
|
+
token.meta = { displayMode: false };
|
|
909
|
+
}
|
|
910
|
+
state.pos = matchEnd + 2;
|
|
911
|
+
return true;
|
|
912
|
+
}
|
|
913
|
+
if (firstChar === 36) {
|
|
914
|
+
const isDouble = src.charCodeAt(start + 1) === 36;
|
|
915
|
+
const delimiterLength = isDouble ? 2 : 1;
|
|
916
|
+
if (!isDouble) {
|
|
917
|
+
const nextChar = src.charCodeAt(start + 1);
|
|
918
|
+
if (nextChar === 32 || nextChar === 9 || nextChar === 10 || nextChar === 13 || isNaN(nextChar)) {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
let matchEnd = -1;
|
|
923
|
+
let pos = start + delimiterLength;
|
|
924
|
+
while (pos < max) {
|
|
925
|
+
if (src.charCodeAt(pos) === 92) {
|
|
926
|
+
pos += 2;
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
if (isDouble) {
|
|
930
|
+
if (src.charCodeAt(pos) === 36 && src.charCodeAt(pos + 1) === 36) {
|
|
931
|
+
matchEnd = pos;
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
} else {
|
|
935
|
+
if (src.charCodeAt(pos) === 36) {
|
|
936
|
+
const prevChar = src.charCodeAt(pos - 1);
|
|
937
|
+
if (prevChar !== 32 && prevChar !== 9 && prevChar !== 10 && prevChar !== 13) {
|
|
938
|
+
const nextChar = src.charCodeAt(pos + 1);
|
|
939
|
+
if (isNaN(nextChar) || nextChar < 48 || nextChar > 57) {
|
|
940
|
+
matchEnd = pos;
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
pos++;
|
|
947
|
+
}
|
|
948
|
+
if (matchEnd === -1) return false;
|
|
949
|
+
const content = src.slice(start + delimiterLength, matchEnd).trim();
|
|
950
|
+
if (content.length === 0) return false;
|
|
951
|
+
if (!silent) {
|
|
952
|
+
const token = state.push(isDouble ? "math_block" : "math_inline", "math", 0);
|
|
953
|
+
token.content = content;
|
|
954
|
+
token.meta = { displayMode: isDouble };
|
|
955
|
+
}
|
|
956
|
+
state.pos = matchEnd + delimiterLength;
|
|
957
|
+
return true;
|
|
958
|
+
}
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
function blockMathRule(state, startLine, endLine, silent) {
|
|
962
|
+
const startPos = state.bMarks[startLine] + state.tShift[startLine];
|
|
963
|
+
const maxPos = state.eMarks[startLine];
|
|
964
|
+
const lineText = state.src.slice(startPos, maxPos).trim();
|
|
965
|
+
const containerMatch = lineText.match(/^:::\s*(math|latex|tex|katex)\s*$/i);
|
|
966
|
+
if (containerMatch) {
|
|
967
|
+
if (silent) return true;
|
|
968
|
+
let nextLine = startLine + 1;
|
|
969
|
+
let foundEnd = false;
|
|
970
|
+
while (nextLine < endLine) {
|
|
971
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
972
|
+
const max = state.eMarks[nextLine];
|
|
973
|
+
const curLine = state.src.slice(pos, max).trim();
|
|
974
|
+
if (/^:::\s*$/.test(curLine) || /^:::\s*(math|latex|tex|katex)\s*$/i.test(curLine)) {
|
|
975
|
+
foundEnd = true;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
nextLine++;
|
|
979
|
+
}
|
|
980
|
+
const contentLines = [];
|
|
981
|
+
for (let l = startLine + 1; l < nextLine; l++) {
|
|
982
|
+
const pos = state.bMarks[l] + state.tShift[l];
|
|
983
|
+
const max = state.eMarks[l];
|
|
984
|
+
contentLines.push(state.src.slice(pos, max));
|
|
985
|
+
}
|
|
986
|
+
const token = state.push("math_block", "math", 0);
|
|
987
|
+
token.block = true;
|
|
988
|
+
token.content = contentLines.join("\n").trim();
|
|
989
|
+
token.meta = { displayMode: true };
|
|
990
|
+
state.line = foundEnd ? nextLine + 1 : nextLine;
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
if (lineText.startsWith("$$")) {
|
|
994
|
+
if (lineText.length > 2 && lineText.endsWith("$$")) {
|
|
995
|
+
if (silent) return true;
|
|
996
|
+
const mathContent = lineText.slice(2, -2).trim();
|
|
997
|
+
const token2 = state.push("math_block", "math", 0);
|
|
998
|
+
token2.block = true;
|
|
999
|
+
token2.content = mathContent;
|
|
1000
|
+
token2.meta = { displayMode: true };
|
|
1001
|
+
state.line = startLine + 1;
|
|
1002
|
+
return true;
|
|
1003
|
+
}
|
|
1004
|
+
if (silent) return true;
|
|
1005
|
+
const firstLineContent = lineText.slice(2).trim();
|
|
1006
|
+
const contentLines = [];
|
|
1007
|
+
if (firstLineContent) {
|
|
1008
|
+
contentLines.push(firstLineContent);
|
|
1009
|
+
}
|
|
1010
|
+
let nextLine = startLine + 1;
|
|
1011
|
+
let foundEnd = false;
|
|
1012
|
+
while (nextLine < endLine) {
|
|
1013
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
1014
|
+
const max = state.eMarks[nextLine];
|
|
1015
|
+
const curLine = state.src.slice(pos, max).trim();
|
|
1016
|
+
if (curLine.endsWith("$$")) {
|
|
1017
|
+
foundEnd = true;
|
|
1018
|
+
const lastLineContent = curLine.slice(0, -2).trim();
|
|
1019
|
+
if (lastLineContent) {
|
|
1020
|
+
contentLines.push(lastLineContent);
|
|
1021
|
+
}
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
contentLines.push(state.src.slice(pos, max));
|
|
1025
|
+
nextLine++;
|
|
1026
|
+
}
|
|
1027
|
+
const token = state.push("math_block", "math", 0);
|
|
1028
|
+
token.block = true;
|
|
1029
|
+
token.content = contentLines.join("\n").trim();
|
|
1030
|
+
token.meta = { displayMode: true };
|
|
1031
|
+
state.line = foundEnd ? nextLine + 1 : nextLine;
|
|
1032
|
+
return true;
|
|
1033
|
+
}
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
md.inline.ruler.before("escape", "math_inline", inlineMathRule);
|
|
1037
|
+
md.block.ruler.before("fence", "math_block", blockMathRule);
|
|
1038
|
+
const originalFence = md.renderer.rules.fence;
|
|
1039
|
+
md.renderer.rules.fence = (tokens, idx, opt, env, self) => {
|
|
1040
|
+
const token = tokens[idx];
|
|
1041
|
+
const info = token.info ? token.info.trim().toLowerCase() : "";
|
|
1042
|
+
if (info === "math" || info === "latex" || info === "tex" || info === "katex") {
|
|
1043
|
+
try {
|
|
1044
|
+
const rendered = katex.renderToString(token.content.trim(), {
|
|
1045
|
+
...defaultKatexOptions,
|
|
1046
|
+
displayMode: true
|
|
1047
|
+
});
|
|
1048
|
+
return `<div class="printdown-math-block katex-display">${rendered}</div>
|
|
1049
|
+
`;
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
1052
|
+
return `<div class="printdown-math-block katex-display"><span class="katex-error">${errMessage}</span></div>
|
|
1053
|
+
`;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (originalFence) {
|
|
1057
|
+
return originalFence(tokens, idx, opt, env, self);
|
|
1058
|
+
}
|
|
1059
|
+
return self.renderToken(tokens, idx, opt);
|
|
1060
|
+
};
|
|
1061
|
+
md.renderer.rules.math_inline = (tokens, idx) => {
|
|
1062
|
+
const token = tokens[idx];
|
|
1063
|
+
try {
|
|
1064
|
+
const rendered = katex.renderToString(token.content, {
|
|
1065
|
+
...defaultKatexOptions,
|
|
1066
|
+
displayMode: false
|
|
1067
|
+
});
|
|
1068
|
+
return `<span class="printdown-math-inline">${rendered}</span>`;
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
1071
|
+
return `<span class="printdown-math-inline katex-error">${errMessage}</span>`;
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
md.renderer.rules.math_block = (tokens, idx) => {
|
|
1075
|
+
const token = tokens[idx];
|
|
1076
|
+
const displayMode = token.meta?.displayMode !== false;
|
|
1077
|
+
try {
|
|
1078
|
+
const rendered = katex.renderToString(token.content, {
|
|
1079
|
+
...defaultKatexOptions,
|
|
1080
|
+
displayMode
|
|
1081
|
+
});
|
|
1082
|
+
return `<div class="printdown-math-block katex-display">${rendered}</div>
|
|
1083
|
+
`;
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
1086
|
+
return `<div class="printdown-math-block katex-display"><span class="katex-error">${errMessage}</span></div>
|
|
1087
|
+
`;
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
670
1092
|
// src/markdown/index.ts
|
|
671
1093
|
var DEFAULT_LANGS = [
|
|
672
1094
|
"javascript",
|
|
@@ -743,14 +1165,22 @@ async function createMarkdownRenderer(options = {}) {
|
|
|
743
1165
|
md.use(kbdPlugin);
|
|
744
1166
|
md.use(centerPlugin);
|
|
745
1167
|
md.use(headingPlugin);
|
|
1168
|
+
md.use(pageBreakPlugin);
|
|
1169
|
+
md.use(coverPlugin);
|
|
1170
|
+
md.use(mathPlugin, { katexOptions: options.katex });
|
|
746
1171
|
const highlighter = await getHighlighter(options.shiki);
|
|
747
1172
|
const theme = options.shiki?.theme || DEFAULT_THEME;
|
|
748
1173
|
const defaultFence = md.renderer.rules.fence;
|
|
749
1174
|
md.renderer.rules.fence = (tokens, idx, fenceOptions, env, self) => {
|
|
750
1175
|
const token = tokens[idx];
|
|
751
1176
|
const info = token.info ? token.info.trim() : "";
|
|
752
|
-
const lang = info ? info.split(/\s+/)[0] : "text";
|
|
1177
|
+
const lang = info ? info.split(/\s+/)[0].toLowerCase() : "text";
|
|
753
1178
|
const code = token.content;
|
|
1179
|
+
if (lang === "math" || lang === "latex" || lang === "tex" || lang === "katex") {
|
|
1180
|
+
if (defaultFence) {
|
|
1181
|
+
return defaultFence(tokens, idx, fenceOptions, env, self);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
754
1184
|
try {
|
|
755
1185
|
const loadedLangs = highlighter.getLoadedLanguages();
|
|
756
1186
|
const targetLang = loadedLangs.includes(lang) ? lang : "text";
|
|
@@ -781,11 +1211,79 @@ async function createMarkdownRenderer(options = {}) {
|
|
|
781
1211
|
import fs from "fs";
|
|
782
1212
|
import path from "path";
|
|
783
1213
|
import { chromium } from "playwright";
|
|
1214
|
+
|
|
1215
|
+
// src/renderer/header-footer.ts
|
|
1216
|
+
var DEFAULT_FONT_STYLE = 'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 8pt; color: #64748b; width: 100%; padding: 0 18mm; box-sizing: border-box; -webkit-print-color-adjust: exact;';
|
|
1217
|
+
function replacePlaceholdersWithHtml(str) {
|
|
1218
|
+
return str.replace(/\{page\}/gi, '<span class="pageNumber"></span>').replace(/\{(?:total|totalPages)\}/gi, '<span class="totalPages"></span>').replace(/\{date\}/gi, '<span class="date"></span>').replace(/\{title\}/gi, '<span class="title"></span>');
|
|
1219
|
+
}
|
|
1220
|
+
function buildHeaderFooterHtml(content, type) {
|
|
1221
|
+
const text = typeof content === "boolean" ? type === "header" ? "{title}" : "{page} / {total}" : content.trim();
|
|
1222
|
+
const parts = text.split("|").map((p) => replacePlaceholdersWithHtml(p.trim()));
|
|
1223
|
+
let innerHtml;
|
|
1224
|
+
if (parts.length === 1) {
|
|
1225
|
+
const isCentered = type === "footer" || text.includes("{page}") || text.includes("{total}") || text.includes('<span class="pageNumber">');
|
|
1226
|
+
innerHtml = `<div style="display: flex; justify-content: ${isCentered ? "center" : "space-between"}; width: 100%; align-items: center;"><span>${parts[0]}</span></div>`;
|
|
1227
|
+
} else if (parts.length === 2) {
|
|
1228
|
+
innerHtml = `<div style="display: flex; justify-content: space-between; width: 100%; align-items: center;"><span>${parts[0]}</span><span>${parts[1]}</span></div>`;
|
|
1229
|
+
} else {
|
|
1230
|
+
innerHtml = `<div style="display: flex; justify-content: space-between; width: 100%; align-items: center;"><span style="flex: 1; text-align: left;">${parts[0]}</span><span style="flex: 1; text-align: center;">${parts[1]}</span><span style="flex: 1; text-align: right;">${parts[2]}</span></div>`;
|
|
1231
|
+
}
|
|
1232
|
+
return `<div style="${DEFAULT_FONT_STYLE}">${innerHtml}</div>`;
|
|
1233
|
+
}
|
|
1234
|
+
function resolveHeaderFooterPdfOptions(options) {
|
|
1235
|
+
const hasHeader = Boolean(options.header || options.headerTemplate);
|
|
1236
|
+
const hasPageNumber = Boolean(options.pageNumber);
|
|
1237
|
+
const hasFooter = Boolean(options.footer || options.footerTemplate || hasPageNumber);
|
|
1238
|
+
const shouldDisplay = options.displayHeaderFooter ?? (hasHeader || hasFooter);
|
|
1239
|
+
if (!shouldDisplay) {
|
|
1240
|
+
return {};
|
|
1241
|
+
}
|
|
1242
|
+
let headerTemplate = options.headerTemplate;
|
|
1243
|
+
if (!headerTemplate && options.header) {
|
|
1244
|
+
headerTemplate = buildHeaderFooterHtml(options.header, "header");
|
|
1245
|
+
}
|
|
1246
|
+
if (!headerTemplate) {
|
|
1247
|
+
headerTemplate = '<span style="font-size: 0px;"></span>';
|
|
1248
|
+
}
|
|
1249
|
+
let footerTemplate = options.footerTemplate;
|
|
1250
|
+
if (!footerTemplate) {
|
|
1251
|
+
if (options.footer) {
|
|
1252
|
+
footerTemplate = buildHeaderFooterHtml(options.footer, "footer");
|
|
1253
|
+
} else if (options.pageNumber) {
|
|
1254
|
+
const pageFormat = typeof options.pageNumber === "string" ? options.pageNumber : "{page} / {total}";
|
|
1255
|
+
footerTemplate = buildHeaderFooterHtml(pageFormat, "footer");
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (!footerTemplate) {
|
|
1259
|
+
footerTemplate = '<span style="font-size: 0px;"></span>';
|
|
1260
|
+
}
|
|
1261
|
+
return {
|
|
1262
|
+
displayHeaderFooter: true,
|
|
1263
|
+
headerTemplate,
|
|
1264
|
+
footerTemplate,
|
|
1265
|
+
margin: {
|
|
1266
|
+
top: "20mm",
|
|
1267
|
+
bottom: "20mm",
|
|
1268
|
+
left: "18mm",
|
|
1269
|
+
right: "18mm"
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
function formatHeaderFooterCanvasText(content, pageIndex, totalPages, docTitle) {
|
|
1274
|
+
const text = typeof content === "boolean" ? "{page} / {total}" : content;
|
|
1275
|
+
const today = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
1276
|
+
const title = docTitle || "";
|
|
1277
|
+
return text.replace(/\{page\}/gi, String(pageIndex)).replace(/\{(?:total|totalPages)\}/gi, String(totalPages)).replace(/\{date\}/gi, today).replace(/\{title\}/gi, title);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/renderer/index.ts
|
|
784
1281
|
async function setupFontRouting(context) {
|
|
785
1282
|
const interDir = getFontDir("@fontsource-variable/inter/index.css");
|
|
786
1283
|
const notoDir = getFontDir("@fontsource-variable/noto-sans-jp/index.css");
|
|
787
1284
|
const jbDir = getFontDir("@fontsource-variable/jetbrains-mono/index.css");
|
|
788
1285
|
const genDir = getFontDir("gen-interface-jp/500.css");
|
|
1286
|
+
const katexDir = getFontDir("katex/dist/katex.min.css");
|
|
789
1287
|
await context.route("https://printdown.local/fonts/**", async (route) => {
|
|
790
1288
|
const reqUrl = route.request().url();
|
|
791
1289
|
let filePath = "";
|
|
@@ -813,13 +1311,20 @@ async function setupFontRouting(context) {
|
|
|
813
1311
|
"w",
|
|
814
1312
|
reqUrl.replace("https://printdown.local/fonts/gen-interface-jp/w/", "")
|
|
815
1313
|
);
|
|
1314
|
+
} else if (katexDir && reqUrl.startsWith("https://printdown.local/fonts/katex/")) {
|
|
1315
|
+
filePath = path.join(
|
|
1316
|
+
katexDir,
|
|
1317
|
+
"fonts",
|
|
1318
|
+
reqUrl.replace("https://printdown.local/fonts/katex/", "")
|
|
1319
|
+
);
|
|
816
1320
|
}
|
|
817
1321
|
if (filePath && fs.existsSync(filePath)) {
|
|
818
1322
|
try {
|
|
819
1323
|
const body = fs.readFileSync(filePath);
|
|
1324
|
+
const contentType = filePath.endsWith(".woff2") ? "font/woff2" : filePath.endsWith(".woff") ? "font/woff" : filePath.endsWith(".ttf") ? "font/ttf" : "font/woff2";
|
|
820
1325
|
await route.fulfill({
|
|
821
1326
|
status: 200,
|
|
822
|
-
contentType
|
|
1327
|
+
contentType,
|
|
823
1328
|
body
|
|
824
1329
|
});
|
|
825
1330
|
return;
|
|
@@ -906,9 +1411,11 @@ var BrowserRenderer = class {
|
|
|
906
1411
|
);
|
|
907
1412
|
try {
|
|
908
1413
|
if (format === "pdf") {
|
|
1414
|
+
const headerFooterOptions = resolveHeaderFooterPdfOptions(options);
|
|
909
1415
|
const pdfBuffer = await page.pdf({
|
|
910
1416
|
printBackground: true,
|
|
911
1417
|
preferCSSPageSize: true,
|
|
1418
|
+
...headerFooterOptions,
|
|
912
1419
|
...options.pdf
|
|
913
1420
|
});
|
|
914
1421
|
return Buffer.from(pdfBuffer);
|
|
@@ -962,8 +1469,14 @@ var BrowserRenderer = class {
|
|
|
962
1469
|
const deviceScaleFactor = options.scale ?? options.deviceScaleFactor ?? 2;
|
|
963
1470
|
const pagesHtml = html.includes("</head>") ? html.replace(
|
|
964
1471
|
"</head>",
|
|
965
|
-
|
|
966
|
-
|
|
1472
|
+
`<style>
|
|
1473
|
+
.printdown { padding-top: 0 !important; padding-bottom: 0 !important; }
|
|
1474
|
+
.printdown .printdown-cover, .printdown cover { min-height: ${contentHeightPerPage}px !important; height: ${contentHeightPerPage}px !important; margin: 0 !important; }
|
|
1475
|
+
</style></head>`
|
|
1476
|
+
) : html + `<style>
|
|
1477
|
+
.printdown { padding-top: 0 !important; padding-bottom: 0 !important; }
|
|
1478
|
+
.printdown .printdown-cover, .printdown cover { min-height: ${contentHeightPerPage}px !important; height: ${contentHeightPerPage}px !important; margin: 0 !important; }
|
|
1479
|
+
</style>`;
|
|
967
1480
|
const { browser, context, page, isLocalBrowser } = await this.preparePage(
|
|
968
1481
|
pagesHtml,
|
|
969
1482
|
width,
|
|
@@ -972,10 +1485,112 @@ var BrowserRenderer = class {
|
|
|
972
1485
|
);
|
|
973
1486
|
let composePage = null;
|
|
974
1487
|
try {
|
|
1488
|
+
const pageSlices = await page.evaluate((maxPageH) => {
|
|
1489
|
+
const totalH = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
|
|
1490
|
+
const selectors = [
|
|
1491
|
+
"h1",
|
|
1492
|
+
"h2",
|
|
1493
|
+
"h3",
|
|
1494
|
+
"h4",
|
|
1495
|
+
"h5",
|
|
1496
|
+
"h6",
|
|
1497
|
+
"p",
|
|
1498
|
+
"ul > li",
|
|
1499
|
+
"ol > li",
|
|
1500
|
+
".printdown-task-list-item",
|
|
1501
|
+
"blockquote",
|
|
1502
|
+
"table",
|
|
1503
|
+
"tr",
|
|
1504
|
+
"pre",
|
|
1505
|
+
".printdown-cover",
|
|
1506
|
+
"cover",
|
|
1507
|
+
".printdown-page-break",
|
|
1508
|
+
".page-break",
|
|
1509
|
+
".break-before",
|
|
1510
|
+
".break-after"
|
|
1511
|
+
];
|
|
1512
|
+
const elements = Array.from(document.querySelectorAll(selectors.join(",")));
|
|
1513
|
+
const items = [];
|
|
1514
|
+
for (const el of elements) {
|
|
1515
|
+
const rect = el.getBoundingClientRect();
|
|
1516
|
+
const top = rect.top + window.scrollY;
|
|
1517
|
+
const bottom = rect.bottom + window.scrollY;
|
|
1518
|
+
const isForcedBreak = el.classList.contains("printdown-page-break") || el.classList.contains("page-break") || el.classList.contains("break-before") || window.getComputedStyle(el).breakBefore === "page" || window.getComputedStyle(el).pageBreakBefore === "always";
|
|
1519
|
+
const isForcedAfterBreak = el.classList.contains("printdown-cover") || el.tagName.toLowerCase() === "cover" || el.classList.contains("break-after") || el.classList.contains("printdown-break-after") || window.getComputedStyle(el).breakAfter === "page" || window.getComputedStyle(el).pageBreakAfter === "always";
|
|
1520
|
+
if (rect.height > 0 || isForcedBreak || isForcedAfterBreak) {
|
|
1521
|
+
items.push({ top, bottom, isForcedBreak, isForcedAfterBreak });
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
items.sort((a, b) => a.top - b.top);
|
|
1525
|
+
const headings = Array.from(
|
|
1526
|
+
document.querySelectorAll("h1, h2, h3, h4, h5, h6")
|
|
1527
|
+
).map((h) => {
|
|
1528
|
+
const rect = h.getBoundingClientRect();
|
|
1529
|
+
return {
|
|
1530
|
+
top: rect.top + window.scrollY,
|
|
1531
|
+
bottom: rect.bottom + window.scrollY
|
|
1532
|
+
};
|
|
1533
|
+
});
|
|
1534
|
+
const slices = [];
|
|
1535
|
+
let currentY = 0;
|
|
1536
|
+
while (currentY < totalH - 5) {
|
|
1537
|
+
const idealBottom = currentY + maxPageH;
|
|
1538
|
+
if (idealBottom >= totalH) {
|
|
1539
|
+
slices.push({ y: currentY, h: totalH - currentY });
|
|
1540
|
+
break;
|
|
1541
|
+
}
|
|
1542
|
+
const forcedAfterBreak = items.find(
|
|
1543
|
+
(item) => item.isForcedAfterBreak && item.bottom > currentY && item.top >= currentY - 5
|
|
1544
|
+
);
|
|
1545
|
+
if (forcedAfterBreak && forcedAfterBreak.bottom > currentY) {
|
|
1546
|
+
slices.push({ y: currentY, h: Math.max(20, forcedAfterBreak.bottom - currentY) });
|
|
1547
|
+
currentY = forcedAfterBreak.bottom;
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
const forcedBreak = items.find(
|
|
1551
|
+
(item) => item.isForcedBreak && item.top > currentY + 10 && item.top <= idealBottom
|
|
1552
|
+
);
|
|
1553
|
+
if (forcedBreak) {
|
|
1554
|
+
slices.push({ y: currentY, h: Math.max(20, forcedBreak.top - currentY) });
|
|
1555
|
+
currentY = forcedBreak.top;
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
let bestBreakY = idealBottom;
|
|
1559
|
+
let foundGoodBreak = false;
|
|
1560
|
+
const orphanHeading = headings.find(
|
|
1561
|
+
(h) => h.top > currentY + 50 && h.top <= idealBottom && idealBottom - h.top < 80
|
|
1562
|
+
);
|
|
1563
|
+
if (orphanHeading) {
|
|
1564
|
+
bestBreakY = orphanHeading.top;
|
|
1565
|
+
foundGoodBreak = true;
|
|
1566
|
+
} else {
|
|
1567
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
1568
|
+
const item = items[i];
|
|
1569
|
+
if (item.top > currentY + 40 && item.top <= idealBottom) {
|
|
1570
|
+
if (item.bottom > idealBottom) {
|
|
1571
|
+
bestBreakY = item.top;
|
|
1572
|
+
foundGoodBreak = true;
|
|
1573
|
+
break;
|
|
1574
|
+
} else if (idealBottom - item.bottom <= 50) {
|
|
1575
|
+
bestBreakY = item.bottom;
|
|
1576
|
+
foundGoodBreak = true;
|
|
1577
|
+
break;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
if (!foundGoodBreak || bestBreakY <= currentY + 50) {
|
|
1583
|
+
bestBreakY = idealBottom;
|
|
1584
|
+
}
|
|
1585
|
+
slices.push({ y: currentY, h: bestBreakY - currentY });
|
|
1586
|
+
currentY = bestBreakY;
|
|
1587
|
+
}
|
|
1588
|
+
return slices.length > 0 ? slices : [{ y: 0, h: totalH }];
|
|
1589
|
+
}, contentHeightPerPage);
|
|
975
1590
|
const totalContentHeight = await page.evaluate(
|
|
976
1591
|
() => Math.max(document.documentElement.scrollHeight, document.body.scrollHeight)
|
|
977
1592
|
);
|
|
978
|
-
const pageCount =
|
|
1593
|
+
const pageCount = pageSlices.length;
|
|
979
1594
|
await page.setViewportSize({
|
|
980
1595
|
width,
|
|
981
1596
|
height: Math.max(totalContentHeight + 100, pageHeight)
|
|
@@ -1003,10 +1618,35 @@ var BrowserRenderer = class {
|
|
|
1003
1618
|
const pageBuffers = [];
|
|
1004
1619
|
const imageType = format === "jpeg" ? "jpeg" : format === "webp" ? "webp" : "png";
|
|
1005
1620
|
for (let i = 0; i < pageCount; i++) {
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1621
|
+
const slice = pageSlices[i];
|
|
1622
|
+
const srcY = slice.y;
|
|
1623
|
+
const srcH = slice.h;
|
|
1624
|
+
const headerText = options.header ? formatHeaderFooterCanvasText(
|
|
1625
|
+
options.header === true ? options.title || "" : options.header,
|
|
1626
|
+
i + 1,
|
|
1627
|
+
pageCount,
|
|
1628
|
+
options.title
|
|
1629
|
+
) : "";
|
|
1630
|
+
const footerContent = options.footer || options.pageNumber;
|
|
1631
|
+
const footerText = footerContent ? formatHeaderFooterCanvasText(
|
|
1632
|
+
typeof footerContent === "boolean" ? "{page} / {total}" : footerContent,
|
|
1633
|
+
i + 1,
|
|
1634
|
+
pageCount,
|
|
1635
|
+
options.title
|
|
1636
|
+
) : "";
|
|
1008
1637
|
await composePage.evaluate(
|
|
1009
|
-
async ({
|
|
1638
|
+
async ({
|
|
1639
|
+
base64,
|
|
1640
|
+
width: width2,
|
|
1641
|
+
pageHeight: pageHeight2,
|
|
1642
|
+
marginTop: marginTop2,
|
|
1643
|
+
marginBottom: marginBottom2,
|
|
1644
|
+
srcY: srcY2,
|
|
1645
|
+
srcH: srcH2,
|
|
1646
|
+
scale,
|
|
1647
|
+
headerText: headerText2,
|
|
1648
|
+
footerText: footerText2
|
|
1649
|
+
}) => {
|
|
1010
1650
|
const canvas = document.getElementById("canvas");
|
|
1011
1651
|
const ctx = canvas.getContext("2d");
|
|
1012
1652
|
if (!ctx) return;
|
|
@@ -1028,14 +1668,66 @@ var BrowserRenderer = class {
|
|
|
1028
1668
|
width2 * scale,
|
|
1029
1669
|
srcH2 * scale
|
|
1030
1670
|
);
|
|
1671
|
+
if (headerText2) {
|
|
1672
|
+
ctx.font = `${9 * scale}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
1673
|
+
ctx.fillStyle = "#64748b";
|
|
1674
|
+
ctx.textBaseline = "middle";
|
|
1675
|
+
const parts = headerText2.split("|").map((s) => s.trim());
|
|
1676
|
+
const paddingX = 18 / 210 * width2 * scale;
|
|
1677
|
+
const y = marginTop2 * scale / 2;
|
|
1678
|
+
if (parts.length === 1) {
|
|
1679
|
+
ctx.textAlign = "center";
|
|
1680
|
+
ctx.fillText(parts[0], width2 * scale / 2, y);
|
|
1681
|
+
} else if (parts.length === 2) {
|
|
1682
|
+
ctx.textAlign = "left";
|
|
1683
|
+
ctx.fillText(parts[0], paddingX, y);
|
|
1684
|
+
ctx.textAlign = "right";
|
|
1685
|
+
ctx.fillText(parts[1], width2 * scale - paddingX, y);
|
|
1686
|
+
} else {
|
|
1687
|
+
ctx.textAlign = "left";
|
|
1688
|
+
ctx.fillText(parts[0], paddingX, y);
|
|
1689
|
+
ctx.textAlign = "center";
|
|
1690
|
+
ctx.fillText(parts[1], width2 * scale / 2, y);
|
|
1691
|
+
ctx.textAlign = "right";
|
|
1692
|
+
ctx.fillText(parts[2], width2 * scale - paddingX, y);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
if (footerText2) {
|
|
1696
|
+
ctx.font = `${9 * scale}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
|
|
1697
|
+
ctx.fillStyle = "#64748b";
|
|
1698
|
+
ctx.textBaseline = "middle";
|
|
1699
|
+
const parts = footerText2.split("|").map((s) => s.trim());
|
|
1700
|
+
const paddingX = 18 / 210 * width2 * scale;
|
|
1701
|
+
const y = (pageHeight2 - marginBottom2 / 2) * scale;
|
|
1702
|
+
if (parts.length === 1) {
|
|
1703
|
+
ctx.textAlign = "center";
|
|
1704
|
+
ctx.fillText(parts[0], width2 * scale / 2, y);
|
|
1705
|
+
} else if (parts.length === 2) {
|
|
1706
|
+
ctx.textAlign = "left";
|
|
1707
|
+
ctx.fillText(parts[0], paddingX, y);
|
|
1708
|
+
ctx.textAlign = "right";
|
|
1709
|
+
ctx.fillText(parts[1], width2 * scale - paddingX, y);
|
|
1710
|
+
} else {
|
|
1711
|
+
ctx.textAlign = "left";
|
|
1712
|
+
ctx.fillText(parts[0], paddingX, y);
|
|
1713
|
+
ctx.textAlign = "center";
|
|
1714
|
+
ctx.fillText(parts[1], width2 * scale / 2, y);
|
|
1715
|
+
ctx.textAlign = "right";
|
|
1716
|
+
ctx.fillText(parts[2], width2 * scale - paddingX, y);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1031
1719
|
},
|
|
1032
1720
|
{
|
|
1033
1721
|
base64: base64Img,
|
|
1034
1722
|
width,
|
|
1723
|
+
pageHeight,
|
|
1035
1724
|
marginTop,
|
|
1725
|
+
marginBottom,
|
|
1036
1726
|
srcY,
|
|
1037
1727
|
srcH,
|
|
1038
|
-
scale: deviceScaleFactor
|
|
1728
|
+
scale: deviceScaleFactor,
|
|
1729
|
+
headerText,
|
|
1730
|
+
footerText
|
|
1039
1731
|
}
|
|
1040
1732
|
);
|
|
1041
1733
|
const screenshotBuffer = await composePage.screenshot({
|
|
@@ -1072,8 +1764,106 @@ async function renderHtmlToPageBuffers(html, format, options = {}) {
|
|
|
1072
1764
|
}
|
|
1073
1765
|
|
|
1074
1766
|
// src/renderer/html.ts
|
|
1767
|
+
import fs3 from "fs";
|
|
1768
|
+
import path3 from "path";
|
|
1769
|
+
|
|
1770
|
+
// src/renderer/images.ts
|
|
1075
1771
|
import fs2 from "fs";
|
|
1076
1772
|
import path2 from "path";
|
|
1773
|
+
import url from "url";
|
|
1774
|
+
var MIME_TYPES = {
|
|
1775
|
+
".png": "image/png",
|
|
1776
|
+
".jpg": "image/jpeg",
|
|
1777
|
+
".jpeg": "image/jpeg",
|
|
1778
|
+
".webp": "image/webp",
|
|
1779
|
+
".gif": "image/gif",
|
|
1780
|
+
".svg": "image/svg+xml",
|
|
1781
|
+
".ico": "image/x-icon",
|
|
1782
|
+
".avif": "image/avif",
|
|
1783
|
+
".bmp": "image/bmp",
|
|
1784
|
+
".tiff": "image/tiff",
|
|
1785
|
+
".tif": "image/tiff"
|
|
1786
|
+
};
|
|
1787
|
+
function resolveLocalImageToDataUri(src, baseUrl) {
|
|
1788
|
+
if (!src || typeof src !== "string") return null;
|
|
1789
|
+
const trimmed = src.trim();
|
|
1790
|
+
if (trimmed.startsWith("data:") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("//") || trimmed.startsWith("blob:")) {
|
|
1791
|
+
return null;
|
|
1792
|
+
}
|
|
1793
|
+
let rawPath = trimmed;
|
|
1794
|
+
if (rawPath.startsWith("file://")) {
|
|
1795
|
+
try {
|
|
1796
|
+
rawPath = url.fileURLToPath(rawPath);
|
|
1797
|
+
} catch {
|
|
1798
|
+
rawPath = rawPath.replace(/^file:\/\//, "");
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
const [cleanPath] = rawPath.split(/[?#]/);
|
|
1802
|
+
let decodedPath;
|
|
1803
|
+
try {
|
|
1804
|
+
decodedPath = decodeURIComponent(cleanPath);
|
|
1805
|
+
} catch {
|
|
1806
|
+
decodedPath = cleanPath;
|
|
1807
|
+
}
|
|
1808
|
+
const candidates = [];
|
|
1809
|
+
if (path2.isAbsolute(decodedPath)) {
|
|
1810
|
+
candidates.push(decodedPath);
|
|
1811
|
+
} else {
|
|
1812
|
+
if (baseUrl) {
|
|
1813
|
+
const baseDir = fs2.existsSync(baseUrl) && fs2.statSync(baseUrl).isFile() ? path2.dirname(baseUrl) : baseUrl;
|
|
1814
|
+
candidates.push(path2.resolve(baseDir, decodedPath));
|
|
1815
|
+
}
|
|
1816
|
+
candidates.push(path2.resolve(process.cwd(), decodedPath));
|
|
1817
|
+
}
|
|
1818
|
+
for (const candidate of candidates) {
|
|
1819
|
+
try {
|
|
1820
|
+
if (fs2.existsSync(candidate) && fs2.statSync(candidate).isFile()) {
|
|
1821
|
+
const fileBuffer = fs2.readFileSync(candidate);
|
|
1822
|
+
const ext = path2.extname(candidate).toLowerCase();
|
|
1823
|
+
const mimeType = MIME_TYPES[ext] || "image/png";
|
|
1824
|
+
return `data:${mimeType};base64,${fileBuffer.toString("base64")}`;
|
|
1825
|
+
}
|
|
1826
|
+
} catch {
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
return null;
|
|
1830
|
+
}
|
|
1831
|
+
function inlineLocalImagesInHtml(html, baseUrl) {
|
|
1832
|
+
if (!html) return html;
|
|
1833
|
+
let result = html.replace(
|
|
1834
|
+
/(<img\b[^>]*?\bsrc\s*=\s*)(["']?)([^"'\s>]+)\2([^>]*>)/gi,
|
|
1835
|
+
(match, prefix, _quote, src, suffix) => {
|
|
1836
|
+
const dataUri = resolveLocalImageToDataUri(src, baseUrl);
|
|
1837
|
+
if (dataUri) {
|
|
1838
|
+
return `${prefix}"${dataUri}"${suffix}`;
|
|
1839
|
+
}
|
|
1840
|
+
return match;
|
|
1841
|
+
}
|
|
1842
|
+
);
|
|
1843
|
+
result = result.replace(
|
|
1844
|
+
/(<image\b[^>]*?\b(?:href|xlink:href)\s*=\s*)(["']?)([^"'\s>]+)\2([^>]*>)/gi,
|
|
1845
|
+
(match, prefix, _quote, src, suffix) => {
|
|
1846
|
+
const dataUri = resolveLocalImageToDataUri(src, baseUrl);
|
|
1847
|
+
if (dataUri) {
|
|
1848
|
+
return `${prefix}"${dataUri}"${suffix}`;
|
|
1849
|
+
}
|
|
1850
|
+
return match;
|
|
1851
|
+
}
|
|
1852
|
+
);
|
|
1853
|
+
result = result.replace(/url\(\s*(["']?)([^"')]+)\1\s*\)/gi, (match, _quote, urlStr) => {
|
|
1854
|
+
if (urlStr.startsWith("https://printdown.local/fonts/") || urlStr.startsWith("data:") || urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
|
|
1855
|
+
return match;
|
|
1856
|
+
}
|
|
1857
|
+
const dataUri = resolveLocalImageToDataUri(urlStr, baseUrl);
|
|
1858
|
+
if (dataUri) {
|
|
1859
|
+
return `url("${dataUri}")`;
|
|
1860
|
+
}
|
|
1861
|
+
return match;
|
|
1862
|
+
});
|
|
1863
|
+
return result;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// src/renderer/html.ts
|
|
1077
1867
|
function resolveCss(cssInput, baseUrl) {
|
|
1078
1868
|
const inputs = Array.isArray(cssInput) ? cssInput : [cssInput];
|
|
1079
1869
|
const resolvedCssParts = [];
|
|
@@ -1081,22 +1871,22 @@ function resolveCss(cssInput, baseUrl) {
|
|
|
1081
1871
|
if (!input || typeof input !== "string") continue;
|
|
1082
1872
|
let isFile = false;
|
|
1083
1873
|
let filePath = input;
|
|
1084
|
-
if (baseUrl && !
|
|
1085
|
-
const candidate =
|
|
1086
|
-
if (
|
|
1874
|
+
if (baseUrl && !path3.isAbsolute(input)) {
|
|
1875
|
+
const candidate = path3.resolve(baseUrl, input);
|
|
1876
|
+
if (fs3.existsSync(candidate) && fs3.statSync(candidate).isFile()) {
|
|
1087
1877
|
isFile = true;
|
|
1088
1878
|
filePath = candidate;
|
|
1089
1879
|
}
|
|
1090
1880
|
}
|
|
1091
|
-
if (!isFile && (
|
|
1092
|
-
if (
|
|
1881
|
+
if (!isFile && (fs3.existsSync(input) || input.endsWith(".css"))) {
|
|
1882
|
+
if (fs3.existsSync(input) && fs3.statSync(input).isFile()) {
|
|
1093
1883
|
isFile = true;
|
|
1094
|
-
filePath =
|
|
1884
|
+
filePath = path3.resolve(input);
|
|
1095
1885
|
}
|
|
1096
1886
|
}
|
|
1097
1887
|
if (isFile) {
|
|
1098
1888
|
try {
|
|
1099
|
-
const fileContent =
|
|
1889
|
+
const fileContent = fs3.readFileSync(filePath, "utf-8");
|
|
1100
1890
|
resolvedCssParts.push(fileContent);
|
|
1101
1891
|
} catch (err) {
|
|
1102
1892
|
console.warn(`[printdown] Failed to read CSS file "${filePath}":`, err);
|
|
@@ -1120,7 +1910,9 @@ function buildHtmlDocument(bodyHtml, options = {}) {
|
|
|
1120
1910
|
themeCss = defaultThemeCss;
|
|
1121
1911
|
}
|
|
1122
1912
|
}
|
|
1123
|
-
const
|
|
1913
|
+
const userCssRaw = options.css ? resolveCss(options.css, options.baseUrl) : "";
|
|
1914
|
+
const userCss = userCssRaw ? inlineLocalImagesInHtml(userCssRaw, options.baseUrl) : "";
|
|
1915
|
+
const processedBodyHtml = inlineLocalImagesInHtml(bodyHtml, options.baseUrl);
|
|
1124
1916
|
const baseTag = options.baseUrl ? `<base href="${options.baseUrl.endsWith("/") ? options.baseUrl : options.baseUrl + "/"}">` : "";
|
|
1125
1917
|
return `<!DOCTYPE html>
|
|
1126
1918
|
<html lang="ja">
|
|
@@ -1134,7 +1926,7 @@ function buildHtmlDocument(bodyHtml, options = {}) {
|
|
|
1134
1926
|
</head>
|
|
1135
1927
|
<body>
|
|
1136
1928
|
<article class="printdown">
|
|
1137
|
-
${
|
|
1929
|
+
${processedBodyHtml}
|
|
1138
1930
|
</article>
|
|
1139
1931
|
</body>
|
|
1140
1932
|
</html>`;
|
|
@@ -1146,7 +1938,7 @@ function inferFormat(output, explicitFormat) {
|
|
|
1146
1938
|
return explicitFormat;
|
|
1147
1939
|
}
|
|
1148
1940
|
if (output) {
|
|
1149
|
-
const ext =
|
|
1941
|
+
const ext = path4.extname(output).toLowerCase();
|
|
1150
1942
|
if (ext === ".png") return "png";
|
|
1151
1943
|
if (ext === ".jpg" || ext === ".jpeg") return "jpeg";
|
|
1152
1944
|
if (ext === ".webp") return "webp";
|
|
@@ -1160,14 +1952,18 @@ function resolvePageOutputFilePath(outputPath, pageIndex, totalPages) {
|
|
|
1160
1952
|
return outputPath.replace(/%d/g, String(pageNumber));
|
|
1161
1953
|
}
|
|
1162
1954
|
if (totalPages === 1 && !outputPath.includes("-1")) {
|
|
1163
|
-
const parsed2 =
|
|
1164
|
-
return
|
|
1955
|
+
const parsed2 = path4.parse(outputPath);
|
|
1956
|
+
return path4.join(parsed2.dir, `${parsed2.name}-${pageNumber}${parsed2.ext}`);
|
|
1165
1957
|
}
|
|
1166
|
-
const parsed =
|
|
1167
|
-
return
|
|
1958
|
+
const parsed = path4.parse(outputPath);
|
|
1959
|
+
return path4.join(parsed.dir, `${parsed.name}-${pageNumber}${parsed.ext}`);
|
|
1168
1960
|
}
|
|
1169
1961
|
async function renderToHtml(markdown, options = {}) {
|
|
1170
|
-
const
|
|
1962
|
+
const markdownOptions = {
|
|
1963
|
+
...options.markdown,
|
|
1964
|
+
...options.katex ? { katex: options.katex } : {}
|
|
1965
|
+
};
|
|
1966
|
+
const md = await createMarkdownRenderer(markdownOptions);
|
|
1171
1967
|
const bodyHtml = md.render(markdown);
|
|
1172
1968
|
return buildHtmlDocument(bodyHtml, options);
|
|
1173
1969
|
}
|
|
@@ -1176,27 +1972,27 @@ async function renderPages(markdown, options = {}) {
|
|
|
1176
1972
|
const html = await renderToHtml(markdown, options);
|
|
1177
1973
|
const buffers = await renderHtmlToPageBuffers(html, format, options);
|
|
1178
1974
|
if (options.output) {
|
|
1179
|
-
const resolvedPath =
|
|
1180
|
-
const outputDir =
|
|
1181
|
-
if (!
|
|
1182
|
-
|
|
1975
|
+
const resolvedPath = path4.resolve(options.output);
|
|
1976
|
+
const outputDir = path4.dirname(resolvedPath);
|
|
1977
|
+
if (!fs4.existsSync(outputDir)) {
|
|
1978
|
+
fs4.mkdirSync(outputDir, { recursive: true });
|
|
1183
1979
|
}
|
|
1184
1980
|
if (format === "pdf" && buffers.length === 1) {
|
|
1185
|
-
|
|
1981
|
+
fs4.writeFileSync(resolvedPath, buffers[0]);
|
|
1186
1982
|
} else {
|
|
1187
1983
|
buffers.forEach((buf, idx) => {
|
|
1188
1984
|
const filePath = resolvePageOutputFilePath(resolvedPath, idx, buffers.length);
|
|
1189
|
-
|
|
1985
|
+
fs4.writeFileSync(filePath, buf);
|
|
1190
1986
|
});
|
|
1191
1987
|
}
|
|
1192
1988
|
}
|
|
1193
1989
|
return buffers;
|
|
1194
1990
|
}
|
|
1195
1991
|
async function renderFilePages(filePath, options = {}) {
|
|
1196
|
-
const absolutePath =
|
|
1992
|
+
const absolutePath = path4.resolve(filePath);
|
|
1197
1993
|
const encoding = options.encoding ?? "utf-8";
|
|
1198
|
-
const markdown =
|
|
1199
|
-
const fileDir =
|
|
1994
|
+
const markdown = fs4.readFileSync(absolutePath, encoding);
|
|
1995
|
+
const fileDir = path4.dirname(absolutePath);
|
|
1200
1996
|
const baseUrl = options.baseUrl ?? fileDir;
|
|
1201
1997
|
return renderPages(markdown, {
|
|
1202
1998
|
baseUrl,
|
|
@@ -1211,20 +2007,20 @@ async function render(markdown, options = {}) {
|
|
|
1211
2007
|
const html = await renderToHtml(markdown, options);
|
|
1212
2008
|
const buffer = await renderHtmlToBuffer(html, format, options);
|
|
1213
2009
|
if (options.output) {
|
|
1214
|
-
const outputPath =
|
|
1215
|
-
const outputDir =
|
|
1216
|
-
if (!
|
|
1217
|
-
|
|
2010
|
+
const outputPath = path4.resolve(options.output);
|
|
2011
|
+
const outputDir = path4.dirname(outputPath);
|
|
2012
|
+
if (!fs4.existsSync(outputDir)) {
|
|
2013
|
+
fs4.mkdirSync(outputDir, { recursive: true });
|
|
1218
2014
|
}
|
|
1219
|
-
|
|
2015
|
+
fs4.writeFileSync(outputPath, buffer);
|
|
1220
2016
|
}
|
|
1221
2017
|
return buffer;
|
|
1222
2018
|
}
|
|
1223
2019
|
async function renderFile(filePath, options = {}) {
|
|
1224
|
-
const absolutePath =
|
|
2020
|
+
const absolutePath = path4.resolve(filePath);
|
|
1225
2021
|
const encoding = options.encoding ?? "utf-8";
|
|
1226
|
-
const markdown =
|
|
1227
|
-
const fileDir =
|
|
2022
|
+
const markdown = fs4.readFileSync(absolutePath, encoding);
|
|
2023
|
+
const fileDir = path4.dirname(absolutePath);
|
|
1228
2024
|
const baseUrl = options.baseUrl ?? fileDir;
|
|
1229
2025
|
if (options.pages) {
|
|
1230
2026
|
return renderPages(markdown, {
|
|
@@ -1239,13 +2035,19 @@ async function renderFile(filePath, options = {}) {
|
|
|
1239
2035
|
}
|
|
1240
2036
|
|
|
1241
2037
|
export {
|
|
2038
|
+
mathPlugin,
|
|
1242
2039
|
createMarkdownRenderer,
|
|
2040
|
+
buildHeaderFooterHtml,
|
|
2041
|
+
resolveHeaderFooterPdfOptions,
|
|
2042
|
+
formatHeaderFooterCanvasText,
|
|
1243
2043
|
BrowserRenderer,
|
|
1244
2044
|
renderHtmlToPageBuffers,
|
|
2045
|
+
resolveLocalImageToDataUri,
|
|
2046
|
+
inlineLocalImagesInHtml,
|
|
1245
2047
|
renderToHtml,
|
|
1246
2048
|
renderPages,
|
|
1247
2049
|
renderFilePages,
|
|
1248
2050
|
render,
|
|
1249
2051
|
renderFile
|
|
1250
2052
|
};
|
|
1251
|
-
//# sourceMappingURL=chunk-
|
|
2053
|
+
//# sourceMappingURL=chunk-BLKOSAAA.js.map
|