@sofereditor/export-docx 0.10.0 → 0.10.1
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 +6 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/package.json +10 -22
package/dist/index.cjs
CHANGED
|
@@ -213,7 +213,8 @@ function makeTable(block, images, settings) {
|
|
|
213
213
|
return new import_docx.Table({
|
|
214
214
|
rows: rowsOut,
|
|
215
215
|
borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),
|
|
216
|
-
width: { size: 100, type: import_docx.WidthType.PERCENTAGE }
|
|
216
|
+
width: { size: 100, type: import_docx.WidthType.PERCENTAGE },
|
|
217
|
+
visuallyRightToLeft: block.attrs.dir === "rtl" ? true : void 0
|
|
217
218
|
});
|
|
218
219
|
}
|
|
219
220
|
const larguraUtilTwips = (0, import_docx.convertMillimetersToTwip)(
|
|
@@ -229,7 +230,10 @@ function makeTable(block, images, settings) {
|
|
|
229
230
|
// O `layout: FIXED` permanece pelo mesmo motivo de antes: sem ele o Word
|
|
230
231
|
// autoajusta ao conteúdo e trata `columnWidths` como mero palpite.
|
|
231
232
|
layout: import_docx.TableLayoutType.FIXED,
|
|
232
|
-
width: { size: columnWidths.reduce((a, b) => a + b, 0), type: import_docx.WidthType.DXA }
|
|
233
|
+
width: { size: columnWidths.reduce((a, b) => a + b, 0), type: import_docx.WidthType.DXA },
|
|
234
|
+
// `w:bidiVisual`: o Word lê as células em ordem lógica (1ª = direita), que
|
|
235
|
+
// é exatamente como o modelo guarda — nada a reordenar.
|
|
236
|
+
visuallyRightToLeft: block.attrs.dir === "rtl" ? true : void 0
|
|
233
237
|
});
|
|
234
238
|
}
|
|
235
239
|
function docxTableBorders(preset, color) {
|
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 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":[]}
|
|
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 visuallyRightToLeft: block.attrs.dir === \"rtl\" ? true : undefined,\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 // `w:bidiVisual`: o Word lê as células em ordem lógica (1ª = direita), que\n // é exatamente como o modelo guarda — nada a reordenar.\n visuallyRightToLeft: block.attrs.dir === \"rtl\" ? true : undefined,\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
|
@@ -215,7 +215,8 @@ function makeTable(block, images, settings) {
|
|
|
215
215
|
return new Table({
|
|
216
216
|
rows: rowsOut,
|
|
217
217
|
borders: docxTableBorders(block.attrs.borderPreset, block.attrs.borderColor),
|
|
218
|
-
width: { size: 100, type: WidthType.PERCENTAGE }
|
|
218
|
+
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
219
|
+
visuallyRightToLeft: block.attrs.dir === "rtl" ? true : void 0
|
|
219
220
|
});
|
|
220
221
|
}
|
|
221
222
|
const larguraUtilTwips = convertMillimetersToTwip(
|
|
@@ -231,7 +232,10 @@ function makeTable(block, images, settings) {
|
|
|
231
232
|
// O `layout: FIXED` permanece pelo mesmo motivo de antes: sem ele o Word
|
|
232
233
|
// autoajusta ao conteúdo e trata `columnWidths` como mero palpite.
|
|
233
234
|
layout: TableLayoutType.FIXED,
|
|
234
|
-
width: { size: columnWidths.reduce((a, b) => a + b, 0), type: WidthType.DXA }
|
|
235
|
+
width: { size: columnWidths.reduce((a, b) => a + b, 0), type: WidthType.DXA },
|
|
236
|
+
// `w:bidiVisual`: o Word lê as células em ordem lógica (1ª = direita), que
|
|
237
|
+
// é exatamente como o modelo guarda — nada a reordenar.
|
|
238
|
+
visuallyRightToLeft: block.attrs.dir === "rtl" ? true : void 0
|
|
235
239
|
});
|
|
236
240
|
}
|
|
237
241
|
function docxTableBorders(preset, color) {
|
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 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":[]}
|
|
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 visuallyRightToLeft: block.attrs.dir === \"rtl\" ? true : undefined,\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 // `w:bidiVisual`: o Word lê as células em ordem lógica (1ª = direita), que\n // é exatamente como o modelo guarda — nada a reordenar.\n visuallyRightToLeft: block.attrs.dir === \"rtl\" ? true : undefined,\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.10.
|
|
3
|
+
"version": "0.10.1",
|
|
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",
|
|
@@ -34,27 +34,9 @@
|
|
|
34
34
|
"files": [
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
|
-
"publishConfig": {
|
|
38
|
-
"main": "./dist/index.cjs",
|
|
39
|
-
"module": "./dist/index.js",
|
|
40
|
-
"types": "./dist/index.d.ts",
|
|
41
|
-
"exports": {
|
|
42
|
-
".": {
|
|
43
|
-
"types": "./dist/index.d.ts",
|
|
44
|
-
"import": "./dist/index.js",
|
|
45
|
-
"require": "./dist/index.cjs"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
"scripts": {
|
|
50
|
-
"build": "tsup",
|
|
51
|
-
"dev": "tsup --watch",
|
|
52
|
-
"typecheck": "tsc --noEmit",
|
|
53
|
-
"test": "vitest run"
|
|
54
|
-
},
|
|
55
37
|
"dependencies": {
|
|
56
|
-
"
|
|
57
|
-
"
|
|
38
|
+
"docx": "^9.6.1",
|
|
39
|
+
"@sofereditor/core": "0.10.1"
|
|
58
40
|
},
|
|
59
41
|
"devDependencies": {
|
|
60
42
|
"jszip": "^3.10.1",
|
|
@@ -62,5 +44,11 @@
|
|
|
62
44
|
"typescript": "^5.7.2",
|
|
63
45
|
"vitest": "^2.1.8"
|
|
64
46
|
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"dev": "tsup --watch",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"test": "vitest run"
|
|
52
|
+
},
|
|
65
53
|
"module": "./dist/index.js"
|
|
66
|
-
}
|
|
54
|
+
}
|