@pixerate/editor 0.72.0 → 0.74.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.
Files changed (42) hide show
  1. package/THIRD_PARTY_NOTICES.md +1 -0
  2. package/dist/animation-editor/index.cjs +786 -325
  3. package/dist/animation-editor/index.cjs.map +1 -1
  4. package/dist/animation-editor/index.d.cts +57 -2
  5. package/dist/animation-editor/index.d.ts +57 -2
  6. package/dist/animation-editor/index.js +750 -289
  7. package/dist/animation-editor/index.js.map +1 -1
  8. package/dist/chart/index.cjs +11 -9
  9. package/dist/chart/index.cjs.map +1 -1
  10. package/dist/chart/index.js +5 -3
  11. package/dist/chart/index.js.map +1 -1
  12. package/dist/{chunk-3S72F46R.js → chunk-2GFUA4X5.js} +2 -322
  13. package/dist/chunk-2GFUA4X5.js.map +1 -0
  14. package/dist/chunk-33AHNSEB.js +323 -0
  15. package/dist/chunk-33AHNSEB.js.map +1 -0
  16. package/dist/{chunk-NN3JGUSX.cjs → chunk-5PTR7HTL.cjs} +3 -323
  17. package/dist/chunk-5PTR7HTL.cjs.map +1 -0
  18. package/dist/{chunk-4YFMSPBU.js → chunk-EIXXBKUB.js} +6 -4
  19. package/dist/{chunk-4YFMSPBU.js.map → chunk-EIXXBKUB.js.map} +1 -1
  20. package/dist/chunk-EMQWJA3G.cjs +323 -0
  21. package/dist/chunk-EMQWJA3G.cjs.map +1 -0
  22. package/dist/{chunk-BQQZ6MOI.js → chunk-FGW5OWJ4.js} +6 -4
  23. package/dist/{chunk-BQQZ6MOI.js.map → chunk-FGW5OWJ4.js.map} +1 -1
  24. package/dist/{chunk-YZZMX6V7.cjs → chunk-FV5AXTSF.cjs} +11 -9
  25. package/dist/chunk-FV5AXTSF.cjs.map +1 -0
  26. package/dist/{chunk-7ZSU4MQZ.cjs → chunk-PL3MPFPA.cjs} +10 -8
  27. package/dist/chunk-PL3MPFPA.cjs.map +1 -0
  28. package/dist/export/index.cjs +4 -3
  29. package/dist/export/index.cjs.map +1 -1
  30. package/dist/export/index.js +3 -2
  31. package/dist/extensions/index.cjs +4 -3
  32. package/dist/extensions/index.cjs.map +1 -1
  33. package/dist/extensions/index.js +3 -2
  34. package/dist/index.cjs +19 -18
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.js +12 -11
  37. package/dist/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-3S72F46R.js.map +0 -1
  40. package/dist/chunk-7ZSU4MQZ.cjs.map +0 -1
  41. package/dist/chunk-NN3JGUSX.cjs.map +0 -1
  42. package/dist/chunk-YZZMX6V7.cjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-NN3JGUSX.cjs","../src/chart/ooxml.ts","../src/import/zip.ts"],"names":["rich"],"mappings":"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AACE,IAAA;AACxC,EAAA;AAE6B,EAAA;AACrB,EAAA;AACwB,EAAA;AACqB,IAAA;AACX,MAAA;AACxC,IAAA;AACkC,IAAA;AACE,IAAA;AACD,IAAA;AACO,IAAA;AACV,IAAA;AACQ,IAAA;AACC,IAAA;AACE,IAAA;AACE,IAAA;AACvB,IAAA;AACS,IAAA;AACS,IAAA;AACI,IAAA;AAGG,IAAA;AAClB,MAAA;AACK,MAAA;AACb,MAAA;AACL,QAAA;AACI,QAAA;AACyB,QAAA;AAChB,UAAA;AAClB,UAAA;AACP,QAAA;AACmD,QAAA;AAChB,UAAA;AAC5B,UAAA;AACP,QAAA;AACsD,QAAA;AACxD,MAAA;AACS,MAAA;AACX,IAAA;AAEqD,IAAA;AACd,IAAA;AACnC,IAAA;AAC0B,IAAA;AACQ,MAAA;AACiB,MAAA;AACxC,QAAA;AACZ,MAAA;AACH,IAAA;AACa,IAAA;AACX,MAAA;AAC8B,MAAA;AACE,MAAA;AAChC,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AACkD,EAAA;AACpD;AFwc6D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-NN3JGUSX.cjs","sourcesContent":[null,"// The DrawingML chart reader follows deckcraft-main/crates/pptx/src/read/chart.rs (group → type mapping,\n// secondary-axis detection by axis id and position, cache / literal data, number-format precedence) and the\n// chartex reader gridcraft-main/crates/xlsx/src/chartex.rs; the \"editable only when it regenerates\" idea is\n// from wordcraft-main/crates/docx/src/chart_spec.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors).\n// See THIRD_PARTY_NOTICES.md.\nimport { parseChartColor, chartRgbToHex } from './color.js';\nimport { chartFamily, chartSeriesType, defaultChartAxis, isCartesianChart, normalizeChartSpec } from './spec.js';\nimport type { ChartAxis, ChartErrorBars, ChartErrorBarType, ChartIssue, ChartLegendPosition, ChartMarker, ChartNumberFormat, ChartSeries, ChartSpec, ChartTrendline, ChartTrendlineType, ChartType, ChartValue } from './types.js';\n\nexport const CHART_OOXML_NS = 'http://schemas.openxmlformats.org/drawingml/2006/chart';\nexport const CHART_OOXML_CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml';\nexport const CHART_OOXML_RELATIONSHIP = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart';\n\ntype ParserCtor = { new (): DOMParser };\n\nexport interface ChartOoxmlReadOptions {\n\t/** `DOMParser` constructor (default: the global one; pass jsdom's or xmldom's in Node). */\n\tDOMParser?: ParserCtor;\n}\n\nexport interface ChartOoxmlReadResult {\n\tspec: ChartSpec;\n\t/** Problems and simplifications, as sentences. */\n\tissues: ChartIssue[];\n\t/** Features the file uses that the spec cannot hold (3-D, stock, surface, gradients, …). */\n\tunsupported: string[];\n\t/**\n\t * Nothing was dropped: writing `spec` back gives an equivalent chart. When false, keep the\n\t * original part (or show the chart read-only) if the file must stay as it was.\n\t */\n\tlossless: boolean;\n}\n\nconst MAX_POINTS = 100_000;\n\nfunction parser(options: ChartOoxmlReadOptions): ParserCtor {\n\tconst P = options.DOMParser ?? (globalThis as { DOMParser?: ParserCtor }).DOMParser;\n\tif (!P) throw new Error('Reading chart XML needs a DOMParser. In Node, pass one (e.g. from jsdom or @xmldom/xmldom) as { DOMParser }.');\n\treturn P;\n}\n\nconst kids = (el: Element | null | undefined, name?: string): Element[] => (el ? Array.from(el.childNodes).filter((n): n is Element => n.nodeType === 1 && (!name || (n as Element).localName === name)) : []);\nconst kid = (el: Element | null | undefined, name: string) => kids(el, name)[0];\nconst val = (el: Element | null | undefined, name: string) => kid(el, name)?.getAttribute('val') ?? undefined;\nconst path = (el: Element | null | undefined, ...names: string[]) => names.reduce<Element | undefined>((e, n) => kid(e, n), el ?? undefined);\nconst find = (el: Element | null | undefined, name: string): Element | undefined => {\n\tif (!el) return undefined;\n\tfor (const c of kids(el)) {\n\t\tif (c.localName === name) return c;\n\t\tconst deep = find(c, name);\n\t\tif (deep) return deep;\n\t}\n\treturn undefined;\n};\nconst bool = (v: string | undefined, fallback = false) => (v === undefined ? fallback : v === '1' || v === 'true');\nconst numberOf = (v: string | undefined) => {\n\tif (v === undefined) return null;\n\tconst n = Number(v);\n\treturn Number.isFinite(n) ? n : null;\n};\n\nfunction strings(e: Element | undefined): string[] {\n\tif (!e) return [];\n\tconst cache = find(e, 'strCache') ?? find(e, 'numCache') ?? find(e, 'strLit') ?? find(e, 'numLit');\n\tif (cache) {\n\t\tconst n = Math.min(MAX_POINTS, Number(val(cache, 'ptCount') ?? 0) || 0);\n\t\tconst out: string[] = Array(n).fill('');\n\t\tfor (const pt of kids(cache, 'pt').slice(0, MAX_POINTS)) {\n\t\t\tconst i = Number(pt.getAttribute('idx') ?? 0);\n\t\t\tif (!Number.isInteger(i) || i < 0 || i >= MAX_POINTS) continue;\n\t\t\twhile (out.length <= i) out.push('');\n\t\t\tout[i] = kid(pt, 'v')?.textContent ?? '';\n\t\t}\n\t\treturn out;\n\t}\n\tconst multi = find(e, 'multiLvlStrCache');\n\tif (multi) {\n\t\tconst lvl = kid(multi, 'lvl');\n\t\treturn kids(lvl, 'pt').map((p) => kid(p, 'v')?.textContent ?? '');\n\t}\n\tconst v = kid(e, 'v') ?? find(e, 'v');\n\treturn v ? [v.textContent ?? ''] : [];\n}\n\nconst numbers = (e: Element | undefined): ChartValue[] => strings(e).map((s) => (s.trim() === '' ? null : numberOf(s.trim())));\n\nfunction formatCodeOf(e: Element | undefined): string | undefined {\n\treturn find(e, 'numCache') ? (kid(find(e, 'numCache'), 'formatCode')?.textContent ?? undefined) : undefined;\n}\n\nfunction richText(t: Element | undefined): string | undefined {\n\tif (!t) return undefined;\n\tconst tx = kid(t, 'tx');\n\tconst rich = kid(tx, 'rich');\n\tif (rich) return kids(rich, 'p').map((p) => Array.from(p.getElementsByTagNameNS('*', 't')).map((x) => x.textContent ?? '').join('')).join('\\n');\n\tconst v = find(tx, 'v');\n\treturn v?.textContent ?? undefined;\n}\n\nfunction solidColor(spPr: Element | undefined): string | undefined {\n\tconst fill = kid(spPr, 'solidFill') ?? kid(kid(spPr, 'ln'), 'solidFill');\n\tconst srgb = kid(fill, 'srgbClr');\n\tconst v = srgb?.getAttribute('val');\n\treturn v && /^[0-9a-f]{6}$/i.test(v) ? `#${v.toLowerCase()}` : undefined;\n}\n\nconst MARKERS: Record<string, ChartMarker> = { circle: 'circle', dot: 'circle', square: 'square', diamond: 'diamond', triangle: 'triangle', x: 'cross', plus: 'cross', star: 'cross', dash: 'square', none: 'none' };\nconst TREND_IN: Record<string, ChartTrendlineType> = { linear: 'linear', exp: 'exponential', log: 'logarithmic', poly: 'polynomial', power: 'power', movingAvg: 'moving-average' };\nconst TREND_OUT: Record<ChartTrendlineType, string> = { linear: 'linear', exponential: 'exp', logarithmic: 'log', polynomial: 'poly', power: 'power', 'moving-average': 'movingAvg' };\nconst LEGEND_IN: Record<string, ChartLegendPosition> = { b: 'bottom', t: 'top', l: 'left', r: 'right', tr: 'right' };\nconst ERR_IN: Record<string, ChartErrorBarType> = { fixedVal: 'fixed', percentage: 'percent', stdDev: 'stddev', stdErr: 'stderr', cust: 'custom' };\nconst ERR_OUT: Record<ChartErrorBarType, string> = { fixed: 'fixedVal', percent: 'percentage', stddev: 'stdDev', stderr: 'stdErr', custom: 'cust' };\n\n/** Reads one `c:errBars` (Excel's error amount types map one to one); `null` for an unknown type. */\nfunction readErrorBars(e: Element): ChartErrorBars | null {\n\tconst type = ERR_IN[val(e, 'errValType') ?? 'fixedVal'];\n\tif (!type) return null;\n\tconst bars: ChartErrorBars = { type };\n\tconst value = numberOf(val(e, 'val'));\n\tif (value !== null && type !== 'custom' && type !== 'stderr') bars.value = Math.abs(value);\n\tif (type === 'custom') {\n\t\tconst plus = kid(e, 'plus');\n\t\tconst minus = kid(e, 'minus');\n\t\tconst p = plus ? numbers(plus).map((v) => (v === null ? null : Math.abs(v))) : [];\n\t\tconst m = minus ? numbers(minus).map((v) => (v === null ? null : Math.abs(v))) : [];\n\t\t// Minus amounts default to the plus ones; an empty side is left out.\n\t\tif (p.some((v) => v !== null)) bars.plus = p;\n\t\tif (m.some((v) => v !== null) && JSON.stringify(m) !== JSON.stringify(p)) bars.minus = m;\n\t}\n\tconst direction = val(e, 'errBarType');\n\tif (direction === 'plus' || direction === 'minus') bars.direction = direction;\n\tif (bool(val(e, 'noEndCap'))) bars.cap = false;\n\treturn bars;\n}\n\n/**\n * Reads a DrawingML chart part (`c:chartSpace`, e.g. `word/charts/chart1.xml`) or an Office\n * 2016 chartex part (`cx:chartSpace`: waterfall, funnel, treemap, histogram, box & whisker)\n * as a chart spec. Data comes from the cached values. Never throws for a malformed chart: it\n * returns an empty chart and says why.\n */\nexport function chartFromOoxml(xml: string, options: ChartOoxmlReadOptions = {}): ChartOoxmlReadResult {\n\tconst issues: ChartIssue[] = [];\n\tconst unsupported: string[] = [];\n\tlet doc: Document;\n\ttry {\n\t\tdoc = new (parser(options))().parseFromString(xml, 'application/xml');\n\t} catch (error) {\n\t\treturn { spec: normalizeChartSpec({}).spec, issues: [{ path: '', message: `The chart XML could not be read: ${(error as Error).message}` }], unsupported, lossless: false };\n\t}\n\tconst root = doc.documentElement;\n\tif (!root || root.localName !== 'chartSpace' || doc.getElementsByTagName('parsererror').length) {\n\t\treturn { spec: normalizeChartSpec({}).spec, issues: [{ path: '', message: 'This is not a chart part: expected a c:chartSpace (or cx:chartSpace) root element.' }], unsupported, lossless: false };\n\t}\n\tif (root.namespaceURI?.includes('chartex') || kid(root, 'chartData')) return readChartex(root, issues, unsupported);\n\n\tconst chart = kid(root, 'chart');\n\tconst plot = kid(chart, 'plotArea');\n\tconst input: Record<string, unknown> = { type: 'bar' };\n\tconst titleEl = kid(chart, 'title');\n\tif (titleEl && !bool(val(chart, 'autoTitleDeleted'))) input.title = richText(titleEl) ?? '';\n\tconst legend = kid(chart, 'legend');\n\tinput.legend = { show: !!legend, position: LEGEND_IN[val(legend, 'legendPos') ?? 'r'] ?? 'right' };\n\tif (kid(root, 'roundedCorners') && bool(val(root, 'roundedCorners'))) unsupported.push('rounded corners');\n\n\tconst valAxes = kids(plot, 'valAx');\n\tconst vertical = (a: Element) => ['l', 'r'].includes(val(a, 'axPos') ?? '');\n\tconst secondaryPlaced = (a: Element) => val(a, 'crosses') === 'max' || ['r', 't'].includes(val(a, 'axPos') ?? '');\n\tconst groupValAxis = (g: Element) => {\n\t\tconst ids = kids(g, 'axId').map((a) => a.getAttribute('val'));\n\t\tconst axes = ids.map((id) => valAxes.find((a) => val(a, 'axId') === id)).filter((a): a is Element => !!a);\n\t\treturn axes.find(vertical) ?? axes[0];\n\t};\n\tconst groups = kids(plot).filter((g) => g.localName.endsWith('Chart'));\n\tconst used = groups.map(groupValAxis).filter((a): a is Element => !!a);\n\tconst primaryAxis = used.find((a) => !secondaryPlaced(a)) ?? used[0];\n\n\tconst series: ChartSeries[] = [];\n\tlet categories: string[] = [];\n\tconst types: ChartType[] = [];\n\tlet stacking: ChartSpec['stacking'] = 'none';\n\tlet orientation: ChartSpec['orientation'] = 'vertical';\n\tlet dataLabels = false;\n\tlet points = false;\n\tfor (const g of groups) {\n\t\tconst name = g.localName;\n\t\tconst grouping = val(g, 'grouping') ?? 'clustered';\n\t\tlet type: ChartType;\n\t\tif (/3D/.test(name)) unsupported.push(`3-D ${name.replace(/3DChart$/, '')} chart (drawn flat)`);\n\t\tswitch (name) {\n\t\t\tcase 'barChart':\n\t\t\tcase 'bar3DChart':\n\t\t\t\ttype = 'bar';\n\t\t\t\tif (val(g, 'barDir') === 'bar') orientation = 'horizontal';\n\t\t\t\tbreak;\n\t\t\tcase 'lineChart':\n\t\t\tcase 'line3DChart':\n\t\t\t\ttype = 'line';\n\t\t\t\t// Points are on when a series shows a marker (the group's marker flag is usually set).\n\t\t\t\tif (kids(g, 'ser').some((s) => (val(kid(s, 'marker'), 'symbol') ?? (bool(val(g, 'marker'), true) ? 'auto' : 'none')) !== 'none')) points = true;\n\t\t\t\tbreak;\n\t\t\tcase 'areaChart':\n\t\t\tcase 'area3DChart':\n\t\t\t\ttype = 'area';\n\t\t\t\tbreak;\n\t\t\tcase 'pieChart':\n\t\t\tcase 'pie3DChart':\n\t\t\tcase 'ofPieChart':\n\t\t\t\ttype = 'pie';\n\t\t\t\tif (name === 'ofPieChart') unsupported.push('pie-of-pie (drawn as one pie)');\n\t\t\t\tbreak;\n\t\t\tcase 'doughnutChart':\n\t\t\t\ttype = 'donut';\n\t\t\t\tinput.innerRadius = Math.min(0.9, Math.max(0, (numberOf(val(g, 'holeSize')) ?? 50) / 100));\n\t\t\t\tbreak;\n\t\t\tcase 'scatterChart':\n\t\t\t\ttype = 'scatter';\n\t\t\t\tif (/line/i.test(val(g, 'scatterStyle') ?? '')) points = true;\n\t\t\t\tbreak;\n\t\t\tcase 'bubbleChart':\n\t\t\t\ttype = 'bubble';\n\t\t\t\tbreak;\n\t\t\tcase 'radarChart':\n\t\t\t\ttype = 'radar';\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tunsupported.push(`${name.replace(/Chart$/, '')} chart`);\n\t\t\t\tcontinue;\n\t\t}\n\t\tif (grouping === 'stacked') stacking = 'stacked';\n\t\telse if (grouping === 'percentStacked') stacking = 'percent';\n\t\tconst gap = numberOf(val(g, 'gapWidth'));\n\t\tconst overlap = numberOf(val(g, 'overlap'));\n\t\tif (type === 'bar') {\n\t\t\t// gapWidth is the gap between groups as a % of one bar; ours is a fraction of the category.\n\t\t\tconst n = Math.max(1, kids(g, 'ser').length);\n\t\t\tconst g100 = (gap ?? 150) / 100;\n\t\t\tconst o = Math.max(-1, Math.min(1, (overlap ?? (grouping === 'clustered' ? 0 : 100)) / 100));\n\t\t\tconst barsWidth = grouping === 'clustered' ? n - (n - 1) * o : 1;\n\t\t\tinput.gapWidth = Math.round(Math.max(0, Math.min(0.9, g100 / (barsWidth + g100))) * 100) / 100;\n\t\t\tif (grouping === 'clustered') input.overlap = o;\n\t\t}\n\t\tif (bool(val(kid(g, 'dLbls'), 'showVal'))) dataLabels = true;\n\t\ttypes.push(type);\n\t\tconst axis = groupValAxis(g);\n\t\tconst secondary = !!axis && !!primaryAxis && axis !== primaryAxis;\n\t\tfor (const s of kids(g, 'ser').slice(0, 1000)) {\n\t\t\tconst out: ChartSeries = { id: `series-${series.length + 1}`, name: strings(kid(s, 'tx'))[0] ?? `Series ${series.length + 1}`, values: [] };\n\t\t\tconst color = solidColor(kid(s, 'spPr'));\n\t\t\tif (color) out.color = color;\n\t\t\tout.type = type === 'bubble' ? undefined : (type as ChartSeries['type']);\n\t\t\tif (secondary) out.axis = 'secondary';\n\t\t\tconst marker = val(kid(s, 'marker'), 'symbol');\n\t\t\t// Circles (and none on a line chart without points) are the defaults; other shapes are kept.\n\t\t\tif (marker && MARKERS[marker] && MARKERS[marker] !== 'circle' && (type === 'scatter' || MARKERS[marker] !== 'none') && (type === 'line' || type === 'scatter')) out.marker = MARKERS[marker];\n\t\t\tif (bool(val(s, 'smooth'))) out.curve = 'smooth';\n\t\t\tconst sl = kid(s, 'dLbls');\n\t\t\tif (sl && val(sl, 'showVal') !== undefined) out.dataLabels = bool(val(sl, 'showVal'));\n\t\t\tconst labelCode = [kid(sl, 'numFmt'), kid(kid(g, 'dLbls'), 'numFmt')].find((n) => n && n.getAttribute('sourceLinked') !== '1')?.getAttribute('formatCode');\n\t\t\tconst valueEl = kid(s, 'val') ?? kid(s, 'yVal');\n\t\t\tconst code = labelCode ?? formatCodeOf(valueEl);\n\t\t\tif (code && code !== 'General') out.format = { code };\n\t\t\tconst catEl = kid(s, 'cat') ?? kid(s, 'xVal');\n\t\t\tconst cats = strings(catEl);\n\t\t\tif (type === 'scatter' || type === 'bubble') {\n\t\t\t\tconst xs = cats.map((c) => numberOf(c.trim()));\n\t\t\t\tif (xs.some((x) => x !== null)) out.x = xs;\n\t\t\t}\n\t\t\tif (!categories.length && cats.length) categories = cats;\n\t\t\tout.values = numbers(valueEl);\n\t\t\tif (type === 'bubble') out.sizes = numbers(kid(s, 'bubbleSize'));\n\t\t\tconst dPts = kids(s, 'dPt');\n\t\t\tif (dPts.length) {\n\t\t\t\tconst pc: (string | null)[] = [];\n\t\t\t\tfor (const dp of dPts) {\n\t\t\t\t\tconst i = Number(val(dp, 'idx'));\n\t\t\t\t\tconst c = solidColor(kid(dp, 'spPr'));\n\t\t\t\t\tif (Number.isInteger(i) && i >= 0 && i < MAX_POINTS && c) {\n\t\t\t\t\t\twhile (pc.length <= i) pc.push(null);\n\t\t\t\t\t\tpc[i] = c;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (pc.some(Boolean)) out.pointColors = pc;\n\t\t\t}\n\t\t\tconst trends: ChartTrendline[] = [];\n\t\t\tfor (const t of kids(s, 'trendline')) {\n\t\t\t\tconst kind = TREND_IN[val(t, 'trendlineType') ?? ''];\n\t\t\t\tif (!kind) continue;\n\t\t\t\tconst line: ChartTrendline = { type: kind };\n\t\t\t\tconst order = numberOf(val(t, 'order'));\n\t\t\t\tconst period = numberOf(val(t, 'period'));\n\t\t\t\tif (kind === 'polynomial' && order) line.order = order;\n\t\t\t\tif (kind === 'moving-average' && period) line.period = period;\n\t\t\t\tfor (const k of ['forward', 'backward', 'intercept'] as const) {\n\t\t\t\t\tconst n = numberOf(val(t, k));\n\t\t\t\t\tif (n !== null && (k === 'intercept' || n > 0)) line[k] = n;\n\t\t\t\t}\n\t\t\t\tif (bool(val(t, 'dispEq'))) line.showEquation = true;\n\t\t\t\tif (bool(val(t, 'dispRSqr'))) line.showR2 = true;\n\t\t\t\tconst tName = kid(t, 'name')?.textContent;\n\t\t\t\tif (tName) line.name = tName;\n\t\t\t\tconst tc = solidColor(kid(t, 'spPr'));\n\t\t\t\tif (tc) line.color = tc;\n\t\t\t\ttrends.push(line);\n\t\t\t}\n\t\t\tif (trends.length) out.trendlines = trends;\n\t\t\tfor (const e of kids(s, 'errBars')) {\n\t\t\t\tconst bars = readErrorBars(e);\n\t\t\t\tif (!bars) {\n\t\t\t\t\tunsupported.push(`error bars of type \"${val(e, 'errValType')}\"`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// x error bars only exist on scatter and bubble series.\n\t\t\t\tif (val(e, 'errDir') === 'x') {\n\t\t\t\t\tif (type === 'scatter' || type === 'bubble') out.xErrorBars = bars;\n\t\t\t\t\telse unsupported.push('x error bars on a category chart');\n\t\t\t\t} else if (type === 'pie' || type === 'donut' || type === 'radar') unsupported.push(`error bars on a ${type} chart`);\n\t\t\t\telse out.errorBars = bars;\n\t\t\t}\n\t\t\tseries.push(out);\n\t\t}\n\t}\n\tif (!types.length) {\n\t\tissues.push({ path: 'plotArea', message: unsupported.length ? `The chart type is not supported: ${unsupported.join(', ')}.` : 'The chart has no plot groups.' });\n\t\treturn { spec: normalizeChartSpec(input).spec, issues, unsupported, lossless: false };\n\t}\n\tconst first = types[0];\n\tconst combo = types.some((t) => t !== first);\n\tinput.type = combo ? (types.includes('bar') ? 'bar' : first) : first;\n\tif (!combo) for (const s of series) delete s.type;\n\telse for (const s of series) if (s.type === input.type) delete s.type;\n\tif (!combo) for (const s of series) if (s.axis) issues.push({ path: 'series', message: `\"${s.name}\" is on a second value axis.` });\n\tif (types.some((t) => !isCartesianChart(t)) && combo) unsupported.push('a pie or radar mixed with other chart types');\n\tinput.series = series;\n\tinput.categories = categories.length ? categories : Array.from({ length: Math.max(0, ...series.map((s) => s.values.length)) }, (_, i) => `${i + 1}`);\n\tinput.stacking = stacking;\n\tinput.orientation = orientation;\n\tinput.dataLabels = dataLabels;\n\tinput.points = points || first !== 'line';\n\tif (bool(val(groups[0], 'varyColors')) && (first === 'bar' || first === 'line') && series.length === 1) unsupported.push('one colour per point (vary colours)');\n\n\t// Axes\n\tconst readAxis = (a: Element | undefined, fallback: ChartAxis): ChartAxis => {\n\t\tconst axis: ChartAxis = { ...fallback, format: {} };\n\t\tif (!a) return axis;\n\t\taxis.show = !bool(val(a, 'delete'));\n\t\taxis.title = richText(kid(a, 'title')) ?? '';\n\t\taxis.gridlines = !!kid(a, 'majorGridlines');\n\t\tconst scaling = kid(a, 'scaling');\n\t\taxis.min = numberOf(val(scaling, 'min'));\n\t\taxis.max = numberOf(val(scaling, 'max'));\n\t\tif (val(scaling, 'orientation') === 'maxMin') axis.reverse = true;\n\t\tif (kid(scaling, 'logBase')) {\n\t\t\tif (a.localName !== 'valAx') unsupported.push('a logarithmic category axis');\n\t\t\telse {\n\t\t\t\taxis.scale = 'log';\n\t\t\t\tconst base = numberOf(val(scaling, 'logBase')) ?? 10;\n\t\t\t\tif (base !== 10) axis.logBase = base;\n\t\t\t}\n\t\t}\n\t\taxis.step = numberOf(val(a, 'majorUnit'));\n\t\tconst fmt = kid(a, 'numFmt');\n\t\tconst code = fmt?.getAttribute('formatCode');\n\t\tif (code && code !== 'General' && fmt?.getAttribute('sourceLinked') !== '1') axis.format = { code };\n\t\treturn axis;\n\t};\n\tconst xy = first === 'scatter' || first === 'bubble';\n\tconst catAxis = kid(plot, 'catAx') ?? kid(plot, 'dateAx') ?? (xy ? valAxes.find((a) => !vertical(a)) : undefined);\n\tinput.xAxis = readAxis(catAxis, defaultChartAxis(false));\n\tif (kid(plot, 'dateAx')) (input.xAxis as ChartAxis).kind = 'date';\n\tinput.yAxis = readAxis(primaryAxis ?? valAxes.find(vertical) ?? valAxes[0], defaultChartAxis(true));\n\tconst secondaryAxis = valAxes.find((a) => a !== primaryAxis && (xy ? vertical(a) && a !== primaryAxis : true) && a !== catAxis);\n\tif (series.some((s) => s.axis === 'secondary') && secondaryAxis) input.yAxis2 = readAxis(secondaryAxis, defaultChartAxis(false));\n\tconst { spec, issues: specIssues } = normalizeChartSpec(input);\n\tissues.push(...specIssues);\n\tconst uniq = [...new Set(unsupported)];\n\treturn { spec, issues, unsupported: uniq, lossless: uniq.length === 0 && specIssues.length === 0 };\n}\n\nfunction readChartex(root: Element, issues: ChartIssue[], unsupported: string[]): ChartOoxmlReadResult {\n\tconst data = new Map<string, { categories: string[]; values: ChartValue[]; code?: string }>();\n\tfor (const d of kids(kid(root, 'chartData'), 'data')) {\n\t\tconst id = d.getAttribute('id') ?? String(data.size);\n\t\tconst strDim = kids(d, 'strDim')[0];\n\t\tconst numDim = kids(d, 'numDim')[0];\n\t\tconst lvl = (dim: Element | undefined) => kids(dim, 'lvl')[0];\n\t\tconst cats = kids(lvl(strDim), 'pt').map((p) => p.textContent ?? '');\n\t\tconst values: ChartValue[] = [];\n\t\tconst nl = lvl(numDim);\n\t\tfor (const p of kids(nl, 'pt')) {\n\t\t\tconst i = Number(p.getAttribute('idx') ?? values.length);\n\t\t\twhile (values.length < i) values.push(null);\n\t\t\tvalues[i] = numberOf((p.textContent ?? '').trim());\n\t\t}\n\t\tdata.set(id, { categories: cats, values, code: nl?.getAttribute('formatCode') ?? undefined });\n\t}\n\tconst chart = kid(root, 'chart');\n\tconst region = path(chart, 'plotArea', 'plotAreaRegion');\n\tconst seriesEls = kids(region, 'series');\n\tconst layouts: Record<string, ChartType> = { waterfall: 'waterfall', funnel: 'funnel', treemap: 'treemap', sunburst: 'treemap', boxWhisker: 'box', clusteredColumn: 'histogram', paretoLine: 'histogram' };\n\tconst layout = seriesEls[0]?.getAttribute('layoutId') ?? '';\n\tconst type = layouts[layout];\n\tif (!type) {\n\t\tissues.push({ path: 'plotArea', message: `The chart layout \"${layout || 'unknown'}\" is not supported.` });\n\t\treturn { spec: normalizeChartSpec({}).spec, issues, unsupported: [layout || 'unknown chartex layout'], lossless: false };\n\t}\n\tif (layout === 'sunburst') unsupported.push('sunburst (drawn as a treemap of the innermost level)');\n\tif (layout === 'paretoLine') unsupported.push('pareto line');\n\tconst series: ChartSeries[] = [];\n\tlet categories: string[] = [];\n\tfor (const s of seriesEls) {\n\t\tif (s.getAttribute('layoutId') === 'paretoLine') continue;\n\t\tconst ref = kid(s, 'dataId')?.getAttribute('val') ?? '0';\n\t\tconst d = data.get(ref);\n\t\tif (!d) continue;\n\t\tif (!categories.length) categories = d.categories;\n\t\tconst out: ChartSeries = { id: `series-${series.length + 1}`, name: strings(kid(s, 'tx'))[0] ?? `Series ${series.length + 1}`, values: d.values };\n\t\tif (d.code && d.code !== 'General') out.format = { code: d.code };\n\t\tseries.push(out);\n\t}\n\tconst title = richText(kid(chart, 'title'));\n\tconst longest = Math.max(0, ...series.map((s) => s.values.length));\n\tconst input: Record<string, unknown> = {\n\t\ttype,\n\t\ttitle: title ?? '',\n\t\tseries,\n\t\tcategories: categories.length >= longest ? categories : Array.from({ length: longest }, (_, i) => categories[i] ?? `${i + 1}`)\n\t};\n\tif (type === 'waterfall') input.showTotal = false;\n\tconst { spec, issues: specIssues } = normalizeChartSpec(input);\n\tissues.push(...specIssues);\n\treturn { spec, issues, unsupported, lossless: unsupported.length === 0 && specIssues.length === 0 };\n}\n\n// ---------------------------------------------------------------------------\n// Writing\n// ---------------------------------------------------------------------------\n\nconst esc = (s: string) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\nconst hex = (color: string | undefined) => {\n\tif (!color) return null;\n\tconst rgb = parseChartColor(color);\n\treturn rgb ? chartRgbToHex(rgb).slice(1).toUpperCase() : null;\n};\nconst fillXml = (color: string | undefined, line = false) => {\n\tconst h = hex(color);\n\tif (!h) return '';\n\treturn line ? `<c:spPr><a:ln w=\"28575\" cap=\"rnd\"><a:solidFill><a:srgbClr val=\"${h}\"/></a:solidFill><a:round/></a:ln></c:spPr>` : `<c:spPr><a:solidFill><a:srgbClr val=\"${h}\"/></a:solidFill></c:spPr>`;\n};\nconst rich = (text: string) => `<c:tx><c:rich><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>${esc(text)}</a:t></a:r></a:p></c:rich></c:tx><c:overlay val=\"0\"/>`;\nconst strLit = (items: readonly string[]) => `<c:strLit><c:ptCount val=\"${items.length}\"/>${items.map((s, i) => `<c:pt idx=\"${i}\"><c:v>${esc(s)}</c:v></c:pt>`).join('')}</c:strLit>`;\nconst numLit = (items: readonly ChartValue[], code = 'General') =>\n\t`<c:numLit><c:formatCode>${esc(code)}</c:formatCode><c:ptCount val=\"${items.length}\"/>${items.map((v, i) => (v === null || !Number.isFinite(v) ? '' : `<c:pt idx=\"${i}\"><c:v>${v}</c:v></c:pt>`)).join('')}</c:numLit>`;\n\nconst CURRENCY_SYMBOL: Record<string, string> = { USD: '$', EUR: '€', GBP: '£', JPY: '¥', CNY: '¥', INR: '₹' };\n\n/** A spreadsheet format code equivalent to a chart number format (`General` when there is none). */\nexport function chartFormatToCode(format: ChartNumberFormat): string {\n\tif (format.code) return format.code;\n\tconst d = format.decimals;\n\tconst frac = (n: number | undefined, fallback: number) => {\n\t\tconst k = n ?? fallback;\n\t\treturn k > 0 ? `.${'0'.repeat(k)}` : '';\n\t};\n\tconst wrap = (body: string) => `${format.prefix ? `\"${format.prefix.replace(/\"/g, '')}\"` : ''}${body}${format.suffix ? `\"${format.suffix.replace(/\"/g, '')}\"` : ''}`;\n\tswitch (format.style) {\n\t\tcase 'percent':\n\t\t\treturn wrap(`#,##0${frac(d, 0)}%`);\n\t\tcase 'currency': {\n\t\t\tconst code = (format.currency || 'USD').toUpperCase();\n\t\t\tconst sym = CURRENCY_SYMBOL[code] ?? `${code} `;\n\t\t\treturn wrap(`\"${sym}\"#,##0${frac(d, 0)}`);\n\t\t}\n\t\tcase 'compact':\n\t\t\treturn wrap('#,##0.0,,\"M\"');\n\t\tdefault:\n\t\t\treturn d !== undefined || format.prefix || format.suffix ? wrap(`#,##0${frac(d, 0)}`) : 'General';\n\t}\n}\n\n/** One `c:errBars` (CT_ErrBars order: errDir, errBarType, errValType, noEndCap, plus, minus, val). */\nfunction errBarsXml(bars: ChartErrorBars, dir: 'x' | 'y' | null): string {\n\tlet x = `<c:errBars>${dir ? `<c:errDir val=\"${dir}\"/>` : ''}<c:errBarType val=\"${bars.direction ?? 'both'}\"/><c:errValType val=\"${ERR_OUT[bars.type]}\"/><c:noEndCap val=\"${bars.cap === false ? 1 : 0}\"/>`;\n\tif (bars.type === 'custom') {\n\t\tconst plus = bars.plus ?? (bars.minus ?? []).map(() => null);\n\t\tx += `<c:plus>${numLit(plus)}</c:plus><c:minus>${numLit(bars.minus ?? plus)}</c:minus>`;\n\t} else if (bars.type !== 'stderr') x += `<c:val val=\"${bars.value ?? (bars.type === 'percent' ? 5 : 1)}\"/>`;\n\treturn `${x}</c:errBars>`;\n}\n\nconst MARKER_OUT: Record<ChartMarker, string> = { circle: 'circle', square: 'square', diamond: 'diamond', triangle: 'triangle', cross: 'x', none: 'none' };\n\nexport interface ChartOoxmlWriteResult {\n\t/** The `c:chartSpace` part. */\n\txml: string;\n\t/** What could not be written as DrawingML, as sentences (empty when nothing was lost). */\n\tissues: string[];\n}\n\n/**\n * Writes a chart as a DrawingML chart part (`c:chartSpace`) with the data as literals (no\n * embedded workbook), for `word/charts/chartN.xml` or `ppt/charts/chartN.xml`. Waterfall,\n * histogram, box, funnel and treemap charts need Office 2016 chartex parts; they are written\n * as the closest classic chart and reported in `issues`.\n */\nexport function chartToOoxml(spec: ChartSpec): ChartOoxmlWriteResult {\n\tconst issues: string[] = [];\n\tconst family = chartFamily(spec);\n\tlet type = spec.type;\n\tif (family === 'waterfall' || family === 'histogram' || family === 'box' || family === 'funnel') {\n\t\tissues.push(`${spec.type} charts are written as bar charts: DrawingML has no ${spec.type} type (Office 2016 stores it as chartex).`);\n\t\ttype = 'bar';\n\t} else if (family === 'treemap') {\n\t\tissues.push('Treemaps are written as pie charts: DrawingML has no treemap type.');\n\t\ttype = 'pie';\n\t}\n\tconst visible = spec.series.filter((s) => !s.hidden);\n\tif (visible.length < spec.series.length) issues.push(`${spec.series.length - visible.length} hidden series ${spec.series.length - visible.length === 1 ? 'is' : 'are'} left out.`);\n\tconst grouping = spec.stacking === 'stacked' ? 'stacked' : spec.stacking === 'percent' ? 'percentStacked' : 'standard';\n\tconst ids = { cat: 500000001, val: 500000002, cat2: 500000003, val2: 500000004 };\n\tconst xy = family === 'xy' || type === 'scatter' || type === 'bubble';\n\tconst pieLike = type === 'pie' || type === 'donut';\n\tconst typed = (s: ChartSeries) => (pieLike || type === 'radar' ? type : type === 'bubble' ? 'bubble' : xy ? 'scatter' : chartSeriesType({ type }, s));\n\t// Groups: (kind, axis) in first-appearance order.\n\tconst groups: { kind: string; secondary: boolean; series: { s: ChartSeries; index: number }[] }[] = [];\n\tvisible.forEach((s) => {\n\t\tconst kind = typed(s);\n\t\tconst secondary = !pieLike && s.axis === 'secondary';\n\t\tlet g = groups.find((x) => x.kind === kind && x.secondary === secondary);\n\t\tif (!g) groups.push((g = { kind, secondary, series: [] }));\n\t\tg.series.push({ s, index: spec.series.indexOf(s) });\n\t});\n\tif (pieLike && visible.length > 1) issues.push('Only the first series of a pie is drawn; the others are kept in the part.');\n\tconst catLit = strLit(spec.categories);\n\tlet order = 0;\n\tconst serXml = (kind: string, s: ChartSeries, index: number) => {\n\t\tconst parts = [`<c:idx val=\"${index}\"/>`, `<c:order val=\"${order++}\"/>`, `<c:tx><c:v>${esc(s.name)}</c:v></c:tx>`];\n\t\tconst lineLike = kind === 'line' || kind === 'scatter' || kind === 'radar';\n\t\tparts.push(fillXml(s.color, lineLike));\n\t\tif (kind === 'bar' || kind === 'bubble') parts.push('<c:invertIfNegative val=\"0\"/>');\n\t\tif (lineLike) {\n\t\t\tconst marker = s.marker ?? (spec.points || kind === 'scatter' ? 'circle' : 'none');\n\t\t\tparts.push(`<c:marker><c:symbol val=\"${MARKER_OUT[marker]}\"/>${marker !== 'none' ? '<c:size val=\"5\"/>' : ''}</c:marker>`);\n\t\t}\n\t\t(s.pointColors ?? []).forEach((c, i) => {\n\t\t\tconst h = hex(c ?? undefined);\n\t\t\tif (h) parts.push(`<c:dPt><c:idx val=\"${i}\"/>${kind === 'bar' ? '<c:invertIfNegative val=\"0\"/>' : ''}<c:bubble3D val=\"0\"/><c:spPr><a:solidFill><a:srgbClr val=\"${h}\"/></a:solidFill></c:spPr></c:dPt>`);\n\t\t});\n\t\tconst labels = s.dataLabels ?? spec.dataLabels;\n\t\tconst code = s.format ? chartFormatToCode(s.format) : undefined;\n\t\tif (s.dataLabels !== undefined || code)\n\t\t\tparts.push(`<c:dLbls>${code ? `<c:numFmt formatCode=\"${esc(code)}\" sourceLinked=\"0\"/>` : ''}<c:showLegendKey val=\"0\"/><c:showVal val=\"${labels ? 1 : 0}\"/><c:showCatName val=\"0\"/><c:showSerName val=\"0\"/><c:showPercent val=\"0\"/><c:showBubbleSize val=\"0\"/></c:dLbls>`);\n\t\tif (!pieLike && kind !== 'radar')\n\t\t\tfor (const t of s.trendlines ?? []) {\n\t\t\t\tlet x = `<c:trendline>${t.name ? `<c:name>${esc(t.name)}</c:name>` : ''}${fillXml(t.color, true)}<c:trendlineType val=\"${TREND_OUT[t.type]}\"/>`;\n\t\t\t\tif (t.type === 'polynomial') x += `<c:order val=\"${t.order ?? 2}\"/>`;\n\t\t\t\tif (t.type === 'moving-average') x += `<c:period val=\"${t.period ?? 2}\"/>`;\n\t\t\t\tif (t.forward) x += `<c:forward val=\"${t.forward}\"/>`;\n\t\t\t\tif (t.backward) x += `<c:backward val=\"${t.backward}\"/>`;\n\t\t\t\tif (t.intercept !== undefined && t.intercept !== null) x += `<c:intercept val=\"${t.intercept}\"/>`;\n\t\t\t\tx += `<c:dispRSqr val=\"${t.showR2 ? 1 : 0}\"/><c:dispEq val=\"${t.showEquation ? 1 : 0}\"/></c:trendline>`;\n\t\t\t\tparts.push(x);\n\t\t\t}\n\t\tif (!pieLike && kind !== 'radar') {\n\t\t\tconst xyKind = kind === 'scatter' || kind === 'bubble';\n\t\t\tif (s.errorBars) parts.push(errBarsXml(s.errorBars, xyKind ? 'y' : null));\n\t\t\tif (s.xErrorBars) {\n\t\t\t\tif (xyKind) parts.push(errBarsXml(s.xErrorBars, 'x'));\n\t\t\t\telse issues.push(`\"${s.name}\" has x error bars, which only scatter and bubble charts can hold; they are left out.`);\n\t\t\t}\n\t\t} else if (s.errorBars || s.xErrorBars) issues.push(`Error bars on \"${s.name}\" are left out: ${kind} charts cannot show them.`);\n\t\tconst valCode = chartFormatToCode(s.format ?? (s.axis === 'secondary' ? spec.yAxis2.format : spec.yAxis.format));\n\t\tif (kind === 'scatter' || kind === 'bubble') {\n\t\t\tconst xs = s.x ?? spec.categories.map((_, i) => i + 1);\n\t\t\tparts.push(`<c:xVal>${numLit(xs)}</c:xVal><c:yVal>${numLit(s.values, valCode)}</c:yVal>`);\n\t\t\tif (kind === 'bubble') parts.push(`<c:bubbleSize>${numLit(s.sizes ?? s.values.map(() => 1))}</c:bubbleSize><c:bubble3D val=\"0\"/>`);\n\t\t\telse parts.push(`<c:smooth val=\"${(s.curve ?? spec.curve) === 'smooth' ? 1 : 0}\"/>`);\n\t\t} else {\n\t\t\tparts.push(`<c:cat>${catLit}</c:cat><c:val>${numLit(s.values, valCode)}</c:val>`);\n\t\t\tif (kind === 'line') parts.push(`<c:smooth val=\"${(s.curve ?? spec.curve) === 'smooth' ? 1 : 0}\"/>`);\n\t\t}\n\t\tif ((s.curve ?? spec.curve) === 'step' && lineLike) issues.push(`\"${s.name}\" uses step lines, which DrawingML cannot express; they are written straight.`);\n\t\tif ((s.curve ?? spec.curve) === 'smooth' && kind === 'area') issues.push(`\"${s.name}\" is a smoothed area, which DrawingML cannot express; it is written straight.`);\n\t\treturn `<c:ser>${parts.join('')}</c:ser>`;\n\t};\n\tconst dLbls = `<c:dLbls><c:showLegendKey val=\"0\"/><c:showVal val=\"${spec.dataLabels ? 1 : 0}\"/><c:showCatName val=\"0\"/><c:showSerName val=\"0\"/><c:showPercent val=\"0\"/><c:showBubbleSize val=\"0\"/></c:dLbls>`;\n\tconst groupXml = groups.map((g) => {\n\t\tconst axes = g.secondary ? `<c:axId val=\"${ids.cat2}\"/><c:axId val=\"${ids.val2}\"/>` : `<c:axId val=\"${ids.cat}\"/><c:axId val=\"${ids.val}\"/>`;\n\t\tconst sers = g.series.map(({ s, index }) => serXml(g.kind, s, index)).join('');\n\t\tswitch (g.kind) {\n\t\t\tcase 'bar': {\n\t\t\t\tconst n = g.series.length;\n\t\t\t\tconst barsWidth = spec.stacking === 'none' ? n - (n - 1) * spec.overlap : 1;\n\t\t\t\tconst gapPct = Math.round(Math.min(500, (spec.gapWidth / Math.max(0.01, 1 - spec.gapWidth)) * barsWidth * 100));\n\t\t\t\tconst overlapPct = spec.stacking === 'none' ? Math.round(spec.overlap * 100) : 100;\n\t\t\t\treturn `<c:barChart><c:barDir val=\"${spec.orientation === 'horizontal' ? 'bar' : 'col'}\"/><c:grouping val=\"${spec.stacking === 'none' ? 'clustered' : grouping}\"/><c:varyColors val=\"0\"/>${sers}${dLbls}<c:gapWidth val=\"${gapPct}\"/><c:overlap val=\"${overlapPct}\"/>${axes}</c:barChart>`;\n\t\t\t}\n\t\t\tcase 'line':\n\t\t\t\treturn `<c:lineChart><c:grouping val=\"${spec.stacking === 'none' ? 'standard' : 'standard'}\"/><c:varyColors val=\"0\"/>${sers}${dLbls}<c:marker val=\"1\"/>${axes}</c:lineChart>`;\n\t\t\tcase 'area':\n\t\t\t\treturn `<c:areaChart><c:grouping val=\"${grouping}\"/><c:varyColors val=\"0\"/>${sers}${dLbls}${axes}</c:areaChart>`;\n\t\t\tcase 'scatter':\n\t\t\t\treturn `<c:scatterChart><c:scatterStyle val=\"lineMarker\"/><c:varyColors val=\"0\"/>${sers}${dLbls}${axes}</c:scatterChart>`;\n\t\t\tcase 'bubble':\n\t\t\t\treturn `<c:bubbleChart><c:varyColors val=\"0\"/>${sers}${dLbls}<c:bubbleScale val=\"100\"/>${axes}</c:bubbleChart>`;\n\t\t\tcase 'radar':\n\t\t\t\treturn `<c:radarChart><c:radarStyle val=\"marker\"/><c:varyColors val=\"0\"/>${sers}${dLbls}${axes}</c:radarChart>`;\n\t\t\tcase 'donut':\n\t\t\t\treturn `<c:doughnutChart><c:varyColors val=\"1\"/>${sers}${dLbls}<c:firstSliceAng val=\"0\"/><c:holeSize val=\"${Math.round(Math.max(10, Math.min(90, spec.innerRadius * 100)))}\"/></c:doughnutChart>`;\n\t\t\tdefault:\n\t\t\t\treturn `<c:pieChart><c:varyColors val=\"1\"/>${sers}${dLbls}<c:firstSliceAng val=\"0\"/></c:pieChart>`;\n\t\t}\n\t});\n\t// CT_Scaling order: logBase, orientation, max, min.\n\tconst scaling = (a: ChartAxis) =>\n\t\t`<c:scaling>${a.scale === 'log' ? `<c:logBase val=\"${a.logBase ?? 10}\"/>` : ''}<c:orientation val=\"${a.reverse ? 'maxMin' : 'minMax'}\"/>${a.max !== null ? `<c:max val=\"${a.max}\"/>` : ''}${a.min !== null ? `<c:min val=\"${a.min}\"/>` : ''}</c:scaling>`;\n\tconst title = (a: ChartAxis) => (a.title ? `<c:title>${rich(a.title)}</c:title>` : '');\n\tconst fmt = (a: ChartAxis) => {\n\t\tconst code = chartFormatToCode(a.format);\n\t\treturn code !== 'General' ? `<c:numFmt formatCode=\"${esc(code)}\" sourceLinked=\"0\"/>` : `<c:numFmt formatCode=\"${spec.stacking === 'percent' ? '0%' : 'General'}\" sourceLinked=\"1\"/>`;\n\t};\n\tconst horizontal = spec.orientation === 'horizontal';\n\tconst catPos = horizontal ? 'l' : 'b';\n\tconst valPos = horizontal ? 'b' : 'l';\n\tconst valAx = (id: number, cross: number, a: ChartAxis, pos: string, crosses: string, catKind = false) =>\n\t\t`<c:valAx><c:axId val=\"${id}\"/>${scaling(a)}<c:delete val=\"${a.show ? 0 : 1}\"/><c:axPos val=\"${pos}\"/>${a.gridlines ? '<c:majorGridlines/>' : ''}${title(a)}${fmt(a)}<c:majorTickMark val=\"out\"/><c:minorTickMark val=\"none\"/><c:tickLblPos val=\"nextTo\"/><c:crossAx val=\"${cross}\"/><c:crosses val=\"${crosses}\"/><c:crossBetween val=\"${catKind ? 'midCat' : 'between'}\"/>${a.step ? `<c:majorUnit val=\"${a.step}\"/>` : ''}</c:valAx>`;\n\tconst catAx = (id: number, cross: number, a: ChartAxis, pos: string, hidden = false) =>\n\t\t`<c:${a.kind === 'date' ? 'dateAx' : 'catAx'}><c:axId val=\"${id}\"/><c:scaling><c:orientation val=\"${a.reverse ? 'maxMin' : 'minMax'}\"/></c:scaling><c:delete val=\"${a.show && !hidden ? 0 : 1}\"/><c:axPos val=\"${pos}\"/>${a.gridlines ? '<c:majorGridlines/>' : ''}${title(a)}<c:numFmt formatCode=\"General\" sourceLinked=\"1\"/><c:majorTickMark val=\"out\"/><c:minorTickMark val=\"none\"/><c:tickLblPos val=\"nextTo\"/><c:crossAx val=\"${cross}\"/><c:crosses val=\"autoZero\"/><c:auto val=\"1\"/>${a.kind === 'date' ? '<c:lblOffset val=\"100\"/><c:baseTimeUnit val=\"days\"/>' : '<c:lblAlgn val=\"ctr\"/><c:lblOffset val=\"100\"/><c:noMultiLvlLbl val=\"0\"/>'}</c:${a.kind === 'date' ? 'dateAx' : 'catAx'}>`;\n\tlet axes = '';\n\tif (!pieLike) {\n\t\tif (xy) axes = valAx(ids.cat, ids.val, spec.xAxis, 'b', 'autoZero', true) + valAx(ids.val, ids.cat, spec.yAxis, 'l', 'autoZero', true);\n\t\telse {\n\t\t\tif (spec.xAxis.scale === 'log') issues.push('The category axis cannot be logarithmic; its log scale is left out.');\n\t\t\taxes = catAx(ids.cat, ids.val, spec.xAxis, catPos) + valAx(ids.val, ids.cat, spec.yAxis, valPos, 'autoZero');\n\t\t}\n\t\tif (groups.some((g) => g.secondary)) axes += catAx(ids.cat2, ids.val2, spec.xAxis, catPos, true) + valAx(ids.val2, ids.cat2, spec.yAxis2, horizontal ? 't' : 'r', 'max');\n\t}\n\tconst legendPos = { bottom: 'b', top: 't', left: 'l', right: 'r' }[spec.legend.position];\n\tconst legend = spec.legend.show ? `<c:legend><c:legendPos val=\"${legendPos}\"/><c:overlay val=\"0\"/></c:legend>` : '';\n\tconst titleXml = spec.title ? `<c:title>${rich(spec.title)}</c:title><c:autoTitleDeleted val=\"0\"/>` : '<c:autoTitleDeleted val=\"1\"/>';\n\tif (spec.subtitle) issues.push('The subtitle is left out: DrawingML charts have one title.');\n\tif (family === 'radar' && spec.series.some((s) => s.trendlines?.length)) issues.push('Trendlines on a radar chart are left out.');\n\tconst xml =\n\t\t`<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>` +\n\t\t`<c:chartSpace xmlns:c=\"${CHART_OOXML_NS}\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">` +\n\t\t`<c:roundedCorners val=\"0\"/><c:chart>${titleXml}<c:plotArea><c:layout/>${groupXml.join('')}${axes}</c:plotArea>${legend}<c:plotVisOnly val=\"1\"/><c:dispBlanksAs val=\"gap\"/></c:chart>` +\n\t\t`<c:txPr><a:bodyPr/><a:lstStyle/><a:p><a:pPr><a:defRPr sz=\"${Math.round(10 * 100)}\"/></a:pPr><a:endParaRPr lang=\"en-US\"/></a:p></c:txPr></c:chartSpace>`;\n\treturn { xml, issues: [...new Set(issues)] };\n}\n","/**\n * Minimal, dependency-free ZIP reader used by the DOCX and zipped-export\n * import adapters.\n *\n * - Reads the central directory (including ZIP64 end records and extra\n * fields), so sizes come from the authoritative directory rather than local\n * headers or data descriptors.\n * - Supports stored (method 0) and deflate (method 8) entries; deflate is\n * inflated with the platform `DecompressionStream(\"deflate-raw\")` (modern\n * browsers, Node ≥ 18).\n * - Guards against zip bombs (entry-count and total-uncompressed-size caps,\n * enforced on the bytes actually inflated, not only the declared sizes),\n * path traversal in entry names and encrypted entries.\n * - Verifies CRC-32 checksums of extracted entries.\n */\n\nexport type ZipErrorCode =\n /** Not a ZIP archive, or its directory is truncated / inconsistent. */\n | \"invalid_archive\"\n /** Compression method other than stored/deflate, multi-disk archives, … */\n | \"unsupported_feature\"\n /** The entry is encrypted (general-purpose flag bit 0). */\n | \"encrypted\"\n /** An entry name is absolute or contains `..` segments. */\n | \"unsafe_path\"\n /** More entries than `maxEntries`. */\n | \"too_many_entries\"\n /** Extracting would exceed `maxTotalSize` uncompressed bytes. */\n | \"too_large\"\n /** The entry data is corrupt (bad header, size or CRC mismatch, bad deflate stream). */\n | \"corrupt_entry\"\n /** No entry with the requested name. */\n | \"entry_not_found\"\n /** `DecompressionStream` is unavailable in this environment. */\n | \"unsupported_environment\";\n\nexport class ZipError extends Error {\n readonly code: ZipErrorCode;\n readonly entryName?: string;\n\n constructor(code: ZipErrorCode, message: string, details: { entryName?: string; cause?: unknown } = {}) {\n super(message, details.cause !== undefined ? { cause: details.cause } : undefined);\n this.name = \"ZipError\";\n this.code = code;\n this.entryName = details.entryName;\n }\n}\n\nexport interface ZipEntry {\n /** Entry path with `/` separators (backslashes are normalized). */\n name: string;\n /** Whether the entry is a directory (name ends with `/`). */\n isDirectory: boolean;\n /** Whether the entry is encrypted; reading it throws `ZipError(\"encrypted\")`. */\n encrypted: boolean;\n /** Compression method (0 = stored, 8 = deflate). */\n method: number;\n /** Declared compressed size in bytes. */\n compressedSize: number;\n /** Declared uncompressed size in bytes. */\n size: number;\n /** CRC-32 of the uncompressed data. */\n crc32: number;\n /** Offset of the entry's local file header. */\n localHeaderOffset: number;\n}\n\nexport interface ZipReadOptions {\n /** Maximum number of entries in the central directory (default 10 000). */\n maxEntries?: number;\n /**\n * Maximum number of uncompressed bytes extracted from the archive, summed\n * over every `read` call (default 256 MiB). Enforced while inflating.\n */\n maxTotalSize?: number;\n /**\n * What to do with entries whose names are absolute or contain `..`\n * segments: `\"error\"` (default) rejects the archive, `\"skip\"` leaves them\n * out of `entries`.\n */\n unsafePaths?: \"error\" | \"skip\";\n}\n\nexport const DEFAULT_ZIP_MAX_ENTRIES = 10_000;\nexport const DEFAULT_ZIP_MAX_TOTAL_SIZE = 256 * 1024 * 1024;\n\nconst SIG_EOCD = 0x06054b50;\nconst SIG_ZIP64_EOCD = 0x06064b50;\nconst SIG_ZIP64_LOCATOR = 0x07064b50;\nconst SIG_CENTRAL = 0x02014b50;\nconst SIG_LOCAL = 0x04034b50;\nconst EOCD_SIZE = 22;\nconst MAX_COMMENT = 0xffff;\n\nlet crcTable: Uint32Array | null = null;\n\n/** CRC-32 (IEEE 802.3), as used by ZIP. */\nexport function zipCrc32(data: Uint8Array): number {\n if (!crcTable) {\n crcTable = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;\n crcTable[n] = c >>> 0;\n }\n }\n let crc = 0xffffffff;\n for (let i = 0; i < data.length; i++) crc = crcTable[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);\n return (crc ^ 0xffffffff) >>> 0;\n}\n\nfunction toBytes(data: ArrayBuffer | ArrayBufferView): Uint8Array {\n // Duck-typed so ArrayBuffers from other realms (iframes, jsdom) work too.\n if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n return new Uint8Array(data as ArrayBuffer);\n}\n\n/** Whether `name` is absolute, has a drive letter, or contains `..` / NUL. */\nexport function isUnsafeZipPath(name: string): boolean {\n if (!name || name.includes(\"\\0\")) return true;\n const normalized = name.replace(/\\\\/g, \"/\");\n if (normalized.startsWith(\"/\") || /^[a-zA-Z]:/.test(normalized)) return true;\n return normalized.split(\"/\").some((segment) => segment === \"..\");\n}\n\nconst utf8 = new TextDecoder(\"utf-8\");\n\n/** CP437 high half, used for names without the UTF-8 flag. */\nconst CP437_HIGH =\n \"ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ \";\n\nfunction decodeName(bytes: Uint8Array, isUtf8: boolean): string {\n if (isUtf8 || bytes.every((b) => b < 0x80)) return utf8.decode(bytes);\n let out = \"\";\n for (const b of bytes) out += b < 0x80 ? String.fromCharCode(b) : CP437_HIGH[b - 0x80];\n return out;\n}\n\n/**\n * A parsed ZIP archive. Entry data is extracted lazily with {@link read}.\n * Create it with {@link readZip}.\n */\nexport class ZipArchive {\n /** Entries in central-directory order. */\n readonly entries: readonly ZipEntry[];\n private readonly byName = new Map<string, ZipEntry>();\n private extracted = 0;\n\n constructor(\n private readonly bytes: Uint8Array,\n entries: ZipEntry[],\n private readonly maxTotalSize: number,\n ) {\n this.entries = entries;\n entries.forEach((entry) => {\n if (!this.byName.has(entry.name)) this.byName.set(entry.name, entry);\n });\n }\n\n /** Looks up an entry by exact name (a leading `/` is ignored). */\n get(name: string): ZipEntry | undefined {\n return this.byName.get(name.replace(/^\\/+/, \"\"));\n }\n\n has(name: string): boolean {\n return !!this.get(name);\n }\n\n /** Uncompressed bytes extracted so far (counted against `maxTotalSize`). */\n get extractedSize(): number {\n return this.extracted;\n }\n\n /** Extracts an entry's data, verifying its size and CRC-32. */\n async read(entryOrName: ZipEntry | string): Promise<Uint8Array> {\n const entry = typeof entryOrName === \"string\" ? this.get(entryOrName) : entryOrName;\n if (!entry) {\n throw new ZipError(\"entry_not_found\", `ZIP entry \"${String(entryOrName)}\" not found.`, {\n entryName: String(entryOrName),\n });\n }\n const name = entry.name;\n if (entry.encrypted) {\n throw new ZipError(\"encrypted\", `ZIP entry \"${name}\" is encrypted; encrypted archives are not supported.`, {\n entryName: name,\n });\n }\n if (entry.isDirectory) return new Uint8Array(0);\n const remaining = this.maxTotalSize - this.extracted;\n if (entry.size > remaining) {\n throw new ZipError(\n \"too_large\",\n `Extracting \"${name}\" (${entry.size} bytes) would exceed the ${this.maxTotalSize}-byte limit.`,\n { entryName: name },\n );\n }\n\n const { bytes } = this;\n const offset = entry.localHeaderOffset;\n if (offset + 30 > bytes.length || readU32(bytes, offset) !== SIG_LOCAL) {\n throw new ZipError(\"corrupt_entry\", `ZIP entry \"${name}\" has no valid local header.`, { entryName: name });\n }\n const dataStart = offset + 30 + readU16(bytes, offset + 26) + readU16(bytes, offset + 28);\n const dataEnd = dataStart + entry.compressedSize;\n if (dataEnd > bytes.length) {\n throw new ZipError(\"corrupt_entry\", `ZIP entry \"${name}\" is truncated.`, { entryName: name });\n }\n const raw = bytes.subarray(dataStart, dataEnd);\n\n let data: Uint8Array;\n if (entry.method === 0) {\n if (entry.compressedSize !== entry.size) {\n throw new ZipError(\"corrupt_entry\", `Stored ZIP entry \"${name}\" has inconsistent sizes.`, { entryName: name });\n }\n data = raw.slice();\n } else if (entry.method === 8) {\n // Cap at the declared size: more output means a lying header (or a bomb).\n data = await inflateRaw(raw, entry.size, name);\n } else {\n throw new ZipError(\n \"unsupported_feature\",\n `ZIP entry \"${name}\" uses unsupported compression method ${entry.method}; only stored and deflate are supported.`,\n { entryName: name },\n );\n }\n if (data.length !== entry.size) {\n throw new ZipError(\"corrupt_entry\", `ZIP entry \"${name}\" size does not match the directory.`, { entryName: name });\n }\n this.extracted += data.length;\n if (zipCrc32(data) !== entry.crc32) {\n throw new ZipError(\"corrupt_entry\", `ZIP entry \"${name}\" failed its CRC-32 check.`, { entryName: name });\n }\n return data;\n }\n\n /** Extracts an entry and decodes it as UTF-8 (a leading BOM is removed). */\n async readText(entryOrName: ZipEntry | string): Promise<string> {\n const text = utf8.decode(await this.read(entryOrName));\n return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n }\n}\n\nfunction readU16(bytes: Uint8Array, offset: number): number {\n return bytes[offset] | (bytes[offset + 1] << 8);\n}\n\nfunction readU32(bytes: Uint8Array, offset: number): number {\n return (bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24)) >>> 0;\n}\n\nfunction readU64(bytes: Uint8Array, offset: number): number {\n const value = readU32(bytes, offset) + readU32(bytes, offset + 4) * 0x100000000;\n if (!Number.isSafeInteger(value)) {\n throw new ZipError(\"unsupported_feature\", \"ZIP64 value exceeds the supported range.\");\n }\n return value;\n}\n\n/** Inflates a raw deflate stream, failing as soon as output exceeds `limit` bytes. */\nasync function inflateRaw(raw: Uint8Array, limit: number, entryName: string): Promise<Uint8Array> {\n const Stream: typeof DecompressionStream | undefined = (globalThis as any).DecompressionStream;\n if (typeof Stream !== \"function\") {\n throw new ZipError(\n \"unsupported_environment\",\n \"Inflating ZIP entries needs DecompressionStream (modern browsers, Node 18+).\",\n { entryName },\n );\n }\n const stream = new Stream(\"deflate-raw\");\n const writer = stream.writable.getWriter();\n const reader = stream.readable.getReader();\n // Feed the input in slices so output is pulled (and checked) incrementally.\n const CHUNK = 64 * 1024;\n const feeding = (async () => {\n for (let i = 0; i < raw.length; i += CHUNK) await writer.write(raw.subarray(i, i + CHUNK) as unknown as BufferSource);\n await writer.close();\n })();\n feeding.catch(() => {});\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.length;\n if (total > limit) {\n throw new ZipError(\n \"corrupt_entry\",\n `ZIP entry \"${entryName}\" inflates beyond its declared size of ${limit} bytes.`,\n { entryName },\n );\n }\n chunks.push(value);\n }\n } catch (error) {\n reader.cancel().catch(() => {});\n writer.abort().catch(() => {});\n if (error instanceof ZipError) throw error;\n throw new ZipError(\"corrupt_entry\", `ZIP entry \"${entryName}\" has an invalid deflate stream.`, {\n entryName,\n cause: error,\n });\n }\n await feeding.catch(() => {});\n const out = new Uint8Array(total);\n let pos = 0;\n for (const chunk of chunks) {\n out.set(chunk, pos);\n pos += chunk.length;\n }\n return out;\n}\n\nfunction findEndOfCentralDirectory(bytes: Uint8Array): number {\n const min = Math.max(0, bytes.length - EOCD_SIZE - MAX_COMMENT);\n for (let i = bytes.length - EOCD_SIZE; i >= min; i--) {\n if (readU32(bytes, i) === SIG_EOCD && i + EOCD_SIZE + readU16(bytes, i + 20) <= bytes.length) return i;\n }\n return -1;\n}\n\n/**\n * Parses a ZIP archive's central directory. Entry data is not extracted until\n * {@link ZipArchive.read} is called. Throws {@link ZipError}.\n */\nexport async function readZip(data: ArrayBuffer | ArrayBufferView, options: ZipReadOptions = {}): Promise<ZipArchive> {\n const bytes = toBytes(data);\n const maxEntries = options.maxEntries ?? DEFAULT_ZIP_MAX_ENTRIES;\n const maxTotalSize = options.maxTotalSize ?? DEFAULT_ZIP_MAX_TOTAL_SIZE;\n\n const eocd = findEndOfCentralDirectory(bytes);\n if (eocd < 0) throw new ZipError(\"invalid_archive\", \"Not a ZIP archive (no end of central directory record).\");\n\n let diskNumber = readU16(bytes, eocd + 4);\n let cdDisk = readU16(bytes, eocd + 6);\n let count = readU16(bytes, eocd + 10);\n let cdSize = readU32(bytes, eocd + 12);\n let cdOffset = readU32(bytes, eocd + 16);\n\n if (count === 0xffff || cdSize === 0xffffffff || cdOffset === 0xffffffff) {\n const locator = eocd - 20;\n if (locator < 0 || readU32(bytes, locator) !== SIG_ZIP64_LOCATOR) {\n throw new ZipError(\"invalid_archive\", \"ZIP64 archive is missing its end of central directory locator.\");\n }\n const record = readU64(bytes, locator + 8);\n if (record + 56 > bytes.length || readU32(bytes, record) !== SIG_ZIP64_EOCD) {\n throw new ZipError(\"invalid_archive\", \"ZIP64 end of central directory record not found.\");\n }\n diskNumber = readU32(bytes, record + 16);\n cdDisk = readU32(bytes, record + 20);\n count = readU64(bytes, record + 32);\n cdSize = readU64(bytes, record + 40);\n cdOffset = readU64(bytes, record + 48);\n }\n if (diskNumber !== 0 || cdDisk !== 0) {\n throw new ZipError(\"unsupported_feature\", \"Multi-disk (spanned) ZIP archives are not supported.\");\n }\n if (count > maxEntries) {\n throw new ZipError(\"too_many_entries\", `ZIP archive has ${count} entries; the limit is ${maxEntries}.`);\n }\n if (cdOffset + cdSize > bytes.length) {\n throw new ZipError(\"invalid_archive\", \"ZIP central directory lies outside the file.\");\n }\n\n const entries: ZipEntry[] = [];\n let p = cdOffset;\n for (let i = 0; i < count; i++) {\n if (p + 46 > bytes.length || readU32(bytes, p) !== SIG_CENTRAL) {\n throw new ZipError(\"invalid_archive\", \"ZIP central directory is corrupt.\");\n }\n const flags = readU16(bytes, p + 8);\n const method = readU16(bytes, p + 10);\n const crc32 = readU32(bytes, p + 16);\n let compressedSize = readU32(bytes, p + 20);\n let size = readU32(bytes, p + 24);\n const nameLength = readU16(bytes, p + 28);\n const extraLength = readU16(bytes, p + 30);\n const commentLength = readU16(bytes, p + 32);\n let localHeaderOffset = readU32(bytes, p + 42);\n const nameStart = p + 46;\n const extraStart = nameStart + nameLength;\n const next = extraStart + extraLength + commentLength;\n if (next > bytes.length) throw new ZipError(\"invalid_archive\", \"ZIP central directory is truncated.\");\n\n // ZIP64 extended information: only the fields that overflowed are present, in order.\n for (let e = extraStart; e + 4 <= extraStart + extraLength; ) {\n const id = readU16(bytes, e);\n const len = readU16(bytes, e + 2);\n if (id === 0x0001) {\n let f = e + 4;\n const end = f + len;\n if (size === 0xffffffff && f + 8 <= end) {\n size = readU64(bytes, f);\n f += 8;\n }\n if (compressedSize === 0xffffffff && f + 8 <= end) {\n compressedSize = readU64(bytes, f);\n f += 8;\n }\n if (localHeaderOffset === 0xffffffff && f + 8 <= end) localHeaderOffset = readU64(bytes, f);\n }\n e += 4 + len;\n }\n\n const rawName = decodeName(bytes.subarray(nameStart, extraStart), (flags & 0x0800) !== 0);\n const name = rawName.replace(/\\\\/g, \"/\");\n p = next;\n if (isUnsafeZipPath(rawName)) {\n if (options.unsafePaths === \"skip\") continue;\n throw new ZipError(\"unsafe_path\", `ZIP entry name \"${rawName}\" is absolute or escapes the archive root.`, {\n entryName: rawName,\n });\n }\n entries.push({\n name,\n isDirectory: name.endsWith(\"/\"),\n encrypted: (flags & 0x0001) !== 0,\n method,\n compressedSize,\n size,\n crc32,\n localHeaderOffset,\n });\n }\n return new ZipArchive(bytes, entries, maxTotalSize);\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-YZZMX6V7.cjs","../src/export/images.ts","../src/export/units.ts","../src/export/xml.ts","../src/export/zip-writer.ts","../src/export/docx.ts","../src/export/controller.ts"],"names":["attrs","format","document"],"mappings":"AAAA;AACE;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACfO,IAAM,iBAAA,EAA2C;AAAA,EACtD,WAAA,EAAa,KAAA;AAAA,EACb,YAAA,EAAc,MAAA;AAAA,EACd,WAAA,EAAa,MAAA;AAAA,EACb,WAAA,EAAa,KAAA;AAAA,EACb,WAAA,EAAa,KAAA;AAAA,EACb,YAAA,EAAc,MAAA;AAAA,EACd,YAAA,EAAc;AAChB,CAAA;AAGO,IAAM,yBAAA,EAAmD;AAAA,EAC9D,GAAA,EAAK,WAAA;AAAA,EACL,IAAA,EAAM,YAAA;AAAA,EACN,GAAA,EAAK,WAAA;AAAA,EACL,GAAA,EAAK,WAAA;AAAA,EACL,IAAA,EAAM,YAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAQA,IAAM,aAAA,EAAe,GAAA,EAAK,KAAA,EAAO,IAAA;AAG1B,SAAS,aAAA,CAAc,GAAA,EAAoC;AAChE,EAAA,GAAA,CAAI,GAAA,CAAI,OAAA,EAAS,YAAA,EAAc,OAAO,IAAA;AACtC,EAAA,MAAM,MAAA,EAAQ,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC7B,EAAA,GAAA,CAAI,MAAA,IAAU,CAAA,CAAA,EAAI,OAAO,IAAA;AACzB,EAAA,MAAM,OAAA,EAAS,sEAAA,CAAuE,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,KAAK,CAAC,CAAA;AAC9G,EAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,OAAO,IAAA;AACpB,EAAA,MAAM,QAAA,EAAU,GAAA,CAAI,KAAA,CAAM,MAAA,EAAQ,CAAC,CAAA,CAAE,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACvD,EAAA,GAAA,CAAI,CAAC,wBAAA,CAAyB,IAAA,CAAK,OAAO,CAAA,EAAG,OAAO,IAAA;AACpD,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,EAAS,IAAA,CAAK,OAAO,CAAA;AAC3B,IAAA,MAAM,MAAA,EAAQ,IAAI,UAAA,CAAW,MAAA,CAAO,MAAM,CAAA;AAC1C,IAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,MAAA,CAAO,MAAA,EAAQ,CAAA,EAAA,EAAK,KAAA,CAAM,CAAC,EAAA,EAAI,MAAA,CAAO,UAAA,CAAW,CAAC,CAAA;AACtE,IAAA,OAAO,EAAE,IAAA,EAAM,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,CAAY,CAAA,EAAG,MAAM,CAAA;AAAA,EAChD,EAAA,UAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,IAAM,MAAA,EAAQ,CAAC,CAAA,EAAe,CAAA,EAAA,GAAe,CAAA,CAAE,CAAC,EAAA,GAAK,EAAA,EAAK,CAAA,CAAE,EAAA,EAAI,CAAC,CAAA;AACjE,IAAM,MAAA,EAAQ,CAAC,CAAA,EAAe,CAAA,EAAA,GAAc,CAAA,CAAE,CAAC,EAAA,EAAK,CAAA,CAAE,EAAA,EAAI,CAAC,EAAA,GAAK,CAAA;AAChE,IAAM,MAAA,EAAQ,CAAC,CAAA,EAAe,CAAA,EAAA,GAAA,CAAgB,CAAA,CAAE,CAAC,EAAA,GAAK,GAAA,EAAO,CAAA,CAAE,EAAA,EAAI,CAAC,EAAA,GAAK,GAAA,EAAO,CAAA,CAAE,EAAA,EAAI,CAAC,EAAA,GAAK,EAAA,EAAK,CAAA,CAAE,EAAA,EAAI,CAAC,CAAA,EAAA,IAAO,CAAA;AAC/G,IAAM,MAAA,EAAQ,CAAC,CAAA,EAAe,CAAA,EAAA,GAAc,CAAA,CAAE,CAAC,EAAA,EAAK,CAAA,CAAE,EAAA,EAAI,CAAC,EAAA,GAAK,EAAA,EAAM,CAAA,CAAE,EAAA,EAAI,CAAC,EAAA,GAAK,GAAA,EAAO,CAAA,CAAE,EAAA,EAAI,CAAC,EAAA,GAAK,EAAA;AAG9F,SAAS,cAAA,CAAe,CAAA,EAAyD;AACtF,EAAA,MAAM,GAAA,EAAK,CAAC,KAAA,EAAe,MAAA,EAAA,GACzB,MAAA,EAAQ,EAAA,GAAK,OAAA,EAAS,EAAA,GAAK,MAAA,EAAQ,IAAA,GAAO,OAAA,EAAS,IAAA,EAAM,EAAE,KAAA,EAAO,OAAO,EAAA,EAAI,IAAA;AAC/E,EAAA,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,GAAA,GAAM,CAAA,CAAE,CAAC,EAAA,IAAM,IAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,EAAA,EAAM;AACtF,IAAA,OAAO,EAAA,CAAG,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA;AAAA,EACtC;AACA,EAAA,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,GAAA,GAAM,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,EAAA,EAAM,OAAO,EAAA,CAAG,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAG,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA;AACzG,EAAA,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,GAAA,GAAM,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,EAAA,EAAM,OAAO,EAAA,CAAG,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAC,CAAA;AAC9G,EAAA,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,GAAA,GAAM,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,EAAA,EAAM;AACtF,IAAA,MAAM,MAAA,EAAQ,MAAA,CAAO,YAAA,CAAa,CAAA,CAAE,EAAE,CAAA,EAAG,CAAA,CAAE,EAAE,CAAA,EAAG,CAAA,CAAE,EAAE,CAAA,EAAG,CAAA,CAAE,EAAE,CAAC,CAAA;AAC5D,IAAA,GAAA,CAAI,MAAA,IAAU,MAAA,EAAQ,OAAO,EAAA,CAAG,KAAA,CAAM,CAAA,EAAG,EAAE,EAAA,EAAI,KAAA,EAAQ,KAAA,CAAM,CAAA,EAAG,EAAE,EAAA,EAAI,KAAM,CAAA;AAC5E,IAAA,GAAA,CAAI,MAAA,IAAU,MAAA,EAAQ;AACpB,MAAA,MAAM,KAAA,EAAO,CAAA,CAAE,EAAE,EAAA,EAAK,CAAA,CAAE,EAAE,EAAA,GAAK,EAAA,EAAM,CAAA,CAAE,EAAE,EAAA,GAAK,GAAA,EAAO,CAAA,CAAE,EAAE,EAAA,GAAK,EAAA;AAC9D,MAAA,OAAO,EAAA,CAAA,CAAI,KAAA,EAAO,KAAA,EAAA,EAAU,CAAA,EAAA,CAAK,KAAA,IAAS,GAAA,EAAM,KAAA,EAAA,EAAU,CAAC,CAAA;AAAA,IAC7D;AACA,IAAA,GAAA,CAAI,MAAA,IAAU,MAAA,EAAQ,OAAO,EAAA,CAAA,CAAI,CAAA,CAAE,EAAE,EAAA,EAAK,CAAA,CAAE,EAAE,EAAA,GAAK,EAAA,EAAM,CAAA,CAAE,EAAE,EAAA,GAAK,EAAA,EAAA,EAAO,CAAA,EAAA,CAAI,CAAA,CAAE,EAAE,EAAA,EAAK,CAAA,CAAE,EAAE,EAAA,GAAK,EAAA,EAAM,CAAA,CAAE,EAAE,EAAA,GAAK,EAAA,EAAA,EAAO,CAAC,CAAA;AACtH,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,GAAA,CAAI,CAAA,CAAE,OAAA,GAAU,EAAA,GAAK,CAAA,CAAE,CAAC,EAAA,IAAM,IAAA,GAAQ,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,EAAM;AAEnD,IAAA,IAAI,EAAA,EAAI,CAAA;AACR,IAAA,MAAA,CAAO,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,MAAA,EAAQ;AACvB,MAAA,GAAA,CAAI,CAAA,CAAE,CAAC,EAAA,IAAM,GAAA,EAAM,OAAO,IAAA;AAC1B,MAAA,MAAM,OAAA,EAAS,CAAA,CAAE,EAAA,EAAI,CAAC,CAAA;AACtB,MAAA,GAAA,CAAI,OAAA,IAAW,IAAA,GAAQ,OAAA,IAAW,EAAA,GAAS,OAAA,GAAU,IAAA,GAAQ,OAAA,GAAU,GAAA,EAAO;AAC5E,QAAA,EAAA,GAAK,CAAA;AACL,QAAA,QAAA;AAAA,MACF;AACA,MAAA,MAAM,OAAA,EAAS,KAAA,CAAM,CAAA,EAAG,EAAA,EAAI,CAAC,CAAA;AAC7B,MAAA,GAAA,CAAI,OAAA,GAAU,IAAA,GAAQ,OAAA,GAAU,IAAA,GAAQ,OAAA,IAAW,IAAA,GAAQ,OAAA,IAAW,IAAA,GAAQ,OAAA,IAAW,GAAA,EAAM;AAC7F,QAAA,OAAO,EAAA,CAAG,KAAA,CAAM,CAAA,EAAG,EAAA,EAAI,CAAC,CAAA,EAAG,KAAA,CAAM,CAAA,EAAG,EAAA,EAAI,CAAC,CAAC,CAAA;AAAA,MAC5C;AACA,MAAA,GAAA,CAAI,OAAA,EAAS,CAAA,EAAG,OAAO,IAAA;AACvB,MAAA,EAAA,GAAK,EAAA,EAAI,MAAA;AAAA,IACX;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,SAAA,CAAU,KAAA,EAAoC;AAC5D,EAAA,GAAA,CAAI,OAAO,MAAA,IAAU,QAAA,EAAU,OAAO,MAAA,CAAO,QAAA,CAAS,KAAK,EAAA,GAAK,MAAA,EAAQ,EAAA,EAAI,MAAA,EAAQ,KAAA,CAAA;AACpF,EAAA,GAAA,CAAI,OAAO,MAAA,IAAU,QAAA,EAAU,OAAO,KAAA,CAAA;AACtC,EAAA,MAAM,MAAA,EAAQ,kCAAA,CAAmC,IAAA,CAAK,KAAK,CAAA;AAC3D,EAAA,OAAO,MAAA,EAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,EAAA,GAAK,KAAA,EAAA,EAAY,KAAA,CAAA;AACjD;ADDA;AACA;AEhGO,IAAM,kBAAA,EAAoB,EAAA;AAEjC,IAAM,WAAA,EAAa,IAAA;AAEnB,IAAM,QAAA,EAAkC;AAAA,EACtC,EAAA,EAAI,CAAA;AAAA,EACJ,EAAA,EAAI,IAAA;AAAA,EACJ,EAAA,EAAI,EAAA;AAAA,EACJ,EAAA,EAAI,GAAA,EAAK,IAAA;AAAA,EACT,EAAA,EAAI,GAAA,EAAK,IAAA;AAAA,EACT,EAAA,EAAI;AACN,CAAA;AAOO,SAAS,aAAA,CAAc,KAAA,EAAgB,OAAA,EAAS,iBAAA,EAAuC;AAC5F,EAAA,GAAA,CAAI,OAAO,MAAA,IAAU,QAAA,EAAU,OAAO,MAAA,CAAO,QAAA,CAAS,KAAK,EAAA,EAAI,OAAA,CAAQ,MAAA,EAAQ,OAAA,CAAQ,EAAE,EAAA,EAAI,KAAA,CAAA;AAC7F,EAAA,GAAA,CAAI,OAAO,MAAA,IAAU,QAAA,EAAU,OAAO,KAAA,CAAA;AACtC,EAAA,MAAM,MAAA,EAAQ,gDAAA,CAAiD,IAAA,CAAK,KAAK,CAAA;AACzE,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO,OAAO,KAAA,CAAA;AACnB,EAAA,MAAM,EAAA,EAAI,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA;AACzB,EAAA,MAAM,KAAA,EAAO,KAAA,CAAM,CAAC,CAAA,CAAE,WAAA,CAAY,EAAA,GAAK,IAAA;AACvC,EAAA,IAAI,EAAA;AACJ,EAAA,GAAA,CAAI,KAAA,IAAS,KAAA,GAAQ,KAAA,IAAS,KAAA,EAAO,GAAA,EAAK,EAAA,EAAI,MAAA;AAAA,EAAA,KAAA,GAAA,CACrC,KAAA,IAAS,GAAA,EAAK,GAAA,EAAM,EAAA,EAAI,IAAA,EAAO,MAAA;AAAA,EAAA,KAAA,GAAA,CAC/B,KAAA,GAAQ,OAAA,EAAS,GAAA,EAAK,EAAA,EAAI,OAAA,CAAQ,IAAI,CAAA;AAC/C,EAAA,OAAO,GAAA,IAAO,KAAA,EAAA,GAAa,CAAC,MAAA,CAAO,QAAA,CAAS,EAAE,EAAA,EAAI,KAAA,EAAA,EAAY,OAAA,CAAQ,EAAE,CAAA;AAC1E;AAEA,SAAS,OAAA,CAAQ,EAAA,EAAoB;AACnC,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,UAAA,EAAY,IAAA,CAAK,GAAA,CAAI,UAAA,EAAY,EAAE,CAAC,CAAA;AACvD;AAGO,IAAM,UAAA,EAAY,CAAC,EAAA,EAAA,GAAuB,IAAA,CAAK,KAAA,CAAM,GAAA,EAAK,EAAE,CAAA;AAE5D,IAAM,eAAA,EAAiB,CAAC,EAAA,EAAA,GAAuB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,GAAA,EAAK,CAAC,CAAC,CAAC,CAAA;AAE7F,IAAM,QAAA,EAAU,CAAC,EAAA,EAAA,GAAuB,IAAA,CAAK,KAAA,CAAM,GAAA,EAAK,IAAI,CAAA;AAE5D,IAAM,UAAA,EAAY,CAAC,EAAA,EAAA,GAAuB,IAAA,CAAK,KAAA,CAAM,GAAA,EAAK,EAAE,CAAA;AAOnE,IAAM,aAAA,EAAuC;AAAA,EAC3C,KAAA,EAAO,QAAA;AAAA,EACP,KAAA,EAAO,QAAA;AAAA,EACP,GAAA,EAAK,QAAA;AAAA,EACL,KAAA,EAAO,QAAA;AAAA,EACP,IAAA,EAAM,QAAA;AAAA,EACN,IAAA,EAAM,QAAA;AAAA,EACN,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM,QAAA;AAAA,EACN,IAAA,EAAM,QAAA;AAAA,EACN,OAAA,EAAS,QAAA;AAAA,EACT,OAAA,EAAS,QAAA;AAAA,EACT,IAAA,EAAM,QAAA;AAAA,EACN,IAAA,EAAM,QAAA;AAAA,EACN,MAAA,EAAQ,QAAA;AAAA,EACR,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM,QAAA;AAAA,EACN,KAAA,EAAO,QAAA;AAAA,EACP,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM,QAAA;AAAA,EACN,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM;AACR,CAAA;AAMO,SAAS,aAAA,CAAc,KAAA,EAAoC;AAChE,EAAA,GAAA,CAAI,OAAO,MAAA,IAAU,QAAA,EAAU,OAAO,KAAA,CAAA;AACtC,EAAA,MAAM,MAAA,EAAQ,KAAA,CAAM,IAAA,CAAK,CAAA,CAAE,WAAA,CAAY,CAAA;AACvC,EAAA,GAAA,CAAI,MAAA,GAAS,YAAA,EAAc,OAAO,YAAA,CAAa,KAAK,CAAA;AACpD,EAAA,IAAI,MAAA,EAAQ,oBAAA,CAAqB,IAAA,CAAK,KAAK,CAAA;AAC3C,EAAA,GAAA,CAAI,KAAA,EAAO,OAAO,KAAA,CAAM,CAAC,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,CAAE,KAAA,CAAM,EAAE,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,EAAA,EAAI,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,CAAA,CAAE,WAAA,CAAY,CAAA;AACxF,EAAA,MAAA,EAAQ,kCAAA,CAAmC,IAAA,CAAK,KAAK,CAAA;AACrD,EAAA,GAAA,CAAI,KAAA,EAAO,OAAO,KAAA,CAAM,CAAC,CAAA,CAAE,WAAA,CAAY,CAAA;AACvC,EAAA,MAAA,EAAQ,yFAAA,CAA0F,IAAA,CAAK,KAAK,CAAA;AAC5G,EAAA,GAAA,CAAI,KAAA,EAAO;AACT,IAAA,OAAO,KAAA,CACJ,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,CACV,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,MAAA,CAAO,CAAC,CAAC,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CACjE,IAAA,CAAK,EAAE,CAAA,CACP,WAAA,CAAY,CAAA;AAAA,EACjB;AACA,EAAA,OAAO,KAAA,CAAA;AACT;AAGO,IAAM,gBAAA,EAA0C;AAAA,EACrD,MAAA,EAAQ,QAAA;AAAA,EACR,QAAA,EAAU,OAAA;AAAA,EACV,QAAA,EAAU,MAAA;AAAA,EACV,MAAA,EAAQ,SAAA;AAAA,EACR,QAAA,EAAU,MAAA;AAAA,EACV,MAAA,EAAQ,KAAA;AAAA,EACR,QAAA,EAAU,UAAA;AAAA,EACV,QAAA,EAAU,UAAA;AAAA,EACV,QAAA,EAAU,WAAA;AAAA,EACV,QAAA,EAAU,aAAA;AAAA,EACV,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,YAAA;AAAA,EACV,QAAA,EAAU,UAAA;AAAA,EACV,MAAA,EAAQ,WAAA;AAAA,EACR,QAAA,EAAU,OAAA;AAAA,EACV,MAAA,EAAQ;AACV,CAAA;AFmEA;AACA;AGnLA,IAAM,kBAAA,EAAoB,uCAAA;AAGnB,SAAS,oBAAA,CAAqB,KAAA,EAAuB;AAC1D,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAA;AAC5C;AAGO,SAAS,aAAA,CAAc,KAAA,EAAuB;AACnD,EAAA,OAAO,oBAAA,CAAqB,KAAK,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,MAAM,CAAA;AACtG;AAOO,SAAS,aAAA,CAAc,KAAA,EAAuB;AACnD,EAAA,OAAO,aAAA,CAAc,KAAK,CAAA,CACvB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CACtB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CACtB,OAAA,CAAQ,KAAA,EAAO,MAAM,CAAA,CACrB,OAAA,CAAQ,KAAA,EAAO,OAAO,CAAA,CACtB,OAAA,CAAQ,KAAA,EAAO,OAAO,CAAA;AAC3B;AAIA,SAAS,KAAA,CAAM,MAAA,EAAsC;AACnD,EAAA,GAAA,CAAI,CAAC,MAAA,EAAQ,OAAO,EAAA;AACpB,EAAA,IAAI,IAAA,EAAM,EAAA;AACV,EAAA,IAAA,CAAA,MAAW,CAAC,IAAA,EAAM,KAAK,EAAA,GAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AAClD,IAAA,GAAA,CAAI,MAAA,IAAU,KAAA,EAAA,GAAa,MAAA,IAAU,KAAA,GAAQ,MAAA,IAAU,KAAA,EAAO,QAAA;AAC9D,IAAA,IAAA,GAAO,CAAA,CAAA,EAAI,IAAI,CAAA,EAAA,EAAK,aAAA,CAAc,MAAA,CAAO,KAAK,CAAC,CAAC,CAAA,CAAA,CAAA;AAAA,EAClD;AACA,EAAA,OAAO,GAAA;AACT;AAMO,IAAM,UAAA,EAAN,MAAgB;AAAA,EACb;AAAA;AAAA,EAGR,WAAA,CAAY,YAAA,EAAc,IAAA,EAAM;AAC9B,IAAA,IAAA,CAAK,MAAA,EAAQ,YAAA,EAAc,CAAC,2DAA2D,EAAA,EAAI,CAAC,CAAA;AAAA,EAC9F;AAAA,EAEA,IAAA,CAAK,IAAA,EAAc,MAAA,EAAyB;AAC1C,IAAA,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,CAAA,CAAA,EAAI,IAAI,CAAA,EAAA;AACjB,IAAA;AACT,EAAA;AAE6C,EAAA;AACnB,IAAA;AACjB,IAAA;AACT,EAAA;AAE0B,EAAA;AACC,IAAA;AAClB,IAAA;AACT,EAAA;AAE0B,EAAA;AACR,IAAA;AACT,IAAA;AACT,EAAA;AAAA;AAGqE,EAAA;AAC5C,IAAA;AACzB,EAAA;AAAA;AAGgD,EAAA;AACtB,IAAA;AAC1B,EAAA;AAAA;AAG0B,EAAA;AACF,IAAA;AACf,IAAA;AACT,EAAA;AAEmB,EAAA;AACQ,IAAA;AAC3B,EAAA;AACF;AHqJ6B;AACA;AIvNT;AACH;AAEM;AACG,EAAA;AACM,EAAA;AACjB,EAAA;AACJ,EAAA;AACiB,IAAA;AACd,IAAA;AACO,IAAA;AACD,IAAA;AAClB,EAAA;AAC2B,EAAA;AACjB,EAAA;AACU,EAAA;AACA,IAAA;AACL,IAAA;AACf,EAAA;AACO,EAAA;AACT;AAGiC;AACiD,EAAA;AAC1D,EAAA;AAClB,EAAA;AACA,EAAA;AACkB,IAAA;AACd,EAAA;AACC,IAAA;AACT,EAAA;AAEsB,EAAA;AAEnB,EAAA;AAEc,EAAA;AACS,EAAA;AACpB,EAAA;AACC,EAAA;AACT;AAEiE;AACpC,EAAA;AACpB,EAAA;AACmB,IAAA;AACO,IAAA;AACjC,EAAA;AACF;AAEe;AACmB,iBAAA;AACvB,kBAAA;AAEqB,EAAA;AACP,IAAA;AACD,IAAA;AACtB,EAAA;AAEwC,EAAA;AAClB,IAAA;AACD,IAAA;AACF,IAAA;AACP,IAAA;AACc,IAAA;AACA,MAAA;AACF,MAAA;AACb,MAAA;AACT,IAAA;AACiB,IAAA;AACnB,EAAA;AAE2B,EAAA;AACT,IAAA;AACN,IAAA;AACe,IAAA;AACL,MAAA;AACL,MAAA;AACf,IAAA;AACO,IAAA;AACT,EAAA;AACF;AAG+B;AACA,EAAA;AACN,EAAA;AACE,EAAA;AACL,EAAA;AACP,EAAA;AACO,EAAA;AACC,IAAA;AACD,IAAA;AACA,MAAA;AAClB,IAAA;AACoB,IAAA;AACP,IAAA;AACY,IAAA;AACA,IAAA;AACJ,IAAA;AACR,IAAA;AACE,IAAA;AACG,IAAA;AACM,IAAA;AACL,IAAA;AACD,IAAA;AACK,MAAA;AACvB,IAAA;AACc,IAAA;AACH,IAAA;AACK,MAAA;AAAS,MAAA;AAAY,MAAA;AAAa,MAAA;AAAW,MAAA;AAAW,MAAA;AAC/D,MAAA;AAAkB,MAAA;AAAiB,MAAA;AAAuB,MAAA;AAAQ,MAAA;AAC1E,IAAA;AACkB,IAAA;AACL,IAAA;AACC,IAAA;AACC,MAAA;AAAS,MAAA;AAAS,MAAA;AAAY,MAAA;AAAa,MAAA;AAAW,MAAA;AAAW,MAAA;AACxE,MAAA;AAAkB,MAAA;AAAiB,MAAA;AAAuB,MAAA;AAAQ,MAAA;AAAQ,MAAA;AAC5E,MAAA;AAAQ,MAAA;AAAQ,MAAA;AAAa,MAAA;AACnC,IAAA;AACsB,IAAA;AACzB,EAAA;AACqB,EAAA;AACF,EAAA;AACP,EAAA;AACD,EAAA;AACK,IAAA;AAAQ,IAAA;AAAQ,IAAA;AAAqB,IAAA;AAAqB,IAAA;AAC3D,IAAA;AAAe,IAAA;AAAQ,IAAA;AACrC,EAAA;AACuB,EAAA;AAC1B;AJyO6B;AACA;AK7VQ;AAoG1B;AACN,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AACE,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACP;AAE0B;AACV,EAAA;AACA,EAAA;AACC,EAAA;AACD,EAAA;AACE,EAAA;AACD,EAAA;AACC,EAAA;AACA,EAAA;AACA,EAAA;AAClB;AAEY;AACM,EAAA;AACV,EAAA;AACD,EAAA;AACG,EAAA;AACG,EAAA;AACD,EAAA;AACA,EAAA;AACQ,EAAA;AACV,EAAA;AACD,EAAA;AACI,EAAA;AACb;AAEe;AAGa;AAED;AAEP;AAEiB;AACV;AACT;AAGI;AACA;AACC;AACF;AAGA;AACnB,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAEwB;AAkDH;AAI4D;AAC3D,EAAA;AACM,EAAA;AACb,EAAA;AACG,EAAA;AACA,EAAA;AACU,EAAA;AAC7B;AAE+G;AACvF,EAAA;AACK,EAAA;AACb,EAAA;AAC+B,EAAA;AAClB,EAAA;AAC7B;AAEiF;AAC3D,EAAA;AACK,EAAA;AAC3B;AAGkD;AACxB,EAAA;AACG,EAAA;AACJ,EAAA;AACE,EAAA;AAC3B;AAE0C;AAE/B,EAAA;AAMS,EAAA;AACpB;AAG6C;AAC3B,EAAA;AACC,EAAA;AACnB;AAEuC;AAGqC;AAC/C,EAAA;AACR,EAAA;AACE,IAAA;AACA,IAAA;AACZ,MAAA;AACW,QAAA;AACd,QAAA;AACG,MAAA;AACa,QAAA;AAChB,QAAA;AACG,MAAA;AACgB,QAAA;AACnB,QAAA;AACG,MAAA;AACa,QAAA;AAChB,QAAA;AACG,MAAA;AACgB,QAAA;AACnB,QAAA;AACG,MAAA;AACgB,QAAA;AACnB,QAAA;AACG,MAAA;AACW,QAAA;AACd,QAAA;AACgB,MAAA;AACF,QAAA;AACI,QAAA;AACP,QAAA;AACa,QAAA;AACP,QAAA;AACDA,UAAAA;AAChB,QAAA;AACA,QAAA;AACF,MAAA;AACkB,MAAA;AACI,QAAA;AACP,QAAA;AACG,QAAA;AAChB,QAAA;AACF,MAAA;AACK,MAAA;AACgBA,QAAAA;AACnB,QAAA;AACG,MAAA;AACkB,QAAA;AACrB,QAAA;AACF,MAAA;AACE,QAAA;AACJ,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAGyD;AAChC,EAAA;AACH,EAAA;AACK,EAAA;AACE,EAAA;AACL,EAAA;AACH,EAAA;AACD,EAAA;AACA,IAAA;AACE,IAAA;AACpB,EAAA;AACuB,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACzB;AAEsC;AAE7B;AACe,EAAA;AACP,EAAA;AACY,EAAA;AACN,EAAA;AACI,EAAA;AACN,EAAA;AACM,EAAA;AACX,EAAA;AACO,EAAA;AACM,EAAA;AACE,EAAA;AACV,EAAA;AACD,EAAA;AACX,EAAA;AACT;AAGS;AACe,EAAA;AACP,EAAA;AACD,EAAA;AACO,EAAA;AACjB,EAAA;AACA,EAAA;AACqB,EAAA;AACE,IAAA;AACd,IAAA;AACS,EAAA;AACK,IAAA;AACD,IAAA;AACH,MAAA;AACR,MAAA;AACb,IAAA;AACF,EAAA;AAC4B,EAAA;AACL,IAAA;AACI,MAAA;AACF,MAAA;AACX,MAAA;AACI,MAAA;AACf,IAAA;AACH,EAAA;AAC2B,EAAA;AACb,EAAA;AACA,EAAA;AACY,EAAA;AACP,IAAA;AACI,MAAA;AACE,MAAA;AACN,MAAA;AACQ,MAAA;AACxB,IAAA;AACH,EAAA;AACwB,EAAA;AACA,EAAA;AACK,EAAA;AAC/B;AAEuD;AACtC,EAAA;AACR,IAAA;AACI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","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-YZZMX6V7.cjs","sourcesContent":[null,"/** Image types Word displays without a fallback, by MIME type → file extension. */\nexport const DOCX_IMAGE_TYPES: Record<string, string> = {\n \"image/png\": \"png\",\n \"image/jpeg\": \"jpeg\",\n \"image/jpg\": \"jpeg\",\n \"image/gif\": \"gif\",\n \"image/bmp\": \"bmp\",\n \"image/webp\": \"webp\",\n \"image/tiff\": \"tiff\",\n};\n\n/** Content types of the image extensions above. */\nexport const DOCX_IMAGE_CONTENT_TYPES: Record<string, string> = {\n png: \"image/png\",\n jpeg: \"image/jpeg\",\n gif: \"image/gif\",\n bmp: \"image/bmp\",\n webp: \"image/webp\",\n tiff: \"image/tiff\",\n};\n\nexport interface DecodedDataUrl {\n mime: string;\n bytes: Uint8Array;\n}\n\n/** Largest data URL decoded (characters), so a huge attribute cannot exhaust memory twice. */\nconst MAX_DATA_URL = 64 * 1024 * 1024;\n\n/** Decodes a base64 `data:` URL. Returns `null` for anything else or malformed input. */\nexport function decodeDataUrl(src: string): DecodedDataUrl | null {\n if (src.length > MAX_DATA_URL) return null;\n const comma = src.indexOf(\",\");\n if (comma === -1) return null;\n const header = /^data:([a-z0-9.+-]+\\/[a-z0-9.+-]+)((?:;[a-z0-9-]+=[^;,]*)*);base64$/i.exec(src.slice(0, comma));\n if (!header) return null;\n const payload = src.slice(comma + 1).replace(/\\s+/g, \"\");\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(payload)) return null;\n try {\n const binary = atob(payload);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return { mime: header[1].toLowerCase(), bytes };\n } catch {\n return null;\n }\n}\n\nconst u16be = (b: Uint8Array, i: number) => (b[i] << 8) | b[i + 1];\nconst u16le = (b: Uint8Array, i: number) => b[i] | (b[i + 1] << 8);\nconst u32be = (b: Uint8Array, i: number) => ((b[i] << 24) | (b[i + 1] << 16) | (b[i + 2] << 8) | b[i + 3]) >>> 0;\nconst i32le = (b: Uint8Array, i: number) => b[i] | (b[i + 1] << 8) | (b[i + 2] << 16) | (b[i + 3] << 24);\n\n/** Pixel size read from a PNG, GIF, JPEG, BMP or WebP header, or `null`. */\nexport function imagePixelSize(b: Uint8Array): { width: number; height: number } | null {\n const ok = (width: number, height: number) =>\n width > 0 && height > 0 && width < 1e6 && height < 1e6 ? { width, height } : null;\n if (b.length >= 24 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) {\n return ok(u32be(b, 16), u32be(b, 20));\n }\n if (b.length >= 10 && b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46) return ok(u16le(b, 6), u16le(b, 8));\n if (b.length >= 26 && b[0] === 0x42 && b[1] === 0x4d) return ok(Math.abs(i32le(b, 18)), Math.abs(i32le(b, 22)));\n if (b.length >= 30 && b[0] === 0x52 && b[1] === 0x49 && b[8] === 0x57 && b[9] === 0x45) {\n const chunk = String.fromCharCode(b[12], b[13], b[14], b[15]);\n if (chunk === \"VP8 \") return ok(u16le(b, 26) & 0x3fff, u16le(b, 28) & 0x3fff);\n if (chunk === \"VP8L\") {\n const bits = b[21] | (b[22] << 8) | (b[23] << 16) | (b[24] << 24);\n return ok((bits & 0x3fff) + 1, ((bits >>> 14) & 0x3fff) + 1);\n }\n if (chunk === \"VP8X\") return ok((b[24] | (b[25] << 8) | (b[26] << 16)) + 1, (b[27] | (b[28] << 8) | (b[29] << 16)) + 1);\n return null;\n }\n if (b.length >= 4 && b[0] === 0xff && b[1] === 0xd8) {\n // Walk the JPEG segments to the first start-of-frame marker.\n let i = 2;\n while (i + 9 < b.length) {\n if (b[i] !== 0xff) return null;\n const marker = b[i + 1];\n if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {\n i += 2;\n continue;\n }\n const length = u16be(b, i + 2);\n if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {\n return ok(u16be(b, i + 7), u16be(b, i + 5));\n }\n if (length < 2) return null;\n i += 2 + length;\n }\n }\n return null;\n}\n\n/** A CSS pixel size from a node attribute (`320`, `\"320\"`, `\"320px\"`), or `undefined`. */\nexport function pixelAttr(value: unknown): number | undefined {\n if (typeof value === \"number\") return Number.isFinite(value) && value > 0 ? value : undefined;\n if (typeof value !== \"string\") return undefined;\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*(px)?\\s*$/i.exec(value);\n return match ? Number(match[1]) || undefined : undefined;\n}\n","// Portions ported from wordcraft/crates/docx/src/units.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/** Base font size (points) that `em` / `rem` / `%` lengths are relative to: Word's default 11 pt. */\nexport const DOCX_BASE_FONT_PT = 11;\n/** Largest length accepted, in points (22 inches, Word's page limit). */\nconst MAX_LEN_PT = 1584;\n\nconst UNIT_PT: Record<string, number> = {\n pt: 1,\n px: 0.75,\n in: 72,\n cm: 72 / 2.54,\n mm: 72 / 25.4,\n pc: 12,\n};\n\n/**\n * Converts a CSS length (`12pt`, `16px`, `1.5em`, `120%`, `0.5in`) to points.\n * Unit-less numbers are pixels. Returns `undefined` for anything else\n * (`calc()`, `var()`, keywords) and clamps to ±1584 pt.\n */\nexport function cssLengthToPt(value: unknown, basePt = DOCX_BASE_FONT_PT): number | undefined {\n if (typeof value === \"number\") return Number.isFinite(value) ? clampPt(value * UNIT_PT.px) : undefined;\n if (typeof value !== \"string\") return undefined;\n const match = /^\\s*(-?\\d+(?:\\.\\d+)?|-?\\.\\d+)\\s*([a-z%]*)\\s*$/i.exec(value);\n if (!match) return undefined;\n const n = Number(match[1]);\n const unit = match[2].toLowerCase() || \"px\";\n let pt: number | undefined;\n if (unit === \"em\" || unit === \"rem\") pt = n * basePt;\n else if (unit === \"%\") pt = (n / 100) * basePt;\n else if (unit in UNIT_PT) pt = n * UNIT_PT[unit];\n return pt === undefined || !Number.isFinite(pt) ? undefined : clampPt(pt);\n}\n\nfunction clampPt(pt: number): number {\n return Math.max(-MAX_LEN_PT, Math.min(MAX_LEN_PT, pt));\n}\n\n/** Points → twentieths of a point (twips), as an integer. */\nexport const ptToTwips = (pt: number): number => Math.round(pt * 20);\n/** Points → half-points (`w:sz`), at least 1. */\nexport const ptToHalfPoints = (pt: number): number => Math.max(1, Math.min(3276, Math.round(pt * 2)));\n/** CSS pixels (96 dpi) → English Metric Units (images). */\nexport const pxToEmu = (px: number): number => Math.round(px * 9525);\n/** CSS pixels (96 dpi) → twips. */\nexport const pxToTwips = (px: number): number => Math.round(px * 15);\n\n/** Formats points for CSS, e.g. `12pt` or `10.5pt`. */\nexport function ptToCss(pt: number): string {\n return `${Math.round(pt * 100) / 100}pt`;\n}\n\nconst NAMED_COLORS: Record<string, string> = {\n black: \"000000\",\n white: \"FFFFFF\",\n red: \"FF0000\",\n green: \"008000\",\n lime: \"00FF00\",\n blue: \"0000FF\",\n yellow: \"FFFF00\",\n cyan: \"00FFFF\",\n aqua: \"00FFFF\",\n magenta: \"FF00FF\",\n fuchsia: \"FF00FF\",\n gray: \"808080\",\n grey: \"808080\",\n silver: \"C0C0C0\",\n maroon: \"800000\",\n navy: \"000080\",\n olive: \"808000\",\n purple: \"800080\",\n teal: \"008080\",\n orange: \"FFA500\",\n pink: \"FFC0CB\",\n};\n\n/**\n * A CSS colour (`#abc`, `#aabbcc`, `#aabbccdd`, `rgb()` / `rgba()`, a few\n * names) as six upper-case hex digits, or `undefined`.\n */\nexport function cssColorToHex(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const color = value.trim().toLowerCase();\n if (color in NAMED_COLORS) return NAMED_COLORS[color];\n let match = /^#([0-9a-f]{3,4})$/.exec(color);\n if (match) return match[1].slice(0, 3).split(\"\").map((c) => c + c).join(\"\").toUpperCase();\n match = /^#([0-9a-f]{6})(?:[0-9a-f]{2})?$/.exec(color);\n if (match) return match[1].toUpperCase();\n match = /^rgba?\\(\\s*(\\d{1,3})\\s*[, ]\\s*(\\d{1,3})\\s*[, ]\\s*(\\d{1,3})\\s*(?:[,/]\\s*[\\d.]+%?\\s*)?\\)$/.exec(color);\n if (match) {\n return match\n .slice(1, 4)\n .map((n) => Math.min(255, Number(n)).toString(16).padStart(2, \"0\"))\n .join(\"\")\n .toUpperCase();\n }\n return undefined;\n}\n\n/** Word's highlight colours (`w:highlight`), by their RGB value. */\nexport const WORD_HIGHLIGHTS: Record<string, string> = {\n FFFF00: \"yellow\",\n \"00FF00\": \"green\",\n \"00FFFF\": \"cyan\",\n FF00FF: \"magenta\",\n \"0000FF\": \"blue\",\n FF0000: \"red\",\n \"000080\": \"darkBlue\",\n \"008080\": \"darkCyan\",\n \"008000\": \"darkGreen\",\n \"800080\": \"darkMagenta\",\n \"800000\": \"darkRed\",\n \"808000\": \"darkYellow\",\n \"808080\": \"darkGray\",\n C0C0C0: \"lightGray\",\n \"000000\": \"black\",\n FFFFFF: \"white\",\n};\n","// Portions ported from wordcraft/crates/docx/src/xml.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Characters XML 1.0 cannot carry (C0 controls other than tab / LF / CR,\n * lone surrogates, U+FFFE / U+FFFF). They are dropped, never escaped:\n * `&#1;` is not well-formed XML 1.0 either.\n */\nconst INVALID_XML_CHARS = /[^\\t\\n\\r -퟿-�\\u{10000}-\\u{10FFFF}]/gu;\n\n/** Removes characters that are not allowed in XML 1.0 documents. */\nexport function stripInvalidXmlChars(value: string): string {\n return value.replace(INVALID_XML_CHARS, \"\");\n}\n\n/** Escapes text for XML element content (`&`, `<`, `>`), dropping invalid characters. */\nexport function escapeXmlText(value: string): string {\n return stripInvalidXmlChars(value).replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\");\n}\n\n/**\n * Escapes a value for a double- or single-quoted XML attribute. White space\n * other than spaces is written as character references, because attribute\n * value normalisation would otherwise turn tabs and newlines into spaces.\n */\nexport function escapeXmlAttr(value: string): string {\n return escapeXmlText(value)\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\")\n .replace(/\\t/g, \"&#9;\")\n .replace(/\\n/g, \"&#10;\")\n .replace(/\\r/g, \"&#13;\");\n}\n\nexport type XmlAttrs = Record<string, string | number | undefined | null | false>;\n\nfunction attrs(values: XmlAttrs | undefined): string {\n if (!values) return \"\";\n let out = \"\";\n for (const [name, value] of Object.entries(values)) {\n if (value === undefined || value === null || value === false) continue;\n out += ` ${name}=\"${escapeXmlAttr(String(value))}\"`;\n }\n return out;\n}\n\n/**\n * A small string-building XML writer; every attribute value and text node is\n * escaped. Element names are trusted (always literals in this codebase).\n */\nexport class XmlWriter {\n private parts: string[];\n\n /** `declaration: false` writes a fragment (e.g. one paragraph). */\n constructor(declaration = true) {\n this.parts = declaration ? ['<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\\n'] : [];\n }\n\n open(name: string, values?: XmlAttrs): this {\n this.parts.push(`<${name}${attrs(values)}>`);\n return this;\n }\n\n empty(name: string, values?: XmlAttrs): this {\n this.parts.push(`<${name}${attrs(values)}/>`);\n return this;\n }\n\n close(name: string): this {\n this.parts.push(`</${name}>`);\n return this;\n }\n\n text(value: string): this {\n this.parts.push(escapeXmlText(value));\n return this;\n }\n\n /** `<name attrs>text</name>`. */\n leaf(name: string, values: XmlAttrs | undefined, text: string): this {\n return this.open(name, values).text(text).close(name);\n }\n\n /** `<name w:val=\"value\"/>`. */\n val(name: string, value: string | number): this {\n return this.empty(name, { \"w:val\": value });\n }\n\n /** Appends markup produced by another writer. */\n raw(markup: string): this {\n this.parts.push(markup);\n return this;\n }\n\n toString(): string {\n return this.parts.join(\"\");\n }\n}\n","import { zipCrc32 } from \"../import/zip\";\n\n/**\n * Minimal, dependency-free ZIP writer (the counterpart of `readZip`), used by\n * the DOCX export. Entries are deflated with the platform\n * `CompressionStream(\"deflate-raw\")` when it exists (modern browsers,\n * Node ≥ 18) and stored otherwise, or when deflating does not make them\n * smaller. Names are written as UTF-8 (flag bit 11). No ZIP64: archives are\n * limited to 65 535 entries and 4 GiB.\n */\n\nexport interface ZipWriteEntry {\n /** Entry path with `/` separators; must be relative and free of `..`. */\n name: string;\n /** Strings are encoded as UTF-8. */\n data: Uint8Array | string;\n}\n\nexport interface ZipWriteOptions {\n /**\n * `\"auto\"` (default) deflates when `CompressionStream` is available,\n * `\"store\"` never compresses.\n */\n compression?: \"auto\" | \"store\";\n /**\n * Modification time written for every entry (default 1980-01-01, the DOS\n * epoch, so the output is deterministic).\n */\n modified?: Date;\n}\n\nconst encoder = new TextEncoder();\nconst MAX_SIZE = 0xffffffff;\n\nasync function readAll(readable: ReadableStream<Uint8Array>): Promise<Uint8Array> {\n const reader = readable.getReader();\n const chunks: Uint8Array[] = [];\n let length = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n length += value.length;\n }\n const out = new Uint8Array(length);\n let pos = 0;\n for (const chunk of chunks) {\n out.set(chunk, pos);\n pos += chunk.length;\n }\n return out;\n}\n\n/** Raw deflate with `CompressionStream`, or `null` when it is unavailable. */\nexport async function deflateRaw(data: Uint8Array): Promise<Uint8Array | null> {\n const Stream = (globalThis as { CompressionStream?: typeof CompressionStream }).CompressionStream;\n if (typeof Stream !== \"function\") return null;\n let stream: CompressionStream;\n try {\n stream = new Stream(\"deflate-raw\" as CompressionFormat);\n } catch {\n return null;\n }\n // Plain stream APIs only: jsdom's Blob has no `stream()` (see GOTCHAS).\n const writer = stream.writable.getWriter();\n const written = writer\n .write(data as unknown as BufferSource)\n .then(() => writer.close())\n .catch(() => {});\n const out = await readAll(stream.readable as ReadableStream<Uint8Array>);\n await written;\n return out;\n}\n\nfunction dosDateTime(date: Date): { time: number; date: number } {\n const year = Math.min(Math.max(date.getUTCFullYear(), 1980), 2107);\n return {\n time: (date.getUTCHours() << 11) | (date.getUTCMinutes() << 5) | Math.floor(date.getUTCSeconds() / 2),\n date: ((year - 1980) << 9) | ((date.getUTCMonth() + 1) << 5) | date.getUTCDate(),\n };\n}\n\nclass ByteSink {\n private chunks: Uint8Array[] = [];\n length = 0;\n\n bytes(data: Uint8Array): void {\n this.chunks.push(data);\n this.length += data.length;\n }\n\n header(fields: [number, 2 | 4][]): void {\n const size = fields.reduce((n, [, width]) => n + width, 0);\n const buffer = new Uint8Array(size);\n const view = new DataView(buffer.buffer);\n let pos = 0;\n for (const [value, width] of fields) {\n if (width === 2) view.setUint16(pos, value, true);\n else view.setUint32(pos, value >>> 0, true);\n pos += width;\n }\n this.bytes(buffer);\n }\n\n toUint8Array(): Uint8Array {\n const out = new Uint8Array(this.length);\n let pos = 0;\n for (const chunk of this.chunks) {\n out.set(chunk, pos);\n pos += chunk.length;\n }\n return out;\n }\n}\n\n/** Writes a ZIP archive. Throws on unsafe or duplicate names and oversized archives. */\nexport async function writeZip(entries: readonly ZipWriteEntry[], options: ZipWriteOptions = {}): Promise<Uint8Array> {\n if (entries.length > 0xffff) throw new RangeError(\"Too many ZIP entries (no ZIP64 support).\");\n const { time, date } = dosDateTime(options.modified ?? new Date(Date.UTC(1980, 0, 1)));\n const out = new ByteSink();\n const central = new ByteSink();\n const seen = new Set<string>();\n for (const entry of entries) {\n const name = entry.name;\n if (!name || name.startsWith(\"/\") || name.includes(\"\\\\\") || name.split(\"/\").includes(\"..\") || name.includes(\"\\0\")) {\n throw new Error(`Unsafe ZIP entry name: ${JSON.stringify(name)}`);\n }\n if (seen.has(name)) throw new Error(`Duplicate ZIP entry name: ${JSON.stringify(name)}`);\n seen.add(name);\n const raw = typeof entry.data === \"string\" ? encoder.encode(entry.data) : entry.data;\n const deflated = options.compression === \"store\" || raw.length === 0 ? null : await deflateRaw(raw);\n const useDeflate = !!deflated && deflated.length < raw.length;\n const body = useDeflate ? deflated! : raw;\n const method = useDeflate ? 8 : 0;\n const nameBytes = encoder.encode(name);\n const crc = zipCrc32(raw);\n const offset = out.length;\n if (body.length > MAX_SIZE || raw.length > MAX_SIZE || offset > MAX_SIZE) {\n throw new RangeError(\"ZIP archive too large (no ZIP64 support).\");\n }\n const flags = 0x0800;\n out.header([\n [0x04034b50, 4], [20, 2], [flags, 2], [method, 2], [time, 2], [date, 2],\n [crc, 4], [body.length, 4], [raw.length, 4], [nameBytes.length, 2], [0, 2],\n ]);\n out.bytes(nameBytes);\n out.bytes(body);\n central.header([\n [0x02014b50, 4], [20, 2], [20, 2], [flags, 2], [method, 2], [time, 2], [date, 2],\n [crc, 4], [body.length, 4], [raw.length, 4], [nameBytes.length, 2], [0, 2], [0, 2],\n [0, 2], [0, 2], [0, 4], [offset, 4],\n ]);\n central.bytes(nameBytes);\n }\n const cdOffset = out.length;\n const cd = central.toUint8Array();\n out.bytes(cd);\n out.header([\n [0x06054b50, 4], [0, 2], [0, 2], [entries.length, 2], [entries.length, 2],\n [cd.length, 4], [cdOffset, 4], [0, 2],\n ]);\n return out.toUint8Array();\n}\n","// Portions ported from wordcraft/crates/docx/src/write/mod.rs, wordcraft/crates/docx/src/write/story.rs, wordcraft/crates/docx/src/write/props.rs and wordcraft/crates/docx/src/package.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\nimport { CHART_OOXML_CONTENT_TYPE, CHART_OOXML_NS, CHART_OOXML_RELATIONSHIP, chartToOoxml } from \"../chart/ooxml\";\nimport { normalizeChartSpec } from \"../chart/spec\";\nimport type { CommentThread, ReviewAuthor, SerializedComments } from \"../extensions/comment-store\";\nimport {\n DEFAULT_STYLES,\n isValidStyleId,\n NORMAL_STYLE_ID,\n type CharacterFormat,\n type ParagraphFormat,\n type StyleDefaults,\n type StyleDefinition,\n type StyleRegistry,\n type StyleRegistryJSON,\n} from \"../extensions/paragraph-styles/registry\";\nimport { DOCX_IMAGE_CONTENT_TYPES, DOCX_IMAGE_TYPES, decodeDataUrl, imagePixelSize, pixelAttr } from \"./images\";\nimport {\n cssColorToHex,\n cssLengthToPt,\n ptToHalfPoints,\n ptToTwips,\n pxToEmu,\n pxToTwips,\n WORD_HIGHLIGHTS,\n} from \"./units\";\nimport { XmlWriter, type XmlAttrs } from \"./xml\";\nimport { writeZip, type ZipWriteEntry } from \"./zip-writer\";\n\n/**\n * Dependency-free DOCX (WordprocessingML) export of TipTap documents.\n *\n * Takes an editor (anything with `getJSON()`) or its JSON and writes a\n * minimal, valid package: `[Content_Types].xml`, `_rels/.rels`,\n * `word/document.xml` (+ relationships), `styles.xml` (from the paragraph\n * style registry, with `basedOn` / `next` / `link`), `numbering.xml` (one\n * `w:num` per list), `settings.xml`, `comments.xml` +\n * `commentsExtended.xml` (replies and resolved state) + `people.xml` (author\n * ids), `docProps/core.xml` and `docProps/app.xml`, plus embedded images and\n * charts (`chart` nodes become native Word charts in `word/charts/chartN.xml`;\n * inline `sparkline` nodes are written as text: their label, or ▁▃▇ bars).\n *\n * Tracked insertions, deletions, moves (`w:moveFrom` / `w:moveTo` with\n * named move ranges), formatting changes (`w:rPrChange`) and block changes\n * (`w:pPrChange`, paragraph-mark `w:ins` / `w:del`) keep their authors and\n * dates. Anything that cannot be written is listed in the returned report.\n */\n\nexport const DOCX_EXPORT_MIME_TYPE = \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\";\n\n/** A ProseMirror / TipTap JSON node (`editor.getJSON()`). */\nexport interface DocxJSONNode {\n type?: string;\n attrs?: Record<string, any>;\n content?: DocxJSONNode[];\n marks?: DocxJSONMark[];\n text?: string;\n}\n\nexport interface DocxJSONMark {\n type: string;\n attrs?: Record<string, any>;\n}\n\n/** Anything with `getJSON()`, e.g. a TipTap `Editor`. Its comment store and style registry are used by default. */\nexport interface DocxExportEditorLike {\n getJSON(): DocxJSONNode;\n storage?: unknown;\n}\n\n/** A comment store (`CommentStore`), its `toJSON()` output, or a thread list. */\nexport type DocxCommentSource =\n | { toJSON(): SerializedComments }\n | { getSnapshot(): { threads: readonly CommentThread[] } }\n | SerializedComments\n | readonly CommentThread[];\n\nexport interface DocxExportOptions {\n /**\n * Paragraph and character styles written to `styles.xml`. Defaults to the\n * editor's registry (`ParagraphStyles` extension), else the built-in styles.\n */\n styles?: StyleRegistry | StyleRegistryJSON | readonly StyleDefinition[];\n /** Comment threads. Defaults to the editor's comment store (`Comments` extension). */\n comments?: DocxCommentSource | null;\n /**\n * The document's author (`dc:creator`) and the author of tracked changes\n * and comments that name none.\n */\n author?: string | ReviewAuthor;\n /**\n * `\"strip-authors\"` writes every comment, reply and tracked change as\n * \"Author\" (initials \"A\"), leaves out `people.xml`, the creator and last\n * modified by, and sets `w:removePersonalInformation` so Word keeps doing\n * so. Default `\"keep\"`.\n */\n privacy?: \"keep\" | \"strip-authors\";\n /** `dc:title` of the document. */\n title?: string;\n /**\n * Images with `http(s)` URLs: `\"skip\"` (default, reported) or `\"link\"`\n * (an external relationship Word may load when the file is opened).\n */\n remoteImages?: \"skip\" | \"link\";\n /** Time written as created / modified (default: now). */\n now?: () => Date;\n /** ZIP compression (default `\"auto\"`: deflate when `CompressionStream` exists). */\n compression?: \"auto\" | \"store\";\n}\n\nexport type DocxExportIssueKind = \"node\" | \"mark\" | \"image\" | \"link\" | \"comment\" | \"style\";\n\n/** Something the export left out or simplified, counted per kind, name and reason. */\nexport interface DocxExportIssue {\n kind: DocxExportIssueKind;\n /** Node / mark type, style id, image or link URL (truncated), or comment thread id. */\n name: string;\n reason: string;\n count: number;\n}\n\nexport interface DocxExportReport {\n /** Content that was dropped or simplified. Empty when everything was written. */\n dropped: DocxExportIssue[];\n /** Counts of what was written. */\n written: {\n paragraphs: number;\n tables: number;\n images: number;\n /** `chart` nodes written as native Word charts. */\n charts: number;\n comments: number;\n replies: number;\n trackedChanges: number;\n };\n}\n\nexport interface DocxExportResult {\n /** The `.docx` file. */\n data: Uint8Array;\n report: DocxExportReport;\n}\n\nexport interface DocxPackagePart {\n name: string;\n data: Uint8Array | string;\n}\n\nconst NS = {\n w: \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n r: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n wp: \"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\",\n a: \"http://schemas.openxmlformats.org/drawingml/2006/main\",\n pic: \"http://schemas.openxmlformats.org/drawingml/2006/picture\",\n mc: \"http://schemas.openxmlformats.org/markup-compatibility/2006\",\n w14: \"http://schemas.microsoft.com/office/word/2010/wordml\",\n w15: \"http://schemas.microsoft.com/office/word/2012/wordml\",\n};\n\nconst BODY_NS: XmlAttrs = {\n \"xmlns:w\": NS.w,\n \"xmlns:r\": NS.r,\n \"xmlns:wp\": NS.wp,\n \"xmlns:a\": NS.a,\n \"xmlns:pic\": NS.pic,\n \"xmlns:mc\": NS.mc,\n \"xmlns:w14\": NS.w14,\n \"xmlns:w15\": NS.w15,\n \"mc:Ignorable\": \"w14 w15\",\n};\n\nconst REL = {\n officeDocument: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\",\n core: \"http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties\",\n app: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties\",\n styles: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\",\n numbering: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering\",\n settings: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings\",\n comments: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments\",\n commentsExtended: \"http://schemas.microsoft.com/office/2011/relationships/commentsExtended\",\n people: \"http://schemas.microsoft.com/office/2011/relationships/people\",\n image: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image\",\n hyperlink: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink\",\n};\n\nconst CT_WML = \"application/vnd.openxmlformats-officedocument.wordprocessingml.\";\n\n/** Text width of a US Letter page with 1\" margins, in twips (6.5\"). */\nconst CONTENT_WIDTH_TWIPS = 9360;\n/** Widest image, in CSS pixels (6.5\" at 96 dpi). */\nconst MAX_IMAGE_WIDTH_PX = 624;\n/** Indent per list or quote level, in twips (0.5\"). */\nconst INDENT_STEP = 720;\n/** Name written for every author with `privacy: \"strip-authors\"` (as Word does). */\nexport const DOCX_ANONYMOUS_AUTHOR = \"Author\";\nconst ANONYMOUS_INITIALS = \"A\";\nconst MAX_DEPTH = 64;\n\n/** Bullet characters per list level. */\nconst BULLETS = [\"•\", \"◦\", \"▪\"];\nconst BULLET_FONTS = [\"Symbol\", \"Courier New\", \"Wingdings\"];\nconst TASK_UNCHECKED = \"☐\";\nconst TASK_CHECKED = \"☒\";\n\n/** Formatting marks whose `rPr` is written; the rest are reported. */\nconst FORMAT_MARKS = new Set([\n \"bold\",\n \"italic\",\n \"underline\",\n \"strike\",\n \"subscript\",\n \"superscript\",\n \"code\",\n \"textStyle\",\n \"highlight\",\n \"characterStyle\",\n]);\n/** Marks handled separately (links, review) or that carry no formatting. */\nconst STRUCTURAL_MARKS = new Set([\"link\", \"insertion\", \"deletion\", \"formatChange\", \"comment\", \"bookmark\"]);\n\ninterface RunFormat {\n charStyle?: string;\n font?: string;\n bold?: boolean;\n italic?: boolean;\n strike?: boolean;\n color?: string;\n halfPoints?: number;\n highlight?: string;\n shading?: string;\n underline?: boolean;\n vertAlign?: \"superscript\" | \"subscript\";\n}\n\ninterface Revision {\n kind: \"ins\" | \"del\";\n attrs: Record<string, any>;\n moveId?: string;\n}\n\ntype PieceKind = \"text\" | \"tab\" | \"br\" | \"image\";\n\ninterface Piece {\n kind: PieceKind;\n text?: string;\n node: DocxJSONNode;\n /** Last piece produced by `node` (comment range ends go after it). */\n last: boolean;\n marks: DocxJSONMark[];\n}\n\ninterface BlockContext {\n depth: number;\n quoteLevel: number;\n list?: { numId: number; level: number; task?: boolean; checked?: boolean };\n /** The list item's first paragraph gets the number; later ones are indented. */\n listItemFirst?: boolean;\n}\n\ninterface BlockChangeRecord {\n changeId?: string;\n authorId?: string;\n authorName?: string;\n createdAt?: number;\n previous?: { type?: string; attrs?: Record<string, any> };\n operation?: \"insert\" | \"delete\";\n}\n\nconst isEditorLike = (value: unknown): value is DocxExportEditorLike =>\n !!value && typeof (value as DocxExportEditorLike).getJSON === \"function\";\n\n/** Threads from any supported comment source. */\nfunction threadsOf(source: DocxCommentSource | null | undefined): CommentThread[] {\n if (!source) return [];\n if (Array.isArray(source)) return [...source];\n const value = source as any;\n if (typeof value.toJSON === \"function\") return [...(value.toJSON()?.threads ?? [])];\n if (typeof value.getSnapshot === \"function\") return [...(value.getSnapshot()?.threads ?? [])];\n return Array.isArray(value.threads) ? [...value.threads] : [];\n}\n\nfunction stylesOf(source: DocxExportOptions[\"styles\"]): { styles: StyleDefinition[]; defaults: StyleDefaults } {\n if (!source) return { styles: DEFAULT_STYLES.map((s) => ({ ...s })), defaults: {} };\n if (Array.isArray(source)) return { styles: [...source], defaults: {} };\n const value = source as any;\n const json: StyleRegistryJSON = typeof value.toJSON === \"function\" ? value.toJSON() : value;\n return { styles: [...(json.styles ?? [])], defaults: json.defaults ?? {} };\n}\n\nfunction authorOf(author: DocxExportOptions[\"author\"]): ReviewAuthor | undefined {\n if (!author) return undefined;\n return typeof author === \"string\" ? { id: author, name: author } : author;\n}\n\n/** W3CDTF date without milliseconds, or `undefined` for missing / invalid times. */\nfunction isoDate(ms: unknown): string | undefined {\n const n = typeof ms === \"number\" ? ms : Number(ms);\n if (!Number.isFinite(n) || n <= 0) return undefined;\n const date = new Date(n);\n return Number.isNaN(date.getTime()) ? undefined : date.toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n}\n\nfunction initialsOf(name: string): string {\n const letters = name\n .split(/\\s+/)\n .filter(Boolean)\n .map((word) => Array.from(word)[0] ?? \"\")\n .join(\"\")\n .slice(0, 3)\n .toUpperCase();\n return letters || ANONYMOUS_INITIALS;\n}\n\n/** Word keeps built-in style names in lower case (`heading 1`) in `styles.xml`. */\nfunction fileStyleName(name: string): string {\n const heading = /^Heading ([1-9])$/.exec(name);\n return heading ? `heading ${heading[1]}` : name;\n}\n\nconst truncate = (value: string, max = 80) => (value.length > max ? `${value.slice(0, max - 1)}…` : value);\n\n/** Converts a mark list to run formatting. */\nfunction runFormatOf(marks: readonly DocxJSONMark[] | undefined): RunFormat {\n const format: RunFormat = {};\n for (const mark of marks ?? []) {\n const attrs = mark.attrs ?? {};\n switch (mark.type) {\n case \"bold\":\n format.bold = true;\n break;\n case \"italic\":\n format.italic = true;\n break;\n case \"underline\":\n format.underline = true;\n break;\n case \"strike\":\n format.strike = true;\n break;\n case \"subscript\":\n format.vertAlign = \"subscript\";\n break;\n case \"superscript\":\n format.vertAlign = \"superscript\";\n break;\n case \"code\":\n format.font = \"Consolas\";\n break;\n case \"textStyle\": {\n const color = cssColorToHex(attrs.color);\n if (color) format.color = color;\n const pt = cssLengthToPt(attrs.fontSize);\n if (pt !== undefined && pt > 0) format.halfPoints = ptToHalfPoints(pt);\n if (typeof attrs.fontFamily === \"string\" && attrs.fontFamily.trim()) {\n format.font = attrs.fontFamily.split(\",\")[0].trim().replace(/^[\"']|[\"']$/g, \"\");\n }\n break;\n }\n case \"highlight\": {\n const color = attrs.color ? cssColorToHex(attrs.color) : \"FFFF00\";\n if (color && WORD_HIGHLIGHTS[color]) format.highlight = WORD_HIGHLIGHTS[color];\n else if (color) format.shading = color;\n break;\n }\n case \"characterStyle\":\n if (isValidStyleId(attrs.styleId)) format.charStyle = attrs.styleId;\n break;\n case \"link\":\n format.charStyle ??= \"Hyperlink\";\n break;\n default:\n break;\n }\n }\n return format;\n}\n\n/** `w:rPr` children in CT_RPr order. */\nfunction writeRunProps(w: XmlWriter, f: RunFormat): void {\n if (f.charStyle) w.val(\"w:rStyle\", f.charStyle);\n if (f.font) w.empty(\"w:rFonts\", { \"w:ascii\": f.font, \"w:hAnsi\": f.font, \"w:cs\": f.font, \"w:eastAsia\": f.font });\n if (f.bold) w.empty(\"w:b\");\n if (f.italic) w.empty(\"w:i\");\n if (f.strike) w.empty(\"w:strike\");\n if (f.color) w.val(\"w:color\", f.color);\n if (f.halfPoints) {\n w.val(\"w:sz\", f.halfPoints);\n w.val(\"w:szCs\", f.halfPoints);\n }\n if (f.highlight) w.val(\"w:highlight\", f.highlight);\n if (f.underline) w.val(\"w:u\", \"single\");\n if (f.shading) w.empty(\"w:shd\", { \"w:val\": \"clear\", \"w:color\": \"auto\", \"w:fill\": f.shading });\n if (f.vertAlign) w.val(\"w:vertAlign\", f.vertAlign);\n}\n\nconst hasRunProps = (f: RunFormat) => Object.values(f).some((v) => v !== undefined && v !== false);\n\nfunction characterFormatToRun(c: CharacterFormat | undefined): RunFormat {\n const f: RunFormat = {};\n if (!c) return f;\n if (c.fontFamily) f.font = c.fontFamily.split(\",\")[0].trim().replace(/^[\"']|[\"']$/g, \"\");\n if (c.bold) f.bold = true;\n if (c.italic) f.italic = true;\n if (c.underline) f.underline = true;\n if (c.strike) f.strike = true;\n const color = cssColorToHex(c.color);\n if (color) f.color = color;\n const pt = cssLengthToPt(c.fontSize);\n if (pt !== undefined && pt > 0) f.halfPoints = ptToHalfPoints(pt);\n const background = cssColorToHex(c.backgroundColor);\n if (background) f.shading = background;\n return f;\n}\n\n/** `w:pPr` content of a style's paragraph format (spacing, indentation, alignment). */\nfunction writeParagraphFormat(w: XmlWriter, p: ParagraphFormat | undefined, outline?: number): void {\n if (!p && outline === undefined) return;\n const before = cssLengthToPt(p?.spaceBefore);\n const after = cssLengthToPt(p?.spaceAfter);\n const lineValue = p?.lineHeight;\n let line: number | undefined;\n let lineRule: string | undefined;\n if (typeof lineValue === \"string\" && /^\\s*\\d+(\\.\\d+)?\\s*$/.test(lineValue)) {\n line = Math.round(Number(lineValue) * 240);\n lineRule = \"auto\";\n } else if (lineValue) {\n const pt = cssLengthToPt(lineValue);\n if (pt !== undefined && pt > 0) {\n line = ptToTwips(pt);\n lineRule = \"atLeast\";\n }\n }\n if (before !== undefined || after !== undefined || line !== undefined) {\n w.empty(\"w:spacing\", {\n \"w:before\": before !== undefined ? Math.max(0, ptToTwips(before)) : undefined,\n \"w:after\": after !== undefined ? Math.max(0, ptToTwips(after)) : undefined,\n \"w:line\": line,\n \"w:lineRule\": lineRule,\n });\n }\n const left = cssLengthToPt(p?.indentLeft);\n const right = cssLengthToPt(p?.indentRight);\n const first = cssLengthToPt(p?.indentFirstLine);\n if (left !== undefined || right !== undefined || first !== undefined) {\n w.empty(\"w:ind\", {\n \"w:left\": left !== undefined ? ptToTwips(left) : undefined,\n \"w:right\": right !== undefined ? ptToTwips(right) : undefined,\n \"w:firstLine\": first !== undefined && first >= 0 ? ptToTwips(first) : undefined,\n \"w:hanging\": first !== undefined && first < 0 ? ptToTwips(-first) : undefined,\n });\n }\n const jc = alignment(p?.textAlign);\n if (jc) w.val(\"w:jc\", jc);\n if (outline !== undefined) w.val(\"w:outlineLvl\", outline);\n}\n\nfunction alignment(value: unknown): string | undefined {\n switch (value) {\n case \"left\":\n return \"left\";\n case \"center\":\n return \"center\";\n case \"right\":\n return \"right\";\n case \"justify\":\n return \"both\";\n default:\n return undefined;\n }\n}\n\nclass Relationships {\n private list: { id: string; type: string; target: string; external: boolean }[] = [];\n private index = new Map<string, string>();\n\n add(type: string, target: string, external = false): string {\n const key = `${type}\\n${target}\\n${external}`;\n const existing = this.index.get(key);\n if (existing) return existing;\n const id = `rId${this.list.length + 1}`;\n this.list.push({ id, type, target, external });\n this.index.set(key, id);\n return id;\n }\n\n get size(): number {\n return this.list.length;\n }\n\n xml(): string {\n const w = new XmlWriter();\n w.open(\"Relationships\", { xmlns: \"http://schemas.openxmlformats.org/package/2006/relationships\" });\n for (const rel of this.list) {\n w.empty(\"Relationship\", {\n Id: rel.id,\n Type: rel.type,\n Target: rel.target,\n TargetMode: rel.external ? \"External\" : undefined,\n });\n }\n w.close(\"Relationships\");\n return w.toString();\n }\n}\n\ninterface CommentEntry {\n thread: CommentThread;\n /** File id of the thread's comment, then of each reply. */\n ids: number[];\n started: boolean;\n ended: boolean;\n}\n\nclass DocxBuilder {\n readonly report: DocxExportReport = {\n dropped: [],\n written: { paragraphs: 0, tables: 0, images: 0, charts: 0, comments: 0, replies: 0, trackedChanges: 0 },\n };\n private rels = new Relationships();\n private media: { name: string; bytes: Uint8Array }[] = [];\n private mediaTypes = new Set<string>();\n private charts: string[] = [];\n private nums: { numId: number; abstractId: number; start?: number; level: number }[] = [];\n private revisionId = 0;\n private docPrId = 0;\n private paraIdCounter = 0x10000000;\n private comments = new Map<string, CommentEntry>();\n /** Thread id → JSON node carrying its last comment mark. */\n private commentEnds = new Map<string, DocxJSONNode>();\n /** JSON node → threads whose range ends after it (so ends are found in O(1) per node). */\n private endsByNode = new Map<DocxJSONNode, string[]>();\n private people = new Map<string, string>();\n private styleIds: Set<string>;\n private extraStyles = new Set<string>();\n private readonly anonymous: boolean;\n private readonly defaultAuthor: ReviewAuthor | undefined;\n\n constructor(\n private readonly doc: DocxJSONNode,\n private readonly options: DocxExportOptions,\n private readonly threads: CommentThread[],\n private readonly styles: { styles: StyleDefinition[]; defaults: StyleDefaults },\n ) {\n this.anonymous = options.privacy === \"strip-authors\";\n this.defaultAuthor = authorOf(options.author);\n this.styleIds = new Set(styles.styles.filter((s) => isValidStyleId(s.id)).map((s) => s.id));\n }\n\n drop(kind: DocxExportIssueKind, name: string, reason: string): void {\n const issue = this.report.dropped.find((i) => i.kind === kind && i.name === name && i.reason === reason);\n if (issue) issue.count++;\n else this.report.dropped.push({ kind, name: truncate(name), reason, count: 1 });\n }\n\n // -- Authors and revisions ------------------------------------------------\n\n private authorName(name: unknown, id: unknown): string {\n if (this.anonymous) return DOCX_ANONYMOUS_AUTHOR;\n const author =\n (typeof name === \"string\" && name.trim()) ||\n (typeof id === \"string\" && id.trim()) ||\n this.defaultAuthor?.name ||\n \"Unknown\";\n const authorId = (typeof id === \"string\" && id.trim()) || this.defaultAuthor?.id;\n if (authorId && authorId !== author && !this.people.has(author)) this.people.set(author, authorId);\n return author;\n }\n\n private changeAttrs(attrs: Record<string, any>): XmlAttrs {\n return {\n \"w:id\": ++this.revisionId,\n \"w:author\": this.authorName(attrs.authorName, attrs.authorId),\n \"w:date\": isoDate(attrs.createdAt),\n };\n }\n\n // -- Comments ---------------------------------------------------------------\n\n private prepareComments(): void {\n const marked = new Set<string>();\n const visit = (node: DocxJSONNode, depth: number) => {\n if (depth > MAX_DEPTH) return;\n for (const mark of node.marks ?? []) {\n if (mark.type === \"comment\" && typeof mark.attrs?.commentId === \"string\") {\n marked.add(mark.attrs.commentId);\n this.commentEnds.set(mark.attrs.commentId, node);\n }\n }\n node.content?.forEach((child) => visit(child, depth + 1));\n };\n visit(this.doc, 0);\n let next = 0;\n for (const thread of this.threads) {\n if (!thread || typeof thread.id !== \"string\") continue;\n if (!marked.has(thread.id)) {\n this.drop(\"comment\", thread.id, \"comment text is not in the document\");\n continue;\n }\n if (!thread.body?.trim()) {\n this.drop(\"comment\", thread.id, \"draft comment without text\");\n this.commentEnds.delete(thread.id);\n continue;\n }\n const replies = (thread.replies ?? []).filter((reply) => reply && reply.body?.trim());\n const ids = [next++, ...replies.map(() => next++)];\n this.comments.set(thread.id, { thread: { ...thread, replies }, ids, started: false, ended: false });\n }\n for (const [threadId, node] of this.commentEnds) {\n if (!this.comments.has(threadId)) continue;\n const list = this.endsByNode.get(node) ?? [];\n list.push(threadId);\n this.endsByNode.set(node, list);\n }\n }\n\n // -- Document ---------------------------------------------------------------\n\n documentXml(): string {\n this.prepareComments();\n const body = new XmlWriter(false);\n const blocks = this.doc.type === \"doc\" || !this.doc.type ? (this.doc.content ?? []) : [this.doc];\n this.blocks(body, blocks, { depth: 0, quoteLevel: 0 });\n // Word wants a paragraph before the section properties.\n if (!blocks.length || blocks[blocks.length - 1].type === \"table\") body.empty(\"w:p\");\n // Ranges whose end was never written (e.g. text in a dropped node) are closed here.\n for (const [id, entry] of this.comments) if (entry.started && !entry.ended) this.endComment(body, id);\n const w = new XmlWriter();\n w.open(\"w:document\", BODY_NS).open(\"w:body\").raw(body.toString());\n w.open(\"w:sectPr\")\n .empty(\"w:pgSz\", { \"w:w\": 12240, \"w:h\": 15840 })\n .empty(\"w:pgMar\", {\n \"w:top\": 1440,\n \"w:right\": 1440,\n \"w:bottom\": 1440,\n \"w:left\": 1440,\n \"w:header\": 720,\n \"w:footer\": 720,\n \"w:gutter\": 0,\n })\n .close(\"w:sectPr\");\n w.close(\"w:body\").close(\"w:document\");\n return w.toString();\n }\n\n private blocks(w: XmlWriter, nodes: readonly DocxJSONNode[], ctx: BlockContext): void {\n if (ctx.depth > MAX_DEPTH) {\n this.drop(\"node\", \"nesting\", `content nested deeper than ${MAX_DEPTH} levels`);\n return;\n }\n let first = true;\n nodes.forEach((node, i) => {\n const itemCtx: BlockContext = { ...ctx, depth: ctx.depth + 1, listItemFirst: ctx.list ? first : undefined };\n this.block(w, node, itemCtx);\n if (node.type !== \"bulletList\" && node.type !== \"orderedList\" && node.type !== \"taskList\") first = false;\n // Two tables in a row would merge into one in Word, so a paragraph separates them.\n if (node.type === \"table\" && nodes[i + 1]?.type === \"table\") w.empty(\"w:p\");\n });\n }\n\n private block(w: XmlWriter, node: DocxJSONNode, ctx: BlockContext): void {\n const type = node.type ?? \"\";\n switch (type) {\n case \"paragraph\":\n this.paragraph(w, node, ctx);\n return;\n case \"heading\":\n this.paragraph(w, node, ctx, { heading: Math.max(1, Math.min(6, Number(node.attrs?.level) || 1)) });\n return;\n case \"codeBlock\":\n this.paragraph(w, node, ctx, { code: true });\n return;\n case \"blockquote\":\n this.blocks(w, node.content ?? [], { ...ctx, quoteLevel: ctx.quoteLevel + 1 });\n return;\n case \"bulletList\":\n case \"orderedList\":\n case \"taskList\":\n this.list(w, node, ctx);\n return;\n case \"table\":\n this.table(w, node, ctx);\n return;\n case \"horizontalRule\":\n w.open(\"w:p\").open(\"w:pPr\").open(\"w:pBdr\");\n w.empty(\"w:bottom\", { \"w:val\": \"single\", \"w:sz\": 6, \"w:space\": 1, \"w:color\": \"auto\" });\n w.close(\"w:pBdr\").close(\"w:pPr\").close(\"w:p\");\n this.report.written.paragraphs++;\n return;\n case \"pageBreak\":\n w.open(\"w:p\").open(\"w:r\").empty(\"w:br\", { \"w:type\": \"page\" }).close(\"w:r\").close(\"w:p\");\n return;\n case \"image\":\n this.paragraph(w, { type: \"paragraph\", content: [node] }, ctx);\n return;\n case \"chart\":\n this.chartParagraph(w, node, ctx);\n return;\n default:\n break;\n }\n // Unknown blocks: inline content becomes a paragraph, block content is flattened.\n const content = node.content ?? [];\n if (content.some((child) => child.type === \"text\" || isInlineType(child.type))) {\n this.drop(\"node\", type || \"unknown\", \"written as a plain paragraph\");\n this.paragraph(w, { type: \"paragraph\", content }, ctx);\n } else if (content.length) {\n this.drop(\"node\", type || \"unknown\", \"container flattened\");\n this.blocks(w, content, ctx);\n } else {\n this.drop(\"node\", type || \"unknown\", \"unsupported node\");\n }\n }\n\n // -- Lists --------------------------------------------------------------------\n\n private list(w: XmlWriter, node: DocxJSONNode, ctx: BlockContext): void {\n const level = Math.min(8, ctx.list ? ctx.list.level + 1 : 0);\n const abstractId = node.type === \"orderedList\" ? 2 : node.type === \"taskList\" ? 3 : 1;\n const numId = this.nums.length + 1;\n const start = node.type === \"orderedList\" ? Number(node.attrs?.start ?? 1) : undefined;\n this.nums.push({ numId, abstractId, level, start: start !== undefined && start !== 1 && Number.isFinite(start) ? start : undefined });\n for (const item of node.content ?? []) {\n const task = item.type === \"taskItem\";\n this.blocks(w, item.content ?? [], {\n ...ctx,\n list: { numId, level, task, checked: task ? !!item.attrs?.checked : undefined },\n });\n }\n }\n\n // -- Paragraphs -----------------------------------------------------------------\n\n private styleFor(node: DocxJSONNode, ctx: BlockContext, opts: { heading?: number; code?: boolean }): string | undefined {\n const own = node.attrs?.styleId;\n if (own) {\n if (isValidStyleId(own) && this.styleIds.has(own)) return own;\n this.drop(\"style\", String(own), \"style is not in the registry\");\n }\n if (opts.heading) return this.needStyle(`Heading${opts.heading}`);\n if (opts.code) return this.needStyle(\"Code\");\n if (ctx.quoteLevel > 0) return this.needStyle(\"BlockText\");\n if (ctx.list) return this.needStyle(\"ListParagraph\");\n return undefined;\n }\n\n /** Uses a style the registry may not define (it is then written with defaults). */\n private needStyle(id: string): string {\n if (!this.styleIds.has(id)) this.extraStyles.add(id);\n return id;\n }\n\n private previousStyle(previous: BlockChangeRecord[\"previous\"]): string | undefined {\n if (!previous) return undefined;\n const id = previous.attrs?.styleId;\n if (isValidStyleId(id) && this.styleIds.has(id)) return id;\n if (previous.type === \"heading\") return this.needStyle(`Heading${Math.max(1, Math.min(6, Number(previous.attrs?.level) || 1))}`);\n if (previous.type === \"codeBlock\") return this.needStyle(\"Code\");\n return undefined;\n }\n\n private paragraph(\n w: XmlWriter,\n node: DocxJSONNode,\n ctx: BlockContext,\n opts: { heading?: number; code?: boolean } = {},\n ): void {\n this.report.written.paragraphs++;\n const style = this.styleFor(node, ctx, opts);\n const change = (node.attrs?.blockChange ?? null) as BlockChangeRecord | null;\n const operation = change?.operation;\n const list = ctx.list;\n const numbered = list && ctx.listItemFirst;\n const indent = ctx.quoteLevel * INDENT_STEP + (list && !numbered ? (list.level + 1) * INDENT_STEP : 0);\n const jc = alignment(node.attrs?.textAlign);\n\n w.open(\"w:p\");\n const ppr = new XmlWriter(false);\n if (style) ppr.val(\"w:pStyle\", style);\n if (numbered) {\n ppr.open(\"w:numPr\").val(\"w:ilvl\", list!.level).val(\"w:numId\", list!.numId).close(\"w:numPr\");\n }\n if (indent && !(ctx.quoteLevel > 0 && !list && ctx.quoteLevel === 1)) ppr.empty(\"w:ind\", { \"w:left\": indent });\n if (jc) ppr.val(\"w:jc\", jc);\n if (change && operation) {\n ppr.open(\"w:rPr\").empty(operation === \"insert\" ? \"w:ins\" : \"w:del\", this.changeAttrs(change)).close(\"w:rPr\");\n this.report.written.trackedChanges++;\n } else if (change?.previous) {\n ppr.open(\"w:pPrChange\", this.changeAttrs(change)).open(\"w:pPr\");\n const old = this.previousStyle(change.previous);\n if (old) ppr.val(\"w:pStyle\", old);\n const oldJc = alignment(change.previous.attrs?.textAlign);\n if (oldJc) ppr.val(\"w:jc\", oldJc);\n ppr.close(\"w:pPr\").close(\"w:pPrChange\");\n this.report.written.trackedChanges++;\n }\n const pprXml = ppr.toString();\n if (pprXml) w.open(\"w:pPr\").raw(pprXml).close(\"w:pPr\");\n\n if (list?.task && numbered) {\n // Word has no checklist numbering: the box is the first character.\n w.open(\"w:r\").leaf(\"w:t\", { \"xml:space\": \"preserve\" }, `${list.checked ? TASK_CHECKED : TASK_UNCHECKED} `).close(\"w:r\");\n }\n const synthetic: Revision | undefined =\n change && operation ? { kind: operation === \"insert\" ? \"ins\" : \"del\", attrs: change } : undefined;\n this.inline(w, this.pieces(node.content ?? [], opts.code === true), synthetic);\n w.close(\"w:p\");\n }\n\n /** Flattens inline content into runs of text, tabs, breaks and images. */\n private pieces(nodes: readonly DocxJSONNode[], code: boolean, out: Piece[] = [], depth = 0): Piece[] {\n if (depth > MAX_DEPTH) return out;\n for (const node of nodes) {\n const marks = node.marks ?? [];\n switch (node.type) {\n case \"text\": {\n const text = node.text ?? \"\";\n const parts = text.split(/(\\t|\\r\\n|\\n|\\r)/);\n const pieces: Piece[] = [];\n for (const part of parts) {\n if (!part) continue;\n if (part === \"\\t\") pieces.push({ kind: \"tab\", node, last: false, marks });\n else if (part === \"\\n\" || part === \"\\r\\n\" || part === \"\\r\") pieces.push({ kind: \"br\", node, last: false, marks });\n else pieces.push({ kind: \"text\", text: part, node, last: false, marks });\n }\n if (!pieces.length) pieces.push({ kind: \"text\", text: \"\", node, last: false, marks });\n pieces[pieces.length - 1].last = true;\n out.push(...pieces);\n break;\n }\n case \"hardBreak\":\n out.push({ kind: \"br\", node, last: true, marks });\n break;\n case \"image\":\n out.push({ kind: \"image\", node, last: true, marks });\n break;\n case \"sparkline\": {\n const text = sparklineText(node.attrs);\n this.drop(\"node\", \"sparkline\", \"written as text (its label, or ▁▃▇ bars)\");\n out.push({ kind: \"text\", text, node, last: true, marks });\n break;\n }\n case \"mention\": {\n const label = node.attrs?.label ?? node.attrs?.id ?? \"\";\n out.push({ kind: \"text\", text: label ? `@${label}` : \"\", node, last: true, marks });\n break;\n }\n default:\n if (node.content?.length) {\n this.pieces(node.content, code, out, depth + 1);\n } else if (typeof node.attrs?.label === \"string\" || typeof node.attrs?.text === \"string\") {\n this.drop(\"node\", node.type ?? \"unknown\", \"written as text\");\n out.push({ kind: \"text\", text: String(node.attrs.label ?? node.attrs.text), node, last: true, marks });\n } else {\n this.drop(\"node\", node.type ?? \"unknown\", \"unsupported inline node\");\n }\n }\n }\n return out;\n }\n\n private revisionsOf(marks: readonly DocxJSONMark[], synthetic?: Revision): Revision[] {\n const out: Revision[] = [];\n const ins = marks.find((m) => m.type === \"insertion\");\n const del = marks.find((m) => m.type === \"deletion\");\n if (ins) out.push({ kind: \"ins\", attrs: ins.attrs ?? {}, moveId: ins.attrs?.moveId || undefined });\n if (del) out.push({ kind: \"del\", attrs: del.attrs ?? {}, moveId: del.attrs?.moveId || undefined });\n if (!out.length && synthetic) out.push(synthetic);\n return out;\n }\n\n private linkOf(marks: readonly DocxJSONMark[]): string | undefined {\n const link = marks.find((m) => m.type === \"link\");\n const href = typeof link?.attrs?.href === \"string\" ? link.attrs.href.trim() : \"\";\n if (!href) return undefined;\n if (href.startsWith(\"#\") || /^(https?|mailto|tel|ftp):/i.test(href) || !/^[a-z][a-z0-9+.-]*:/i.test(href)) return href;\n this.drop(\"link\", href, \"unsafe URL scheme\");\n return undefined;\n }\n\n /** Writes pieces grouped into hyperlinks (outer) and tracked-change wrappers (inner). */\n private inline(w: XmlWriter, pieces: Piece[], synthetic?: Revision): void {\n let openLink: string | undefined;\n type OpenRevisions = { key: string; tags: string[]; moveRanges: { tag: string; id: number }[] };\n let openRevs: OpenRevisions | null = null;\n const closeRevs = () => {\n const open = openRevs as OpenRevisions | null;\n if (!open) return;\n for (const tag of [...open.tags].reverse()) w.close(tag);\n for (const range of open.moveRanges) w.empty(range.tag, { \"w:id\": range.id });\n openRevs = null;\n };\n const closeLink = () => {\n closeRevs();\n if (openLink !== undefined) w.close(\"w:hyperlink\");\n openLink = undefined;\n };\n for (const piece of pieces) {\n const href = this.linkOf(piece.marks);\n if (href !== openLink) {\n closeLink();\n if (href !== undefined) {\n if (href.startsWith(\"#\")) w.open(\"w:hyperlink\", { \"w:anchor\": href.slice(1), \"w:history\": 1 });\n else w.open(\"w:hyperlink\", { \"r:id\": this.rels.add(REL.hyperlink, href, true), \"w:history\": 1 });\n openLink = href;\n }\n }\n const revs = this.revisionsOf(piece.marks, synthetic);\n const key = revs.map((r) => `${r.kind}:${r.attrs.changeId ?? \"\"}:${r.moveId ?? \"\"}:${r.attrs.authorName ?? \"\"}`).join(\"|\");\n if (key !== ((openRevs as OpenRevisions | null)?.key ?? \"\")) {\n closeRevs();\n if (revs.length) {\n const state: OpenRevisions = { key, tags: [], moveRanges: [] };\n for (const rev of revs) {\n const attrs = this.changeAttrs(rev.attrs);\n this.report.written.trackedChanges++;\n if (rev.moveId) {\n const rangeTag = rev.kind === \"ins\" ? \"w:moveToRange\" : \"w:moveFromRange\";\n const rangeId = ++this.revisionId;\n w.empty(`${rangeTag}Start`, { \"w:id\": rangeId, \"w:name\": rev.moveId, \"w:author\": attrs[\"w:author\"], \"w:date\": attrs[\"w:date\"] });\n state.moveRanges.push({ tag: `${rangeTag}End`, id: rangeId });\n }\n const tag = rev.moveId ? (rev.kind === \"ins\" ? \"w:moveTo\" : \"w:moveFrom\") : rev.kind === \"ins\" ? \"w:ins\" : \"w:del\";\n w.open(tag, attrs);\n state.tags.push(tag);\n }\n openRevs = state;\n }\n }\n this.commentStarts(w, piece.marks);\n this.run(w, piece, revs);\n if (piece.last) this.commentEndsAfter(w, piece.node);\n }\n closeLink();\n }\n\n private commentStarts(w: XmlWriter, marks: readonly DocxJSONMark[]): void {\n for (const mark of marks) {\n if (mark.type !== \"comment\") continue;\n const entry = this.comments.get(mark.attrs?.commentId);\n if (!entry || entry.started) continue;\n entry.started = true;\n for (const id of entry.ids) w.empty(\"w:commentRangeStart\", { \"w:id\": id });\n }\n }\n\n private commentEndsAfter(w: XmlWriter, node: DocxJSONNode): void {\n for (const threadId of this.endsByNode.get(node) ?? []) {\n const entry = this.comments.get(threadId);\n if (entry?.started && !entry.ended) this.endComment(w, threadId);\n }\n }\n\n private endComment(w: XmlWriter, threadId: string): void {\n const entry = this.comments.get(threadId)!;\n entry.ended = true;\n for (const id of entry.ids) {\n w.empty(\"w:commentRangeEnd\", { \"w:id\": id });\n w.open(\"w:r\").open(\"w:rPr\").val(\"w:rStyle\", \"CommentReference\").close(\"w:rPr\");\n w.empty(\"w:commentReference\", { \"w:id\": id }).close(\"w:r\");\n }\n this.needStyle(\"CommentReference\");\n this.needStyle(\"CommentText\");\n }\n\n private run(w: XmlWriter, piece: Piece, revs: Revision[]): void {\n for (const mark of piece.marks) {\n if (!FORMAT_MARKS.has(mark.type) && !STRUCTURAL_MARKS.has(mark.type)) this.drop(\"mark\", mark.type, \"formatting not supported\");\n if (mark.type === \"characterStyle\" && mark.attrs?.styleId && !this.styleIds.has(mark.attrs.styleId)) {\n this.drop(\"style\", String(mark.attrs.styleId), \"character style is not in the registry\");\n }\n }\n if (piece.kind === \"image\") {\n const drawing = this.image(piece.node);\n if (!drawing) return;\n w.open(\"w:r\");\n const format = runFormatOf(piece.marks);\n delete format.charStyle;\n if (hasRunProps(format)) {\n w.open(\"w:rPr\");\n writeRunProps(w, format);\n w.close(\"w:rPr\");\n }\n w.raw(drawing).close(\"w:r\");\n return;\n }\n const format = runFormatOf(piece.marks.filter((m) => m.type !== \"characterStyle\" || this.styleIds.has(m.attrs?.styleId)));\n if (format.charStyle === \"Hyperlink\") this.needStyle(\"Hyperlink\");\n const formatChange = piece.marks.find((m) => m.type === \"formatChange\");\n w.open(\"w:r\");\n if (hasRunProps(format) || formatChange) {\n w.open(\"w:rPr\");\n writeRunProps(w, format);\n if (formatChange) {\n const attrs = formatChange.attrs ?? {};\n w.open(\"w:rPrChange\", this.changeAttrs(attrs)).open(\"w:rPr\");\n writeRunProps(w, runFormatOf(Array.isArray(attrs.before) ? attrs.before : []));\n w.close(\"w:rPr\").close(\"w:rPrChange\");\n this.report.written.trackedChanges++;\n }\n w.close(\"w:rPr\");\n }\n // Deleted text is `w:delText`; text moved away stays `w:t` (ECMA-376 §17.13.5.22).\n const deleted = revs.some((r) => r.kind === \"del\" && !r.moveId);\n if (piece.kind === \"tab\") w.empty(\"w:tab\");\n else if (piece.kind === \"br\") w.empty(\"w:br\");\n else if (piece.text) w.leaf(deleted ? \"w:delText\" : \"w:t\", { \"xml:space\": \"preserve\" }, piece.text);\n w.close(\"w:r\");\n }\n\n // -- Images -----------------------------------------------------------------\n\n private image(node: DocxJSONNode): string | null {\n const src = typeof node.attrs?.src === \"string\" ? node.attrs.src.trim() : \"\";\n const alt = typeof node.attrs?.alt === \"string\" ? node.attrs.alt : \"\";\n const title = typeof node.attrs?.title === \"string\" ? node.attrs.title : \"\";\n let relId: string;\n let natural: { width: number; height: number } | null = null;\n let linked = false;\n if (src.startsWith(\"data:\")) {\n const decoded = decodeDataUrl(src);\n const ext = decoded ? DOCX_IMAGE_TYPES[decoded.mime] : undefined;\n if (!decoded || !ext) {\n this.drop(\"image\", decoded?.mime ?? \"data URL\", \"unsupported image type\");\n return null;\n }\n const name = `image${this.media.length + 1}.${ext}`;\n this.media.push({ name, bytes: decoded.bytes });\n this.mediaTypes.add(ext);\n relId = this.rels.add(REL.image, `media/${name}`);\n natural = imagePixelSize(decoded.bytes);\n } else if (/^https?:\\/\\//i.test(src)) {\n if (this.options.remoteImages !== \"link\") {\n this.drop(\"image\", src, \"remote image skipped (use remoteImages: \\\"link\\\")\");\n return null;\n }\n relId = this.rels.add(REL.image, src, true);\n linked = true;\n } else {\n this.drop(\"image\", src || \"image\", \"image source is neither a data URL nor http(s)\");\n return null;\n }\n let width = pixelAttr(node.attrs?.width) ?? natural?.width ?? 400;\n let height = pixelAttr(node.attrs?.height) ?? (natural ? (natural.height * width) / natural.width : 300);\n if (width > MAX_IMAGE_WIDTH_PX) {\n height = (height * MAX_IMAGE_WIDTH_PX) / width;\n width = MAX_IMAGE_WIDTH_PX;\n }\n const cx = pxToEmu(width);\n const cy = pxToEmu(height);\n const id = ++this.docPrId;\n this.report.written.images++;\n const w = new XmlWriter(false);\n w.open(\"w:drawing\").open(\"wp:inline\", { distT: 0, distB: 0, distL: 0, distR: 0 });\n w.empty(\"wp:extent\", { cx, cy }).empty(\"wp:effectExtent\", { l: 0, t: 0, r: 0, b: 0 });\n w.empty(\"wp:docPr\", { id, name: `Picture ${id}`, descr: alt || undefined, title: title || undefined });\n w.open(\"wp:cNvGraphicFramePr\").empty(\"a:graphicFrameLocks\", { noChangeAspect: 1 }).close(\"wp:cNvGraphicFramePr\");\n w.open(\"a:graphic\").open(\"a:graphicData\", { uri: \"http://schemas.openxmlformats.org/drawingml/2006/picture\" });\n w.open(\"pic:pic\").open(\"pic:nvPicPr\").empty(\"pic:cNvPr\", { id: 0, name: `Picture ${id}`, descr: alt || undefined });\n w.empty(\"pic:cNvPicPr\").close(\"pic:nvPicPr\");\n w.open(\"pic:blipFill\").empty(\"a:blip\", linked ? { \"r:link\": relId } : { \"r:embed\": relId });\n w.open(\"a:stretch\").empty(\"a:fillRect\").close(\"a:stretch\").close(\"pic:blipFill\");\n w.open(\"pic:spPr\").open(\"a:xfrm\").empty(\"a:off\", { x: 0, y: 0 }).empty(\"a:ext\", { cx, cy }).close(\"a:xfrm\");\n w.open(\"a:prstGeom\", { prst: \"rect\" }).empty(\"a:avLst\").close(\"a:prstGeom\").close(\"pic:spPr\");\n w.close(\"pic:pic\").close(\"a:graphicData\").close(\"a:graphic\").close(\"wp:inline\").close(\"w:drawing\");\n return w.toString();\n }\n\n // -- Charts -----------------------------------------------------------------\n\n /** A `chart` node as a paragraph holding a native Word chart (`c:chart` in `wp:inline`). */\n private chartParagraph(w: XmlWriter, node: DocxJSONNode, ctx: BlockContext): void {\n const raw = node.attrs?.spec;\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n this.drop(\"node\", \"chart\", \"chart has no spec\");\n return;\n }\n const spec = normalizeChartSpec(raw).spec;\n const written = chartToOoxml(spec);\n for (const issue of written.issues) this.drop(\"node\", \"chart\", issue);\n const name = `chart${this.charts.length + 1}.xml`;\n this.charts.push(written.xml);\n const relId = this.rels.add(CHART_OOXML_RELATIONSHIP, `charts/${name}`);\n // A chart without a width fills the line (the page's text width).\n let width = pixelAttr(node.attrs?.width) ?? MAX_IMAGE_WIDTH_PX;\n let height = pixelAttr(node.attrs?.height) ?? 320;\n if (width > MAX_IMAGE_WIDTH_PX) {\n height = (height * MAX_IMAGE_WIDTH_PX) / width;\n width = MAX_IMAGE_WIDTH_PX;\n }\n const cx = pxToEmu(width);\n const cy = pxToEmu(height);\n const id = ++this.docPrId;\n const title = spec.title || \"\";\n this.report.written.charts++;\n\n const drawing = new XmlWriter(false);\n drawing.open(\"w:r\").open(\"w:drawing\").open(\"wp:inline\", { distT: 0, distB: 0, distL: 0, distR: 0 });\n drawing.empty(\"wp:extent\", { cx, cy }).empty(\"wp:effectExtent\", { l: 0, t: 0, r: 0, b: 0 });\n drawing.empty(\"wp:docPr\", { id, name: `Chart ${id}`, descr: title || undefined, title: title || undefined });\n drawing.empty(\"wp:cNvGraphicFramePr\");\n drawing.open(\"a:graphic\").open(\"a:graphicData\", { uri: CHART_OOXML_NS });\n drawing.empty(\"c:chart\", { \"xmlns:c\": CHART_OOXML_NS, \"r:id\": relId });\n drawing.close(\"a:graphicData\").close(\"a:graphic\").close(\"wp:inline\").close(\"w:drawing\").close(\"w:r\");\n\n this.report.written.paragraphs++;\n w.open(\"w:p\");\n const indent = ctx.quoteLevel * INDENT_STEP;\n const jc = alignment(node.attrs?.textAlign);\n if (indent || jc) {\n w.open(\"w:pPr\");\n if (indent) w.empty(\"w:ind\", { \"w:left\": indent });\n if (jc) w.val(\"w:jc\", jc);\n w.close(\"w:pPr\");\n }\n // The chart carries the comment marks of an atom node, if any.\n this.commentStarts(w, node.marks ?? []);\n w.raw(drawing.toString());\n this.commentEndsAfter(w, node);\n w.close(\"w:p\");\n }\n\n // -- Tables -------------------------------------------------------------------\n\n private table(w: XmlWriter, node: DocxJSONNode, ctx: BlockContext): void {\n type Slot = { cell: DocxJSONNode; rowOffset: number; colOffset: number; colspan: number; rowspan: number } | null;\n const rows = (node.content ?? []).filter((row) => row.type === \"tableRow\" || row.content?.length);\n if (!rows.length) {\n this.drop(\"node\", \"table\", \"empty table\");\n return;\n }\n const grid: Slot[][] = rows.map(() => []);\n rows.forEach((row, r) => {\n let col = 0;\n for (const cell of row.content ?? []) {\n while (grid[r][col]) col++;\n const colspan = Math.max(1, Math.min(63, Number(cell.attrs?.colspan) || 1));\n const rowspan = Math.max(1, Math.min(rows.length - r, Number(cell.attrs?.rowspan) || 1));\n for (let i = 0; i < rowspan; i++) {\n for (let j = 0; j < colspan; j++) {\n grid[r + i][col + j] = { cell, rowOffset: i, colOffset: j, colspan, rowspan };\n }\n }\n col += colspan;\n }\n });\n const cols = Math.max(1, Math.min(63, ...grid.map((row) => row.length)));\n const widths: number[] = [];\n for (let c = 0; c < cols; c++) {\n const slot = grid[0][c];\n const colwidth = slot?.cell.attrs?.colwidth;\n const px = Array.isArray(colwidth) ? Number(colwidth[slot!.colOffset]) : NaN;\n widths.push(Number.isFinite(px) && px > 0 ? pxToTwips(px) : 0);\n }\n const known = widths.reduce((n, v) => n + v, 0);\n const unknown = widths.filter((v) => !v).length;\n const fill = unknown ? Math.max(360, Math.floor(Math.max(0, CONTENT_WIDTH_TWIPS - known) / unknown)) : 0;\n for (let c = 0; c < cols; c++) if (!widths[c]) widths[c] = fill;\n\n this.report.written.tables++;\n w.open(\"w:tbl\").open(\"w:tblPr\");\n w.empty(\"w:tblW\", { \"w:w\": 0, \"w:type\": \"auto\" });\n w.open(\"w:tblBorders\");\n for (const side of [\"top\", \"left\", \"bottom\", \"right\", \"insideH\", \"insideV\"]) {\n w.empty(`w:${side}`, { \"w:val\": \"single\", \"w:sz\": 4, \"w:space\": 0, \"w:color\": \"auto\" });\n }\n w.close(\"w:tblBorders\");\n w.empty(\"w:tblLook\", { \"w:val\": \"04A0\", \"w:firstRow\": 1, \"w:lastRow\": 0, \"w:firstColumn\": 1, \"w:lastColumn\": 0, \"w:noHBand\": 0, \"w:noVBand\": 1 });\n w.close(\"w:tblPr\");\n w.open(\"w:tblGrid\");\n widths.forEach((width) => w.empty(\"w:gridCol\", { \"w:w\": width }));\n w.close(\"w:tblGrid\");\n const cellCtx: BlockContext = { depth: ctx.depth + 1, quoteLevel: 0 };\n rows.forEach((row, r) => {\n w.open(\"w:tr\");\n const cells = row.content ?? [];\n if (cells.length && cells.every((cell) => cell.type === \"tableHeader\")) {\n w.open(\"w:trPr\").empty(\"w:tblHeader\").close(\"w:trPr\");\n }\n for (let c = 0; c < cols; c++) {\n const slot = grid[r][c];\n if (slot && slot.colOffset > 0) continue;\n const span = slot?.colspan ?? 1;\n const width = widths.slice(c, c + span).reduce((n, v) => n + v, 0);\n w.open(\"w:tc\").open(\"w:tcPr\").empty(\"w:tcW\", { \"w:w\": width, \"w:type\": \"dxa\" });\n if (span > 1) w.val(\"w:gridSpan\", span);\n if (slot && slot.rowspan > 1) {\n if (slot.rowOffset === 0) w.val(\"w:vMerge\", \"restart\");\n else w.empty(\"w:vMerge\");\n }\n w.close(\"w:tcPr\");\n const content = slot && slot.rowOffset === 0 ? (slot.cell.content ?? []) : [];\n if (content.length) this.blocks(w, content, cellCtx);\n // A cell must end with a paragraph.\n const lastType = content[content.length - 1]?.type;\n if (!content.length || lastType === \"table\") w.empty(\"w:p\");\n w.close(\"w:tc\");\n }\n w.close(\"w:tr\");\n });\n w.close(\"w:tbl\");\n }\n\n // -- Other parts --------------------------------------------------------------\n\n numberingXml(): string | null {\n if (!this.nums.length) return null;\n const w = new XmlWriter();\n w.open(\"w:numbering\", BODY_NS);\n const abstract = (id: number, kind: \"bullet\" | \"decimal\" | \"task\") => {\n w.open(\"w:abstractNum\", { \"w:abstractNumId\": id }).val(\"w:multiLevelType\", \"hybridMultilevel\");\n for (let level = 0; level < 9; level++) {\n w.open(\"w:lvl\", { \"w:ilvl\": level }).val(\"w:start\", 1);\n if (kind === \"decimal\") {\n const formats = [\"decimal\", \"lowerLetter\", \"lowerRoman\"];\n w.val(\"w:numFmt\", formats[level % 3]).val(\"w:lvlText\", `%${level + 1}.`);\n } else if (kind === \"bullet\") {\n w.val(\"w:numFmt\", \"bullet\").val(\"w:lvlText\", BULLETS[level % 3]);\n } else {\n w.val(\"w:numFmt\", \"none\").val(\"w:lvlText\", \"\");\n }\n w.val(\"w:lvlJc\", \"left\");\n w.open(\"w:pPr\").empty(\"w:ind\", { \"w:left\": (level + 1) * INDENT_STEP, \"w:hanging\": kind === \"task\" ? 0 : 360 }).close(\"w:pPr\");\n if (kind === \"bullet\") {\n const font = BULLET_FONTS[level % 3] === \"Symbol\" ? \"Arial\" : BULLET_FONTS[level % 3];\n w.open(\"w:rPr\").empty(\"w:rFonts\", { \"w:ascii\": font, \"w:hAnsi\": font, \"w:hint\": \"default\" }).close(\"w:rPr\");\n }\n w.close(\"w:lvl\");\n }\n w.close(\"w:abstractNum\");\n };\n abstract(1, \"bullet\");\n abstract(2, \"decimal\");\n abstract(3, \"task\");\n for (const num of this.nums) {\n w.open(\"w:num\", { \"w:numId\": num.numId }).val(\"w:abstractNumId\", num.abstractId);\n if (num.start !== undefined) {\n w.open(\"w:lvlOverride\", { \"w:ilvl\": num.level }).val(\"w:startOverride\", num.start).close(\"w:lvlOverride\");\n }\n w.close(\"w:num\");\n }\n w.close(\"w:numbering\");\n return w.toString();\n }\n\n stylesXml(): string {\n const w = new XmlWriter();\n w.open(\"w:styles\", BODY_NS);\n const defaults = this.styles.defaults;\n const defaultRun = characterFormatToRun(defaults.character);\n w.open(\"w:docDefaults\").open(\"w:rPrDefault\").open(\"w:rPr\");\n const font = defaultRun.font ?? \"Calibri\";\n w.empty(\"w:rFonts\", { \"w:ascii\": font, \"w:hAnsi\": font, \"w:eastAsia\": font, \"w:cs\": font });\n const size = defaultRun.halfPoints ?? 22;\n w.val(\"w:sz\", size).val(\"w:szCs\", size);\n w.val(\"w:lang\", \"en-US\");\n w.close(\"w:rPr\").close(\"w:rPrDefault\");\n w.open(\"w:pPrDefault\").open(\"w:pPr\");\n if (defaults.paragraph && Object.keys(defaults.paragraph).length) writeParagraphFormat(w, defaults.paragraph);\n else w.empty(\"w:spacing\", { \"w:after\": 160, \"w:line\": 259, \"w:lineRule\": \"auto\" });\n w.close(\"w:pPr\").close(\"w:pPrDefault\").close(\"w:docDefaults\");\n\n const byId = new Map(this.styles.styles.filter((s) => isValidStyleId(s.id)).map((s) => [s.id, s]));\n const written = new Set<string>();\n const blockOf = (style: StyleDefinition): StyleDefinition[\"block\"] => {\n let current: StyleDefinition | undefined = style;\n for (let i = 0; current && i < 32; i++) {\n if (current.block) return current.block;\n current = current.basedOn ? byId.get(current.basedOn) : undefined;\n }\n return null;\n };\n const writeStyle = (style: StyleDefinition) => {\n if (written.has(style.id)) return;\n written.add(style.id);\n const isNormal = style.kind === \"paragraph\" && style.id === NORMAL_STYLE_ID;\n w.open(\"w:style\", {\n \"w:type\": style.kind === \"character\" ? \"character\" : \"paragraph\",\n \"w:default\": isNormal ? 1 : undefined,\n \"w:customStyle\": style.builtin ? undefined : 1,\n \"w:styleId\": style.id,\n });\n w.val(\"w:name\", fileStyleName(style.name || style.id));\n const base = style.basedOn && byId.has(style.basedOn) && byId.get(style.basedOn)!.kind === style.kind ? style.basedOn : undefined;\n if (base) w.val(\"w:basedOn\", base);\n if (style.kind === \"paragraph\" && style.next && byId.get(style.next)?.kind === \"paragraph\") w.val(\"w:next\", style.next);\n if (style.linked && byId.has(style.linked)) w.val(\"w:link\", style.linked);\n if (style.priority !== undefined && Number.isFinite(style.priority)) w.val(\"w:uiPriority\", Math.max(0, Math.round(style.priority)));\n if (style.hidden) w.empty(\"w:semiHidden\").empty(\"w:unhideWhenUsed\");\n if (style.quick) w.empty(\"w:qFormat\");\n const block = style.kind === \"paragraph\" ? blockOf(style) : null;\n const ownBlock = style.kind === \"paragraph\" ? style.block : null;\n const outline = ownBlock?.type === \"heading\" ? Math.max(0, Math.min(8, (Number(ownBlock.attrs?.level) || 1) - 1)) : undefined;\n if (style.kind === \"paragraph\" && (style.paragraph || outline !== undefined)) {\n const p = new XmlWriter(false);\n writeParagraphFormat(p, style.paragraph, outline);\n const xml = p.toString();\n if (xml) w.open(\"w:pPr\").raw(xml).close(\"w:pPr\");\n }\n const run = characterFormatToRun(style.character);\n if (block?.type === \"codeBlock\" && !run.font) run.font = \"Consolas\";\n if (hasRunProps(run)) {\n w.open(\"w:rPr\");\n writeRunProps(w, run);\n w.close(\"w:rPr\");\n }\n w.close(\"w:style\");\n };\n // Base styles first, so Word never sees a `basedOn` before its target.\n const visit = (style: StyleDefinition, depth = 0) => {\n if (depth > 32 || written.has(style.id)) return;\n const base = style.basedOn ? byId.get(style.basedOn) : undefined;\n if (base) visit(base, depth + 1);\n writeStyle(style);\n };\n byId.forEach((style) => visit(style));\n for (const id of this.extraStyles) {\n if (written.has(id)) continue;\n const extra = EXTRA_STYLES[id] ?? { id, name: id, kind: \"paragraph\" as const, basedOn: NORMAL_STYLE_ID };\n byId.set(id, extra);\n writeStyle(extra);\n }\n w.close(\"w:styles\");\n return w.toString();\n }\n\n settingsXml(): string {\n const w = new XmlWriter();\n w.open(\"w:settings\", BODY_NS).empty(\"w:zoom\", { \"w:percent\": 100 });\n if (this.anonymous) w.empty(\"w:removePersonalInformation\");\n w.val(\"w:defaultTabStop\", 720);\n w.val(\"w:characterSpacingControl\", \"doNotCompress\");\n w.open(\"w:compat\").empty(\"w:compatSetting\", {\n \"w:name\": \"compatibilityMode\",\n \"w:uri\": \"http://schemas.microsoft.com/office/word\",\n \"w:val\": 15,\n });\n w.close(\"w:compat\").close(\"w:settings\");\n return w.toString();\n }\n\n commentParts(): { comments: string; extended: string } | null {\n if (!this.comments.size) return null;\n const w = new XmlWriter();\n const ext = new XmlWriter();\n w.open(\"w:comments\", BODY_NS);\n ext.open(\"w15:commentsEx\", { \"xmlns:w15\": NS.w15, \"xmlns:mc\": NS.mc, \"mc:Ignorable\": \"w15\" });\n for (const entry of this.comments.values()) {\n if (!entry.started) continue;\n const { thread } = entry;\n const parentPara = this.comment(w, entry.ids[0], thread.author, thread.createdAt, thread.body);\n ext.empty(\"w15:commentEx\", { \"w15:paraId\": parentPara, \"w15:done\": thread.resolved ? 1 : 0 });\n this.report.written.comments++;\n thread.replies.forEach((reply, i) => {\n const para = this.comment(w, entry.ids[i + 1], reply.author, reply.createdAt, reply.body);\n ext.empty(\"w15:commentEx\", { \"w15:paraId\": para, \"w15:paraIdParent\": parentPara, \"w15:done\": thread.resolved ? 1 : 0 });\n this.report.written.replies++;\n });\n }\n w.close(\"w:comments\");\n ext.close(\"w15:commentsEx\");\n return { comments: w.toString(), extended: ext.toString() };\n }\n\n private comment(w: XmlWriter, id: number, author: ReviewAuthor | undefined, createdAt: number, body: string): string {\n const name = this.authorName(author?.name, author?.id);\n w.open(\"w:comment\", {\n \"w:id\": id,\n \"w:author\": name,\n \"w:date\": isoDate(createdAt),\n \"w:initials\": this.anonymous ? ANONYMOUS_INITIALS : initialsOf(name),\n });\n const lines = String(body ?? \"\").replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n let paraId = \"\";\n lines.forEach((line, i) => {\n const last = i === lines.length - 1;\n if (last) paraId = (this.paraIdCounter++).toString(16).toUpperCase().padStart(8, \"0\");\n w.open(\"w:p\", last ? { \"w14:paraId\": paraId, \"w14:textId\": \"77777777\" } : undefined);\n w.open(\"w:pPr\").val(\"w:pStyle\", \"CommentText\").close(\"w:pPr\");\n if (i === 0) {\n w.open(\"w:r\").open(\"w:rPr\").val(\"w:rStyle\", \"CommentReference\").close(\"w:rPr\").empty(\"w:annotationRef\").close(\"w:r\");\n }\n if (line) w.open(\"w:r\").leaf(\"w:t\", { \"xml:space\": \"preserve\" }, line).close(\"w:r\");\n w.close(\"w:p\");\n });\n w.close(\"w:comment\");\n return paraId;\n }\n\n peopleXml(): string | null {\n if (this.anonymous || !this.people.size) return null;\n const w = new XmlWriter();\n w.open(\"w15:people\", { \"xmlns:w15\": NS.w15, \"xmlns:mc\": NS.mc, \"mc:Ignorable\": \"w15\" });\n for (const [name, id] of this.people) {\n w.open(\"w15:person\", { \"w15:author\": name });\n w.empty(\"w15:presenceInfo\", { \"w15:providerId\": \"None\", \"w15:userId\": id });\n w.close(\"w15:person\");\n }\n w.close(\"w15:people\");\n return w.toString();\n }\n\n coreXml(now: Date): string {\n const w = new XmlWriter();\n w.open(\"cp:coreProperties\", {\n \"xmlns:cp\": \"http://schemas.openxmlformats.org/package/2006/metadata/core-properties\",\n \"xmlns:dc\": \"http://purl.org/dc/elements/1.1/\",\n \"xmlns:dcterms\": \"http://purl.org/dc/terms/\",\n \"xmlns:dcmitype\": \"http://purl.org/dc/dcmitype/\",\n \"xmlns:xsi\": \"http://www.w3.org/2001/XMLSchema-instance\",\n });\n if (this.options.title) w.leaf(\"dc:title\", undefined, this.options.title);\n const creator = this.anonymous ? undefined : this.defaultAuthor?.name;\n if (creator) w.leaf(\"dc:creator\", undefined, creator).leaf(\"cp:lastModifiedBy\", undefined, creator);\n const date = isoDate(now.getTime()) ?? \"2026-01-01T00:00:00Z\";\n w.leaf(\"dcterms:created\", { \"xsi:type\": \"dcterms:W3CDTF\" }, date);\n w.leaf(\"dcterms:modified\", { \"xsi:type\": \"dcterms:W3CDTF\" }, date);\n w.close(\"cp:coreProperties\");\n return w.toString();\n }\n\n /** Assembles every part; `[Content_Types].xml` first. */\n parts(now: Date): DocxPackagePart[] {\n const document = this.documentXml();\n const parts: DocxPackagePart[] = [];\n const overrides: [string, string][] = [];\n const add = (name: string, data: string, contentType: string) => {\n parts.push({ name, data });\n overrides.push([`/${name}`, contentType]);\n };\n this.rels.add(REL.styles, \"styles.xml\");\n this.rels.add(REL.settings, \"settings.xml\");\n const numbering = this.numberingXml();\n if (numbering) this.rels.add(REL.numbering, \"numbering.xml\");\n const comments = this.commentParts();\n if (comments) {\n this.rels.add(REL.comments, \"comments.xml\");\n this.rels.add(REL.commentsExtended, \"commentsExtended.xml\");\n }\n const people = this.peopleXml();\n if (people) this.rels.add(REL.people, \"people.xml\");\n // Styles last: the document and comments decide which extra styles are needed.\n const styles = this.stylesXml();\n\n add(\"word/document.xml\", document, `${CT_WML}document.main+xml`);\n add(\"word/styles.xml\", styles, `${CT_WML}styles+xml`);\n add(\"word/settings.xml\", this.settingsXml(), `${CT_WML}settings+xml`);\n if (numbering) add(\"word/numbering.xml\", numbering, `${CT_WML}numbering+xml`);\n if (comments) {\n add(\"word/comments.xml\", comments.comments, `${CT_WML}comments+xml`);\n add(\"word/commentsExtended.xml\", comments.extended, `${CT_WML}commentsExtended+xml`);\n }\n if (people) add(\"word/people.xml\", people, `${CT_WML}people+xml`);\n parts.push({ name: \"word/_rels/document.xml.rels\", data: this.rels.xml() });\n for (const file of this.media) parts.push({ name: `word/media/${file.name}`, data: file.bytes });\n this.charts.forEach((xml, i) => add(`word/charts/chart${i + 1}.xml`, xml, CHART_OOXML_CONTENT_TYPE));\n add(\"docProps/core.xml\", this.coreXml(now), \"application/vnd.openxmlformats-package.core-properties+xml\");\n const app = new XmlWriter();\n app.open(\"Properties\", { xmlns: \"http://schemas.openxmlformats.org/officeDocument/2006/extended-properties\" });\n app.leaf(\"Application\", undefined, \"@pixerate/editor\").close(\"Properties\");\n add(\"docProps/app.xml\", app.toString(), \"application/vnd.openxmlformats-officedocument.extended-properties+xml\");\n\n const root = new Relationships();\n root.add(REL.officeDocument, \"word/document.xml\");\n root.add(REL.core, \"docProps/core.xml\");\n root.add(REL.app, \"docProps/app.xml\");\n\n const types = new XmlWriter();\n types.open(\"Types\", { xmlns: \"http://schemas.openxmlformats.org/package/2006/content-types\" });\n types.empty(\"Default\", { Extension: \"rels\", ContentType: \"application/vnd.openxmlformats-package.relationships+xml\" });\n types.empty(\"Default\", { Extension: \"xml\", ContentType: \"application/xml\" });\n for (const ext of this.mediaTypes) types.empty(\"Default\", { Extension: ext, ContentType: DOCX_IMAGE_CONTENT_TYPES[ext] });\n for (const [name, contentType] of overrides) types.empty(\"Override\", { PartName: name, ContentType: contentType });\n types.close(\"Types\");\n return [\n { name: \"[Content_Types].xml\", data: types.toString() },\n { name: \"_rels/.rels\", data: root.xml() },\n ...parts,\n ];\n }\n}\n\nconst SPARK_BARS = \"▁▂▃▄▅▆▇█\";\n\n/** A sparkline as text: its label, else one block character per value (▁ lowest, █ highest). */\nfunction sparklineText(attrs: Record<string, any> | undefined): string {\n if (typeof attrs?.label === \"string\" && attrs.label.trim()) return attrs.label;\n const values: number[] = (Array.isArray(attrs?.values) ? attrs.values : []).filter(\n (v: unknown): v is number => typeof v === \"number\" && Number.isFinite(v),\n );\n if (!values.length) return \"\";\n const min = Math.min(...values);\n const span = Math.max(...values) - min;\n return values.map((v) => SPARK_BARS[span ? Math.round(((v - min) / span) * (SPARK_BARS.length - 1)) : 3]).join(\"\");\n}\n\nconst isInlineType = (type: string | undefined) =>\n type === \"hardBreak\" || type === \"image\" || type === \"mention\" || type === \"sparkline\" || type === \"text\";\n\n/** Styles written when the document uses them but the registry has no definition. */\nconst EXTRA_STYLES: Record<string, StyleDefinition> = {\n BlockText: {\n id: \"BlockText\",\n name: \"Block Text\",\n kind: \"paragraph\",\n basedOn: NORMAL_STYLE_ID,\n paragraph: { indentLeft: \"0.5in\", indentRight: \"0.5in\" },\n character: { italic: true },\n priority: 99,\n builtin: true,\n },\n ListParagraph: {\n id: \"ListParagraph\",\n name: \"List Paragraph\",\n kind: \"paragraph\",\n basedOn: NORMAL_STYLE_ID,\n paragraph: { indentLeft: \"0.5in\" },\n priority: 34,\n quick: true,\n builtin: true,\n },\n Code: {\n id: \"Code\",\n name: \"Code\",\n kind: \"paragraph\",\n basedOn: NORMAL_STYLE_ID,\n block: { type: \"codeBlock\" },\n character: { fontFamily: \"Consolas\", fontSize: \"10pt\" },\n paragraph: { spaceAfter: \"0pt\", lineHeight: \"1\" },\n priority: 30,\n },\n Hyperlink: {\n id: \"Hyperlink\",\n name: \"Hyperlink\",\n kind: \"character\",\n character: { color: \"#0563C1\", underline: true },\n priority: 99,\n hidden: true,\n builtin: true,\n },\n CommentReference: {\n id: \"CommentReference\",\n name: \"annotation reference\",\n kind: \"character\",\n character: { fontSize: \"8pt\" },\n priority: 99,\n hidden: true,\n builtin: true,\n },\n CommentText: {\n id: \"CommentText\",\n name: \"annotation text\",\n kind: \"paragraph\",\n basedOn: NORMAL_STYLE_ID,\n character: { fontSize: \"10pt\" },\n priority: 99,\n hidden: true,\n builtin: true,\n },\n};\nfor (let level = 1; level <= 6; level++) {\n EXTRA_STYLES[`Heading${level}`] = {\n id: `Heading${level}`,\n name: `Heading ${level}`,\n kind: \"paragraph\",\n basedOn: NORMAL_STYLE_ID,\n next: NORMAL_STYLE_ID,\n block: { type: \"heading\", attrs: { level } },\n character: { bold: true },\n priority: 9,\n quick: true,\n builtin: true,\n };\n}\n\nfunction resolveInput(\n input: DocxExportEditorLike | DocxJSONNode,\n options: DocxExportOptions,\n): { doc: DocxJSONNode; options: DocxExportOptions } {\n if (!isEditorLike(input)) return { doc: input, options };\n const storage = (input.storage ?? {}) as any;\n return {\n doc: input.getJSON(),\n options: {\n ...options,\n comments: options.comments !== undefined ? options.comments : (storage.comment?.store ?? null),\n styles: options.styles ?? storage.paragraphStyles?.registry ?? undefined,\n },\n };\n}\n\n/**\n * Builds the package parts of a DOCX file without zipping them (synchronous;\n * useful for inspection and tests). See {@link exportDocx}.\n */\nexport function createDocxParts(\n input: DocxExportEditorLike | DocxJSONNode,\n options: DocxExportOptions = {},\n): { parts: DocxPackagePart[]; report: DocxExportReport } {\n const resolved = resolveInput(input, options);\n const builder = new DocxBuilder(\n resolved.doc ?? { type: \"doc\", content: [] },\n resolved.options,\n threadsOf(resolved.options.comments),\n stylesOf(resolved.options.styles),\n );\n const parts = builder.parts(resolved.options.now?.() ?? new Date());\n return { parts, report: builder.report };\n}\n\n/**\n * Exports an editor (or its JSON) as a Word document. Resolves with the file\n * bytes and a report of what was dropped. Never fetches anything: remote\n * images are skipped (or linked with `remoteImages: \"link\"`).\n */\nexport async function exportDocx(\n input: DocxExportEditorLike | DocxJSONNode,\n options: DocxExportOptions = {},\n): Promise<DocxExportResult> {\n const { parts, report } = createDocxParts(input, options);\n const data = await writeZip(parts as ZipWriteEntry[], { compression: options.compression });\n return { data, report };\n}\n\n/** Like {@link exportDocx}, but resolves with a `Blob` of type {@link DOCX_EXPORT_MIME_TYPE}. */\nexport async function exportDocxBlob(\n input: DocxExportEditorLike | DocxJSONNode,\n options: DocxExportOptions = {},\n): Promise<{ blob: Blob; report: DocxExportReport }> {\n const { data, report } = await exportDocx(input, options);\n return { blob: new Blob([data as BlobPart], { type: DOCX_EXPORT_MIME_TYPE }), report };\n}\n","import {\n exportDocxBlob,\n type DocxExportEditorLike,\n type DocxExportOptions,\n type DocxExportReport,\n type DocxJSONNode,\n} from \"./docx\";\n\n/**\n * Saves a `Blob` as a file through a temporary `<a download>` link. Does\n * nothing (returns `false`) without a DOM.\n */\nexport function downloadBlob(blob: Blob, fileName: string): boolean {\n if (typeof document === \"undefined\" || typeof URL === \"undefined\" || typeof URL.createObjectURL !== \"function\") {\n return false;\n }\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = fileName;\n link.rel = \"noopener\";\n link.style.display = \"none\";\n document.body.appendChild(link);\n link.click();\n link.remove();\n // Revoke after the click has been handled, or some browsers cancel the download.\n setTimeout(() => URL.revokeObjectURL(url), 0);\n return true;\n}\n\n/** `name.docx`, with characters that are invalid in file names replaced. */\nexport function docxFileName(name: string | undefined): string {\n const base = (name ?? \"\")\n .replace(/[\\u0000-\\u001f\\u007f<>:\"/\\\\|?*]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n .replace(/\\.docx$/i, \"\")\n .slice(0, 120);\n return `${base || \"document\"}.docx`;\n}\n\nexport interface DocxExportState {\n isExporting: boolean;\n /** Last export error (cleared when a new export starts). */\n error: Error | null;\n /** Report of the most recent successful export. */\n lastReport: DocxExportReport | null;\n}\n\nexport interface DocxExportControllerOptions extends DocxExportOptions {\n /** Download file name (default: `title`, else `document`, plus `.docx`). */\n fileName?: string;\n /** Called after each successful export with its report. */\n onExport?: (report: DocxExportReport, blob: Blob) => void;\n /** Called when an export fails (the error is also kept in the state). */\n onError?: (error: Error) => void;\n}\n\n/**\n * Framework-agnostic state for an \"Export to Word\" button: tracks\n * `isExporting` / `error` / `lastReport` and downloads the `.docx` of the\n * editor returned by `getSource`. React's `useDocxExport` and Svelte's\n * `createDocxExport` wrap it.\n */\nexport class DocxExportController {\n private state: DocxExportState = { isExporting: false, error: null, lastReport: null };\n private readonly listeners = new Set<(state: DocxExportState) => void>();\n private pending = 0;\n\n constructor(\n private readonly getSource: () => DocxExportEditorLike | DocxJSONNode | null | undefined,\n private options: DocxExportControllerOptions = {},\n ) {}\n\n setOptions(options: DocxExportControllerOptions): void {\n this.options = options;\n }\n\n getState = (): DocxExportState => this.state;\n\n subscribe = (listener: (state: DocxExportState) => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n private setState(patch: Partial<DocxExportState>): void {\n this.state = { ...this.state, ...patch };\n this.listeners.forEach((listener) => listener(this.state));\n }\n\n /** Builds the `.docx` without downloading it. Resolves with `null` when there is no editor or it fails. */\n async exportBlob(): Promise<{ blob: Blob; report: DocxExportReport } | null> {\n const source = this.getSource();\n if (!source) return null;\n this.pending++;\n this.setState({ isExporting: true, error: null });\n try {\n const { fileName: _fileName, onExport: _onExport, onError: _onError, ...exportOptions } = this.options;\n const result = await exportDocxBlob(source, exportOptions);\n this.setState({ lastReport: result.report });\n this.options.onExport?.(result.report, result.blob);\n return result;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.setState({ error: err });\n if (this.options.onError) this.options.onError(err);\n else console.error(\"[Editor] DOCX export failed:\", err);\n return null;\n } finally {\n this.pending--;\n if (this.pending === 0) this.setState({ isExporting: false });\n }\n }\n\n /** Exports and downloads the document. Resolves with the report, or `null`. */\n async download(fileName?: string): Promise<DocxExportReport | null> {\n const result = await this.exportBlob();\n if (!result) return null;\n downloadBlob(result.blob, docxFileName(fileName ?? this.options.fileName ?? this.options.title));\n return result.report;\n }\n}\n\nexport function createDocxExportController(\n getSource: () => DocxExportEditorLike | DocxJSONNode | null | undefined,\n options?: DocxExportControllerOptions,\n): DocxExportController {\n return new DocxExportController(getSource, options);\n}\n"]}