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