@illusions-lab/mdi-to-docx 2.0.24 → 2.0.26
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 +7 -5
- package/dist/index.cjs +8 -398
- package/dist/index.d.cts +5 -11
- package/dist/index.d.ts +5 -11
- package/dist/index.js +8 -421
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @illusions-lab/mdi-to-docx
|
|
2
2
|
|
|
3
|
-
Creates a DOCX file from an MDI-flavoured mdast tree. It is a
|
|
4
|
-
|
|
3
|
+
Creates a DOCX file from an MDI-flavoured mdast tree. It is a compatibility
|
|
4
|
+
entry point for unified applications: the tree is serialized to MDI, then the
|
|
5
|
+
same Rust OOXML renderer used by every binding creates the document.
|
|
5
6
|
|
|
6
7
|
## Install
|
|
7
8
|
|
|
@@ -44,12 +45,13 @@ remaining fallbacks:
|
|
|
44
45
|
| blank paragraph / pagebreak | Empty paragraph / native page break |
|
|
45
46
|
|
|
46
47
|
The source mdast remains available to the host application, so a UI can retain
|
|
47
|
-
source spans and render diagnostics before choosing to export.
|
|
48
|
-
|
|
48
|
+
source spans and render diagnostics before choosing to export. JavaScript does
|
|
49
|
+
not contain a second DOCX generator, and the package does not claim
|
|
50
|
+
pixel-equivalent Japanese layout across Word-compatible readers.
|
|
49
51
|
|
|
50
52
|
## Documentation
|
|
51
53
|
|
|
52
54
|
- [Output model](https://mdi.illusions.app/ecosystem/outputs/)
|
|
53
|
-
- [Export-profile guide](https://mdi.illusions.app/
|
|
55
|
+
- [Export-profile guide](https://mdi.illusions.app/ecosystem/export-profiles/)
|
|
54
56
|
- [API reference](https://mdi.illusions.app/api/to-docx/)
|
|
55
57
|
- [JavaScript documentation](https://mdi.illusions.app/bindings/javascript/)
|
package/dist/index.cjs
CHANGED
|
@@ -24,407 +24,17 @@ __export(index_exports, {
|
|
|
24
24
|
mdiToDocx: () => mdiToDocx
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var import_mdi_core = require("@illusions-lab/mdi-core");
|
|
28
|
+
var import_mdast_util_mdi = require("mdast-util-mdi");
|
|
29
29
|
var MDI_SPEC_VERSION = "2.0";
|
|
30
30
|
async function mdiToDocx(tree, profile) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
if (!tree || tree.type !== "root" || !Array.isArray(tree.children))
|
|
32
|
+
throw new TypeError("tree must be an mdast root");
|
|
33
|
+
if (profile !== void 0 && (!profile || typeof profile !== "object" || Array.isArray(profile)))
|
|
34
|
+
throw new TypeError("profile must be an object");
|
|
35
|
+
return Buffer.from(
|
|
36
|
+
(0, import_mdi_core.renderDocxWithProfile)((0, import_mdast_util_mdi.mdastToMdiSource)(tree), JSON.stringify(profile ?? {}))
|
|
34
37
|
);
|
|
35
|
-
const definitions = tree.children.filter(
|
|
36
|
-
(node) => node.type === "footnoteDefinition"
|
|
37
|
-
);
|
|
38
|
-
const footnoteIds = new Map(
|
|
39
|
-
definitions.map((definition, index) => [definition.identifier, index + 1])
|
|
40
|
-
);
|
|
41
|
-
const context = { footnoteIds };
|
|
42
|
-
const children = tree.children.flatMap((node) => block(node, options, context));
|
|
43
|
-
const dimensions = import_mdi_export_profile.PAGE_DIMENSIONS[options.pagination.pageSize];
|
|
44
|
-
const widthMm = options.pagination.landscape ? dimensions.height : dimensions.width;
|
|
45
|
-
const heightMm = options.pagination.landscape ? dimensions.width : dimensions.height;
|
|
46
|
-
const primary = options.typesetting.writingMode === "vertical" ? heightMm - options.pagination.margins.top - options.pagination.margins.bottom : widthMm - options.pagination.margins.left - options.pagination.margins.right;
|
|
47
|
-
const cross = options.typesetting.writingMode === "vertical" ? widthMm - options.pagination.margins.left - options.pagination.margins.right : heightMm - options.pagination.margins.top - options.pagination.margins.bottom;
|
|
48
|
-
const fontSizeMm = options.typesetting.fontSize / 72 * 25.4;
|
|
49
|
-
const fontSizeHalfPoints = Math.round(fontSizeMm / 25.4 * 72 * 2);
|
|
50
|
-
const strictGrid = options.pagination.gridMode === "strict";
|
|
51
|
-
const characterPitchPoints = primary / options.pagination.charactersPerLine / 25.4 * 72;
|
|
52
|
-
const characterSpace = Math.max(
|
|
53
|
-
0,
|
|
54
|
-
Math.round((characterPitchPoints - fontSizeHalfPoints / 2) * 4096)
|
|
55
|
-
);
|
|
56
|
-
const lineTwips = options.typesetting.lineSpacing === void 0 ? Math.round(cross / options.pagination.linesPerPage / 25.4 * 1440) : Math.round(fontSizeHalfPoints * 10 * options.typesetting.lineSpacing);
|
|
57
|
-
const document = new import_docx.Document({
|
|
58
|
-
title: options.metadata.title ?? tree.data?.frontmatter?.title,
|
|
59
|
-
creator: options.metadata.author ?? tree.data?.frontmatter?.author,
|
|
60
|
-
footnotes: Object.fromEntries(
|
|
61
|
-
definitions.map((definition, index) => [
|
|
62
|
-
String(index + 1),
|
|
63
|
-
{
|
|
64
|
-
children: definition.children.flatMap(
|
|
65
|
-
(child) => child.type === "paragraph" ? [new import_docx.Paragraph({ children: inline(child.children, context) })] : []
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
])
|
|
69
|
-
),
|
|
70
|
-
styles: {
|
|
71
|
-
default: {
|
|
72
|
-
document: {
|
|
73
|
-
run: {
|
|
74
|
-
font: options.typesetting.fontFamily,
|
|
75
|
-
size: fontSizeHalfPoints,
|
|
76
|
-
color: "000000"
|
|
77
|
-
},
|
|
78
|
-
paragraph: { spacing: { line: lineTwips, after: strictGrid ? 0 : 120 } }
|
|
79
|
-
},
|
|
80
|
-
heading1: headingStyle(options, 1, fontSizeHalfPoints),
|
|
81
|
-
heading2: headingStyle(options, 2, fontSizeHalfPoints),
|
|
82
|
-
heading3: headingStyle(options, 3, fontSizeHalfPoints),
|
|
83
|
-
heading4: headingStyle(options, 4, fontSizeHalfPoints),
|
|
84
|
-
heading5: headingStyle(options, 5, fontSizeHalfPoints),
|
|
85
|
-
heading6: headingStyle(options, 6, fontSizeHalfPoints),
|
|
86
|
-
listParagraph: {
|
|
87
|
-
run: { font: options.typesetting.fontFamily, color: "000000" }
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
paragraphStyles: [
|
|
91
|
-
customHeadingStyle(options, 7, fontSizeHalfPoints),
|
|
92
|
-
customHeadingStyle(options, 8, fontSizeHalfPoints),
|
|
93
|
-
customHeadingStyle(options, 9, fontSizeHalfPoints),
|
|
94
|
-
{
|
|
95
|
-
id: "MdiQuote",
|
|
96
|
-
name: "MDI Quote",
|
|
97
|
-
basedOn: "Normal",
|
|
98
|
-
run: { font: options.typesetting.fontFamily, color: "000000", italics: true },
|
|
99
|
-
paragraph: {
|
|
100
|
-
indent: { left: 720, right: 360 },
|
|
101
|
-
spacing: { before: strictGrid ? 0 : 120, after: strictGrid ? 0 : 120, line: lineTwips }
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: "MdiList",
|
|
106
|
-
name: "MDI List",
|
|
107
|
-
basedOn: "ListParagraph",
|
|
108
|
-
run: { font: options.typesetting.fontFamily, color: "000000" },
|
|
109
|
-
paragraph: { spacing: { after: strictGrid ? 0 : 60, line: lineTwips } }
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
id: "MdiCode",
|
|
113
|
-
name: "MDI Code Block",
|
|
114
|
-
basedOn: "Normal",
|
|
115
|
-
run: { font: "Courier New", color: "000000" },
|
|
116
|
-
paragraph: {
|
|
117
|
-
indent: { left: 360, right: 360 },
|
|
118
|
-
spacing: { before: strictGrid ? 0 : 120, after: strictGrid ? 0 : 120, line: lineTwips }
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
id: "MdiThematicBreak",
|
|
123
|
-
name: "MDI Thematic Break",
|
|
124
|
-
basedOn: "Normal",
|
|
125
|
-
run: { font: options.typesetting.fontFamily, color: "000000" },
|
|
126
|
-
paragraph: { spacing: { before: strictGrid ? 0 : 120, after: strictGrid ? 0 : 180, line: lineTwips } }
|
|
127
|
-
}
|
|
128
|
-
]
|
|
129
|
-
},
|
|
130
|
-
sections: [
|
|
131
|
-
{
|
|
132
|
-
...pageNumberSection(options, fontSizeHalfPoints),
|
|
133
|
-
properties: {
|
|
134
|
-
page: {
|
|
135
|
-
size: { width: mmToTwips(widthMm), height: mmToTwips(heightMm) },
|
|
136
|
-
margin: Object.fromEntries(
|
|
137
|
-
Object.entries(options.pagination.margins).map(([key, value]) => [
|
|
138
|
-
key,
|
|
139
|
-
mmToTwips(value)
|
|
140
|
-
])
|
|
141
|
-
),
|
|
142
|
-
...options.pagination.pageNumbers.enabled ? { pageNumbers: { start: 1 } } : {},
|
|
143
|
-
...options.typesetting.writingMode === "vertical" ? {
|
|
144
|
-
textDirection: import_docx.PageTextDirectionType.TOP_TO_BOTTOM_RIGHT_TO_LEFT
|
|
145
|
-
} : {}
|
|
146
|
-
},
|
|
147
|
-
// Word's document grid is the portable OOXML mechanism that records
|
|
148
|
-
// a publisher's characters × lines contract. `linePitch` comes from
|
|
149
|
-
// the resolved physical block extent; the matching PDF profile uses
|
|
150
|
-
// the same resolved page geometry.
|
|
151
|
-
...strictGrid ? {
|
|
152
|
-
grid: {
|
|
153
|
-
type: "linesAndChars",
|
|
154
|
-
...characterSpace === 0 ? {} : { charSpace: characterSpace },
|
|
155
|
-
linePitch: lineTwips
|
|
156
|
-
}
|
|
157
|
-
} : {}
|
|
158
|
-
},
|
|
159
|
-
children
|
|
160
|
-
}
|
|
161
|
-
]
|
|
162
|
-
});
|
|
163
|
-
addBookMarginSettings(document, options);
|
|
164
|
-
return import_docx.Packer.toBuffer(document);
|
|
165
|
-
}
|
|
166
|
-
function addBookMarginSettings(document, options) {
|
|
167
|
-
if (options.layout.marginMode !== "mirror") return;
|
|
168
|
-
const namespace = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"';
|
|
169
|
-
document.Settings.addChildElement(
|
|
170
|
-
import_docx.ImportedXmlComponent.fromXmlString(`<w:mirrorMargins ${namespace}/>`)
|
|
171
|
-
);
|
|
172
|
-
if (options.layout.bindingSide === "right")
|
|
173
|
-
document.Settings.addChildElement(
|
|
174
|
-
import_docx.ImportedXmlComponent.fromXmlString(`<w:rtlGutter ${namespace}/>`)
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
function headingStyle(options, level, bodyFontSizeHalfPoints2 = 22) {
|
|
178
|
-
const before = [360, 300, 240, 180, 150, 120][level - 1] ?? 120;
|
|
179
|
-
const strictGrid = options.pagination.gridMode === "strict";
|
|
180
|
-
return {
|
|
181
|
-
run: {
|
|
182
|
-
font: options.typesetting.fontFamily,
|
|
183
|
-
color: "000000",
|
|
184
|
-
bold: true,
|
|
185
|
-
size: strictGrid ? bodyFontSizeHalfPoints2 : headingFontSize(level, bodyFontSizeHalfPoints2)
|
|
186
|
-
},
|
|
187
|
-
paragraph: {
|
|
188
|
-
spacing: { before: strictGrid ? 0 : before, after: strictGrid ? 0 : 120 },
|
|
189
|
-
keepNext: true,
|
|
190
|
-
keepLines: true,
|
|
191
|
-
outlineLevel: level - 1
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
function headingFontSize(level, bodyFontSizeHalfPoints2 = 22) {
|
|
196
|
-
const scale = [1.8, 1.55, 1.35, 1.2, 1.1, 1][level - 1] ?? 1;
|
|
197
|
-
return Math.round(bodyFontSizeHalfPoints2 * scale);
|
|
198
|
-
}
|
|
199
|
-
function customHeadingStyle(options, level, bodyFontSizeHalfPoints2 = 22) {
|
|
200
|
-
const style = headingStyle(options, level, bodyFontSizeHalfPoints2);
|
|
201
|
-
return {
|
|
202
|
-
id: `Heading${level}`,
|
|
203
|
-
name: `Heading ${level}`,
|
|
204
|
-
basedOn: "Normal",
|
|
205
|
-
next: "Normal",
|
|
206
|
-
quickFormat: true,
|
|
207
|
-
...style
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function pageNumberSection(options, size) {
|
|
211
|
-
const page = options.pagination.pageNumbers;
|
|
212
|
-
if (!page.enabled) return {};
|
|
213
|
-
const alignment = page.position.endsWith("left") ? import_docx.AlignmentType.LEFT : page.position.endsWith("right") ? import_docx.AlignmentType.RIGHT : import_docx.AlignmentType.CENTER;
|
|
214
|
-
const current = new import_docx.TextRun({ children: [import_docx.PageNumber.CURRENT], size });
|
|
215
|
-
const runs = page.format === "dash" ? [
|
|
216
|
-
new import_docx.TextRun({ text: "\u2014 ", size }),
|
|
217
|
-
current,
|
|
218
|
-
new import_docx.TextRun({ text: " \u2014", size })
|
|
219
|
-
] : page.format === "fraction" ? [
|
|
220
|
-
current,
|
|
221
|
-
new import_docx.TextRun({ text: " / ", size }),
|
|
222
|
-
new import_docx.TextRun({ children: [import_docx.PageNumber.TOTAL_PAGES], size })
|
|
223
|
-
] : [current];
|
|
224
|
-
const paragraph2 = new import_docx.Paragraph({ alignment, children: runs });
|
|
225
|
-
return page.position.startsWith("top-") ? { headers: { default: new import_docx.Header({ children: [paragraph2] }) } } : { footers: { default: new import_docx.Footer({ children: [paragraph2] }) } };
|
|
226
|
-
}
|
|
227
|
-
function block(node, options, context) {
|
|
228
|
-
if (node.type === "heading") {
|
|
229
|
-
const runSize = options.pagination.gridMode === "strict" ? bodyFontSizeHalfPoints(options) : headingFontSize(node.depth);
|
|
230
|
-
return [
|
|
231
|
-
new import_docx.Paragraph({
|
|
232
|
-
heading: [
|
|
233
|
-
import_docx.HeadingLevel.HEADING_1,
|
|
234
|
-
import_docx.HeadingLevel.HEADING_2,
|
|
235
|
-
import_docx.HeadingLevel.HEADING_3,
|
|
236
|
-
import_docx.HeadingLevel.HEADING_4,
|
|
237
|
-
import_docx.HeadingLevel.HEADING_5,
|
|
238
|
-
import_docx.HeadingLevel.HEADING_6
|
|
239
|
-
][node.depth - 1],
|
|
240
|
-
children: inline(node.children, context, runSize)
|
|
241
|
-
})
|
|
242
|
-
];
|
|
243
|
-
}
|
|
244
|
-
if (node.type === "paragraph") return [paragraph(node.children, options, void 0, context)];
|
|
245
|
-
if (node.type === "list")
|
|
246
|
-
return node.children.flatMap(
|
|
247
|
-
(item) => item.children.filter((n) => n.type === "paragraph").map(
|
|
248
|
-
(n) => new import_docx.Paragraph({
|
|
249
|
-
style: "MdiList",
|
|
250
|
-
children: inline(n.children, context),
|
|
251
|
-
bullet: { level: 0 }
|
|
252
|
-
})
|
|
253
|
-
)
|
|
254
|
-
);
|
|
255
|
-
if (node.type === "blockquote")
|
|
256
|
-
return node.children.flatMap(
|
|
257
|
-
(child) => child.type === "paragraph" ? [paragraph(child.children, options, "MdiQuote", context)] : block(child, options, context)
|
|
258
|
-
);
|
|
259
|
-
if (node.type === "code")
|
|
260
|
-
return [
|
|
261
|
-
new import_docx.Paragraph({
|
|
262
|
-
style: "MdiCode",
|
|
263
|
-
children: node.value.split("\n").map(
|
|
264
|
-
(line, index) => new import_docx.TextRun({
|
|
265
|
-
text: line,
|
|
266
|
-
break: index === 0 ? void 0 : 1,
|
|
267
|
-
font: "Courier New"
|
|
268
|
-
})
|
|
269
|
-
)
|
|
270
|
-
})
|
|
271
|
-
];
|
|
272
|
-
if (node.type === "thematicBreak")
|
|
273
|
-
return [
|
|
274
|
-
new import_docx.Paragraph({
|
|
275
|
-
style: "MdiThematicBreak",
|
|
276
|
-
alignment: import_docx.AlignmentType.CENTER,
|
|
277
|
-
children: [new import_docx.TextRun("\u2014 \u2014 \u2014")]
|
|
278
|
-
})
|
|
279
|
-
];
|
|
280
|
-
if (node.type === "table") return [tableBlock(node, options, context)];
|
|
281
|
-
if (node.type === "mdiPagebreak")
|
|
282
|
-
return [new import_docx.Paragraph({ children: [new import_docx.PageBreak()] })];
|
|
283
|
-
if (node.type === "mdiBlank") return [new import_docx.Paragraph("")];
|
|
284
|
-
return [];
|
|
285
|
-
}
|
|
286
|
-
function tableBlock(node, options, context) {
|
|
287
|
-
const columns = Math.max(1, ...node.children.map((row) => row.children.length));
|
|
288
|
-
const dimensions = import_mdi_export_profile.PAGE_DIMENSIONS[options.pagination.pageSize];
|
|
289
|
-
const pageWidth = options.pagination.landscape ? dimensions.height : dimensions.width;
|
|
290
|
-
const usableWidth = mmToTwips(
|
|
291
|
-
pageWidth - options.pagination.margins.left - options.pagination.margins.right
|
|
292
|
-
);
|
|
293
|
-
const columnWidth = Math.floor(usableWidth / columns);
|
|
294
|
-
return new import_docx.Table({
|
|
295
|
-
width: { size: usableWidth, type: import_docx.WidthType.DXA },
|
|
296
|
-
columnWidths: Array.from({ length: columns }, () => columnWidth),
|
|
297
|
-
layout: import_docx.TableLayoutType.FIXED,
|
|
298
|
-
margins: { top: 80, bottom: 80, left: 100, right: 100 },
|
|
299
|
-
rows: node.children.map(
|
|
300
|
-
(row, rowIndex) => new import_docx.TableRow({
|
|
301
|
-
children: row.children.map(
|
|
302
|
-
(cell) => new import_docx.TableCell({
|
|
303
|
-
width: { size: columnWidth, type: import_docx.WidthType.DXA },
|
|
304
|
-
children: [
|
|
305
|
-
new import_docx.Paragraph({
|
|
306
|
-
children: rowIndex === 0 ? [new import_docx.TextRun({ text: inlineText(cell.children), bold: true })] : inline(cell.children, context)
|
|
307
|
-
})
|
|
308
|
-
]
|
|
309
|
-
})
|
|
310
|
-
)
|
|
311
|
-
})
|
|
312
|
-
)
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
function paragraph(nodes, options, style, context) {
|
|
316
|
-
const fontSizeMm = options.typesetting.fontSize / 72 * 25.4;
|
|
317
|
-
const prefix = options.typesetting.fullwidthSpaceIndent ? [new import_docx.TextRun("\u3000".repeat(Math.round(options.typesetting.textIndentEm)))] : [];
|
|
318
|
-
return new import_docx.Paragraph({
|
|
319
|
-
style,
|
|
320
|
-
indent: options.typesetting.fullwidthSpaceIndent ? void 0 : {
|
|
321
|
-
...style ? {} : {
|
|
322
|
-
firstLine: Math.round(
|
|
323
|
-
options.typesetting.textIndentEm * fontSizeMm / 25.4 * 1440
|
|
324
|
-
)
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
children: [...prefix, ...inline(nodes, context)]
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
function inline(nodes, context, rubyBaseTextSize = 24) {
|
|
331
|
-
return nodes.flatMap((node) => {
|
|
332
|
-
if (node.type === "text") return [new import_docx.TextRun(node.value)];
|
|
333
|
-
if (node.type === "break" || node.type === "mdiBreak")
|
|
334
|
-
return [new import_docx.TextRun({ break: 1 })];
|
|
335
|
-
if (node.type === "emphasis")
|
|
336
|
-
return [new import_docx.TextRun({ italics: true, children: inline(node.children, context, rubyBaseTextSize) })];
|
|
337
|
-
if (node.type === "strong")
|
|
338
|
-
return [new import_docx.TextRun({ bold: true, children: inline(node.children, context, rubyBaseTextSize) })];
|
|
339
|
-
if (node.type === "delete")
|
|
340
|
-
return [new import_docx.TextRun({ strike: true, children: inline(node.children, context, rubyBaseTextSize) })];
|
|
341
|
-
if (node.type === "inlineCode")
|
|
342
|
-
return [new import_docx.TextRun({ text: node.value, font: "Courier New" })];
|
|
343
|
-
if (node.type === "image")
|
|
344
|
-
return [new import_docx.TextRun({ text: node.alt ? `[Image: ${node.alt}]` : "[Image]" })];
|
|
345
|
-
if (node.type === "link")
|
|
346
|
-
return [
|
|
347
|
-
new import_docx.ExternalHyperlink({
|
|
348
|
-
link: node.url,
|
|
349
|
-
children: inline(node.children, context, rubyBaseTextSize)
|
|
350
|
-
})
|
|
351
|
-
];
|
|
352
|
-
if (node.type === "footnoteReference") {
|
|
353
|
-
const id = context.footnoteIds.get(node.identifier);
|
|
354
|
-
return id ? [new import_docx.FootnoteReferenceRun(id)] : [];
|
|
355
|
-
}
|
|
356
|
-
if (node.type === "mdiEm")
|
|
357
|
-
return [
|
|
358
|
-
new import_docx.TextRun({
|
|
359
|
-
emphasisMark: { type: "dot" },
|
|
360
|
-
children: inline(node.children, context, rubyBaseTextSize)
|
|
361
|
-
})
|
|
362
|
-
];
|
|
363
|
-
if (node.type === "mdiRuby") return [rawRun(rubyXml(node.base, node.ruby, rubyBaseTextSize))];
|
|
364
|
-
if (node.type === "mdiTcy")
|
|
365
|
-
return [
|
|
366
|
-
rawRun(
|
|
367
|
-
`<w:r><w:rPr><w:eastAsianLayout w:combine="1" w:combineBrackets="none"/></w:rPr><w:t>${xml(
|
|
368
|
-
node.value
|
|
369
|
-
)}</w:t></w:r>`
|
|
370
|
-
)
|
|
371
|
-
];
|
|
372
|
-
if (node.type === "mdiWarichu")
|
|
373
|
-
return [
|
|
374
|
-
new import_docx.TextRun({
|
|
375
|
-
size: Math.max(10, Math.round(rubyBaseTextSize * 0.6)),
|
|
376
|
-
children: inline(node.children, context, rubyBaseTextSize)
|
|
377
|
-
})
|
|
378
|
-
];
|
|
379
|
-
if (node.type === "mdiKern") {
|
|
380
|
-
const spacing = kernTwips(node.amount, rubyBaseTextSize);
|
|
381
|
-
return [
|
|
382
|
-
new import_docx.TextRun({
|
|
383
|
-
...spacing === 0 ? {} : { characterSpacing: spacing },
|
|
384
|
-
children: inline(node.children, context, rubyBaseTextSize)
|
|
385
|
-
})
|
|
386
|
-
];
|
|
387
|
-
}
|
|
388
|
-
if (node.type === "mdiNoBreak")
|
|
389
|
-
return inline(node.children, context, rubyBaseTextSize);
|
|
390
|
-
if ("children" in node) return inline(node.children, context, rubyBaseTextSize);
|
|
391
|
-
return [];
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
function kernTwips(amount, baseTextSize) {
|
|
395
|
-
const value = Number.parseFloat(amount);
|
|
396
|
-
if (!Number.isFinite(value) || !amount.endsWith("em")) return 0;
|
|
397
|
-
return Math.round(value * baseTextSize * 10);
|
|
398
|
-
}
|
|
399
|
-
function inlineText(nodes) {
|
|
400
|
-
return nodes.map((node) => node.type === "text" ? node.value : "children" in node ? inlineText(node.children) : "").join("");
|
|
401
|
-
}
|
|
402
|
-
function mmToTwips(mm) {
|
|
403
|
-
return Math.round(mm / 25.4 * 1440);
|
|
404
|
-
}
|
|
405
|
-
function bodyFontSizeHalfPoints(options) {
|
|
406
|
-
return Math.round(options.typesetting.fontSize * 2);
|
|
407
|
-
}
|
|
408
|
-
function rubyXml(base, reading, baseTextSize = 24) {
|
|
409
|
-
const text = Array.isArray(reading) ? reading.join(".") : reading;
|
|
410
|
-
const rubySize = Math.max(10, Math.round(baseTextSize / 2));
|
|
411
|
-
const rubyRaise = baseTextSize === 24 ? 18 : Math.max(18, Math.round(baseTextSize * 0.8));
|
|
412
|
-
return `<w:ruby><w:rubyPr><w:rubyAlign w:val="center"/><w:hps w:val="${rubySize}"/><w:hpsRaise w:val="${rubyRaise}"/><w:hpsBaseText w:val="${baseTextSize}"/></w:rubyPr><w:rt><w:r><w:t>${xml(
|
|
413
|
-
text
|
|
414
|
-
)}</w:t></w:r></w:rt><w:rubyBase><w:r><w:t>${xml(
|
|
415
|
-
base
|
|
416
|
-
)}</w:t></w:r></w:rubyBase></w:ruby>`;
|
|
417
|
-
}
|
|
418
|
-
function rawRun(xmlString) {
|
|
419
|
-
const namespaced = xmlString.replace(
|
|
420
|
-
/^<w:([\w-]+)/,
|
|
421
|
-
'<w:$1 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'
|
|
422
|
-
);
|
|
423
|
-
const imported = import_docx.ImportedXmlComponent.fromXmlString(namespaced);
|
|
424
|
-
return imported.root[0];
|
|
425
|
-
}
|
|
426
|
-
function xml(value) {
|
|
427
|
-
return value.replaceAll("&", "&").replaceAll("<", "<");
|
|
428
38
|
}
|
|
429
39
|
// Annotate the CommonJS export names for ESM import in node:
|
|
430
40
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { Root } from 'mdast';
|
|
2
1
|
import { ExportProfile } from '@illusions-lab/mdi-export-profile';
|
|
2
|
+
import { Root } from 'mdast';
|
|
3
3
|
|
|
4
|
-
declare module "mdast" {
|
|
5
|
-
interface RootData {
|
|
6
|
-
frontmatter?: {
|
|
7
|
-
title?: string;
|
|
8
|
-
author?: string;
|
|
9
|
-
writingMode: "horizontal" | "vertical";
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
4
|
declare const MDI_SPEC_VERSION = "2.0";
|
|
14
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Preserve the mdast-facing package API while delegating OOXML generation to
|
|
7
|
+
* the single Rust implementation used by every language binding.
|
|
8
|
+
*/
|
|
15
9
|
declare function mdiToDocx(tree: Root, profile?: ExportProfile): Promise<Buffer>;
|
|
16
10
|
|
|
17
11
|
export { MDI_SPEC_VERSION, mdiToDocx };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { Root } from 'mdast';
|
|
2
1
|
import { ExportProfile } from '@illusions-lab/mdi-export-profile';
|
|
2
|
+
import { Root } from 'mdast';
|
|
3
3
|
|
|
4
|
-
declare module "mdast" {
|
|
5
|
-
interface RootData {
|
|
6
|
-
frontmatter?: {
|
|
7
|
-
title?: string;
|
|
8
|
-
author?: string;
|
|
9
|
-
writingMode: "horizontal" | "vertical";
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
4
|
declare const MDI_SPEC_VERSION = "2.0";
|
|
14
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Preserve the mdast-facing package API while delegating OOXML generation to
|
|
7
|
+
* the single Rust implementation used by every language binding.
|
|
8
|
+
*/
|
|
15
9
|
declare function mdiToDocx(tree: Root, profile?: ExportProfile): Promise<Buffer>;
|
|
16
10
|
|
|
17
11
|
export { MDI_SPEC_VERSION, mdiToDocx };
|
package/dist/index.js
CHANGED
|
@@ -1,428 +1,15 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
Document,
|
|
5
|
-
ExternalHyperlink,
|
|
6
|
-
Footer,
|
|
7
|
-
FootnoteReferenceRun,
|
|
8
|
-
HeadingLevel,
|
|
9
|
-
Header,
|
|
10
|
-
ImportedXmlComponent,
|
|
11
|
-
Packer,
|
|
12
|
-
PageBreak,
|
|
13
|
-
PageNumber,
|
|
14
|
-
PageTextDirectionType,
|
|
15
|
-
Paragraph,
|
|
16
|
-
Table as DocxTable,
|
|
17
|
-
TableCell,
|
|
18
|
-
TableLayoutType,
|
|
19
|
-
TableRow,
|
|
20
|
-
TextRun,
|
|
21
|
-
WidthType
|
|
22
|
-
} from "docx";
|
|
23
|
-
import {
|
|
24
|
-
PAGE_DIMENSIONS,
|
|
25
|
-
resolvePrintProfile
|
|
26
|
-
} from "@illusions-lab/mdi-export-profile";
|
|
2
|
+
import { renderDocxWithProfile } from "@illusions-lab/mdi-core";
|
|
3
|
+
import { mdastToMdiSource } from "mdast-util-mdi";
|
|
27
4
|
var MDI_SPEC_VERSION = "2.0";
|
|
28
5
|
async function mdiToDocx(tree, profile) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
6
|
+
if (!tree || tree.type !== "root" || !Array.isArray(tree.children))
|
|
7
|
+
throw new TypeError("tree must be an mdast root");
|
|
8
|
+
if (profile !== void 0 && (!profile || typeof profile !== "object" || Array.isArray(profile)))
|
|
9
|
+
throw new TypeError("profile must be an object");
|
|
10
|
+
return Buffer.from(
|
|
11
|
+
renderDocxWithProfile(mdastToMdiSource(tree), JSON.stringify(profile ?? {}))
|
|
32
12
|
);
|
|
33
|
-
const definitions = tree.children.filter(
|
|
34
|
-
(node) => node.type === "footnoteDefinition"
|
|
35
|
-
);
|
|
36
|
-
const footnoteIds = new Map(
|
|
37
|
-
definitions.map((definition, index) => [definition.identifier, index + 1])
|
|
38
|
-
);
|
|
39
|
-
const context = { footnoteIds };
|
|
40
|
-
const children = tree.children.flatMap((node) => block(node, options, context));
|
|
41
|
-
const dimensions = PAGE_DIMENSIONS[options.pagination.pageSize];
|
|
42
|
-
const widthMm = options.pagination.landscape ? dimensions.height : dimensions.width;
|
|
43
|
-
const heightMm = options.pagination.landscape ? dimensions.width : dimensions.height;
|
|
44
|
-
const primary = options.typesetting.writingMode === "vertical" ? heightMm - options.pagination.margins.top - options.pagination.margins.bottom : widthMm - options.pagination.margins.left - options.pagination.margins.right;
|
|
45
|
-
const cross = options.typesetting.writingMode === "vertical" ? widthMm - options.pagination.margins.left - options.pagination.margins.right : heightMm - options.pagination.margins.top - options.pagination.margins.bottom;
|
|
46
|
-
const fontSizeMm = options.typesetting.fontSize / 72 * 25.4;
|
|
47
|
-
const fontSizeHalfPoints = Math.round(fontSizeMm / 25.4 * 72 * 2);
|
|
48
|
-
const strictGrid = options.pagination.gridMode === "strict";
|
|
49
|
-
const characterPitchPoints = primary / options.pagination.charactersPerLine / 25.4 * 72;
|
|
50
|
-
const characterSpace = Math.max(
|
|
51
|
-
0,
|
|
52
|
-
Math.round((characterPitchPoints - fontSizeHalfPoints / 2) * 4096)
|
|
53
|
-
);
|
|
54
|
-
const lineTwips = options.typesetting.lineSpacing === void 0 ? Math.round(cross / options.pagination.linesPerPage / 25.4 * 1440) : Math.round(fontSizeHalfPoints * 10 * options.typesetting.lineSpacing);
|
|
55
|
-
const document = new Document({
|
|
56
|
-
title: options.metadata.title ?? tree.data?.frontmatter?.title,
|
|
57
|
-
creator: options.metadata.author ?? tree.data?.frontmatter?.author,
|
|
58
|
-
footnotes: Object.fromEntries(
|
|
59
|
-
definitions.map((definition, index) => [
|
|
60
|
-
String(index + 1),
|
|
61
|
-
{
|
|
62
|
-
children: definition.children.flatMap(
|
|
63
|
-
(child) => child.type === "paragraph" ? [new Paragraph({ children: inline(child.children, context) })] : []
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
])
|
|
67
|
-
),
|
|
68
|
-
styles: {
|
|
69
|
-
default: {
|
|
70
|
-
document: {
|
|
71
|
-
run: {
|
|
72
|
-
font: options.typesetting.fontFamily,
|
|
73
|
-
size: fontSizeHalfPoints,
|
|
74
|
-
color: "000000"
|
|
75
|
-
},
|
|
76
|
-
paragraph: { spacing: { line: lineTwips, after: strictGrid ? 0 : 120 } }
|
|
77
|
-
},
|
|
78
|
-
heading1: headingStyle(options, 1, fontSizeHalfPoints),
|
|
79
|
-
heading2: headingStyle(options, 2, fontSizeHalfPoints),
|
|
80
|
-
heading3: headingStyle(options, 3, fontSizeHalfPoints),
|
|
81
|
-
heading4: headingStyle(options, 4, fontSizeHalfPoints),
|
|
82
|
-
heading5: headingStyle(options, 5, fontSizeHalfPoints),
|
|
83
|
-
heading6: headingStyle(options, 6, fontSizeHalfPoints),
|
|
84
|
-
listParagraph: {
|
|
85
|
-
run: { font: options.typesetting.fontFamily, color: "000000" }
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
paragraphStyles: [
|
|
89
|
-
customHeadingStyle(options, 7, fontSizeHalfPoints),
|
|
90
|
-
customHeadingStyle(options, 8, fontSizeHalfPoints),
|
|
91
|
-
customHeadingStyle(options, 9, fontSizeHalfPoints),
|
|
92
|
-
{
|
|
93
|
-
id: "MdiQuote",
|
|
94
|
-
name: "MDI Quote",
|
|
95
|
-
basedOn: "Normal",
|
|
96
|
-
run: { font: options.typesetting.fontFamily, color: "000000", italics: true },
|
|
97
|
-
paragraph: {
|
|
98
|
-
indent: { left: 720, right: 360 },
|
|
99
|
-
spacing: { before: strictGrid ? 0 : 120, after: strictGrid ? 0 : 120, line: lineTwips }
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: "MdiList",
|
|
104
|
-
name: "MDI List",
|
|
105
|
-
basedOn: "ListParagraph",
|
|
106
|
-
run: { font: options.typesetting.fontFamily, color: "000000" },
|
|
107
|
-
paragraph: { spacing: { after: strictGrid ? 0 : 60, line: lineTwips } }
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
id: "MdiCode",
|
|
111
|
-
name: "MDI Code Block",
|
|
112
|
-
basedOn: "Normal",
|
|
113
|
-
run: { font: "Courier New", color: "000000" },
|
|
114
|
-
paragraph: {
|
|
115
|
-
indent: { left: 360, right: 360 },
|
|
116
|
-
spacing: { before: strictGrid ? 0 : 120, after: strictGrid ? 0 : 120, line: lineTwips }
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
id: "MdiThematicBreak",
|
|
121
|
-
name: "MDI Thematic Break",
|
|
122
|
-
basedOn: "Normal",
|
|
123
|
-
run: { font: options.typesetting.fontFamily, color: "000000" },
|
|
124
|
-
paragraph: { spacing: { before: strictGrid ? 0 : 120, after: strictGrid ? 0 : 180, line: lineTwips } }
|
|
125
|
-
}
|
|
126
|
-
]
|
|
127
|
-
},
|
|
128
|
-
sections: [
|
|
129
|
-
{
|
|
130
|
-
...pageNumberSection(options, fontSizeHalfPoints),
|
|
131
|
-
properties: {
|
|
132
|
-
page: {
|
|
133
|
-
size: { width: mmToTwips(widthMm), height: mmToTwips(heightMm) },
|
|
134
|
-
margin: Object.fromEntries(
|
|
135
|
-
Object.entries(options.pagination.margins).map(([key, value]) => [
|
|
136
|
-
key,
|
|
137
|
-
mmToTwips(value)
|
|
138
|
-
])
|
|
139
|
-
),
|
|
140
|
-
...options.pagination.pageNumbers.enabled ? { pageNumbers: { start: 1 } } : {},
|
|
141
|
-
...options.typesetting.writingMode === "vertical" ? {
|
|
142
|
-
textDirection: PageTextDirectionType.TOP_TO_BOTTOM_RIGHT_TO_LEFT
|
|
143
|
-
} : {}
|
|
144
|
-
},
|
|
145
|
-
// Word's document grid is the portable OOXML mechanism that records
|
|
146
|
-
// a publisher's characters × lines contract. `linePitch` comes from
|
|
147
|
-
// the resolved physical block extent; the matching PDF profile uses
|
|
148
|
-
// the same resolved page geometry.
|
|
149
|
-
...strictGrid ? {
|
|
150
|
-
grid: {
|
|
151
|
-
type: "linesAndChars",
|
|
152
|
-
...characterSpace === 0 ? {} : { charSpace: characterSpace },
|
|
153
|
-
linePitch: lineTwips
|
|
154
|
-
}
|
|
155
|
-
} : {}
|
|
156
|
-
},
|
|
157
|
-
children
|
|
158
|
-
}
|
|
159
|
-
]
|
|
160
|
-
});
|
|
161
|
-
addBookMarginSettings(document, options);
|
|
162
|
-
return Packer.toBuffer(document);
|
|
163
|
-
}
|
|
164
|
-
function addBookMarginSettings(document, options) {
|
|
165
|
-
if (options.layout.marginMode !== "mirror") return;
|
|
166
|
-
const namespace = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"';
|
|
167
|
-
document.Settings.addChildElement(
|
|
168
|
-
ImportedXmlComponent.fromXmlString(`<w:mirrorMargins ${namespace}/>`)
|
|
169
|
-
);
|
|
170
|
-
if (options.layout.bindingSide === "right")
|
|
171
|
-
document.Settings.addChildElement(
|
|
172
|
-
ImportedXmlComponent.fromXmlString(`<w:rtlGutter ${namespace}/>`)
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
function headingStyle(options, level, bodyFontSizeHalfPoints2 = 22) {
|
|
176
|
-
const before = [360, 300, 240, 180, 150, 120][level - 1] ?? 120;
|
|
177
|
-
const strictGrid = options.pagination.gridMode === "strict";
|
|
178
|
-
return {
|
|
179
|
-
run: {
|
|
180
|
-
font: options.typesetting.fontFamily,
|
|
181
|
-
color: "000000",
|
|
182
|
-
bold: true,
|
|
183
|
-
size: strictGrid ? bodyFontSizeHalfPoints2 : headingFontSize(level, bodyFontSizeHalfPoints2)
|
|
184
|
-
},
|
|
185
|
-
paragraph: {
|
|
186
|
-
spacing: { before: strictGrid ? 0 : before, after: strictGrid ? 0 : 120 },
|
|
187
|
-
keepNext: true,
|
|
188
|
-
keepLines: true,
|
|
189
|
-
outlineLevel: level - 1
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
function headingFontSize(level, bodyFontSizeHalfPoints2 = 22) {
|
|
194
|
-
const scale = [1.8, 1.55, 1.35, 1.2, 1.1, 1][level - 1] ?? 1;
|
|
195
|
-
return Math.round(bodyFontSizeHalfPoints2 * scale);
|
|
196
|
-
}
|
|
197
|
-
function customHeadingStyle(options, level, bodyFontSizeHalfPoints2 = 22) {
|
|
198
|
-
const style = headingStyle(options, level, bodyFontSizeHalfPoints2);
|
|
199
|
-
return {
|
|
200
|
-
id: `Heading${level}`,
|
|
201
|
-
name: `Heading ${level}`,
|
|
202
|
-
basedOn: "Normal",
|
|
203
|
-
next: "Normal",
|
|
204
|
-
quickFormat: true,
|
|
205
|
-
...style
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
function pageNumberSection(options, size) {
|
|
209
|
-
const page = options.pagination.pageNumbers;
|
|
210
|
-
if (!page.enabled) return {};
|
|
211
|
-
const alignment = page.position.endsWith("left") ? AlignmentType.LEFT : page.position.endsWith("right") ? AlignmentType.RIGHT : AlignmentType.CENTER;
|
|
212
|
-
const current = new TextRun({ children: [PageNumber.CURRENT], size });
|
|
213
|
-
const runs = page.format === "dash" ? [
|
|
214
|
-
new TextRun({ text: "\u2014 ", size }),
|
|
215
|
-
current,
|
|
216
|
-
new TextRun({ text: " \u2014", size })
|
|
217
|
-
] : page.format === "fraction" ? [
|
|
218
|
-
current,
|
|
219
|
-
new TextRun({ text: " / ", size }),
|
|
220
|
-
new TextRun({ children: [PageNumber.TOTAL_PAGES], size })
|
|
221
|
-
] : [current];
|
|
222
|
-
const paragraph2 = new Paragraph({ alignment, children: runs });
|
|
223
|
-
return page.position.startsWith("top-") ? { headers: { default: new Header({ children: [paragraph2] }) } } : { footers: { default: new Footer({ children: [paragraph2] }) } };
|
|
224
|
-
}
|
|
225
|
-
function block(node, options, context) {
|
|
226
|
-
if (node.type === "heading") {
|
|
227
|
-
const runSize = options.pagination.gridMode === "strict" ? bodyFontSizeHalfPoints(options) : headingFontSize(node.depth);
|
|
228
|
-
return [
|
|
229
|
-
new Paragraph({
|
|
230
|
-
heading: [
|
|
231
|
-
HeadingLevel.HEADING_1,
|
|
232
|
-
HeadingLevel.HEADING_2,
|
|
233
|
-
HeadingLevel.HEADING_3,
|
|
234
|
-
HeadingLevel.HEADING_4,
|
|
235
|
-
HeadingLevel.HEADING_5,
|
|
236
|
-
HeadingLevel.HEADING_6
|
|
237
|
-
][node.depth - 1],
|
|
238
|
-
children: inline(node.children, context, runSize)
|
|
239
|
-
})
|
|
240
|
-
];
|
|
241
|
-
}
|
|
242
|
-
if (node.type === "paragraph") return [paragraph(node.children, options, void 0, context)];
|
|
243
|
-
if (node.type === "list")
|
|
244
|
-
return node.children.flatMap(
|
|
245
|
-
(item) => item.children.filter((n) => n.type === "paragraph").map(
|
|
246
|
-
(n) => new Paragraph({
|
|
247
|
-
style: "MdiList",
|
|
248
|
-
children: inline(n.children, context),
|
|
249
|
-
bullet: { level: 0 }
|
|
250
|
-
})
|
|
251
|
-
)
|
|
252
|
-
);
|
|
253
|
-
if (node.type === "blockquote")
|
|
254
|
-
return node.children.flatMap(
|
|
255
|
-
(child) => child.type === "paragraph" ? [paragraph(child.children, options, "MdiQuote", context)] : block(child, options, context)
|
|
256
|
-
);
|
|
257
|
-
if (node.type === "code")
|
|
258
|
-
return [
|
|
259
|
-
new Paragraph({
|
|
260
|
-
style: "MdiCode",
|
|
261
|
-
children: node.value.split("\n").map(
|
|
262
|
-
(line, index) => new TextRun({
|
|
263
|
-
text: line,
|
|
264
|
-
break: index === 0 ? void 0 : 1,
|
|
265
|
-
font: "Courier New"
|
|
266
|
-
})
|
|
267
|
-
)
|
|
268
|
-
})
|
|
269
|
-
];
|
|
270
|
-
if (node.type === "thematicBreak")
|
|
271
|
-
return [
|
|
272
|
-
new Paragraph({
|
|
273
|
-
style: "MdiThematicBreak",
|
|
274
|
-
alignment: AlignmentType.CENTER,
|
|
275
|
-
children: [new TextRun("\u2014 \u2014 \u2014")]
|
|
276
|
-
})
|
|
277
|
-
];
|
|
278
|
-
if (node.type === "table") return [tableBlock(node, options, context)];
|
|
279
|
-
if (node.type === "mdiPagebreak")
|
|
280
|
-
return [new Paragraph({ children: [new PageBreak()] })];
|
|
281
|
-
if (node.type === "mdiBlank") return [new Paragraph("")];
|
|
282
|
-
return [];
|
|
283
|
-
}
|
|
284
|
-
function tableBlock(node, options, context) {
|
|
285
|
-
const columns = Math.max(1, ...node.children.map((row) => row.children.length));
|
|
286
|
-
const dimensions = PAGE_DIMENSIONS[options.pagination.pageSize];
|
|
287
|
-
const pageWidth = options.pagination.landscape ? dimensions.height : dimensions.width;
|
|
288
|
-
const usableWidth = mmToTwips(
|
|
289
|
-
pageWidth - options.pagination.margins.left - options.pagination.margins.right
|
|
290
|
-
);
|
|
291
|
-
const columnWidth = Math.floor(usableWidth / columns);
|
|
292
|
-
return new DocxTable({
|
|
293
|
-
width: { size: usableWidth, type: WidthType.DXA },
|
|
294
|
-
columnWidths: Array.from({ length: columns }, () => columnWidth),
|
|
295
|
-
layout: TableLayoutType.FIXED,
|
|
296
|
-
margins: { top: 80, bottom: 80, left: 100, right: 100 },
|
|
297
|
-
rows: node.children.map(
|
|
298
|
-
(row, rowIndex) => new TableRow({
|
|
299
|
-
children: row.children.map(
|
|
300
|
-
(cell) => new TableCell({
|
|
301
|
-
width: { size: columnWidth, type: WidthType.DXA },
|
|
302
|
-
children: [
|
|
303
|
-
new Paragraph({
|
|
304
|
-
children: rowIndex === 0 ? [new TextRun({ text: inlineText(cell.children), bold: true })] : inline(cell.children, context)
|
|
305
|
-
})
|
|
306
|
-
]
|
|
307
|
-
})
|
|
308
|
-
)
|
|
309
|
-
})
|
|
310
|
-
)
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
function paragraph(nodes, options, style, context) {
|
|
314
|
-
const fontSizeMm = options.typesetting.fontSize / 72 * 25.4;
|
|
315
|
-
const prefix = options.typesetting.fullwidthSpaceIndent ? [new TextRun("\u3000".repeat(Math.round(options.typesetting.textIndentEm)))] : [];
|
|
316
|
-
return new Paragraph({
|
|
317
|
-
style,
|
|
318
|
-
indent: options.typesetting.fullwidthSpaceIndent ? void 0 : {
|
|
319
|
-
...style ? {} : {
|
|
320
|
-
firstLine: Math.round(
|
|
321
|
-
options.typesetting.textIndentEm * fontSizeMm / 25.4 * 1440
|
|
322
|
-
)
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
children: [...prefix, ...inline(nodes, context)]
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
function inline(nodes, context, rubyBaseTextSize = 24) {
|
|
329
|
-
return nodes.flatMap((node) => {
|
|
330
|
-
if (node.type === "text") return [new TextRun(node.value)];
|
|
331
|
-
if (node.type === "break" || node.type === "mdiBreak")
|
|
332
|
-
return [new TextRun({ break: 1 })];
|
|
333
|
-
if (node.type === "emphasis")
|
|
334
|
-
return [new TextRun({ italics: true, children: inline(node.children, context, rubyBaseTextSize) })];
|
|
335
|
-
if (node.type === "strong")
|
|
336
|
-
return [new TextRun({ bold: true, children: inline(node.children, context, rubyBaseTextSize) })];
|
|
337
|
-
if (node.type === "delete")
|
|
338
|
-
return [new TextRun({ strike: true, children: inline(node.children, context, rubyBaseTextSize) })];
|
|
339
|
-
if (node.type === "inlineCode")
|
|
340
|
-
return [new TextRun({ text: node.value, font: "Courier New" })];
|
|
341
|
-
if (node.type === "image")
|
|
342
|
-
return [new TextRun({ text: node.alt ? `[Image: ${node.alt}]` : "[Image]" })];
|
|
343
|
-
if (node.type === "link")
|
|
344
|
-
return [
|
|
345
|
-
new ExternalHyperlink({
|
|
346
|
-
link: node.url,
|
|
347
|
-
children: inline(node.children, context, rubyBaseTextSize)
|
|
348
|
-
})
|
|
349
|
-
];
|
|
350
|
-
if (node.type === "footnoteReference") {
|
|
351
|
-
const id = context.footnoteIds.get(node.identifier);
|
|
352
|
-
return id ? [new FootnoteReferenceRun(id)] : [];
|
|
353
|
-
}
|
|
354
|
-
if (node.type === "mdiEm")
|
|
355
|
-
return [
|
|
356
|
-
new TextRun({
|
|
357
|
-
emphasisMark: { type: "dot" },
|
|
358
|
-
children: inline(node.children, context, rubyBaseTextSize)
|
|
359
|
-
})
|
|
360
|
-
];
|
|
361
|
-
if (node.type === "mdiRuby") return [rawRun(rubyXml(node.base, node.ruby, rubyBaseTextSize))];
|
|
362
|
-
if (node.type === "mdiTcy")
|
|
363
|
-
return [
|
|
364
|
-
rawRun(
|
|
365
|
-
`<w:r><w:rPr><w:eastAsianLayout w:combine="1" w:combineBrackets="none"/></w:rPr><w:t>${xml(
|
|
366
|
-
node.value
|
|
367
|
-
)}</w:t></w:r>`
|
|
368
|
-
)
|
|
369
|
-
];
|
|
370
|
-
if (node.type === "mdiWarichu")
|
|
371
|
-
return [
|
|
372
|
-
new TextRun({
|
|
373
|
-
size: Math.max(10, Math.round(rubyBaseTextSize * 0.6)),
|
|
374
|
-
children: inline(node.children, context, rubyBaseTextSize)
|
|
375
|
-
})
|
|
376
|
-
];
|
|
377
|
-
if (node.type === "mdiKern") {
|
|
378
|
-
const spacing = kernTwips(node.amount, rubyBaseTextSize);
|
|
379
|
-
return [
|
|
380
|
-
new TextRun({
|
|
381
|
-
...spacing === 0 ? {} : { characterSpacing: spacing },
|
|
382
|
-
children: inline(node.children, context, rubyBaseTextSize)
|
|
383
|
-
})
|
|
384
|
-
];
|
|
385
|
-
}
|
|
386
|
-
if (node.type === "mdiNoBreak")
|
|
387
|
-
return inline(node.children, context, rubyBaseTextSize);
|
|
388
|
-
if ("children" in node) return inline(node.children, context, rubyBaseTextSize);
|
|
389
|
-
return [];
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
function kernTwips(amount, baseTextSize) {
|
|
393
|
-
const value = Number.parseFloat(amount);
|
|
394
|
-
if (!Number.isFinite(value) || !amount.endsWith("em")) return 0;
|
|
395
|
-
return Math.round(value * baseTextSize * 10);
|
|
396
|
-
}
|
|
397
|
-
function inlineText(nodes) {
|
|
398
|
-
return nodes.map((node) => node.type === "text" ? node.value : "children" in node ? inlineText(node.children) : "").join("");
|
|
399
|
-
}
|
|
400
|
-
function mmToTwips(mm) {
|
|
401
|
-
return Math.round(mm / 25.4 * 1440);
|
|
402
|
-
}
|
|
403
|
-
function bodyFontSizeHalfPoints(options) {
|
|
404
|
-
return Math.round(options.typesetting.fontSize * 2);
|
|
405
|
-
}
|
|
406
|
-
function rubyXml(base, reading, baseTextSize = 24) {
|
|
407
|
-
const text = Array.isArray(reading) ? reading.join(".") : reading;
|
|
408
|
-
const rubySize = Math.max(10, Math.round(baseTextSize / 2));
|
|
409
|
-
const rubyRaise = baseTextSize === 24 ? 18 : Math.max(18, Math.round(baseTextSize * 0.8));
|
|
410
|
-
return `<w:ruby><w:rubyPr><w:rubyAlign w:val="center"/><w:hps w:val="${rubySize}"/><w:hpsRaise w:val="${rubyRaise}"/><w:hpsBaseText w:val="${baseTextSize}"/></w:rubyPr><w:rt><w:r><w:t>${xml(
|
|
411
|
-
text
|
|
412
|
-
)}</w:t></w:r></w:rt><w:rubyBase><w:r><w:t>${xml(
|
|
413
|
-
base
|
|
414
|
-
)}</w:t></w:r></w:rubyBase></w:ruby>`;
|
|
415
|
-
}
|
|
416
|
-
function rawRun(xmlString) {
|
|
417
|
-
const namespaced = xmlString.replace(
|
|
418
|
-
/^<w:([\w-]+)/,
|
|
419
|
-
'<w:$1 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'
|
|
420
|
-
);
|
|
421
|
-
const imported = ImportedXmlComponent.fromXmlString(namespaced);
|
|
422
|
-
return imported.root[0];
|
|
423
|
-
}
|
|
424
|
-
function xml(value) {
|
|
425
|
-
return value.replaceAll("&", "&").replaceAll("<", "<");
|
|
426
13
|
}
|
|
427
14
|
export {
|
|
428
15
|
MDI_SPEC_VERSION,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@illusions-lab/mdi-to-docx",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.26",
|
|
4
4
|
"description": "Render MDI (.mdi) documents to DOCX",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"typecheck": "tsc --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@illusions-lab/mdi-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
31
|
+
"@illusions-lab/mdi-core": "^2.0.13",
|
|
32
|
+
"@illusions-lab/mdi-export-profile": "^2.0.18",
|
|
33
|
+
"mdast-util-mdi": "^2.0.21",
|
|
34
34
|
"@types/mdast": "^4.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|