@sofereditor/export-docx 0.3.1 → 0.3.3
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/dist/index.cjs +121 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -4
- package/dist/index.d.ts +22 -4
- package/dist/index.js +122 -36
- package/dist/index.js.map +1 -1
- package/package.json +26 -9
- package/src/__tests__/docx.test.ts +0 -94
- package/src/docx.ts +0 -514
- package/src/index.ts +0 -2
- package/tsconfig.json +0 -8
- package/tsup.config.ts +0 -11
package/dist/index.cjs
CHANGED
|
@@ -31,19 +31,32 @@ var import_docx = require("docx");
|
|
|
31
31
|
var BULLET_REF = "ed-bullet";
|
|
32
32
|
var ORDERED_REF = "ed-ordered";
|
|
33
33
|
async function documentToDocxBlob(doc, options = {}) {
|
|
34
|
-
const
|
|
35
|
-
|
|
34
|
+
const normalized = normalize(doc);
|
|
35
|
+
const { images, skipped } = await resolveAllImages(
|
|
36
|
+
normalized,
|
|
37
|
+
options.resolveImage ?? defaultResolveImage
|
|
38
|
+
);
|
|
39
|
+
const built = buildDocument(normalized, options, images);
|
|
40
|
+
return { blob: await import_docx.Packer.toBlob(built), skippedImages: skipped };
|
|
36
41
|
}
|
|
37
42
|
async function documentToDocxBuffer(doc, options = {}) {
|
|
38
|
-
const
|
|
43
|
+
const normalized = normalize(doc);
|
|
44
|
+
const { images, skipped } = await resolveAllImages(
|
|
45
|
+
normalized,
|
|
46
|
+
options.resolveImage ?? defaultResolveImage
|
|
47
|
+
);
|
|
48
|
+
const built = buildDocument(normalized, options, images);
|
|
39
49
|
const buf = await import_docx.Packer.toBuffer(built);
|
|
40
|
-
return
|
|
50
|
+
return {
|
|
51
|
+
buffer: buf instanceof Uint8Array ? buf : new Uint8Array(buf),
|
|
52
|
+
skippedImages: skipped
|
|
53
|
+
};
|
|
41
54
|
}
|
|
42
55
|
function normalize(doc) {
|
|
43
56
|
return (0, import_core.isLegacySerializedDocument)(doc) ? { blocks: doc } : doc;
|
|
44
57
|
}
|
|
45
|
-
function buildDocument(doc, options) {
|
|
46
|
-
const children = blocksToDocxChildren(doc);
|
|
58
|
+
function buildDocument(doc, options, images) {
|
|
59
|
+
const children = blocksToDocxChildren(doc, images);
|
|
47
60
|
const settings = doc.pageSettings ?? import_core.DEFAULT_PAGE_SETTINGS;
|
|
48
61
|
return new import_docx.Document({
|
|
49
62
|
creator: options.creator,
|
|
@@ -85,53 +98,53 @@ function sectionPropertiesFor(settings) {
|
|
|
85
98
|
}
|
|
86
99
|
};
|
|
87
100
|
}
|
|
88
|
-
function blocksToDocxChildren(doc) {
|
|
101
|
+
function blocksToDocxChildren(doc, images) {
|
|
89
102
|
const out = [];
|
|
90
103
|
const blocks = doc.blocks;
|
|
91
104
|
for (let i = 0; i < blocks.length; i++) {
|
|
92
105
|
const block = blocks[i];
|
|
93
106
|
switch (block.type) {
|
|
94
107
|
case "paragraph":
|
|
95
|
-
out.push(makeParagraph(block));
|
|
108
|
+
out.push(makeParagraph(block, images));
|
|
96
109
|
break;
|
|
97
110
|
case "heading":
|
|
98
|
-
out.push(makeHeading(block));
|
|
111
|
+
out.push(makeHeading(block, images));
|
|
99
112
|
break;
|
|
100
113
|
case "blockquote":
|
|
101
|
-
out.push(makeBlockquote(block));
|
|
114
|
+
out.push(makeBlockquote(block, images));
|
|
102
115
|
break;
|
|
103
116
|
case "codeBlock":
|
|
104
|
-
out.push(makeCodeBlock(block));
|
|
117
|
+
out.push(makeCodeBlock(block, images));
|
|
105
118
|
break;
|
|
106
119
|
case "listItem":
|
|
107
|
-
out.push(makeListItem(block));
|
|
120
|
+
out.push(makeListItem(block, images));
|
|
108
121
|
break;
|
|
109
122
|
case "table":
|
|
110
|
-
out.push(makeTable(block));
|
|
123
|
+
out.push(makeTable(block, images));
|
|
111
124
|
break;
|
|
112
125
|
default:
|
|
113
|
-
out.push(makeParagraph(block));
|
|
126
|
+
out.push(makeParagraph(block, images));
|
|
114
127
|
}
|
|
115
128
|
}
|
|
116
129
|
return out;
|
|
117
130
|
}
|
|
118
131
|
var ARIAL = { font: "Arial" };
|
|
119
|
-
function makeParagraph(block) {
|
|
132
|
+
function makeParagraph(block, images) {
|
|
120
133
|
return new import_docx.Paragraph({
|
|
121
134
|
alignment: alignFor(block.attrs.align),
|
|
122
135
|
bidirectional: block.attrs.dir === "rtl" ? true : void 0,
|
|
123
|
-
children: deltaToRuns(block.delta, ARIAL)
|
|
136
|
+
children: deltaToRuns(block.delta, ARIAL, images)
|
|
124
137
|
});
|
|
125
138
|
}
|
|
126
|
-
function makeHeading(block) {
|
|
139
|
+
function makeHeading(block, images) {
|
|
127
140
|
const level = clampHeadingLevel(block.attrs.level);
|
|
128
141
|
return new import_docx.Paragraph({
|
|
129
142
|
heading: HEADING_LEVELS[level - 1],
|
|
130
143
|
alignment: alignFor(block.attrs.align),
|
|
131
|
-
children: deltaToRuns(block.delta, ARIAL)
|
|
144
|
+
children: deltaToRuns(block.delta, ARIAL, images)
|
|
132
145
|
});
|
|
133
146
|
}
|
|
134
|
-
function makeBlockquote(block) {
|
|
147
|
+
function makeBlockquote(block, images) {
|
|
135
148
|
return new import_docx.Paragraph({
|
|
136
149
|
alignment: alignFor(block.attrs.align),
|
|
137
150
|
indent: { left: 480 },
|
|
@@ -139,27 +152,27 @@ function makeBlockquote(block) {
|
|
|
139
152
|
border: {
|
|
140
153
|
left: { color: "CBD5E1", space: 12, style: import_docx.BorderStyle.SINGLE, size: 12 }
|
|
141
154
|
},
|
|
142
|
-
children: deltaToRuns(block.delta, { ...ARIAL, italics: true })
|
|
155
|
+
children: deltaToRuns(block.delta, { ...ARIAL, italics: true }, images)
|
|
143
156
|
});
|
|
144
157
|
}
|
|
145
|
-
function makeCodeBlock(block) {
|
|
158
|
+
function makeCodeBlock(block, images) {
|
|
146
159
|
return new import_docx.Paragraph({
|
|
147
160
|
spacing: { before: 100, after: 100 },
|
|
148
161
|
shading: { type: import_docx.ShadingType.CLEAR, color: "auto", fill: "F1F5F9" },
|
|
149
|
-
children: deltaToRuns(block.delta, { font: "Consolas", size: 20 })
|
|
162
|
+
children: deltaToRuns(block.delta, { font: "Consolas", size: 20 }, images)
|
|
150
163
|
});
|
|
151
164
|
}
|
|
152
|
-
function makeListItem(block) {
|
|
165
|
+
function makeListItem(block, images) {
|
|
153
166
|
const kind = block.attrs.listKind === "ordered" ? "ordered" : "bullet";
|
|
154
167
|
const reference = kind === "ordered" ? ORDERED_REF : BULLET_REF;
|
|
155
168
|
const level = clampListLevel(block.attrs.listLevel);
|
|
156
169
|
return new import_docx.Paragraph({
|
|
157
170
|
numbering: { reference, level },
|
|
158
171
|
alignment: alignFor(block.attrs.align),
|
|
159
|
-
children: deltaToRuns(block.delta, ARIAL)
|
|
172
|
+
children: deltaToRuns(block.delta, ARIAL, images)
|
|
160
173
|
});
|
|
161
174
|
}
|
|
162
|
-
function makeTable(block) {
|
|
175
|
+
function makeTable(block, images) {
|
|
163
176
|
const rows = typeof block.attrs.rows === "number" ? block.attrs.rows : 0;
|
|
164
177
|
const cols = typeof block.attrs.cols === "number" ? block.attrs.cols : 0;
|
|
165
178
|
const cells = block.cells ?? [];
|
|
@@ -173,29 +186,46 @@ function makeTable(block) {
|
|
|
173
186
|
continue;
|
|
174
187
|
}
|
|
175
188
|
if (cell.attrs?.covered) continue;
|
|
176
|
-
cellsOut.push(makeCell(cell));
|
|
189
|
+
cellsOut.push(makeCell(cell, images));
|
|
177
190
|
}
|
|
178
191
|
rowsOut.push(new import_docx.TableRow({ children: cellsOut }));
|
|
179
192
|
}
|
|
193
|
+
const rawColWidths = block.attrs.colWidths;
|
|
194
|
+
const isValidColWidths = Array.isArray(rawColWidths) && cols > 0 && rawColWidths.length === cols && rawColWidths.every((w) => typeof w === "number" && w > 0);
|
|
195
|
+
const columnWidths = isValidColWidths ? rawColWidths.map((px) => (0, import_docx.convertMillimetersToTwip)((0, import_core.pxToMm)(px))) : void 0;
|
|
180
196
|
return new import_docx.Table({
|
|
181
197
|
rows: rowsOut,
|
|
182
|
-
|
|
198
|
+
...columnWidths ? {
|
|
199
|
+
columnWidths,
|
|
200
|
+
// Fix the layout so Word honors the column widths verbatim instead
|
|
201
|
+
// of autofitting to content (the OOXML default when `tblLayout` is
|
|
202
|
+
// absent, which would make `columnWidths` a mere hint).
|
|
203
|
+
layout: import_docx.TableLayoutType.FIXED,
|
|
204
|
+
width: { size: columnWidths.reduce((a, b) => a + b, 0), type: import_docx.WidthType.DXA }
|
|
205
|
+
} : { width: { size: 100, type: import_docx.WidthType.PERCENTAGE } }
|
|
183
206
|
});
|
|
184
207
|
}
|
|
185
|
-
function makeCell(cell) {
|
|
208
|
+
function makeCell(cell, images) {
|
|
186
209
|
const span = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;
|
|
187
210
|
const rowSpan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;
|
|
211
|
+
const fill = cssColorToDocxHex(cell.attrs?.bgColor);
|
|
188
212
|
return new import_docx.TableCell({
|
|
189
213
|
columnSpan: span,
|
|
190
214
|
rowSpan,
|
|
191
215
|
verticalAlign: import_docx.VerticalAlign.TOP,
|
|
192
|
-
|
|
216
|
+
shading: fill ? { type: import_docx.ShadingType.CLEAR, color: "auto", fill } : void 0,
|
|
217
|
+
children: [
|
|
218
|
+
new import_docx.Paragraph({
|
|
219
|
+
alignment: alignFor(cell.attrs?.align),
|
|
220
|
+
children: deltaToRuns(cell.delta, ARIAL, images)
|
|
221
|
+
})
|
|
222
|
+
]
|
|
193
223
|
});
|
|
194
224
|
}
|
|
195
225
|
function emptyCell() {
|
|
196
226
|
return new import_docx.TableCell({ children: [new import_docx.Paragraph("")] });
|
|
197
227
|
}
|
|
198
|
-
function deltaToRuns(delta, defaults = {}) {
|
|
228
|
+
function deltaToRuns(delta, defaults = {}, images) {
|
|
199
229
|
if (delta.length === 0) return [new import_docx.TextRun("")];
|
|
200
230
|
const out = [];
|
|
201
231
|
for (const op of delta) {
|
|
@@ -204,7 +234,7 @@ function deltaToRuns(delta, defaults = {}) {
|
|
|
204
234
|
out.push(makeTextRun(op.insert, op.attributes, defaults));
|
|
205
235
|
} else if ((0, import_core.isImageEmbed)(op.insert)) {
|
|
206
236
|
const embed = op.insert;
|
|
207
|
-
const img = makeImageRun(embed);
|
|
237
|
+
const img = makeImageRun(embed, images);
|
|
208
238
|
if (img) out.push(img);
|
|
209
239
|
if (embed.caption && embed.caption.length > 0) {
|
|
210
240
|
out.push(
|
|
@@ -249,12 +279,12 @@ function makeTextRun(text, marks, defaults) {
|
|
|
249
279
|
size: fontSize ?? defaults.size
|
|
250
280
|
});
|
|
251
281
|
}
|
|
252
|
-
function makeImageRun(embed) {
|
|
253
|
-
const
|
|
254
|
-
if (!
|
|
282
|
+
function makeImageRun(embed, images) {
|
|
283
|
+
const resolved = images.get(embed.src);
|
|
284
|
+
if (!resolved) return null;
|
|
255
285
|
return new import_docx.ImageRun({
|
|
256
|
-
data:
|
|
257
|
-
type:
|
|
286
|
+
data: resolved.data,
|
|
287
|
+
type: resolved.type,
|
|
258
288
|
transformation: {
|
|
259
289
|
width: Math.max(1, Math.round(embed.width)),
|
|
260
290
|
height: Math.max(1, Math.round(embed.height))
|
|
@@ -270,6 +300,61 @@ function decodeDataUrl(src) {
|
|
|
270
300
|
const bytes = base64ToBytes(base64);
|
|
271
301
|
return { bytes, kind };
|
|
272
302
|
}
|
|
303
|
+
async function defaultResolveImage(src) {
|
|
304
|
+
const decoded = decodeDataUrl(src);
|
|
305
|
+
if (decoded) return { data: decoded.bytes, type: decoded.kind };
|
|
306
|
+
if (/^https?:\/\//i.test(src) && typeof fetch === "function") {
|
|
307
|
+
try {
|
|
308
|
+
const res = await fetch(src);
|
|
309
|
+
if (!res.ok) return null;
|
|
310
|
+
const ct = (res.headers.get("content-type") ?? "").toLowerCase();
|
|
311
|
+
const type = ct.includes("png") ? "png" : ct.includes("jpeg") || ct.includes("jpg") ? "jpg" : ct.includes("gif") ? "gif" : ct.includes("bmp") ? "bmp" : ct.includes("svg") ? "svg" : typeFromExtension(src) ?? "png";
|
|
312
|
+
return { data: new Uint8Array(await res.arrayBuffer()), type };
|
|
313
|
+
} catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
function typeFromExtension(src) {
|
|
320
|
+
const m = /\.(png|jpe?g|gif|bmp|svg)(\?|#|$)/i.exec(src);
|
|
321
|
+
if (!m) return null;
|
|
322
|
+
const ext = m[1].toLowerCase();
|
|
323
|
+
return ext === "jpeg" || ext === "jpg" ? "jpg" : ext;
|
|
324
|
+
}
|
|
325
|
+
async function resolveAllImages(doc, resolve) {
|
|
326
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
327
|
+
const collect = (delta) => {
|
|
328
|
+
for (const op of delta) if ((0, import_core.isImageEmbed)(op.insert)) srcs.add(op.insert.src);
|
|
329
|
+
};
|
|
330
|
+
for (const block of doc.blocks) {
|
|
331
|
+
collect(block.delta);
|
|
332
|
+
for (const cell of block.cells ?? []) collect(cell.delta);
|
|
333
|
+
}
|
|
334
|
+
const images = /* @__PURE__ */ new Map();
|
|
335
|
+
await Promise.all(
|
|
336
|
+
[...srcs].map(async (src) => {
|
|
337
|
+
let resolved = null;
|
|
338
|
+
try {
|
|
339
|
+
resolved = await resolve(src);
|
|
340
|
+
} catch {
|
|
341
|
+
resolved = null;
|
|
342
|
+
}
|
|
343
|
+
if (resolved?.type === "svg") resolved = null;
|
|
344
|
+
images.set(src, resolved);
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
let skipped = 0;
|
|
348
|
+
const count = (delta) => {
|
|
349
|
+
for (const op of delta)
|
|
350
|
+
if ((0, import_core.isImageEmbed)(op.insert) && !images.get(op.insert.src)) skipped++;
|
|
351
|
+
};
|
|
352
|
+
for (const block of doc.blocks) {
|
|
353
|
+
count(block.delta);
|
|
354
|
+
for (const cell of block.cells ?? []) count(cell.delta);
|
|
355
|
+
}
|
|
356
|
+
return { images, skipped };
|
|
357
|
+
}
|
|
273
358
|
function base64ToBytes(b64) {
|
|
274
359
|
if (typeof atob === "function") {
|
|
275
360
|
const bin = atob(b64);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/docx.ts"],"sourcesContent":["export { documentToDocxBlob, documentToDocxBuffer } from \"./docx\";\nexport type { DocumentToDocxOptions, ResolvedImage } 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 TableLayoutType,\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 ResolvedImage {\n data: Uint8Array;\n type: \"png\" | \"jpg\" | \"gif\" | \"bmp\" | \"svg\";\n}\n\nexport interface DocumentToDocxOptions {\n /** Document title; embedded in core properties. */\n title?: string;\n /** Author for core properties. */\n creator?: string;\n /**\n * Resolve um `src` de imagem para bytes. Default: data URLs (decodificadas\n * localmente) e http(s) via fetch. Retornar null pula a imagem (contada em\n * `skippedImages`) sem abortar o export.\n */\n resolveImage?: (src: string) => Promise<ResolvedImage | null>;\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 bytes resolved via `resolveImage` (default:\n * data URLs decoded locally, http(s) fetched; unresolved images are\n * skipped and counted in `skippedImages` instead of aborting the export)\n */\nexport async function documentToDocxBlob(\n doc: SerializedDocument | LegacySerializedDocument,\n options: DocumentToDocxOptions = {},\n): Promise<{ blob: Blob; skippedImages: number }> {\n const normalized = normalize(doc);\n const { images, skipped } = await resolveAllImages(\n normalized,\n options.resolveImage ?? defaultResolveImage,\n );\n const built = buildDocument(normalized, options, images);\n return { blob: await Packer.toBlob(built), skippedImages: skipped };\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<{ buffer: Uint8Array; skippedImages: number }> {\n const normalized = normalize(doc);\n const { images, skipped } = await resolveAllImages(\n normalized,\n options.resolveImage ?? defaultResolveImage,\n );\n const built = buildDocument(normalized, options, images);\n const buf = await Packer.toBuffer(built);\n // `Packer.toBuffer` returns a Node Buffer at runtime — coerce for consumers.\n return {\n buffer: buf instanceof Uint8Array ? buf : new Uint8Array(buf),\n skippedImages: skipped,\n };\n}\n\nfunction normalize(doc: SerializedDocument | LegacySerializedDocument): SerializedDocument {\n return isLegacySerializedDocument(doc) ? { blocks: doc } : doc;\n}\n\nfunction buildDocument(\n doc: SerializedDocument,\n options: DocumentToDocxOptions,\n images: Map<string, ResolvedImage | null>,\n): Document {\n const children = blocksToDocxChildren(doc, images);\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(\n doc: SerializedDocument,\n images: Map<string, ResolvedImage | null>,\n): 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, images));\n break;\n case \"heading\":\n out.push(makeHeading(block, images));\n break;\n case \"blockquote\":\n out.push(makeBlockquote(block, images));\n break;\n case \"codeBlock\":\n out.push(makeCodeBlock(block, images));\n break;\n case \"listItem\":\n out.push(makeListItem(block, images));\n break;\n case \"table\":\n out.push(makeTable(block, images));\n break;\n default:\n out.push(makeParagraph(block, images));\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(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n): 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, images),\n });\n}\n\nfunction makeHeading(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n): 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, images),\n });\n}\n\nfunction makeBlockquote(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n): 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 }, images),\n });\n}\n\nfunction makeCodeBlock(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n): 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 }, images),\n });\n}\n\nfunction makeListItem(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n): 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, images),\n });\n}\n\nfunction makeTable(block: SerializedBlock, images: Map<string, ResolvedImage | null>): 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, images));\n }\n rowsOut.push(new TableRow({ children: cellsOut }));\n }\n\n // Validate the RAW array against `cols` — filtering invalid entries first\n // (e.g. dropping a negative width) would silently shift the remaining\n // widths onto the wrong columns while still passing a length check.\n const rawColWidths = block.attrs.colWidths;\n const isValidColWidths =\n Array.isArray(rawColWidths) &&\n cols > 0 &&\n rawColWidths.length === cols &&\n rawColWidths.every((w): w is number => typeof w === \"number\" && w > 0);\n const columnWidths = isValidColWidths\n ? rawColWidths.map((px) => convertMillimetersToTwip(pxToMm(px)))\n : undefined;\n\n return new Table({\n rows: rowsOut,\n ...(columnWidths\n ? {\n columnWidths,\n // Fix the layout so Word honors the column widths verbatim instead\n // of autofitting to content (the OOXML default when `tblLayout` is\n // absent, which would make `columnWidths` a mere hint).\n layout: TableLayoutType.FIXED,\n width: { size: columnWidths.reduce((a, b) => a + b, 0), type: WidthType.DXA },\n }\n : { width: { size: 100, type: WidthType.PERCENTAGE } }),\n });\n}\n\nfunction makeCell(cell: SerializedCell, images: Map<string, ResolvedImage | null>): 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 const fill = cssColorToDocxHex(cell.attrs?.bgColor);\n return new TableCell({\n columnSpan: span,\n rowSpan,\n verticalAlign: VerticalAlign.TOP,\n shading: fill ? { type: ShadingType.CLEAR, color: \"auto\", fill } : undefined,\n children: [\n new Paragraph({\n alignment: alignFor(cell.attrs?.align),\n children: deltaToRuns(cell.delta, ARIAL, images),\n }),\n ],\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 images: Map<string, ResolvedImage | null>,\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, images);\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(\n embed: ImageEmbed,\n images: Map<string, ResolvedImage | null>,\n): ImageRun | null {\n const resolved = images.get(embed.src);\n if (!resolved) return null;\n return new ImageRun({\n data: resolved.data,\n type: resolved.type,\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\n/**\n * Default `resolveImage`: decode data URLs locally, fetch http(s) URLs, and\n * return null for anything else (caller counts it in `skippedImages`).\n */\nasync function defaultResolveImage(src: string): Promise<ResolvedImage | null> {\n const decoded = decodeDataUrl(src);\n if (decoded) return { data: decoded.bytes, type: decoded.kind };\n if (/^https?:\\/\\//i.test(src) && typeof fetch === \"function\") {\n try {\n const res = await fetch(src);\n if (!res.ok) return null;\n const ct = (res.headers.get(\"content-type\") ?? \"\").toLowerCase();\n const type: ResolvedImage[\"type\"] = ct.includes(\"png\")\n ? \"png\"\n : ct.includes(\"jpeg\") || ct.includes(\"jpg\")\n ? \"jpg\"\n : ct.includes(\"gif\")\n ? \"gif\"\n : ct.includes(\"bmp\")\n ? \"bmp\"\n : ct.includes(\"svg\")\n ? \"svg\"\n : (typeFromExtension(src) ?? \"png\");\n return { data: new Uint8Array(await res.arrayBuffer()), type };\n } catch {\n return null;\n }\n }\n return null;\n}\n\nfunction typeFromExtension(src: string): ResolvedImage[\"type\"] | null {\n const m = /\\.(png|jpe?g|gif|bmp|svg)(\\?|#|$)/i.exec(src);\n if (!m) return null;\n const ext = m[1].toLowerCase();\n return ext === \"jpeg\" || ext === \"jpg\" ? \"jpg\" : (ext as ResolvedImage[\"type\"]);\n}\n\n/**\n * Async pre-pass: collect every image `src` referenced by the document\n * (block deltas and table cell deltas), resolve them in parallel via\n * `resolve`, and return a lookup map plus the count of embeds that failed to\n * resolve. The rest of the build pipeline stays synchronous and reads from\n * this map.\n */\nasync function resolveAllImages(\n doc: SerializedDocument,\n resolve: (src: string) => Promise<ResolvedImage | null>,\n): Promise<{ images: Map<string, ResolvedImage | null>; skipped: number }> {\n const srcs = new Set<string>();\n const collect = (delta: DeltaOp[]) => {\n for (const op of delta) if (isImageEmbed(op.insert)) srcs.add(op.insert.src);\n };\n for (const block of doc.blocks) {\n collect(block.delta);\n for (const cell of block.cells ?? []) collect(cell.delta);\n }\n const images = new Map<string, ResolvedImage | null>();\n await Promise.all(\n [...srcs].map(async (src) => {\n let resolved: ResolvedImage | null = null;\n try {\n resolved = await resolve(src);\n } catch {\n resolved = null;\n }\n // docx's ImageRun requires a `fallback` for type \"svg\" (RegularImageOptions);\n // without it the constructor throws synchronously inside buildDocument,\n // which is outside this try/catch and would abort the whole export. Treat\n // svg as unresolved (skip) until we add fallback support.\n if (resolved?.type === \"svg\") resolved = null;\n images.set(src, resolved);\n }),\n );\n // skipped conta OCORRÊNCIAS de embed sem resolução (não srcs únicos).\n let skipped = 0;\n const count = (delta: DeltaOp[]) => {\n for (const op of delta)\n if (isImageEmbed(op.insert) && !images.get(op.insert.src)) skipped++;\n };\n for (const block of doc.blocks) {\n count(block.delta);\n for (const cell of block.cells ?? []) count(cell.delta);\n }\n return { images, skipped };\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
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { SerializedDocument, LegacySerializedDocument } from '@sofereditor/core';
|
|
2
2
|
|
|
3
|
+
interface ResolvedImage {
|
|
4
|
+
data: Uint8Array;
|
|
5
|
+
type: "png" | "jpg" | "gif" | "bmp" | "svg";
|
|
6
|
+
}
|
|
3
7
|
interface DocumentToDocxOptions {
|
|
4
8
|
/** Document title; embedded in core properties. */
|
|
5
9
|
title?: string;
|
|
6
10
|
/** Author for core properties. */
|
|
7
11
|
creator?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve um `src` de imagem para bytes. Default: data URLs (decodificadas
|
|
14
|
+
* localmente) e http(s) via fetch. Retornar null pula a imagem (contada em
|
|
15
|
+
* `skippedImages`) sem abortar o export.
|
|
16
|
+
*/
|
|
17
|
+
resolveImage?: (src: string) => Promise<ResolvedImage | null>;
|
|
8
18
|
}
|
|
9
19
|
/**
|
|
10
20
|
* Build a docx `Document` from a `SerializedDocument`. Output is a Blob you can
|
|
@@ -18,10 +28,18 @@ interface DocumentToDocxOptions {
|
|
|
18
28
|
* `columnSpan`; "covered" cells are skipped (docx infers cover from spans)
|
|
19
29
|
* - inline marks → TextRun props (bold, italics, underline, strike, color,
|
|
20
30
|
* font, size in half-points)
|
|
21
|
-
* - image embeds → ImageRun with
|
|
31
|
+
* - image embeds → ImageRun with bytes resolved via `resolveImage` (default:
|
|
32
|
+
* data URLs decoded locally, http(s) fetched; unresolved images are
|
|
33
|
+
* skipped and counted in `skippedImages` instead of aborting the export)
|
|
22
34
|
*/
|
|
23
|
-
declare function documentToDocxBlob(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<
|
|
35
|
+
declare function documentToDocxBlob(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<{
|
|
36
|
+
blob: Blob;
|
|
37
|
+
skippedImages: number;
|
|
38
|
+
}>;
|
|
24
39
|
/** Same as `documentToDocxBlob` but returns a Uint8Array (useful in Node/tests). */
|
|
25
|
-
declare function documentToDocxBuffer(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<
|
|
40
|
+
declare function documentToDocxBuffer(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<{
|
|
41
|
+
buffer: Uint8Array;
|
|
42
|
+
skippedImages: number;
|
|
43
|
+
}>;
|
|
26
44
|
|
|
27
|
-
export { type DocumentToDocxOptions, documentToDocxBlob, documentToDocxBuffer };
|
|
45
|
+
export { type DocumentToDocxOptions, type ResolvedImage, documentToDocxBlob, documentToDocxBuffer };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { SerializedDocument, LegacySerializedDocument } from '@sofereditor/core';
|
|
2
2
|
|
|
3
|
+
interface ResolvedImage {
|
|
4
|
+
data: Uint8Array;
|
|
5
|
+
type: "png" | "jpg" | "gif" | "bmp" | "svg";
|
|
6
|
+
}
|
|
3
7
|
interface DocumentToDocxOptions {
|
|
4
8
|
/** Document title; embedded in core properties. */
|
|
5
9
|
title?: string;
|
|
6
10
|
/** Author for core properties. */
|
|
7
11
|
creator?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve um `src` de imagem para bytes. Default: data URLs (decodificadas
|
|
14
|
+
* localmente) e http(s) via fetch. Retornar null pula a imagem (contada em
|
|
15
|
+
* `skippedImages`) sem abortar o export.
|
|
16
|
+
*/
|
|
17
|
+
resolveImage?: (src: string) => Promise<ResolvedImage | null>;
|
|
8
18
|
}
|
|
9
19
|
/**
|
|
10
20
|
* Build a docx `Document` from a `SerializedDocument`. Output is a Blob you can
|
|
@@ -18,10 +28,18 @@ interface DocumentToDocxOptions {
|
|
|
18
28
|
* `columnSpan`; "covered" cells are skipped (docx infers cover from spans)
|
|
19
29
|
* - inline marks → TextRun props (bold, italics, underline, strike, color,
|
|
20
30
|
* font, size in half-points)
|
|
21
|
-
* - image embeds → ImageRun with
|
|
31
|
+
* - image embeds → ImageRun with bytes resolved via `resolveImage` (default:
|
|
32
|
+
* data URLs decoded locally, http(s) fetched; unresolved images are
|
|
33
|
+
* skipped and counted in `skippedImages` instead of aborting the export)
|
|
22
34
|
*/
|
|
23
|
-
declare function documentToDocxBlob(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<
|
|
35
|
+
declare function documentToDocxBlob(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<{
|
|
36
|
+
blob: Blob;
|
|
37
|
+
skippedImages: number;
|
|
38
|
+
}>;
|
|
24
39
|
/** Same as `documentToDocxBlob` but returns a Uint8Array (useful in Node/tests). */
|
|
25
|
-
declare function documentToDocxBuffer(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<
|
|
40
|
+
declare function documentToDocxBuffer(doc: SerializedDocument | LegacySerializedDocument, options?: DocumentToDocxOptions): Promise<{
|
|
41
|
+
buffer: Uint8Array;
|
|
42
|
+
skippedImages: number;
|
|
43
|
+
}>;
|
|
26
44
|
|
|
27
|
-
export { type DocumentToDocxOptions, documentToDocxBlob, documentToDocxBuffer };
|
|
45
|
+
export { type DocumentToDocxOptions, type ResolvedImage, documentToDocxBlob, documentToDocxBuffer };
|