@sofereditor/export-docx 0.1.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/LICENSE +661 -0
- package/README.md +19 -0
- package/dist/index.cjs +378 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +374 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/src/__tests__/docx.test.ts +94 -0
- package/src/docx.ts +514 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +11 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
documentToDocxBlob: () => documentToDocxBlob,
|
|
24
|
+
documentToDocxBuffer: () => documentToDocxBuffer
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/docx.ts
|
|
29
|
+
var import_core = require("@sofereditor/core");
|
|
30
|
+
var import_docx = require("docx");
|
|
31
|
+
var BULLET_REF = "ed-bullet";
|
|
32
|
+
var ORDERED_REF = "ed-ordered";
|
|
33
|
+
async function documentToDocxBlob(doc, options = {}) {
|
|
34
|
+
const built = buildDocument(normalize(doc), options);
|
|
35
|
+
return import_docx.Packer.toBlob(built);
|
|
36
|
+
}
|
|
37
|
+
async function documentToDocxBuffer(doc, options = {}) {
|
|
38
|
+
const built = buildDocument(normalize(doc), options);
|
|
39
|
+
const buf = await import_docx.Packer.toBuffer(built);
|
|
40
|
+
return buf instanceof Uint8Array ? buf : new Uint8Array(buf);
|
|
41
|
+
}
|
|
42
|
+
function normalize(doc) {
|
|
43
|
+
return (0, import_core.isLegacySerializedDocument)(doc) ? { blocks: doc } : doc;
|
|
44
|
+
}
|
|
45
|
+
function buildDocument(doc, options) {
|
|
46
|
+
const children = blocksToDocxChildren(doc);
|
|
47
|
+
const settings = doc.pageSettings ?? import_core.DEFAULT_PAGE_SETTINGS;
|
|
48
|
+
return new import_docx.Document({
|
|
49
|
+
creator: options.creator,
|
|
50
|
+
title: options.title,
|
|
51
|
+
numbering: {
|
|
52
|
+
config: [
|
|
53
|
+
{
|
|
54
|
+
reference: BULLET_REF,
|
|
55
|
+
levels: buildBulletLevels()
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
reference: ORDERED_REF,
|
|
59
|
+
levels: buildOrderedLevels()
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
sections: [
|
|
64
|
+
{
|
|
65
|
+
properties: sectionPropertiesFor(settings),
|
|
66
|
+
children
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function sectionPropertiesFor(settings) {
|
|
72
|
+
return {
|
|
73
|
+
page: {
|
|
74
|
+
size: {
|
|
75
|
+
width: (0, import_docx.convertMillimetersToTwip)((0, import_core.pxToMm)(settings.width)),
|
|
76
|
+
height: (0, import_docx.convertMillimetersToTwip)((0, import_core.pxToMm)(settings.height)),
|
|
77
|
+
orientation: import_docx.PageOrientation.PORTRAIT
|
|
78
|
+
},
|
|
79
|
+
margin: {
|
|
80
|
+
top: (0, import_docx.convertMillimetersToTwip)((0, import_core.pxToMm)(settings.marginTop)),
|
|
81
|
+
bottom: (0, import_docx.convertMillimetersToTwip)((0, import_core.pxToMm)(settings.marginBottom)),
|
|
82
|
+
left: (0, import_docx.convertMillimetersToTwip)((0, import_core.pxToMm)(settings.marginLeft)),
|
|
83
|
+
right: (0, import_docx.convertMillimetersToTwip)((0, import_core.pxToMm)(settings.marginRight))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function blocksToDocxChildren(doc) {
|
|
89
|
+
const out = [];
|
|
90
|
+
const blocks = doc.blocks;
|
|
91
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
92
|
+
const block = blocks[i];
|
|
93
|
+
switch (block.type) {
|
|
94
|
+
case "paragraph":
|
|
95
|
+
out.push(makeParagraph(block));
|
|
96
|
+
break;
|
|
97
|
+
case "heading":
|
|
98
|
+
out.push(makeHeading(block));
|
|
99
|
+
break;
|
|
100
|
+
case "blockquote":
|
|
101
|
+
out.push(makeBlockquote(block));
|
|
102
|
+
break;
|
|
103
|
+
case "codeBlock":
|
|
104
|
+
out.push(makeCodeBlock(block));
|
|
105
|
+
break;
|
|
106
|
+
case "listItem":
|
|
107
|
+
out.push(makeListItem(block));
|
|
108
|
+
break;
|
|
109
|
+
case "table":
|
|
110
|
+
out.push(makeTable(block));
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
out.push(makeParagraph(block));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
var ARIAL = { font: "Arial" };
|
|
119
|
+
function makeParagraph(block) {
|
|
120
|
+
return new import_docx.Paragraph({
|
|
121
|
+
alignment: alignFor(block.attrs.align),
|
|
122
|
+
bidirectional: block.attrs.dir === "rtl" ? true : void 0,
|
|
123
|
+
children: deltaToRuns(block.delta, ARIAL)
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function makeHeading(block) {
|
|
127
|
+
const level = clampHeadingLevel(block.attrs.level);
|
|
128
|
+
return new import_docx.Paragraph({
|
|
129
|
+
heading: HEADING_LEVELS[level - 1],
|
|
130
|
+
alignment: alignFor(block.attrs.align),
|
|
131
|
+
children: deltaToRuns(block.delta, ARIAL)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function makeBlockquote(block) {
|
|
135
|
+
return new import_docx.Paragraph({
|
|
136
|
+
alignment: alignFor(block.attrs.align),
|
|
137
|
+
indent: { left: 480 },
|
|
138
|
+
// ~0.25"
|
|
139
|
+
border: {
|
|
140
|
+
left: { color: "CBD5E1", space: 12, style: import_docx.BorderStyle.SINGLE, size: 12 }
|
|
141
|
+
},
|
|
142
|
+
children: deltaToRuns(block.delta, { ...ARIAL, italics: true })
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function makeCodeBlock(block) {
|
|
146
|
+
return new import_docx.Paragraph({
|
|
147
|
+
spacing: { before: 100, after: 100 },
|
|
148
|
+
shading: { type: import_docx.ShadingType.CLEAR, color: "auto", fill: "F1F5F9" },
|
|
149
|
+
children: deltaToRuns(block.delta, { font: "Consolas", size: 20 })
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function makeListItem(block) {
|
|
153
|
+
const kind = block.attrs.listKind === "ordered" ? "ordered" : "bullet";
|
|
154
|
+
const reference = kind === "ordered" ? ORDERED_REF : BULLET_REF;
|
|
155
|
+
const level = clampListLevel(block.attrs.listLevel);
|
|
156
|
+
return new import_docx.Paragraph({
|
|
157
|
+
numbering: { reference, level },
|
|
158
|
+
alignment: alignFor(block.attrs.align),
|
|
159
|
+
children: deltaToRuns(block.delta, ARIAL)
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function makeTable(block) {
|
|
163
|
+
const rows = typeof block.attrs.rows === "number" ? block.attrs.rows : 0;
|
|
164
|
+
const cols = typeof block.attrs.cols === "number" ? block.attrs.cols : 0;
|
|
165
|
+
const cells = block.cells ?? [];
|
|
166
|
+
const rowsOut = [];
|
|
167
|
+
for (let r = 0; r < rows; r++) {
|
|
168
|
+
const cellsOut = [];
|
|
169
|
+
for (let c = 0; c < cols; c++) {
|
|
170
|
+
const cell = cells[r * cols + c];
|
|
171
|
+
if (!cell) {
|
|
172
|
+
cellsOut.push(emptyCell());
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (cell.attrs?.covered) continue;
|
|
176
|
+
cellsOut.push(makeCell(cell));
|
|
177
|
+
}
|
|
178
|
+
rowsOut.push(new import_docx.TableRow({ children: cellsOut }));
|
|
179
|
+
}
|
|
180
|
+
return new import_docx.Table({
|
|
181
|
+
rows: rowsOut,
|
|
182
|
+
width: { size: 100, type: import_docx.WidthType.PERCENTAGE }
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function makeCell(cell) {
|
|
186
|
+
const span = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;
|
|
187
|
+
const rowSpan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;
|
|
188
|
+
return new import_docx.TableCell({
|
|
189
|
+
columnSpan: span,
|
|
190
|
+
rowSpan,
|
|
191
|
+
verticalAlign: import_docx.VerticalAlign.TOP,
|
|
192
|
+
children: [new import_docx.Paragraph({ children: deltaToRuns(cell.delta, ARIAL) })]
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function emptyCell() {
|
|
196
|
+
return new import_docx.TableCell({ children: [new import_docx.Paragraph("")] });
|
|
197
|
+
}
|
|
198
|
+
function deltaToRuns(delta, defaults = {}) {
|
|
199
|
+
if (delta.length === 0) return [new import_docx.TextRun("")];
|
|
200
|
+
const out = [];
|
|
201
|
+
for (const op of delta) {
|
|
202
|
+
if (typeof op.insert === "string") {
|
|
203
|
+
if (op.insert.length === 0) continue;
|
|
204
|
+
out.push(makeTextRun(op.insert, op.attributes, defaults));
|
|
205
|
+
} else if ((0, import_core.isImageEmbed)(op.insert)) {
|
|
206
|
+
const embed = op.insert;
|
|
207
|
+
const img = makeImageRun(embed);
|
|
208
|
+
if (img) out.push(img);
|
|
209
|
+
if (embed.caption && embed.caption.length > 0) {
|
|
210
|
+
out.push(
|
|
211
|
+
new import_docx.TextRun({
|
|
212
|
+
text: embed.caption,
|
|
213
|
+
italics: true,
|
|
214
|
+
size: 18,
|
|
215
|
+
// 9pt — typical caption size
|
|
216
|
+
break: 1
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (out.length === 0) out.push(new import_docx.TextRun(""));
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
function makeTextRun(text, marks, defaults) {
|
|
226
|
+
const m = marks ?? {};
|
|
227
|
+
const fontSize = parseFontSizeToHalfPoints(m.fontSize);
|
|
228
|
+
const segments = text.split("\n");
|
|
229
|
+
if (segments.length === 1) {
|
|
230
|
+
return new import_docx.TextRun({
|
|
231
|
+
text,
|
|
232
|
+
bold: m.bold || defaults.bold,
|
|
233
|
+
italics: m.italic || defaults.italics,
|
|
234
|
+
underline: m.underline ? { type: import_docx.UnderlineType.SINGLE } : void 0,
|
|
235
|
+
strike: m.strike,
|
|
236
|
+
color: cssColorToDocxHex(m.color),
|
|
237
|
+
font: m.fontFamily ?? defaults.font,
|
|
238
|
+
size: fontSize ?? defaults.size
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return new import_docx.TextRun({
|
|
242
|
+
text: text.replace(/\n/g, " "),
|
|
243
|
+
bold: m.bold || defaults.bold,
|
|
244
|
+
italics: m.italic || defaults.italics,
|
|
245
|
+
underline: m.underline ? { type: import_docx.UnderlineType.SINGLE } : void 0,
|
|
246
|
+
strike: m.strike,
|
|
247
|
+
color: cssColorToDocxHex(m.color),
|
|
248
|
+
font: m.fontFamily ?? defaults.font,
|
|
249
|
+
size: fontSize ?? defaults.size
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function makeImageRun(embed) {
|
|
253
|
+
const decoded = decodeDataUrl(embed.src);
|
|
254
|
+
if (!decoded) return null;
|
|
255
|
+
return new import_docx.ImageRun({
|
|
256
|
+
data: decoded.bytes,
|
|
257
|
+
type: decoded.kind,
|
|
258
|
+
transformation: {
|
|
259
|
+
width: Math.max(1, Math.round(embed.width)),
|
|
260
|
+
height: Math.max(1, Math.round(embed.height))
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
function decodeDataUrl(src) {
|
|
265
|
+
const m = /^data:image\/(png|jpe?g|gif|bmp|svg\+xml);base64,(.+)$/i.exec(src);
|
|
266
|
+
if (!m) return null;
|
|
267
|
+
const fmt = m[1].toLowerCase();
|
|
268
|
+
const kind = fmt === "jpeg" || fmt === "jpg" ? "jpg" : fmt === "svg+xml" ? "svg" : fmt;
|
|
269
|
+
const base64 = m[2];
|
|
270
|
+
const bytes = base64ToBytes(base64);
|
|
271
|
+
return { bytes, kind };
|
|
272
|
+
}
|
|
273
|
+
function base64ToBytes(b64) {
|
|
274
|
+
if (typeof atob === "function") {
|
|
275
|
+
const bin = atob(b64);
|
|
276
|
+
const out = new Uint8Array(bin.length);
|
|
277
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
281
|
+
}
|
|
282
|
+
var HEADING_LEVELS = [
|
|
283
|
+
import_docx.HeadingLevel.HEADING_1,
|
|
284
|
+
import_docx.HeadingLevel.HEADING_2,
|
|
285
|
+
import_docx.HeadingLevel.HEADING_3,
|
|
286
|
+
import_docx.HeadingLevel.HEADING_4,
|
|
287
|
+
import_docx.HeadingLevel.HEADING_5,
|
|
288
|
+
import_docx.HeadingLevel.HEADING_6
|
|
289
|
+
];
|
|
290
|
+
function clampHeadingLevel(l) {
|
|
291
|
+
if (typeof l !== "number") return 1;
|
|
292
|
+
const n = Math.trunc(l);
|
|
293
|
+
if (n < 1) return 1;
|
|
294
|
+
if (n > 6) return 6;
|
|
295
|
+
return n;
|
|
296
|
+
}
|
|
297
|
+
function clampListLevel(l) {
|
|
298
|
+
if (typeof l !== "number" || !Number.isFinite(l)) return 0;
|
|
299
|
+
return Math.max(0, Math.min(5, Math.trunc(l)));
|
|
300
|
+
}
|
|
301
|
+
function alignFor(a) {
|
|
302
|
+
switch (a) {
|
|
303
|
+
case "left":
|
|
304
|
+
return import_docx.AlignmentType.LEFT;
|
|
305
|
+
case "center":
|
|
306
|
+
return import_docx.AlignmentType.CENTER;
|
|
307
|
+
case "right":
|
|
308
|
+
return import_docx.AlignmentType.RIGHT;
|
|
309
|
+
case "justify":
|
|
310
|
+
return import_docx.AlignmentType.JUSTIFIED;
|
|
311
|
+
default:
|
|
312
|
+
return void 0;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function cssColorToDocxHex(color) {
|
|
316
|
+
if (!color) return void 0;
|
|
317
|
+
const c = color.trim();
|
|
318
|
+
if (/^#[0-9a-f]{6}$/i.test(c)) return c.slice(1).toUpperCase();
|
|
319
|
+
if (/^#[0-9a-f]{3}$/i.test(c)) {
|
|
320
|
+
return c.slice(1).split("").map((d) => d + d).join("").toUpperCase();
|
|
321
|
+
}
|
|
322
|
+
const rgb = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(c);
|
|
323
|
+
if (rgb) {
|
|
324
|
+
const r = Number(rgb[1]);
|
|
325
|
+
const g = Number(rgb[2]);
|
|
326
|
+
const b = Number(rgb[3]);
|
|
327
|
+
return [r, g, b].map((n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
328
|
+
}
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
function parseFontSizeToHalfPoints(size) {
|
|
332
|
+
if (!size) return void 0;
|
|
333
|
+
const s = size.trim();
|
|
334
|
+
const m = /^(-?\d*\.?\d+)\s*(pt|px|em|rem)?$/i.exec(s);
|
|
335
|
+
if (!m) return void 0;
|
|
336
|
+
const n = Number(m[1]);
|
|
337
|
+
if (!Number.isFinite(n) || n <= 0) return void 0;
|
|
338
|
+
const unit = (m[2] ?? "pt").toLowerCase();
|
|
339
|
+
const pt = unit === "pt" ? n : unit === "px" ? n * 0.75 : n * 12;
|
|
340
|
+
return Math.round(pt * 2);
|
|
341
|
+
}
|
|
342
|
+
function buildBulletLevels() {
|
|
343
|
+
const symbols = ["\u2022", "\u25E6", "\u25AA", "\u2022", "\u25E6", "\u25AA"];
|
|
344
|
+
return symbols.map((sym, i) => ({
|
|
345
|
+
level: i,
|
|
346
|
+
format: import_docx.LevelFormat.BULLET,
|
|
347
|
+
text: sym,
|
|
348
|
+
alignment: import_docx.AlignmentType.LEFT,
|
|
349
|
+
style: {
|
|
350
|
+
paragraph: { indent: { left: 360 + i * 360, hanging: 260 } }
|
|
351
|
+
}
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
function buildOrderedLevels() {
|
|
355
|
+
const formats = [
|
|
356
|
+
import_docx.LevelFormat.DECIMAL,
|
|
357
|
+
import_docx.LevelFormat.LOWER_LETTER,
|
|
358
|
+
import_docx.LevelFormat.LOWER_ROMAN,
|
|
359
|
+
import_docx.LevelFormat.DECIMAL,
|
|
360
|
+
import_docx.LevelFormat.LOWER_LETTER,
|
|
361
|
+
import_docx.LevelFormat.LOWER_ROMAN
|
|
362
|
+
];
|
|
363
|
+
return formats.map((format, i) => ({
|
|
364
|
+
level: i,
|
|
365
|
+
format,
|
|
366
|
+
text: `%${i + 1}.`,
|
|
367
|
+
alignment: import_docx.AlignmentType.LEFT,
|
|
368
|
+
style: {
|
|
369
|
+
paragraph: { indent: { left: 360 + i * 360, hanging: 260 } }
|
|
370
|
+
}
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
374
|
+
0 && (module.exports = {
|
|
375
|
+
documentToDocxBlob,
|
|
376
|
+
documentToDocxBuffer
|
|
377
|
+
});
|
|
378
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/docx.ts"],"sourcesContent":["export { documentToDocxBlob, documentToDocxBuffer } from \"./docx\";\nexport type { DocumentToDocxOptions } from \"./docx\";\n","import type {\n AlignValue,\n DeltaOp,\n ImageEmbed,\n LegacySerializedDocument,\n ListKind,\n MarkAttrs,\n SerializedBlock,\n SerializedCell,\n SerializedDocument,\n} from \"@sofereditor/core\";\nimport {\n DEFAULT_PAGE_SETTINGS,\n isImageEmbed,\n isLegacySerializedDocument,\n pxToMm,\n type PageSettings,\n} from \"@sofereditor/core\";\nimport {\n AlignmentType,\n BorderStyle,\n Document,\n HeadingLevel,\n ImageRun,\n LevelFormat,\n Packer,\n Paragraph,\n PageOrientation,\n ShadingType,\n Table,\n TableCell,\n TableRow,\n TextRun,\n UnderlineType,\n VerticalAlign,\n WidthType,\n convertMillimetersToTwip,\n} from \"docx\";\n\nconst BULLET_REF = \"ed-bullet\";\nconst ORDERED_REF = \"ed-ordered\";\n\nexport interface DocumentToDocxOptions {\n /** Document title; embedded in core properties. */\n title?: string;\n /** Author for core properties. */\n creator?: string;\n}\n\n/**\n * Build a docx `Document` from a `SerializedDocument`. Output is a Blob you can\n * download with `downloadBlob` from `@sofereditor/export-pdf`.\n *\n * Mappings:\n * - paragraph / heading / blockquote / codeBlock → Paragraph (with `heading`,\n * shading, monospace font for code)\n * - listItem → Paragraph with `numbering: { reference, level }`\n * - table → docx `Table`. `rowspan`/`colspan` map to TableCell's `rowSpan`/\n * `columnSpan`; \"covered\" cells are skipped (docx infers cover from spans)\n * - inline marks → TextRun props (bold, italics, underline, strike, color,\n * font, size in half-points)\n * - image embeds → ImageRun with the base64 payload decoded to Uint8Array\n */\nexport async function documentToDocxBlob(\n doc: SerializedDocument | LegacySerializedDocument,\n options: DocumentToDocxOptions = {},\n): Promise<Blob> {\n const built = buildDocument(normalize(doc), options);\n return Packer.toBlob(built);\n}\n\n/** Same as `documentToDocxBlob` but returns a Uint8Array (useful in Node/tests). */\nexport async function documentToDocxBuffer(\n doc: SerializedDocument | LegacySerializedDocument,\n options: DocumentToDocxOptions = {},\n): Promise<Uint8Array> {\n const built = buildDocument(normalize(doc), options);\n const buf = await Packer.toBuffer(built);\n // `Packer.toBuffer` returns a Node Buffer at runtime — coerce for consumers.\n return buf instanceof Uint8Array ? buf : new Uint8Array(buf);\n}\n\nfunction normalize(doc: SerializedDocument | LegacySerializedDocument): SerializedDocument {\n return isLegacySerializedDocument(doc) ? { blocks: doc } : doc;\n}\n\nfunction buildDocument(doc: SerializedDocument, options: DocumentToDocxOptions): Document {\n const children = blocksToDocxChildren(doc);\n const settings = doc.pageSettings ?? DEFAULT_PAGE_SETTINGS;\n return new Document({\n creator: options.creator,\n title: options.title,\n numbering: {\n config: [\n {\n reference: BULLET_REF,\n levels: buildBulletLevels(),\n },\n {\n reference: ORDERED_REF,\n levels: buildOrderedLevels(),\n },\n ],\n },\n sections: [\n {\n properties: sectionPropertiesFor(settings),\n children,\n },\n ],\n });\n}\n\nfunction sectionPropertiesFor(settings: PageSettings) {\n return {\n page: {\n size: {\n width: convertMillimetersToTwip(pxToMm(settings.width)),\n height: convertMillimetersToTwip(pxToMm(settings.height)),\n orientation: PageOrientation.PORTRAIT,\n },\n margin: {\n top: convertMillimetersToTwip(pxToMm(settings.marginTop)),\n bottom: convertMillimetersToTwip(pxToMm(settings.marginBottom)),\n left: convertMillimetersToTwip(pxToMm(settings.marginLeft)),\n right: convertMillimetersToTwip(pxToMm(settings.marginRight)),\n },\n },\n };\n}\n\nfunction blocksToDocxChildren(doc: SerializedDocument): Array<Paragraph | Table> {\n const out: Array<Paragraph | Table> = [];\n const blocks = doc.blocks;\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i];\n switch (block.type) {\n case \"paragraph\":\n out.push(makeParagraph(block));\n break;\n case \"heading\":\n out.push(makeHeading(block));\n break;\n case \"blockquote\":\n out.push(makeBlockquote(block));\n break;\n case \"codeBlock\":\n out.push(makeCodeBlock(block));\n break;\n case \"listItem\":\n out.push(makeListItem(block));\n break;\n case \"table\":\n out.push(makeTable(block));\n break;\n default:\n out.push(makeParagraph(block));\n }\n }\n return out;\n}\n\n// ---------- block builders ----------\n\n// School standard: Arial for all body text. Code blocks override with\n// Consolas (monospace is more legible for code). Italics on blockquote is\n// kept as a stylistic default.\nconst ARIAL: RunDefaults = { font: \"Arial\" };\n\nfunction makeParagraph(block: SerializedBlock): Paragraph {\n return new Paragraph({\n alignment: alignFor(block.attrs.align),\n bidirectional: block.attrs.dir === \"rtl\" ? true : undefined,\n children: deltaToRuns(block.delta, ARIAL),\n });\n}\n\nfunction makeHeading(block: SerializedBlock): Paragraph {\n const level = clampHeadingLevel(block.attrs.level);\n return new Paragraph({\n heading: HEADING_LEVELS[level - 1],\n alignment: alignFor(block.attrs.align),\n children: deltaToRuns(block.delta, ARIAL),\n });\n}\n\nfunction makeBlockquote(block: SerializedBlock): Paragraph {\n return new Paragraph({\n alignment: alignFor(block.attrs.align),\n indent: { left: 480 }, // ~0.25\"\n border: {\n left: { color: \"CBD5E1\", space: 12, style: BorderStyle.SINGLE, size: 12 },\n },\n children: deltaToRuns(block.delta, { ...ARIAL, italics: true }),\n });\n}\n\nfunction makeCodeBlock(block: SerializedBlock): Paragraph {\n return new Paragraph({\n spacing: { before: 100, after: 100 },\n shading: { type: ShadingType.CLEAR, color: \"auto\", fill: \"F1F5F9\" },\n children: deltaToRuns(block.delta, { font: \"Consolas\", size: 20 }),\n });\n}\n\nfunction makeListItem(block: SerializedBlock): Paragraph {\n const kind: ListKind = block.attrs.listKind === \"ordered\" ? \"ordered\" : \"bullet\";\n const reference = kind === \"ordered\" ? ORDERED_REF : BULLET_REF;\n const level = clampListLevel(block.attrs.listLevel);\n return new Paragraph({\n numbering: { reference, level },\n alignment: alignFor(block.attrs.align),\n children: deltaToRuns(block.delta, ARIAL),\n });\n}\n\nfunction makeTable(block: SerializedBlock): Table {\n const rows = typeof block.attrs.rows === \"number\" ? block.attrs.rows : 0;\n const cols = typeof block.attrs.cols === \"number\" ? block.attrs.cols : 0;\n const cells = block.cells ?? [];\n\n const rowsOut: TableRow[] = [];\n for (let r = 0; r < rows; r++) {\n const cellsOut: TableCell[] = [];\n for (let c = 0; c < cols; c++) {\n const cell = cells[r * cols + c];\n if (!cell) {\n cellsOut.push(emptyCell());\n continue;\n }\n if (cell.attrs?.covered) continue;\n cellsOut.push(makeCell(cell));\n }\n rowsOut.push(new TableRow({ children: cellsOut }));\n }\n\n return new Table({\n rows: rowsOut,\n width: { size: 100, type: WidthType.PERCENTAGE },\n });\n}\n\nfunction makeCell(cell: SerializedCell): TableCell {\n const span = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;\n const rowSpan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;\n return new TableCell({\n columnSpan: span,\n rowSpan,\n verticalAlign: VerticalAlign.TOP,\n children: [new Paragraph({ children: deltaToRuns(cell.delta, ARIAL) })],\n });\n}\n\nfunction emptyCell(): TableCell {\n return new TableCell({ children: [new Paragraph(\"\")] });\n}\n\n// ---------- inline ----------\n\ninterface RunDefaults {\n bold?: boolean;\n italics?: boolean;\n font?: string;\n size?: number;\n}\n\nfunction deltaToRuns(\n delta: DeltaOp[],\n defaults: RunDefaults = {},\n): Array<TextRun | ImageRun> {\n if (delta.length === 0) return [new TextRun(\"\")];\n const out: Array<TextRun | ImageRun> = [];\n for (const op of delta) {\n if (typeof op.insert === \"string\") {\n if (op.insert.length === 0) continue;\n out.push(makeTextRun(op.insert, op.attributes, defaults));\n } else if (isImageEmbed(op.insert)) {\n const embed = op.insert;\n const img = makeImageRun(embed);\n if (img) out.push(img);\n // Captions are emitted as italic-smaller text on a new line right after\n // the image. Word-style \"Caption\" paragraph would need a paragraph\n // break, which we can't do mid-delta — visually correct but loses the\n // semantic \"Caption\" style.\n if (embed.caption && embed.caption.length > 0) {\n out.push(\n new TextRun({\n text: embed.caption,\n italics: true,\n size: 18, // 9pt — typical caption size\n break: 1,\n }),\n );\n }\n }\n }\n if (out.length === 0) out.push(new TextRun(\"\"));\n return out;\n}\n\nfunction makeTextRun(\n text: string,\n marks: MarkAttrs | undefined,\n defaults: RunDefaults,\n): TextRun {\n const m = marks ?? {};\n const fontSize = parseFontSizeToHalfPoints(m.fontSize);\n // TextRun reads newlines via `break`; convert \\n into break runs.\n const segments = text.split(\"\\n\");\n if (segments.length === 1) {\n return new TextRun({\n text,\n bold: m.bold || defaults.bold,\n italics: m.italic || defaults.italics,\n underline: m.underline ? { type: UnderlineType.SINGLE } : undefined,\n strike: m.strike,\n color: cssColorToDocxHex(m.color),\n font: m.fontFamily ?? defaults.font,\n size: fontSize ?? defaults.size,\n });\n }\n // Multi-line: rejoin via TextRun children — but TextRun expects a single text\n // string. Approximate with non-printing line breaks: produce a sequence of\n // TextRuns separated by `new TextRun({ break: 1 })` packed into the parent.\n // Simpler: collapse to a single TextRun with the string, treating \\n as a\n // soft-break placeholder. Word renders \\n inside a w:t as a literal space,\n // so we replace with a small symbol-friendly fallback.\n return new TextRun({\n text: text.replace(/\\n/g, \" \"),\n bold: m.bold || defaults.bold,\n italics: m.italic || defaults.italics,\n underline: m.underline ? { type: UnderlineType.SINGLE } : undefined,\n strike: m.strike,\n color: cssColorToDocxHex(m.color),\n font: m.fontFamily ?? defaults.font,\n size: fontSize ?? defaults.size,\n });\n}\n\nfunction makeImageRun(embed: ImageEmbed): ImageRun | null {\n const decoded = decodeDataUrl(embed.src);\n if (!decoded) return null;\n return new ImageRun({\n data: decoded.bytes,\n type: decoded.kind,\n transformation: {\n width: Math.max(1, Math.round(embed.width)),\n height: Math.max(1, Math.round(embed.height)),\n },\n } as ConstructorParameters<typeof ImageRun>[0]);\n}\n\ninterface DecodedImage {\n bytes: Uint8Array;\n kind: \"png\" | \"jpg\" | \"gif\" | \"bmp\" | \"svg\";\n}\n\nfunction decodeDataUrl(src: string): DecodedImage | null {\n const m = /^data:image\\/(png|jpe?g|gif|bmp|svg\\+xml);base64,(.+)$/i.exec(src);\n if (!m) return null;\n const fmt = m[1].toLowerCase();\n const kind: DecodedImage[\"kind\"] =\n fmt === \"jpeg\" || fmt === \"jpg\"\n ? \"jpg\"\n : fmt === \"svg+xml\"\n ? \"svg\"\n : (fmt as \"png\" | \"gif\" | \"bmp\");\n const base64 = m[2];\n const bytes = base64ToBytes(base64);\n return { bytes, kind };\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n if (typeof atob === \"function\") {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n // Node fallback.\n return new Uint8Array(Buffer.from(b64, \"base64\"));\n}\n\n// ---------- attribute mapping ----------\n\nconst HEADING_LEVELS = [\n HeadingLevel.HEADING_1,\n HeadingLevel.HEADING_2,\n HeadingLevel.HEADING_3,\n HeadingLevel.HEADING_4,\n HeadingLevel.HEADING_5,\n HeadingLevel.HEADING_6,\n];\n\nfunction clampHeadingLevel(l: unknown): 1 | 2 | 3 | 4 | 5 | 6 {\n if (typeof l !== \"number\") return 1;\n const n = Math.trunc(l);\n if (n < 1) return 1;\n if (n > 6) return 6;\n return n as 1 | 2 | 3 | 4 | 5 | 6;\n}\n\nfunction clampListLevel(l: unknown): number {\n if (typeof l !== \"number\" || !Number.isFinite(l)) return 0;\n return Math.max(0, Math.min(5, Math.trunc(l)));\n}\n\nfunction alignFor(a: AlignValue | undefined): (typeof AlignmentType)[keyof typeof AlignmentType] | undefined {\n switch (a) {\n case \"left\":\n return AlignmentType.LEFT;\n case \"center\":\n return AlignmentType.CENTER;\n case \"right\":\n return AlignmentType.RIGHT;\n case \"justify\":\n return AlignmentType.JUSTIFIED;\n default:\n return undefined;\n }\n}\n\nfunction cssColorToDocxHex(color: string | undefined): string | undefined {\n if (!color) return undefined;\n // docx expects RRGGBB without leading '#'.\n const c = color.trim();\n if (/^#[0-9a-f]{6}$/i.test(c)) return c.slice(1).toUpperCase();\n if (/^#[0-9a-f]{3}$/i.test(c)) {\n // expand short hex\n return c\n .slice(1)\n .split(\"\")\n .map((d) => d + d)\n .join(\"\")\n .toUpperCase();\n }\n const rgb = /^rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)/i.exec(c);\n if (rgb) {\n const r = Number(rgb[1]);\n const g = Number(rgb[2]);\n const b = Number(rgb[3]);\n return [r, g, b].map((n) => Math.max(0, Math.min(255, n)).toString(16).padStart(2, \"0\")).join(\"\").toUpperCase();\n }\n // Named colors / other forms — let docx fall back to default.\n return undefined;\n}\n\n/**\n * docx sizes are in half-points (24 = 12pt). Parse common CSS sizes.\n * Returns `undefined` when not parseable so callers can pick a default.\n */\nfunction parseFontSizeToHalfPoints(size: string | undefined): number | undefined {\n if (!size) return undefined;\n const s = size.trim();\n const m = /^(-?\\d*\\.?\\d+)\\s*(pt|px|em|rem)?$/i.exec(s);\n if (!m) return undefined;\n const n = Number(m[1]);\n if (!Number.isFinite(n) || n <= 0) return undefined;\n const unit = (m[2] ?? \"pt\").toLowerCase();\n const pt =\n unit === \"pt\"\n ? n\n : unit === \"px\"\n ? n * 0.75 // CSS px → pt at 96dpi\n : n * 12;\n return Math.round(pt * 2);\n}\n\n// ---------- list numbering definitions ----------\n\nfunction buildBulletLevels(): Array<{\n level: number;\n format: (typeof LevelFormat)[keyof typeof LevelFormat];\n text: string;\n alignment: (typeof AlignmentType)[keyof typeof AlignmentType];\n style?: { paragraph?: { indent?: { left: number; hanging: number } } };\n}> {\n const symbols = [\"•\", \"◦\", \"▪\", \"•\", \"◦\", \"▪\"];\n return symbols.map((sym, i) => ({\n level: i,\n format: LevelFormat.BULLET,\n text: sym,\n alignment: AlignmentType.LEFT,\n style: {\n paragraph: { indent: { left: 360 + i * 360, hanging: 260 } },\n },\n }));\n}\n\nfunction buildOrderedLevels(): Array<{\n level: number;\n format: (typeof LevelFormat)[keyof typeof LevelFormat];\n text: string;\n alignment: (typeof AlignmentType)[keyof typeof AlignmentType];\n style?: { paragraph?: { indent?: { left: number; hanging: number } } };\n}> {\n const formats = [\n LevelFormat.DECIMAL,\n LevelFormat.LOWER_LETTER,\n LevelFormat.LOWER_ROMAN,\n LevelFormat.DECIMAL,\n LevelFormat.LOWER_LETTER,\n LevelFormat.LOWER_ROMAN,\n ];\n return formats.map((format, i) => ({\n level: i,\n format,\n text: `%${i + 1}.`,\n alignment: AlignmentType.LEFT,\n style: {\n paragraph: { indent: { left: 360 + i * 360, hanging: 260 } },\n },\n }));\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SerializedDocument, LegacySerializedDocument } from '@sofereditor/core';
|
|
2
|
+
|
|
3
|
+
interface DocumentToDocxOptions {
|
|
4
|
+
/** Document title; embedded in core properties. */
|
|
5
|
+
title?: string;
|
|
6
|
+
/** Author for core properties. */
|
|
7
|
+
creator?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build a docx `Document` from a `SerializedDocument`. Output is a Blob you can
|
|
11
|
+
* download with `downloadBlob` from `@sofereditor/export-pdf`.
|
|
12
|
+
*
|
|
13
|
+
* Mappings:
|
|
14
|
+
* - paragraph / heading / blockquote / codeBlock → Paragraph (with `heading`,
|
|
15
|
+
* shading, monospace font for code)
|
|
16
|
+
* - listItem → Paragraph with `numbering: { reference, level }`
|
|
17
|
+
* - table → docx `Table`. `rowspan`/`colspan` map to TableCell's `rowSpan`/
|
|
18
|
+
* `columnSpan`; "covered" cells are skipped (docx infers cover from spans)
|
|
19
|
+
* - inline marks → TextRun props (bold, italics, underline, strike, color,
|
|
20
|
+
* font, size in half-points)
|
|
21
|
+
* - image embeds → ImageRun with the base64 payload decoded to Uint8Array
|
|
22
|
+
*/
|
|
23
|
+
declare function documentToDocxBlob(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<Blob>;
|
|
24
|
+
/** Same as `documentToDocxBlob` but returns a Uint8Array (useful in Node/tests). */
|
|
25
|
+
declare function documentToDocxBuffer(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<Uint8Array>;
|
|
26
|
+
|
|
27
|
+
export { type DocumentToDocxOptions, documentToDocxBlob, documentToDocxBuffer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SerializedDocument, LegacySerializedDocument } from '@sofereditor/core';
|
|
2
|
+
|
|
3
|
+
interface DocumentToDocxOptions {
|
|
4
|
+
/** Document title; embedded in core properties. */
|
|
5
|
+
title?: string;
|
|
6
|
+
/** Author for core properties. */
|
|
7
|
+
creator?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build a docx `Document` from a `SerializedDocument`. Output is a Blob you can
|
|
11
|
+
* download with `downloadBlob` from `@sofereditor/export-pdf`.
|
|
12
|
+
*
|
|
13
|
+
* Mappings:
|
|
14
|
+
* - paragraph / heading / blockquote / codeBlock → Paragraph (with `heading`,
|
|
15
|
+
* shading, monospace font for code)
|
|
16
|
+
* - listItem → Paragraph with `numbering: { reference, level }`
|
|
17
|
+
* - table → docx `Table`. `rowspan`/`colspan` map to TableCell's `rowSpan`/
|
|
18
|
+
* `columnSpan`; "covered" cells are skipped (docx infers cover from spans)
|
|
19
|
+
* - inline marks → TextRun props (bold, italics, underline, strike, color,
|
|
20
|
+
* font, size in half-points)
|
|
21
|
+
* - image embeds → ImageRun with the base64 payload decoded to Uint8Array
|
|
22
|
+
*/
|
|
23
|
+
declare function documentToDocxBlob(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<Blob>;
|
|
24
|
+
/** Same as `documentToDocxBlob` but returns a Uint8Array (useful in Node/tests). */
|
|
25
|
+
declare function documentToDocxBuffer(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<Uint8Array>;
|
|
26
|
+
|
|
27
|
+
export { type DocumentToDocxOptions, documentToDocxBlob, documentToDocxBuffer };
|