@sofereditor/export-pdf 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +37 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +47 -7
- package/dist/index.js.map +1 -1
- package/package.json +16 -4
package/dist/index.cjs
CHANGED
|
@@ -152,17 +152,33 @@ function renderTable(block) {
|
|
|
152
152
|
const cells = block.cells ?? [];
|
|
153
153
|
if (rows <= 0 || cols <= 0) return "";
|
|
154
154
|
const colGroup = renderColGroup(block.attrs.colWidths, cols);
|
|
155
|
+
const preset = block.attrs.borderPreset;
|
|
156
|
+
const borderColor = block.attrs.borderColor;
|
|
155
157
|
const trs = [];
|
|
156
158
|
for (let r = 0; r < rows; r++) {
|
|
157
159
|
const tds = [];
|
|
158
160
|
for (let c = 0; c < cols; c++) {
|
|
159
161
|
const cell = cells[r * cols + c];
|
|
162
|
+
const at = (rowspan2, colspan2) => ({
|
|
163
|
+
row: r,
|
|
164
|
+
col: c,
|
|
165
|
+
rowspan: rowspan2,
|
|
166
|
+
colspan: colspan2,
|
|
167
|
+
cols,
|
|
168
|
+
rowStart: 0,
|
|
169
|
+
rowEnd: rows
|
|
170
|
+
});
|
|
160
171
|
if (!cell) {
|
|
161
|
-
|
|
172
|
+
const style = (0, import_core.styleToCssText)((0, import_core.cellBorderStyle)(preset, at(1, 1), "print", borderColor));
|
|
173
|
+
tds.push(`<td class="ed-cell" style="${style}"></td>`);
|
|
162
174
|
continue;
|
|
163
175
|
}
|
|
164
176
|
if (cell.attrs?.covered) continue;
|
|
165
|
-
|
|
177
|
+
const rowspan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;
|
|
178
|
+
const colspan = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;
|
|
179
|
+
tds.push(
|
|
180
|
+
renderCell(cell, (0, import_core.cellBorderStyle)(preset, at(rowspan, colspan), "print", borderColor))
|
|
181
|
+
);
|
|
166
182
|
}
|
|
167
183
|
trs.push(`<tr>${tds.join("")}</tr>`);
|
|
168
184
|
}
|
|
@@ -177,10 +193,10 @@ function renderColGroup(widths, cols) {
|
|
|
177
193
|
}
|
|
178
194
|
return `<colgroup>${cells.join("")}</colgroup>`;
|
|
179
195
|
}
|
|
180
|
-
function renderCell(cell) {
|
|
196
|
+
function renderCell(cell, border) {
|
|
181
197
|
const rs = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? ` rowspan="${cell.attrs.rowspan}"` : "";
|
|
182
198
|
const cs = cell.attrs?.colspan && cell.attrs.colspan > 1 ? ` colspan="${cell.attrs.colspan}"` : "";
|
|
183
|
-
const styles = [];
|
|
199
|
+
const styles = [(0, import_core.styleToCssText)(border)];
|
|
184
200
|
const align = clampAlign(cell.attrs?.align);
|
|
185
201
|
if (align) styles.push(`text-align:${align}`);
|
|
186
202
|
if (cell.attrs?.bgColor) styles.push(`background-color:${cssValue(cell.attrs.bgColor)}`);
|
|
@@ -194,7 +210,7 @@ function renderInline(delta) {
|
|
|
194
210
|
for (const op of delta) {
|
|
195
211
|
if (typeof op.insert === "string") {
|
|
196
212
|
if (op.insert.length === 0) continue;
|
|
197
|
-
parts.push(applyMarks(
|
|
213
|
+
parts.push(applyMarks(decorateBlanks(op.insert), op.attributes));
|
|
198
214
|
} else if ((0, import_core.isImageEmbed)(op.insert)) {
|
|
199
215
|
const layout = op.insert.layout ?? "inline";
|
|
200
216
|
if (layout === "wrap-left" || layout === "wrap-right") {
|
|
@@ -207,6 +223,14 @@ function renderInline(delta) {
|
|
|
207
223
|
if (wraps.length === 0 && parts.length === 0) return "<br>";
|
|
208
224
|
return wraps.join("") + parts.join("");
|
|
209
225
|
}
|
|
226
|
+
function decorateBlanks(text) {
|
|
227
|
+
const segs = (0, import_core.splitUnderscoreRuns)(text);
|
|
228
|
+
if (segs.length === 1 && !segs[0].blank) return escapeHtml(text);
|
|
229
|
+
const css = (0, import_core.styleToCssText)(import_core.BLANK_STYLE);
|
|
230
|
+
return segs.map(
|
|
231
|
+
(s) => s.blank ? `<span data-blank="true" style="${css}">${escapeHtml(s.text)}</span>` : escapeHtml(s.text)
|
|
232
|
+
).join("");
|
|
233
|
+
}
|
|
210
234
|
function applyMarks(text, attrs) {
|
|
211
235
|
if (!attrs) return text;
|
|
212
236
|
let html = text;
|
|
@@ -216,6 +240,7 @@ function applyMarks(text, attrs) {
|
|
|
216
240
|
if (attrs.strike) html = `<s>${html}</s>`;
|
|
217
241
|
const styles = [];
|
|
218
242
|
if (attrs.color) styles.push(`color:${cssValue(attrs.color)}`);
|
|
243
|
+
if (attrs.highlight) styles.push(`background-color:${cssValue(attrs.highlight)}`);
|
|
219
244
|
if (attrs.fontFamily) styles.push(`font-family:${cssValue(attrs.fontFamily)}`);
|
|
220
245
|
if (attrs.fontSize) styles.push(`font-size:${cssValue(attrs.fontSize)}`);
|
|
221
246
|
if (styles.length > 0) html = `<span style="${styles.join(";")}">${html}</span>`;
|
|
@@ -299,7 +324,9 @@ function blockAttrs(baseClass, attrs) {
|
|
|
299
324
|
if (align) classes.push(`ed-align-${align}`);
|
|
300
325
|
const dir = clampDir(attrs.dir);
|
|
301
326
|
const dirAttr = dir ? ` dir="${dir}"` : "";
|
|
302
|
-
|
|
327
|
+
const answer = (0, import_core.answerLineStyle)(attrs);
|
|
328
|
+
const styleAttr = answer ? ` style="${(0, import_core.styleToCssText)(answer)}"` : "";
|
|
329
|
+
return ` class="${classes.join(" ")}"${dirAttr}${styleAttr}`;
|
|
303
330
|
}
|
|
304
331
|
function clampHeadingLevel(l) {
|
|
305
332
|
if (typeof l !== "number") return 1;
|
|
@@ -431,6 +458,10 @@ function serializePaginatedHtml(root, options = {}) {
|
|
|
431
458
|
window.print(), onde "gr\xE1ficos de fundo" vem desmarcado por padr\xE3o. */
|
|
432
459
|
* { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
|
|
433
460
|
@media print {
|
|
461
|
+
/* Guias de tela das bordas desligadas por preset. O snapshot clona o DOM
|
|
462
|
+
vivo do editor, ent\xE3o elas viriam junto \u2014 aqui somem. A geometria n\xE3o
|
|
463
|
+
muda: o lado continua com 1px, s\xF3 perde a cor. */
|
|
464
|
+
.ed-root, .ed-page, .ed-table, .ed-cell { --ed-guide-color: transparent !important; }
|
|
434
465
|
@page { size: ${pageSize.width} ${pageSize.height}; margin: 0; }
|
|
435
466
|
.ed-page { box-shadow: none !important; border: none !important; border-radius: 0 !important; margin: 0 !important; page-break-after: always; break-after: page; }
|
|
436
467
|
.ed-page:last-child { page-break-after: auto; break-after: auto; }
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/html.ts","../src/pdf.ts","../src/download.ts"],"sourcesContent":["export {\n documentToHtml,\n documentToHtmlFragment,\n documentToJson,\n} from \"./html\";\nexport type { DocumentToHtmlOptions } from \"./html\";\nexport {\n exportPdfFromDocument,\n exportPdfFromElement,\n serializePaginatedHtml,\n} from \"./pdf\";\nexport type {\n ExportPdfFromDocumentOptions,\n ExportPdfFromElementOptions,\n SerializePaginatedHtmlOptions,\n} from \"./pdf\";\nexport { downloadBlob, downloadString } from \"./download\";\n","import type {\n AlignValue,\n BlockAttrs,\n DeltaOp,\n DirValue,\n ImageEmbed,\n LegacySerializedDocument,\n ListKind,\n MarkAttrs,\n PageSettings,\n SerializedBlock,\n SerializedCell,\n SerializedDocument,\n} from \"@sofereditor/core\";\nimport { isImageEmbed, isLegacySerializedDocument, pxToMm } from \"@sofereditor/core\";\n\nfunction normalize(doc: SerializedDocument | LegacySerializedDocument): SerializedDocument {\n return isLegacySerializedDocument(doc) ? { blocks: doc } : doc;\n}\n\nexport interface DocumentToHtmlOptions {\n /** Document title (set on `<title>` and used as the file name hint). Default \"Documento\". */\n title?: string;\n /** When true (default) emits a full `<html>` document. When false emits only the body fragment. */\n standalone?: boolean;\n /** Extra CSS appended after the default print stylesheet. Only honored when `standalone`. */\n extraCss?: string;\n /** Page geometry hints injected into `@page`. Defaults to A4. */\n page?: {\n width: string;\n height: string;\n marginTop: string;\n marginBottom: string;\n marginLeft: string;\n marginRight: string;\n };\n}\n\nconst DEFAULT_PAGE = {\n width: \"210mm\",\n height: \"297mm\",\n marginTop: \"25mm\",\n marginBottom: \"25mm\",\n marginLeft: \"25mm\",\n marginRight: \"25mm\",\n};\n\n/**\n * Serialize a `SerializedDocument` to standalone HTML.\n *\n * Output structure mirrors what `@sofereditor/react` paints at runtime: each\n * top-level block becomes one element (`<p>`, `<h1..6>`, `<blockquote>`, `<pre>`,\n * `<table>`), consecutive `listItem` blocks are reconstructed into nested\n * `<ul>`/`<ol>` trees, and image embeds are emitted as `<img>` tags carrying the\n * embed's data URL (so the export is self-contained).\n *\n * NOT a fragment of the live editor DOM — this walks the model so the output is\n * editor-state agnostic (no caret artifacts, no zero-width phantom anchors).\n */\nexport function documentToHtml(\n doc: SerializedDocument | LegacySerializedDocument,\n options: DocumentToHtmlOptions = {},\n): string {\n const normalized = normalize(doc);\n const body = documentToHtmlFragment(normalized);\n if (options.standalone === false) return body;\n const page = options.page ?? pageFromSettings(normalized.pageSettings);\n const title = escapeHtml(options.title ?? \"Documento\");\n const extra = options.extraCss ?? \"\";\n return [\n \"<!doctype html>\",\n `<html lang=\"pt-BR\"><head><meta charset=\"utf-8\"><title>${title}</title>`,\n `<style>${baseStylesheet(page)}${extra}</style>`,\n \"</head><body>\",\n body,\n \"</body></html>\",\n ].join(\"\");\n}\n\nfunction pageFromSettings(s: PageSettings | undefined): NonNullable<DocumentToHtmlOptions[\"page\"]> {\n if (!s) return DEFAULT_PAGE;\n const fmt = (px: number) => `${pxToMm(px).toFixed(2)}mm`;\n return {\n width: fmt(s.width),\n height: fmt(s.height),\n marginTop: fmt(s.marginTop),\n marginBottom: fmt(s.marginBottom),\n marginLeft: fmt(s.marginLeft),\n marginRight: fmt(s.marginRight),\n };\n}\n\n/** Body-only HTML for embedding in other documents. */\nexport function documentToHtmlFragment(\n doc: SerializedDocument | LegacySerializedDocument,\n): string {\n const blocks = normalize(doc).blocks;\n const out: string[] = [];\n let i = 0;\n while (i < blocks.length) {\n const block = blocks[i];\n if (block.type === \"listItem\") {\n const groupStart = i;\n const kind: ListKind = block.attrs.listKind === \"ordered\" ? \"ordered\" : \"bullet\";\n const items: SerializedBlock[] = [];\n while (\n i < blocks.length &&\n blocks[i].type === \"listItem\" &&\n (blocks[i].attrs.listKind ?? \"bullet\") === kind\n ) {\n items.push(blocks[i]);\n i++;\n }\n out.push(renderListGroup(items, kind, groupStart));\n continue;\n }\n out.push(renderBlock(block, i));\n i++;\n }\n return out.join(\"\");\n}\n\n/** JSON export — a stable serialization for save/load round-trips. */\nexport function documentToJson(\n doc: SerializedDocument | LegacySerializedDocument,\n pretty = true,\n): string {\n return JSON.stringify(normalize(doc), null, pretty ? 2 : 0);\n}\n\n// ---------- blocks ----------\n\nfunction renderBlock(block: SerializedBlock, index: number): string {\n switch (block.type) {\n case \"paragraph\":\n return `<p${blockAttrs(\"ed-paragraph\", block.attrs)}>${renderInline(block.delta)}</p>`;\n case \"heading\": {\n const level = clampHeadingLevel(block.attrs.level);\n return `<h${level}${blockAttrs(`ed-heading ed-heading-${level}`, block.attrs)}>${renderInline(\n block.delta,\n )}</h${level}>`;\n }\n case \"blockquote\":\n return `<blockquote${blockAttrs(\"ed-blockquote\", block.attrs)}>${renderInline(\n block.delta,\n )}</blockquote>`;\n case \"codeBlock\": {\n const lang = block.attrs.language;\n const codeAttrs = lang ? ` class=\"language-${escapeAttr(lang)}\"` : \"\";\n return `<pre${blockAttrs(\"ed-codeblock\", block.attrs)}><code${codeAttrs}>${renderInline(\n block.delta,\n )}</code></pre>`;\n }\n case \"table\":\n return renderTable(block);\n default:\n return `<p${blockAttrs(\"ed-paragraph\", block.attrs)}>${renderInline(block.delta)}</p>`;\n }\n}\n\nfunction renderListGroup(items: SerializedBlock[], kind: ListKind, key: number): string {\n // Tree-build using listLevel just like Editor.buildListTree.\n interface Node { item: SerializedBlock; children: Node[]; }\n const roots: Node[] = [];\n const stack: { children: Node[]; level: number }[] = [{ children: roots, level: -1 }];\n for (const it of items) {\n const level = clampListLevel(it.attrs.listLevel);\n while (stack.length > 1 && stack[stack.length - 1].level >= level) stack.pop();\n const node: Node = { item: it, children: [] };\n stack[stack.length - 1].children.push(node);\n stack.push({ children: node.children, level });\n }\n return renderListTree(roots, kind, `list-${key}`);\n}\n\nfunction renderListTree(nodes: { item: SerializedBlock; children: any[] }[], kind: ListKind, _key: string): string {\n const Tag = kind === \"ordered\" ? \"ol\" : \"ul\";\n const cls = `ed-list ed-list-${kind}`;\n const items = nodes\n .map((n) => {\n const inner = renderInline(n.item.delta);\n const nested =\n n.children.length > 0 ? renderListTree(n.children, kind, _key + \"n\") : \"\";\n return `<li${blockAttrs(\"ed-listitem\", n.item.attrs)}>${inner}${nested}</li>`;\n })\n .join(\"\");\n return `<${Tag} class=\"${cls}\" data-list-kind=\"${kind}\">${items}</${Tag}>`;\n}\n\nfunction renderTable(block: SerializedBlock): string {\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 if (rows <= 0 || cols <= 0) return \"\";\n\n const colGroup = renderColGroup(block.attrs.colWidths, cols);\n const trs: string[] = [];\n for (let r = 0; r < rows; r++) {\n const tds: string[] = [];\n for (let c = 0; c < cols; c++) {\n const cell = cells[r * cols + c];\n if (!cell) {\n tds.push(\"<td class=\\\"ed-cell\\\"></td>\");\n continue;\n }\n if (cell.attrs?.covered) continue;\n tds.push(renderCell(cell));\n }\n trs.push(`<tr>${tds.join(\"\")}</tr>`);\n }\n return `<div class=\"ed-table-wrap\"><table class=\"ed-table\">${colGroup}<tbody>${trs.join(\"\")}</tbody></table></div>`;\n}\n\nfunction renderColGroup(widths: number[] | undefined, cols: number): string {\n if (!widths || widths.length === 0) return \"\";\n const cells: string[] = [];\n for (let i = 0; i < cols; i++) {\n const w = widths[i];\n cells.push(typeof w === \"number\" ? `<col style=\"width:${w}px\">` : \"<col>\");\n }\n return `<colgroup>${cells.join(\"\")}</colgroup>`;\n}\n\nfunction renderCell(cell: SerializedCell): string {\n const rs = cell.attrs?.rowspan && cell.attrs.rowspan > 1\n ? ` rowspan=\"${cell.attrs.rowspan}\"`\n : \"\";\n const cs = cell.attrs?.colspan && cell.attrs.colspan > 1\n ? ` colspan=\"${cell.attrs.colspan}\"`\n : \"\";\n const styles: string[] = [];\n const align = clampAlign(cell.attrs?.align);\n if (align) styles.push(`text-align:${align}`);\n if (cell.attrs?.bgColor) styles.push(`background-color:${cssValue(cell.attrs.bgColor)}`);\n const style = styles.length > 0 ? ` style=\"${styles.join(\";\")}\"` : \"\";\n return `<td class=\"ed-cell\"${rs}${cs}${style}>${renderInline(cell.delta)}</td>`;\n}\n\n// ---------- inline ----------\n\nfunction renderInline(delta: DeltaOp[]): string {\n if (delta.length === 0) return \"<br>\";\n const parts: string[] = [];\n // Floating wrap-left/wrap-right embeds are emitted FIRST so CSS float wraps\n // the following inline text — mirrors the in-editor renderer.\n const wraps: string[] = [];\n for (const op of delta) {\n if (typeof op.insert === \"string\") {\n if (op.insert.length === 0) continue;\n parts.push(applyMarks(escapeHtml(op.insert), op.attributes));\n } else if (isImageEmbed(op.insert)) {\n const layout = op.insert.layout ?? \"inline\";\n if (layout === \"wrap-left\" || layout === \"wrap-right\") {\n wraps.push(renderImg(op.insert));\n } else {\n parts.push(renderImg(op.insert));\n }\n }\n }\n if (wraps.length === 0 && parts.length === 0) return \"<br>\";\n return wraps.join(\"\") + parts.join(\"\");\n}\n\nfunction applyMarks(text: string, attrs: MarkAttrs | undefined): string {\n if (!attrs) return text;\n let html = text;\n if (attrs.bold) html = `<strong>${html}</strong>`;\n if (attrs.italic) html = `<em>${html}</em>`;\n if (attrs.underline) html = `<u>${html}</u>`;\n if (attrs.strike) html = `<s>${html}</s>`;\n const styles: string[] = [];\n if (attrs.color) styles.push(`color:${cssValue(attrs.color)}`);\n if (attrs.fontFamily) styles.push(`font-family:${cssValue(attrs.fontFamily)}`);\n if (attrs.fontSize) styles.push(`font-size:${cssValue(attrs.fontSize)}`);\n if (styles.length > 0) html = `<span style=\"${styles.join(\";\")}\">${html}</span>`;\n if (attrs.link?.href) {\n const t = attrs.link.title ? ` title=\"${escapeAttr(attrs.link.title)}\"` : \"\";\n html = `<a href=\"${escapeAttr(attrs.link.href)}\"${t} rel=\"noopener noreferrer\" target=\"_blank\">${html}</a>`;\n }\n if (attrs.comment?.markId) {\n html = `<span data-comment-id=\"${escapeAttr(attrs.comment.markId)}\">${html}</span>`;\n }\n return html;\n}\n\nfunction renderImg(embed: ImageEmbed): string {\n const layout = embed.layout ?? \"inline\";\n const align = embed.align;\n const ox = embed.offsetX ?? 0;\n const oy = embed.offsetY ?? 0;\n const hasCaption = !!embed.caption && embed.caption.length > 0;\n\n // The wrapper carries the positioning (float/absolute/inline-block/block).\n // The img keeps the intrinsic size when wrapped; when there's no caption we\n // keep the historical bare `<img>` output for unchanged DOM/CSS.\n const wrapperStyles: string[] = [];\n switch (layout) {\n case \"wrap-left\":\n wrapperStyles.push(\n \"float:left\",\n `width:${embed.width}px`,\n `margin-right:${Math.max(8, ox)}px`,\n `margin-top:${Math.max(0, oy)}px`,\n \"margin-bottom:4px\",\n );\n break;\n case \"wrap-right\":\n wrapperStyles.push(\n \"float:right\",\n `width:${embed.width}px`,\n `margin-left:${Math.max(8, ox)}px`,\n `margin-top:${Math.max(0, oy)}px`,\n \"margin-bottom:4px\",\n );\n break;\n case \"behind\":\n case \"front\":\n wrapperStyles.push(\n \"position:absolute\",\n `left:${ox}px`,\n `top:${oy}px`,\n `width:${embed.width}px`,\n `z-index:${layout === \"behind\" ? -1 : 2}`,\n );\n break;\n case \"inline\":\n default:\n if (align) {\n wrapperStyles.push(\n \"display:block\",\n `width:${embed.width}px`,\n `margin-left:${align === \"left\" ? \"0\" : \"auto\"}`,\n `margin-right:${align === \"right\" ? \"0\" : \"auto\"}`,\n );\n } else {\n wrapperStyles.push(\n \"display:inline-block\",\n \"vertical-align:text-bottom\",\n `width:${embed.width}px`,\n );\n }\n break;\n }\n\n const imgStyles: string[] = hasCaption\n ? [`width:${embed.width}px`, `height:${embed.height}px`, \"display:block\"]\n : wrapperStyles.concat(\n // Preserve old height when emitting bare <img>.\n `height:${embed.height}px`,\n );\n\n const imgHtml = `<img src=\"${escapeAttr(embed.src)}\" width=\"${embed.width}\" height=\"${embed.height}\" alt=\"${escapeAttr(embed.caption ?? \"\")}\" data-embed=\"image\" data-embed-layout=\"${layout}\"${\n align ? ` data-embed-align=\"${escapeAttr(align)}\"` : \"\"\n } style=\"${imgStyles.join(\";\")}\">`;\n\n if (!hasCaption) return imgHtml;\n\n const captionAlign = embed.captionAlign ?? \"center\";\n return `<figure class=\"ed-figure\" data-embed-figure=\"true\" data-embed-layout=\"${layout}\" style=\"${wrapperStyles.join(\";\")}\">${imgHtml}<figcaption class=\"ed-figcaption\" data-caption-align=\"${captionAlign}\" style=\"text-align:${captionAlign}\">${escapeHtml(\n embed.caption!,\n )}</figcaption></figure>`;\n}\n\n// ---------- helpers ----------\n\nfunction blockAttrs(baseClass: string, attrs: BlockAttrs): string {\n const classes = [baseClass, \"ed-block\"];\n const align = clampAlign(attrs.align);\n if (align) classes.push(`ed-align-${align}`);\n const dir = clampDir(attrs.dir);\n const dirAttr = dir ? ` dir=\"${dir}\"` : \"\";\n return ` class=\"${classes.join(\" \")}\"${dirAttr}`;\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 clampAlign(a: unknown): AlignValue | undefined {\n if (a === \"left\" || a === \"center\" || a === \"right\" || a === \"justify\") return a;\n return undefined;\n}\n\nfunction clampDir(d: unknown): DirValue | undefined {\n if (d === \"ltr\" || d === \"rtl\") return d;\n return undefined;\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\n/** Allow only safe characters in CSS values — defense in depth against injected `</style>`. */\nfunction cssValue(v: string): string {\n return v.replace(/[<>\"';\\\\]/g, \"\");\n}\n\nfunction baseStylesheet(page: NonNullable<DocumentToHtmlOptions[\"page\"]>): string {\n // Margins are owned by `@page` only. Chromium honors that block both for\n // `window.print()` (client-side `exportPdfFromDocument`) and for\n // `page.pdf({ preferCSSPageSize: true })` (server-side `renderPdf` in\n // `@sofereditor/export-pdf-server`). Adding the same offsets via `body { padding }`\n // would compound and double the margin in the output PDF.\n return `\n@page {\n size: ${page.width} ${page.height};\n margin: ${page.marginTop} ${page.marginRight} ${page.marginBottom} ${page.marginLeft};\n}\nhtml, body { margin: 0; padding: 0; background: white; color: #1a1a1f; }\n/* Sem isto, o diálogo de impressão descarta background-color/imagens de\n fundo por padrão (\"gráficos de fundo\" desmarcado) — ex.: células com\n bgColor da tabela de pontuação saíam brancas no PDF. */\n* { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\nbody {\n font-family: \"Arial\", \"Helvetica\", sans-serif;\n font-size: 11pt;\n line-height: 1.5;\n}\n.ed-paragraph { margin: 0 0 0.5em; min-height: 1.5em; }\n.ed-heading { margin: 0.5em 0 0.25em; font-weight: 600; line-height: 1.25; }\n.ed-heading-1 { font-size: 28pt; }\n.ed-heading-2 { font-size: 22pt; }\n.ed-heading-3 { font-size: 18pt; }\n.ed-heading-4 { font-size: 14pt; }\n.ed-heading-5 { font-size: 12pt; }\n.ed-heading-6 { font-size: 11pt; color: #6b7280; }\n.ed-blockquote {\n margin: 0 0 0.5em; padding: 0.25em 12px;\n border-inline-start: 3px solid #e5e7eb; color: #6b7280;\n}\n.ed-codeblock {\n margin: 0 0 0.5em; background: #0f172a; color: #e2e8f0;\n padding: 12px 14px; border-radius: 6px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11pt; white-space: pre-wrap;\n}\n.ed-codeblock code { font-family: inherit; background: transparent; color: inherit; }\n.ed-block[dir=\"rtl\"] { text-align: start; }\n.ed-align-left { text-align: left; }\n.ed-align-center { text-align: center; }\n.ed-align-right { text-align: right; }\n.ed-align-justify { text-align: justify; }\n.ed-list { margin: 0 0 0.5em; padding-inline-start: 28px; }\n.ed-list .ed-list { margin-block: 0; }\n.ed-list-bullet { list-style-type: disc; }\n.ed-list-bullet .ed-list-bullet { list-style-type: circle; }\n.ed-list-bullet .ed-list-bullet .ed-list-bullet { list-style-type: square; }\n.ed-list-ordered { list-style-type: decimal; }\n.ed-list-ordered .ed-list-ordered { list-style-type: lower-alpha; }\n.ed-list-ordered .ed-list-ordered .ed-list-ordered { list-style-type: lower-roman; }\n.ed-listitem { margin: 0; min-height: 1.5em; }\n.ed-table-wrap { margin: 0 0 0.5em; position: relative; }\n.ed-table { width: 100%; border-collapse: collapse; table-layout: fixed; }\n.ed-cell {\n border: 1px solid #cbd5e1; padding: 6px 8px;\n vertical-align: top; word-break: break-word; white-space: pre-wrap;\n}\nimg[data-embed=\"image\"] { max-width: 100%; }\n.ed-block { position: relative; isolation: isolate; }\na { color: #2563eb; }\n`;\n}\n","import type { LegacySerializedDocument, SerializedDocument } from \"@sofereditor/core\";\nimport { documentToHtml, type DocumentToHtmlOptions } from \"./html\";\n\nexport interface ExportPdfFromDocumentOptions extends DocumentToHtmlOptions {\n /** Suggested file name (used by the OS save dialog as the default). */\n filename?: string;\n}\n\n/**\n * Open the browser's print dialog for a `SerializedDocument`.\n *\n * Strategy: render the document to standalone HTML in a hidden iframe and call\n * `iframe.contentWindow.print()`. The user picks \"Save as PDF\" (or any printer)\n * from the native dialog. No external PDF library required.\n *\n * The returned promise resolves after the print dialog closes (`afterprint`).\n */\nexport async function exportPdfFromDocument(\n doc: SerializedDocument | LegacySerializedDocument,\n options: ExportPdfFromDocumentOptions = {},\n): Promise<void> {\n const html = documentToHtml(doc, { ...options, standalone: true });\n await printHtmlInIframe(html, options.filename ?? options.title ?? \"Documento\");\n}\n\nexport interface SerializePaginatedHtmlOptions {\n /** Document title (becomes `<title>` and is used as default filename). */\n title?: string;\n /** Extra CSS appended to the cloned styles (e.g. for headers/footers). */\n extraCss?: string;\n /**\n * Override the `@page { size }`. Default reads `--ed-page-width` /\n * `--ed-page-height` CSS variables off the root (set by `<Editor>` from the\n * current `PageSettings`); falls back to A4 if absent.\n */\n pageSize?: { width: string; height: string };\n}\n\nexport interface ExportPdfFromElementOptions extends SerializePaginatedHtmlOptions {\n /** Suggested file name. */\n filename?: string;\n}\n\nfunction cssPageSizeFromRoot(\n cs: CSSStyleDeclaration | undefined,\n): { width: string; height: string } {\n if (!cs) return { width: \"210mm\", height: \"297mm\" };\n const w = cs.getPropertyValue(\"--ed-page-width\").trim();\n const h = cs.getPropertyValue(\"--ed-page-height\").trim();\n if (!w || !h) return { width: \"210mm\", height: \"297mm\" };\n // CSS vars come back as e.g. \"794px\". `@page` accepts px directly.\n return { width: w, height: h };\n}\n\n/**\n * Serialize a live editor root (the `.ed-root` paginated DOM) into a\n * self-contained HTML string. The output preserves the in-editor pagination\n * decisions verbatim — each `.ed-page` becomes a fixed-size page in the final\n * print medium with no re-layout possible.\n *\n * The returned HTML inlines every accessible stylesheet from the host document\n * (or links it as a fallback for cross-origin sheets) so the layout survives\n * transplant to an iframe, a saved file, or a headless renderer.\n *\n * This is the building block for two paths:\n * - `exportPdfFromElement` (this file): writes the HTML into a hidden iframe\n * and calls `window.print()` for the local \"Save as PDF\" flow.\n * - Upload-to-server snapshots: POST the string to a backend that converts it\n * to PDF via Puppeteer's `setContent + page.pdf()` without re-running the\n * pagination engine.\n */\nexport function serializePaginatedHtml(\n root: HTMLElement,\n options: SerializePaginatedHtmlOptions = {},\n): string {\n const clone = root.cloneNode(true) as HTMLElement;\n // Strip interactive scaffolding that print styles can't see ahead of time.\n clone.querySelectorAll(\"[contenteditable]\").forEach((el) => {\n (el as HTMLElement).removeAttribute(\"contenteditable\");\n });\n clone.querySelectorAll(\".ed-image-overlay\").forEach((el) => el.remove());\n clone.querySelectorAll(\".ed-remote-cursors\").forEach((el) => el.remove());\n\n // Read the page size baked into the editor via `--ed-page-*` CSS variables.\n // The Editor component sets these on its root from the current PageSettings\n // (`packages/react/src/Editor.tsx`). Falls back to A4 if we can't read them.\n const cs = root.ownerDocument?.defaultView?.getComputedStyle(root);\n const pageSize = options.pageSize ?? cssPageSizeFromRoot(cs);\n\n const styles = collectStyles();\n const title = escapeHtml(options.title ?? \"Documento\");\n const extra = options.extraCss ?? \"\";\n // The `@page { size }` and page-break rules live inside `@media print` so\n // they merge cleanly with any host-document print stylesheet that already\n // declares its own `@page` (e.g. `apps/playground/src/styles.css`). Putting\n // ours after the cloned `<style>`s gives our override last-write-wins.\n return [\n \"<!doctype html>\",\n `<html><head><meta charset=\"utf-8\"><meta name=\"ed-print-snapshot\" content=\"1\"><title>${title}</title>`,\n styles,\n `<style>\n html, body { margin: 0; padding: 0; background: white; }\n /* Preserva background-color (ex.: células da tabela de pontuação) no\n window.print(), onde \"gráficos de fundo\" vem desmarcado por padrão. */\n * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\n @media print {\n @page { size: ${pageSize.width} ${pageSize.height}; margin: 0; }\n .ed-page { box-shadow: none !important; border: none !important; border-radius: 0 !important; margin: 0 !important; page-break-after: always; break-after: page; }\n .ed-page:last-child { page-break-after: auto; break-after: auto; }\n .ed-image-overlay { display: none !important; }\n }\n ${extra}\n </style>`,\n \"</head><body>\",\n clone.outerHTML,\n \"</body></html>\",\n ].join(\"\");\n}\n\n/**\n * Snapshot a live editor root element (the `.ed-root` paginated DOM) and open\n * the browser's print dialog. Preserves the in-editor pagination decisions\n * verbatim — no re-layout in the print frame.\n *\n * For the \"send the snapshot to a server\" use case, use\n * `serializePaginatedHtml` directly and POST the returned string.\n */\nexport async function exportPdfFromElement(\n root: HTMLElement,\n options: ExportPdfFromElementOptions = {},\n): Promise<void> {\n const html = serializePaginatedHtml(root, options);\n await printHtmlInIframe(html, options.filename ?? options.title ?? \"Documento\");\n}\n\n// ---------- internals ----------\n\nasync function printHtmlInIframe(html: string, _filename: string): Promise<void> {\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\"aria-hidden\", \"true\");\n iframe.style.cssText =\n \"position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden;\";\n document.body.appendChild(iframe);\n\n const win = iframe.contentWindow;\n const docu = iframe.contentDocument ?? win?.document;\n if (!win || !docu) {\n iframe.remove();\n throw new Error(\"export-pdf: failed to access iframe document\");\n }\n\n docu.open();\n docu.write(html);\n docu.close();\n\n // Wait for the iframe document to finish loading (DOM, stylesheets, images).\n await new Promise<void>((resolve) => {\n if (docu.readyState === \"complete\") resolve();\n else iframe.addEventListener(\"load\", () => resolve(), { once: true });\n });\n // Best-effort wait for embedded images: print dialogs that snapshot too early\n // miss late-loading data URIs (rare, but real).\n await waitForImages(docu);\n // CRITICAL: wait for @font-face fonts to finish loading. The `load` event does\n // NOT wait for web fonts, so printing too early triggers FOIT (Flash of\n // Invisible Text): the browser hides every glyph until its font loads, while\n // text-decoration underlines still paint. Symptom seen in the wild: the\n // letterhead + field underlines printed but ALL text was missing. `fonts.ready`\n // resolves once all used fonts are loaded; cap the wait so a font that never\n // resolves can't hang the print.\n await waitForFonts(docu);\n\n await new Promise<void>((resolve) => {\n const cleanup = () => {\n win.removeEventListener(\"afterprint\", cleanup);\n // Defer removal one tick so Safari's print queue doesn't choke.\n setTimeout(() => iframe.remove(), 0);\n resolve();\n };\n win.addEventListener(\"afterprint\", cleanup);\n win.focus();\n win.print();\n // Fallback in case afterprint never fires (older browsers).\n setTimeout(() => {\n if (iframe.isConnected) cleanup();\n }, 60_000);\n });\n}\n\nfunction collectStyles(): string {\n const parts: string[] = [];\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n const rules = sheet.cssRules;\n if (!rules) continue;\n const css = Array.from(rules)\n .map((r) => r.cssText)\n .join(\"\\n\");\n if (css) parts.push(`<style>${css}</style>`);\n } catch {\n // Cross-origin sheet — fall back to linking it.\n const href = sheet.href;\n if (href) parts.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(href)}\">`);\n }\n }\n return parts.join(\"\\n\");\n}\n\nasync function waitForImages(docu: Document): Promise<void> {\n const imgs = Array.from(docu.images);\n await Promise.all(\n imgs.map(\n (img) =>\n new Promise<void>((resolve) => {\n if (img.complete) return resolve();\n img.addEventListener(\"load\", () => resolve(), { once: true });\n img.addEventListener(\"error\", () => resolve(), { once: true });\n }),\n ),\n );\n}\n\n/**\n * Wait for the iframe document's web fonts (`@font-face`) to finish loading,\n * capped so a font that never resolves can't hang the print. Without this the\n * print can fire mid-font-load and render invisible text (FOIT).\n */\nasync function waitForFonts(docu: Document): Promise<void> {\n const fonts = (docu as Document & { fonts?: FontFaceSet }).fonts;\n if (!fonts?.ready) return;\n await Promise.race([\n fonts.ready.then(() => undefined),\n new Promise<void>((resolve) => {\n setTimeout(resolve, 3000);\n }),\n ]);\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\nfunction escapeAttr(s: string): string {\n return escapeHtml(s);\n}\n","/**\n * Trigger a browser download for an in-memory blob/string. Tiny utility — kept\n * in this package because PDF/HTML/JSON exporters all want it.\n */\nexport function downloadBlob(blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n a.rel = \"noopener\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n setTimeout(() => {\n a.remove();\n URL.revokeObjectURL(url);\n }, 0);\n}\n\nexport function downloadString(\n text: string,\n filename: string,\n mimeType = \"text/plain;charset=utf-8\",\n): void {\n downloadBlob(new Blob([text], { type: mimeType }), filename);\n}\n"],"mappings":"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","names":["escapeHtml","escapeAttr","HTML_ESCAPES"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/html.ts","../src/pdf.ts","../src/download.ts"],"sourcesContent":["export {\n documentToHtml,\n documentToHtmlFragment,\n documentToJson,\n} from \"./html\";\nexport type { DocumentToHtmlOptions } from \"./html\";\nexport {\n exportPdfFromDocument,\n exportPdfFromElement,\n serializePaginatedHtml,\n} from \"./pdf\";\nexport type {\n ExportPdfFromDocumentOptions,\n ExportPdfFromElementOptions,\n SerializePaginatedHtmlOptions,\n} from \"./pdf\";\nexport { downloadBlob, downloadString } from \"./download\";\n","import type {\n AlignValue,\n BlockAttrs,\n DeltaOp,\n DirValue,\n ImageEmbed,\n LegacySerializedDocument,\n ListKind,\n MarkAttrs,\n PageSettings,\n SerializedBlock,\n SerializedCell,\n SerializedDocument,\n} from \"@sofereditor/core\";\nimport {\n BLANK_STYLE,\n answerLineStyle,\n cellBorderStyle,\n isImageEmbed,\n isLegacySerializedDocument,\n pxToMm,\n splitUnderscoreRuns,\n styleToCssText,\n type CellBorderPos,\n type StyleRecord,\n} from \"@sofereditor/core\";\n\nfunction normalize(doc: SerializedDocument | LegacySerializedDocument): SerializedDocument {\n return isLegacySerializedDocument(doc) ? { blocks: doc } : doc;\n}\n\nexport interface DocumentToHtmlOptions {\n /** Document title (set on `<title>` and used as the file name hint). Default \"Documento\". */\n title?: string;\n /** When true (default) emits a full `<html>` document. When false emits only the body fragment. */\n standalone?: boolean;\n /** Extra CSS appended after the default print stylesheet. Only honored when `standalone`. */\n extraCss?: string;\n /** Page geometry hints injected into `@page`. Defaults to A4. */\n page?: {\n width: string;\n height: string;\n marginTop: string;\n marginBottom: string;\n marginLeft: string;\n marginRight: string;\n };\n}\n\nconst DEFAULT_PAGE = {\n width: \"210mm\",\n height: \"297mm\",\n marginTop: \"25mm\",\n marginBottom: \"25mm\",\n marginLeft: \"25mm\",\n marginRight: \"25mm\",\n};\n\n/**\n * Serialize a `SerializedDocument` to standalone HTML.\n *\n * Output structure mirrors what `@sofereditor/react` paints at runtime: each\n * top-level block becomes one element (`<p>`, `<h1..6>`, `<blockquote>`, `<pre>`,\n * `<table>`), consecutive `listItem` blocks are reconstructed into nested\n * `<ul>`/`<ol>` trees, and image embeds are emitted as `<img>` tags carrying the\n * embed's data URL (so the export is self-contained).\n *\n * NOT a fragment of the live editor DOM — this walks the model so the output is\n * editor-state agnostic (no caret artifacts, no zero-width phantom anchors).\n */\nexport function documentToHtml(\n doc: SerializedDocument | LegacySerializedDocument,\n options: DocumentToHtmlOptions = {},\n): string {\n const normalized = normalize(doc);\n const body = documentToHtmlFragment(normalized);\n if (options.standalone === false) return body;\n const page = options.page ?? pageFromSettings(normalized.pageSettings);\n const title = escapeHtml(options.title ?? \"Documento\");\n const extra = options.extraCss ?? \"\";\n return [\n \"<!doctype html>\",\n `<html lang=\"pt-BR\"><head><meta charset=\"utf-8\"><title>${title}</title>`,\n `<style>${baseStylesheet(page)}${extra}</style>`,\n \"</head><body>\",\n body,\n \"</body></html>\",\n ].join(\"\");\n}\n\nfunction pageFromSettings(s: PageSettings | undefined): NonNullable<DocumentToHtmlOptions[\"page\"]> {\n if (!s) return DEFAULT_PAGE;\n const fmt = (px: number) => `${pxToMm(px).toFixed(2)}mm`;\n return {\n width: fmt(s.width),\n height: fmt(s.height),\n marginTop: fmt(s.marginTop),\n marginBottom: fmt(s.marginBottom),\n marginLeft: fmt(s.marginLeft),\n marginRight: fmt(s.marginRight),\n };\n}\n\n/** Body-only HTML for embedding in other documents. */\nexport function documentToHtmlFragment(\n doc: SerializedDocument | LegacySerializedDocument,\n): string {\n const blocks = normalize(doc).blocks;\n const out: string[] = [];\n let i = 0;\n while (i < blocks.length) {\n const block = blocks[i];\n if (block.type === \"listItem\") {\n const groupStart = i;\n const kind: ListKind = block.attrs.listKind === \"ordered\" ? \"ordered\" : \"bullet\";\n const items: SerializedBlock[] = [];\n while (\n i < blocks.length &&\n blocks[i].type === \"listItem\" &&\n (blocks[i].attrs.listKind ?? \"bullet\") === kind\n ) {\n items.push(blocks[i]);\n i++;\n }\n out.push(renderListGroup(items, kind, groupStart));\n continue;\n }\n out.push(renderBlock(block, i));\n i++;\n }\n return out.join(\"\");\n}\n\n/** JSON export — a stable serialization for save/load round-trips. */\nexport function documentToJson(\n doc: SerializedDocument | LegacySerializedDocument,\n pretty = true,\n): string {\n return JSON.stringify(normalize(doc), null, pretty ? 2 : 0);\n}\n\n// ---------- blocks ----------\n\nfunction renderBlock(block: SerializedBlock, index: number): string {\n switch (block.type) {\n case \"paragraph\":\n return `<p${blockAttrs(\"ed-paragraph\", block.attrs)}>${renderInline(block.delta)}</p>`;\n case \"heading\": {\n const level = clampHeadingLevel(block.attrs.level);\n return `<h${level}${blockAttrs(`ed-heading ed-heading-${level}`, block.attrs)}>${renderInline(\n block.delta,\n )}</h${level}>`;\n }\n case \"blockquote\":\n return `<blockquote${blockAttrs(\"ed-blockquote\", block.attrs)}>${renderInline(\n block.delta,\n )}</blockquote>`;\n case \"codeBlock\": {\n const lang = block.attrs.language;\n const codeAttrs = lang ? ` class=\"language-${escapeAttr(lang)}\"` : \"\";\n return `<pre${blockAttrs(\"ed-codeblock\", block.attrs)}><code${codeAttrs}>${renderInline(\n block.delta,\n )}</code></pre>`;\n }\n case \"table\":\n return renderTable(block);\n default:\n return `<p${blockAttrs(\"ed-paragraph\", block.attrs)}>${renderInline(block.delta)}</p>`;\n }\n}\n\nfunction renderListGroup(items: SerializedBlock[], kind: ListKind, key: number): string {\n // Tree-build using listLevel just like Editor.buildListTree.\n interface Node { item: SerializedBlock; children: Node[]; }\n const roots: Node[] = [];\n const stack: { children: Node[]; level: number }[] = [{ children: roots, level: -1 }];\n for (const it of items) {\n const level = clampListLevel(it.attrs.listLevel);\n while (stack.length > 1 && stack[stack.length - 1].level >= level) stack.pop();\n const node: Node = { item: it, children: [] };\n stack[stack.length - 1].children.push(node);\n stack.push({ children: node.children, level });\n }\n return renderListTree(roots, kind, `list-${key}`);\n}\n\nfunction renderListTree(nodes: { item: SerializedBlock; children: any[] }[], kind: ListKind, _key: string): string {\n const Tag = kind === \"ordered\" ? \"ol\" : \"ul\";\n const cls = `ed-list ed-list-${kind}`;\n const items = nodes\n .map((n) => {\n const inner = renderInline(n.item.delta);\n const nested =\n n.children.length > 0 ? renderListTree(n.children, kind, _key + \"n\") : \"\";\n return `<li${blockAttrs(\"ed-listitem\", n.item.attrs)}>${inner}${nested}</li>`;\n })\n .join(\"\");\n return `<${Tag} class=\"${cls}\" data-list-kind=\"${kind}\">${items}</${Tag}>`;\n}\n\nfunction renderTable(block: SerializedBlock): string {\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 if (rows <= 0 || cols <= 0) return \"\";\n\n const colGroup = renderColGroup(block.attrs.colWidths, cols);\n const preset = block.attrs.borderPreset;\n const borderColor = block.attrs.borderColor;\n const trs: string[] = [];\n for (let r = 0; r < rows; r++) {\n const tds: string[] = [];\n for (let c = 0; c < cols; c++) {\n const cell = cells[r * cols + c];\n // Este caminho NÃO fragmenta a tabela (o HTML de servidor não pagina),\n // então os limites do fragmento são os da tabela lógica.\n const at = (rowspan: number, colspan: number): CellBorderPos => ({\n row: r,\n col: c,\n rowspan,\n colspan,\n cols,\n rowStart: 0,\n rowEnd: rows,\n });\n if (!cell) {\n // Célula ausente também precisa das bordas, senão vira um buraco na grade.\n const style = styleToCssText(cellBorderStyle(preset, at(1, 1), \"print\", borderColor));\n tds.push(`<td class=\"ed-cell\" style=\"${style}\"></td>`);\n continue;\n }\n if (cell.attrs?.covered) continue;\n const rowspan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;\n const colspan = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;\n tds.push(\n renderCell(cell, cellBorderStyle(preset, at(rowspan, colspan), \"print\", borderColor)),\n );\n }\n trs.push(`<tr>${tds.join(\"\")}</tr>`);\n }\n return `<div class=\"ed-table-wrap\"><table class=\"ed-table\">${colGroup}<tbody>${trs.join(\"\")}</tbody></table></div>`;\n}\n\nfunction renderColGroup(widths: number[] | undefined, cols: number): string {\n if (!widths || widths.length === 0) return \"\";\n const cells: string[] = [];\n for (let i = 0; i < cols; i++) {\n const w = widths[i];\n cells.push(typeof w === \"number\" ? `<col style=\"width:${w}px\">` : \"<col>\");\n }\n return `<colgroup>${cells.join(\"\")}</colgroup>`;\n}\n\nfunction renderCell(cell: SerializedCell, border: StyleRecord): string {\n const rs = cell.attrs?.rowspan && cell.attrs.rowspan > 1\n ? ` rowspan=\"${cell.attrs.rowspan}\"`\n : \"\";\n const cs = cell.attrs?.colspan && cell.attrs.colspan > 1\n ? ` colspan=\"${cell.attrs.colspan}\"`\n : \"\";\n // `variant: \"print\"` sempre: este HTML é para servidor/PDF, nunca para a tela\n // do editor — logo, lado desligado é `transparent`, nunca a guia visual.\n const styles: string[] = [styleToCssText(border)];\n const align = clampAlign(cell.attrs?.align);\n if (align) styles.push(`text-align:${align}`);\n if (cell.attrs?.bgColor) styles.push(`background-color:${cssValue(cell.attrs.bgColor)}`);\n const style = styles.length > 0 ? ` style=\"${styles.join(\";\")}\"` : \"\";\n return `<td class=\"ed-cell\"${rs}${cs}${style}>${renderInline(cell.delta)}</td>`;\n}\n\n// ---------- inline ----------\n\nfunction renderInline(delta: DeltaOp[]): string {\n if (delta.length === 0) return \"<br>\";\n const parts: string[] = [];\n // Floating wrap-left/wrap-right embeds are emitted FIRST so CSS float wraps\n // the following inline text — mirrors the in-editor renderer.\n const wraps: string[] = [];\n for (const op of delta) {\n if (typeof op.insert === \"string\") {\n if (op.insert.length === 0) continue;\n parts.push(applyMarks(decorateBlanks(op.insert), op.attributes));\n } else if (isImageEmbed(op.insert)) {\n const layout = op.insert.layout ?? \"inline\";\n if (layout === \"wrap-left\" || layout === \"wrap-right\") {\n wraps.push(renderImg(op.insert));\n } else {\n parts.push(renderImg(op.insert));\n }\n }\n }\n if (wraps.length === 0 && parts.length === 0) return \"<br>\";\n return wraps.join(\"\") + parts.join(\"\");\n}\n\n/**\n * Espelha `decorateBlanks` de `@sofereditor/react`: corridas de 3+ underlines\n * viram traço contínuo, com os caracteres preservados. A segmentação vem do\n * mesmo helper puro em `@sofereditor/core` — se divergir daqui, o teste de\n * paridade quebra.\n *\n * Devolve HTML já escapado, que é o que `applyMarks` espera receber.\n */\nfunction decorateBlanks(text: string): string {\n const segs = splitUnderscoreRuns(text);\n if (segs.length === 1 && !segs[0].blank) return escapeHtml(text);\n const css = styleToCssText(BLANK_STYLE);\n return segs\n .map((s) =>\n s.blank\n ? `<span data-blank=\"true\" style=\"${css}\">${escapeHtml(s.text)}</span>`\n : escapeHtml(s.text),\n )\n .join(\"\");\n}\n\nfunction applyMarks(text: string, attrs: MarkAttrs | undefined): string {\n if (!attrs) return text;\n let html = text;\n if (attrs.bold) html = `<strong>${html}</strong>`;\n if (attrs.italic) html = `<em>${html}</em>`;\n if (attrs.underline) html = `<u>${html}</u>`;\n if (attrs.strike) html = `<s>${html}</s>`;\n const styles: string[] = [];\n if (attrs.color) styles.push(`color:${cssValue(attrs.color)}`);\n if (attrs.highlight) styles.push(`background-color:${cssValue(attrs.highlight)}`);\n if (attrs.fontFamily) styles.push(`font-family:${cssValue(attrs.fontFamily)}`);\n if (attrs.fontSize) styles.push(`font-size:${cssValue(attrs.fontSize)}`);\n if (styles.length > 0) html = `<span style=\"${styles.join(\";\")}\">${html}</span>`;\n if (attrs.link?.href) {\n const t = attrs.link.title ? ` title=\"${escapeAttr(attrs.link.title)}\"` : \"\";\n html = `<a href=\"${escapeAttr(attrs.link.href)}\"${t} rel=\"noopener noreferrer\" target=\"_blank\">${html}</a>`;\n }\n if (attrs.comment?.markId) {\n html = `<span data-comment-id=\"${escapeAttr(attrs.comment.markId)}\">${html}</span>`;\n }\n return html;\n}\n\nfunction renderImg(embed: ImageEmbed): string {\n const layout = embed.layout ?? \"inline\";\n const align = embed.align;\n const ox = embed.offsetX ?? 0;\n const oy = embed.offsetY ?? 0;\n const hasCaption = !!embed.caption && embed.caption.length > 0;\n\n // The wrapper carries the positioning (float/absolute/inline-block/block).\n // The img keeps the intrinsic size when wrapped; when there's no caption we\n // keep the historical bare `<img>` output for unchanged DOM/CSS.\n const wrapperStyles: string[] = [];\n switch (layout) {\n case \"wrap-left\":\n wrapperStyles.push(\n \"float:left\",\n `width:${embed.width}px`,\n `margin-right:${Math.max(8, ox)}px`,\n `margin-top:${Math.max(0, oy)}px`,\n \"margin-bottom:4px\",\n );\n break;\n case \"wrap-right\":\n wrapperStyles.push(\n \"float:right\",\n `width:${embed.width}px`,\n `margin-left:${Math.max(8, ox)}px`,\n `margin-top:${Math.max(0, oy)}px`,\n \"margin-bottom:4px\",\n );\n break;\n case \"behind\":\n case \"front\":\n wrapperStyles.push(\n \"position:absolute\",\n `left:${ox}px`,\n `top:${oy}px`,\n `width:${embed.width}px`,\n `z-index:${layout === \"behind\" ? -1 : 2}`,\n );\n break;\n case \"inline\":\n default:\n if (align) {\n wrapperStyles.push(\n \"display:block\",\n `width:${embed.width}px`,\n `margin-left:${align === \"left\" ? \"0\" : \"auto\"}`,\n `margin-right:${align === \"right\" ? \"0\" : \"auto\"}`,\n );\n } else {\n wrapperStyles.push(\n \"display:inline-block\",\n \"vertical-align:text-bottom\",\n `width:${embed.width}px`,\n );\n }\n break;\n }\n\n const imgStyles: string[] = hasCaption\n ? [`width:${embed.width}px`, `height:${embed.height}px`, \"display:block\"]\n : wrapperStyles.concat(\n // Preserve old height when emitting bare <img>.\n `height:${embed.height}px`,\n );\n\n const imgHtml = `<img src=\"${escapeAttr(embed.src)}\" width=\"${embed.width}\" height=\"${embed.height}\" alt=\"${escapeAttr(embed.caption ?? \"\")}\" data-embed=\"image\" data-embed-layout=\"${layout}\"${\n align ? ` data-embed-align=\"${escapeAttr(align)}\"` : \"\"\n } style=\"${imgStyles.join(\";\")}\">`;\n\n if (!hasCaption) return imgHtml;\n\n const captionAlign = embed.captionAlign ?? \"center\";\n return `<figure class=\"ed-figure\" data-embed-figure=\"true\" data-embed-layout=\"${layout}\" style=\"${wrapperStyles.join(\";\")}\">${imgHtml}<figcaption class=\"ed-figcaption\" data-caption-align=\"${captionAlign}\" style=\"text-align:${captionAlign}\">${escapeHtml(\n embed.caption!,\n )}</figcaption></figure>`;\n}\n\n// ---------- helpers ----------\n\nfunction blockAttrs(baseClass: string, attrs: BlockAttrs): string {\n const classes = [baseClass, \"ed-block\"];\n const align = clampAlign(attrs.align);\n if (align) classes.push(`ed-align-${align}`);\n const dir = clampDir(attrs.dir);\n const dirAttr = dir ? ` dir=\"${dir}\"` : \"\";\n // Linha de resposta: mesmo helper puro que o renderizador do editor usa, para\n // a régua e a entrelinha saírem idênticas nos dois caminhos.\n const answer = answerLineStyle(attrs);\n const styleAttr = answer ? ` style=\"${styleToCssText(answer)}\"` : \"\";\n return ` class=\"${classes.join(\" \")}\"${dirAttr}${styleAttr}`;\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 clampAlign(a: unknown): AlignValue | undefined {\n if (a === \"left\" || a === \"center\" || a === \"right\" || a === \"justify\") return a;\n return undefined;\n}\n\nfunction clampDir(d: unknown): DirValue | undefined {\n if (d === \"ltr\" || d === \"rtl\") return d;\n return undefined;\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\n/** Allow only safe characters in CSS values — defense in depth against injected `</style>`. */\nfunction cssValue(v: string): string {\n return v.replace(/[<>\"';\\\\]/g, \"\");\n}\n\nfunction baseStylesheet(page: NonNullable<DocumentToHtmlOptions[\"page\"]>): string {\n // Margins are owned by `@page` only. Chromium honors that block both for\n // `window.print()` (client-side `exportPdfFromDocument`) and for\n // `page.pdf({ preferCSSPageSize: true })` (server-side `renderPdf` in\n // `@sofereditor/export-pdf-server`). Adding the same offsets via `body { padding }`\n // would compound and double the margin in the output PDF.\n return `\n@page {\n size: ${page.width} ${page.height};\n margin: ${page.marginTop} ${page.marginRight} ${page.marginBottom} ${page.marginLeft};\n}\nhtml, body { margin: 0; padding: 0; background: white; color: #1a1a1f; }\n/* Sem isto, o diálogo de impressão descarta background-color/imagens de\n fundo por padrão (\"gráficos de fundo\" desmarcado) — ex.: células com\n bgColor da tabela de pontuação saíam brancas no PDF. */\n* { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\nbody {\n font-family: \"Arial\", \"Helvetica\", sans-serif;\n font-size: 11pt;\n line-height: 1.5;\n}\n.ed-paragraph { margin: 0 0 0.5em; min-height: 1.5em; }\n.ed-heading { margin: 0.5em 0 0.25em; font-weight: 600; line-height: 1.25; }\n.ed-heading-1 { font-size: 28pt; }\n.ed-heading-2 { font-size: 22pt; }\n.ed-heading-3 { font-size: 18pt; }\n.ed-heading-4 { font-size: 14pt; }\n.ed-heading-5 { font-size: 12pt; }\n.ed-heading-6 { font-size: 11pt; color: #6b7280; }\n.ed-blockquote {\n margin: 0 0 0.5em; padding: 0.25em 12px;\n border-inline-start: 3px solid #e5e7eb; color: #6b7280;\n}\n.ed-codeblock {\n margin: 0 0 0.5em; background: #0f172a; color: #e2e8f0;\n padding: 12px 14px; border-radius: 6px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11pt; white-space: pre-wrap;\n}\n.ed-codeblock code { font-family: inherit; background: transparent; color: inherit; }\n.ed-block[dir=\"rtl\"] { text-align: start; }\n.ed-align-left { text-align: left; }\n.ed-align-center { text-align: center; }\n.ed-align-right { text-align: right; }\n.ed-align-justify { text-align: justify; }\n.ed-list { margin: 0 0 0.5em; padding-inline-start: 28px; }\n.ed-list .ed-list { margin-block: 0; }\n.ed-list-bullet { list-style-type: disc; }\n.ed-list-bullet .ed-list-bullet { list-style-type: circle; }\n.ed-list-bullet .ed-list-bullet .ed-list-bullet { list-style-type: square; }\n.ed-list-ordered { list-style-type: decimal; }\n.ed-list-ordered .ed-list-ordered { list-style-type: lower-alpha; }\n.ed-list-ordered .ed-list-ordered .ed-list-ordered { list-style-type: lower-roman; }\n.ed-listitem { margin: 0; min-height: 1.5em; }\n.ed-table-wrap { margin: 0 0 0.5em; position: relative; }\n.ed-table { width: 100%; border-collapse: collapse; table-layout: fixed; }\n.ed-cell {\n border: 1px solid #cbd5e1; padding: 6px 8px;\n vertical-align: top; word-break: break-word; white-space: pre-wrap;\n}\nimg[data-embed=\"image\"] { max-width: 100%; }\n.ed-block { position: relative; isolation: isolate; }\na { color: #2563eb; }\n`;\n}\n","import type { LegacySerializedDocument, SerializedDocument } from \"@sofereditor/core\";\nimport { documentToHtml, type DocumentToHtmlOptions } from \"./html\";\n\nexport interface ExportPdfFromDocumentOptions extends DocumentToHtmlOptions {\n /** Suggested file name (used by the OS save dialog as the default). */\n filename?: string;\n}\n\n/**\n * Open the browser's print dialog for a `SerializedDocument`.\n *\n * Strategy: render the document to standalone HTML in a hidden iframe and call\n * `iframe.contentWindow.print()`. The user picks \"Save as PDF\" (or any printer)\n * from the native dialog. No external PDF library required.\n *\n * The returned promise resolves after the print dialog closes (`afterprint`).\n */\nexport async function exportPdfFromDocument(\n doc: SerializedDocument | LegacySerializedDocument,\n options: ExportPdfFromDocumentOptions = {},\n): Promise<void> {\n const html = documentToHtml(doc, { ...options, standalone: true });\n await printHtmlInIframe(html, options.filename ?? options.title ?? \"Documento\");\n}\n\nexport interface SerializePaginatedHtmlOptions {\n /** Document title (becomes `<title>` and is used as default filename). */\n title?: string;\n /** Extra CSS appended to the cloned styles (e.g. for headers/footers). */\n extraCss?: string;\n /**\n * Override the `@page { size }`. Default reads `--ed-page-width` /\n * `--ed-page-height` CSS variables off the root (set by `<Editor>` from the\n * current `PageSettings`); falls back to A4 if absent.\n */\n pageSize?: { width: string; height: string };\n}\n\nexport interface ExportPdfFromElementOptions extends SerializePaginatedHtmlOptions {\n /** Suggested file name. */\n filename?: string;\n}\n\nfunction cssPageSizeFromRoot(\n cs: CSSStyleDeclaration | undefined,\n): { width: string; height: string } {\n if (!cs) return { width: \"210mm\", height: \"297mm\" };\n const w = cs.getPropertyValue(\"--ed-page-width\").trim();\n const h = cs.getPropertyValue(\"--ed-page-height\").trim();\n if (!w || !h) return { width: \"210mm\", height: \"297mm\" };\n // CSS vars come back as e.g. \"794px\". `@page` accepts px directly.\n return { width: w, height: h };\n}\n\n/**\n * Serialize a live editor root (the `.ed-root` paginated DOM) into a\n * self-contained HTML string. The output preserves the in-editor pagination\n * decisions verbatim — each `.ed-page` becomes a fixed-size page in the final\n * print medium with no re-layout possible.\n *\n * The returned HTML inlines every accessible stylesheet from the host document\n * (or links it as a fallback for cross-origin sheets) so the layout survives\n * transplant to an iframe, a saved file, or a headless renderer.\n *\n * This is the building block for two paths:\n * - `exportPdfFromElement` (this file): writes the HTML into a hidden iframe\n * and calls `window.print()` for the local \"Save as PDF\" flow.\n * - Upload-to-server snapshots: POST the string to a backend that converts it\n * to PDF via Puppeteer's `setContent + page.pdf()` without re-running the\n * pagination engine.\n */\nexport function serializePaginatedHtml(\n root: HTMLElement,\n options: SerializePaginatedHtmlOptions = {},\n): string {\n const clone = root.cloneNode(true) as HTMLElement;\n // Strip interactive scaffolding that print styles can't see ahead of time.\n clone.querySelectorAll(\"[contenteditable]\").forEach((el) => {\n (el as HTMLElement).removeAttribute(\"contenteditable\");\n });\n clone.querySelectorAll(\".ed-image-overlay\").forEach((el) => el.remove());\n clone.querySelectorAll(\".ed-remote-cursors\").forEach((el) => el.remove());\n\n // Read the page size baked into the editor via `--ed-page-*` CSS variables.\n // The Editor component sets these on its root from the current PageSettings\n // (`packages/react/src/Editor.tsx`). Falls back to A4 if we can't read them.\n const cs = root.ownerDocument?.defaultView?.getComputedStyle(root);\n const pageSize = options.pageSize ?? cssPageSizeFromRoot(cs);\n\n const styles = collectStyles();\n const title = escapeHtml(options.title ?? \"Documento\");\n const extra = options.extraCss ?? \"\";\n // The `@page { size }` and page-break rules live inside `@media print` so\n // they merge cleanly with any host-document print stylesheet that already\n // declares its own `@page` (e.g. `apps/playground/src/styles.css`). Putting\n // ours after the cloned `<style>`s gives our override last-write-wins.\n return [\n \"<!doctype html>\",\n `<html><head><meta charset=\"utf-8\"><meta name=\"ed-print-snapshot\" content=\"1\"><title>${title}</title>`,\n styles,\n `<style>\n html, body { margin: 0; padding: 0; background: white; }\n /* Preserva background-color (ex.: células da tabela de pontuação) no\n window.print(), onde \"gráficos de fundo\" vem desmarcado por padrão. */\n * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\n @media print {\n /* Guias de tela das bordas desligadas por preset. O snapshot clona o DOM\n vivo do editor, então elas viriam junto — aqui somem. A geometria não\n muda: o lado continua com 1px, só perde a cor. */\n .ed-root, .ed-page, .ed-table, .ed-cell { --ed-guide-color: transparent !important; }\n @page { size: ${pageSize.width} ${pageSize.height}; margin: 0; }\n .ed-page { box-shadow: none !important; border: none !important; border-radius: 0 !important; margin: 0 !important; page-break-after: always; break-after: page; }\n .ed-page:last-child { page-break-after: auto; break-after: auto; }\n .ed-image-overlay { display: none !important; }\n }\n ${extra}\n </style>`,\n \"</head><body>\",\n clone.outerHTML,\n \"</body></html>\",\n ].join(\"\");\n}\n\n/**\n * Snapshot a live editor root element (the `.ed-root` paginated DOM) and open\n * the browser's print dialog. Preserves the in-editor pagination decisions\n * verbatim — no re-layout in the print frame.\n *\n * For the \"send the snapshot to a server\" use case, use\n * `serializePaginatedHtml` directly and POST the returned string.\n */\nexport async function exportPdfFromElement(\n root: HTMLElement,\n options: ExportPdfFromElementOptions = {},\n): Promise<void> {\n const html = serializePaginatedHtml(root, options);\n await printHtmlInIframe(html, options.filename ?? options.title ?? \"Documento\");\n}\n\n// ---------- internals ----------\n\nasync function printHtmlInIframe(html: string, _filename: string): Promise<void> {\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\"aria-hidden\", \"true\");\n iframe.style.cssText =\n \"position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden;\";\n document.body.appendChild(iframe);\n\n const win = iframe.contentWindow;\n const docu = iframe.contentDocument ?? win?.document;\n if (!win || !docu) {\n iframe.remove();\n throw new Error(\"export-pdf: failed to access iframe document\");\n }\n\n docu.open();\n docu.write(html);\n docu.close();\n\n // Wait for the iframe document to finish loading (DOM, stylesheets, images).\n await new Promise<void>((resolve) => {\n if (docu.readyState === \"complete\") resolve();\n else iframe.addEventListener(\"load\", () => resolve(), { once: true });\n });\n // Best-effort wait for embedded images: print dialogs that snapshot too early\n // miss late-loading data URIs (rare, but real).\n await waitForImages(docu);\n // CRITICAL: wait for @font-face fonts to finish loading. The `load` event does\n // NOT wait for web fonts, so printing too early triggers FOIT (Flash of\n // Invisible Text): the browser hides every glyph until its font loads, while\n // text-decoration underlines still paint. Symptom seen in the wild: the\n // letterhead + field underlines printed but ALL text was missing. `fonts.ready`\n // resolves once all used fonts are loaded; cap the wait so a font that never\n // resolves can't hang the print.\n await waitForFonts(docu);\n\n await new Promise<void>((resolve) => {\n const cleanup = () => {\n win.removeEventListener(\"afterprint\", cleanup);\n // Defer removal one tick so Safari's print queue doesn't choke.\n setTimeout(() => iframe.remove(), 0);\n resolve();\n };\n win.addEventListener(\"afterprint\", cleanup);\n win.focus();\n win.print();\n // Fallback in case afterprint never fires (older browsers).\n setTimeout(() => {\n if (iframe.isConnected) cleanup();\n }, 60_000);\n });\n}\n\nfunction collectStyles(): string {\n const parts: string[] = [];\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n const rules = sheet.cssRules;\n if (!rules) continue;\n const css = Array.from(rules)\n .map((r) => r.cssText)\n .join(\"\\n\");\n if (css) parts.push(`<style>${css}</style>`);\n } catch {\n // Cross-origin sheet — fall back to linking it.\n const href = sheet.href;\n if (href) parts.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(href)}\">`);\n }\n }\n return parts.join(\"\\n\");\n}\n\nasync function waitForImages(docu: Document): Promise<void> {\n const imgs = Array.from(docu.images);\n await Promise.all(\n imgs.map(\n (img) =>\n new Promise<void>((resolve) => {\n if (img.complete) return resolve();\n img.addEventListener(\"load\", () => resolve(), { once: true });\n img.addEventListener(\"error\", () => resolve(), { once: true });\n }),\n ),\n );\n}\n\n/**\n * Wait for the iframe document's web fonts (`@font-face`) to finish loading,\n * capped so a font that never resolves can't hang the print. Without this the\n * print can fire mid-font-load and render invisible text (FOIT).\n */\nasync function waitForFonts(docu: Document): Promise<void> {\n const fonts = (docu as Document & { fonts?: FontFaceSet }).fonts;\n if (!fonts?.ready) return;\n await Promise.race([\n fonts.ready.then(() => undefined),\n new Promise<void>((resolve) => {\n setTimeout(resolve, 3000);\n }),\n ]);\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\nfunction escapeAttr(s: string): string {\n return escapeHtml(s);\n}\n","/**\n * Trigger a browser download for an in-memory blob/string. Tiny utility — kept\n * in this package because PDF/HTML/JSON exporters all want it.\n */\nexport function downloadBlob(blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n a.rel = \"noopener\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n setTimeout(() => {\n a.remove();\n URL.revokeObjectURL(url);\n }, 0);\n}\n\nexport function downloadString(\n text: string,\n filename: string,\n mimeType = \"text/plain;charset=utf-8\",\n): void {\n downloadBlob(new Blob([text], { type: mimeType }), filename);\n}\n"],"mappings":"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","names":["rowspan","colspan","escapeHtml","escapeAttr","HTML_ESCAPES"]}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// src/html.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BLANK_STYLE,
|
|
4
|
+
answerLineStyle,
|
|
5
|
+
cellBorderStyle,
|
|
6
|
+
isImageEmbed,
|
|
7
|
+
isLegacySerializedDocument,
|
|
8
|
+
pxToMm,
|
|
9
|
+
splitUnderscoreRuns,
|
|
10
|
+
styleToCssText
|
|
11
|
+
} from "@sofereditor/core";
|
|
3
12
|
function normalize(doc) {
|
|
4
13
|
return isLegacySerializedDocument(doc) ? { blocks: doc } : doc;
|
|
5
14
|
}
|
|
@@ -119,17 +128,33 @@ function renderTable(block) {
|
|
|
119
128
|
const cells = block.cells ?? [];
|
|
120
129
|
if (rows <= 0 || cols <= 0) return "";
|
|
121
130
|
const colGroup = renderColGroup(block.attrs.colWidths, cols);
|
|
131
|
+
const preset = block.attrs.borderPreset;
|
|
132
|
+
const borderColor = block.attrs.borderColor;
|
|
122
133
|
const trs = [];
|
|
123
134
|
for (let r = 0; r < rows; r++) {
|
|
124
135
|
const tds = [];
|
|
125
136
|
for (let c = 0; c < cols; c++) {
|
|
126
137
|
const cell = cells[r * cols + c];
|
|
138
|
+
const at = (rowspan2, colspan2) => ({
|
|
139
|
+
row: r,
|
|
140
|
+
col: c,
|
|
141
|
+
rowspan: rowspan2,
|
|
142
|
+
colspan: colspan2,
|
|
143
|
+
cols,
|
|
144
|
+
rowStart: 0,
|
|
145
|
+
rowEnd: rows
|
|
146
|
+
});
|
|
127
147
|
if (!cell) {
|
|
128
|
-
|
|
148
|
+
const style = styleToCssText(cellBorderStyle(preset, at(1, 1), "print", borderColor));
|
|
149
|
+
tds.push(`<td class="ed-cell" style="${style}"></td>`);
|
|
129
150
|
continue;
|
|
130
151
|
}
|
|
131
152
|
if (cell.attrs?.covered) continue;
|
|
132
|
-
|
|
153
|
+
const rowspan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;
|
|
154
|
+
const colspan = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;
|
|
155
|
+
tds.push(
|
|
156
|
+
renderCell(cell, cellBorderStyle(preset, at(rowspan, colspan), "print", borderColor))
|
|
157
|
+
);
|
|
133
158
|
}
|
|
134
159
|
trs.push(`<tr>${tds.join("")}</tr>`);
|
|
135
160
|
}
|
|
@@ -144,10 +169,10 @@ function renderColGroup(widths, cols) {
|
|
|
144
169
|
}
|
|
145
170
|
return `<colgroup>${cells.join("")}</colgroup>`;
|
|
146
171
|
}
|
|
147
|
-
function renderCell(cell) {
|
|
172
|
+
function renderCell(cell, border) {
|
|
148
173
|
const rs = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? ` rowspan="${cell.attrs.rowspan}"` : "";
|
|
149
174
|
const cs = cell.attrs?.colspan && cell.attrs.colspan > 1 ? ` colspan="${cell.attrs.colspan}"` : "";
|
|
150
|
-
const styles = [];
|
|
175
|
+
const styles = [styleToCssText(border)];
|
|
151
176
|
const align = clampAlign(cell.attrs?.align);
|
|
152
177
|
if (align) styles.push(`text-align:${align}`);
|
|
153
178
|
if (cell.attrs?.bgColor) styles.push(`background-color:${cssValue(cell.attrs.bgColor)}`);
|
|
@@ -161,7 +186,7 @@ function renderInline(delta) {
|
|
|
161
186
|
for (const op of delta) {
|
|
162
187
|
if (typeof op.insert === "string") {
|
|
163
188
|
if (op.insert.length === 0) continue;
|
|
164
|
-
parts.push(applyMarks(
|
|
189
|
+
parts.push(applyMarks(decorateBlanks(op.insert), op.attributes));
|
|
165
190
|
} else if (isImageEmbed(op.insert)) {
|
|
166
191
|
const layout = op.insert.layout ?? "inline";
|
|
167
192
|
if (layout === "wrap-left" || layout === "wrap-right") {
|
|
@@ -174,6 +199,14 @@ function renderInline(delta) {
|
|
|
174
199
|
if (wraps.length === 0 && parts.length === 0) return "<br>";
|
|
175
200
|
return wraps.join("") + parts.join("");
|
|
176
201
|
}
|
|
202
|
+
function decorateBlanks(text) {
|
|
203
|
+
const segs = splitUnderscoreRuns(text);
|
|
204
|
+
if (segs.length === 1 && !segs[0].blank) return escapeHtml(text);
|
|
205
|
+
const css = styleToCssText(BLANK_STYLE);
|
|
206
|
+
return segs.map(
|
|
207
|
+
(s) => s.blank ? `<span data-blank="true" style="${css}">${escapeHtml(s.text)}</span>` : escapeHtml(s.text)
|
|
208
|
+
).join("");
|
|
209
|
+
}
|
|
177
210
|
function applyMarks(text, attrs) {
|
|
178
211
|
if (!attrs) return text;
|
|
179
212
|
let html = text;
|
|
@@ -183,6 +216,7 @@ function applyMarks(text, attrs) {
|
|
|
183
216
|
if (attrs.strike) html = `<s>${html}</s>`;
|
|
184
217
|
const styles = [];
|
|
185
218
|
if (attrs.color) styles.push(`color:${cssValue(attrs.color)}`);
|
|
219
|
+
if (attrs.highlight) styles.push(`background-color:${cssValue(attrs.highlight)}`);
|
|
186
220
|
if (attrs.fontFamily) styles.push(`font-family:${cssValue(attrs.fontFamily)}`);
|
|
187
221
|
if (attrs.fontSize) styles.push(`font-size:${cssValue(attrs.fontSize)}`);
|
|
188
222
|
if (styles.length > 0) html = `<span style="${styles.join(";")}">${html}</span>`;
|
|
@@ -266,7 +300,9 @@ function blockAttrs(baseClass, attrs) {
|
|
|
266
300
|
if (align) classes.push(`ed-align-${align}`);
|
|
267
301
|
const dir = clampDir(attrs.dir);
|
|
268
302
|
const dirAttr = dir ? ` dir="${dir}"` : "";
|
|
269
|
-
|
|
303
|
+
const answer = answerLineStyle(attrs);
|
|
304
|
+
const styleAttr = answer ? ` style="${styleToCssText(answer)}"` : "";
|
|
305
|
+
return ` class="${classes.join(" ")}"${dirAttr}${styleAttr}`;
|
|
270
306
|
}
|
|
271
307
|
function clampHeadingLevel(l) {
|
|
272
308
|
if (typeof l !== "number") return 1;
|
|
@@ -398,6 +434,10 @@ function serializePaginatedHtml(root, options = {}) {
|
|
|
398
434
|
window.print(), onde "gr\xE1ficos de fundo" vem desmarcado por padr\xE3o. */
|
|
399
435
|
* { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
|
|
400
436
|
@media print {
|
|
437
|
+
/* Guias de tela das bordas desligadas por preset. O snapshot clona o DOM
|
|
438
|
+
vivo do editor, ent\xE3o elas viriam junto \u2014 aqui somem. A geometria n\xE3o
|
|
439
|
+
muda: o lado continua com 1px, s\xF3 perde a cor. */
|
|
440
|
+
.ed-root, .ed-page, .ed-table, .ed-cell { --ed-guide-color: transparent !important; }
|
|
401
441
|
@page { size: ${pageSize.width} ${pageSize.height}; margin: 0; }
|
|
402
442
|
.ed-page { box-shadow: none !important; border: none !important; border-radius: 0 !important; margin: 0 !important; page-break-after: always; break-after: page; }
|
|
403
443
|
.ed-page:last-child { page-break-after: auto; break-after: auto; }
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/html.ts","../src/pdf.ts","../src/download.ts"],"sourcesContent":["import type {\n AlignValue,\n BlockAttrs,\n DeltaOp,\n DirValue,\n ImageEmbed,\n LegacySerializedDocument,\n ListKind,\n MarkAttrs,\n PageSettings,\n SerializedBlock,\n SerializedCell,\n SerializedDocument,\n} from \"@sofereditor/core\";\nimport { isImageEmbed, isLegacySerializedDocument, pxToMm } from \"@sofereditor/core\";\n\nfunction normalize(doc: SerializedDocument | LegacySerializedDocument): SerializedDocument {\n return isLegacySerializedDocument(doc) ? { blocks: doc } : doc;\n}\n\nexport interface DocumentToHtmlOptions {\n /** Document title (set on `<title>` and used as the file name hint). Default \"Documento\". */\n title?: string;\n /** When true (default) emits a full `<html>` document. When false emits only the body fragment. */\n standalone?: boolean;\n /** Extra CSS appended after the default print stylesheet. Only honored when `standalone`. */\n extraCss?: string;\n /** Page geometry hints injected into `@page`. Defaults to A4. */\n page?: {\n width: string;\n height: string;\n marginTop: string;\n marginBottom: string;\n marginLeft: string;\n marginRight: string;\n };\n}\n\nconst DEFAULT_PAGE = {\n width: \"210mm\",\n height: \"297mm\",\n marginTop: \"25mm\",\n marginBottom: \"25mm\",\n marginLeft: \"25mm\",\n marginRight: \"25mm\",\n};\n\n/**\n * Serialize a `SerializedDocument` to standalone HTML.\n *\n * Output structure mirrors what `@sofereditor/react` paints at runtime: each\n * top-level block becomes one element (`<p>`, `<h1..6>`, `<blockquote>`, `<pre>`,\n * `<table>`), consecutive `listItem` blocks are reconstructed into nested\n * `<ul>`/`<ol>` trees, and image embeds are emitted as `<img>` tags carrying the\n * embed's data URL (so the export is self-contained).\n *\n * NOT a fragment of the live editor DOM — this walks the model so the output is\n * editor-state agnostic (no caret artifacts, no zero-width phantom anchors).\n */\nexport function documentToHtml(\n doc: SerializedDocument | LegacySerializedDocument,\n options: DocumentToHtmlOptions = {},\n): string {\n const normalized = normalize(doc);\n const body = documentToHtmlFragment(normalized);\n if (options.standalone === false) return body;\n const page = options.page ?? pageFromSettings(normalized.pageSettings);\n const title = escapeHtml(options.title ?? \"Documento\");\n const extra = options.extraCss ?? \"\";\n return [\n \"<!doctype html>\",\n `<html lang=\"pt-BR\"><head><meta charset=\"utf-8\"><title>${title}</title>`,\n `<style>${baseStylesheet(page)}${extra}</style>`,\n \"</head><body>\",\n body,\n \"</body></html>\",\n ].join(\"\");\n}\n\nfunction pageFromSettings(s: PageSettings | undefined): NonNullable<DocumentToHtmlOptions[\"page\"]> {\n if (!s) return DEFAULT_PAGE;\n const fmt = (px: number) => `${pxToMm(px).toFixed(2)}mm`;\n return {\n width: fmt(s.width),\n height: fmt(s.height),\n marginTop: fmt(s.marginTop),\n marginBottom: fmt(s.marginBottom),\n marginLeft: fmt(s.marginLeft),\n marginRight: fmt(s.marginRight),\n };\n}\n\n/** Body-only HTML for embedding in other documents. */\nexport function documentToHtmlFragment(\n doc: SerializedDocument | LegacySerializedDocument,\n): string {\n const blocks = normalize(doc).blocks;\n const out: string[] = [];\n let i = 0;\n while (i < blocks.length) {\n const block = blocks[i];\n if (block.type === \"listItem\") {\n const groupStart = i;\n const kind: ListKind = block.attrs.listKind === \"ordered\" ? \"ordered\" : \"bullet\";\n const items: SerializedBlock[] = [];\n while (\n i < blocks.length &&\n blocks[i].type === \"listItem\" &&\n (blocks[i].attrs.listKind ?? \"bullet\") === kind\n ) {\n items.push(blocks[i]);\n i++;\n }\n out.push(renderListGroup(items, kind, groupStart));\n continue;\n }\n out.push(renderBlock(block, i));\n i++;\n }\n return out.join(\"\");\n}\n\n/** JSON export — a stable serialization for save/load round-trips. */\nexport function documentToJson(\n doc: SerializedDocument | LegacySerializedDocument,\n pretty = true,\n): string {\n return JSON.stringify(normalize(doc), null, pretty ? 2 : 0);\n}\n\n// ---------- blocks ----------\n\nfunction renderBlock(block: SerializedBlock, index: number): string {\n switch (block.type) {\n case \"paragraph\":\n return `<p${blockAttrs(\"ed-paragraph\", block.attrs)}>${renderInline(block.delta)}</p>`;\n case \"heading\": {\n const level = clampHeadingLevel(block.attrs.level);\n return `<h${level}${blockAttrs(`ed-heading ed-heading-${level}`, block.attrs)}>${renderInline(\n block.delta,\n )}</h${level}>`;\n }\n case \"blockquote\":\n return `<blockquote${blockAttrs(\"ed-blockquote\", block.attrs)}>${renderInline(\n block.delta,\n )}</blockquote>`;\n case \"codeBlock\": {\n const lang = block.attrs.language;\n const codeAttrs = lang ? ` class=\"language-${escapeAttr(lang)}\"` : \"\";\n return `<pre${blockAttrs(\"ed-codeblock\", block.attrs)}><code${codeAttrs}>${renderInline(\n block.delta,\n )}</code></pre>`;\n }\n case \"table\":\n return renderTable(block);\n default:\n return `<p${blockAttrs(\"ed-paragraph\", block.attrs)}>${renderInline(block.delta)}</p>`;\n }\n}\n\nfunction renderListGroup(items: SerializedBlock[], kind: ListKind, key: number): string {\n // Tree-build using listLevel just like Editor.buildListTree.\n interface Node { item: SerializedBlock; children: Node[]; }\n const roots: Node[] = [];\n const stack: { children: Node[]; level: number }[] = [{ children: roots, level: -1 }];\n for (const it of items) {\n const level = clampListLevel(it.attrs.listLevel);\n while (stack.length > 1 && stack[stack.length - 1].level >= level) stack.pop();\n const node: Node = { item: it, children: [] };\n stack[stack.length - 1].children.push(node);\n stack.push({ children: node.children, level });\n }\n return renderListTree(roots, kind, `list-${key}`);\n}\n\nfunction renderListTree(nodes: { item: SerializedBlock; children: any[] }[], kind: ListKind, _key: string): string {\n const Tag = kind === \"ordered\" ? \"ol\" : \"ul\";\n const cls = `ed-list ed-list-${kind}`;\n const items = nodes\n .map((n) => {\n const inner = renderInline(n.item.delta);\n const nested =\n n.children.length > 0 ? renderListTree(n.children, kind, _key + \"n\") : \"\";\n return `<li${blockAttrs(\"ed-listitem\", n.item.attrs)}>${inner}${nested}</li>`;\n })\n .join(\"\");\n return `<${Tag} class=\"${cls}\" data-list-kind=\"${kind}\">${items}</${Tag}>`;\n}\n\nfunction renderTable(block: SerializedBlock): string {\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 if (rows <= 0 || cols <= 0) return \"\";\n\n const colGroup = renderColGroup(block.attrs.colWidths, cols);\n const trs: string[] = [];\n for (let r = 0; r < rows; r++) {\n const tds: string[] = [];\n for (let c = 0; c < cols; c++) {\n const cell = cells[r * cols + c];\n if (!cell) {\n tds.push(\"<td class=\\\"ed-cell\\\"></td>\");\n continue;\n }\n if (cell.attrs?.covered) continue;\n tds.push(renderCell(cell));\n }\n trs.push(`<tr>${tds.join(\"\")}</tr>`);\n }\n return `<div class=\"ed-table-wrap\"><table class=\"ed-table\">${colGroup}<tbody>${trs.join(\"\")}</tbody></table></div>`;\n}\n\nfunction renderColGroup(widths: number[] | undefined, cols: number): string {\n if (!widths || widths.length === 0) return \"\";\n const cells: string[] = [];\n for (let i = 0; i < cols; i++) {\n const w = widths[i];\n cells.push(typeof w === \"number\" ? `<col style=\"width:${w}px\">` : \"<col>\");\n }\n return `<colgroup>${cells.join(\"\")}</colgroup>`;\n}\n\nfunction renderCell(cell: SerializedCell): string {\n const rs = cell.attrs?.rowspan && cell.attrs.rowspan > 1\n ? ` rowspan=\"${cell.attrs.rowspan}\"`\n : \"\";\n const cs = cell.attrs?.colspan && cell.attrs.colspan > 1\n ? ` colspan=\"${cell.attrs.colspan}\"`\n : \"\";\n const styles: string[] = [];\n const align = clampAlign(cell.attrs?.align);\n if (align) styles.push(`text-align:${align}`);\n if (cell.attrs?.bgColor) styles.push(`background-color:${cssValue(cell.attrs.bgColor)}`);\n const style = styles.length > 0 ? ` style=\"${styles.join(\";\")}\"` : \"\";\n return `<td class=\"ed-cell\"${rs}${cs}${style}>${renderInline(cell.delta)}</td>`;\n}\n\n// ---------- inline ----------\n\nfunction renderInline(delta: DeltaOp[]): string {\n if (delta.length === 0) return \"<br>\";\n const parts: string[] = [];\n // Floating wrap-left/wrap-right embeds are emitted FIRST so CSS float wraps\n // the following inline text — mirrors the in-editor renderer.\n const wraps: string[] = [];\n for (const op of delta) {\n if (typeof op.insert === \"string\") {\n if (op.insert.length === 0) continue;\n parts.push(applyMarks(escapeHtml(op.insert), op.attributes));\n } else if (isImageEmbed(op.insert)) {\n const layout = op.insert.layout ?? \"inline\";\n if (layout === \"wrap-left\" || layout === \"wrap-right\") {\n wraps.push(renderImg(op.insert));\n } else {\n parts.push(renderImg(op.insert));\n }\n }\n }\n if (wraps.length === 0 && parts.length === 0) return \"<br>\";\n return wraps.join(\"\") + parts.join(\"\");\n}\n\nfunction applyMarks(text: string, attrs: MarkAttrs | undefined): string {\n if (!attrs) return text;\n let html = text;\n if (attrs.bold) html = `<strong>${html}</strong>`;\n if (attrs.italic) html = `<em>${html}</em>`;\n if (attrs.underline) html = `<u>${html}</u>`;\n if (attrs.strike) html = `<s>${html}</s>`;\n const styles: string[] = [];\n if (attrs.color) styles.push(`color:${cssValue(attrs.color)}`);\n if (attrs.fontFamily) styles.push(`font-family:${cssValue(attrs.fontFamily)}`);\n if (attrs.fontSize) styles.push(`font-size:${cssValue(attrs.fontSize)}`);\n if (styles.length > 0) html = `<span style=\"${styles.join(\";\")}\">${html}</span>`;\n if (attrs.link?.href) {\n const t = attrs.link.title ? ` title=\"${escapeAttr(attrs.link.title)}\"` : \"\";\n html = `<a href=\"${escapeAttr(attrs.link.href)}\"${t} rel=\"noopener noreferrer\" target=\"_blank\">${html}</a>`;\n }\n if (attrs.comment?.markId) {\n html = `<span data-comment-id=\"${escapeAttr(attrs.comment.markId)}\">${html}</span>`;\n }\n return html;\n}\n\nfunction renderImg(embed: ImageEmbed): string {\n const layout = embed.layout ?? \"inline\";\n const align = embed.align;\n const ox = embed.offsetX ?? 0;\n const oy = embed.offsetY ?? 0;\n const hasCaption = !!embed.caption && embed.caption.length > 0;\n\n // The wrapper carries the positioning (float/absolute/inline-block/block).\n // The img keeps the intrinsic size when wrapped; when there's no caption we\n // keep the historical bare `<img>` output for unchanged DOM/CSS.\n const wrapperStyles: string[] = [];\n switch (layout) {\n case \"wrap-left\":\n wrapperStyles.push(\n \"float:left\",\n `width:${embed.width}px`,\n `margin-right:${Math.max(8, ox)}px`,\n `margin-top:${Math.max(0, oy)}px`,\n \"margin-bottom:4px\",\n );\n break;\n case \"wrap-right\":\n wrapperStyles.push(\n \"float:right\",\n `width:${embed.width}px`,\n `margin-left:${Math.max(8, ox)}px`,\n `margin-top:${Math.max(0, oy)}px`,\n \"margin-bottom:4px\",\n );\n break;\n case \"behind\":\n case \"front\":\n wrapperStyles.push(\n \"position:absolute\",\n `left:${ox}px`,\n `top:${oy}px`,\n `width:${embed.width}px`,\n `z-index:${layout === \"behind\" ? -1 : 2}`,\n );\n break;\n case \"inline\":\n default:\n if (align) {\n wrapperStyles.push(\n \"display:block\",\n `width:${embed.width}px`,\n `margin-left:${align === \"left\" ? \"0\" : \"auto\"}`,\n `margin-right:${align === \"right\" ? \"0\" : \"auto\"}`,\n );\n } else {\n wrapperStyles.push(\n \"display:inline-block\",\n \"vertical-align:text-bottom\",\n `width:${embed.width}px`,\n );\n }\n break;\n }\n\n const imgStyles: string[] = hasCaption\n ? [`width:${embed.width}px`, `height:${embed.height}px`, \"display:block\"]\n : wrapperStyles.concat(\n // Preserve old height when emitting bare <img>.\n `height:${embed.height}px`,\n );\n\n const imgHtml = `<img src=\"${escapeAttr(embed.src)}\" width=\"${embed.width}\" height=\"${embed.height}\" alt=\"${escapeAttr(embed.caption ?? \"\")}\" data-embed=\"image\" data-embed-layout=\"${layout}\"${\n align ? ` data-embed-align=\"${escapeAttr(align)}\"` : \"\"\n } style=\"${imgStyles.join(\";\")}\">`;\n\n if (!hasCaption) return imgHtml;\n\n const captionAlign = embed.captionAlign ?? \"center\";\n return `<figure class=\"ed-figure\" data-embed-figure=\"true\" data-embed-layout=\"${layout}\" style=\"${wrapperStyles.join(\";\")}\">${imgHtml}<figcaption class=\"ed-figcaption\" data-caption-align=\"${captionAlign}\" style=\"text-align:${captionAlign}\">${escapeHtml(\n embed.caption!,\n )}</figcaption></figure>`;\n}\n\n// ---------- helpers ----------\n\nfunction blockAttrs(baseClass: string, attrs: BlockAttrs): string {\n const classes = [baseClass, \"ed-block\"];\n const align = clampAlign(attrs.align);\n if (align) classes.push(`ed-align-${align}`);\n const dir = clampDir(attrs.dir);\n const dirAttr = dir ? ` dir=\"${dir}\"` : \"\";\n return ` class=\"${classes.join(\" \")}\"${dirAttr}`;\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 clampAlign(a: unknown): AlignValue | undefined {\n if (a === \"left\" || a === \"center\" || a === \"right\" || a === \"justify\") return a;\n return undefined;\n}\n\nfunction clampDir(d: unknown): DirValue | undefined {\n if (d === \"ltr\" || d === \"rtl\") return d;\n return undefined;\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\n/** Allow only safe characters in CSS values — defense in depth against injected `</style>`. */\nfunction cssValue(v: string): string {\n return v.replace(/[<>\"';\\\\]/g, \"\");\n}\n\nfunction baseStylesheet(page: NonNullable<DocumentToHtmlOptions[\"page\"]>): string {\n // Margins are owned by `@page` only. Chromium honors that block both for\n // `window.print()` (client-side `exportPdfFromDocument`) and for\n // `page.pdf({ preferCSSPageSize: true })` (server-side `renderPdf` in\n // `@sofereditor/export-pdf-server`). Adding the same offsets via `body { padding }`\n // would compound and double the margin in the output PDF.\n return `\n@page {\n size: ${page.width} ${page.height};\n margin: ${page.marginTop} ${page.marginRight} ${page.marginBottom} ${page.marginLeft};\n}\nhtml, body { margin: 0; padding: 0; background: white; color: #1a1a1f; }\n/* Sem isto, o diálogo de impressão descarta background-color/imagens de\n fundo por padrão (\"gráficos de fundo\" desmarcado) — ex.: células com\n bgColor da tabela de pontuação saíam brancas no PDF. */\n* { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\nbody {\n font-family: \"Arial\", \"Helvetica\", sans-serif;\n font-size: 11pt;\n line-height: 1.5;\n}\n.ed-paragraph { margin: 0 0 0.5em; min-height: 1.5em; }\n.ed-heading { margin: 0.5em 0 0.25em; font-weight: 600; line-height: 1.25; }\n.ed-heading-1 { font-size: 28pt; }\n.ed-heading-2 { font-size: 22pt; }\n.ed-heading-3 { font-size: 18pt; }\n.ed-heading-4 { font-size: 14pt; }\n.ed-heading-5 { font-size: 12pt; }\n.ed-heading-6 { font-size: 11pt; color: #6b7280; }\n.ed-blockquote {\n margin: 0 0 0.5em; padding: 0.25em 12px;\n border-inline-start: 3px solid #e5e7eb; color: #6b7280;\n}\n.ed-codeblock {\n margin: 0 0 0.5em; background: #0f172a; color: #e2e8f0;\n padding: 12px 14px; border-radius: 6px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11pt; white-space: pre-wrap;\n}\n.ed-codeblock code { font-family: inherit; background: transparent; color: inherit; }\n.ed-block[dir=\"rtl\"] { text-align: start; }\n.ed-align-left { text-align: left; }\n.ed-align-center { text-align: center; }\n.ed-align-right { text-align: right; }\n.ed-align-justify { text-align: justify; }\n.ed-list { margin: 0 0 0.5em; padding-inline-start: 28px; }\n.ed-list .ed-list { margin-block: 0; }\n.ed-list-bullet { list-style-type: disc; }\n.ed-list-bullet .ed-list-bullet { list-style-type: circle; }\n.ed-list-bullet .ed-list-bullet .ed-list-bullet { list-style-type: square; }\n.ed-list-ordered { list-style-type: decimal; }\n.ed-list-ordered .ed-list-ordered { list-style-type: lower-alpha; }\n.ed-list-ordered .ed-list-ordered .ed-list-ordered { list-style-type: lower-roman; }\n.ed-listitem { margin: 0; min-height: 1.5em; }\n.ed-table-wrap { margin: 0 0 0.5em; position: relative; }\n.ed-table { width: 100%; border-collapse: collapse; table-layout: fixed; }\n.ed-cell {\n border: 1px solid #cbd5e1; padding: 6px 8px;\n vertical-align: top; word-break: break-word; white-space: pre-wrap;\n}\nimg[data-embed=\"image\"] { max-width: 100%; }\n.ed-block { position: relative; isolation: isolate; }\na { color: #2563eb; }\n`;\n}\n","import type { LegacySerializedDocument, SerializedDocument } from \"@sofereditor/core\";\nimport { documentToHtml, type DocumentToHtmlOptions } from \"./html\";\n\nexport interface ExportPdfFromDocumentOptions extends DocumentToHtmlOptions {\n /** Suggested file name (used by the OS save dialog as the default). */\n filename?: string;\n}\n\n/**\n * Open the browser's print dialog for a `SerializedDocument`.\n *\n * Strategy: render the document to standalone HTML in a hidden iframe and call\n * `iframe.contentWindow.print()`. The user picks \"Save as PDF\" (or any printer)\n * from the native dialog. No external PDF library required.\n *\n * The returned promise resolves after the print dialog closes (`afterprint`).\n */\nexport async function exportPdfFromDocument(\n doc: SerializedDocument | LegacySerializedDocument,\n options: ExportPdfFromDocumentOptions = {},\n): Promise<void> {\n const html = documentToHtml(doc, { ...options, standalone: true });\n await printHtmlInIframe(html, options.filename ?? options.title ?? \"Documento\");\n}\n\nexport interface SerializePaginatedHtmlOptions {\n /** Document title (becomes `<title>` and is used as default filename). */\n title?: string;\n /** Extra CSS appended to the cloned styles (e.g. for headers/footers). */\n extraCss?: string;\n /**\n * Override the `@page { size }`. Default reads `--ed-page-width` /\n * `--ed-page-height` CSS variables off the root (set by `<Editor>` from the\n * current `PageSettings`); falls back to A4 if absent.\n */\n pageSize?: { width: string; height: string };\n}\n\nexport interface ExportPdfFromElementOptions extends SerializePaginatedHtmlOptions {\n /** Suggested file name. */\n filename?: string;\n}\n\nfunction cssPageSizeFromRoot(\n cs: CSSStyleDeclaration | undefined,\n): { width: string; height: string } {\n if (!cs) return { width: \"210mm\", height: \"297mm\" };\n const w = cs.getPropertyValue(\"--ed-page-width\").trim();\n const h = cs.getPropertyValue(\"--ed-page-height\").trim();\n if (!w || !h) return { width: \"210mm\", height: \"297mm\" };\n // CSS vars come back as e.g. \"794px\". `@page` accepts px directly.\n return { width: w, height: h };\n}\n\n/**\n * Serialize a live editor root (the `.ed-root` paginated DOM) into a\n * self-contained HTML string. The output preserves the in-editor pagination\n * decisions verbatim — each `.ed-page` becomes a fixed-size page in the final\n * print medium with no re-layout possible.\n *\n * The returned HTML inlines every accessible stylesheet from the host document\n * (or links it as a fallback for cross-origin sheets) so the layout survives\n * transplant to an iframe, a saved file, or a headless renderer.\n *\n * This is the building block for two paths:\n * - `exportPdfFromElement` (this file): writes the HTML into a hidden iframe\n * and calls `window.print()` for the local \"Save as PDF\" flow.\n * - Upload-to-server snapshots: POST the string to a backend that converts it\n * to PDF via Puppeteer's `setContent + page.pdf()` without re-running the\n * pagination engine.\n */\nexport function serializePaginatedHtml(\n root: HTMLElement,\n options: SerializePaginatedHtmlOptions = {},\n): string {\n const clone = root.cloneNode(true) as HTMLElement;\n // Strip interactive scaffolding that print styles can't see ahead of time.\n clone.querySelectorAll(\"[contenteditable]\").forEach((el) => {\n (el as HTMLElement).removeAttribute(\"contenteditable\");\n });\n clone.querySelectorAll(\".ed-image-overlay\").forEach((el) => el.remove());\n clone.querySelectorAll(\".ed-remote-cursors\").forEach((el) => el.remove());\n\n // Read the page size baked into the editor via `--ed-page-*` CSS variables.\n // The Editor component sets these on its root from the current PageSettings\n // (`packages/react/src/Editor.tsx`). Falls back to A4 if we can't read them.\n const cs = root.ownerDocument?.defaultView?.getComputedStyle(root);\n const pageSize = options.pageSize ?? cssPageSizeFromRoot(cs);\n\n const styles = collectStyles();\n const title = escapeHtml(options.title ?? \"Documento\");\n const extra = options.extraCss ?? \"\";\n // The `@page { size }` and page-break rules live inside `@media print` so\n // they merge cleanly with any host-document print stylesheet that already\n // declares its own `@page` (e.g. `apps/playground/src/styles.css`). Putting\n // ours after the cloned `<style>`s gives our override last-write-wins.\n return [\n \"<!doctype html>\",\n `<html><head><meta charset=\"utf-8\"><meta name=\"ed-print-snapshot\" content=\"1\"><title>${title}</title>`,\n styles,\n `<style>\n html, body { margin: 0; padding: 0; background: white; }\n /* Preserva background-color (ex.: células da tabela de pontuação) no\n window.print(), onde \"gráficos de fundo\" vem desmarcado por padrão. */\n * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\n @media print {\n @page { size: ${pageSize.width} ${pageSize.height}; margin: 0; }\n .ed-page { box-shadow: none !important; border: none !important; border-radius: 0 !important; margin: 0 !important; page-break-after: always; break-after: page; }\n .ed-page:last-child { page-break-after: auto; break-after: auto; }\n .ed-image-overlay { display: none !important; }\n }\n ${extra}\n </style>`,\n \"</head><body>\",\n clone.outerHTML,\n \"</body></html>\",\n ].join(\"\");\n}\n\n/**\n * Snapshot a live editor root element (the `.ed-root` paginated DOM) and open\n * the browser's print dialog. Preserves the in-editor pagination decisions\n * verbatim — no re-layout in the print frame.\n *\n * For the \"send the snapshot to a server\" use case, use\n * `serializePaginatedHtml` directly and POST the returned string.\n */\nexport async function exportPdfFromElement(\n root: HTMLElement,\n options: ExportPdfFromElementOptions = {},\n): Promise<void> {\n const html = serializePaginatedHtml(root, options);\n await printHtmlInIframe(html, options.filename ?? options.title ?? \"Documento\");\n}\n\n// ---------- internals ----------\n\nasync function printHtmlInIframe(html: string, _filename: string): Promise<void> {\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\"aria-hidden\", \"true\");\n iframe.style.cssText =\n \"position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden;\";\n document.body.appendChild(iframe);\n\n const win = iframe.contentWindow;\n const docu = iframe.contentDocument ?? win?.document;\n if (!win || !docu) {\n iframe.remove();\n throw new Error(\"export-pdf: failed to access iframe document\");\n }\n\n docu.open();\n docu.write(html);\n docu.close();\n\n // Wait for the iframe document to finish loading (DOM, stylesheets, images).\n await new Promise<void>((resolve) => {\n if (docu.readyState === \"complete\") resolve();\n else iframe.addEventListener(\"load\", () => resolve(), { once: true });\n });\n // Best-effort wait for embedded images: print dialogs that snapshot too early\n // miss late-loading data URIs (rare, but real).\n await waitForImages(docu);\n // CRITICAL: wait for @font-face fonts to finish loading. The `load` event does\n // NOT wait for web fonts, so printing too early triggers FOIT (Flash of\n // Invisible Text): the browser hides every glyph until its font loads, while\n // text-decoration underlines still paint. Symptom seen in the wild: the\n // letterhead + field underlines printed but ALL text was missing. `fonts.ready`\n // resolves once all used fonts are loaded; cap the wait so a font that never\n // resolves can't hang the print.\n await waitForFonts(docu);\n\n await new Promise<void>((resolve) => {\n const cleanup = () => {\n win.removeEventListener(\"afterprint\", cleanup);\n // Defer removal one tick so Safari's print queue doesn't choke.\n setTimeout(() => iframe.remove(), 0);\n resolve();\n };\n win.addEventListener(\"afterprint\", cleanup);\n win.focus();\n win.print();\n // Fallback in case afterprint never fires (older browsers).\n setTimeout(() => {\n if (iframe.isConnected) cleanup();\n }, 60_000);\n });\n}\n\nfunction collectStyles(): string {\n const parts: string[] = [];\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n const rules = sheet.cssRules;\n if (!rules) continue;\n const css = Array.from(rules)\n .map((r) => r.cssText)\n .join(\"\\n\");\n if (css) parts.push(`<style>${css}</style>`);\n } catch {\n // Cross-origin sheet — fall back to linking it.\n const href = sheet.href;\n if (href) parts.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(href)}\">`);\n }\n }\n return parts.join(\"\\n\");\n}\n\nasync function waitForImages(docu: Document): Promise<void> {\n const imgs = Array.from(docu.images);\n await Promise.all(\n imgs.map(\n (img) =>\n new Promise<void>((resolve) => {\n if (img.complete) return resolve();\n img.addEventListener(\"load\", () => resolve(), { once: true });\n img.addEventListener(\"error\", () => resolve(), { once: true });\n }),\n ),\n );\n}\n\n/**\n * Wait for the iframe document's web fonts (`@font-face`) to finish loading,\n * capped so a font that never resolves can't hang the print. Without this the\n * print can fire mid-font-load and render invisible text (FOIT).\n */\nasync function waitForFonts(docu: Document): Promise<void> {\n const fonts = (docu as Document & { fonts?: FontFaceSet }).fonts;\n if (!fonts?.ready) return;\n await Promise.race([\n fonts.ready.then(() => undefined),\n new Promise<void>((resolve) => {\n setTimeout(resolve, 3000);\n }),\n ]);\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\nfunction escapeAttr(s: string): string {\n return escapeHtml(s);\n}\n","/**\n * Trigger a browser download for an in-memory blob/string. Tiny utility — kept\n * in this package because PDF/HTML/JSON exporters all want it.\n */\nexport function downloadBlob(blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n a.rel = \"noopener\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n setTimeout(() => {\n a.remove();\n URL.revokeObjectURL(url);\n }, 0);\n}\n\nexport function downloadString(\n text: string,\n filename: string,\n mimeType = \"text/plain;charset=utf-8\",\n): void {\n downloadBlob(new Blob([text], { type: mimeType }), filename);\n}\n"],"mappings":"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","names":["escapeHtml","escapeAttr","HTML_ESCAPES"]}
|
|
1
|
+
{"version":3,"sources":["../src/html.ts","../src/pdf.ts","../src/download.ts"],"sourcesContent":["import type {\n AlignValue,\n BlockAttrs,\n DeltaOp,\n DirValue,\n ImageEmbed,\n LegacySerializedDocument,\n ListKind,\n MarkAttrs,\n PageSettings,\n SerializedBlock,\n SerializedCell,\n SerializedDocument,\n} from \"@sofereditor/core\";\nimport {\n BLANK_STYLE,\n answerLineStyle,\n cellBorderStyle,\n isImageEmbed,\n isLegacySerializedDocument,\n pxToMm,\n splitUnderscoreRuns,\n styleToCssText,\n type CellBorderPos,\n type StyleRecord,\n} from \"@sofereditor/core\";\n\nfunction normalize(doc: SerializedDocument | LegacySerializedDocument): SerializedDocument {\n return isLegacySerializedDocument(doc) ? { blocks: doc } : doc;\n}\n\nexport interface DocumentToHtmlOptions {\n /** Document title (set on `<title>` and used as the file name hint). Default \"Documento\". */\n title?: string;\n /** When true (default) emits a full `<html>` document. When false emits only the body fragment. */\n standalone?: boolean;\n /** Extra CSS appended after the default print stylesheet. Only honored when `standalone`. */\n extraCss?: string;\n /** Page geometry hints injected into `@page`. Defaults to A4. */\n page?: {\n width: string;\n height: string;\n marginTop: string;\n marginBottom: string;\n marginLeft: string;\n marginRight: string;\n };\n}\n\nconst DEFAULT_PAGE = {\n width: \"210mm\",\n height: \"297mm\",\n marginTop: \"25mm\",\n marginBottom: \"25mm\",\n marginLeft: \"25mm\",\n marginRight: \"25mm\",\n};\n\n/**\n * Serialize a `SerializedDocument` to standalone HTML.\n *\n * Output structure mirrors what `@sofereditor/react` paints at runtime: each\n * top-level block becomes one element (`<p>`, `<h1..6>`, `<blockquote>`, `<pre>`,\n * `<table>`), consecutive `listItem` blocks are reconstructed into nested\n * `<ul>`/`<ol>` trees, and image embeds are emitted as `<img>` tags carrying the\n * embed's data URL (so the export is self-contained).\n *\n * NOT a fragment of the live editor DOM — this walks the model so the output is\n * editor-state agnostic (no caret artifacts, no zero-width phantom anchors).\n */\nexport function documentToHtml(\n doc: SerializedDocument | LegacySerializedDocument,\n options: DocumentToHtmlOptions = {},\n): string {\n const normalized = normalize(doc);\n const body = documentToHtmlFragment(normalized);\n if (options.standalone === false) return body;\n const page = options.page ?? pageFromSettings(normalized.pageSettings);\n const title = escapeHtml(options.title ?? \"Documento\");\n const extra = options.extraCss ?? \"\";\n return [\n \"<!doctype html>\",\n `<html lang=\"pt-BR\"><head><meta charset=\"utf-8\"><title>${title}</title>`,\n `<style>${baseStylesheet(page)}${extra}</style>`,\n \"</head><body>\",\n body,\n \"</body></html>\",\n ].join(\"\");\n}\n\nfunction pageFromSettings(s: PageSettings | undefined): NonNullable<DocumentToHtmlOptions[\"page\"]> {\n if (!s) return DEFAULT_PAGE;\n const fmt = (px: number) => `${pxToMm(px).toFixed(2)}mm`;\n return {\n width: fmt(s.width),\n height: fmt(s.height),\n marginTop: fmt(s.marginTop),\n marginBottom: fmt(s.marginBottom),\n marginLeft: fmt(s.marginLeft),\n marginRight: fmt(s.marginRight),\n };\n}\n\n/** Body-only HTML for embedding in other documents. */\nexport function documentToHtmlFragment(\n doc: SerializedDocument | LegacySerializedDocument,\n): string {\n const blocks = normalize(doc).blocks;\n const out: string[] = [];\n let i = 0;\n while (i < blocks.length) {\n const block = blocks[i];\n if (block.type === \"listItem\") {\n const groupStart = i;\n const kind: ListKind = block.attrs.listKind === \"ordered\" ? \"ordered\" : \"bullet\";\n const items: SerializedBlock[] = [];\n while (\n i < blocks.length &&\n blocks[i].type === \"listItem\" &&\n (blocks[i].attrs.listKind ?? \"bullet\") === kind\n ) {\n items.push(blocks[i]);\n i++;\n }\n out.push(renderListGroup(items, kind, groupStart));\n continue;\n }\n out.push(renderBlock(block, i));\n i++;\n }\n return out.join(\"\");\n}\n\n/** JSON export — a stable serialization for save/load round-trips. */\nexport function documentToJson(\n doc: SerializedDocument | LegacySerializedDocument,\n pretty = true,\n): string {\n return JSON.stringify(normalize(doc), null, pretty ? 2 : 0);\n}\n\n// ---------- blocks ----------\n\nfunction renderBlock(block: SerializedBlock, index: number): string {\n switch (block.type) {\n case \"paragraph\":\n return `<p${blockAttrs(\"ed-paragraph\", block.attrs)}>${renderInline(block.delta)}</p>`;\n case \"heading\": {\n const level = clampHeadingLevel(block.attrs.level);\n return `<h${level}${blockAttrs(`ed-heading ed-heading-${level}`, block.attrs)}>${renderInline(\n block.delta,\n )}</h${level}>`;\n }\n case \"blockquote\":\n return `<blockquote${blockAttrs(\"ed-blockquote\", block.attrs)}>${renderInline(\n block.delta,\n )}</blockquote>`;\n case \"codeBlock\": {\n const lang = block.attrs.language;\n const codeAttrs = lang ? ` class=\"language-${escapeAttr(lang)}\"` : \"\";\n return `<pre${blockAttrs(\"ed-codeblock\", block.attrs)}><code${codeAttrs}>${renderInline(\n block.delta,\n )}</code></pre>`;\n }\n case \"table\":\n return renderTable(block);\n default:\n return `<p${blockAttrs(\"ed-paragraph\", block.attrs)}>${renderInline(block.delta)}</p>`;\n }\n}\n\nfunction renderListGroup(items: SerializedBlock[], kind: ListKind, key: number): string {\n // Tree-build using listLevel just like Editor.buildListTree.\n interface Node { item: SerializedBlock; children: Node[]; }\n const roots: Node[] = [];\n const stack: { children: Node[]; level: number }[] = [{ children: roots, level: -1 }];\n for (const it of items) {\n const level = clampListLevel(it.attrs.listLevel);\n while (stack.length > 1 && stack[stack.length - 1].level >= level) stack.pop();\n const node: Node = { item: it, children: [] };\n stack[stack.length - 1].children.push(node);\n stack.push({ children: node.children, level });\n }\n return renderListTree(roots, kind, `list-${key}`);\n}\n\nfunction renderListTree(nodes: { item: SerializedBlock; children: any[] }[], kind: ListKind, _key: string): string {\n const Tag = kind === \"ordered\" ? \"ol\" : \"ul\";\n const cls = `ed-list ed-list-${kind}`;\n const items = nodes\n .map((n) => {\n const inner = renderInline(n.item.delta);\n const nested =\n n.children.length > 0 ? renderListTree(n.children, kind, _key + \"n\") : \"\";\n return `<li${blockAttrs(\"ed-listitem\", n.item.attrs)}>${inner}${nested}</li>`;\n })\n .join(\"\");\n return `<${Tag} class=\"${cls}\" data-list-kind=\"${kind}\">${items}</${Tag}>`;\n}\n\nfunction renderTable(block: SerializedBlock): string {\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 if (rows <= 0 || cols <= 0) return \"\";\n\n const colGroup = renderColGroup(block.attrs.colWidths, cols);\n const preset = block.attrs.borderPreset;\n const borderColor = block.attrs.borderColor;\n const trs: string[] = [];\n for (let r = 0; r < rows; r++) {\n const tds: string[] = [];\n for (let c = 0; c < cols; c++) {\n const cell = cells[r * cols + c];\n // Este caminho NÃO fragmenta a tabela (o HTML de servidor não pagina),\n // então os limites do fragmento são os da tabela lógica.\n const at = (rowspan: number, colspan: number): CellBorderPos => ({\n row: r,\n col: c,\n rowspan,\n colspan,\n cols,\n rowStart: 0,\n rowEnd: rows,\n });\n if (!cell) {\n // Célula ausente também precisa das bordas, senão vira um buraco na grade.\n const style = styleToCssText(cellBorderStyle(preset, at(1, 1), \"print\", borderColor));\n tds.push(`<td class=\"ed-cell\" style=\"${style}\"></td>`);\n continue;\n }\n if (cell.attrs?.covered) continue;\n const rowspan = cell.attrs?.rowspan && cell.attrs.rowspan > 1 ? cell.attrs.rowspan : 1;\n const colspan = cell.attrs?.colspan && cell.attrs.colspan > 1 ? cell.attrs.colspan : 1;\n tds.push(\n renderCell(cell, cellBorderStyle(preset, at(rowspan, colspan), \"print\", borderColor)),\n );\n }\n trs.push(`<tr>${tds.join(\"\")}</tr>`);\n }\n return `<div class=\"ed-table-wrap\"><table class=\"ed-table\">${colGroup}<tbody>${trs.join(\"\")}</tbody></table></div>`;\n}\n\nfunction renderColGroup(widths: number[] | undefined, cols: number): string {\n if (!widths || widths.length === 0) return \"\";\n const cells: string[] = [];\n for (let i = 0; i < cols; i++) {\n const w = widths[i];\n cells.push(typeof w === \"number\" ? `<col style=\"width:${w}px\">` : \"<col>\");\n }\n return `<colgroup>${cells.join(\"\")}</colgroup>`;\n}\n\nfunction renderCell(cell: SerializedCell, border: StyleRecord): string {\n const rs = cell.attrs?.rowspan && cell.attrs.rowspan > 1\n ? ` rowspan=\"${cell.attrs.rowspan}\"`\n : \"\";\n const cs = cell.attrs?.colspan && cell.attrs.colspan > 1\n ? ` colspan=\"${cell.attrs.colspan}\"`\n : \"\";\n // `variant: \"print\"` sempre: este HTML é para servidor/PDF, nunca para a tela\n // do editor — logo, lado desligado é `transparent`, nunca a guia visual.\n const styles: string[] = [styleToCssText(border)];\n const align = clampAlign(cell.attrs?.align);\n if (align) styles.push(`text-align:${align}`);\n if (cell.attrs?.bgColor) styles.push(`background-color:${cssValue(cell.attrs.bgColor)}`);\n const style = styles.length > 0 ? ` style=\"${styles.join(\";\")}\"` : \"\";\n return `<td class=\"ed-cell\"${rs}${cs}${style}>${renderInline(cell.delta)}</td>`;\n}\n\n// ---------- inline ----------\n\nfunction renderInline(delta: DeltaOp[]): string {\n if (delta.length === 0) return \"<br>\";\n const parts: string[] = [];\n // Floating wrap-left/wrap-right embeds are emitted FIRST so CSS float wraps\n // the following inline text — mirrors the in-editor renderer.\n const wraps: string[] = [];\n for (const op of delta) {\n if (typeof op.insert === \"string\") {\n if (op.insert.length === 0) continue;\n parts.push(applyMarks(decorateBlanks(op.insert), op.attributes));\n } else if (isImageEmbed(op.insert)) {\n const layout = op.insert.layout ?? \"inline\";\n if (layout === \"wrap-left\" || layout === \"wrap-right\") {\n wraps.push(renderImg(op.insert));\n } else {\n parts.push(renderImg(op.insert));\n }\n }\n }\n if (wraps.length === 0 && parts.length === 0) return \"<br>\";\n return wraps.join(\"\") + parts.join(\"\");\n}\n\n/**\n * Espelha `decorateBlanks` de `@sofereditor/react`: corridas de 3+ underlines\n * viram traço contínuo, com os caracteres preservados. A segmentação vem do\n * mesmo helper puro em `@sofereditor/core` — se divergir daqui, o teste de\n * paridade quebra.\n *\n * Devolve HTML já escapado, que é o que `applyMarks` espera receber.\n */\nfunction decorateBlanks(text: string): string {\n const segs = splitUnderscoreRuns(text);\n if (segs.length === 1 && !segs[0].blank) return escapeHtml(text);\n const css = styleToCssText(BLANK_STYLE);\n return segs\n .map((s) =>\n s.blank\n ? `<span data-blank=\"true\" style=\"${css}\">${escapeHtml(s.text)}</span>`\n : escapeHtml(s.text),\n )\n .join(\"\");\n}\n\nfunction applyMarks(text: string, attrs: MarkAttrs | undefined): string {\n if (!attrs) return text;\n let html = text;\n if (attrs.bold) html = `<strong>${html}</strong>`;\n if (attrs.italic) html = `<em>${html}</em>`;\n if (attrs.underline) html = `<u>${html}</u>`;\n if (attrs.strike) html = `<s>${html}</s>`;\n const styles: string[] = [];\n if (attrs.color) styles.push(`color:${cssValue(attrs.color)}`);\n if (attrs.highlight) styles.push(`background-color:${cssValue(attrs.highlight)}`);\n if (attrs.fontFamily) styles.push(`font-family:${cssValue(attrs.fontFamily)}`);\n if (attrs.fontSize) styles.push(`font-size:${cssValue(attrs.fontSize)}`);\n if (styles.length > 0) html = `<span style=\"${styles.join(\";\")}\">${html}</span>`;\n if (attrs.link?.href) {\n const t = attrs.link.title ? ` title=\"${escapeAttr(attrs.link.title)}\"` : \"\";\n html = `<a href=\"${escapeAttr(attrs.link.href)}\"${t} rel=\"noopener noreferrer\" target=\"_blank\">${html}</a>`;\n }\n if (attrs.comment?.markId) {\n html = `<span data-comment-id=\"${escapeAttr(attrs.comment.markId)}\">${html}</span>`;\n }\n return html;\n}\n\nfunction renderImg(embed: ImageEmbed): string {\n const layout = embed.layout ?? \"inline\";\n const align = embed.align;\n const ox = embed.offsetX ?? 0;\n const oy = embed.offsetY ?? 0;\n const hasCaption = !!embed.caption && embed.caption.length > 0;\n\n // The wrapper carries the positioning (float/absolute/inline-block/block).\n // The img keeps the intrinsic size when wrapped; when there's no caption we\n // keep the historical bare `<img>` output for unchanged DOM/CSS.\n const wrapperStyles: string[] = [];\n switch (layout) {\n case \"wrap-left\":\n wrapperStyles.push(\n \"float:left\",\n `width:${embed.width}px`,\n `margin-right:${Math.max(8, ox)}px`,\n `margin-top:${Math.max(0, oy)}px`,\n \"margin-bottom:4px\",\n );\n break;\n case \"wrap-right\":\n wrapperStyles.push(\n \"float:right\",\n `width:${embed.width}px`,\n `margin-left:${Math.max(8, ox)}px`,\n `margin-top:${Math.max(0, oy)}px`,\n \"margin-bottom:4px\",\n );\n break;\n case \"behind\":\n case \"front\":\n wrapperStyles.push(\n \"position:absolute\",\n `left:${ox}px`,\n `top:${oy}px`,\n `width:${embed.width}px`,\n `z-index:${layout === \"behind\" ? -1 : 2}`,\n );\n break;\n case \"inline\":\n default:\n if (align) {\n wrapperStyles.push(\n \"display:block\",\n `width:${embed.width}px`,\n `margin-left:${align === \"left\" ? \"0\" : \"auto\"}`,\n `margin-right:${align === \"right\" ? \"0\" : \"auto\"}`,\n );\n } else {\n wrapperStyles.push(\n \"display:inline-block\",\n \"vertical-align:text-bottom\",\n `width:${embed.width}px`,\n );\n }\n break;\n }\n\n const imgStyles: string[] = hasCaption\n ? [`width:${embed.width}px`, `height:${embed.height}px`, \"display:block\"]\n : wrapperStyles.concat(\n // Preserve old height when emitting bare <img>.\n `height:${embed.height}px`,\n );\n\n const imgHtml = `<img src=\"${escapeAttr(embed.src)}\" width=\"${embed.width}\" height=\"${embed.height}\" alt=\"${escapeAttr(embed.caption ?? \"\")}\" data-embed=\"image\" data-embed-layout=\"${layout}\"${\n align ? ` data-embed-align=\"${escapeAttr(align)}\"` : \"\"\n } style=\"${imgStyles.join(\";\")}\">`;\n\n if (!hasCaption) return imgHtml;\n\n const captionAlign = embed.captionAlign ?? \"center\";\n return `<figure class=\"ed-figure\" data-embed-figure=\"true\" data-embed-layout=\"${layout}\" style=\"${wrapperStyles.join(\";\")}\">${imgHtml}<figcaption class=\"ed-figcaption\" data-caption-align=\"${captionAlign}\" style=\"text-align:${captionAlign}\">${escapeHtml(\n embed.caption!,\n )}</figcaption></figure>`;\n}\n\n// ---------- helpers ----------\n\nfunction blockAttrs(baseClass: string, attrs: BlockAttrs): string {\n const classes = [baseClass, \"ed-block\"];\n const align = clampAlign(attrs.align);\n if (align) classes.push(`ed-align-${align}`);\n const dir = clampDir(attrs.dir);\n const dirAttr = dir ? ` dir=\"${dir}\"` : \"\";\n // Linha de resposta: mesmo helper puro que o renderizador do editor usa, para\n // a régua e a entrelinha saírem idênticas nos dois caminhos.\n const answer = answerLineStyle(attrs);\n const styleAttr = answer ? ` style=\"${styleToCssText(answer)}\"` : \"\";\n return ` class=\"${classes.join(\" \")}\"${dirAttr}${styleAttr}`;\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 clampAlign(a: unknown): AlignValue | undefined {\n if (a === \"left\" || a === \"center\" || a === \"right\" || a === \"justify\") return a;\n return undefined;\n}\n\nfunction clampDir(d: unknown): DirValue | undefined {\n if (d === \"ltr\" || d === \"rtl\") return d;\n return undefined;\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\nfunction escapeAttr(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\n\n/** Allow only safe characters in CSS values — defense in depth against injected `</style>`. */\nfunction cssValue(v: string): string {\n return v.replace(/[<>\"';\\\\]/g, \"\");\n}\n\nfunction baseStylesheet(page: NonNullable<DocumentToHtmlOptions[\"page\"]>): string {\n // Margins are owned by `@page` only. Chromium honors that block both for\n // `window.print()` (client-side `exportPdfFromDocument`) and for\n // `page.pdf({ preferCSSPageSize: true })` (server-side `renderPdf` in\n // `@sofereditor/export-pdf-server`). Adding the same offsets via `body { padding }`\n // would compound and double the margin in the output PDF.\n return `\n@page {\n size: ${page.width} ${page.height};\n margin: ${page.marginTop} ${page.marginRight} ${page.marginBottom} ${page.marginLeft};\n}\nhtml, body { margin: 0; padding: 0; background: white; color: #1a1a1f; }\n/* Sem isto, o diálogo de impressão descarta background-color/imagens de\n fundo por padrão (\"gráficos de fundo\" desmarcado) — ex.: células com\n bgColor da tabela de pontuação saíam brancas no PDF. */\n* { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\nbody {\n font-family: \"Arial\", \"Helvetica\", sans-serif;\n font-size: 11pt;\n line-height: 1.5;\n}\n.ed-paragraph { margin: 0 0 0.5em; min-height: 1.5em; }\n.ed-heading { margin: 0.5em 0 0.25em; font-weight: 600; line-height: 1.25; }\n.ed-heading-1 { font-size: 28pt; }\n.ed-heading-2 { font-size: 22pt; }\n.ed-heading-3 { font-size: 18pt; }\n.ed-heading-4 { font-size: 14pt; }\n.ed-heading-5 { font-size: 12pt; }\n.ed-heading-6 { font-size: 11pt; color: #6b7280; }\n.ed-blockquote {\n margin: 0 0 0.5em; padding: 0.25em 12px;\n border-inline-start: 3px solid #e5e7eb; color: #6b7280;\n}\n.ed-codeblock {\n margin: 0 0 0.5em; background: #0f172a; color: #e2e8f0;\n padding: 12px 14px; border-radius: 6px;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11pt; white-space: pre-wrap;\n}\n.ed-codeblock code { font-family: inherit; background: transparent; color: inherit; }\n.ed-block[dir=\"rtl\"] { text-align: start; }\n.ed-align-left { text-align: left; }\n.ed-align-center { text-align: center; }\n.ed-align-right { text-align: right; }\n.ed-align-justify { text-align: justify; }\n.ed-list { margin: 0 0 0.5em; padding-inline-start: 28px; }\n.ed-list .ed-list { margin-block: 0; }\n.ed-list-bullet { list-style-type: disc; }\n.ed-list-bullet .ed-list-bullet { list-style-type: circle; }\n.ed-list-bullet .ed-list-bullet .ed-list-bullet { list-style-type: square; }\n.ed-list-ordered { list-style-type: decimal; }\n.ed-list-ordered .ed-list-ordered { list-style-type: lower-alpha; }\n.ed-list-ordered .ed-list-ordered .ed-list-ordered { list-style-type: lower-roman; }\n.ed-listitem { margin: 0; min-height: 1.5em; }\n.ed-table-wrap { margin: 0 0 0.5em; position: relative; }\n.ed-table { width: 100%; border-collapse: collapse; table-layout: fixed; }\n.ed-cell {\n border: 1px solid #cbd5e1; padding: 6px 8px;\n vertical-align: top; word-break: break-word; white-space: pre-wrap;\n}\nimg[data-embed=\"image\"] { max-width: 100%; }\n.ed-block { position: relative; isolation: isolate; }\na { color: #2563eb; }\n`;\n}\n","import type { LegacySerializedDocument, SerializedDocument } from \"@sofereditor/core\";\nimport { documentToHtml, type DocumentToHtmlOptions } from \"./html\";\n\nexport interface ExportPdfFromDocumentOptions extends DocumentToHtmlOptions {\n /** Suggested file name (used by the OS save dialog as the default). */\n filename?: string;\n}\n\n/**\n * Open the browser's print dialog for a `SerializedDocument`.\n *\n * Strategy: render the document to standalone HTML in a hidden iframe and call\n * `iframe.contentWindow.print()`. The user picks \"Save as PDF\" (or any printer)\n * from the native dialog. No external PDF library required.\n *\n * The returned promise resolves after the print dialog closes (`afterprint`).\n */\nexport async function exportPdfFromDocument(\n doc: SerializedDocument | LegacySerializedDocument,\n options: ExportPdfFromDocumentOptions = {},\n): Promise<void> {\n const html = documentToHtml(doc, { ...options, standalone: true });\n await printHtmlInIframe(html, options.filename ?? options.title ?? \"Documento\");\n}\n\nexport interface SerializePaginatedHtmlOptions {\n /** Document title (becomes `<title>` and is used as default filename). */\n title?: string;\n /** Extra CSS appended to the cloned styles (e.g. for headers/footers). */\n extraCss?: string;\n /**\n * Override the `@page { size }`. Default reads `--ed-page-width` /\n * `--ed-page-height` CSS variables off the root (set by `<Editor>` from the\n * current `PageSettings`); falls back to A4 if absent.\n */\n pageSize?: { width: string; height: string };\n}\n\nexport interface ExportPdfFromElementOptions extends SerializePaginatedHtmlOptions {\n /** Suggested file name. */\n filename?: string;\n}\n\nfunction cssPageSizeFromRoot(\n cs: CSSStyleDeclaration | undefined,\n): { width: string; height: string } {\n if (!cs) return { width: \"210mm\", height: \"297mm\" };\n const w = cs.getPropertyValue(\"--ed-page-width\").trim();\n const h = cs.getPropertyValue(\"--ed-page-height\").trim();\n if (!w || !h) return { width: \"210mm\", height: \"297mm\" };\n // CSS vars come back as e.g. \"794px\". `@page` accepts px directly.\n return { width: w, height: h };\n}\n\n/**\n * Serialize a live editor root (the `.ed-root` paginated DOM) into a\n * self-contained HTML string. The output preserves the in-editor pagination\n * decisions verbatim — each `.ed-page` becomes a fixed-size page in the final\n * print medium with no re-layout possible.\n *\n * The returned HTML inlines every accessible stylesheet from the host document\n * (or links it as a fallback for cross-origin sheets) so the layout survives\n * transplant to an iframe, a saved file, or a headless renderer.\n *\n * This is the building block for two paths:\n * - `exportPdfFromElement` (this file): writes the HTML into a hidden iframe\n * and calls `window.print()` for the local \"Save as PDF\" flow.\n * - Upload-to-server snapshots: POST the string to a backend that converts it\n * to PDF via Puppeteer's `setContent + page.pdf()` without re-running the\n * pagination engine.\n */\nexport function serializePaginatedHtml(\n root: HTMLElement,\n options: SerializePaginatedHtmlOptions = {},\n): string {\n const clone = root.cloneNode(true) as HTMLElement;\n // Strip interactive scaffolding that print styles can't see ahead of time.\n clone.querySelectorAll(\"[contenteditable]\").forEach((el) => {\n (el as HTMLElement).removeAttribute(\"contenteditable\");\n });\n clone.querySelectorAll(\".ed-image-overlay\").forEach((el) => el.remove());\n clone.querySelectorAll(\".ed-remote-cursors\").forEach((el) => el.remove());\n\n // Read the page size baked into the editor via `--ed-page-*` CSS variables.\n // The Editor component sets these on its root from the current PageSettings\n // (`packages/react/src/Editor.tsx`). Falls back to A4 if we can't read them.\n const cs = root.ownerDocument?.defaultView?.getComputedStyle(root);\n const pageSize = options.pageSize ?? cssPageSizeFromRoot(cs);\n\n const styles = collectStyles();\n const title = escapeHtml(options.title ?? \"Documento\");\n const extra = options.extraCss ?? \"\";\n // The `@page { size }` and page-break rules live inside `@media print` so\n // they merge cleanly with any host-document print stylesheet that already\n // declares its own `@page` (e.g. `apps/playground/src/styles.css`). Putting\n // ours after the cloned `<style>`s gives our override last-write-wins.\n return [\n \"<!doctype html>\",\n `<html><head><meta charset=\"utf-8\"><meta name=\"ed-print-snapshot\" content=\"1\"><title>${title}</title>`,\n styles,\n `<style>\n html, body { margin: 0; padding: 0; background: white; }\n /* Preserva background-color (ex.: células da tabela de pontuação) no\n window.print(), onde \"gráficos de fundo\" vem desmarcado por padrão. */\n * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\n @media print {\n /* Guias de tela das bordas desligadas por preset. O snapshot clona o DOM\n vivo do editor, então elas viriam junto — aqui somem. A geometria não\n muda: o lado continua com 1px, só perde a cor. */\n .ed-root, .ed-page, .ed-table, .ed-cell { --ed-guide-color: transparent !important; }\n @page { size: ${pageSize.width} ${pageSize.height}; margin: 0; }\n .ed-page { box-shadow: none !important; border: none !important; border-radius: 0 !important; margin: 0 !important; page-break-after: always; break-after: page; }\n .ed-page:last-child { page-break-after: auto; break-after: auto; }\n .ed-image-overlay { display: none !important; }\n }\n ${extra}\n </style>`,\n \"</head><body>\",\n clone.outerHTML,\n \"</body></html>\",\n ].join(\"\");\n}\n\n/**\n * Snapshot a live editor root element (the `.ed-root` paginated DOM) and open\n * the browser's print dialog. Preserves the in-editor pagination decisions\n * verbatim — no re-layout in the print frame.\n *\n * For the \"send the snapshot to a server\" use case, use\n * `serializePaginatedHtml` directly and POST the returned string.\n */\nexport async function exportPdfFromElement(\n root: HTMLElement,\n options: ExportPdfFromElementOptions = {},\n): Promise<void> {\n const html = serializePaginatedHtml(root, options);\n await printHtmlInIframe(html, options.filename ?? options.title ?? \"Documento\");\n}\n\n// ---------- internals ----------\n\nasync function printHtmlInIframe(html: string, _filename: string): Promise<void> {\n const iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\"aria-hidden\", \"true\");\n iframe.style.cssText =\n \"position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden;\";\n document.body.appendChild(iframe);\n\n const win = iframe.contentWindow;\n const docu = iframe.contentDocument ?? win?.document;\n if (!win || !docu) {\n iframe.remove();\n throw new Error(\"export-pdf: failed to access iframe document\");\n }\n\n docu.open();\n docu.write(html);\n docu.close();\n\n // Wait for the iframe document to finish loading (DOM, stylesheets, images).\n await new Promise<void>((resolve) => {\n if (docu.readyState === \"complete\") resolve();\n else iframe.addEventListener(\"load\", () => resolve(), { once: true });\n });\n // Best-effort wait for embedded images: print dialogs that snapshot too early\n // miss late-loading data URIs (rare, but real).\n await waitForImages(docu);\n // CRITICAL: wait for @font-face fonts to finish loading. The `load` event does\n // NOT wait for web fonts, so printing too early triggers FOIT (Flash of\n // Invisible Text): the browser hides every glyph until its font loads, while\n // text-decoration underlines still paint. Symptom seen in the wild: the\n // letterhead + field underlines printed but ALL text was missing. `fonts.ready`\n // resolves once all used fonts are loaded; cap the wait so a font that never\n // resolves can't hang the print.\n await waitForFonts(docu);\n\n await new Promise<void>((resolve) => {\n const cleanup = () => {\n win.removeEventListener(\"afterprint\", cleanup);\n // Defer removal one tick so Safari's print queue doesn't choke.\n setTimeout(() => iframe.remove(), 0);\n resolve();\n };\n win.addEventListener(\"afterprint\", cleanup);\n win.focus();\n win.print();\n // Fallback in case afterprint never fires (older browsers).\n setTimeout(() => {\n if (iframe.isConnected) cleanup();\n }, 60_000);\n });\n}\n\nfunction collectStyles(): string {\n const parts: string[] = [];\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n const rules = sheet.cssRules;\n if (!rules) continue;\n const css = Array.from(rules)\n .map((r) => r.cssText)\n .join(\"\\n\");\n if (css) parts.push(`<style>${css}</style>`);\n } catch {\n // Cross-origin sheet — fall back to linking it.\n const href = sheet.href;\n if (href) parts.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(href)}\">`);\n }\n }\n return parts.join(\"\\n\");\n}\n\nasync function waitForImages(docu: Document): Promise<void> {\n const imgs = Array.from(docu.images);\n await Promise.all(\n imgs.map(\n (img) =>\n new Promise<void>((resolve) => {\n if (img.complete) return resolve();\n img.addEventListener(\"load\", () => resolve(), { once: true });\n img.addEventListener(\"error\", () => resolve(), { once: true });\n }),\n ),\n );\n}\n\n/**\n * Wait for the iframe document's web fonts (`@font-face`) to finish loading,\n * capped so a font that never resolves can't hang the print. Without this the\n * print can fire mid-font-load and render invisible text (FOIT).\n */\nasync function waitForFonts(docu: Document): Promise<void> {\n const fonts = (docu as Document & { fonts?: FontFaceSet }).fonts;\n if (!fonts?.ready) return;\n await Promise.race([\n fonts.ready.then(() => undefined),\n new Promise<void>((resolve) => {\n setTimeout(resolve, 3000);\n }),\n ]);\n}\n\nconst HTML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => HTML_ESCAPES[c]);\n}\nfunction escapeAttr(s: string): string {\n return escapeHtml(s);\n}\n","/**\n * Trigger a browser download for an in-memory blob/string. Tiny utility — kept\n * in this package because PDF/HTML/JSON exporters all want it.\n */\nexport function downloadBlob(blob: Blob, filename: string): void {\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n a.rel = \"noopener\";\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n setTimeout(() => {\n a.remove();\n URL.revokeObjectURL(url);\n }, 0);\n}\n\nexport function downloadString(\n text: string,\n filename: string,\n mimeType = \"text/plain;charset=utf-8\",\n): void {\n downloadBlob(new Blob([text], { type: mimeType }), filename);\n}\n"],"mappings":"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","names":["rowspan","colspan","escapeHtml","escapeAttr","HTML_ESCAPES"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sofereditor/export-pdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "@sofereditor/export-pdf — Serialize the paginated Sofer DOM to self-contained HTML, ready for Puppeteer PDF rendering.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Rafael Marreca",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
],
|
|
23
23
|
"type": "module",
|
|
24
24
|
"main": "./dist/index.cjs",
|
|
25
|
-
"module": "./dist/index.js",
|
|
26
25
|
"types": "./dist/index.d.ts",
|
|
27
26
|
"exports": {
|
|
28
27
|
".": {
|
|
@@ -36,6 +35,18 @@
|
|
|
36
35
|
"README.md",
|
|
37
36
|
"LICENSE"
|
|
38
37
|
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"main": "./dist/index.cjs",
|
|
40
|
+
"module": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"import": "./dist/index.js",
|
|
46
|
+
"require": "./dist/index.cjs"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
39
50
|
"scripts": {
|
|
40
51
|
"build": "tsup",
|
|
41
52
|
"dev": "tsup --watch",
|
|
@@ -43,11 +54,12 @@
|
|
|
43
54
|
"test": "vitest run"
|
|
44
55
|
},
|
|
45
56
|
"dependencies": {
|
|
46
|
-
"@sofereditor/core": "^0.
|
|
57
|
+
"@sofereditor/core": "^0.5.0"
|
|
47
58
|
},
|
|
48
59
|
"devDependencies": {
|
|
49
60
|
"typescript": "^5.7.2",
|
|
50
61
|
"tsup": "^8.3.5",
|
|
51
62
|
"vitest": "^2.1.8"
|
|
52
|
-
}
|
|
63
|
+
},
|
|
64
|
+
"module": "./dist/index.js"
|
|
53
65
|
}
|