md2gost 1.3.2 → 1.4.1
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/CHANGELOG.md +18 -0
- package/README.md +35 -9
- package/assets/run.ps1 +15 -2
- package/dist/index.d.ts +30 -2
- package/dist/index.js +300 -41
- package/dist/index.js.map +2 -2
- package/dist/index.mjs +302 -43
- package/dist/index.mjs.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,6 +54,7 @@ var Doc = class {
|
|
|
54
54
|
ctime;
|
|
55
55
|
mtime;
|
|
56
56
|
hyphenation = false;
|
|
57
|
+
totalPagesOffset = 0;
|
|
57
58
|
headings = {
|
|
58
59
|
h1: { size: 14, spacing: { before: 18, after: 4 }, indent_full: false, uppercase: false },
|
|
59
60
|
h3: { size: 14, spacing: { before: 8, after: 4 }, indent_full: false, uppercase: false },
|
|
@@ -277,9 +278,17 @@ async function parseMD(file, logwarn = console.warn) {
|
|
|
277
278
|
return node;
|
|
278
279
|
}
|
|
279
280
|
function parseSection(text) {
|
|
281
|
+
text = text.toLowerCase();
|
|
280
282
|
const match = /from\s+(\d+)/.exec(text);
|
|
281
283
|
const num = match ? parseInt(match[1] || "") : NaN;
|
|
282
|
-
|
|
284
|
+
const unpaged = text.includes("unpaged");
|
|
285
|
+
const portrait = text.includes("portrait");
|
|
286
|
+
const landscape = text.includes("landscape");
|
|
287
|
+
return {
|
|
288
|
+
type: "sectionBreak",
|
|
289
|
+
pageStart: unpaged ? -1 : isFinite(num) ? num : null,
|
|
290
|
+
orientation: portrait ? "portrait" : landscape ? "landscape" : null
|
|
291
|
+
};
|
|
283
292
|
}
|
|
284
293
|
class RuleError extends Error {
|
|
285
294
|
}
|
|
@@ -806,23 +815,44 @@ var import_prism_yaml = require("prismjs/components/prism-yaml");
|
|
|
806
815
|
var import_prism_markdown = require("prismjs/components/prism-markdown");
|
|
807
816
|
var import_prism_docker = require("prismjs/components/prism-docker");
|
|
808
817
|
var import_prism_nginx = require("prismjs/components/prism-nginx");
|
|
818
|
+
|
|
819
|
+
// src/errors.ts
|
|
820
|
+
var UserInputError = class extends Error {
|
|
821
|
+
constructor(message) {
|
|
822
|
+
super(message);
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
// src/serializer.ts
|
|
809
827
|
var STYLE_list = "afc";
|
|
810
828
|
var STYLE_table_title = "TableCaption";
|
|
811
829
|
var STYLE_code_title = "ListingCaption";
|
|
812
830
|
var STYLE_code = "ListingCode";
|
|
813
|
-
async function serializeDocx(doc, fout, workdir, assets) {
|
|
831
|
+
async function serializeDocx(doc, fout, workdir, assets, allowExternalFiles) {
|
|
814
832
|
const getPath = (fname) => {
|
|
833
|
+
if (process.platform != "win32") fname = fname.replaceAll("\\", "/");
|
|
815
834
|
if (process.platform == "win32" && fname[0] == "/") fname = "." + fname;
|
|
816
|
-
|
|
835
|
+
const targetPath = import_path2.default.isAbsolute(fname) ? import_path2.default.resolve(fname) : import_path2.default.resolve(workdir, fname);
|
|
836
|
+
if (!allowExternalFiles) {
|
|
837
|
+
const relative = import_path2.default.relative(workdir, targetPath);
|
|
838
|
+
const isOutside = relative.startsWith("..") || import_path2.default.isAbsolute(relative);
|
|
839
|
+
if (isOutside) throw new UserInputError(`Access denied: "${fname}" resolves outside the working directory.`);
|
|
840
|
+
}
|
|
841
|
+
return targetPath;
|
|
817
842
|
};
|
|
818
843
|
const sections = [];
|
|
819
844
|
const numbering = [];
|
|
820
845
|
const docSections = (function splitSections() {
|
|
821
|
-
const sections2 = [{ pageStart: 1, nodes: [] }];
|
|
846
|
+
const sections2 = [{ displayPageNum: true, orientation: "portrait", pageStart: 1, nodes: [] }];
|
|
822
847
|
for (let j = 0; j < doc.nodes.length; j++) {
|
|
823
848
|
const node = doc.nodes[j];
|
|
824
849
|
if (node.type == "sectionBreak")
|
|
825
|
-
sections2.push({
|
|
850
|
+
sections2.push({
|
|
851
|
+
displayPageNum: node.pageStart == null ? sections2.at(-1)?.displayPageNum || true : node.pageStart >= 0,
|
|
852
|
+
orientation: node.orientation || sections2.at(-1)?.orientation || "portrait",
|
|
853
|
+
pageStart: node.pageStart,
|
|
854
|
+
nodes: []
|
|
855
|
+
});
|
|
826
856
|
else
|
|
827
857
|
sections2.at(-1)?.nodes.push(node);
|
|
828
858
|
}
|
|
@@ -940,13 +970,13 @@ async function serializeDocx(doc, fout, workdir, assets) {
|
|
|
940
970
|
case "image":
|
|
941
971
|
const type = node.src.split(".").at(-1) || "";
|
|
942
972
|
if (!["jpg", "png", "gif", "bmp", "svg"].includes(type))
|
|
943
|
-
throw new
|
|
973
|
+
throw new UserInputError(`Unsupported image format: "${type}", file: ${node.src}`);
|
|
944
974
|
const img_path = getPath(node.src);
|
|
945
975
|
if (!fs3.existsSync(img_path))
|
|
946
|
-
throw new
|
|
976
|
+
throw new UserInputError(`File not exist: ${node.src}`);
|
|
947
977
|
const data = fs3.readFileSync(img_path);
|
|
948
978
|
const dimensions = (0, import_image_size.imageSize)(data);
|
|
949
|
-
const [MaxW, MaxH] = [600,
|
|
979
|
+
const [MaxW, MaxH] = section.orientation == "landscape" ? [950, 600] : [600, 900];
|
|
950
980
|
let [width, height] = [dimensions.width, dimensions.height];
|
|
951
981
|
if (node.width && node.height) [width, height] = [node.width, node.height];
|
|
952
982
|
if (node.width) [width, height] = [node.width, height / width * node.width];
|
|
@@ -959,6 +989,8 @@ async function serializeDocx(doc, fout, workdir, assets) {
|
|
|
959
989
|
indent: { firstLine: 0 },
|
|
960
990
|
spacing: {
|
|
961
991
|
line: 240,
|
|
992
|
+
lineRule: import_docx.LineRuleType.AT_LEAST,
|
|
993
|
+
// For LibreOffice to not shrink image
|
|
962
994
|
...!node.text && doc.img.spacing.after !== void 0 ? { after: doc.img.spacing.after * 20 } : {},
|
|
963
995
|
...doc.img.spacing.before !== void 0 ? { before: doc.img.spacing.before * 20 } : {}
|
|
964
996
|
},
|
|
@@ -1004,10 +1036,10 @@ async function serializeDocx(doc, fout, workdir, assets) {
|
|
|
1004
1036
|
case "externalDoc":
|
|
1005
1037
|
const doc_path = getPath(node.path);
|
|
1006
1038
|
if (!fs3.existsSync(doc_path))
|
|
1007
|
-
throw new
|
|
1039
|
+
throw new UserInputError(`File not exist: ${node.path}`);
|
|
1008
1040
|
const ext = import_path2.default.extname(doc_path);
|
|
1009
1041
|
if (ext != ".docx" && ext != ".pdf")
|
|
1010
|
-
throw new
|
|
1042
|
+
throw new UserInputError(`File not .docx or .pdf: ${node.path}`);
|
|
1011
1043
|
return new import_docx.Paragraph({
|
|
1012
1044
|
text: `!!(${doc_path})${JSON.stringify(node.dict)}`,
|
|
1013
1045
|
indent: { firstLine: 0 },
|
|
@@ -1030,6 +1062,18 @@ async function serializeDocx(doc, fout, workdir, assets) {
|
|
|
1030
1062
|
children,
|
|
1031
1063
|
properties: {
|
|
1032
1064
|
page: {
|
|
1065
|
+
// size: section.orientation == "landscape" ? {
|
|
1066
|
+
// orientation: PageOrientation.LANDSCAPE,
|
|
1067
|
+
// width: convertMillimetersToTwip(297),
|
|
1068
|
+
// height: convertMillimetersToTwip(210),
|
|
1069
|
+
// } : {
|
|
1070
|
+
// orientation: PageOrientation.PORTRAIT,
|
|
1071
|
+
// width: convertMillimetersToTwip(210),
|
|
1072
|
+
// height: convertMillimetersToTwip(297),
|
|
1073
|
+
// },
|
|
1074
|
+
size: {
|
|
1075
|
+
orientation: section.orientation == "landscape" ? import_docx.PageOrientation.LANDSCAPE : import_docx.PageOrientation.PORTRAIT
|
|
1076
|
+
},
|
|
1033
1077
|
pageNumbers: !section.pageStart ? {} : {
|
|
1034
1078
|
start: section.pageStart
|
|
1035
1079
|
},
|
|
@@ -1047,7 +1091,7 @@ async function serializeDocx(doc, fout, workdir, assets) {
|
|
|
1047
1091
|
new import_docx.Paragraph({
|
|
1048
1092
|
alignment: import_docx.AlignmentType.CENTER,
|
|
1049
1093
|
children: [new import_docx.TextRun({
|
|
1050
|
-
children: section.
|
|
1094
|
+
children: section.displayPageNum ? [import_docx.PageNumber.CURRENT] : []
|
|
1051
1095
|
})],
|
|
1052
1096
|
indent: { firstLine: 0 },
|
|
1053
1097
|
spacing: { line: 240 }
|
|
@@ -1103,7 +1147,26 @@ async function serializeDocx(doc, fout, workdir, assets) {
|
|
|
1103
1147
|
let children = null;
|
|
1104
1148
|
if (rune.type == "val" && rune.text == "pages") {
|
|
1105
1149
|
rune.type = "text";
|
|
1106
|
-
children = [import_docx.PageNumber.TOTAL_PAGES]
|
|
1150
|
+
children = doc.totalPagesOffset == 0 ? [import_docx.PageNumber.TOTAL_PAGES] : [
|
|
1151
|
+
// Complex Formula Field: { = { NUMPAGES } + 1 }
|
|
1152
|
+
// Outer field start: "="
|
|
1153
|
+
new RawXml("w:fldChar", { "w:fldCharType": "begin" }),
|
|
1154
|
+
new RawXml("w:instrText", { "xml:space": "preserve" }, " = "),
|
|
1155
|
+
// Inner field start: "NUMPAGES"
|
|
1156
|
+
new RawXml("w:fldChar", { "w:fldCharType": "begin" }),
|
|
1157
|
+
new RawXml("w:instrText", { "xml:space": "preserve" }, " NUMPAGES "),
|
|
1158
|
+
new RawXml("w:fldChar", { "w:fldCharType": "separate" }),
|
|
1159
|
+
new RawXml("w:fldChar", { "w:fldCharType": "end" }),
|
|
1160
|
+
// Operator
|
|
1161
|
+
new RawXml(
|
|
1162
|
+
"w:instrText",
|
|
1163
|
+
{ "xml:space": "preserve" },
|
|
1164
|
+
doc.totalPagesOffset > 0 ? ` + ${doc.totalPagesOffset}` : ` - ${-doc.totalPagesOffset}`
|
|
1165
|
+
),
|
|
1166
|
+
// Outer field close
|
|
1167
|
+
new RawXml("w:fldChar", { "w:fldCharType": "separate" }),
|
|
1168
|
+
new RawXml("w:fldChar", { "w:fldCharType": "end" })
|
|
1169
|
+
];
|
|
1107
1170
|
}
|
|
1108
1171
|
return new import_docx.TextRun({
|
|
1109
1172
|
...children ? { children } : { text: rune.text },
|
|
@@ -1180,7 +1243,6 @@ function addListItemLevel(levels, level, startIndex, itemCount, ordered, alterna
|
|
|
1180
1243
|
}
|
|
1181
1244
|
const longList = startIndex + itemCount > 10;
|
|
1182
1245
|
const left = alternativeStyle ? 0 : longList ? 20.5 : 17.5;
|
|
1183
|
-
const hanging = longList && !alternativeStyle ? (0, import_docx.convertMillimetersToTwip)(8) : null;
|
|
1184
1246
|
const text = ordered ? alternativeStyle ? `%${level + 1}.` : mark == "." ? `%${level + 1}.` : `%${level + 1})` : (
|
|
1185
1247
|
// mark == "*" ? "\u2022" : "\u2012";
|
|
1186
1248
|
mark == "*" ? "\xB7" : "\u2012"
|
|
@@ -1199,13 +1261,22 @@ function addListItemLevel(levels, level, startIndex, itemCount, ordered, alterna
|
|
|
1199
1261
|
paragraph: {
|
|
1200
1262
|
indent: {
|
|
1201
1263
|
left: (0, import_docx.convertMillimetersToTwip)(left + 5 * indent),
|
|
1202
|
-
|
|
1203
|
-
...
|
|
1264
|
+
hanging: (0, import_docx.convertMillimetersToTwip)(longList && !alternativeStyle ? 8 : 5),
|
|
1265
|
+
...alternativeStyle ? { firstLine: (0, import_docx.convertMillimetersToTwip)(12.5) } : {}
|
|
1204
1266
|
}
|
|
1205
1267
|
}
|
|
1206
1268
|
}
|
|
1207
1269
|
});
|
|
1208
1270
|
}
|
|
1271
|
+
var RawXml = class extends import_docx.XmlComponent {
|
|
1272
|
+
constructor(tag, attributes = {}, text = null) {
|
|
1273
|
+
super(tag);
|
|
1274
|
+
if (Object.keys(attributes).length > 0)
|
|
1275
|
+
this.root.push({ _attr: attributes });
|
|
1276
|
+
if (text)
|
|
1277
|
+
this.root.push(text);
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1209
1280
|
function renderCodeHighlighting(code, lang) {
|
|
1210
1281
|
const grammar = import_prismjs.default.languages[lang.trim().toLowerCase()];
|
|
1211
1282
|
if (!grammar) return null;
|
|
@@ -1826,14 +1897,22 @@ async function render({
|
|
|
1826
1897
|
renderPDF = false,
|
|
1827
1898
|
removeIntermediateDocx = false,
|
|
1828
1899
|
disableMacros = false,
|
|
1900
|
+
useLibreOffice = false,
|
|
1901
|
+
allowExternalFiles = false,
|
|
1829
1902
|
logwarn = console.warn,
|
|
1903
|
+
loginfo = console.info,
|
|
1830
1904
|
logPS = (msg) => console.log(`PS: ${msg}`),
|
|
1831
|
-
logPSError = (msg) => console.error(`PS: ${msg}`)
|
|
1905
|
+
logPSError = (msg) => console.error(`PS ERROR: ${msg}`),
|
|
1906
|
+
signal = null
|
|
1832
1907
|
}) {
|
|
1833
|
-
|
|
1908
|
+
signal?.throwIfAborted();
|
|
1834
1909
|
const fin = import_path3.default.resolve(file);
|
|
1910
|
+
progress(10, "\u041F\u044B\u0442\u0430\u0435\u043C\u0441\u044F \u043F\u043E\u043D\u044F\u0442\u044C, \u0447\u0442\u043E \u0432\u044B \u0442\u0443\u0442 \u043D\u0430\u043F\u0438\u0441\u0430\u043B\u0438...");
|
|
1911
|
+
loginfo(`[!1] Parsing file ${fin}`);
|
|
1835
1912
|
const doc = await parseMD(fin, logwarn);
|
|
1913
|
+
signal?.throwIfAborted();
|
|
1836
1914
|
progress(10, "\u041A\u043E\u043B\u0434\u0443\u0435\u043C \u043D\u0430\u0434 \u0441\u0438\u043D\u0442\u0430\u043A\u0441\u0438\u0441\u043E\u043C...");
|
|
1915
|
+
loginfo("[!2] Processing document");
|
|
1837
1916
|
enrichDoc(doc, logwarn);
|
|
1838
1917
|
const runicDoc = runifyDoc(doc);
|
|
1839
1918
|
alchemist(runicDoc, logwarn);
|
|
@@ -1848,64 +1927,107 @@ async function render({
|
|
|
1848
1927
|
throw new Error(`\u0417\u0430\u043A\u0440\u043E\u0439\u0442\u0435 docx \u0444\u0430\u0439\u043B. Output file is busy or locked: ${ftmp}`);
|
|
1849
1928
|
if (await checkIfFileIsBlocked(fout))
|
|
1850
1929
|
throw new Error(`\u0417\u0430\u043A\u0440\u043E\u0439\u0442\u0435 docx \u0444\u0430\u0439\u043B. Output file is busy or locked: ${fout}`);
|
|
1930
|
+
signal?.throwIfAborted();
|
|
1851
1931
|
progress(10, phrase_renderDocx());
|
|
1852
|
-
|
|
1932
|
+
loginfo("[!3] Serializing to docx");
|
|
1933
|
+
await serializeDocx(runicDoc, ftmp, fdir, assets, allowExternalFiles);
|
|
1853
1934
|
let willRunMacros = !disableMacros && (renderPDF || hasReasonForRunningMacros(runicDoc));
|
|
1854
1935
|
let limitedRender = false;
|
|
1855
|
-
if (process.platform != "win32" && willRunMacros) {
|
|
1936
|
+
if (process.platform != "win32" && willRunMacros && !useLibreOffice) {
|
|
1856
1937
|
limitedRender = true;
|
|
1857
1938
|
willRunMacros = false;
|
|
1858
1939
|
}
|
|
1859
1940
|
if (willRunMacros) {
|
|
1941
|
+
signal?.throwIfAborted();
|
|
1860
1942
|
progress(20, phrase_runMacros());
|
|
1943
|
+
loginfo("[!4] Starting macros");
|
|
1861
1944
|
const tmpfolder = import_path3.default.join(pout ? pout.dir : fdir, ".md2gost_out");
|
|
1862
1945
|
await import_promises3.default.rm(tmpfolder, { recursive: true, force: true });
|
|
1863
1946
|
try {
|
|
1864
|
-
const
|
|
1947
|
+
const macroRunner = useLibreOffice ? runUnoScript : runDocxMacro;
|
|
1948
|
+
const ok = await macroRunner(progress, logPS, logPSError, assets, fdir, ftmp, fout, renderPDF, signal);
|
|
1865
1949
|
if (!ok) {
|
|
1866
1950
|
await import_promises3.default.rename(ftmp, fout);
|
|
1867
1951
|
return { fout, err: "inPS" };
|
|
1868
1952
|
}
|
|
1869
1953
|
} catch (x) {
|
|
1954
|
+
if (signal?.aborted) {
|
|
1955
|
+
await import_promises3.default.unlink(ftmp).catch(() => {
|
|
1956
|
+
});
|
|
1957
|
+
throw x;
|
|
1958
|
+
}
|
|
1870
1959
|
await import_promises3.default.rename(ftmp, fout);
|
|
1871
1960
|
return { fout, err: "noPS", errS: x };
|
|
1872
1961
|
}
|
|
1873
|
-
|
|
1962
|
+
if ((0, import_fs.existsSync)(ftmp))
|
|
1963
|
+
await import_promises3.default.unlink(ftmp);
|
|
1964
|
+
signal?.throwIfAborted();
|
|
1874
1965
|
const errorTxt = import_path3.default.join(tmpfolder, "error.txt");
|
|
1875
1966
|
if ((0, import_fs.existsSync)(errorTxt)) {
|
|
1876
1967
|
const err = await import_promises3.default.readFile(errorTxt);
|
|
1877
1968
|
await import_promises3.default.rm(tmpfolder, { recursive: true, force: true });
|
|
1878
1969
|
return { fout, err: "vba", errS: `${err}` };
|
|
1879
1970
|
}
|
|
1971
|
+
loginfo("[!5] Updating metadata");
|
|
1880
1972
|
await updateMetadata(fout, doc);
|
|
1881
1973
|
if (renderPDF) {
|
|
1974
|
+
signal?.throwIfAborted();
|
|
1882
1975
|
progress(10, phrase_combine());
|
|
1976
|
+
loginfo("[!6] Merging pdfs");
|
|
1883
1977
|
if (!(0, import_fs.existsSync)(tmpfolder))
|
|
1884
1978
|
return { fout, err: "pdf" };
|
|
1885
1979
|
try {
|
|
1886
1980
|
const files = (await import_promises3.default.readdir(tmpfolder)).sort().map((f) => import_path3.default.join(tmpfolder, f));
|
|
1887
1981
|
const pdf = pout ? import_path3.default.join(pout.dir, pout.base) : import_path3.default.join(fdir, fname + ".pdf");
|
|
1888
|
-
await mergePDFs(files, pdf, doc);
|
|
1982
|
+
await mergePDFs(files, pdf, doc, signal || void 0);
|
|
1889
1983
|
await import_promises3.default.rm(tmpfolder, { recursive: true, force: true });
|
|
1890
1984
|
if (removeIntermediateDocx && (0, import_fs.existsSync)(fout)) await import_promises3.default.unlink(fout);
|
|
1891
1985
|
return { fout: pdf };
|
|
1892
1986
|
} catch (err) {
|
|
1987
|
+
if (signal?.aborted) throw err;
|
|
1893
1988
|
return { fout, err: "pdf", errS: err };
|
|
1894
1989
|
}
|
|
1895
1990
|
}
|
|
1896
1991
|
} else {
|
|
1897
1992
|
await import_promises3.default.rename(ftmp, fout);
|
|
1898
1993
|
}
|
|
1994
|
+
loginfo(`[!7] Rendered to ${fout}`);
|
|
1899
1995
|
return { fout, err: limitedRender ? "noWin" : void 0 };
|
|
1900
1996
|
}
|
|
1901
1997
|
function hasReasonForRunningMacros(doc) {
|
|
1902
1998
|
if (DISABLE_MACRO) return false;
|
|
1903
1999
|
return doc.nodes.some((n) => n.type == "code" || n.type == "table");
|
|
1904
2000
|
}
|
|
1905
|
-
function
|
|
2001
|
+
function killProcessTree(child, wordPid) {
|
|
2002
|
+
if (!child.pid) return;
|
|
2003
|
+
if (process.platform === "win32") {
|
|
2004
|
+
if (wordPid) {
|
|
2005
|
+
(0, import_child_process.exec)(`taskkill /pid ${wordPid}`, () => {
|
|
2006
|
+
setTimeout(() => {
|
|
2007
|
+
(0, import_child_process.exec)(`taskkill /pid ${wordPid} /F`, () => {
|
|
2008
|
+
});
|
|
2009
|
+
setTimeout(() => {
|
|
2010
|
+
killProcessTree(child);
|
|
2011
|
+
}, 2e3);
|
|
2012
|
+
}, 2e3);
|
|
2013
|
+
});
|
|
2014
|
+
} else {
|
|
2015
|
+
(0, import_child_process.exec)(`taskkill /pid ${child.pid} /T /F`, () => {
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
} else {
|
|
2019
|
+
try {
|
|
2020
|
+
process.kill(-child.pid, "SIGKILL");
|
|
2021
|
+
} catch {
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
function runDocxMacro(progress, log, logError, assets, cwd, fin, fout, renderPDF, signal) {
|
|
1906
2026
|
const script = import_path3.default.join(assets, "run.ps1");
|
|
1907
2027
|
const templateMacro = import_path3.default.join(assets, "template.dotm");
|
|
1908
2028
|
return new Promise((res, rej) => {
|
|
2029
|
+
if (signal?.aborted)
|
|
2030
|
+
return rej(signal.reason ?? new Error("Aborted before execution started"));
|
|
1909
2031
|
const child = (0, import_child_process.spawn)("powershell", [
|
|
1910
2032
|
"-NoProfile",
|
|
1911
2033
|
"-ExecutionPolicy",
|
|
@@ -1919,10 +2041,28 @@ function runDocxMacro(progress, log, logError, assets, cwd, fin, fout, renderPDF
|
|
|
1919
2041
|
"-MacroTemplate",
|
|
1920
2042
|
templateMacro,
|
|
1921
2043
|
...renderPDF ? ["-RenderPDF"] : []
|
|
1922
|
-
], {
|
|
2044
|
+
], {
|
|
2045
|
+
cwd,
|
|
2046
|
+
detached: process.platform !== "win32"
|
|
2047
|
+
// Enables PGID on POSIX
|
|
2048
|
+
// detached: true,
|
|
2049
|
+
// shell: true,
|
|
2050
|
+
});
|
|
2051
|
+
let wordPid = null;
|
|
2052
|
+
let onAbort;
|
|
2053
|
+
if (signal) {
|
|
2054
|
+
onAbort = () => {
|
|
2055
|
+
logError("Operation aborted. Terminating process tree...");
|
|
2056
|
+
killProcessTree(child, wordPid);
|
|
2057
|
+
rej(signal.reason ?? new Error("Job timed out or aborted"));
|
|
2058
|
+
};
|
|
2059
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2060
|
+
}
|
|
1923
2061
|
child.stdout.on("data", (data) => {
|
|
1924
|
-
|
|
1925
|
-
const
|
|
2062
|
+
const raw = `${data}`;
|
|
2063
|
+
const pidMatch = /\[WordPID:(\d+)\]/.exec(raw);
|
|
2064
|
+
if (pidMatch) wordPid = parseInt(pidMatch[1], 10);
|
|
2065
|
+
const m = /\[\*(\d)\]/.exec(raw);
|
|
1926
2066
|
if (m) progress(12, {
|
|
1927
2067
|
// "1": "Fixing breaks (listings)",
|
|
1928
2068
|
"1": render_fixingBreaks(),
|
|
@@ -1931,31 +2071,115 @@ function runDocxMacro(progress, log, logError, assets, cwd, fin, fout, renderPDF
|
|
|
1931
2071
|
// "3": "Rendering to PDF",
|
|
1932
2072
|
"3": render_renderPDF()
|
|
1933
2073
|
}[m[1]] || "Make some work");
|
|
1934
|
-
log(
|
|
2074
|
+
log(raw);
|
|
1935
2075
|
});
|
|
1936
2076
|
let ok = true;
|
|
1937
2077
|
child.stderr.on("data", (data) => {
|
|
1938
2078
|
ok = false;
|
|
1939
2079
|
logError(`${data}`);
|
|
1940
2080
|
});
|
|
1941
|
-
child.on("close", () =>
|
|
1942
|
-
|
|
2081
|
+
child.on("close", (code) => {
|
|
2082
|
+
if (signal && onAbort) {
|
|
2083
|
+
signal.removeEventListener("abort", onAbort);
|
|
2084
|
+
}
|
|
2085
|
+
res(ok && code === 0);
|
|
2086
|
+
});
|
|
2087
|
+
child.on("error", (err) => {
|
|
2088
|
+
if (signal && onAbort) {
|
|
2089
|
+
signal.removeEventListener("abort", onAbort);
|
|
2090
|
+
}
|
|
2091
|
+
rej(err);
|
|
2092
|
+
});
|
|
1943
2093
|
});
|
|
1944
2094
|
}
|
|
1945
|
-
|
|
2095
|
+
function getPythonExecutable() {
|
|
2096
|
+
if (process.platform === "win32") {
|
|
2097
|
+
const loPython = "C:\\Program Files\\LibreOffice\\program\\python.exe";
|
|
2098
|
+
if ((0, import_fs.existsSync)(loPython)) return loPython;
|
|
2099
|
+
}
|
|
2100
|
+
return process.env.UNO_PYTHON_PATH || "python3";
|
|
2101
|
+
}
|
|
2102
|
+
function runUnoScript(progress, log, logError, assets, cwd, fin, fout, renderPDF, signal) {
|
|
2103
|
+
const script = process.env.MACRO_ENGINE_PATH || import_path3.default.join(assets, "macro_engine.py");
|
|
2104
|
+
const pythonExec = getPythonExecutable();
|
|
2105
|
+
return new Promise((res, rej) => {
|
|
2106
|
+
if (signal?.aborted) {
|
|
2107
|
+
return rej(signal.reason ?? new Error("Aborted before execution started"));
|
|
2108
|
+
}
|
|
2109
|
+
const args = [
|
|
2110
|
+
script,
|
|
2111
|
+
"--input",
|
|
2112
|
+
fin,
|
|
2113
|
+
"--output",
|
|
2114
|
+
fout
|
|
2115
|
+
];
|
|
2116
|
+
if (renderPDF) args.push("--render-pdf");
|
|
2117
|
+
const child = (0, import_child_process.spawn)(pythonExec, args, {
|
|
2118
|
+
cwd,
|
|
2119
|
+
detached: process.platform !== "win32"
|
|
2120
|
+
// Enables PGID on POSIX
|
|
2121
|
+
});
|
|
2122
|
+
let onAbort;
|
|
2123
|
+
if (signal) {
|
|
2124
|
+
onAbort = () => {
|
|
2125
|
+
logError("Operation aborted. Terminating process tree...");
|
|
2126
|
+
killProcessTree(child);
|
|
2127
|
+
rej(signal.reason ?? new Error("Job timed out or aborted"));
|
|
2128
|
+
};
|
|
2129
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2130
|
+
}
|
|
2131
|
+
child.stdout.on("data", (data) => {
|
|
2132
|
+
const lines = `${data}`.split("\n");
|
|
2133
|
+
for (const line of lines) {
|
|
2134
|
+
log(line.trimEnd());
|
|
2135
|
+
if (!line.trimEnd()) continue;
|
|
2136
|
+
const m = /\[\*(\d)\]/.exec(line);
|
|
2137
|
+
if (m) {
|
|
2138
|
+
const msg = {
|
|
2139
|
+
"1": render_fixingBreaks(),
|
|
2140
|
+
"2": render_fixingBreaks(),
|
|
2141
|
+
"3": render_renderPDF()
|
|
2142
|
+
}[m[1]] || "Processing document...";
|
|
2143
|
+
progress(12, msg);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2147
|
+
let ok = true;
|
|
2148
|
+
child.stderr.on("data", (data) => {
|
|
2149
|
+
ok = false;
|
|
2150
|
+
logError(`${data}`);
|
|
2151
|
+
});
|
|
2152
|
+
child.on("close", (code) => {
|
|
2153
|
+
if (signal && onAbort) {
|
|
2154
|
+
signal.removeEventListener("abort", onAbort);
|
|
2155
|
+
}
|
|
2156
|
+
res(ok && code === 0);
|
|
2157
|
+
});
|
|
2158
|
+
child.on("error", (err) => {
|
|
2159
|
+
if (signal && onAbort) {
|
|
2160
|
+
signal.removeEventListener("abort", onAbort);
|
|
2161
|
+
}
|
|
2162
|
+
rej(err);
|
|
2163
|
+
});
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
async function mergePDFs(files, fout, doc, signal) {
|
|
1946
2167
|
const mergedPdf = await import_pdf_lib.PDFDocument.create();
|
|
1947
2168
|
if (doc?.title) mergedPdf.setTitle(doc.title, { showInWindowTitleBar: true });
|
|
1948
2169
|
if (doc?.author) mergedPdf.setAuthor(doc.author);
|
|
1949
2170
|
if (doc?.ctime) mergedPdf.setCreationDate(doc.ctime);
|
|
1950
2171
|
if (doc?.mtime) mergedPdf.setModificationDate(doc.mtime);
|
|
1951
2172
|
for (const file of files) {
|
|
1952
|
-
|
|
2173
|
+
if (!file.endsWith(".pdf")) continue;
|
|
2174
|
+
const bytes = await import_promises3.default.readFile(file, { signal });
|
|
1953
2175
|
const pdf = await import_pdf_lib.PDFDocument.load(bytes);
|
|
2176
|
+
signal?.throwIfAborted();
|
|
1954
2177
|
const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
|
1955
2178
|
pages.forEach((page) => mergedPdf.addPage(page));
|
|
2179
|
+
signal?.throwIfAborted();
|
|
1956
2180
|
}
|
|
1957
2181
|
const pdfBytes = await mergedPdf.save();
|
|
1958
|
-
await import_promises3.default.writeFile(fout, pdfBytes);
|
|
2182
|
+
await import_promises3.default.writeFile(fout, pdfBytes, { signal });
|
|
1959
2183
|
}
|
|
1960
2184
|
async function updateMetadata(docfile, doc) {
|
|
1961
2185
|
const content = await import_promises3.default.readFile(docfile);
|
|
@@ -2047,17 +2271,32 @@ async function renderMarkdown(config) {
|
|
|
2047
2271
|
assert(config.format == "docx" || config.format == "pdf" || typeof config.format == "undefined", "Format must be either 'docx' or 'pdf', or undefined.");
|
|
2048
2272
|
assert(typeof config.keepIntermediateDocx == "boolean" || typeof config.keepIntermediateDocx == "undefined", "keepIntermediateDocx must be a boolean or undefined.");
|
|
2049
2273
|
assert(typeof config.disableMacros == "boolean" || typeof config.disableMacros == "undefined", "disableMacros must be a boolean or undefined.");
|
|
2274
|
+
assert(typeof config.useLibreOffice == "boolean" || typeof config.useLibreOffice == "undefined", "useLibreOffice must be a boolean or undefined.");
|
|
2275
|
+
assert(typeof config.allowExternalFiles == "boolean" || typeof config.allowExternalFiles == "undefined", "allowExternalFiles must be a boolean or undefined.");
|
|
2050
2276
|
assert(typeof config.progress == "function" || typeof config.progress == "undefined", "Progress callback must be a function or undefined.");
|
|
2277
|
+
assert(config.abortSignal instanceof AbortSignal || typeof config.abortSignal == "undefined", "abortSignal must be an AbortSignal or undefined.");
|
|
2051
2278
|
{
|
|
2052
2279
|
const extraProps = getExtraProperties(
|
|
2053
2280
|
config,
|
|
2054
|
-
["input", "output", "format", "keepIntermediateDocx", "disableMacros", "progress"]
|
|
2281
|
+
["input", "output", "format", "keepIntermediateDocx", "disableMacros", "useLibreOffice", "progress", "logger", "abortSignal", "allowExternalFiles"]
|
|
2055
2282
|
);
|
|
2056
2283
|
assert(extraProps.length == 0, `Found unknown properties in config: ${extraProps.join(", ")}`);
|
|
2057
2284
|
}
|
|
2285
|
+
assert(typeof config.logger == "object" && config.logger || typeof config.logger == "undefined", "Logger must be a object or undefined.");
|
|
2286
|
+
const logger = config.logger;
|
|
2287
|
+
if (logger) {
|
|
2288
|
+
const props = ["info", "warn", "error"];
|
|
2289
|
+
const extraProps = getExtraProperties(logger, props);
|
|
2290
|
+
assert(extraProps.length == 0, `Found unknown properties in config.logger: ${extraProps.join(", ")}`);
|
|
2291
|
+
props.forEach(
|
|
2292
|
+
(p) => assert(typeof logger[p] == "function", `config.logger.${p} must be a function.`)
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2058
2295
|
const renderPDF = config.format === "pdf" || !config.format && !!config.output?.endsWith(".pdf");
|
|
2059
2296
|
const removeIntermediateDocx = !config.keepIntermediateDocx;
|
|
2060
2297
|
const disableMacros = !!config.disableMacros;
|
|
2298
|
+
const useLibreOffice = !!config.useLibreOffice;
|
|
2299
|
+
const allowExternalFiles = !!config.allowExternalFiles;
|
|
2061
2300
|
assert(!(disableMacros && renderPDF), "Macros must be enabled to render PDF output.");
|
|
2062
2301
|
const pin = import_path4.default.parse(import_path4.default.resolve(config.input));
|
|
2063
2302
|
const fname = pin.name.endsWith(".g") ? pin.name.slice(0, -2) : pin.name;
|
|
@@ -2072,10 +2311,10 @@ async function renderMarkdown(config) {
|
|
|
2072
2311
|
const warnings = [];
|
|
2073
2312
|
const logPS = [];
|
|
2074
2313
|
let totalPercent = 0;
|
|
2075
|
-
const handleProgress =
|
|
2314
|
+
const handleProgress = (increment, message) => {
|
|
2076
2315
|
totalPercent = Math.min(totalPercent + increment, 99);
|
|
2077
|
-
config.progress(totalPercent, message);
|
|
2078
|
-
}
|
|
2316
|
+
config.progress?.(totalPercent, message);
|
|
2317
|
+
};
|
|
2079
2318
|
try {
|
|
2080
2319
|
let throwMDE2 = function(code, msg) {
|
|
2081
2320
|
throw new MDRenderError(code, msg, fout, warnings, logPS, errS ? { cause: errS } : void 0);
|
|
@@ -2089,13 +2328,31 @@ async function renderMarkdown(config) {
|
|
|
2089
2328
|
renderPDF,
|
|
2090
2329
|
removeIntermediateDocx,
|
|
2091
2330
|
disableMacros,
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2331
|
+
useLibreOffice,
|
|
2332
|
+
allowExternalFiles,
|
|
2333
|
+
loginfo: logger?.info,
|
|
2334
|
+
logwarn: (msg) => {
|
|
2335
|
+
logger?.warn(msg);
|
|
2336
|
+
warnings.push(msg);
|
|
2337
|
+
},
|
|
2338
|
+
logPS: (msg) => {
|
|
2339
|
+
logger?.info(msg);
|
|
2340
|
+
logPS.push(msg);
|
|
2341
|
+
},
|
|
2342
|
+
logPSError: (msg) => {
|
|
2343
|
+
logger?.error(msg);
|
|
2344
|
+
logPS.push(msg);
|
|
2345
|
+
},
|
|
2346
|
+
signal: config.abortSignal
|
|
2095
2347
|
});
|
|
2096
2348
|
config.progress?.(100, "Done!");
|
|
2097
|
-
if (
|
|
2098
|
-
|
|
2349
|
+
if (useLibreOffice) {
|
|
2350
|
+
if (err == "noPS") throwMDE2("noPS", `Failed to run the LibreOffice binary (soffice). Ensure LibreOffice is installed and added to your system's PATH environment variable.`);
|
|
2351
|
+
if (err == "inPS") throwMDE2("inPS", `An unexpected error occurred executing the LibreOffice conversion command. Verify that LibreOffice is properly installed, up to date, and has permission to access the target files.`);
|
|
2352
|
+
} else {
|
|
2353
|
+
if (err == "noPS") throwMDE2("noPS", `Failed to initialize PowerShell. Ensure PowerShell is installed and explicitly added to your system's PATH environment variable.`);
|
|
2354
|
+
if (err == "inPS") throwMDE2("inPS", `An unexpected error occurred executing commands inside PowerShell. Verify that Microsoft Word is properly installed and licensed.`);
|
|
2355
|
+
}
|
|
2099
2356
|
if (err == "vba") throwMDE2("vba", `Microsoft Word VBA Macro execution failed: ${errS}`);
|
|
2100
2357
|
if (err == "pdf") throwMDE2("pdf", `Failed to export document to PDF.`);
|
|
2101
2358
|
if (err == "noWin") throwMDE2("noWin", `Platform restriction: Extended formatting macros and PDF rendering are only supported natively on Windows systems.`);
|
|
@@ -2111,6 +2368,7 @@ async function renderMarkdown(config) {
|
|
|
2111
2368
|
};
|
|
2112
2369
|
} catch (x) {
|
|
2113
2370
|
if (x instanceof MDRenderError) throw x;
|
|
2371
|
+
if (x instanceof UserInputError) throw new MDRenderError("userInput", x.message, null, warnings, logPS, { cause: x });
|
|
2114
2372
|
const message = x instanceof Error ? x.message : String(x);
|
|
2115
2373
|
throw new MDRenderError("unknown", `An unhandled internal error occurred during rendering: ${message}`, null, warnings, logPS, { cause: x });
|
|
2116
2374
|
}
|
|
@@ -2123,6 +2381,7 @@ var MDRenderError = class _MDRenderError extends Error {
|
|
|
2123
2381
|
/**
|
|
2124
2382
|
* Code indicating the underlying cause of the failure:
|
|
2125
2383
|
* - `unknown`: An unhandled or unexpected internal runtime error.
|
|
2384
|
+
* - `userInput`: Invalid user input or Markdown document structure.
|
|
2126
2385
|
* - `noPS`: PowerShell could not be started or found in the system PATH.
|
|
2127
2386
|
* - `inPS`: A failure occurred inside PowerShell; MS Word may be missing or corrupt.
|
|
2128
2387
|
* - `vba`: An error occurred during the execution of internal Word VBA macros.
|