@sofereditor/export-docx 0.9.0 → 0.10.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/dist/index.cjs +19 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +21 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -128,13 +128,13 @@ function blocksToDocxChildren(doc, images, settings) {
|
|
|
128
128
|
}
|
|
129
129
|
return out;
|
|
130
130
|
}
|
|
131
|
-
var
|
|
131
|
+
var VERDANA = { font: "Verdana" };
|
|
132
132
|
function makeParagraph(block, images) {
|
|
133
133
|
return new import_docx.Paragraph({
|
|
134
134
|
alignment: alignFor(block.attrs.align),
|
|
135
135
|
bidirectional: block.attrs.dir === "rtl" ? true : void 0,
|
|
136
136
|
...answerLineProps(block.attrs),
|
|
137
|
-
children: deltaToRuns(block.delta,
|
|
137
|
+
children: deltaToRuns(block.delta, VERDANA, images)
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
function answerLineProps(attrs) {
|
|
@@ -152,7 +152,7 @@ function makeHeading(block, images) {
|
|
|
152
152
|
return new import_docx.Paragraph({
|
|
153
153
|
heading: HEADING_LEVELS[level - 1],
|
|
154
154
|
alignment: alignFor(block.attrs.align),
|
|
155
|
-
children: deltaToRuns(block.delta,
|
|
155
|
+
children: deltaToRuns(block.delta, VERDANA, images)
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
function makeBlockquote(block, images) {
|
|
@@ -163,7 +163,7 @@ function makeBlockquote(block, images) {
|
|
|
163
163
|
border: {
|
|
164
164
|
left: { color: "CBD5E1", space: 12, style: import_docx.BorderStyle.SINGLE, size: 12 }
|
|
165
165
|
},
|
|
166
|
-
children: deltaToRuns(block.delta, { ...
|
|
166
|
+
children: deltaToRuns(block.delta, { ...VERDANA, italics: true }, images)
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
function makeCodeBlock(block, images) {
|
|
@@ -180,7 +180,7 @@ function makeListItem(block, images) {
|
|
|
180
180
|
return new import_docx.Paragraph({
|
|
181
181
|
numbering: { reference, level },
|
|
182
182
|
alignment: alignFor(block.attrs.align),
|
|
183
|
-
children: deltaToRuns(block.delta,
|
|
183
|
+
children: deltaToRuns(block.delta, VERDANA, images)
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
function makeTable(block, images, settings) {
|
|
@@ -258,24 +258,29 @@ function makeCell(cell, images) {
|
|
|
258
258
|
const span = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;
|
|
259
259
|
const rowSpan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;
|
|
260
260
|
const fill = cssColorToDocxHex(cell.attrs?.bgColor);
|
|
261
|
+
const kind = cell.attrs?.listKind;
|
|
262
|
+
const linhas = (0, import_core.splitDeltaByLines)(cell.delta);
|
|
261
263
|
return new import_docx.TableCell({
|
|
262
264
|
columnSpan: span,
|
|
263
265
|
rowSpan,
|
|
264
266
|
verticalAlign: import_docx.VerticalAlign.TOP,
|
|
265
267
|
shading: fill ? { type: import_docx.ShadingType.CLEAR, color: "auto", fill } : void 0,
|
|
266
|
-
children:
|
|
267
|
-
new import_docx.Paragraph({
|
|
268
|
+
children: linhas.map(
|
|
269
|
+
(linha) => new import_docx.Paragraph({
|
|
268
270
|
alignment: alignFor(cell.attrs?.align),
|
|
269
|
-
|
|
271
|
+
numbering: kind ? { reference: kind === "ordered" ? ORDERED_REF : BULLET_REF, level: 0 } : void 0,
|
|
272
|
+
children: deltaToRuns(linha, VERDANA, images)
|
|
270
273
|
})
|
|
271
|
-
|
|
274
|
+
)
|
|
272
275
|
});
|
|
273
276
|
}
|
|
274
277
|
function emptyCell() {
|
|
275
|
-
return new import_docx.TableCell({
|
|
278
|
+
return new import_docx.TableCell({
|
|
279
|
+
children: [new import_docx.Paragraph({ children: [new import_docx.TextRun({ text: "", font: VERDANA.font })] })]
|
|
280
|
+
});
|
|
276
281
|
}
|
|
277
282
|
function deltaToRuns(delta, defaults = {}, images) {
|
|
278
|
-
if (delta.length === 0) return [new import_docx.TextRun("")];
|
|
283
|
+
if (delta.length === 0) return [new import_docx.TextRun({ text: "", font: defaults.font })];
|
|
279
284
|
const out = [];
|
|
280
285
|
for (const op of delta) {
|
|
281
286
|
if (typeof op.insert === "string") {
|
|
@@ -289,6 +294,8 @@ function deltaToRuns(delta, defaults = {}, images) {
|
|
|
289
294
|
out.push(
|
|
290
295
|
new import_docx.TextRun({
|
|
291
296
|
text: embed.caption,
|
|
297
|
+
font: VERDANA.font,
|
|
298
|
+
// corpo: legenda segue a mesma fonte do texto
|
|
292
299
|
italics: true,
|
|
293
300
|
size: 18,
|
|
294
301
|
// 9pt — typical caption size
|
|
@@ -298,7 +305,7 @@ function deltaToRuns(delta, defaults = {}, images) {
|
|
|
298
305
|
}
|
|
299
306
|
}
|
|
300
307
|
}
|
|
301
|
-
if (out.length === 0) out.push(new import_docx.TextRun(""));
|
|
308
|
+
if (out.length === 0) out.push(new import_docx.TextRun({ text: "", font: defaults.font }));
|
|
302
309
|
return out;
|
|
303
310
|
}
|
|
304
311
|
function textRunProps(text, m, defaults) {
|
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, 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 TableBorderPreset,\n} from \"@sofereditor/core\";\nimport {\n DEFAULT_PAGE_SETTINGS,\n isImageEmbed,\n isLegacySerializedDocument,\n normalizarLarguras,\n pxToMm,\n type PageSettings,\n} from \"@sofereditor/core\";\nimport {\n AlignmentType,\n BorderStyle,\n Document,\n HeadingLevel,\n HeightRule,\n ImageRun,\n LevelFormat,\n LineRuleType,\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 * Só presente quando `type === \"svg\"`: o fallback raster já decodificado,\n * exigido pelo `ImageRun` do docx para `type: \"svg\"`. Preenchido\n * internamente por `resolveAllImages` a partir do `svgFallback` do embed —\n * nunca por um `resolveImage` customizado. Fica aqui (no valor resolvido\n * por `src`, não num mapa paralelo por embed) porque é exatamente isso que\n * \"resolvido\" significa para um svg: dois embeds com o mesmo `src` — um\n * com `svgFallback`, outro sem — compartilham o mesmo `src` e portanto o\n * mesmo SVG; o fallback de qualquer um dos dois serve para ambos.\n */\n svgFallback?: { data: Uint8Array; type: Exclude<ResolvedImage[\"type\"], \"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 settings = doc.pageSettings ?? DEFAULT_PAGE_SETTINGS;\n const children = blocksToDocxChildren(doc, images, 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 settings: PageSettings,\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, settings));\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 ...answerLineProps(block.attrs),\n children: deltaToRuns(block.delta, ARIAL, images),\n });\n}\n\n/**\n * Linha de resposta → borda inferior de parágrafo (`w:pBdr`) + entrelinha.\n * É como um documento profissional desenha uma pauta: a régua acompanha a\n * margem automaticamente, ao contrário de uma fileira de underlines.\n *\n * 240 twips = uma linha simples no OOXML.\n */\nfunction answerLineProps(attrs: SerializedBlock[\"attrs\"]) {\n if (attrs.answerLine !== true) return {};\n const spacing = attrs.answerLineSpacing ?? 1;\n return {\n border: {\n bottom: { style: BorderStyle.SINGLE, size: 6, color: \"000000\", space: 1 },\n },\n spacing: { line: Math.round(240 * spacing), lineRule: LineRuleType.AUTO },\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(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n settings: PageSettings,\n): 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 const alturas = block.attrs.rowHeights;\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 // `atLeast` e NÃO `exact`: é a mesma semântica de mínimo do editor. Com\n // `exact` o Word cortaria o conteúdo, e texto sumindo da prova é o pior\n // desfecho possível — pior que uma linha mais alta do que o pedido.\n // `Math.max(0, h)`: um documento antigo/malformado poderia trazer uma\n // entrada negativa em `rowHeights`; sem o piso, isso viraria um\n // `w:val` negativo no XML, que o Word rejeita.\n const raw = Array.isArray(alturas) && alturas.length === rows ? alturas[r] : undefined;\n const h = typeof raw === \"number\" && isFinite(raw) ? Math.max(0, raw) : undefined;\n rowsOut.push(\n new TableRow({\n children: cellsOut,\n ...(h != null\n ? { height: { value: convertMillimetersToTwip(pxToMm(h)), rule: HeightRule.ATLEAST } }\n : {}),\n }),\n );\n }\n\n // cols<=0 é uma tabela malformada (rows/cols ausentes no bloco) — sem coluna\n // nenhuma para distribuir largura, cai no fallback percentual histórico em\n // vez de produzir um `<w:gridCol w:w=\"0\"/>`.\n if (cols <= 0) {\n return new Table({\n rows: rowsOut,\n borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),\n width: { size: 100, type: WidthType.PERCENTAGE },\n });\n }\n\n // Proporção contra a largura útil REAL da página — é isto que faz a tabela\n // no Word ter a mesma largura do editor. Antes convertia a soma dos px do\n // modelo (513 px numa tabela que o professor via com 600). `pageSettings`\n // vem do documento (A4/Carta/Ofício/custom) e nunca de uma constante\n // cravada, senão qualquer tamanho de página que não A4 quebraria calado.\n const larguraUtilTwips = convertMillimetersToTwip(\n pxToMm(settings.width - settings.marginLeft - settings.marginRight),\n );\n const tabelaTwips = Math.round(larguraUtilTwips * ((block.attrs.tableWidth ?? 100) / 100));\n const pct = normalizarLarguras(block.attrs.colWidths, cols);\n const columnWidths = pct.map((p) => Math.round(tabelaTwips * (p / 100)));\n\n return new Table({\n rows: rowsOut,\n borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),\n columnWidths,\n // O `layout: FIXED` permanece pelo mesmo motivo de antes: sem ele o Word\n // autoajusta ao conteúdo e trata `columnWidths` como mero palpite.\n layout: TableLayoutType.FIXED,\n width: { size: columnWidths.reduce((a, b) => a + b, 0), type: WidthType.DXA },\n });\n}\n\n/**\n * Traduz o preset para `w:tblBorders`. Lado desligado = `BorderStyle.NONE`.\n *\n * A tabela-verdade sai direto de `cellBorderColors`: o que aquele helper liga em\n * TODA célula vira o par externo MAIS o interno correspondente. Ex.: em\n * `vertical`, toda célula tem left/right ligados — logo as laterais externas\n * (left/right) E as internas (insideV) ficam ligadas, e topo/base\n * (top/bottom/insideH) desligados.\n *\n * A tabela é a especificação; a função sai dela, não o contrário.\n */\nfunction docxTableBorders(preset: TableBorderPreset | undefined, color?: string) {\n // Cor inválida ou ausente cai no padrão: `cssColorToDocxHex` devolve undefined\n // para o que não souber converter.\n const hex = cssColorToDocxHex(color) ?? \"CBD5E1\";\n const on = { style: BorderStyle.SINGLE, size: 6, color: hex };\n const off = { style: BorderStyle.NONE, size: 0, color: \"auto\" };\n const s = (visivel: boolean) => (visivel ? on : off);\n\n // [top/bottom, left/right, insideH, insideV]\n const TRUTH: Record<TableBorderPreset, [boolean, boolean, boolean, boolean]> = {\n all: [true, true, true, true],\n outer: [true, true, false, false],\n horizontal: [true, false, true, false],\n vertical: [false, true, false, true],\n none: [false, false, false, false],\n };\n const [tb, lr, iH, iV] = TRUTH[preset ?? \"all\"];\n return {\n top: s(tb),\n bottom: s(tb),\n left: s(lr),\n right: s(lr),\n insideHorizontal: s(iH),\n insideVertical: s(iV),\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\n/**\n * Props de um `TextRun` a partir das marks. Fica numa função só porque\n * `makeTextRun` tem dois caminhos (linha única e multi-linha) que antes\n * duplicavam esta lista inteira — uma mark acrescentada em apenas um dos dois\n * se perdia silenciosamente em texto com quebra de linha.\n *\n * `highlight` sai como `w:shd` e NÃO como `w:highlight`: este último só aceita\n * ~15 valores nomeados e não sobreviveria a uma cor arbitrária do picker.\n */\nfunction textRunProps(text: string, m: MarkAttrs, defaults: RunDefaults) {\n const fill = cssColorToDocxHex(m.highlight);\n return {\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: parseFontSizeToHalfPoints(m.fontSize) ?? defaults.size,\n shading: fill ? { type: ShadingType.CLEAR, color: \"auto\", fill } : undefined,\n };\n}\n\nfunction makeTextRun(\n text: string,\n marks: MarkAttrs | undefined,\n defaults: RunDefaults,\n): TextRun {\n const m = marks ?? {};\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(textRunProps(text, m, defaults));\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(textRunProps(text.replace(/\\n/g, \" \"), m, defaults));\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 if (resolved.type === \"svg\") {\n // O fallback vem do `resolved` (por SRC), NÃO de `embed.svgFallback` (por\n // OCORRÊNCIA). Dois embeds com o mesmo src, um com svgFallback e outro\n // sem — caso real: uma prova salva antes do campo existir, com a mesma\n // fórmula inserida de novo depois — têm que resolver para o MESMO\n // ImageRun. Ler `embed.svgFallback` aqui faria a ocorrência sem fallback\n // sumir do documento sem entrar em `skippedImages`, porque\n // `resolveAllImages` já decidiu \"não pulado\" (o `src` tem fallback em\n // ALGUMA ocorrência) — o `null` devolvido aqui não seria contado.\n // `resolveAllImages` só deixa `resolved.type === \"svg\"` quando já\n // decodificou um fallback; o guarda existe porque o campo é opcional.\n if (!resolved.svgFallback) return null;\n return new ImageRun({\n type: \"svg\",\n data: resolved.data,\n fallback: { type: resolved.svgFallback.type, data: resolved.svgFallback.data },\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 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 matchedType: ResolvedImage[\"type\"] | null = 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 : null;\n // Unknown content-type (e.g. image/webp) with no recognizable extension\n // in the URL: skip rather than mislabeling bytes as PNG (would produce\n // a broken image in Word — contract is \"failure to resolve → skip\").\n const type = matchedType ?? typeFromExtension(src);\n if (!type) return null;\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 // svgFallback é campo do EMBED e o mapa de resolução é por SRC. Colher no\n // mesmo passo evita um segundo percurso do documento. Dois embeds com o\n // mesmo src e fallbacks diferentes é caso que não existe na prática (o src\n // é o próprio conteúdo em data URL); o primeiro ganha.\n const fallbacks = new Map<string, string>();\n const collect = (delta: DeltaOp[]) => {\n for (const op of delta) {\n if (!isImageEmbed(op.insert)) continue;\n srcs.add(op.insert.src);\n const fb = op.insert.svgFallback;\n if (fb && !fallbacks.has(op.insert.src)) fallbacks.set(op.insert.src, fb);\n }\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. SVG\n // deixa de ser descartado: o ImageRun aceita `type: \"svg\"` desde que\n // receba um `fallback` raster. Quem tem `svgFallback` passa; quem não\n // tem continua sendo pulado AQUI, para que `skipped` conte certo — não\n // inventamos um raster no servidor. O fallback é decodificado AQUI (uma\n // vez por src, não por embed) e guardado dentro do próprio\n // `ResolvedImage` — é isso que faz o \"primeiro ganha\" valer para toda\n // ocorrência do mesmo src, mesmo a que não trouxe `svgFallback`.\n if (resolved?.type === \"svg\") {\n const fb = fallbacks.get(src);\n const decodedFb = fb ? decodeDataUrl(fb) : null;\n // Um fallback com `kind === \"svg\"` não é fallback nenhum — é o\n // mesmo SVG de novo (`ImageRun({ type: \"svg\", fallback: { type:\n // \"svg\" } })` não tem sentido pro docx, que exige um raster aqui).\n // Na prática `fb` sempre vem de `svgToPngDataUrl` (sempre PNG), mas\n // `decodeDataUrl` é genérico; tratamos esse caso como \"sem\n // fallback válido\" em vez de deixar o tipo mentir.\n resolved =\n decodedFb && decodedFb.kind !== \"svg\"\n ? { ...resolved, svgFallback: { data: decodedFb.bytes, type: decodedFb.kind } }\n : null;\n }\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":[]}
|
|
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 TableBorderPreset,\n} from \"@sofereditor/core\";\nimport {\n DEFAULT_PAGE_SETTINGS,\n isImageEmbed,\n isLegacySerializedDocument,\n normalizarLarguras,\n pxToMm,\n splitDeltaByLines,\n type PageSettings,\n} from \"@sofereditor/core\";\nimport {\n AlignmentType,\n BorderStyle,\n Document,\n HeadingLevel,\n HeightRule,\n ImageRun,\n LevelFormat,\n LineRuleType,\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 * Só presente quando `type === \"svg\"`: o fallback raster já decodificado,\n * exigido pelo `ImageRun` do docx para `type: \"svg\"`. Preenchido\n * internamente por `resolveAllImages` a partir do `svgFallback` do embed —\n * nunca por um `resolveImage` customizado. Fica aqui (no valor resolvido\n * por `src`, não num mapa paralelo por embed) porque é exatamente isso que\n * \"resolvido\" significa para um svg: dois embeds com o mesmo `src` — um\n * com `svgFallback`, outro sem — compartilham o mesmo `src` e portanto o\n * mesmo SVG; o fallback de qualquer um dos dois serve para ambos.\n */\n svgFallback?: { data: Uint8Array; type: Exclude<ResolvedImage[\"type\"], \"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 settings = doc.pageSettings ?? DEFAULT_PAGE_SETTINGS;\n const children = blocksToDocxChildren(doc, images, 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 settings: PageSettings,\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, settings));\n break;\n default:\n out.push(makeParagraph(block, images));\n }\n }\n return out;\n}\n\n// ---------- block builders ----------\n\n// Padrão da escola: Verdana em todo texto de corpo, alinhada com o `.ed-root`\n// do editor e com o `@font-face` que o servidor embute no PDF. Os três\n// caminhos precisam nomear a MESMA fonte — até 08/2026 este arquivo dizia\n// \"Arial\" enquanto o editor renderizava Liberation Sans, e só não divergia\n// porque as duas são clones métricos. Verdana não tem clone.\n// Code block é a única exceção (Consolas, monospace é mais legível para\n// código). Itálico no blockquote é default estilístico.\nconst VERDANA: RunDefaults = { font: \"Verdana\" };\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 ...answerLineProps(block.attrs),\n children: deltaToRuns(block.delta, VERDANA, images),\n });\n}\n\n/**\n * Linha de resposta → borda inferior de parágrafo (`w:pBdr`) + entrelinha.\n * É como um documento profissional desenha uma pauta: a régua acompanha a\n * margem automaticamente, ao contrário de uma fileira de underlines.\n *\n * 240 twips = uma linha simples no OOXML.\n */\nfunction answerLineProps(attrs: SerializedBlock[\"attrs\"]) {\n if (attrs.answerLine !== true) return {};\n const spacing = attrs.answerLineSpacing ?? 1;\n return {\n border: {\n bottom: { style: BorderStyle.SINGLE, size: 6, color: \"000000\", space: 1 },\n },\n spacing: { line: Math.round(240 * spacing), lineRule: LineRuleType.AUTO },\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, VERDANA, 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, { ...VERDANA, 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, VERDANA, images),\n });\n}\n\nfunction makeTable(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n settings: PageSettings,\n): 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 const alturas = block.attrs.rowHeights;\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 // `atLeast` e NÃO `exact`: é a mesma semântica de mínimo do editor. Com\n // `exact` o Word cortaria o conteúdo, e texto sumindo da prova é o pior\n // desfecho possível — pior que uma linha mais alta do que o pedido.\n // `Math.max(0, h)`: um documento antigo/malformado poderia trazer uma\n // entrada negativa em `rowHeights`; sem o piso, isso viraria um\n // `w:val` negativo no XML, que o Word rejeita.\n const raw = Array.isArray(alturas) && alturas.length === rows ? alturas[r] : undefined;\n const h = typeof raw === \"number\" && isFinite(raw) ? Math.max(0, raw) : undefined;\n rowsOut.push(\n new TableRow({\n children: cellsOut,\n ...(h != null\n ? { height: { value: convertMillimetersToTwip(pxToMm(h)), rule: HeightRule.ATLEAST } }\n : {}),\n }),\n );\n }\n\n // cols<=0 é uma tabela malformada (rows/cols ausentes no bloco) — sem coluna\n // nenhuma para distribuir largura, cai no fallback percentual histórico em\n // vez de produzir um `<w:gridCol w:w=\"0\"/>`.\n if (cols <= 0) {\n return new Table({\n rows: rowsOut,\n borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),\n width: { size: 100, type: WidthType.PERCENTAGE },\n });\n }\n\n // Proporção contra a largura útil REAL da página — é isto que faz a tabela\n // no Word ter a mesma largura do editor. Antes convertia a soma dos px do\n // modelo (513 px numa tabela que o professor via com 600). `pageSettings`\n // vem do documento (A4/Carta/Ofício/custom) e nunca de uma constante\n // cravada, senão qualquer tamanho de página que não A4 quebraria calado.\n const larguraUtilTwips = convertMillimetersToTwip(\n pxToMm(settings.width - settings.marginLeft - settings.marginRight),\n );\n const tabelaTwips = Math.round(larguraUtilTwips * ((block.attrs.tableWidth ?? 100) / 100));\n const pct = normalizarLarguras(block.attrs.colWidths, cols);\n const columnWidths = pct.map((p) => Math.round(tabelaTwips * (p / 100)));\n\n return new Table({\n rows: rowsOut,\n borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),\n columnWidths,\n // O `layout: FIXED` permanece pelo mesmo motivo de antes: sem ele o Word\n // autoajusta ao conteúdo e trata `columnWidths` como mero palpite.\n layout: TableLayoutType.FIXED,\n width: { size: columnWidths.reduce((a, b) => a + b, 0), type: WidthType.DXA },\n });\n}\n\n/**\n * Traduz o preset para `w:tblBorders`. Lado desligado = `BorderStyle.NONE`.\n *\n * A tabela-verdade sai direto de `cellBorderColors`: o que aquele helper liga em\n * TODA célula vira o par externo MAIS o interno correspondente. Ex.: em\n * `vertical`, toda célula tem left/right ligados — logo as laterais externas\n * (left/right) E as internas (insideV) ficam ligadas, e topo/base\n * (top/bottom/insideH) desligados.\n *\n * A tabela é a especificação; a função sai dela, não o contrário.\n */\nfunction docxTableBorders(preset: TableBorderPreset | undefined, color?: string) {\n // Cor inválida ou ausente cai no padrão: `cssColorToDocxHex` devolve undefined\n // para o que não souber converter.\n const hex = cssColorToDocxHex(color) ?? \"CBD5E1\";\n const on = { style: BorderStyle.SINGLE, size: 6, color: hex };\n const off = { style: BorderStyle.NONE, size: 0, color: \"auto\" };\n const s = (visivel: boolean) => (visivel ? on : off);\n\n // [top/bottom, left/right, insideH, insideV]\n const TRUTH: Record<TableBorderPreset, [boolean, boolean, boolean, boolean]> = {\n all: [true, true, true, true],\n outer: [true, true, false, false],\n horizontal: [true, false, true, false],\n vertical: [false, true, false, true],\n none: [false, false, false, false],\n };\n const [tb, lr, iH, iV] = TRUTH[preset ?? \"all\"];\n return {\n top: s(tb),\n bottom: s(tb),\n left: s(lr),\n right: s(lr),\n insideHorizontal: s(iH),\n insideVertical: s(iV),\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 const kind = cell.attrs?.listKind;\n // Uma célula é um Y.Text plano com `\\n`. O Word renderiza `\\n` dentro de\n // <w:t> como espaço (ver deltaToRuns), então uma célula multilinha virava\n // UMA linha. Um <w:p> por linha resolve — e é o que a lista precisa também.\n const linhas = splitDeltaByLines(cell.delta);\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: linhas.map(\n (linha) =>\n new Paragraph({\n alignment: alignFor(cell.attrs?.align),\n numbering: kind\n ? { reference: kind === \"ordered\" ? ORDERED_REF : BULLET_REF, level: 0 }\n : undefined,\n children: deltaToRuns(linha, VERDANA, images),\n }),\n ),\n });\n}\n\nfunction emptyCell(): TableCell {\n return new TableCell({\n children: [new Paragraph({ children: [new TextRun({ text: \"\", font: VERDANA.font })] })],\n });\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({ text: \"\", font: defaults.font })];\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 font: VERDANA.font, // corpo: legenda segue a mesma fonte do texto\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({ text: \"\", font: defaults.font }));\n return out;\n}\n\n/**\n * Props de um `TextRun` a partir das marks. Fica numa função só porque\n * `makeTextRun` tem dois caminhos (linha única e multi-linha) que antes\n * duplicavam esta lista inteira — uma mark acrescentada em apenas um dos dois\n * se perdia silenciosamente em texto com quebra de linha.\n *\n * `highlight` sai como `w:shd` e NÃO como `w:highlight`: este último só aceita\n * ~15 valores nomeados e não sobreviveria a uma cor arbitrária do picker.\n */\nfunction textRunProps(text: string, m: MarkAttrs, defaults: RunDefaults) {\n const fill = cssColorToDocxHex(m.highlight);\n return {\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: parseFontSizeToHalfPoints(m.fontSize) ?? defaults.size,\n shading: fill ? { type: ShadingType.CLEAR, color: \"auto\", fill } : undefined,\n };\n}\n\nfunction makeTextRun(\n text: string,\n marks: MarkAttrs | undefined,\n defaults: RunDefaults,\n): TextRun {\n const m = marks ?? {};\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(textRunProps(text, m, defaults));\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(textRunProps(text.replace(/\\n/g, \" \"), m, defaults));\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 if (resolved.type === \"svg\") {\n // O fallback vem do `resolved` (por SRC), NÃO de `embed.svgFallback` (por\n // OCORRÊNCIA). Dois embeds com o mesmo src, um com svgFallback e outro\n // sem — caso real: uma prova salva antes do campo existir, com a mesma\n // fórmula inserida de novo depois — têm que resolver para o MESMO\n // ImageRun. Ler `embed.svgFallback` aqui faria a ocorrência sem fallback\n // sumir do documento sem entrar em `skippedImages`, porque\n // `resolveAllImages` já decidiu \"não pulado\" (o `src` tem fallback em\n // ALGUMA ocorrência) — o `null` devolvido aqui não seria contado.\n // `resolveAllImages` só deixa `resolved.type === \"svg\"` quando já\n // decodificou um fallback; o guarda existe porque o campo é opcional.\n if (!resolved.svgFallback) return null;\n return new ImageRun({\n type: \"svg\",\n data: resolved.data,\n fallback: { type: resolved.svgFallback.type, data: resolved.svgFallback.data },\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 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 matchedType: ResolvedImage[\"type\"] | null = 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 : null;\n // Unknown content-type (e.g. image/webp) with no recognizable extension\n // in the URL: skip rather than mislabeling bytes as PNG (would produce\n // a broken image in Word — contract is \"failure to resolve → skip\").\n const type = matchedType ?? typeFromExtension(src);\n if (!type) return null;\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 // svgFallback é campo do EMBED e o mapa de resolução é por SRC. Colher no\n // mesmo passo evita um segundo percurso do documento. Dois embeds com o\n // mesmo src e fallbacks diferentes é caso que não existe na prática (o src\n // é o próprio conteúdo em data URL); o primeiro ganha.\n const fallbacks = new Map<string, string>();\n const collect = (delta: DeltaOp[]) => {\n for (const op of delta) {\n if (!isImageEmbed(op.insert)) continue;\n srcs.add(op.insert.src);\n const fb = op.insert.svgFallback;\n if (fb && !fallbacks.has(op.insert.src)) fallbacks.set(op.insert.src, fb);\n }\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. SVG\n // deixa de ser descartado: o ImageRun aceita `type: \"svg\"` desde que\n // receba um `fallback` raster. Quem tem `svgFallback` passa; quem não\n // tem continua sendo pulado AQUI, para que `skipped` conte certo — não\n // inventamos um raster no servidor. O fallback é decodificado AQUI (uma\n // vez por src, não por embed) e guardado dentro do próprio\n // `ResolvedImage` — é isso que faz o \"primeiro ganha\" valer para toda\n // ocorrência do mesmo src, mesmo a que não trouxe `svgFallback`.\n if (resolved?.type === \"svg\") {\n const fb = fallbacks.get(src);\n const decodedFb = fb ? decodeDataUrl(fb) : null;\n // Um fallback com `kind === \"svg\"` não é fallback nenhum — é o\n // mesmo SVG de novo (`ImageRun({ type: \"svg\", fallback: { type:\n // \"svg\" } })` não tem sentido pro docx, que exige um raster aqui).\n // Na prática `fb` sempre vem de `svgToPngDataUrl` (sempre PNG), mas\n // `decodeDataUrl` é genérico; tratamos esse caso como \"sem\n // fallback válido\" em vez de deixar o tipo mentir.\n resolved =\n decodedFb && decodedFb.kind !== \"svg\"\n ? { ...resolved, svgFallback: { data: decodedFb.bytes, type: decodedFb.kind } }\n : null;\n }\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.js
CHANGED
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
isImageEmbed,
|
|
5
5
|
isLegacySerializedDocument,
|
|
6
6
|
normalizarLarguras,
|
|
7
|
-
pxToMm
|
|
7
|
+
pxToMm,
|
|
8
|
+
splitDeltaByLines
|
|
8
9
|
} from "@sofereditor/core";
|
|
9
10
|
import {
|
|
10
11
|
AlignmentType,
|
|
@@ -129,13 +130,13 @@ function blocksToDocxChildren(doc, images, settings) {
|
|
|
129
130
|
}
|
|
130
131
|
return out;
|
|
131
132
|
}
|
|
132
|
-
var
|
|
133
|
+
var VERDANA = { font: "Verdana" };
|
|
133
134
|
function makeParagraph(block, images) {
|
|
134
135
|
return new Paragraph({
|
|
135
136
|
alignment: alignFor(block.attrs.align),
|
|
136
137
|
bidirectional: block.attrs.dir === "rtl" ? true : void 0,
|
|
137
138
|
...answerLineProps(block.attrs),
|
|
138
|
-
children: deltaToRuns(block.delta,
|
|
139
|
+
children: deltaToRuns(block.delta, VERDANA, images)
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
function answerLineProps(attrs) {
|
|
@@ -153,7 +154,7 @@ function makeHeading(block, images) {
|
|
|
153
154
|
return new Paragraph({
|
|
154
155
|
heading: HEADING_LEVELS[level - 1],
|
|
155
156
|
alignment: alignFor(block.attrs.align),
|
|
156
|
-
children: deltaToRuns(block.delta,
|
|
157
|
+
children: deltaToRuns(block.delta, VERDANA, images)
|
|
157
158
|
});
|
|
158
159
|
}
|
|
159
160
|
function makeBlockquote(block, images) {
|
|
@@ -164,7 +165,7 @@ function makeBlockquote(block, images) {
|
|
|
164
165
|
border: {
|
|
165
166
|
left: { color: "CBD5E1", space: 12, style: BorderStyle.SINGLE, size: 12 }
|
|
166
167
|
},
|
|
167
|
-
children: deltaToRuns(block.delta, { ...
|
|
168
|
+
children: deltaToRuns(block.delta, { ...VERDANA, italics: true }, images)
|
|
168
169
|
});
|
|
169
170
|
}
|
|
170
171
|
function makeCodeBlock(block, images) {
|
|
@@ -181,7 +182,7 @@ function makeListItem(block, images) {
|
|
|
181
182
|
return new Paragraph({
|
|
182
183
|
numbering: { reference, level },
|
|
183
184
|
alignment: alignFor(block.attrs.align),
|
|
184
|
-
children: deltaToRuns(block.delta,
|
|
185
|
+
children: deltaToRuns(block.delta, VERDANA, images)
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
188
|
function makeTable(block, images, settings) {
|
|
@@ -259,24 +260,29 @@ function makeCell(cell, images) {
|
|
|
259
260
|
const span = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;
|
|
260
261
|
const rowSpan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;
|
|
261
262
|
const fill = cssColorToDocxHex(cell.attrs?.bgColor);
|
|
263
|
+
const kind = cell.attrs?.listKind;
|
|
264
|
+
const linhas = splitDeltaByLines(cell.delta);
|
|
262
265
|
return new TableCell({
|
|
263
266
|
columnSpan: span,
|
|
264
267
|
rowSpan,
|
|
265
268
|
verticalAlign: VerticalAlign.TOP,
|
|
266
269
|
shading: fill ? { type: ShadingType.CLEAR, color: "auto", fill } : void 0,
|
|
267
|
-
children:
|
|
268
|
-
new Paragraph({
|
|
270
|
+
children: linhas.map(
|
|
271
|
+
(linha) => new Paragraph({
|
|
269
272
|
alignment: alignFor(cell.attrs?.align),
|
|
270
|
-
|
|
273
|
+
numbering: kind ? { reference: kind === "ordered" ? ORDERED_REF : BULLET_REF, level: 0 } : void 0,
|
|
274
|
+
children: deltaToRuns(linha, VERDANA, images)
|
|
271
275
|
})
|
|
272
|
-
|
|
276
|
+
)
|
|
273
277
|
});
|
|
274
278
|
}
|
|
275
279
|
function emptyCell() {
|
|
276
|
-
return new TableCell({
|
|
280
|
+
return new TableCell({
|
|
281
|
+
children: [new Paragraph({ children: [new TextRun({ text: "", font: VERDANA.font })] })]
|
|
282
|
+
});
|
|
277
283
|
}
|
|
278
284
|
function deltaToRuns(delta, defaults = {}, images) {
|
|
279
|
-
if (delta.length === 0) return [new TextRun("")];
|
|
285
|
+
if (delta.length === 0) return [new TextRun({ text: "", font: defaults.font })];
|
|
280
286
|
const out = [];
|
|
281
287
|
for (const op of delta) {
|
|
282
288
|
if (typeof op.insert === "string") {
|
|
@@ -290,6 +296,8 @@ function deltaToRuns(delta, defaults = {}, images) {
|
|
|
290
296
|
out.push(
|
|
291
297
|
new TextRun({
|
|
292
298
|
text: embed.caption,
|
|
299
|
+
font: VERDANA.font,
|
|
300
|
+
// corpo: legenda segue a mesma fonte do texto
|
|
293
301
|
italics: true,
|
|
294
302
|
size: 18,
|
|
295
303
|
// 9pt — typical caption size
|
|
@@ -299,7 +307,7 @@ function deltaToRuns(delta, defaults = {}, images) {
|
|
|
299
307
|
}
|
|
300
308
|
}
|
|
301
309
|
}
|
|
302
|
-
if (out.length === 0) out.push(new TextRun(""));
|
|
310
|
+
if (out.length === 0) out.push(new TextRun({ text: "", font: defaults.font }));
|
|
303
311
|
return out;
|
|
304
312
|
}
|
|
305
313
|
function textRunProps(text, m, defaults) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/docx.ts"],"sourcesContent":["import type {\n AlignValue,\n DeltaOp,\n ImageEmbed,\n LegacySerializedDocument,\n ListKind,\n MarkAttrs,\n SerializedBlock,\n SerializedCell,\n SerializedDocument,\n TableBorderPreset,\n} from \"@sofereditor/core\";\nimport {\n DEFAULT_PAGE_SETTINGS,\n isImageEmbed,\n isLegacySerializedDocument,\n normalizarLarguras,\n pxToMm,\n type PageSettings,\n} from \"@sofereditor/core\";\nimport {\n AlignmentType,\n BorderStyle,\n Document,\n HeadingLevel,\n HeightRule,\n ImageRun,\n LevelFormat,\n LineRuleType,\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 * Só presente quando `type === \"svg\"`: o fallback raster já decodificado,\n * exigido pelo `ImageRun` do docx para `type: \"svg\"`. Preenchido\n * internamente por `resolveAllImages` a partir do `svgFallback` do embed —\n * nunca por um `resolveImage` customizado. Fica aqui (no valor resolvido\n * por `src`, não num mapa paralelo por embed) porque é exatamente isso que\n * \"resolvido\" significa para um svg: dois embeds com o mesmo `src` — um\n * com `svgFallback`, outro sem — compartilham o mesmo `src` e portanto o\n * mesmo SVG; o fallback de qualquer um dos dois serve para ambos.\n */\n svgFallback?: { data: Uint8Array; type: Exclude<ResolvedImage[\"type\"], \"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 settings = doc.pageSettings ?? DEFAULT_PAGE_SETTINGS;\n const children = blocksToDocxChildren(doc, images, 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 settings: PageSettings,\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, settings));\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 ...answerLineProps(block.attrs),\n children: deltaToRuns(block.delta, ARIAL, images),\n });\n}\n\n/**\n * Linha de resposta → borda inferior de parágrafo (`w:pBdr`) + entrelinha.\n * É como um documento profissional desenha uma pauta: a régua acompanha a\n * margem automaticamente, ao contrário de uma fileira de underlines.\n *\n * 240 twips = uma linha simples no OOXML.\n */\nfunction answerLineProps(attrs: SerializedBlock[\"attrs\"]) {\n if (attrs.answerLine !== true) return {};\n const spacing = attrs.answerLineSpacing ?? 1;\n return {\n border: {\n bottom: { style: BorderStyle.SINGLE, size: 6, color: \"000000\", space: 1 },\n },\n spacing: { line: Math.round(240 * spacing), lineRule: LineRuleType.AUTO },\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(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n settings: PageSettings,\n): 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 const alturas = block.attrs.rowHeights;\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 // `atLeast` e NÃO `exact`: é a mesma semântica de mínimo do editor. Com\n // `exact` o Word cortaria o conteúdo, e texto sumindo da prova é o pior\n // desfecho possível — pior que uma linha mais alta do que o pedido.\n // `Math.max(0, h)`: um documento antigo/malformado poderia trazer uma\n // entrada negativa em `rowHeights`; sem o piso, isso viraria um\n // `w:val` negativo no XML, que o Word rejeita.\n const raw = Array.isArray(alturas) && alturas.length === rows ? alturas[r] : undefined;\n const h = typeof raw === \"number\" && isFinite(raw) ? Math.max(0, raw) : undefined;\n rowsOut.push(\n new TableRow({\n children: cellsOut,\n ...(h != null\n ? { height: { value: convertMillimetersToTwip(pxToMm(h)), rule: HeightRule.ATLEAST } }\n : {}),\n }),\n );\n }\n\n // cols<=0 é uma tabela malformada (rows/cols ausentes no bloco) — sem coluna\n // nenhuma para distribuir largura, cai no fallback percentual histórico em\n // vez de produzir um `<w:gridCol w:w=\"0\"/>`.\n if (cols <= 0) {\n return new Table({\n rows: rowsOut,\n borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),\n width: { size: 100, type: WidthType.PERCENTAGE },\n });\n }\n\n // Proporção contra a largura útil REAL da página — é isto que faz a tabela\n // no Word ter a mesma largura do editor. Antes convertia a soma dos px do\n // modelo (513 px numa tabela que o professor via com 600). `pageSettings`\n // vem do documento (A4/Carta/Ofício/custom) e nunca de uma constante\n // cravada, senão qualquer tamanho de página que não A4 quebraria calado.\n const larguraUtilTwips = convertMillimetersToTwip(\n pxToMm(settings.width - settings.marginLeft - settings.marginRight),\n );\n const tabelaTwips = Math.round(larguraUtilTwips * ((block.attrs.tableWidth ?? 100) / 100));\n const pct = normalizarLarguras(block.attrs.colWidths, cols);\n const columnWidths = pct.map((p) => Math.round(tabelaTwips * (p / 100)));\n\n return new Table({\n rows: rowsOut,\n borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),\n columnWidths,\n // O `layout: FIXED` permanece pelo mesmo motivo de antes: sem ele o Word\n // autoajusta ao conteúdo e trata `columnWidths` como mero palpite.\n layout: TableLayoutType.FIXED,\n width: { size: columnWidths.reduce((a, b) => a + b, 0), type: WidthType.DXA },\n });\n}\n\n/**\n * Traduz o preset para `w:tblBorders`. Lado desligado = `BorderStyle.NONE`.\n *\n * A tabela-verdade sai direto de `cellBorderColors`: o que aquele helper liga em\n * TODA célula vira o par externo MAIS o interno correspondente. Ex.: em\n * `vertical`, toda célula tem left/right ligados — logo as laterais externas\n * (left/right) E as internas (insideV) ficam ligadas, e topo/base\n * (top/bottom/insideH) desligados.\n *\n * A tabela é a especificação; a função sai dela, não o contrário.\n */\nfunction docxTableBorders(preset: TableBorderPreset | undefined, color?: string) {\n // Cor inválida ou ausente cai no padrão: `cssColorToDocxHex` devolve undefined\n // para o que não souber converter.\n const hex = cssColorToDocxHex(color) ?? \"CBD5E1\";\n const on = { style: BorderStyle.SINGLE, size: 6, color: hex };\n const off = { style: BorderStyle.NONE, size: 0, color: \"auto\" };\n const s = (visivel: boolean) => (visivel ? on : off);\n\n // [top/bottom, left/right, insideH, insideV]\n const TRUTH: Record<TableBorderPreset, [boolean, boolean, boolean, boolean]> = {\n all: [true, true, true, true],\n outer: [true, true, false, false],\n horizontal: [true, false, true, false],\n vertical: [false, true, false, true],\n none: [false, false, false, false],\n };\n const [tb, lr, iH, iV] = TRUTH[preset ?? \"all\"];\n return {\n top: s(tb),\n bottom: s(tb),\n left: s(lr),\n right: s(lr),\n insideHorizontal: s(iH),\n insideVertical: s(iV),\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\n/**\n * Props de um `TextRun` a partir das marks. Fica numa função só porque\n * `makeTextRun` tem dois caminhos (linha única e multi-linha) que antes\n * duplicavam esta lista inteira — uma mark acrescentada em apenas um dos dois\n * se perdia silenciosamente em texto com quebra de linha.\n *\n * `highlight` sai como `w:shd` e NÃO como `w:highlight`: este último só aceita\n * ~15 valores nomeados e não sobreviveria a uma cor arbitrária do picker.\n */\nfunction textRunProps(text: string, m: MarkAttrs, defaults: RunDefaults) {\n const fill = cssColorToDocxHex(m.highlight);\n return {\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: parseFontSizeToHalfPoints(m.fontSize) ?? defaults.size,\n shading: fill ? { type: ShadingType.CLEAR, color: \"auto\", fill } : undefined,\n };\n}\n\nfunction makeTextRun(\n text: string,\n marks: MarkAttrs | undefined,\n defaults: RunDefaults,\n): TextRun {\n const m = marks ?? {};\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(textRunProps(text, m, defaults));\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(textRunProps(text.replace(/\\n/g, \" \"), m, defaults));\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 if (resolved.type === \"svg\") {\n // O fallback vem do `resolved` (por SRC), NÃO de `embed.svgFallback` (por\n // OCORRÊNCIA). Dois embeds com o mesmo src, um com svgFallback e outro\n // sem — caso real: uma prova salva antes do campo existir, com a mesma\n // fórmula inserida de novo depois — têm que resolver para o MESMO\n // ImageRun. Ler `embed.svgFallback` aqui faria a ocorrência sem fallback\n // sumir do documento sem entrar em `skippedImages`, porque\n // `resolveAllImages` já decidiu \"não pulado\" (o `src` tem fallback em\n // ALGUMA ocorrência) — o `null` devolvido aqui não seria contado.\n // `resolveAllImages` só deixa `resolved.type === \"svg\"` quando já\n // decodificou um fallback; o guarda existe porque o campo é opcional.\n if (!resolved.svgFallback) return null;\n return new ImageRun({\n type: \"svg\",\n data: resolved.data,\n fallback: { type: resolved.svgFallback.type, data: resolved.svgFallback.data },\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 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 matchedType: ResolvedImage[\"type\"] | null = 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 : null;\n // Unknown content-type (e.g. image/webp) with no recognizable extension\n // in the URL: skip rather than mislabeling bytes as PNG (would produce\n // a broken image in Word — contract is \"failure to resolve → skip\").\n const type = matchedType ?? typeFromExtension(src);\n if (!type) return null;\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 // svgFallback é campo do EMBED e o mapa de resolução é por SRC. Colher no\n // mesmo passo evita um segundo percurso do documento. Dois embeds com o\n // mesmo src e fallbacks diferentes é caso que não existe na prática (o src\n // é o próprio conteúdo em data URL); o primeiro ganha.\n const fallbacks = new Map<string, string>();\n const collect = (delta: DeltaOp[]) => {\n for (const op of delta) {\n if (!isImageEmbed(op.insert)) continue;\n srcs.add(op.insert.src);\n const fb = op.insert.svgFallback;\n if (fb && !fallbacks.has(op.insert.src)) fallbacks.set(op.insert.src, fb);\n }\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. SVG\n // deixa de ser descartado: o ImageRun aceita `type: \"svg\"` desde que\n // receba um `fallback` raster. Quem tem `svgFallback` passa; quem não\n // tem continua sendo pulado AQUI, para que `skipped` conte certo — não\n // inventamos um raster no servidor. O fallback é decodificado AQUI (uma\n // vez por src, não por embed) e guardado dentro do próprio\n // `ResolvedImage` — é isso que faz o \"primeiro ganha\" valer para toda\n // ocorrência do mesmo src, mesmo a que não trouxe `svgFallback`.\n if (resolved?.type === \"svg\") {\n const fb = fallbacks.get(src);\n const decodedFb = fb ? decodeDataUrl(fb) : null;\n // Um fallback com `kind === \"svg\"` não é fallback nenhum — é o\n // mesmo SVG de novo (`ImageRun({ type: \"svg\", fallback: { type:\n // \"svg\" } })` não tem sentido pro docx, que exige um raster aqui).\n // Na prática `fb` sempre vem de `svgToPngDataUrl` (sempre PNG), mas\n // `decodeDataUrl` é genérico; tratamos esse caso como \"sem\n // fallback válido\" em vez de deixar o tipo mentir.\n resolved =\n decodedFb && decodedFb.kind !== \"svg\"\n ? { ...resolved, svgFallback: { data: decodedFb.bytes, type: decodedFb.kind } }\n : null;\n }\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/docx.ts"],"sourcesContent":["import type {\n AlignValue,\n DeltaOp,\n ImageEmbed,\n LegacySerializedDocument,\n ListKind,\n MarkAttrs,\n SerializedBlock,\n SerializedCell,\n SerializedDocument,\n TableBorderPreset,\n} from \"@sofereditor/core\";\nimport {\n DEFAULT_PAGE_SETTINGS,\n isImageEmbed,\n isLegacySerializedDocument,\n normalizarLarguras,\n pxToMm,\n splitDeltaByLines,\n type PageSettings,\n} from \"@sofereditor/core\";\nimport {\n AlignmentType,\n BorderStyle,\n Document,\n HeadingLevel,\n HeightRule,\n ImageRun,\n LevelFormat,\n LineRuleType,\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 * Só presente quando `type === \"svg\"`: o fallback raster já decodificado,\n * exigido pelo `ImageRun` do docx para `type: \"svg\"`. Preenchido\n * internamente por `resolveAllImages` a partir do `svgFallback` do embed —\n * nunca por um `resolveImage` customizado. Fica aqui (no valor resolvido\n * por `src`, não num mapa paralelo por embed) porque é exatamente isso que\n * \"resolvido\" significa para um svg: dois embeds com o mesmo `src` — um\n * com `svgFallback`, outro sem — compartilham o mesmo `src` e portanto o\n * mesmo SVG; o fallback de qualquer um dos dois serve para ambos.\n */\n svgFallback?: { data: Uint8Array; type: Exclude<ResolvedImage[\"type\"], \"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 settings = doc.pageSettings ?? DEFAULT_PAGE_SETTINGS;\n const children = blocksToDocxChildren(doc, images, 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 settings: PageSettings,\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, settings));\n break;\n default:\n out.push(makeParagraph(block, images));\n }\n }\n return out;\n}\n\n// ---------- block builders ----------\n\n// Padrão da escola: Verdana em todo texto de corpo, alinhada com o `.ed-root`\n// do editor e com o `@font-face` que o servidor embute no PDF. Os três\n// caminhos precisam nomear a MESMA fonte — até 08/2026 este arquivo dizia\n// \"Arial\" enquanto o editor renderizava Liberation Sans, e só não divergia\n// porque as duas são clones métricos. Verdana não tem clone.\n// Code block é a única exceção (Consolas, monospace é mais legível para\n// código). Itálico no blockquote é default estilístico.\nconst VERDANA: RunDefaults = { font: \"Verdana\" };\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 ...answerLineProps(block.attrs),\n children: deltaToRuns(block.delta, VERDANA, images),\n });\n}\n\n/**\n * Linha de resposta → borda inferior de parágrafo (`w:pBdr`) + entrelinha.\n * É como um documento profissional desenha uma pauta: a régua acompanha a\n * margem automaticamente, ao contrário de uma fileira de underlines.\n *\n * 240 twips = uma linha simples no OOXML.\n */\nfunction answerLineProps(attrs: SerializedBlock[\"attrs\"]) {\n if (attrs.answerLine !== true) return {};\n const spacing = attrs.answerLineSpacing ?? 1;\n return {\n border: {\n bottom: { style: BorderStyle.SINGLE, size: 6, color: \"000000\", space: 1 },\n },\n spacing: { line: Math.round(240 * spacing), lineRule: LineRuleType.AUTO },\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, VERDANA, 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, { ...VERDANA, 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, VERDANA, images),\n });\n}\n\nfunction makeTable(\n block: SerializedBlock,\n images: Map<string, ResolvedImage | null>,\n settings: PageSettings,\n): 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 const alturas = block.attrs.rowHeights;\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 // `atLeast` e NÃO `exact`: é a mesma semântica de mínimo do editor. Com\n // `exact` o Word cortaria o conteúdo, e texto sumindo da prova é o pior\n // desfecho possível — pior que uma linha mais alta do que o pedido.\n // `Math.max(0, h)`: um documento antigo/malformado poderia trazer uma\n // entrada negativa em `rowHeights`; sem o piso, isso viraria um\n // `w:val` negativo no XML, que o Word rejeita.\n const raw = Array.isArray(alturas) && alturas.length === rows ? alturas[r] : undefined;\n const h = typeof raw === \"number\" && isFinite(raw) ? Math.max(0, raw) : undefined;\n rowsOut.push(\n new TableRow({\n children: cellsOut,\n ...(h != null\n ? { height: { value: convertMillimetersToTwip(pxToMm(h)), rule: HeightRule.ATLEAST } }\n : {}),\n }),\n );\n }\n\n // cols<=0 é uma tabela malformada (rows/cols ausentes no bloco) — sem coluna\n // nenhuma para distribuir largura, cai no fallback percentual histórico em\n // vez de produzir um `<w:gridCol w:w=\"0\"/>`.\n if (cols <= 0) {\n return new Table({\n rows: rowsOut,\n borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),\n width: { size: 100, type: WidthType.PERCENTAGE },\n });\n }\n\n // Proporção contra a largura útil REAL da página — é isto que faz a tabela\n // no Word ter a mesma largura do editor. Antes convertia a soma dos px do\n // modelo (513 px numa tabela que o professor via com 600). `pageSettings`\n // vem do documento (A4/Carta/Ofício/custom) e nunca de uma constante\n // cravada, senão qualquer tamanho de página que não A4 quebraria calado.\n const larguraUtilTwips = convertMillimetersToTwip(\n pxToMm(settings.width - settings.marginLeft - settings.marginRight),\n );\n const tabelaTwips = Math.round(larguraUtilTwips * ((block.attrs.tableWidth ?? 100) / 100));\n const pct = normalizarLarguras(block.attrs.colWidths, cols);\n const columnWidths = pct.map((p) => Math.round(tabelaTwips * (p / 100)));\n\n return new Table({\n rows: rowsOut,\n borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),\n columnWidths,\n // O `layout: FIXED` permanece pelo mesmo motivo de antes: sem ele o Word\n // autoajusta ao conteúdo e trata `columnWidths` como mero palpite.\n layout: TableLayoutType.FIXED,\n width: { size: columnWidths.reduce((a, b) => a + b, 0), type: WidthType.DXA },\n });\n}\n\n/**\n * Traduz o preset para `w:tblBorders`. Lado desligado = `BorderStyle.NONE`.\n *\n * A tabela-verdade sai direto de `cellBorderColors`: o que aquele helper liga em\n * TODA célula vira o par externo MAIS o interno correspondente. Ex.: em\n * `vertical`, toda célula tem left/right ligados — logo as laterais externas\n * (left/right) E as internas (insideV) ficam ligadas, e topo/base\n * (top/bottom/insideH) desligados.\n *\n * A tabela é a especificação; a função sai dela, não o contrário.\n */\nfunction docxTableBorders(preset: TableBorderPreset | undefined, color?: string) {\n // Cor inválida ou ausente cai no padrão: `cssColorToDocxHex` devolve undefined\n // para o que não souber converter.\n const hex = cssColorToDocxHex(color) ?? \"CBD5E1\";\n const on = { style: BorderStyle.SINGLE, size: 6, color: hex };\n const off = { style: BorderStyle.NONE, size: 0, color: \"auto\" };\n const s = (visivel: boolean) => (visivel ? on : off);\n\n // [top/bottom, left/right, insideH, insideV]\n const TRUTH: Record<TableBorderPreset, [boolean, boolean, boolean, boolean]> = {\n all: [true, true, true, true],\n outer: [true, true, false, false],\n horizontal: [true, false, true, false],\n vertical: [false, true, false, true],\n none: [false, false, false, false],\n };\n const [tb, lr, iH, iV] = TRUTH[preset ?? \"all\"];\n return {\n top: s(tb),\n bottom: s(tb),\n left: s(lr),\n right: s(lr),\n insideHorizontal: s(iH),\n insideVertical: s(iV),\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 const kind = cell.attrs?.listKind;\n // Uma célula é um Y.Text plano com `\\n`. O Word renderiza `\\n` dentro de\n // <w:t> como espaço (ver deltaToRuns), então uma célula multilinha virava\n // UMA linha. Um <w:p> por linha resolve — e é o que a lista precisa também.\n const linhas = splitDeltaByLines(cell.delta);\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: linhas.map(\n (linha) =>\n new Paragraph({\n alignment: alignFor(cell.attrs?.align),\n numbering: kind\n ? { reference: kind === \"ordered\" ? ORDERED_REF : BULLET_REF, level: 0 }\n : undefined,\n children: deltaToRuns(linha, VERDANA, images),\n }),\n ),\n });\n}\n\nfunction emptyCell(): TableCell {\n return new TableCell({\n children: [new Paragraph({ children: [new TextRun({ text: \"\", font: VERDANA.font })] })],\n });\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({ text: \"\", font: defaults.font })];\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 font: VERDANA.font, // corpo: legenda segue a mesma fonte do texto\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({ text: \"\", font: defaults.font }));\n return out;\n}\n\n/**\n * Props de um `TextRun` a partir das marks. Fica numa função só porque\n * `makeTextRun` tem dois caminhos (linha única e multi-linha) que antes\n * duplicavam esta lista inteira — uma mark acrescentada em apenas um dos dois\n * se perdia silenciosamente em texto com quebra de linha.\n *\n * `highlight` sai como `w:shd` e NÃO como `w:highlight`: este último só aceita\n * ~15 valores nomeados e não sobreviveria a uma cor arbitrária do picker.\n */\nfunction textRunProps(text: string, m: MarkAttrs, defaults: RunDefaults) {\n const fill = cssColorToDocxHex(m.highlight);\n return {\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: parseFontSizeToHalfPoints(m.fontSize) ?? defaults.size,\n shading: fill ? { type: ShadingType.CLEAR, color: \"auto\", fill } : undefined,\n };\n}\n\nfunction makeTextRun(\n text: string,\n marks: MarkAttrs | undefined,\n defaults: RunDefaults,\n): TextRun {\n const m = marks ?? {};\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(textRunProps(text, m, defaults));\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(textRunProps(text.replace(/\\n/g, \" \"), m, defaults));\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 if (resolved.type === \"svg\") {\n // O fallback vem do `resolved` (por SRC), NÃO de `embed.svgFallback` (por\n // OCORRÊNCIA). Dois embeds com o mesmo src, um com svgFallback e outro\n // sem — caso real: uma prova salva antes do campo existir, com a mesma\n // fórmula inserida de novo depois — têm que resolver para o MESMO\n // ImageRun. Ler `embed.svgFallback` aqui faria a ocorrência sem fallback\n // sumir do documento sem entrar em `skippedImages`, porque\n // `resolveAllImages` já decidiu \"não pulado\" (o `src` tem fallback em\n // ALGUMA ocorrência) — o `null` devolvido aqui não seria contado.\n // `resolveAllImages` só deixa `resolved.type === \"svg\"` quando já\n // decodificou um fallback; o guarda existe porque o campo é opcional.\n if (!resolved.svgFallback) return null;\n return new ImageRun({\n type: \"svg\",\n data: resolved.data,\n fallback: { type: resolved.svgFallback.type, data: resolved.svgFallback.data },\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 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 matchedType: ResolvedImage[\"type\"] | null = 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 : null;\n // Unknown content-type (e.g. image/webp) with no recognizable extension\n // in the URL: skip rather than mislabeling bytes as PNG (would produce\n // a broken image in Word — contract is \"failure to resolve → skip\").\n const type = matchedType ?? typeFromExtension(src);\n if (!type) return null;\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 // svgFallback é campo do EMBED e o mapa de resolução é por SRC. Colher no\n // mesmo passo evita um segundo percurso do documento. Dois embeds com o\n // mesmo src e fallbacks diferentes é caso que não existe na prática (o src\n // é o próprio conteúdo em data URL); o primeiro ganha.\n const fallbacks = new Map<string, string>();\n const collect = (delta: DeltaOp[]) => {\n for (const op of delta) {\n if (!isImageEmbed(op.insert)) continue;\n srcs.add(op.insert.src);\n const fb = op.insert.svgFallback;\n if (fb && !fallbacks.has(op.insert.src)) fallbacks.set(op.insert.src, fb);\n }\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. SVG\n // deixa de ser descartado: o ImageRun aceita `type: \"svg\"` desde que\n // receba um `fallback` raster. Quem tem `svgFallback` passa; quem não\n // tem continua sendo pulado AQUI, para que `skipped` conte certo — não\n // inventamos um raster no servidor. O fallback é decodificado AQUI (uma\n // vez por src, não por embed) e guardado dentro do próprio\n // `ResolvedImage` — é isso que faz o \"primeiro ganha\" valer para toda\n // ocorrência do mesmo src, mesmo a que não trouxe `svgFallback`.\n if (resolved?.type === \"svg\") {\n const fb = fallbacks.get(src);\n const decodedFb = fb ? decodeDataUrl(fb) : null;\n // Um fallback com `kind === \"svg\"` não é fallback nenhum — é o\n // mesmo SVG de novo (`ImageRun({ type: \"svg\", fallback: { type:\n // \"svg\" } })` não tem sentido pro docx, que exige um raster aqui).\n // Na prática `fb` sempre vem de `svgToPngDataUrl` (sempre PNG), mas\n // `decodeDataUrl` é genérico; tratamos esse caso como \"sem\n // fallback válido\" em vez de deixar o tipo mentir.\n resolved =\n decodedFb && decodedFb.kind !== \"svg\"\n ? { ...resolved, svgFallback: { data: decodedFb.bytes, type: decodedFb.kind } }\n : null;\n }\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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sofereditor/export-docx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "@sofereditor/export-docx — Emit .docx from a Sofer SerializedDocument. Round-trip with @sofereditor/import-docx.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Rafael Marreca",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"test": "vitest run"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@sofereditor/core": "^0.
|
|
56
|
+
"@sofereditor/core": "^0.10.0",
|
|
57
57
|
"docx": "^9.6.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|