@braincrew-lab/langchain-canvas 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  "use client";
2
2
  import { loadOptional } from './chunk-YZZSJJMQ.js';
3
3
  import { resolveElements } from './chunk-6L3AL6W4.js';
4
- export { useArtifactPatch } from './chunk-UL5F66PN.js';
5
- import { useCanvasStoreApi, useCanvasStore } from './chunk-S54GJDSJ.js';
6
- export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-S54GJDSJ.js';
7
- import { createContext, lazy, useRef, useCallback, useEffect, useContext, useState, useMemo, Suspense, Component } from 'react';
4
+ export { useArtifactPatch } from './chunk-FSFOURG5.js';
5
+ import { useCanvasStoreApi, useCanvasStore } from './chunk-ZLXAWRUP.js';
6
+ export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-ZLXAWRUP.js';
7
+ import { useT, CanvasLocaleProvider } from './chunk-QMOJEGRH.js';
8
+ export { CanvasLocaleProvider, useLocale, useT } from './chunk-QMOJEGRH.js';
9
+ import { createContext, lazy, useRef, useCallback, useEffect, useMemo, useContext, useState, Suspense, Component } from 'react';
8
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
11
 
10
12
  // src/client/sse-client.ts
@@ -850,6 +852,39 @@ function useCanvasReplay() {
850
852
  const reset = useCallback(() => api.getState().reset(), [api]);
851
853
  return { play, stop, reset, canvas, isPlaying };
852
854
  }
855
+ var DEFAULT_DEBOUNCE_MS = 800;
856
+ function useCanvasSave(onSave, debounceMs = DEFAULT_DEBOUNCE_MS) {
857
+ const timers = useRef(/* @__PURE__ */ new Map());
858
+ const latest = useRef(/* @__PURE__ */ new Map());
859
+ const handler = useRef(onSave);
860
+ handler.current = onSave;
861
+ const enabled = Boolean(onSave);
862
+ useEffect(() => {
863
+ const pending = timers.current;
864
+ return () => pending.forEach((t) => clearTimeout(t));
865
+ }, []);
866
+ return useMemo(() => {
867
+ if (!enabled) return null;
868
+ return (artifact2) => {
869
+ latest.current.set(artifact2.id, artifact2);
870
+ const existing = timers.current.get(artifact2.id);
871
+ if (existing) clearTimeout(existing);
872
+ timers.current.set(
873
+ artifact2.id,
874
+ setTimeout(() => {
875
+ timers.current.delete(artifact2.id);
876
+ const current = latest.current.get(artifact2.id);
877
+ if (!current || !handler.current) return;
878
+ void handler.current({
879
+ artifactId: current.id,
880
+ artifact: current,
881
+ baseRevision: typeof current.meta?.revision === "string" ? current.meta.revision : null
882
+ });
883
+ }, debounceMs)
884
+ );
885
+ };
886
+ }, [enabled, debounceMs]);
887
+ }
853
888
 
854
889
  // src/fixtures/scenarios.ts
855
890
  var PRICING_HTML = `<!doctype html>
@@ -1021,6 +1056,31 @@ var slides = {
1021
1056
  { type: "done" }
1022
1057
  ]
1023
1058
  };
1059
+ var VERSIONED_HTML = `<!doctype html><html><body style="font-family:sans-serif;padding:40px">
1060
+ <h1>Coffee history</h1><p>From the Ethiopian highlands to the espresso bar.</p>
1061
+ </body></html>`;
1062
+ var versions = {
1063
+ id: "versions",
1064
+ title: "Version history",
1065
+ description: "An agent builds a page, a user edit and an agent edit each land as described commits \u2014 open the version rail to browse and restore-view snapshots.",
1066
+ events: [
1067
+ { type: "message.delta", messageId: "m1", text: "Built the page \u2014 every change now lands in the version history." },
1068
+ { type: "message.end", messageId: "m1" },
1069
+ {
1070
+ type: "canvas.create",
1071
+ artifact: { id: "page", type: "html", title: "Coffee history", version: 1, status: "streaming", data: { html: VERSIONED_HTML } }
1072
+ },
1073
+ { type: "canvas.status", id: "page", status: "complete" },
1074
+ { type: "canvas.commit", id: "page", description: "Create page", revision: "v1" },
1075
+ // A human tweaks the headline by hand, then saves — one described commit.
1076
+ { type: "canvas.patch", id: "page", patch: { html: VERSIONED_HTML.replace("Coffee history", "A short history of coffee") } },
1077
+ { type: "canvas.commit", id: "page", description: "Manual edit: 1 change", revision: "v2" },
1078
+ // The agent applies a targeted follow-up edit on the current state.
1079
+ { type: "canvas.patch", id: "page", patch: { html: VERSIONED_HTML.replace("Coffee history", "A short history of coffee").replace("espresso bar", "third-wave caf\xE9") } },
1080
+ { type: "canvas.commit", id: "page", description: "Update closing phrase", revision: "v3" },
1081
+ { type: "done" }
1082
+ ]
1083
+ };
1024
1084
  var PDF_DATA_URL = "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgNjEyIDc5Ml0vQ29udGVudHMgNCAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUgMCBSPj4+Pj4+ZW5kb2JqCjQgMCBvYmo8PC9MZW5ndGggODA+PnN0cmVhbQpCVCAvRjEgMjggVGYgNzIgNzAwIFRkIChsYW5nY2hhaW4tY2FudmFzIFBERiB2aWV3ZXIpIFRqIEVUCmVuZHN0cmVhbQplbmRvYmoKNSAwIG9iajw8L1R5cGUvRm9udC9TdWJ0eXBlL1R5cGUxL0Jhc2VGb250L0hlbHZldGljYT4+ZW5kb2JqCnhyZWYKMCA2CnRyYWlsZXI8PC9TaXplIDYvUm9vdCAxIDAgUj4+CiUlRU9GCg==";
1025
1085
  var pdf = {
1026
1086
  id: "pdf",
@@ -1043,7 +1103,60 @@ var pdf = {
1043
1103
  { type: "done" }
1044
1104
  ]
1045
1105
  };
1046
- var scenarios = [htmlPage, document2, chart, table, slides, pdf];
1106
+ var HWP_HTML = `<!doctype html>
1107
+ <html lang="ko"><head><meta charset="utf-8"><style>
1108
+ body{margin:0;background:#eceef1;font-family:"Malgun Gothic","\uB9D1\uC740 \uACE0\uB515","Apple SD Gothic Neo",AppleGothic,sans-serif;color:#1a1a1a}
1109
+ .page{max-width:794px;margin:24px auto;padding:96px 72px;background:#fff;box-shadow:0 2px 14px rgba(0,0,0,.12);line-height:1.7}
1110
+ h1{font-size:22pt;text-align:center;margin:0 0 8px}
1111
+ .stamp{text-align:center;color:#555;font-size:10.5pt;margin:0 0 36px}
1112
+ h2{font-size:14pt;border-bottom:2px solid #2f5597;padding-bottom:4px;margin:28px 0 12px;color:#2f5597}
1113
+ p{margin:0 0 10px;font-size:11pt;text-align:justify}
1114
+ table{border-collapse:collapse;width:100%;margin:12px 0;font-size:10.5pt}
1115
+ th{background:#dbe5f1;border:1px solid #666;padding:7px 10px}
1116
+ td{border:1px solid #666;padding:7px 10px}
1117
+ .sign{margin-top:48px;text-align:right;font-size:12pt}
1118
+ .red{color:#c00000;font-weight:700}
1119
+ </style></head>
1120
+ <body><div class="page">
1121
+ <h1>\uC0AC\uC5C5 \uC218\uD589 \uACC4\uD68D\uC11C</h1>
1122
+ <p class="stamp">\uBB38\uC11C\uBC88\uD638 BC-2026-041 \xB7 2026. 7. 29.</p>
1123
+ <h2>1. \uAC1C\uC694</h2>
1124
+ <p>\uBCF8 \uBB38\uC11C\uB294 <b>\uB300\uD654\uD615 \uC5D0\uC774\uC804\uD2B8 \uBE4C\uB354 \uAD6C\uCD95</b> \uC0AC\uC5C5\uC758 \uC218\uD589 \uACC4\uD68D\uC744 \uC815\uB9AC\uD55C \uAC83\uC785\uB2C8\uB2E4. <span class="red">\uD55C\uAE00(.hwp/.hwpx) \uD30C\uC77C\uC744 \uCE94\uBC84\uC2A4\uC5D0 \uB04C\uC5B4\uB2E4 \uB193\uC73C\uBA74</span> \uC774 \uBB38\uC11C\uCC98\uB7FC <u>\uC11C\uC2DD\uC774 \uBCF4\uC874\uB41C \uD398\uC774\uC9C0</u>\uB85C \uC5F4\uB9BD\uB2C8\uB2E4 \u2014 \uAE00\uAF34 \uD06C\uAE30\xB7\uC0C9\xB7\uC815\uB82C\xB7\uD45C \uD14C\uB450\uB9AC\xB7\uC774\uBBF8\uC9C0\uAE4C\uC9C0.</p>
1125
+ <h2>2. \uCD94\uC9C4 \uC77C\uC815</h2>
1126
+ <table>
1127
+ <tr><th>\uB2E8\uACC4</th><th>\uAE30\uAC04</th><th>\uC0B0\uCD9C\uBB3C</th></tr>
1128
+ <tr><td>\uCC29\uC218</td><td style="text-align:center">2026-07</td><td>\uC0AC\uC5C5\uC218\uD589\uACC4\uD68D\uC11C</td></tr>
1129
+ <tr><td>\uBD84\uC11D/\uC124\uACC4</td><td style="text-align:center">2026-07 ~ 2026-08</td><td>\uC694\uAD6C\uC0AC\uD56D \uC815\uC758\uC11C</td></tr>
1130
+ <tr><td>\uAD6C\uCD95</td><td style="text-align:center">2026-08 ~ 2026-11</td><td>\uAE30\uB2A5 \uAD6C\uD604</td></tr>
1131
+ <tr><td>\uAC80\uC218</td><td style="text-align:center">2026-12</td><td>\uAC80\uC218\uD655\uC778\uC11C</td></tr>
1132
+ </table>
1133
+ <h2>3. \uB0B4\uBCF4\uB0B4\uAE30</h2>
1134
+ <p>\uD3B8\uC9D1\uD55C \uBB38\uC11C\uB294 <b>\uB0B4\uBCF4\uB0B4\uAE30</b> \uBA54\uB274\uC5D0\uC11C PDF\xB7HTML\uB85C \uC800\uC7A5\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC5D0\uC774\uC804\uD2B8\uAC00 \uB9CC\uB4E0 \uBB38\uC11C(document)\uB294 <b>\uD55C\uAE00 (HWPX)</b>\uB85C\uB3C4 \uB0B4\uBCF4\uB0C5\uB2C8\uB2E4.</p>
1135
+ <p class="sign">\uBE0C\uB808\uC778\uD06C\uB8E8 \uC8FC\uC2DD\uD68C\uC0AC</p>
1136
+ </div></body></html>`;
1137
+ var hwp = {
1138
+ id: "hwp",
1139
+ title: "\uD55C\uAE00 (HWP)",
1140
+ description: "A formatted Korean document \u2014 how .hwp/.hwpx files open on the canvas, styling preserved.",
1141
+ events: [
1142
+ { type: "message.delta", messageId: "m-hwp", text: "\uD55C\uAE00 \uD30C\uC77C\uC744 \uC11C\uC2DD \uADF8\uB300\uB85C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4." },
1143
+ {
1144
+ type: "canvas.create",
1145
+ artifact: {
1146
+ id: "hwp-doc",
1147
+ type: "html",
1148
+ title: "\uC0AC\uC5C5 \uC218\uD589 \uACC4\uD68D\uC11C.hwpx",
1149
+ version: 1,
1150
+ status: "complete",
1151
+ meta: { kind: "doc", source: "hwpx" },
1152
+ data: { html: HWP_HTML }
1153
+ }
1154
+ },
1155
+ { type: "canvas.status", id: "hwp-doc", status: "complete" },
1156
+ { type: "done" }
1157
+ ]
1158
+ };
1159
+ var scenarios = [htmlPage, document2, chart, table, slides, pdf, hwp, versions];
1047
1160
  var RegistryContext = createContext({});
1048
1161
  function CanvasRegistryProvider({ registry, children }) {
1049
1162
  return /* @__PURE__ */ jsx(RegistryContext.Provider, { value: registry, children });
@@ -1701,6 +1814,110 @@ async function readZip(buffer) {
1701
1814
  }
1702
1815
  return entries;
1703
1816
  }
1817
+ function parseXml(xml) {
1818
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
1819
+ return doc.querySelector("parsererror") ? null : doc;
1820
+ }
1821
+ var IMAGE_BASE64_BUDGET = 8 * 1024 * 1024;
1822
+ var MIME_BY_EXT = {
1823
+ png: "image/png",
1824
+ jpg: "image/jpeg",
1825
+ jpeg: "image/jpeg",
1826
+ gif: "image/gif",
1827
+ bmp: "image/bmp"
1828
+ };
1829
+ function bytesToBase642(bytes) {
1830
+ let bin = "";
1831
+ for (let i = 0; i < bytes.length; i += 32768) {
1832
+ bin += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + 32768)));
1833
+ }
1834
+ return btoa(bin);
1835
+ }
1836
+ var ImageResolver = class {
1837
+ // ref id → ZIP entry name
1838
+ constructor(entries) {
1839
+ this.entries = entries;
1840
+ this.omitted = 0;
1841
+ this.used = 0;
1842
+ this.index = /* @__PURE__ */ new Map();
1843
+ for (const name of entries.keys()) {
1844
+ const m = /^(?:Contents\/)?BinData\/([^/]+)$/i.exec(name);
1845
+ if (!m) continue;
1846
+ this.index.set(m[1], name);
1847
+ this.index.set(m[1].replace(/\.[^.]+$/, ""), name);
1848
+ }
1849
+ const hpfName = [...entries.keys()].find((n) => /(^|\/)content\.hpf$/i.test(n));
1850
+ const doc = hpfName ? parseXml(new TextDecoder().decode(entries.get(hpfName))) : null;
1851
+ if (!doc) return;
1852
+ for (const item of Array.from(doc.getElementsByTagNameNS("*", "item"))) {
1853
+ const id = item.getAttribute("id");
1854
+ const href = item.getAttribute("href");
1855
+ if (!id || !href) continue;
1856
+ const target = this.entries.has(href) ? href : this.entries.has(`Contents/${href}`) ? `Contents/${href}` : null;
1857
+ if (target) this.index.set(id, target);
1858
+ }
1859
+ }
1860
+ /** Data URL for a binary item id, or null when unresolvable / over budget —
1861
+ * the shared resolution both the markdown and HTML emitters build on. */
1862
+ dataUrl(refId) {
1863
+ const name = this.index.get(refId);
1864
+ const bytes = name ? this.entries.get(name) : void 0;
1865
+ if (!name || !bytes) return null;
1866
+ const base64Length = Math.ceil(bytes.length / 3) * 4;
1867
+ if (this.used + base64Length > IMAGE_BASE64_BUDGET) {
1868
+ this.omitted++;
1869
+ return null;
1870
+ }
1871
+ this.used += base64Length;
1872
+ const ext = /\.([^.]+)$/.exec(name)?.[1]?.toLowerCase() ?? "";
1873
+ return `data:${MIME_BY_EXT[ext] ?? "image/png"};base64,${bytesToBase642(bytes)}`;
1874
+ }
1875
+ /** Markdown image block for a binary item id, or null when unresolvable /
1876
+ * over budget. */
1877
+ markdown(refId) {
1878
+ const url = this.dataUrl(refId);
1879
+ return url ? `![](${url})` : null;
1880
+ }
1881
+ };
1882
+ function binaryItemRef(pic) {
1883
+ const refOf = (el) => {
1884
+ for (const attr of Array.from(el.attributes)) {
1885
+ if (/^(binaryitemidref|bin-item-id|binitemidref)$/i.test(attr.localName)) return attr.value || null;
1886
+ }
1887
+ return null;
1888
+ };
1889
+ const descendants = [pic, ...Array.from(pic.getElementsByTagNameNS("*", "*"))];
1890
+ for (const el of descendants) {
1891
+ if (el.localName === "img") {
1892
+ const ref = refOf(el);
1893
+ if (ref) return ref;
1894
+ }
1895
+ }
1896
+ for (const el of descendants) {
1897
+ const ref = refOf(el);
1898
+ if (ref) return ref;
1899
+ }
1900
+ return null;
1901
+ }
1902
+ var HEADING_STYLE_NAME = /^(개요|Outline|Heading|제목)\s*([1-6])/i;
1903
+ function buildHeadingIndex(entries) {
1904
+ const byStyle = /* @__PURE__ */ new Map();
1905
+ const byParaPr = /* @__PURE__ */ new Map();
1906
+ const name = [...entries.keys()].find((n) => /^Contents\/header\.xml$/i.test(n));
1907
+ const doc = name ? parseXml(new TextDecoder().decode(entries.get(name))) : null;
1908
+ if (doc) {
1909
+ for (const style of Array.from(doc.getElementsByTagNameNS("*", "style"))) {
1910
+ const match = HEADING_STYLE_NAME.exec(style.getAttribute("name") ?? "");
1911
+ if (!match) continue;
1912
+ const level = Number(match[2]);
1913
+ const id = style.getAttribute("id");
1914
+ if (id !== null) byStyle.set(id, level);
1915
+ const paraPr = style.getAttribute("paraPrIDRef");
1916
+ if (paraPr !== null) byParaPr.set(paraPr, level);
1917
+ }
1918
+ }
1919
+ return { byStyle, byParaPr };
1920
+ }
1704
1921
  function paragraphText(p) {
1705
1922
  let out = "";
1706
1923
  const walk = (el) => {
@@ -1715,63 +1932,252 @@ function paragraphText(p) {
1715
1932
  walk(p);
1716
1933
  return out;
1717
1934
  }
1718
- function tableToMarkdown(tbl) {
1719
- const rows = [];
1935
+ async function openHwpx(buffer) {
1936
+ const entries = await readZip(buffer);
1937
+ const names = [...entries.keys()].filter((n) => /^Contents\/section\d+\.xml$/i.test(n)).sort((a, b) => Number(/(\d+)/.exec(a)?.[1] ?? 0) - Number(/(\d+)/.exec(b)?.[1] ?? 0));
1938
+ if (!names.length) {
1939
+ throw new Error("HWPX \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uBCF8\uBB38(section XML)\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (no Contents/section*.xml).");
1940
+ }
1941
+ const decoder = new TextDecoder();
1942
+ return {
1943
+ entries,
1944
+ sections: names.map((n) => decoder.decode(entries.get(n))),
1945
+ headings: buildHeadingIndex(entries),
1946
+ images: new ImageResolver(entries)
1947
+ };
1948
+ }
1949
+ function headingLevelOf(p, headings) {
1950
+ const styleId = p.getAttribute("styleIDRef");
1951
+ if (styleId !== null && headings.byStyle.has(styleId)) return headings.byStyle.get(styleId);
1952
+ const paraPrId = p.getAttribute("paraPrIDRef");
1953
+ return paraPrId !== null && headings.byParaPr.get(paraPrId) || 0;
1954
+ }
1955
+ function escapeHtml(text) {
1956
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1957
+ }
1958
+ var KOREAN_FONT_STACK = "'Malgun Gothic','\uB9D1\uC740 \uACE0\uB515','Apple SD Gothic Neo',AppleGothic,sans-serif";
1959
+ function koreanDocHtml(bodyHtml, title = "\uBB38\uC11C") {
1960
+ return `<!DOCTYPE html>
1961
+ <html lang="ko">
1962
+ <head>
1963
+ <meta charset="utf-8">
1964
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1965
+ <title>${escapeHtml(title)}</title>
1966
+ <style>
1967
+ body{margin:0;padding:32px 16px;background:#eceff1;color:#191919;font-family:${KOREAN_FONT_STACK};line-height:1.6;}
1968
+ .page{max-width:794px;margin:0 auto;background:#fff;padding:96px 72px;box-shadow:0 2px 12px rgba(0,0,0,.12);}
1969
+ .page p{margin:0 0 10px;}
1970
+ .page h1,.page h2,.page h3,.page h4,.page h5,.page h6{margin:18px 0 12px;line-height:1.35;}
1971
+ .page table{border-collapse:collapse;margin:0 0 10px;}
1972
+ .page td,.page th{border:1px solid #666;padding:6px 8px;vertical-align:top;}
1973
+ .page td p:last-child{margin-bottom:0;}
1974
+ .page img{max-width:100%;}
1975
+ </style>
1976
+ </head>
1977
+ <body>
1978
+ <div class="page">
1979
+ ${bodyHtml}
1980
+ </div>
1981
+ </body>
1982
+ </html>`;
1983
+ }
1984
+ var ALIGN_CSS = {
1985
+ LEFT: "left",
1986
+ CENTER: "center",
1987
+ RIGHT: "right",
1988
+ JUSTIFY: "justify",
1989
+ BOTH: "justify",
1990
+ DISTRIBUTE: "justify",
1991
+ DISTRIBUTE_SPACE: "justify"
1992
+ };
1993
+ var HEX_COLOR = /^#[0-9a-f]{6}$/i;
1994
+ var heightToPx = (height) => Math.round(height / 100 * (4 / 3) * 100) / 100;
1995
+ function charPrCss(charPr, faceByLangId) {
1996
+ let bold = false;
1997
+ let italic = false;
1998
+ let underline = false;
1999
+ let face;
2000
+ for (const child of Array.from(charPr.children)) {
2001
+ const tag = child.localName;
2002
+ if (tag === "bold") bold = true;
2003
+ else if (tag === "italic") italic = true;
2004
+ else if (tag === "underline") {
2005
+ underline = (child.getAttribute("type") ?? "SOLID").toUpperCase() !== "NONE";
2006
+ } else if (tag === "fontRef") {
2007
+ const ref = child.getAttribute("hangul") ?? child.getAttribute("latin");
2008
+ if (ref !== null) face = faceByLangId.get(`HANGUL:${ref}`) ?? faceByLangId.get(`LATIN:${ref}`);
2009
+ }
2010
+ }
2011
+ const rules = [];
2012
+ if (face) rules.push(`font-family:'${face.replace(/'/g, "")}',${KOREAN_FONT_STACK}`);
2013
+ const height = Number(charPr.getAttribute("height"));
2014
+ if (Number.isFinite(height) && height > 0) rules.push(`font-size:${heightToPx(height)}px`);
2015
+ const color = charPr.getAttribute("textColor");
2016
+ if (color && HEX_COLOR.test(color) && color.toLowerCase() !== "#000000") rules.push(`color:${color}`);
2017
+ if (bold) rules.push("font-weight:bold");
2018
+ if (italic) rules.push("font-style:italic");
2019
+ if (underline) rules.push("text-decoration:underline");
2020
+ return rules.join(";");
2021
+ }
2022
+ function buildStyleIndex(entries) {
2023
+ const index = { charCss: /* @__PURE__ */ new Map(), paraAlign: /* @__PURE__ */ new Map(), fillColor: /* @__PURE__ */ new Map() };
2024
+ const name = [...entries.keys()].find((n) => /^Contents\/header\.xml$/i.test(n));
2025
+ const doc = name ? parseXml(new TextDecoder().decode(entries.get(name))) : null;
2026
+ if (!doc) return index;
2027
+ const faceByLangId = /* @__PURE__ */ new Map();
2028
+ for (const fontface of Array.from(doc.getElementsByTagNameNS("*", "fontface"))) {
2029
+ const lang = fontface.getAttribute("lang") ?? "";
2030
+ for (const font of Array.from(fontface.getElementsByTagNameNS("*", "font"))) {
2031
+ const id = font.getAttribute("id");
2032
+ const face = font.getAttribute("face");
2033
+ if (id !== null && face) faceByLangId.set(`${lang}:${id}`, face);
2034
+ }
2035
+ }
2036
+ for (const charPr of Array.from(doc.getElementsByTagNameNS("*", "charPr"))) {
2037
+ const id = charPr.getAttribute("id");
2038
+ if (id !== null) index.charCss.set(id, charPrCss(charPr, faceByLangId));
2039
+ }
2040
+ for (const paraPr of Array.from(doc.getElementsByTagNameNS("*", "paraPr"))) {
2041
+ const id = paraPr.getAttribute("id");
2042
+ if (id === null) continue;
2043
+ let raw = paraPr.getAttribute("align") ?? paraPr.getAttribute("alignment");
2044
+ for (const child of Array.from(paraPr.children)) {
2045
+ if (child.localName === "align") raw = child.getAttribute("horizontal") ?? child.getAttribute("align") ?? raw;
2046
+ }
2047
+ const css = raw ? ALIGN_CSS[raw.toUpperCase()] : void 0;
2048
+ if (css) index.paraAlign.set(id, css);
2049
+ }
2050
+ for (const borderFill of Array.from(doc.getElementsByTagNameNS("*", "borderFill"))) {
2051
+ const id = borderFill.getAttribute("id");
2052
+ const color = borderFill.getElementsByTagNameNS("*", "winBrush")[0]?.getAttribute("faceColor");
2053
+ if (id !== null && color && HEX_COLOR.test(color)) index.fillColor.set(id, color);
2054
+ }
2055
+ return index;
2056
+ }
2057
+ function paragraphInlineHtml(p, ctx) {
2058
+ const segments = [];
2059
+ const walk = (el, css) => {
2060
+ for (const child of Array.from(el.children)) {
2061
+ const tag = child.localName;
2062
+ if (tag === "tbl" || tag === "pic") continue;
2063
+ if (tag === "t") {
2064
+ const text = child.textContent ?? "";
2065
+ if (text) segments.push({ css, html: escapeHtml(text) });
2066
+ } else if (tag === "lineBreak") {
2067
+ segments.push({ css, html: "<br>" });
2068
+ } else if (tag === "run") {
2069
+ walk(child, ctx.charCss(child.getAttribute("charPrIDRef")));
2070
+ } else {
2071
+ walk(child, css);
2072
+ }
2073
+ }
2074
+ };
2075
+ walk(p, "");
2076
+ let out = "";
2077
+ for (let i = 0; i < segments.length; ) {
2078
+ const css = segments[i].css;
2079
+ let html = "";
2080
+ for (; i < segments.length && segments[i].css === css; i++) html += segments[i].html;
2081
+ out += css ? `<span style="${escapeHtml(css)}">${html}</span>` : html;
2082
+ }
2083
+ return out;
2084
+ }
2085
+ function paragraphToHtmlBlocks(p, blocks, ctx) {
2086
+ if (paragraphText(p).trim()) {
2087
+ const level = ctx.headingLevel(p);
2088
+ const align = ctx.paraAlign(p.getAttribute("paraPrIDRef"));
2089
+ const style = align ? ` style="text-align:${align}"` : "";
2090
+ const inline = paragraphInlineHtml(p, ctx);
2091
+ blocks.push(level ? `<h${level}${style}>${inline}</h${level}>` : `<p${style}>${inline}</p>`);
2092
+ }
2093
+ collectEmbeddedHtml(p, blocks, ctx);
2094
+ }
2095
+ function collectEmbeddedHtml(el, blocks, ctx) {
2096
+ for (const child of Array.from(el.children)) {
2097
+ if (child.localName === "tbl") {
2098
+ const html = tableToHtml(child, ctx);
2099
+ if (html) blocks.push(html);
2100
+ } else if (child.localName === "pic") {
2101
+ const img = ctx.imageTag(child);
2102
+ if (img) blocks.push(img);
2103
+ } else {
2104
+ collectEmbeddedHtml(child, blocks, ctx);
2105
+ }
2106
+ }
2107
+ }
2108
+ function tableToHtml(tbl, ctx) {
2109
+ let rowsHtml = "";
1720
2110
  for (const tr of Array.from(tbl.getElementsByTagNameNS("*", "tr"))) {
1721
2111
  if (tr.closest("tbl") !== tbl) continue;
1722
- const cells = [];
2112
+ let cells = "";
1723
2113
  for (const tc of Array.from(tr.children)) {
1724
2114
  if (tc.localName !== "tc") continue;
1725
- const text = Array.from(tc.getElementsByTagNameNS("*", "p")).map((p) => paragraphText(p).trim()).filter(Boolean).join(" ").replace(/\|/g, "\\|").replace(/\n/g, " ");
1726
- cells.push(text);
2115
+ let colSpan = 1;
2116
+ let rowSpan = 1;
2117
+ const readSpan = (el) => {
2118
+ for (const attr of Array.from(el.attributes)) {
2119
+ if (/^colspan$/i.test(attr.localName)) colSpan = Math.max(1, Number(attr.value) || 1);
2120
+ else if (/^rowspan$/i.test(attr.localName)) rowSpan = Math.max(1, Number(attr.value) || 1);
2121
+ }
2122
+ };
2123
+ readSpan(tc);
2124
+ for (const child of Array.from(tc.children)) {
2125
+ if (child.localName === "cellSpan") readSpan(child);
2126
+ }
2127
+ const inner = [];
2128
+ const walkCell = (el) => {
2129
+ for (const child of Array.from(el.children)) {
2130
+ if (child.localName === "p") paragraphToHtmlBlocks(child, inner, ctx);
2131
+ else if (child.localName === "tbl") inner.push(tableToHtml(child, ctx));
2132
+ else walkCell(child);
2133
+ }
2134
+ };
2135
+ walkCell(tc);
2136
+ const bg = ctx.cellBackground(tc.getAttribute("borderFillIDRef"));
2137
+ const attrs = (colSpan > 1 ? ` colspan="${colSpan}"` : "") + (rowSpan > 1 ? ` rowspan="${rowSpan}"` : "") + (bg ? ` style="background:${bg}"` : "");
2138
+ cells += `<td${attrs}>${inner.join("")}</td>`;
1727
2139
  }
1728
- if (cells.length) rows.push(cells);
2140
+ if (cells) rowsHtml += `<tr>${cells}</tr>`;
1729
2141
  }
1730
- if (!rows.length) return "";
1731
- const width = Math.max(...rows.map((r) => r.length));
1732
- const pad = (r) => Array.from({ length: width }, (_, i) => r[i] ?? "");
1733
- const line = (r) => `| ${pad(r).join(" | ")} |`;
1734
- const [head, ...body] = rows;
1735
- return [line(head), `| ${Array(width).fill("---").join(" | ")} |`, ...body.map(line)].join("\n");
2142
+ return rowsHtml ? `<table>${rowsHtml}</table>` : "";
1736
2143
  }
1737
- function sectionToMarkdown(xml) {
1738
- const doc = new DOMParser().parseFromString(xml, "application/xml");
1739
- if (doc.querySelector("parsererror")) {
2144
+ function sectionToHtml(xml, ctx) {
2145
+ const doc = parseXml(xml);
2146
+ if (!doc) {
1740
2147
  throw new Error("HWPX \uBCF8\uBB38 XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (invalid section XML).");
1741
2148
  }
1742
2149
  const blocks = [];
1743
2150
  const walk = (el) => {
1744
2151
  for (const child of Array.from(el.children)) {
1745
- const tag = child.localName;
1746
- if (tag === "p") {
1747
- const text = paragraphText(child).trim();
1748
- if (text) blocks.push(text);
1749
- for (const tbl of Array.from(child.getElementsByTagNameNS("*", "tbl"))) {
1750
- if ((tbl.parentElement?.closest("tbl") ?? null) === null) {
1751
- const md = tableToMarkdown(tbl);
1752
- if (md) blocks.push(md);
1753
- }
1754
- }
1755
- } else if (tag !== "tbl") {
1756
- walk(child);
1757
- }
2152
+ if (child.localName === "p") paragraphToHtmlBlocks(child, blocks, ctx);
2153
+ else if (child.localName !== "tbl") walk(child);
1758
2154
  }
1759
2155
  };
1760
2156
  walk(doc.documentElement);
1761
2157
  return blocks;
1762
2158
  }
1763
- async function hwpxToMarkdown(buffer) {
1764
- const entries = await readZip(buffer);
1765
- const sections = [...entries.keys()].filter((n) => /^Contents\/section\d+\.xml$/i.test(n)).sort((a, b) => Number(/(\d+)/.exec(a)?.[1] ?? 0) - Number(/(\d+)/.exec(b)?.[1] ?? 0));
1766
- if (!sections.length) {
1767
- throw new Error("HWPX \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uBCF8\uBB38(section XML)\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (no Contents/section*.xml).");
1768
- }
1769
- const decoder = new TextDecoder();
2159
+ async function hwpxToHtml(buffer) {
2160
+ const { entries, sections, headings, images } = await openHwpx(buffer);
2161
+ const styles2 = buildStyleIndex(entries);
2162
+ const ctx = {
2163
+ headingLevel: (p) => headingLevelOf(p, headings),
2164
+ charCss: (id) => id !== null ? styles2.charCss.get(id) ?? "" : "",
2165
+ paraAlign: (id) => id !== null ? styles2.paraAlign.get(id) ?? null : null,
2166
+ cellBackground: (id) => id !== null ? styles2.fillColor.get(id) ?? null : null,
2167
+ imageTag: (pic) => {
2168
+ const ref = binaryItemRef(pic);
2169
+ const url = ref ? images.dataUrl(ref) : null;
2170
+ return url ? `<img src="${url}" style="max-width:100%">` : null;
2171
+ }
2172
+ };
1770
2173
  const blocks = [];
1771
- for (const name of sections) {
1772
- blocks.push(...sectionToMarkdown(decoder.decode(entries.get(name))));
2174
+ for (const xml of sections) {
2175
+ blocks.push(...sectionToHtml(xml, ctx));
2176
+ }
2177
+ if (images.omitted > 0) {
2178
+ blocks.push(`<p>(\uC774\uBBF8\uC9C0 ${images.omitted}\uC7A5 \uC0DD\uB7B5 / ${images.omitted} images omitted)</p>`);
1773
2179
  }
1774
- return blocks.join("\n\n").trim();
2180
+ return koreanDocHtml(blocks.join("\n"));
1775
2181
  }
1776
2182
 
1777
2183
  // src/io/hwp.ts
@@ -1922,6 +2328,10 @@ function parseFileHeader(bytes) {
1922
2328
  return { compressed: (flags & 1) !== 0 };
1923
2329
  }
1924
2330
  var HWPTAG_PARA_TEXT = 16 + 51;
2331
+ var HWPTAG_CTRL_HEADER = 16 + 55;
2332
+ var HWPTAG_LIST_HEADER = 16 + 56;
2333
+ var HWPTAG_TABLE = 16 + 61;
2334
+ var CTRL_ID_TABLE = 1952607264;
1925
2335
  function decodeParaText(view, offset, byteLength) {
1926
2336
  const paragraphs = [];
1927
2337
  let text = "";
@@ -1963,14 +2373,14 @@ function decodeParaText(view, offset, byteLength) {
1963
2373
  if (text) paragraphs.push(text);
1964
2374
  return paragraphs;
1965
2375
  }
1966
- function extractSectionParagraphs(section) {
1967
- const view = new DataView(section.buffer, section.byteOffset, section.byteLength);
1968
- const paragraphs = [];
2376
+ function parseRecords(section, view) {
2377
+ const records = [];
1969
2378
  let off = 0;
1970
2379
  while (off + 4 <= section.length) {
1971
2380
  const header = view.getUint32(off, true);
1972
2381
  off += 4;
1973
- const tagId = header & 1023;
2382
+ const tag = header & 1023;
2383
+ const level = header >>> 10 & 1023;
1974
2384
  let size = header >>> 20 & 4095;
1975
2385
  if (size === 4095) {
1976
2386
  if (off + 4 > section.length) throw new Error(ERR.corrupt("truncated record header"));
@@ -1978,10 +2388,73 @@ function extractSectionParagraphs(section) {
1978
2388
  off += 4;
1979
2389
  }
1980
2390
  if (off + size > section.length) throw new Error(ERR.corrupt("record overruns section"));
1981
- if (tagId === HWPTAG_PARA_TEXT) paragraphs.push(...decodeParaText(view, off, size));
2391
+ records.push({ tag, level, start: off, size });
1982
2392
  off += size;
1983
2393
  }
1984
- return paragraphs;
2394
+ return records;
2395
+ }
2396
+ function tableBlocks(view, subtree, ctrlLevel) {
2397
+ const tableRec = subtree.find((r) => r.tag === HWPTAG_TABLE);
2398
+ const cellLevel = ctrlLevel + 1;
2399
+ const cells = [];
2400
+ const flat = [];
2401
+ let current = null;
2402
+ let surprised = tableRec === void 0 || tableRec.size < 8;
2403
+ for (const rec of subtree) {
2404
+ if (rec.tag === HWPTAG_LIST_HEADER && rec.level === cellLevel) {
2405
+ current = [];
2406
+ cells.push(current);
2407
+ } else if (rec.tag === HWPTAG_PARA_TEXT) {
2408
+ const paragraphs = decodeParaText(view, rec.start, rec.size);
2409
+ flat.push(...paragraphs);
2410
+ if (current) current.push(...paragraphs);
2411
+ else surprised = true;
2412
+ }
2413
+ }
2414
+ if (!surprised) {
2415
+ const nRows = view.getUint16(tableRec.start + 4, true);
2416
+ const nCols = view.getUint16(tableRec.start + 6, true);
2417
+ if (nRows >= 1 && nCols >= 1 && cells.length === nRows * nCols) {
2418
+ const texts = cells.map((c) => c.filter((p) => p.length > 0).join("\n"));
2419
+ const rows = Array.from({ length: nRows }, (_, r) => texts.slice(r * nCols, (r + 1) * nCols));
2420
+ return [{ kind: "table", rows }];
2421
+ }
2422
+ }
2423
+ return flat.map((text) => ({ kind: "paragraph", text }));
2424
+ }
2425
+ function extractSectionBlocks(section) {
2426
+ const view = new DataView(section.buffer, section.byteOffset, section.byteLength);
2427
+ const records = parseRecords(section, view);
2428
+ const blocks = [];
2429
+ let i = 0;
2430
+ while (i < records.length) {
2431
+ const rec = records[i];
2432
+ if (rec.tag === HWPTAG_CTRL_HEADER && rec.size >= 4 && view.getUint32(rec.start, true) === CTRL_ID_TABLE) {
2433
+ let end = i + 1;
2434
+ while (end < records.length && records[end].level > rec.level) end++;
2435
+ blocks.push(...tableBlocks(view, records.slice(i + 1, end), rec.level));
2436
+ i = end;
2437
+ continue;
2438
+ }
2439
+ if (rec.tag === HWPTAG_PARA_TEXT) {
2440
+ for (const text of decodeParaText(view, rec.start, rec.size)) blocks.push({ kind: "paragraph", text });
2441
+ }
2442
+ i++;
2443
+ }
2444
+ return blocks;
2445
+ }
2446
+ function blocksToHtml(blocks) {
2447
+ const inline = (text) => escapeHtml(text).replace(/\n/g, "<br>");
2448
+ const out = [];
2449
+ for (const block of blocks) {
2450
+ if (block.kind === "paragraph") {
2451
+ if (block.text.length > 0) out.push(`<p>${inline(block.text)}</p>`);
2452
+ continue;
2453
+ }
2454
+ const rows = block.rows.map((row) => `<tr>${row.map((cell) => `<td>${inline(cell)}</td>`).join("")}</tr>`);
2455
+ out.push(`<table>${rows.join("")}</table>`);
2456
+ }
2457
+ return out;
1985
2458
  }
1986
2459
  async function inflateRaw2(data) {
1987
2460
  if (typeof DecompressionStream === "undefined") throw new Error(ERR.noInflate);
@@ -2007,19 +2480,911 @@ async function inflateRaw2(data) {
2007
2480
  }
2008
2481
  return out;
2009
2482
  }
2010
- async function hwpToText(buffer) {
2483
+ async function extractBlocks(buffer) {
2011
2484
  const cfb = parseCfb(new Uint8Array(buffer));
2012
2485
  const headerEntry = cfb.entries.find((e) => e.type === 2 && e.name === "FileHeader");
2013
2486
  if (!headerEntry) throw new Error(ERR.notHwp);
2014
2487
  const header = parseFileHeader(cfb.readStream(headerEntry));
2015
2488
  const sections = cfb.entries.map((e) => ({ entry: e, match: e.type === 2 ? /^Section(\d+)$/.exec(e.name) : null })).filter((s) => s.match !== null).sort((a, b) => Number(a.match[1]) - Number(b.match[1]));
2016
- const paragraphs = [];
2489
+ const blocks = [];
2017
2490
  for (const { entry } of sections) {
2018
2491
  let bytes = cfb.readStream(entry);
2019
2492
  if (header.compressed) bytes = await inflateRaw2(bytes);
2020
- paragraphs.push(...extractSectionParagraphs(bytes).filter((p) => p.length > 0));
2493
+ blocks.push(...extractSectionBlocks(bytes));
2494
+ }
2495
+ return blocks;
2496
+ }
2497
+ async function hwpToHtml(buffer) {
2498
+ return koreanDocHtml(blocksToHtml(await extractBlocks(buffer)).join("\n"));
2499
+ }
2500
+
2501
+ // src/io/docx.ts
2502
+ function childOf(el, name) {
2503
+ for (const c of Array.from(el.children)) if (c.localName === name) return c;
2504
+ return null;
2505
+ }
2506
+ function attrOf(el, name) {
2507
+ for (const a of Array.from(el.attributes)) if (a.localName === name) return a.value;
2508
+ return null;
2509
+ }
2510
+ function toggleOn(rPr, name) {
2511
+ const prop = rPr && childOf(rPr, name);
2512
+ if (!prop) return false;
2513
+ const val = attrOf(prop, "val");
2514
+ return val !== "0" && val !== "false" && val !== "none";
2515
+ }
2516
+ function headingLevel(pPr) {
2517
+ const style = pPr && childOf(pPr, "pStyle");
2518
+ const id = style && attrOf(style, "val");
2519
+ const m = id && /^heading ?([1-6])$/i.exec(id);
2520
+ return m ? Number(m[1]) : 0;
2521
+ }
2522
+ function listLevel(pPr) {
2523
+ const numPr = pPr && childOf(pPr, "numPr");
2524
+ if (!numPr) return null;
2525
+ const ilvl = childOf(numPr, "ilvl");
2526
+ return Number(ilvl && attrOf(ilvl, "val")) || 0;
2527
+ }
2528
+ function runText(run) {
2529
+ let out = "";
2530
+ for (const c of Array.from(run.children)) {
2531
+ if (c.localName === "t") {
2532
+ const raw = c.textContent ?? "";
2533
+ out += attrOf(c, "space") === "preserve" ? raw : raw.trim();
2534
+ } else if (c.localName === "br") out += "\n";
2535
+ else if (c.localName === "tab") out += " ";
2021
2536
  }
2022
- return paragraphs.join("\n\n").replace(/\s+$/, "");
2537
+ return out;
2538
+ }
2539
+ function paragraphSegments(p) {
2540
+ const segs = [];
2541
+ const walk = (el) => {
2542
+ for (const c of Array.from(el.children)) {
2543
+ if (c.localName === "pPr") continue;
2544
+ if (c.localName === "r") {
2545
+ const rPr = childOf(c, "rPr");
2546
+ const text = runText(c);
2547
+ if (text) segs.push({ text, bold: toggleOn(rPr, "b"), italic: toggleOn(rPr, "i") });
2548
+ } else walk(c);
2549
+ }
2550
+ };
2551
+ walk(p);
2552
+ return segs;
2553
+ }
2554
+ function renderSegments(segs) {
2555
+ const merged = [];
2556
+ for (const s of segs) {
2557
+ const last = merged[merged.length - 1];
2558
+ if (last && last.bold === s.bold && last.italic === s.italic) last.text += s.text;
2559
+ else merged.push({ ...s });
2560
+ }
2561
+ return merged.map(({ text, bold, italic }) => {
2562
+ if (!bold && !italic) return text;
2563
+ const [, lead, core, trail] = /^(\s*)([\s\S]*?)(\s*)$/.exec(text);
2564
+ if (!core) return text;
2565
+ const mark = bold && italic ? "***" : bold ? "**" : "*";
2566
+ return `${lead}${mark}${core}${mark}${trail}`;
2567
+ }).join("");
2568
+ }
2569
+ function paragraphToBlock(p) {
2570
+ const text = renderSegments(paragraphSegments(p)).trim();
2571
+ if (!text) return null;
2572
+ const pPr = childOf(p, "pPr");
2573
+ const heading = headingLevel(pPr);
2574
+ if (heading) return { kind: "other", text: `${"#".repeat(heading)} ${text}` };
2575
+ const level = listLevel(pPr);
2576
+ if (level !== null) return { kind: "list", text: `${" ".repeat(level)}- ${text}` };
2577
+ return { kind: "other", text };
2578
+ }
2579
+ function tableToMarkdown(tbl) {
2580
+ const rows = [];
2581
+ for (const tr of Array.from(tbl.children)) {
2582
+ if (tr.localName !== "tr") continue;
2583
+ const cells = [];
2584
+ for (const tc of Array.from(tr.children)) {
2585
+ if (tc.localName !== "tc") continue;
2586
+ const text = Array.from(tc.getElementsByTagNameNS("*", "p")).filter((p) => p.closest("tbl") === tbl).map((p) => renderSegments(paragraphSegments(p)).trim()).filter(Boolean).join(" ").replace(/\|/g, "\\|").replace(/\n/g, " ");
2587
+ cells.push(text);
2588
+ }
2589
+ if (cells.length) rows.push(cells);
2590
+ }
2591
+ if (!rows.length) return "";
2592
+ const width = Math.max(...rows.map((r) => r.length));
2593
+ const pad = (r) => Array.from({ length: width }, (_, i) => r[i] ?? "");
2594
+ const line = (r) => `| ${pad(r).join(" | ")} |`;
2595
+ const [head, ...body] = rows;
2596
+ return [line(head), `| ${Array(width).fill("---").join(" | ")} |`, ...body.map(line)].join("\n");
2597
+ }
2598
+ function documentToMarkdown(doc) {
2599
+ const blocks = [];
2600
+ const walk = (el) => {
2601
+ for (const c of Array.from(el.children)) {
2602
+ if (c.localName === "p") {
2603
+ const block = paragraphToBlock(c);
2604
+ if (block) blocks.push(block);
2605
+ } else if (c.localName === "tbl") {
2606
+ const md = tableToMarkdown(c);
2607
+ if (md) blocks.push({ kind: "other", text: md });
2608
+ } else {
2609
+ walk(c);
2610
+ }
2611
+ }
2612
+ };
2613
+ walk(doc.documentElement);
2614
+ const out = [];
2615
+ let prevKind = null;
2616
+ for (const { kind, text } of blocks) {
2617
+ if (kind === "list" && prevKind === "list") out[out.length - 1] += `
2618
+ ${text}`;
2619
+ else out.push(text);
2620
+ prevKind = kind;
2621
+ }
2622
+ return out.join("\n\n").trim();
2623
+ }
2624
+ async function docxToMarkdown(buffer) {
2625
+ const entries = await readZip(buffer);
2626
+ const body = entries.get("word/document.xml");
2627
+ if (!body) {
2628
+ throw new Error("DOCX \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uBCF8\uBB38(word/document.xml)\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 (Not a DOCX document \u2014 missing word/document.xml).");
2629
+ }
2630
+ const doc = new DOMParser().parseFromString(new TextDecoder().decode(body), "application/xml");
2631
+ if (doc.querySelector("parsererror")) {
2632
+ throw new Error("DOCX \uBCF8\uBB38 XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (invalid document XML).");
2633
+ }
2634
+ return documentToMarkdown(doc);
2635
+ }
2636
+
2637
+ // src/io/pptx.ts
2638
+ function parseXml2(xml) {
2639
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
2640
+ return doc.querySelector("parsererror") ? null : doc;
2641
+ }
2642
+ function childOf2(el, name) {
2643
+ for (const c of Array.from(el.children)) if (c.localName === name) return c;
2644
+ return null;
2645
+ }
2646
+ function attrOf2(el, name) {
2647
+ for (const a of Array.from(el.attributes)) if (a.localName === name) return a.value;
2648
+ return null;
2649
+ }
2650
+ var isOn = (value) => value === "1" || value === "true";
2651
+ function relIdOf(el) {
2652
+ for (const a of Array.from(el.attributes)) {
2653
+ if (a.localName === "id" && /relationships/i.test(a.namespaceURI ?? "")) return a.value;
2654
+ }
2655
+ for (const a of Array.from(el.attributes)) {
2656
+ if (/:id$/i.test(a.name)) return a.value;
2657
+ }
2658
+ return null;
2659
+ }
2660
+ function resolveTarget(baseDir, target) {
2661
+ if (target.startsWith("/")) return target.slice(1);
2662
+ const parts = baseDir ? baseDir.split("/") : [];
2663
+ for (const seg of target.split("/")) {
2664
+ if (seg === "..") parts.pop();
2665
+ else if (seg && seg !== ".") parts.push(seg);
2666
+ }
2667
+ return parts.join("/");
2668
+ }
2669
+ function relsOf(entries, partName) {
2670
+ const rels = /* @__PURE__ */ new Map();
2671
+ const slash = partName.lastIndexOf("/");
2672
+ const dir = partName.slice(0, slash);
2673
+ const relsPart = entries.get(`${dir}/_rels/${partName.slice(slash + 1)}.rels`);
2674
+ const doc = relsPart ? parseXml2(new TextDecoder().decode(relsPart)) : null;
2675
+ if (!doc) return rels;
2676
+ for (const rel of Array.from(doc.getElementsByTagNameNS("*", "Relationship"))) {
2677
+ const id = rel.getAttribute("Id");
2678
+ const target = rel.getAttribute("Target");
2679
+ if (id && target) rels.set(id, { type: rel.getAttribute("Type") ?? "", target: resolveTarget(dir, target) });
2680
+ }
2681
+ return rels;
2682
+ }
2683
+ function relOfType(rels, suffix) {
2684
+ for (const rel of rels.values()) if (rel.type.endsWith(suffix)) return rel;
2685
+ return void 0;
2686
+ }
2687
+ var DEFAULT_SLIDE_CX = 12192e3;
2688
+ var DEFAULT_SLIDE_CY = 6858e3;
2689
+ function emuOf(parent, child, attr) {
2690
+ const el = childOf2(parent, child);
2691
+ const n = el ? Number(attrOf2(el, attr)) : NaN;
2692
+ return Number.isFinite(n) ? n : null;
2693
+ }
2694
+ function xfrmOf(pr) {
2695
+ const xfrm = childOf2(pr, "xfrm");
2696
+ if (!xfrm) return null;
2697
+ const x = emuOf(xfrm, "off", "x");
2698
+ const y = emuOf(xfrm, "off", "y");
2699
+ const cx = emuOf(xfrm, "ext", "cx");
2700
+ const cy = emuOf(xfrm, "ext", "cy");
2701
+ if (x === null || y === null || cx === null || cy === null) return null;
2702
+ return { x, y, cx, cy, rot: Number(attrOf2(xfrm, "rot")) || 0 };
2703
+ }
2704
+ var IDENTITY_MAP = { tx: 0, ty: 0, sx: 1, sy: 1, chx: 0, chy: 0 };
2705
+ function groupChildMap(grp, map) {
2706
+ const pr = childOf2(grp, "grpSpPr");
2707
+ const xfrm = pr && childOf2(pr, "xfrm");
2708
+ if (!pr || !xfrm) return map;
2709
+ const off = xfrmOf(pr);
2710
+ const chx = emuOf(xfrm, "chOff", "x");
2711
+ const chy = emuOf(xfrm, "chOff", "y");
2712
+ const chCx = emuOf(xfrm, "chExt", "cx");
2713
+ const chCy = emuOf(xfrm, "chExt", "cy");
2714
+ if (!off || chx === null || chy === null || !chCx || !chCy || chCx < 0 || chCy < 0) return map;
2715
+ return {
2716
+ tx: map.tx + (off.x - map.chx) * map.sx,
2717
+ ty: map.ty + (off.y - map.chy) * map.sy,
2718
+ sx: map.sx * (off.cx / chCx),
2719
+ sy: map.sy * (off.cy / chCy),
2720
+ chx,
2721
+ chy
2722
+ };
2723
+ }
2724
+ var round2 = (n) => Math.round(n * 100) / 100;
2725
+ var pct = (value, total) => round2(value / total * 100);
2726
+ var DEFAULT_CLR_MAP = [
2727
+ ["bg1", "lt1"],
2728
+ ["tx1", "dk1"],
2729
+ ["bg2", "lt2"],
2730
+ ["tx2", "dk2"]
2731
+ ];
2732
+ var clamp01 = (n) => Math.max(0, Math.min(1, n));
2733
+ var byteHex = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
2734
+ function rgbOf(hex) {
2735
+ return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
2736
+ }
2737
+ function rgbToHsl(r, g, b) {
2738
+ r /= 255;
2739
+ g /= 255;
2740
+ b /= 255;
2741
+ const max = Math.max(r, g, b);
2742
+ const min = Math.min(r, g, b);
2743
+ const l = (max + min) / 2;
2744
+ if (max === min) return [0, 0, l];
2745
+ const d = max - min;
2746
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2747
+ const h = max === r ? ((g - b) / d + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / d + 2) / 6 : ((r - g) / d + 4) / 6;
2748
+ return [h, s, l];
2749
+ }
2750
+ function hslToRgb(h, s, l) {
2751
+ if (s === 0) return [l * 255, l * 255, l * 255];
2752
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
2753
+ const p = 2 * l - q;
2754
+ const channel = (t) => {
2755
+ if (t < 0) t += 1;
2756
+ if (t > 1) t -= 1;
2757
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
2758
+ if (t < 1 / 2) return q;
2759
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
2760
+ return p;
2761
+ };
2762
+ return [channel(h + 1 / 3) * 255, channel(h) * 255, channel(h - 1 / 3) * 255];
2763
+ }
2764
+ var ColorScheme = class {
2765
+ constructor(scheme, clrMap) {
2766
+ this.scheme = scheme;
2767
+ this.clrMap = clrMap;
2768
+ }
2769
+ resolve(clr) {
2770
+ let hex = null;
2771
+ if (clr.localName === "srgbClr") hex = attrOf2(clr, "val");
2772
+ else if (clr.localName === "sysClr") hex = attrOf2(clr, "lastClr");
2773
+ else if (clr.localName === "schemeClr") {
2774
+ const name = attrOf2(clr, "val") ?? "";
2775
+ hex = this.scheme.get(this.clrMap.get(name) ?? name) ?? null;
2776
+ }
2777
+ if (!hex || !/^[0-9a-fA-F]{6}$/.test(hex)) return void 0;
2778
+ let [r, g, b] = rgbOf(hex);
2779
+ for (const t of Array.from(clr.children)) {
2780
+ const raw = Number(attrOf2(t, "val"));
2781
+ if (!Number.isFinite(raw)) continue;
2782
+ const f = raw / 1e5;
2783
+ if (t.localName === "lumMod" || t.localName === "lumOff") {
2784
+ const [h, s, l] = rgbToHsl(r, g, b);
2785
+ [r, g, b] = hslToRgb(h, s, clamp01(t.localName === "lumMod" ? l * f : l + f));
2786
+ } else if (t.localName === "tint") {
2787
+ r = r * f + 255 * (1 - f);
2788
+ g = g * f + 255 * (1 - f);
2789
+ b = b * f + 255 * (1 - f);
2790
+ } else if (t.localName === "shade") {
2791
+ r *= f;
2792
+ g *= f;
2793
+ b *= f;
2794
+ }
2795
+ }
2796
+ return `#${byteHex(r)}${byteHex(g)}${byteHex(b)}`.toUpperCase();
2797
+ }
2798
+ };
2799
+ function solidFillColor(el, colors) {
2800
+ const fill = el && childOf2(el, "solidFill");
2801
+ if (!fill) return void 0;
2802
+ for (const c of Array.from(fill.children)) {
2803
+ const hex = colors.resolve(c);
2804
+ if (!hex) continue;
2805
+ const alphaEl = childOf2(c, "alpha");
2806
+ const alpha = alphaEl ? Number(attrOf2(alphaEl, "val")) : NaN;
2807
+ if (Number.isFinite(alpha) && alpha >= 0 && alpha < 1e5) {
2808
+ return hex + Math.round(alpha / 1e5 * 255).toString(16).padStart(2, "0").toUpperCase();
2809
+ }
2810
+ return hex;
2811
+ }
2812
+ return void 0;
2813
+ }
2814
+ function gradientFirstStop(el, colors) {
2815
+ const grad = el && childOf2(el, "gradFill");
2816
+ const lst = grad && childOf2(grad, "gsLst");
2817
+ const gs = lst && childOf2(lst, "gs");
2818
+ if (!gs) return void 0;
2819
+ for (const c of Array.from(gs.children)) {
2820
+ const hex = colors.resolve(c);
2821
+ if (hex) return hex;
2822
+ }
2823
+ return void 0;
2824
+ }
2825
+ function styleRefColor(sp, ref, colors) {
2826
+ const style = childOf2(sp, "style");
2827
+ const refEl = style && childOf2(style, ref);
2828
+ if (!refEl || attrOf2(refEl, "idx") === "0") return void 0;
2829
+ for (const c of Array.from(refEl.children)) {
2830
+ const hex = colors.resolve(c);
2831
+ if (hex) return hex;
2832
+ }
2833
+ return void 0;
2834
+ }
2835
+ function chainFor(entries, slideRels, cache) {
2836
+ const load = (name) => {
2837
+ if (!name) return null;
2838
+ let doc = cache.get(name);
2839
+ if (doc === void 0) {
2840
+ const part = entries.get(name);
2841
+ doc = part ? parseXml2(new TextDecoder().decode(part)) : null;
2842
+ cache.set(name, doc);
2843
+ }
2844
+ return doc;
2845
+ };
2846
+ const layoutName = relOfType(slideRels, "/slideLayout")?.target;
2847
+ const layoutDoc = load(layoutName);
2848
+ const masterName = layoutName ? relOfType(relsOf(entries, layoutName), "/slideMaster")?.target : void 0;
2849
+ const masterDoc = load(masterName);
2850
+ const themeDoc = masterName ? load(relOfType(relsOf(entries, masterName), "/theme")?.target) : null;
2851
+ const scheme = /* @__PURE__ */ new Map();
2852
+ const clrScheme = themeDoc?.getElementsByTagNameNS("*", "clrScheme")[0];
2853
+ for (const slot of Array.from(clrScheme?.children ?? [])) {
2854
+ const clr = slot.firstElementChild;
2855
+ const hex = clr?.localName === "sysClr" ? attrOf2(clr, "lastClr") : clr && attrOf2(clr, "val");
2856
+ if (hex && /^[0-9a-fA-F]{6}$/.test(hex)) scheme.set(slot.localName, hex);
2857
+ }
2858
+ const clrMap = new Map(DEFAULT_CLR_MAP);
2859
+ const clrMapEl = masterDoc?.getElementsByTagNameNS("*", "clrMap")[0];
2860
+ for (const a of Array.from(clrMapEl?.attributes ?? [])) clrMap.set(a.localName, a.value);
2861
+ const treeOf = (doc) => doc?.getElementsByTagNameNS("*", "spTree")[0] ?? null;
2862
+ return {
2863
+ colors: new ColorScheme(scheme, clrMap),
2864
+ layoutTree: treeOf(layoutDoc),
2865
+ masterTree: treeOf(masterDoc),
2866
+ layoutDoc,
2867
+ masterDoc,
2868
+ txStyles: masterDoc?.getElementsByTagNameNS("*", "txStyles")[0] ?? null
2869
+ };
2870
+ }
2871
+ var MEDIA_BASE64_BUDGET = 12 * 1024 * 1024;
2872
+ var MIME_BY_EXT2 = {
2873
+ png: "image/png",
2874
+ jpg: "image/jpeg",
2875
+ jpeg: "image/jpeg",
2876
+ gif: "image/gif",
2877
+ bmp: "image/bmp",
2878
+ webp: "image/webp",
2879
+ svg: "image/svg+xml"
2880
+ };
2881
+ function bytesToBase643(bytes) {
2882
+ let bin = "";
2883
+ for (let i = 0; i < bytes.length; i += 32768) {
2884
+ bin += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + 32768)));
2885
+ }
2886
+ return btoa(bin);
2887
+ }
2888
+ var MediaResolver = class {
2889
+ constructor(entries) {
2890
+ this.entries = entries;
2891
+ this.used = 0;
2892
+ }
2893
+ dataUrl(entryName) {
2894
+ const bytes = this.entries.get(entryName);
2895
+ if (!bytes) return null;
2896
+ const base64Length = Math.ceil(bytes.length / 3) * 4;
2897
+ if (this.used + base64Length > MEDIA_BASE64_BUDGET) return null;
2898
+ this.used += base64Length;
2899
+ const ext = /\.([^.]+)$/.exec(entryName)?.[1]?.toLowerCase() ?? "";
2900
+ return `data:${MIME_BY_EXT2[ext] ?? "image/png"};base64,${bytesToBase643(bytes)}`;
2901
+ }
2902
+ };
2903
+ function toPercentBox(xfrm, map, ctx) {
2904
+ return {
2905
+ x: pct(map.tx + (xfrm.x - map.chx) * map.sx, ctx.slideCx),
2906
+ y: pct(map.ty + (xfrm.y - map.chy) * map.sy, ctx.slideCy),
2907
+ w: pct(xfrm.cx * map.sx, ctx.slideCx),
2908
+ h: pct(xfrm.cy * map.sy, ctx.slideCy)
2909
+ };
2910
+ }
2911
+ function normalizedRotation(deg) {
2912
+ const n = Math.round((deg % 360 + 360) % 360 * 100) / 100;
2913
+ return n || void 0;
2914
+ }
2915
+ function rotationOf(xfrm) {
2916
+ if (!xfrm.rot) return void 0;
2917
+ const deg = Math.round(xfrm.rot / 6e4 * 100) / 100;
2918
+ return deg || void 0;
2919
+ }
2920
+ function txBodyText(txBody) {
2921
+ const paras = [];
2922
+ for (const p of Array.from(txBody.children)) {
2923
+ if (p.localName !== "p") continue;
2924
+ let line = "";
2925
+ for (const c of Array.from(p.children)) {
2926
+ if (c.localName === "r") line += childOf2(c, "t")?.textContent ?? "";
2927
+ else if (c.localName === "br") line += "\n";
2928
+ }
2929
+ paras.push(line);
2930
+ }
2931
+ return paras.join("\n");
2932
+ }
2933
+ function placeholderOf(sp) {
2934
+ for (const nv of Array.from(sp.children)) {
2935
+ if (!/^nv(Sp|Pic|GrpSp|Cxn|GraphicFrame)Pr$/.test(nv.localName ?? "")) continue;
2936
+ const nvPr = childOf2(nv, "nvPr");
2937
+ const ph = nvPr && childOf2(nvPr, "ph");
2938
+ if (ph) return { type: attrOf2(ph, "type") ?? "body", idx: attrOf2(ph, "idx") };
2939
+ }
2940
+ return null;
2941
+ }
2942
+ function placeholderType(sp) {
2943
+ return placeholderOf(sp)?.type ?? null;
2944
+ }
2945
+ function findPlaceholder(tree, want) {
2946
+ if (!tree) return null;
2947
+ const shapes = Array.from(tree.getElementsByTagNameNS("*", "sp"));
2948
+ for (const sp of shapes) if (placeholderOf(sp)?.type === want.type) return sp;
2949
+ if (want.idx !== null) {
2950
+ for (const sp of shapes) if (placeholderOf(sp)?.idx === want.idx) return sp;
2951
+ }
2952
+ return null;
2953
+ }
2954
+ function inheritedXfrm(sp, ctx) {
2955
+ const ph = placeholderOf(sp);
2956
+ if (!ph) return null;
2957
+ for (const tree of [ctx.chain.layoutTree, ctx.chain.masterTree]) {
2958
+ const match = findPlaceholder(tree, ph);
2959
+ const spPr = match && childOf2(match, "spPr");
2960
+ const xfrm = spPr && xfrmOf(spPr);
2961
+ if (xfrm) return xfrm;
2962
+ }
2963
+ return null;
2964
+ }
2965
+ function styleSources(sp, txBody, ctx) {
2966
+ const out = [];
2967
+ const own = childOf2(txBody, "lstStyle");
2968
+ if (own) out.push(own);
2969
+ const ph = placeholderOf(sp);
2970
+ if (!ph) return out;
2971
+ for (const tree of [ctx.chain.layoutTree, ctx.chain.masterTree]) {
2972
+ const match = findPlaceholder(tree, ph);
2973
+ const body = match && childOf2(match, "txBody");
2974
+ const lstStyle = body && childOf2(body, "lstStyle");
2975
+ if (lstStyle) out.push(lstStyle);
2976
+ }
2977
+ if (ctx.chain.txStyles) {
2978
+ const bucket = /^(title|ctrTitle)$/.test(ph.type) ? "titleStyle" : /^(body|subTitle|obj)$/.test(ph.type) ? "bodyStyle" : "otherStyle";
2979
+ const styles2 = childOf2(ctx.chain.txStyles, bucket);
2980
+ if (styles2) out.push(styles2);
2981
+ }
2982
+ return out;
2983
+ }
2984
+ function lvlPrOf(source, lvl) {
2985
+ return childOf2(source, `lvl${lvl + 1}pPr`) ?? childOf2(source, "lvl1pPr");
2986
+ }
2987
+ function bulletOf(pPr, sources, lvl) {
2988
+ for (const el of [pPr, ...sources.map((s) => lvlPrOf(s, lvl))]) {
2989
+ if (!el) continue;
2990
+ if (childOf2(el, "buNone")) return false;
2991
+ if (childOf2(el, "buChar") || childOf2(el, "buAutoNum")) return true;
2992
+ }
2993
+ return false;
2994
+ }
2995
+ function paragraphsOf(txBody, sp, ctx) {
2996
+ const colors = ctx.chain.colors;
2997
+ const sources = styleSources(sp, txBody, ctx);
2998
+ const shapeTextColor = styleRefColor(sp, "fontRef", colors);
2999
+ const out = [];
3000
+ for (const p of Array.from(txBody.children)) {
3001
+ if (p.localName !== "p") continue;
3002
+ let text = "";
3003
+ for (const c of Array.from(p.children)) {
3004
+ if (c.localName === "r") text += childOf2(c, "t")?.textContent ?? "";
3005
+ else if (c.localName === "br") text += "\n";
3006
+ }
3007
+ const pPr = childOf2(p, "pPr");
3008
+ const lvl = Math.max(0, Number(pPr && attrOf2(pPr, "lvl")) || 0);
3009
+ const firstRun = childOf2(p, "r");
3010
+ const rPr = firstRun && childOf2(firstRun, "rPr");
3011
+ const props = [];
3012
+ if (rPr) props.push(rPr);
3013
+ const pDef = pPr && childOf2(pPr, "defRPr");
3014
+ if (pDef) props.push(pDef);
3015
+ for (const src of sources) {
3016
+ const lvlPr = lvlPrOf(src, lvl);
3017
+ const def = lvlPr && childOf2(lvlPr, "defRPr");
3018
+ if (def) props.push(def);
3019
+ }
3020
+ const para = { text, bullet: bulletOf(pPr, sources, lvl) };
3021
+ for (const pr of props) {
3022
+ if (para.szCenti === void 0) {
3023
+ const sz = Number(attrOf2(pr, "sz"));
3024
+ if (Number.isFinite(sz) && sz > 0) para.szCenti = sz;
3025
+ }
3026
+ if (para.bold === void 0 && attrOf2(pr, "b") !== null) para.bold = isOn(attrOf2(pr, "b"));
3027
+ if (para.color === void 0) {
3028
+ const color = solidFillColor(pr, colors);
3029
+ if (color) para.color = color;
3030
+ }
3031
+ }
3032
+ if (para.color === void 0 && shapeTextColor) para.color = shapeTextColor;
3033
+ for (const el of [pPr, ...sources.map((s) => lvlPrOf(s, lvl))]) {
3034
+ const algn = el && attrOf2(el, "algn");
3035
+ if (!algn) continue;
3036
+ const align = algn === "l" ? "left" : algn === "ctr" ? "center" : algn === "r" ? "right" : void 0;
3037
+ if (align) para.align = align;
3038
+ break;
3039
+ }
3040
+ out.push(para);
3041
+ }
3042
+ return out;
3043
+ }
3044
+ var TITLE_BOX = { x: 8, y: 10, w: 84, h: 18 };
3045
+ var BODY_BOX = { x: 8, y: 32, w: 84, h: 55 };
3046
+ var MAX_SPLIT_PARAGRAPHS = 8;
3047
+ var DESIGN_WIDTH_PX = 1280;
3048
+ var DESIGN_HEIGHT_PX = 720;
3049
+ var DEFAULT_PARA_PX = 24;
3050
+ var LINE_HEIGHT = 1.3;
3051
+ var OVERFLOW_TOLERANCE = 1.05;
3052
+ var OVERFLOW_MIN_SCALE = 0.65;
3053
+ function textWidthEm(text) {
3054
+ let em = 0;
3055
+ for (const ch of text) {
3056
+ em += /[\u1100-\u11ff\u3000-\u9fff\uac00-\ud7af\uf900-\ufaff\uff00-\uffef]/.test(ch) ? 1 : 0.55;
3057
+ }
3058
+ return em;
3059
+ }
3060
+ function estimatedLines(text, sizePx, boxWPct) {
3061
+ const widthPx = Math.max(1, boxWPct / 100 * DESIGN_WIDTH_PX);
3062
+ let lines = 0;
3063
+ for (const seg of text.split("\n")) {
3064
+ lines += Math.max(1, Math.ceil(textWidthEm(seg) * sizePx / widthPx));
3065
+ }
3066
+ return lines;
3067
+ }
3068
+ function fitFontPx(sizePx, text, boxWPct, boxHPct) {
3069
+ const estimated = estimatedLines(text, sizePx, boxWPct) * sizePx * LINE_HEIGHT;
3070
+ const boxPx = boxHPct / 100 * DESIGN_HEIGHT_PX;
3071
+ if (boxPx <= 0 || estimated <= boxPx * OVERFLOW_TOLERANCE) return sizePx;
3072
+ const scale = Math.max(OVERFLOW_MIN_SCALE, boxPx / estimated);
3073
+ return Math.max(6, Math.round(sizePx * scale));
3074
+ }
3075
+ function fontPx(szCenti, fontScale, ctx) {
3076
+ return Math.max(6, Math.round(szCenti / 100 * fontScale * (4 / 3) * (12192e3 / ctx.slideCx)));
3077
+ }
3078
+ function textElements(sp, txBody, map, ctx) {
3079
+ const spPr = childOf2(sp, "spPr");
3080
+ const xfrm = (spPr && xfrmOf(spPr)) ?? inheritedXfrm(sp, ctx);
3081
+ const box = xfrm ? toPercentBox(xfrm, map, ctx) : /^(title|ctrTitle)$/.test(placeholderType(sp) ?? "") ? { ...TITLE_BOX } : { ...BODY_BOX };
3082
+ const rotate = xfrm ? rotationOf(xfrm) : void 0;
3083
+ const bodyPr = childOf2(txBody, "bodyPr");
3084
+ const autofit = bodyPr ? childOf2(bodyPr, "normAutofit") ?? childOf2(bodyPr, "spAutoFit") : null;
3085
+ const rawScale = autofit ? Number(attrOf2(autofit, "fontScale")) : NaN;
3086
+ const fontScale = Number.isFinite(rawScale) && rawScale > 0 ? rawScale / 1e5 : 1;
3087
+ const guard = (sizePx, text, hPct) => (
3088
+ // The guard runs regardless of autofit: PPT's own fontScale is already
3089
+ // applied, but our Korean wrap is wider than PowerPoint's, so even
3090
+ // autofitted boxes can overflow the model.
3091
+ fitFontPx(sizePx, text, box.w, hPct)
3092
+ );
3093
+ const paras = paragraphsOf(txBody, sp, ctx).map((p) => ({
3094
+ ...p,
3095
+ px: p.szCenti !== void 0 ? fontPx(p.szCenti, fontScale, ctx) : void 0
3096
+ }));
3097
+ const filled = paras.filter((p) => p.text.trim());
3098
+ const apply = (el, p) => {
3099
+ if (p.px !== void 0) el.fontSize = p.px;
3100
+ if (p.bold) el.bold = true;
3101
+ if (p.color) el.color = p.color;
3102
+ if (p.align) el.align = p.align;
3103
+ };
3104
+ const distinctSizes = new Set(filled.map((p) => p.px ?? DEFAULT_PARA_PX));
3105
+ const mixedBullets = new Set(filled.map((p) => p.bullet)).size > 1;
3106
+ const split = filled.length >= 2 && paras.length <= MAX_SPLIT_PARAGRAPHS && rotate === void 0 && (distinctSizes.size > 1 || mixedBullets);
3107
+ if (!split) {
3108
+ const el = {
3109
+ id: ctx.nextId(),
3110
+ type: "text",
3111
+ ...box,
3112
+ text: paras.map((p) => p.bullet && p.text.trim() ? `\u2022 ${p.text}` : p.text).join("\n")
3113
+ };
3114
+ const lead = filled[0] ?? paras[0];
3115
+ if (lead) apply(el, lead);
3116
+ if (el.fontSize !== void 0 && el.text) el.fontSize = guard(el.fontSize, el.text, box.h);
3117
+ if (rotate !== void 0) el.rotate = rotate;
3118
+ return [el];
3119
+ }
3120
+ const weights = paras.map((p) => estimatedLines(p.text, p.px ?? DEFAULT_PARA_PX, box.w) * (p.px ?? DEFAULT_PARA_PX));
3121
+ const total = weights.reduce((a, b) => a + b, 0) || 1;
3122
+ const out = [];
3123
+ let used = 0;
3124
+ paras.forEach((p, i) => {
3125
+ const y = box.y + box.h * used / total;
3126
+ used += weights[i];
3127
+ if (!p.text.trim()) return;
3128
+ const el = {
3129
+ id: ctx.nextId(),
3130
+ type: "text",
3131
+ x: box.x,
3132
+ y: round2(y),
3133
+ w: box.w,
3134
+ h: round2(box.h * weights[i] / total),
3135
+ text: p.bullet ? `\u2022 ${p.text}` : p.text
3136
+ };
3137
+ apply(el, p);
3138
+ if (el.fontSize !== void 0) el.fontSize = guard(el.fontSize, el.text ?? "", el.h);
3139
+ out.push(el);
3140
+ });
3141
+ return out;
3142
+ }
3143
+ function mappableShapeOf(prst, isConnector) {
3144
+ if (isConnector) return "line";
3145
+ if (!prst) return null;
3146
+ if (prst === "line" || /^straightConnector\d*$/.test(prst)) return "line";
3147
+ if (prst === "ellipse" || prst === "oval") return "ellipse";
3148
+ if (prst === "rect" || prst === "roundRect") return "rect";
3149
+ return null;
3150
+ }
3151
+ function shapeElement(sp, map, ctx, isConnector) {
3152
+ const colors = ctx.chain.colors;
3153
+ const spPr = childOf2(sp, "spPr");
3154
+ const xfrm = spPr && (xfrmOf(spPr) ?? inheritedXfrm(sp, ctx));
3155
+ if (!spPr || !xfrm) return null;
3156
+ const prst = attrOf2(childOf2(spPr, "prstGeom") ?? spPr, "prst");
3157
+ const shape = mappableShapeOf(prst, isConnector);
3158
+ if (!shape) return null;
3159
+ const el = { id: ctx.nextId(), type: "shape", shape, ...toPercentBox(xfrm, map, ctx) };
3160
+ const ln = childOf2(spPr, "ln");
3161
+ const areaFill = childOf2(spPr, "noFill") ? void 0 : solidFillColor(spPr, colors) ?? gradientFirstStop(spPr, colors) ?? styleRefColor(sp, "fillRef", colors);
3162
+ const lineFill = (ln && !childOf2(ln, "noFill") ? solidFillColor(ln, colors) : void 0) ?? styleRefColor(sp, "lnRef", colors);
3163
+ const fill = shape === "line" ? lineFill ?? areaFill : areaFill;
3164
+ if (fill) el.fill = fill;
3165
+ if (prst === "roundRect") el.radius = 8;
3166
+ el.rotate = rotationOf(xfrm);
3167
+ if (shape === "line" && !(el.w > 3 && el.h > 5)) {
3168
+ if (el.h >= el.w && el.w > 1.2) {
3169
+ el.x = Math.round((el.x + el.w / 2 - 0.3) * 100) / 100;
3170
+ el.w = 0.6;
3171
+ } else if (el.w > el.h && el.h > 1.2) {
3172
+ el.y = Math.round((el.y + el.h / 2 - 0.3) * 100) / 100;
3173
+ el.h = 0.6;
3174
+ }
3175
+ }
3176
+ if (shape === "line" && el.w > 3 && el.h > 5) {
3177
+ const wPx = el.w / 100 * 1280;
3178
+ const hPx = el.h / 100 * 720;
3179
+ const lenPct = Math.hypot(wPx, hPx) / 1280 * 100;
3180
+ const flip = attrOf2(childOf2(spPr, "xfrm") ?? spPr, "flipH") === "1" !== (attrOf2(childOf2(spPr, "xfrm") ?? spPr, "flipV") === "1");
3181
+ const angle = Math.atan2(hPx, wPx) * 180 / Math.PI;
3182
+ const cx = el.x + el.w / 2;
3183
+ const cy = el.y + el.h / 2;
3184
+ el.x = Math.round((cx - lenPct / 2) * 100) / 100;
3185
+ el.y = Math.round((cy - 0.3) * 100) / 100;
3186
+ el.w = Math.round(lenPct * 100) / 100;
3187
+ el.h = 0.6;
3188
+ el.rotate = normalizedRotation((el.rotate ?? 0) + (flip ? -angle : angle));
3189
+ }
3190
+ return el;
3191
+ }
3192
+ function pictureElement(pic, map, ctx) {
3193
+ const spPr = childOf2(pic, "spPr");
3194
+ const xfrm = (spPr && xfrmOf(spPr)) ?? inheritedXfrm(pic, ctx);
3195
+ if (!xfrm) return null;
3196
+ const blip = childOf2(pic, "blipFill") && childOf2(childOf2(pic, "blipFill"), "blip");
3197
+ const embed = blip && attrOf2(blip, "embed");
3198
+ const rel = embed ? ctx.rels.get(embed) : void 0;
3199
+ const src = rel ? ctx.media.dataUrl(rel.target) : null;
3200
+ if (!src) return null;
3201
+ const el = { id: ctx.nextId(), type: "image", ...toPercentBox(xfrm, map, ctx), src };
3202
+ el.rotate = rotationOf(xfrm);
3203
+ return el;
3204
+ }
3205
+ var MAX_TABLE_CELL_ELEMENTS = 60;
3206
+ var TABLE_FONT_PX_CAP = 14;
3207
+ var TABLE_FONT_PX_DEFAULT = 12;
3208
+ function cellText(tc) {
3209
+ const txBody = childOf2(tc, "txBody");
3210
+ return txBody ? txBodyText(txBody).trim() : "";
3211
+ }
3212
+ function tableElements(frame, map, ctx) {
3213
+ try {
3214
+ return tableGrid(frame, map, ctx);
3215
+ } catch {
3216
+ return [];
3217
+ }
3218
+ }
3219
+ function tableGrid(frame, map, ctx) {
3220
+ const graphic = childOf2(frame, "graphic");
3221
+ const data = graphic && childOf2(graphic, "graphicData");
3222
+ const tbl = data && childOf2(data, "tbl");
3223
+ const xfrm = xfrmOf(frame);
3224
+ if (!tbl || !xfrm) return [];
3225
+ const box = toPercentBox(xfrm, map, ctx);
3226
+ const rows = Array.from(tbl.children).filter((c) => c.localName === "tr");
3227
+ const cellsOf = (tr) => Array.from(tr.children).filter((c) => c.localName === "tc");
3228
+ if (!rows.length) return [];
3229
+ const grid = childOf2(tbl, "tblGrid");
3230
+ const colWidths = Array.from(grid?.children ?? []).filter((c) => c.localName === "gridCol").map((c) => Math.max(0, Number(attrOf2(c, "w")) || 0));
3231
+ const colCount = colWidths.length || Math.max(...rows.map((r) => cellsOf(r).length));
3232
+ if (!colCount || !Number.isFinite(colCount)) return [];
3233
+ if (rows.length * colCount > MAX_TABLE_CELL_ELEMENTS) {
3234
+ const text = rows.map((r) => cellsOf(r).map(cellText).join(" ")).join("\n").trim();
3235
+ if (!text) return [];
3236
+ return [{ id: ctx.nextId(), type: "text", ...box, text, fontSize: TABLE_FONT_PX_DEFAULT }];
3237
+ }
3238
+ const colSum = colWidths.reduce((a, b) => a + b, 0);
3239
+ const colFrac = colSum > 0 ? colWidths.map((w) => w / colSum) : Array(colCount).fill(1 / colCount);
3240
+ const rowHeights = rows.map((r) => Math.max(0, Number(attrOf2(r, "h")) || 0));
3241
+ const rowSum = rowHeights.reduce((a, b) => a + b, 0);
3242
+ const rowFrac = rowSum > 0 ? rowHeights.map((h) => h / rowSum) : rows.map(() => 1 / rows.length);
3243
+ const out = [];
3244
+ let yFrac = 0;
3245
+ rows.forEach((tr, ri) => {
3246
+ let col = 0;
3247
+ for (const tc of cellsOf(tr)) {
3248
+ const span = Math.max(1, Number(attrOf2(tc, "gridSpan")) || 1);
3249
+ const from = col;
3250
+ col += span;
3251
+ if (isOn(attrOf2(tc, "hMerge")) || isOn(attrOf2(tc, "vMerge")) || from >= colCount) continue;
3252
+ const text = cellText(tc);
3253
+ if (!text) continue;
3254
+ const xFrac = colFrac.slice(0, from).reduce((a, b) => a + b, 0);
3255
+ const wFrac = colFrac.slice(from, Math.min(from + span, colCount)).reduce((a, b) => a + b, 0);
3256
+ const el = {
3257
+ id: ctx.nextId(),
3258
+ type: "text",
3259
+ x: round2(box.x + box.w * xFrac),
3260
+ y: round2(box.y + box.h * yFrac),
3261
+ w: round2(box.w * wFrac),
3262
+ h: round2(box.h * rowFrac[ri]),
3263
+ text
3264
+ };
3265
+ const txBody = childOf2(tc, "txBody");
3266
+ const para = txBody ? paragraphsOf(txBody, tc, ctx).find((p) => p.text.trim()) : void 0;
3267
+ const sizePx = para?.szCenti !== void 0 ? fontPx(para.szCenti, 1, ctx) : TABLE_FONT_PX_DEFAULT;
3268
+ el.fontSize = Math.min(sizePx, TABLE_FONT_PX_CAP);
3269
+ if (para?.bold) el.bold = true;
3270
+ if (para?.color) el.color = para.color;
3271
+ if (para?.align) el.align = para.align;
3272
+ out.push(el);
3273
+ }
3274
+ yFrac += rowFrac[ri];
3275
+ });
3276
+ return out;
3277
+ }
3278
+ function collectElements(tree, map, ctx, out) {
3279
+ for (const child of Array.from(tree.children)) {
3280
+ if (child.localName === "sp") {
3281
+ const txBody = childOf2(child, "txBody");
3282
+ if (txBody && txBodyText(txBody).trim()) {
3283
+ out.push(...textElements(child, txBody, map, ctx));
3284
+ } else {
3285
+ const el = shapeElement(child, map, ctx, false);
3286
+ if (el) out.push(el);
3287
+ }
3288
+ } else if (child.localName === "cxnSp") {
3289
+ const el = shapeElement(child, map, ctx, true);
3290
+ if (el) out.push(el);
3291
+ } else if (child.localName === "pic") {
3292
+ const el = pictureElement(child, map, ctx);
3293
+ if (el) out.push(el);
3294
+ } else if (child.localName === "graphicFrame") {
3295
+ out.push(...tableElements(child, map, ctx));
3296
+ } else if (child.localName === "grpSp") {
3297
+ collectElements(child, groupChildMap(child, map), ctx, out);
3298
+ }
3299
+ }
3300
+ }
3301
+ function backgroundOf(doc, colors) {
3302
+ const cSld = doc?.getElementsByTagNameNS("*", "cSld")[0];
3303
+ const bg = cSld ? childOf2(cSld, "bg") : null;
3304
+ if (!bg) return void 0;
3305
+ const bgPr = childOf2(bg, "bgPr");
3306
+ if (bgPr) return solidFillColor(bgPr, colors) ?? gradientFirstStop(bgPr, colors);
3307
+ const bgRef = childOf2(bg, "bgRef");
3308
+ if (!bgRef || attrOf2(bgRef, "idx") === "0") return void 0;
3309
+ for (const c of Array.from(bgRef.children)) {
3310
+ const hex = colors.resolve(c);
3311
+ if (hex) return hex;
3312
+ }
3313
+ return void 0;
3314
+ }
3315
+ var NOTES_CHROME = /^(sldNum|sldImg|hdr|ftr|dt)$/;
3316
+ function notesFor(entries, slideName, rels) {
3317
+ let notesName = relOfType(rels, "/notesSlide")?.target;
3318
+ if (!notesName) {
3319
+ const num = /(\d+)\.xml$/.exec(slideName)?.[1];
3320
+ if (num) notesName = `ppt/notesSlides/notesSlide${num}.xml`;
3321
+ }
3322
+ const part = notesName ? entries.get(notesName) : void 0;
3323
+ const doc = part ? parseXml2(new TextDecoder().decode(part)) : null;
3324
+ if (!doc) return void 0;
3325
+ const texts = [];
3326
+ for (const sp of Array.from(doc.getElementsByTagNameNS("*", "sp"))) {
3327
+ if (NOTES_CHROME.test(placeholderType(sp) ?? "")) continue;
3328
+ const txBody = childOf2(sp, "txBody");
3329
+ const text = txBody ? txBodyText(txBody).trim() : "";
3330
+ if (text) texts.push(text);
3331
+ }
3332
+ return texts.length ? texts.join("\n") : void 0;
3333
+ }
3334
+ function orderedSlideNames(entries) {
3335
+ const presPart = entries.get("ppt/presentation.xml");
3336
+ const doc = presPart ? parseXml2(new TextDecoder().decode(presPart)) : null;
3337
+ if (doc) {
3338
+ const rels = relsOf(entries, "ppt/presentation.xml");
3339
+ const names = [];
3340
+ for (const sldId of Array.from(doc.getElementsByTagNameNS("*", "sldId"))) {
3341
+ const rel = relIdOf(sldId) ? rels.get(relIdOf(sldId)) : void 0;
3342
+ if (rel && entries.has(rel.target)) names.push(rel.target);
3343
+ }
3344
+ if (names.length) return names;
3345
+ }
3346
+ return [...entries.keys()].filter((n) => /^ppt\/slides\/slide\d+\.xml$/i.test(n)).sort((a, b) => Number(/(\d+)\.xml$/.exec(a)?.[1] ?? 0) - Number(/(\d+)\.xml$/.exec(b)?.[1] ?? 0));
3347
+ }
3348
+ function slideSizeOf(entries) {
3349
+ const presPart = entries.get("ppt/presentation.xml");
3350
+ const doc = presPart ? parseXml2(new TextDecoder().decode(presPart)) : null;
3351
+ const sldSz = doc && doc.getElementsByTagNameNS("*", "sldSz")[0];
3352
+ const cx = sldSz ? Number(attrOf2(sldSz, "cx")) : NaN;
3353
+ const cy = sldSz ? Number(attrOf2(sldSz, "cy")) : NaN;
3354
+ return { cx: cx > 0 ? cx : DEFAULT_SLIDE_CX, cy: cy > 0 ? cy : DEFAULT_SLIDE_CY };
3355
+ }
3356
+ async function pptxToSlides(buffer) {
3357
+ const entries = await readZip(buffer);
3358
+ const names = orderedSlideNames(entries);
3359
+ if (!names.length) {
3360
+ throw new Error("PPTX \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uC2AC\uB77C\uC774\uB4DC(ppt/slides)\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 (Not a PPTX document \u2014 no slides found).");
3361
+ }
3362
+ const { cx, cy } = slideSizeOf(entries);
3363
+ const media = new MediaResolver(entries);
3364
+ const partCache = /* @__PURE__ */ new Map();
3365
+ const decoder = new TextDecoder();
3366
+ let seq = 0;
3367
+ const slides2 = [];
3368
+ for (const name of names) {
3369
+ const doc = parseXml2(decoder.decode(entries.get(name)));
3370
+ if (!doc) continue;
3371
+ const rels = relsOf(entries, name);
3372
+ const chain = chainFor(entries, rels, partCache);
3373
+ const ctx = { slideCx: cx, slideCy: cy, rels, media, chain, nextId: () => `el_${++seq}` };
3374
+ const elements = [];
3375
+ const spTree = doc.getElementsByTagNameNS("*", "spTree")[0];
3376
+ if (spTree) collectElements(spTree, IDENTITY_MAP, ctx, elements);
3377
+ const slide = { layout: "blank", elements };
3378
+ const background = backgroundOf(doc, chain.colors) ?? backgroundOf(chain.layoutDoc, chain.colors) ?? backgroundOf(chain.masterDoc, chain.colors);
3379
+ if (background) slide.background = background;
3380
+ const notes = notesFor(entries, name, rels);
3381
+ if (notes) slide.notes = notes;
3382
+ slides2.push(slide);
3383
+ }
3384
+ if (!slides2.length) {
3385
+ throw new Error("PPTX \uC2AC\uB77C\uC774\uB4DC XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (Not a readable PPTX \u2014 every slide failed to parse).");
3386
+ }
3387
+ return { slides: slides2 };
2023
3388
  }
2024
3389
 
2025
3390
  // src/io/importers.ts
@@ -2034,7 +3399,9 @@ var IMPORTABLE_EXTENSIONS = [
2034
3399
  ".xlsx",
2035
3400
  ".pdf",
2036
3401
  ".hwpx",
2037
- ".hwp"
3402
+ ".hwp",
3403
+ ".docx",
3404
+ ".pptx"
2038
3405
  ];
2039
3406
  var extensionOf = (name) => {
2040
3407
  const dot = name.lastIndexOf(".");
@@ -2081,12 +3448,20 @@ async function importFile(file) {
2081
3448
  return toEvents(artifact(id, "pdf", title, { src, filename: file.name }));
2082
3449
  }
2083
3450
  case ".hwpx": {
2084
- const content = await hwpxToMarkdown(await file.arrayBuffer());
3451
+ const html = await hwpxToHtml(await file.arrayBuffer());
3452
+ return toEvents({ ...artifact(id, "html", title, { html }), meta: { kind: "doc" } });
3453
+ }
3454
+ case ".docx": {
3455
+ const content = await docxToMarkdown(await file.arrayBuffer());
2085
3456
  return toEvents(artifact(id, "document", title, { format: "markdown", content }));
2086
3457
  }
3458
+ case ".pptx": {
3459
+ const data = await pptxToSlides(await file.arrayBuffer());
3460
+ return toEvents(artifact(id, "slides", title, data));
3461
+ }
2087
3462
  case ".hwp": {
2088
- const content = await hwpToText(await file.arrayBuffer());
2089
- return toEvents(artifact(id, "document", title, { format: "markdown", content }));
3463
+ const html = await hwpToHtml(await file.arrayBuffer());
3464
+ return toEvents({ ...artifact(id, "html", title, { html }), meta: { kind: "doc" } });
2090
3465
  }
2091
3466
  default:
2092
3467
  throw new Error(`Unsupported file type "${ext || file.name}". Supported: ${IMPORTABLE_EXTENSIONS.join(", ")}`);
@@ -2175,6 +3550,8 @@ var DEVICES = [
2175
3550
  { id: "tablet", label: "Tablet", width: "768px" },
2176
3551
  { id: "mobile", label: "Mobile", width: "390px" }
2177
3552
  ];
3553
+ var BLOCK_KEYS = { h2: "heading", p: "text", button: "button", img: "image", hr: "divider" };
3554
+ var DEVICE_KEYS = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
2178
3555
  var BLOCKS = [
2179
3556
  { tag: "h2", label: "Heading" },
2180
3557
  { tag: "p", label: "Text" },
@@ -2447,6 +3824,7 @@ function useSlideFit(ratio, boxRef) {
2447
3824
  return { scale, width, height: SLIDE_H };
2448
3825
  }
2449
3826
  function HtmlRenderer({ artifact: artifact2 }) {
3827
+ const t = useT();
2450
3828
  const iframeRef = useRef(null);
2451
3829
  const imgFileRef = useRef(null);
2452
3830
  const bgFileRef = useRef(null);
@@ -2546,6 +3924,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
2546
3924
  };
2547
3925
  const setSlideStyle = (style) => sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style });
2548
3926
  const ratio = artifact2.meta?.ratio;
3927
+ const isDoc = !ratio && artifact2.meta?.kind === "doc";
2549
3928
  const slide = useSlideFit(ratio, stageRef);
2550
3929
  return /* @__PURE__ */ jsxs("div", { className: "cv-html-wrap", children: [
2551
3930
  /* @__PURE__ */ jsx("input", { ref: imgFileRef, type: "file", accept: "image/*", hidden: true, onChange: (e) => {
@@ -2558,26 +3937,30 @@ function HtmlRenderer({ artifact: artifact2 }) {
2558
3937
  } }),
2559
3938
  /* @__PURE__ */ jsxs("div", { className: "cv-html-bar cv-chrome", children: [
2560
3939
  mode === "design" && /* @__PURE__ */ jsxs(Fragment, { children: [
2561
- !ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
2562
- /* @__PURE__ */ jsx("div", { className: "cv-html-seg", role: "group", "aria-label": "Preview width", children: DEVICES.map((d) => /* @__PURE__ */ jsx("button", { className: device === d.id ? "is-on" : "", onClick: () => setDevice(d.id), children: d.label }, d.id)) }),
3940
+ !ratio && !isDoc && /* @__PURE__ */ jsxs(Fragment, { children: [
3941
+ /* @__PURE__ */ jsx("div", { className: "cv-html-seg", role: "group", "aria-label": "Preview width", children: DEVICES.map((d) => /* @__PURE__ */ jsx("button", { className: device === d.id ? "is-on" : "", onClick: () => setDevice(d.id), children: DEVICE_KEYS[d.id] ? t(DEVICE_KEYS[d.id]) : d.label }, d.id)) }),
2563
3942
  /* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" })
2564
3943
  ] }),
2565
- /* @__PURE__ */ jsx("span", { className: "cv-html-bar__label", children: "Add" }),
2566
- (ratio ? BLOCKS.filter((b) => SLIDE_BLOCK_TAGS.has(b.tag)) : BLOCKS).map((b) => /* @__PURE__ */ jsx("button", { className: "cv-html-add", onClick: () => command("insert", { block: b.tag }), children: b.label }, b.tag)),
2567
- !ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
3944
+ isDoc && /* @__PURE__ */ jsxs("span", { className: "cv-html-doc-chip", title: t("kindDocument"), children: [
3945
+ "\u{1F4C4} ",
3946
+ t("kindDocument")
3947
+ ] }),
3948
+ /* @__PURE__ */ jsx("span", { className: "cv-html-bar__label", children: t("add") }),
3949
+ (ratio || isDoc ? BLOCKS.filter((b) => SLIDE_BLOCK_TAGS.has(b.tag)) : BLOCKS).map((b) => /* @__PURE__ */ jsx("button", { className: "cv-html-add", onClick: () => command("insert", { block: b.tag }), children: BLOCK_KEYS[b.tag] ? t(BLOCK_KEYS[b.tag]) : b.label }, b.tag)),
3950
+ !ratio && !isDoc && /* @__PURE__ */ jsxs(Fragment, { children: [
2568
3951
  /* @__PURE__ */ jsxs(
2569
3952
  "select",
2570
3953
  {
2571
3954
  className: "cv-html-tpl",
2572
3955
  value: "",
2573
- title: "Start from a full page template (replaces the page)",
3956
+ title: t("pageTemplateTitle"),
2574
3957
  onChange: (e) => {
2575
3958
  const s = STARTERS[e.target.value];
2576
3959
  if (s) applyEvent({ type: "canvas.patch", id: artifact2.id, patch: { html: s.build() } });
2577
3960
  e.currentTarget.value = "";
2578
3961
  },
2579
3962
  children: [
2580
- /* @__PURE__ */ jsx("option", { value: "", children: "Page\u2026" }),
3963
+ /* @__PURE__ */ jsx("option", { value: "", children: t("pageMenu") }),
2581
3964
  Object.entries(STARTERS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
2582
3965
  ]
2583
3966
  }
@@ -2587,14 +3970,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
2587
3970
  {
2588
3971
  className: "cv-html-tpl",
2589
3972
  value: "",
2590
- title: "Insert a section template",
3973
+ title: t("sectionTemplateTitle"),
2591
3974
  onChange: (e) => {
2592
- const t = TEMPLATES[e.target.value];
2593
- if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
3975
+ const t2 = TEMPLATES[e.target.value];
3976
+ if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
2594
3977
  e.currentTarget.value = "";
2595
3978
  },
2596
3979
  children: [
2597
- /* @__PURE__ */ jsx("option", { value: "", children: "Section\u2026" }),
3980
+ /* @__PURE__ */ jsx("option", { value: "", children: t("sectionMenu") }),
2598
3981
  Object.entries(TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
2599
3982
  ]
2600
3983
  }
@@ -2604,34 +3987,51 @@ function HtmlRenderer({ artifact: artifact2 }) {
2604
3987
  {
2605
3988
  className: "cv-html-tpl",
2606
3989
  value: "",
2607
- title: "Jump to a heading",
3990
+ title: t("jumpToHeading"),
2608
3991
  onChange: (e) => {
2609
3992
  const idx = Number(e.target.value);
2610
3993
  if (!Number.isNaN(idx)) sendIframeCommand({ artifactId: artifact2.id, type: "scroll_to", index: idx });
2611
3994
  e.currentTarget.value = "";
2612
3995
  },
2613
3996
  children: [
2614
- /* @__PURE__ */ jsx("option", { value: "", children: "Outline\u2026" }),
3997
+ /* @__PURE__ */ jsx("option", { value: "", children: t("outlineMenu") }),
2615
3998
  outline.map((h, i) => /* @__PURE__ */ jsx("option", { value: i, children: "\xA0".repeat((h.level - 1) * 2) + h.text }, i))
2616
3999
  ]
2617
4000
  }
2618
4001
  ),
2619
- /* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "Accessibility check", onClick: () => setA11y(checkA11y(artifact2.data.html)), children: "\u267F Check" })
4002
+ /* @__PURE__ */ jsx("button", { className: "cv-html-add", title: t("a11yTitle"), onClick: () => setA11y(checkA11y(artifact2.data.html)), children: "\u267F Check" })
2620
4003
  ] }),
4004
+ isDoc && outline.length > 0 && /* @__PURE__ */ jsxs(
4005
+ "select",
4006
+ {
4007
+ className: "cv-html-tpl",
4008
+ value: "",
4009
+ title: t("jumpToHeading"),
4010
+ onChange: (e) => {
4011
+ const idx = Number(e.target.value);
4012
+ if (!Number.isNaN(idx)) sendIframeCommand({ artifactId: artifact2.id, type: "scroll_to", index: idx });
4013
+ e.currentTarget.value = "";
4014
+ },
4015
+ children: [
4016
+ /* @__PURE__ */ jsx("option", { value: "", children: t("outlineMenu") }),
4017
+ outline.map((h, i) => /* @__PURE__ */ jsx("option", { value: i, children: h.text }, `d${i}`))
4018
+ ]
4019
+ }
4020
+ ),
2621
4021
  ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
2622
4022
  /* @__PURE__ */ jsxs(
2623
4023
  "select",
2624
4024
  {
2625
4025
  className: "cv-html-tpl",
2626
4026
  value: "",
2627
- title: "Insert a slide layout",
4027
+ title: t("insertSlideLayout"),
2628
4028
  onChange: (e) => {
2629
- const t = SLIDE_TEMPLATES[e.target.value];
2630
- if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
4029
+ const t2 = SLIDE_TEMPLATES[e.target.value];
4030
+ if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
2631
4031
  e.currentTarget.value = "";
2632
4032
  },
2633
4033
  children: [
2634
- /* @__PURE__ */ jsx("option", { value: "", children: "Layout\u2026" }),
4034
+ /* @__PURE__ */ jsx("option", { value: "", children: t("layoutMenu") }),
2635
4035
  Object.entries(SLIDE_TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
2636
4036
  ]
2637
4037
  }
@@ -2641,14 +4041,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
2641
4041
  {
2642
4042
  className: "cv-html-tpl",
2643
4043
  value: "",
2644
- title: "Apply a slide theme",
4044
+ title: t("applySlideTheme"),
2645
4045
  onChange: (e) => {
2646
- const t = SLIDE_THEMES[e.target.value];
2647
- if (t) sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style: t.style });
4046
+ const t2 = SLIDE_THEMES[e.target.value];
4047
+ if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style: t2.style });
2648
4048
  e.currentTarget.value = "";
2649
4049
  },
2650
4050
  children: [
2651
- /* @__PURE__ */ jsx("option", { value: "", children: "Theme\u2026" }),
4051
+ /* @__PURE__ */ jsx("option", { value: "", children: t("themeMenu") }),
2652
4052
  Object.entries(SLIDE_THEMES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
2653
4053
  ]
2654
4054
  }
@@ -2658,14 +4058,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
2658
4058
  {
2659
4059
  className: "cv-html-tpl",
2660
4060
  value: "",
2661
- title: "Insert a shape",
4061
+ title: t("insertShape"),
2662
4062
  onChange: (e) => {
2663
- const t = SLIDE_SHAPES[e.target.value];
2664
- if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
4063
+ const t2 = SLIDE_SHAPES[e.target.value];
4064
+ if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
2665
4065
  e.currentTarget.value = "";
2666
4066
  },
2667
4067
  children: [
2668
- /* @__PURE__ */ jsx("option", { value: "", children: "Shape\u2026" }),
4068
+ /* @__PURE__ */ jsx("option", { value: "", children: t("shapeMenu") }),
2669
4069
  Object.entries(SLIDE_SHAPES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
2670
4070
  ]
2671
4071
  }
@@ -2675,19 +4075,19 @@ function HtmlRenderer({ artifact: artifact2 }) {
2675
4075
  {
2676
4076
  className: "cv-html-tpl",
2677
4077
  value: "",
2678
- title: "Slide font",
4078
+ title: t("slideFont"),
2679
4079
  onChange: (e) => {
2680
4080
  const f = SLIDE_FONTS[e.target.value];
2681
4081
  if (f) setSlideStyle({ fontFamily: f.stack });
2682
4082
  e.currentTarget.value = "";
2683
4083
  },
2684
4084
  children: [
2685
- /* @__PURE__ */ jsx("option", { value: "", children: "Font\u2026" }),
4085
+ /* @__PURE__ */ jsx("option", { value: "", children: t("fontMenu") }),
2686
4086
  Object.entries(SLIDE_FONTS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
2687
4087
  ]
2688
4088
  }
2689
4089
  ),
2690
- /* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "Slide background image", onClick: () => bgFileRef.current?.click(), children: "\u{1F5BC} BG" })
4090
+ /* @__PURE__ */ jsx("button", { className: "cv-html-add", title: t("slideBgImage"), onClick: () => bgFileRef.current?.click(), children: "\u{1F5BC} BG" })
2691
4091
  ] }),
2692
4092
  selected.length >= 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
2693
4093
  /* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" }),
@@ -2717,33 +4117,33 @@ function HtmlRenderer({ artifact: artifact2 }) {
2717
4117
  )
2718
4118
  ] }),
2719
4119
  single && single.tag !== "img" && /* @__PURE__ */ jsxs(Fragment, { children: [
2720
- /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align left", onClick: () => command("style_persist", { prop: "textAlign", value: "left" }), children: "\u2B05" }),
2721
- /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align center", onClick: () => command("style_persist", { prop: "textAlign", value: "center" }), children: "\u2B0C" }),
2722
- /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align right", onClick: () => command("style_persist", { prop: "textAlign", value: "right" }), children: "\u27A1" }),
2723
- /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Bold", onClick: () => command("style_persist", { prop: "fontWeight", value: "800" }), children: "B" })
4120
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: t("alignLeft"), onClick: () => command("style_persist", { prop: "textAlign", value: "left" }), children: "\u2B05" }),
4121
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: t("alignCenter"), onClick: () => command("style_persist", { prop: "textAlign", value: "center" }), children: "\u2B0C" }),
4122
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: t("alignRight"), onClick: () => command("style_persist", { prop: "textAlign", value: "right" }), children: "\u27A1" }),
4123
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: t("bold"), onClick: () => command("style_persist", { prop: "fontWeight", value: "800" }), children: "B" })
2724
4124
  ] }),
2725
4125
  single && /* @__PURE__ */ jsxs(Fragment, { children: [
2726
- /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Duplicate", onClick: () => command("duplicate"), children: "\u29C9" }),
2727
- /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Move up", onClick: () => command("move_up"), children: "\u2191" }),
2728
- /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Move down", onClick: () => command("move_down"), children: "\u2193" }),
2729
- /* @__PURE__ */ jsx("button", { className: "cv-html-act cv-html-act--del", title: "Delete", onClick: () => command("delete"), children: "\u{1F5D1}" })
4126
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: t("duplicate"), onClick: () => command("duplicate"), children: "\u29C9" }),
4127
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: t("moveUp"), onClick: () => command("move_up"), children: "\u2191" }),
4128
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: t("moveDown"), onClick: () => command("move_down"), children: "\u2193" }),
4129
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act cv-html-act--del", title: t("delete"), onClick: () => command("delete"), children: "\u{1F5D1}" })
2730
4130
  ] })
2731
4131
  ] })
2732
4132
  ] }),
2733
4133
  /* @__PURE__ */ jsx("span", { className: "cv-html-bar__spacer" }),
2734
4134
  /* @__PURE__ */ jsxs("div", { className: "cv-html-seg", role: "group", "aria-label": "View mode", children: [
2735
- /* @__PURE__ */ jsx("button", { className: mode === "design" ? "is-on" : "", onClick: () => setMode("design"), children: "Design" }),
2736
- /* @__PURE__ */ jsx("button", { className: mode === "code" ? "is-on" : "", onClick: () => setMode("code"), children: "Code" })
4135
+ /* @__PURE__ */ jsx("button", { className: mode === "design" ? "is-on" : "", onClick: () => setMode("design"), children: t("design") }),
4136
+ /* @__PURE__ */ jsx("button", { className: mode === "code" ? "is-on" : "", onClick: () => setMode("code"), children: t("code") })
2737
4137
  ] })
2738
4138
  ] }),
2739
4139
  a11y !== null && /* @__PURE__ */ jsxs("div", { className: "cv-a11y", role: "status", children: [
2740
4140
  /* @__PURE__ */ jsx("button", { className: "cv-a11y__close", onClick: () => setA11y(null), "aria-label": "Dismiss", children: "\xD7" }),
2741
- a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children: "\u267F No accessibility issues found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4141
+ a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children: t("a11yNone") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2742
4142
  /* @__PURE__ */ jsxs("b", { children: [
2743
4143
  "\u267F ",
2744
4144
  a11y.length,
2745
- " accessibility issue",
2746
- a11y.length > 1 ? "s" : ""
4145
+ " ",
4146
+ t("a11yIssues")
2747
4147
  ] }),
2748
4148
  /* @__PURE__ */ jsx("ul", { children: a11y.map((m, i) => /* @__PURE__ */ jsx("li", { children: m }, i)) })
2749
4149
  ] })
@@ -2803,11 +4203,11 @@ function CodePane({ html, onCommit }) {
2803
4203
  }
2804
4204
 
2805
4205
  // src/components/renderers/index.ts
2806
- var ChartRenderer = lazy(() => import('./ChartRenderer-AAWVGKV5.js').then((m) => ({ default: m.ChartRenderer })));
2807
- var DocumentRenderer = lazy(() => import('./DocumentRenderer-NBRBPYU6.js').then((m) => ({ default: m.DocumentRenderer })));
2808
- var TableRenderer = lazy(() => import('./TableRenderer-VKDD3CB6.js').then((m) => ({ default: m.TableRenderer })));
2809
- var SlidesRenderer = lazy(() => import('./SlidesRenderer-6ZGHXTQX.js').then((m) => ({ default: m.SlidesRenderer })));
2810
- var PdfRenderer = lazy(() => import('./PdfRenderer-DPQT4E7O.js').then((m) => ({ default: m.PdfRenderer })));
4206
+ var ChartRenderer = lazy(() => import('./ChartRenderer-ABRQ5YFN.js').then((m) => ({ default: m.ChartRenderer })));
4207
+ var DocumentRenderer = lazy(() => import('./DocumentRenderer-YTEMC3Z4.js').then((m) => ({ default: m.DocumentRenderer })));
4208
+ var TableRenderer = lazy(() => import('./TableRenderer-BJQE2HMO.js').then((m) => ({ default: m.TableRenderer })));
4209
+ var SlidesRenderer = lazy(() => import('./SlidesRenderer-JNPXNTNJ.js').then((m) => ({ default: m.SlidesRenderer })));
4210
+ var PdfRenderer = lazy(() => import('./PdfRenderer-GDA67MES.js').then((m) => ({ default: m.PdfRenderer })));
2811
4211
  var builtinRenderers = {
2812
4212
  html: HtmlRenderer,
2813
4213
  document: DocumentRenderer,
@@ -2833,6 +4233,232 @@ function slugify(text) {
2833
4233
  return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "artifact";
2834
4234
  }
2835
4235
 
4236
+ // src/io/hwpxWrite.ts
4237
+ var CRC_TABLE = (() => {
4238
+ const table2 = new Uint32Array(256);
4239
+ for (let n = 0; n < 256; n++) {
4240
+ let c = n;
4241
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
4242
+ table2[n] = c >>> 0;
4243
+ }
4244
+ return table2;
4245
+ })();
4246
+ function crc32(bytes) {
4247
+ let c = 4294967295;
4248
+ for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
4249
+ return (c ^ 4294967295) >>> 0;
4250
+ }
4251
+ function storeZip(parts) {
4252
+ const dosTime = 0;
4253
+ const dosDate = 2026 - 1980 << 9 | 1 << 5 | 1;
4254
+ const enc = new TextEncoder();
4255
+ const chunks = [];
4256
+ const central = [];
4257
+ let offset = 0;
4258
+ for (const [name, data] of parts) {
4259
+ const nameBytes = enc.encode(name);
4260
+ const crc = crc32(data);
4261
+ const local = new Uint8Array(30 + nameBytes.length);
4262
+ const lv = new DataView(local.buffer);
4263
+ lv.setUint32(0, 67324752, true);
4264
+ lv.setUint16(4, 20, true);
4265
+ lv.setUint16(8, 0, true);
4266
+ lv.setUint16(10, dosTime, true);
4267
+ lv.setUint16(12, dosDate, true);
4268
+ lv.setUint32(14, crc, true);
4269
+ lv.setUint32(18, data.length, true);
4270
+ lv.setUint32(22, data.length, true);
4271
+ lv.setUint16(26, nameBytes.length, true);
4272
+ local.set(nameBytes, 30);
4273
+ chunks.push(local, data);
4274
+ const cdir = new Uint8Array(46 + nameBytes.length);
4275
+ const cv = new DataView(cdir.buffer);
4276
+ cv.setUint32(0, 33639248, true);
4277
+ cv.setUint16(4, 20, true);
4278
+ cv.setUint16(6, 20, true);
4279
+ cv.setUint16(10, 0, true);
4280
+ cv.setUint16(12, dosTime, true);
4281
+ cv.setUint16(14, dosDate, true);
4282
+ cv.setUint32(16, crc, true);
4283
+ cv.setUint32(20, data.length, true);
4284
+ cv.setUint32(24, data.length, true);
4285
+ cv.setUint16(28, nameBytes.length, true);
4286
+ cv.setUint32(42, offset, true);
4287
+ cdir.set(nameBytes, 46);
4288
+ central.push(cdir);
4289
+ offset += local.length + data.length;
4290
+ }
4291
+ const cdirSize = central.reduce((n, c) => n + c.length, 0);
4292
+ const eocd = new Uint8Array(22);
4293
+ const ev = new DataView(eocd.buffer);
4294
+ ev.setUint32(0, 101010256, true);
4295
+ ev.setUint16(8, parts.length, true);
4296
+ ev.setUint16(10, parts.length, true);
4297
+ ev.setUint32(12, cdirSize, true);
4298
+ ev.setUint32(16, offset, true);
4299
+ const all = [...chunks, ...central, eocd];
4300
+ const out = new Uint8Array(all.reduce((n, c) => n + c.length, 0));
4301
+ let pos = 0;
4302
+ for (const c of all) {
4303
+ out.set(c, pos);
4304
+ pos += c.length;
4305
+ }
4306
+ return out;
4307
+ }
4308
+ var XML_DECL = `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>`;
4309
+ var HWPML_NS = [
4310
+ `xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app"`,
4311
+ `xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph"`,
4312
+ `xmlns:hp10="http://www.hancom.co.kr/hwpml/2016/paragraph"`,
4313
+ `xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section"`,
4314
+ `xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core"`,
4315
+ `xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head"`,
4316
+ `xmlns:hhs="http://www.hancom.co.kr/hwpml/2011/history"`,
4317
+ `xmlns:hm="http://www.hancom.co.kr/hwpml/2011/master-page"`,
4318
+ `xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf"`,
4319
+ `xmlns:dc="http://purl.org/dc/elements/1.1/"`,
4320
+ `xmlns:opf="http://www.idpf.org/2007/opf/"`,
4321
+ `xmlns:ooxmlchart="http://www.hancom.co.kr/hwpml/2016/ooxmlchart"`,
4322
+ `xmlns:hwpunitchar="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar"`,
4323
+ `xmlns:epub="http://www.idpf.org/2007/ops"`,
4324
+ `xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"`
4325
+ ].join(" ");
4326
+ var VERSION_XML = XML_DECL + `<hv:HCFVersion xmlns:hv="http://www.hancom.co.kr/hwpml/2011/version" tagetApplication="WORDPROCESSOR" major="5" minor="0" micro="5" buildNumber="0" xmlVersion="1.4" application="langchain-canvas" appVersion="1.0"/>`;
4327
+ var CONTAINER_XML = XML_DECL + `<ocf:container xmlns:ocf="urn:oasis:names:tc:opendocument:xmlns:container" xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf"><ocf:rootfiles><ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/></ocf:rootfiles></ocf:container>`;
4328
+ var MANIFEST_XML = XML_DECL + `<odf:manifest xmlns:odf="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"/>`;
4329
+ var CONTENT_HPF = XML_DECL + `<opf:package ${HWPML_NS} version="" unique-identifier="" id=""><opf:metadata><opf:title/><opf:language>ko</opf:language></opf:metadata><opf:manifest><opf:item id="header" href="Contents/header.xml" media-type="application/xml"/><opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/><opf:item id="settings" href="settings.xml" media-type="application/xml"/></opf:manifest><opf:spine><opf:itemref idref="header"/><opf:itemref idref="section0"/></opf:spine></opf:package>`;
4330
+ var SETTINGS_XML = XML_DECL + `<ha:HWPApplicationSetting xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"><ha:CaretPosition listIDRef="0" paraIDRef="0" pos="0"/></ha:HWPApplicationSetting>`;
4331
+ function fontfaces() {
4332
+ const langs = ["HANGUL", "LATIN", "HANJA", "JAPANESE", "OTHER", "SYMBOL", "USER"];
4333
+ const face = (lang) => `<hh:fontface lang="${lang}" fontCnt="1"><hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="8" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font></hh:fontface>`;
4334
+ return `<hh:fontfaces itemCnt="7">${langs.map(face).join("")}</hh:fontfaces>`;
4335
+ }
4336
+ function borderFills() {
4337
+ const fill = (id) => `<hh:borderFill id="${id}" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0"><hh:slash type="NONE" Crooked="0" isCounter="0"/><hh:backSlash type="NONE" Crooked="0" isCounter="0"/><hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/><hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/><hh:topBorder type="NONE" width="0.1 mm" color="#000000"/><hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/><hh:diagonal type="SOLID" width="0.1 mm" color="#000000"/></hh:borderFill>`;
4338
+ return `<hh:borderFills itemCnt="2">${fill(1)}${fill(2)}</hh:borderFills>`;
4339
+ }
4340
+ var CHAR_PR = [
4341
+ { id: 0, height: 1e3, bold: false, italic: false },
4342
+ // body
4343
+ { id: 1, height: 1800, bold: true, italic: false },
4344
+ // heading 1
4345
+ { id: 2, height: 1500, bold: true, italic: false },
4346
+ // heading 2
4347
+ { id: 3, height: 1200, bold: true, italic: false },
4348
+ // heading 3
4349
+ { id: 4, height: 1e3, bold: true, italic: false },
4350
+ // inline bold
4351
+ { id: 5, height: 1e3, bold: false, italic: true }
4352
+ // inline italic
4353
+ ];
4354
+ function charProperties() {
4355
+ const one = (c) => `<hh:charPr id="${c.id}" height="${c.height}" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2"><hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>` + (c.bold ? `<hh:bold/>` : "") + (c.italic ? `<hh:italic/>` : "") + `<hh:underline type="NONE" shape="SOLID" color="#000000"/><hh:strikeout shape="NONE" color="#000000"/><hh:outline type="NONE"/><hh:shadow type="NONE" color="#B2B2B2" offsetX="10" offsetY="10"/></hh:charPr>`;
4356
+ return `<hh:charProperties itemCnt="${CHAR_PR.length}">${CHAR_PR.map(one).join("")}</hh:charProperties>`;
4357
+ }
4358
+ function numberings() {
4359
+ const heads = Array.from(
4360
+ { length: 7 },
4361
+ (_, i) => `<hh:paraHead start="1" level="${i + 1}" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="DIGIT" charPrIDRef="4294967295" checkable="1">^${i + 1}.</hh:paraHead>`
4362
+ ).join("");
4363
+ return `<hh:numberings itemCnt="1"><hh:numbering id="1" start="0">${heads}</hh:numbering></hh:numberings>`;
4364
+ }
4365
+ function paraProperties() {
4366
+ return `<hh:paraProperties itemCnt="1"><hh:paraPr id="0" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0"><hh:align horizontal="JUSTIFY" vertical="BASELINE"/><hh:heading type="NONE" idRef="0" level="0"/><hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="KEEP_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/><hh:autoSpacing eAsianEng="0" eAsianNum="0"/><hh:margin><hc:intent value="0" unit="HWPUNIT"/><hc:left value="0" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="0" unit="HWPUNIT"/><hc:next value="0" unit="HWPUNIT"/></hh:margin><hh:lineSpacing type="PERCENT" value="160" unit="HWPUNIT"/><hh:border borderFillIDRef="2" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/></hh:paraPr></hh:paraProperties>`;
4367
+ }
4368
+ var STYLES = [
4369
+ { id: 0, name: "\uBC14\uD0D5\uAE00", engName: "Normal", charPr: 0, next: 0 },
4370
+ { id: 1, name: "\uAC1C\uC694 1", engName: "Outline 1", charPr: 1, next: 1 },
4371
+ { id: 2, name: "\uAC1C\uC694 2", engName: "Outline 2", charPr: 2, next: 2 },
4372
+ { id: 3, name: "\uAC1C\uC694 3", engName: "Outline 3", charPr: 3, next: 3 }
4373
+ ];
4374
+ function styles() {
4375
+ const one = (s) => `<hh:style id="${s.id}" type="PARA" name="${s.name}" engName="${s.engName}" paraPrIDRef="0" charPrIDRef="${s.charPr}" nextStyleIDRef="${s.next}" langID="1042" lockForm="0"/>`;
4376
+ return `<hh:styles itemCnt="${STYLES.length}">${STYLES.map(one).join("")}</hh:styles>`;
4377
+ }
4378
+ function headerXml() {
4379
+ return XML_DECL + `<hh:head ${HWPML_NS} version="1.4" secCnt="1"><hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/><hh:refList>` + fontfaces() + borderFills() + charProperties() + `<hh:tabProperties itemCnt="2"><hh:tabPr id="0" autoTabLeft="0" autoTabRight="0"/><hh:tabPr id="1" autoTabLeft="1" autoTabRight="0"/></hh:tabProperties>` + numberings() + paraProperties() + styles() + `</hh:refList><hh:compatibleDocument targetProgram="HWP201X"><hh:layoutCompatibility/></hh:compatibleDocument><hh:docOption><hh:linkinfo path="" pageInherit="0" footnoteInherit="0"/></hh:docOption><hh:trackchageConfig flags="56"/></hh:head>`;
4380
+ }
4381
+ var SEC_PR = `<hp:secPr id="" textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" tabStopVal="4000" tabStopUnit="HWPUNIT" outlineShapeIDRef="1" memoShapeIDRef="0" textVerticalWidthHead="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:lineNumberShape restartType="0" countBy="0" distance="0" startNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="4252" footer="4252" gutter="0" left="8504" right="8504" top="5668" bottom="4252"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr><hp:pageBorderFill type="BOTH" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill><hp:pageBorderFill type="EVEN" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill><hp:pageBorderFill type="ODD" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill></hp:secPr><hp:ctrl><hp:colPr id="" type="NEWSPAPER" layout="LEFT" colCount="1" sameSz="1" sameGap="0"/></hp:ctrl>`;
4382
+ function escapeXml(value) {
4383
+ return String(value ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
4384
+ }
4385
+ function inlineRuns(text, baseCharPr) {
4386
+ const runs = [];
4387
+ const pattern = /(\*\*[^*\n]+\*\*|\*[^*\n]+\*|`[^`\n]+`)/g;
4388
+ let last = 0;
4389
+ for (const m of text.matchAll(pattern)) {
4390
+ if (m.index > last) runs.push({ text: text.slice(last, m.index), charPr: baseCharPr });
4391
+ const token = m[0];
4392
+ if (token.startsWith("**")) runs.push({ text: token.slice(2, -2), charPr: 4 });
4393
+ else if (token.startsWith("*")) runs.push({ text: token.slice(1, -1), charPr: 5 });
4394
+ else runs.push({ text: token.slice(1, -1), charPr: baseCharPr });
4395
+ last = m.index + token.length;
4396
+ }
4397
+ if (last < text.length) runs.push({ text: text.slice(last), charPr: baseCharPr });
4398
+ return runs.length ? runs : [{ text: "", charPr: baseCharPr }];
4399
+ }
4400
+ function isTableSeparator(line) {
4401
+ return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/.test(line);
4402
+ }
4403
+ function tableCells(line) {
4404
+ return line.trim().replace(/^\|/, "").replace(/\|$/, "").split(/(?<!\\)\|/).map((c) => c.trim().replace(/\\\|/g, "|"));
4405
+ }
4406
+ function markdownToBlocks(content) {
4407
+ const blocks = [];
4408
+ for (const raw of content.split("\n")) {
4409
+ const line = raw.replace(/\s+$/, "");
4410
+ if (!line.trim()) continue;
4411
+ const heading = line.match(/^(#{1,3})\s+(.*)$/);
4412
+ if (heading) {
4413
+ const text = heading[2].replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1");
4414
+ blocks.push({ runs: [{ text, charPr: heading[1].length }], style: heading[1].length });
4415
+ continue;
4416
+ }
4417
+ const bullet = line.match(/^\s*[-*+]\s+(.*)$/);
4418
+ if (bullet) {
4419
+ blocks.push({ runs: [{ text: "\u2022 ", charPr: 0 }, ...inlineRuns(bullet[1], 0)], style: 0 });
4420
+ continue;
4421
+ }
4422
+ const numbered = line.match(/^\s*(\d+)[.)]\s+(.*)$/);
4423
+ if (numbered) {
4424
+ blocks.push({ runs: [{ text: `${numbered[1]}. `, charPr: 0 }, ...inlineRuns(numbered[2], 0)], style: 0 });
4425
+ continue;
4426
+ }
4427
+ if (line.trim().startsWith("|")) {
4428
+ if (isTableSeparator(line)) continue;
4429
+ blocks.push({ runs: [{ text: tableCells(line).join(" | "), charPr: 0 }], style: 0 });
4430
+ continue;
4431
+ }
4432
+ blocks.push({ runs: inlineRuns(line, 0), style: 0 });
4433
+ }
4434
+ return blocks.length ? blocks : [{ runs: [{ text: "", charPr: 0 }], style: 0 }];
4435
+ }
4436
+ function paragraphXml(block, index) {
4437
+ const runs = block.runs.map(
4438
+ (r, i) => `<hp:run charPrIDRef="${r.charPr}">${index === 0 && i === 0 ? SEC_PR : ""}<hp:t>${escapeXml(r.text)}</hp:t></hp:run>`
4439
+ ).join("");
4440
+ return `<hp:p id="${index}" paraPrIDRef="0" styleIDRef="${block.style}" pageBreak="0" columnBreak="0" merged="0">` + runs + `<hp:linesegarray><hp:lineseg textpos="0" vertpos="0" vertsize="1000" textheight="1000" baseline="850" spacing="600" horzpos="0" horzsize="42520" flags="393216"/></hp:linesegarray></hp:p>`;
4441
+ }
4442
+ function sectionXml(blocks) {
4443
+ return XML_DECL + `<hs:sec ${HWPML_NS}>` + blocks.map(paragraphXml).join("") + `</hs:sec>`;
4444
+ }
4445
+ async function documentToHwpx(data) {
4446
+ const enc = new TextEncoder();
4447
+ const blocks = markdownToBlocks(data.content ?? "");
4448
+ const parts = [
4449
+ // `mimetype` must be the container's first entry, stored.
4450
+ ["mimetype", enc.encode("application/hwp+zip")],
4451
+ ["version.xml", enc.encode(VERSION_XML)],
4452
+ ["META-INF/manifest.xml", enc.encode(MANIFEST_XML)],
4453
+ ["META-INF/container.xml", enc.encode(CONTAINER_XML)],
4454
+ ["Contents/content.hpf", enc.encode(CONTENT_HPF)],
4455
+ ["Contents/header.xml", enc.encode(headerXml())],
4456
+ ["Contents/section0.xml", enc.encode(sectionXml(blocks))],
4457
+ ["settings.xml", enc.encode(SETTINGS_XML)]
4458
+ ];
4459
+ return storeZip(parts);
4460
+ }
4461
+
2836
4462
  // src/export/exporters.ts
2837
4463
  var MIME = {
2838
4464
  md: "text/markdown",
@@ -2845,7 +4471,8 @@ var MIME = {
2845
4471
  var dataExporters = {
2846
4472
  document: [
2847
4473
  { label: "Markdown", extension: "md", mime: MIME.md, build: (a) => a.data.content },
2848
- { label: "Word", extension: "docx", mime: MIME.docx, build: (a) => documentToDocx(a.data) }
4474
+ { label: "Word", extension: "docx", mime: MIME.docx, build: (a) => documentToDocx(a.data) },
4475
+ { label: "\uD55C\uAE00 (HWPX)", extension: "hwpx", mime: "application/vnd.hancom.hwpx", build: (a) => documentToHwpx(a.data) }
2849
4476
  ],
2850
4477
  table: [
2851
4478
  { label: "CSV", extension: "csv", mime: MIME.csv, build: (a) => tableToCsv(a.data) },
@@ -2864,7 +4491,7 @@ function toStandaloneHtml(title, renderedHtml) {
2864
4491
  <head>
2865
4492
  <meta charset="utf-8" />
2866
4493
  <meta name="viewport" content="width=device-width, initial-scale=1" />
2867
- <title>${escapeHtml(title)}</title>
4494
+ <title>${escapeHtml2(title)}</title>
2868
4495
  <style>${EXPORT_CSS}</style>
2869
4496
  </head>
2870
4497
  <body>
@@ -2966,8 +4593,10 @@ async function documentToDocx(data) {
2966
4593
  async function slidesToPptx(data, _title) {
2967
4594
  const PptxGenJS = (await loadOptional("pptxgenjs", () => import('pptxgenjs'))).default;
2968
4595
  const pptx = new PptxGenJS();
2969
- const W = 10;
2970
- const H = 5.625;
4596
+ const W = 13.333;
4597
+ const H = 7.5;
4598
+ pptx.defineLayout({ name: "CV_16x9", width: W, height: H });
4599
+ pptx.layout = "CV_16x9";
2971
4600
  for (const slide of data.slides) {
2972
4601
  const s = pptx.addSlide();
2973
4602
  if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
@@ -2976,18 +4605,28 @@ async function slidesToPptx(data, _title) {
2976
4605
  const inset = (v) => pad + v / 100 * (1 - 2 * pad);
2977
4606
  for (const el of resolveElements(slide)) {
2978
4607
  const box = { x: inset(el.x) * W, y: inset(el.y) * H, w: el.w / 100 * (1 - 2 * pad) * W, h: el.h / 100 * (1 - 2 * pad) * H };
4608
+ const spin = el.rotate ? { rotate: (Math.round(el.rotate) % 360 + 360) % 360 } : {};
2979
4609
  if (el.type === "text") {
2980
4610
  const color = el.color ? el.color.replace("#", "") : tc;
2981
- s.addText(el.text ?? "", { ...box, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
4611
+ s.addText(el.text ?? "", { ...box, ...spin, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
2982
4612
  } else if (el.type === "shape") {
2983
4613
  const fill = (el.fill ?? "#5b5bd6").replace("#", "");
4614
+ const paint = el.fill ? { fill: { color: fill } } : { fill: { color: "FFFFFF", transparency: 100 }, line: { color: (tc || "1f2328").replace("#", ""), width: 1 } };
2984
4615
  if (el.shape === "line") {
2985
- s.addShape(pptx.ShapeType.line, { ...box, line: { color: fill, width: 2 } });
4616
+ s.addShape(pptx.ShapeType.line, { ...box, ...spin, line: { color: fill, width: 2 } });
4617
+ } else if (el.shape !== "ellipse" && el.radius) {
4618
+ s.addShape(pptx.ShapeType.roundRect, { ...box, ...spin, ...paint, rectRadius: el.radius * W / 1280 });
2986
4619
  } else {
2987
- s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box, fill: { color: fill } });
4620
+ s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box, ...spin, ...paint });
2988
4621
  }
2989
4622
  } else if (el.src) {
2990
- s.addImage({ data: el.src, ...box, sizing: { type: "contain", w: box.w, h: box.h } });
4623
+ s.addImage({
4624
+ data: el.src,
4625
+ ...box,
4626
+ ...spin,
4627
+ sizing: { type: el.fit === "cover" ? "cover" : "contain", w: box.w, h: box.h },
4628
+ ...el.radius ? { rounding: true } : {}
4629
+ });
2991
4630
  }
2992
4631
  }
2993
4632
  if (slide.notes) s.addNotes(slide.notes);
@@ -3000,29 +4639,34 @@ function slidesToPrintHtml(data, title) {
3000
4639
  const bg = slide.background ?? "#ffffff";
3001
4640
  const fg = slide.textColor ?? "#1f2328";
3002
4641
  const els = resolveElements(slide).map((el) => {
3003
- const box = `left:${el.x}%;top:${el.y}%;width:${el.w}%;height:${el.h}%`;
4642
+ const spin = el.rotate ? `;transform:rotate(${Number(el.rotate)}deg)` : "";
4643
+ const box = `left:${el.x}%;top:${el.y}%;width:${el.w}%;height:${el.h}%${spin}`;
3004
4644
  if (el.type === "text") {
3005
- const style = `${box};font-size:${(el.fontSize ?? 24) / 7.2}vw;font-weight:${el.bold ? 700 : 400};color:${escapeAttr(el.color ?? fg)};text-align:${escapeAttr(el.align ?? "left")};white-space:pre-wrap`;
3006
- return `<div class="el" style="${style}">${escapeXml(el.text ?? "")}</div>`;
4645
+ const style = `${box};font-size:${el.fontSize ?? 24}px;font-weight:${el.bold ? 700 : 400};color:${escapeAttr(el.color ?? fg)};text-align:${escapeAttr(el.align ?? "left")};white-space:pre-wrap`;
4646
+ return `<div class="el" style="${style}">${escapeXml2(el.text ?? "")}</div>`;
3007
4647
  }
3008
4648
  if (el.type === "shape") {
3009
- const fill = escapeAttr(el.fill ?? fg);
3010
- const radius = el.shape === "ellipse" ? "50%" : el.shape === "line" ? "2px" : "8px";
3011
- return `<div class="el" style="${box};background:${fill};border-radius:${radius}"></div>`;
4649
+ const radius = el.shape === "ellipse" ? "50%" : el.shape === "line" ? "2px" : `${Number(el.radius ?? 8)}px`;
4650
+ const paint = el.fill || el.shape === "line" ? `background:${escapeAttr(el.fill ?? fg)}` : `background:transparent;border:1.5px solid ${escapeAttr(fg)};box-sizing:border-box`;
4651
+ return `<div class="el" style="${box};${paint};border-radius:${radius}"></div>`;
3012
4652
  }
3013
4653
  const src = safeSrc(el.src);
3014
- return src ? `<img class="el" style="${box}" src="${escapeAttr(src)}"/>` : "";
4654
+ const imgStyle = `${box};object-fit:${el.fit === "cover" ? "cover" : "contain"}${el.radius ? `;border-radius:${Number(el.radius)}px` : ""}`;
4655
+ return src ? `<img class="el" style="${imgStyle}" src="${escapeAttr(src)}"/>` : "";
3015
4656
  }).join("");
3016
4657
  const pad = slide.padding ?? 0;
3017
4658
  const inner = pad ? `<div style="position:absolute;inset:${pad}%">${els}</div>` : els;
3018
4659
  return `<section class="slide" style="background:${escapeAttr(bg)}">${inner}</section>`;
3019
4660
  }).join("");
3020
- return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml(title)}</title><style>
4661
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml2(title)}</title><style>
3021
4662
  @page { size: 1280px 720px; margin: 0; }
3022
4663
  * { margin: 0; box-sizing: border-box; }
3023
4664
  body { font-family: Inter, Arial, sans-serif; }
3024
4665
  .slide { position: relative; width: 1280px; height: 720px; overflow: hidden; page-break-after: always; }
3025
- .el { position: absolute; overflow: hidden; line-height: 1.25; }
4666
+ .el { position: absolute; overflow: hidden; line-height: 1.2; }
4667
+ /* PowerPoint lets text paint past its box \u2014 only shapes/images clip. */
4668
+ div.el { overflow: visible; }
4669
+ img.el { overflow: hidden; }
3026
4670
  img.el { object-fit: contain; }
3027
4671
  </style></head><body>${pages}</body></html>`;
3028
4672
  }
@@ -3033,7 +4677,7 @@ function htmlSlideToPrintHtml(html, ratio) {
3033
4677
  const i = html.toLowerCase().lastIndexOf("</head>");
3034
4678
  return i === -1 ? style + html : html.slice(0, i) + style + html.slice(i);
3035
4679
  }
3036
- function escapeXml(value) {
4680
+ function escapeXml2(value) {
3037
4681
  return String(value ?? "").replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
3038
4682
  }
3039
4683
  function escapeAttr(value) {
@@ -3046,7 +4690,7 @@ function safeSrc(src) {
3046
4690
  function csvCell(value) {
3047
4691
  return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
3048
4692
  }
3049
- function escapeHtml(text) {
4693
+ function escapeHtml2(text) {
3050
4694
  return text.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
3051
4695
  }
3052
4696
  var EXPORT_CSS = `
@@ -3090,9 +4734,10 @@ function printToPdf(html) {
3090
4734
  document.body.appendChild(iframe);
3091
4735
  }
3092
4736
  var PDF_TYPES = /* @__PURE__ */ new Set(["html", "document", "chart", "slides"]);
3093
- var escapeHtml2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3094
- var escapeAttr2 = (s) => escapeHtml2(s).replace(/"/g, "&quot;");
4737
+ var escapeHtml3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4738
+ var escapeAttr2 = (s) => escapeHtml3(s).replace(/"/g, "&quot;");
3095
4739
  function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
4740
+ const t = useT();
3096
4741
  const [open, setOpen] = useState(false);
3097
4742
  const stem = slugify(artifact2.title);
3098
4743
  const dataOptions = dataExporters[artifact2.type] ?? [];
@@ -3133,7 +4778,7 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
3133
4778
  return h == null ? null : toStandaloneHtml(artifact2.title, h);
3134
4779
  })();
3135
4780
  if (html == null) return;
3136
- const wrapper = `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml2(artifact2.title)}</title><style>html,body{margin:0;height:100%}iframe{display:block;width:100%;height:100%;border:0}</style></head><body><iframe sandbox="allow-scripts allow-popups allow-modals" srcdoc="${escapeAttr2(html)}"></iframe></body></html>`;
4781
+ const wrapper = `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml3(artifact2.title)}</title><style>html,body{margin:0;height:100%}iframe{display:block;width:100%;height:100%;border:0}</style></head><body><iframe sandbox="allow-scripts allow-popups allow-modals" srcdoc="${escapeAttr2(html)}"></iframe></body></html>`;
3137
4782
  const url = URL.createObjectURL(new Blob([wrapper], { type: "text/html" }));
3138
4783
  window.open(url, "_blank", "noopener");
3139
4784
  setTimeout(() => URL.revokeObjectURL(url), 1e4);
@@ -3160,14 +4805,14 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
3160
4805
  onClick: () => setOpen((o) => !o),
3161
4806
  "aria-haspopup": "menu",
3162
4807
  "aria-expanded": open,
3163
- children: "Export \u25BE"
4808
+ children: t("export")
3164
4809
  }
3165
4810
  ),
3166
4811
  open && /* @__PURE__ */ jsxs(Fragment, { children: [
3167
4812
  /* @__PURE__ */ jsx("div", { className: "cv-export__scrim", onClick: () => setOpen(false) }),
3168
4813
  /* @__PURE__ */ jsxs("div", { className: "cv-export__menu", role: "menu", children: [
3169
- /* @__PURE__ */ jsx("button", { role: "menuitem", onClick: openInTab, children: "Open in new tab \u2197" }),
3170
- /* @__PURE__ */ jsx("button", { role: "menuitem", onClick: copyHtml, children: copied ? "Copied \u2713" : "Copy HTML" }),
4814
+ /* @__PURE__ */ jsx("button", { role: "menuitem", onClick: openInTab, children: t("openInTab") }),
4815
+ /* @__PURE__ */ jsx("button", { role: "menuitem", onClick: copyHtml, children: copied ? t("copied") : t("copyHtml") }),
3171
4816
  /* @__PURE__ */ jsxs("button", { role: "menuitem", onClick: exportHtml, children: [
3172
4817
  "HTML ",
3173
4818
  /* @__PURE__ */ jsx("span", { className: "cv-export__ext", children: ".html" })
@@ -3256,17 +4901,17 @@ var GRADIENTS = [
3256
4901
  function StylePanel({ selection }) {
3257
4902
  const send = useCanvasStore((s) => s.sendIframeCommand);
3258
4903
  const setSelections = useCanvasStore((s) => s.setSelections);
3259
- const styles = selection.styles ?? {};
3260
- const [color, setColor] = useState(toHex2(styles.color));
3261
- const [background, setBackground] = useState(toHex2(styles.backgroundColor));
3262
- const [fontSize, setFontSize] = useState(px(styles.fontSize, 16));
3263
- const [fontWeight, setFontWeight] = useState(String(styles.fontWeight ?? "400"));
3264
- const [textAlign, setTextAlign] = useState(styles.textAlign ?? "left");
3265
- const [lineHeight, setLineHeight] = useState(px(styles.lineHeight, 0));
3266
- const [letterSpacing, setLetterSpacing] = useState(px(styles.letterSpacing, 0));
3267
- const [padding, setPadding] = useState(px(styles.padding, 0));
3268
- const [radius, setRadius] = useState(px(styles.borderRadius, 0));
3269
- const [width, setWidth] = useState(px(styles.width, 0));
4904
+ const styles2 = selection.styles ?? {};
4905
+ const [color, setColor] = useState(toHex2(styles2.color));
4906
+ const [background, setBackground] = useState(toHex2(styles2.backgroundColor));
4907
+ const [fontSize, setFontSize] = useState(px(styles2.fontSize, 16));
4908
+ const [fontWeight, setFontWeight] = useState(String(styles2.fontWeight ?? "400"));
4909
+ const [textAlign, setTextAlign] = useState(styles2.textAlign ?? "left");
4910
+ const [lineHeight, setLineHeight] = useState(px(styles2.lineHeight, 0));
4911
+ const [letterSpacing, setLetterSpacing] = useState(px(styles2.letterSpacing, 0));
4912
+ const [padding, setPadding] = useState(px(styles2.padding, 0));
4913
+ const [radius, setRadius] = useState(px(styles2.borderRadius, 0));
4914
+ const [width, setWidth] = useState(px(styles2.width, 0));
3270
4915
  const bgFileRef = useRef(null);
3271
4916
  const dirty = useRef(false);
3272
4917
  const setStyle = (prop, value) => {
@@ -3508,10 +5153,17 @@ function useCanvasImport() {
3508
5153
  return { importFiles, canImport };
3509
5154
  }
3510
5155
  var ACCEPT = IMPORTABLE_EXTENSIONS.join(",");
3511
- function Canvas({ registry = builtinRenderers, emptyState, onEditElement, onUserEdit }) {
3512
- return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(CanvasPanel, { emptyState, onEditElement, onUserEdit }) });
5156
+ function Canvas({ registry = builtinRenderers, locale, emptyState, onEditElement, onUserEdit, onSave }) {
5157
+ return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(CanvasLocaleProvider, { locale, children: /* @__PURE__ */ jsx(CanvasPanel, { emptyState, onEditElement, onUserEdit, onSave }) }) });
3513
5158
  }
3514
- function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
5159
+ function CanvasPanel({
5160
+ emptyState,
5161
+ onEditElement,
5162
+ onUserEdit,
5163
+ onSave
5164
+ }) {
5165
+ const t = useT();
5166
+ const debouncedSave = useCanvasSave(onSave);
3515
5167
  const { artifacts, order, activeId } = useCanvasStore((s) => s.canvas);
3516
5168
  const history = useCanvasStore((s) => s.canvas.history);
3517
5169
  const setActive = useCanvasStore((s) => s.setActiveArtifact);
@@ -3521,9 +5173,16 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
3521
5173
  const { importFiles } = useCanvasImport();
3522
5174
  const [dropping, setDropping] = useState(false);
3523
5175
  useEffect(() => {
3524
- setOnUserEdit(onUserEdit ?? null);
5176
+ if (!onUserEdit && !debouncedSave) {
5177
+ setOnUserEdit(null);
5178
+ return;
5179
+ }
5180
+ setOnUserEdit((artifact2) => {
5181
+ onUserEdit?.(artifact2);
5182
+ debouncedSave?.(artifact2);
5183
+ });
3525
5184
  return () => setOnUserEdit(null);
3526
- }, [onUserEdit, setOnUserEdit]);
5185
+ }, [onUserEdit, debouncedSave, setOnUserEdit]);
3527
5186
  useEffect(() => {
3528
5187
  if (!selections.length) return;
3529
5188
  const onKey = (e) => {
@@ -3549,14 +5208,14 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
3549
5208
  if (e.dataTransfer.files.length) void importFiles(e.dataTransfer.files);
3550
5209
  }
3551
5210
  };
3552
- const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: "Drop to open on the canvas" }) : null;
5211
+ const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: t("dropToOpen") }) : null;
3553
5212
  if (!active) {
3554
5213
  return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas cv-canvas--empty", ...dropProps, children: [
3555
5214
  emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles: importFiles }),
3556
5215
  dropOverlay
3557
5216
  ] });
3558
5217
  }
3559
- const versions = history[active.id] ?? [active];
5218
+ const versions2 = history[active.id] ?? [active];
3560
5219
  const showSelection = Boolean(onEditElement) && selections.length > 0 && selections[0].artifactId === active.id;
3561
5220
  return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas", ...dropProps, children: [
3562
5221
  dropOverlay,
@@ -3571,18 +5230,19 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
3571
5230
  },
3572
5231
  id
3573
5232
  )) }),
3574
- /* @__PURE__ */ jsx(ArtifactView, { artifact: active, versions }, active.id),
5233
+ /* @__PURE__ */ jsx(ArtifactView, { artifact: active, versions: versions2 }, active.id),
3575
5234
  showSelection && onEditElement && /* @__PURE__ */ jsxs(Fragment, { children: [
3576
5235
  active.type === "html" && selections.length === 1 && /* @__PURE__ */ jsx(StylePanel, { selection: selections[0] }, selections[0].cid),
3577
5236
  /* @__PURE__ */ jsx(SelectionBar, { selections, onEdit: onEditElement, onClear: () => setSelections([]) })
3578
5237
  ] })
3579
5238
  ] });
3580
5239
  }
3581
- function ArtifactView({ artifact: artifact2, versions }) {
5240
+ function ArtifactView({ artifact: artifact2, versions: versions2 }) {
5241
+ const t = useT();
3582
5242
  const [viewIndex, setViewIndex] = useState(null);
3583
5243
  const bodyRef = useRef(null);
3584
- const shown = viewIndex === null ? artifact2 : versions[Math.min(viewIndex, versions.length - 1)] ?? artifact2;
3585
- const isHistory = viewIndex !== null && shown !== artifact2;
5244
+ const shown = viewIndex === null ? artifact2 : versions2[Math.min(viewIndex, versions2.length - 1)] ?? artifact2;
5245
+ const viewingHistory = viewIndex !== null && shown !== artifact2;
3586
5246
  const Renderer = useRenderer(shown.type);
3587
5247
  const getRenderedHtml = () => {
3588
5248
  const node = bodyRef.current;
@@ -3600,40 +5260,45 @@ function ArtifactView({ artifact: artifact2, versions }) {
3600
5260
  ] }),
3601
5261
  /* @__PURE__ */ jsxs("div", { className: "cv-header__actions", children: [
3602
5262
  /* @__PURE__ */ jsx(UndoRedo, {}),
3603
- versions.length > 1 && /* @__PURE__ */ jsx(
3604
- VersionRail,
5263
+ versions2.length > 1 && /* @__PURE__ */ jsx(
5264
+ VersionHistory,
3605
5265
  {
3606
- total: versions.length,
3607
- index: viewIndex ?? versions.length - 1,
3608
- onSelect: (i) => setViewIndex(i === versions.length - 1 ? null : i)
5266
+ versions: versions2,
5267
+ index: viewIndex ?? versions2.length - 1,
5268
+ onSelect: (i) => setViewIndex(i === versions2.length - 1 ? null : i)
3609
5269
  }
3610
5270
  ),
3611
5271
  /* @__PURE__ */ jsx(ExportMenu, { artifact: shown, getRenderedHtml })
3612
5272
  ] })
3613
5273
  ] }),
3614
- /* @__PURE__ */ jsxs(
5274
+ viewingHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-banner", role: "status", children: [
5275
+ "v",
5276
+ Math.min((viewIndex ?? 0) + 1, versions2.length),
5277
+ " / ",
5278
+ versions2.length,
5279
+ " \xB7 ",
5280
+ t("historyNote"),
5281
+ " ",
5282
+ /* @__PURE__ */ jsx("button", { onClick: () => setViewIndex(null), children: t("backToLatest") })
5283
+ ] }),
5284
+ /* @__PURE__ */ jsx(
3615
5285
  "div",
3616
5286
  {
3617
- className: `cv-body${shown.type === "table" || shown.type === "pdf" ? " cv-body--flush" : ""}${isHistory ? " cv-body--history" : ""}`,
5287
+ className: `cv-body${shown.type === "table" || shown.type === "pdf" ? " cv-body--flush" : ""}${viewingHistory ? " cv-body--history" : ""}`,
3618
5288
  ref: bodyRef,
3619
- "aria-readonly": isHistory || void 0,
3620
- children: [
3621
- isHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-note", role: "status", children: [
3622
- "Viewing v",
3623
- Math.min(viewIndex + 1, versions.length),
3624
- " (read-only) \u2014 select the latest version to edit"
3625
- ] }),
3626
- Renderer ? /* @__PURE__ */ jsx(RendererBoundary, { resetKey: `${shown.id}:${shown.version}`, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-fallback", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Renderer, { artifact: shown }) }) }) : /* @__PURE__ */ jsxs("div", { className: "cv-fallback", children: [
3627
- "No renderer registered for type \u201C",
3628
- shown.type,
3629
- "\u201D."
3630
- ] })
3631
- ]
5289
+ "aria-readonly": viewingHistory || void 0,
5290
+ children: Renderer ? /* @__PURE__ */ jsx(RendererBoundary, { resetKey: `${shown.id}:${shown.version}`, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-fallback", children: t("loading") }), children: /* @__PURE__ */ jsx(Renderer, { artifact: shown }) }) }) : /* @__PURE__ */ jsxs("div", { className: "cv-fallback", children: [
5291
+ t("noRenderer"),
5292
+ " \u201C",
5293
+ shown.type,
5294
+ "\u201D."
5295
+ ] })
3632
5296
  }
3633
5297
  )
3634
5298
  ] });
3635
5299
  }
3636
5300
  function UndoRedo() {
5301
+ const t = useT();
3637
5302
  const undo = useCanvasStore((s) => s.undo);
3638
5303
  const redo = useCanvasStore((s) => s.redo);
3639
5304
  const canUndo = useCanvasStore((s) => s.undoStack.length > 0);
@@ -3653,29 +5318,65 @@ function UndoRedo() {
3653
5318
  return () => window.removeEventListener("keydown", onKey);
3654
5319
  }, [undo, redo]);
3655
5320
  return /* @__PURE__ */ jsxs("div", { className: "cv-undo", role: "group", "aria-label": "Undo and redo", children: [
3656
- /* @__PURE__ */ jsx("button", { onClick: undo, disabled: !canUndo, title: "Undo (\u2318Z)", "aria-label": "Undo", children: "\u21B6" }),
3657
- /* @__PURE__ */ jsx("button", { onClick: redo, disabled: !canRedo, title: "Redo (\u2318\u21E7Z)", "aria-label": "Redo", children: "\u21B7" })
5321
+ /* @__PURE__ */ jsx("button", { onClick: undo, disabled: !canUndo, title: `${t("undo")} (\u2318Z)`, "aria-label": t("undo"), children: "\u21B6" }),
5322
+ /* @__PURE__ */ jsx("button", { onClick: redo, disabled: !canRedo, title: `${t("redo")} (\u2318\u21E7Z)`, "aria-label": t("redo"), children: "\u21B7" })
3658
5323
  ] });
3659
5324
  }
3660
- function VersionRail({ total, index, onSelect }) {
3661
- return /* @__PURE__ */ jsxs("div", { className: "cv-versions", role: "group", "aria-label": "Version history", children: [
3662
- /* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => onSelect(index - 1), "aria-label": "Previous version", children: "\u2039" }),
3663
- /* @__PURE__ */ jsxs("span", { className: "cv-versions__label", children: [
3664
- "v",
3665
- index + 1,
3666
- " / ",
3667
- total
3668
- ] }),
5325
+ function VersionHistory({
5326
+ versions: versions2,
5327
+ index,
5328
+ onSelect
5329
+ }) {
5330
+ const t = useT();
5331
+ const [open, setOpen] = useState(false);
5332
+ const total = versions2.length;
5333
+ const pick = (i) => {
5334
+ setOpen(false);
5335
+ onSelect(i);
5336
+ };
5337
+ return /* @__PURE__ */ jsxs("div", { className: "cv-versions", role: "group", "aria-label": t("versionHistory"), children: [
5338
+ /* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => pick(index - 1), "aria-label": t("prevVersion"), children: "\u2039" }),
5339
+ /* @__PURE__ */ jsxs(
5340
+ "button",
5341
+ {
5342
+ className: "cv-versions__label",
5343
+ "aria-expanded": open,
5344
+ "aria-label": "Open version history",
5345
+ onClick: () => setOpen((v) => !v),
5346
+ children: [
5347
+ "v",
5348
+ index + 1,
5349
+ " / ",
5350
+ total
5351
+ ]
5352
+ }
5353
+ ),
3669
5354
  /* @__PURE__ */ jsx(
3670
5355
  "button",
3671
5356
  {
3672
5357
  className: "cv-versions__nav",
3673
5358
  disabled: index === total - 1,
3674
- onClick: () => onSelect(index + 1),
3675
- "aria-label": "Next version",
5359
+ onClick: () => pick(index + 1),
5360
+ "aria-label": t("nextVersion"),
3676
5361
  children: "\u203A"
3677
5362
  }
3678
- )
5363
+ ),
5364
+ open && /* @__PURE__ */ jsx("ul", { className: "cv-versions__list", role: "listbox", "aria-label": "Versions", children: versions2.map((snapshot, i) => ({ snapshot, i })).reverse().map(({ snapshot, i }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
5365
+ "button",
5366
+ {
5367
+ role: "option",
5368
+ "aria-selected": i === index,
5369
+ className: i === index ? "is-current" : void 0,
5370
+ onClick: () => pick(i),
5371
+ children: [
5372
+ /* @__PURE__ */ jsxs("span", { className: "cv-versions__v", children: [
5373
+ "v",
5374
+ i + 1
5375
+ ] }),
5376
+ /* @__PURE__ */ jsx("span", { className: "cv-versions__desc", children: typeof snapshot.meta?.commitDescription === "string" ? snapshot.meta.commitDescription : "Snapshot" })
5377
+ ]
5378
+ }
5379
+ ) }, i)) })
3679
5380
  ] });
3680
5381
  }
3681
5382
  function StatusBadge({ status }) {
@@ -3708,12 +5409,12 @@ function EmptyState({ onOpenFiles }) {
3708
5409
  ] });
3709
5410
  }
3710
5411
  var TYPE_META = {
3711
- html: { icon: "\u{1F310}", label: "Web page" },
3712
- document: { icon: "\u{1F4C4}", label: "Word document" },
3713
- chart: { icon: "\u{1F4CA}", label: "Chart" },
3714
- table: { icon: "\u{1F522}", label: "Excel sheet" },
3715
- slides: { icon: "\u{1F4FD}\uFE0F", label: "PowerPoint deck" },
3716
- pdf: { icon: "\u{1F4D5}", label: "PDF" }
5412
+ html: { icon: "\u{1F310}", label: "kindWeb" },
5413
+ document: { icon: "\u{1F4C4}", label: "kindDocument" },
5414
+ chart: { icon: "\u{1F4CA}", label: "kindChart" },
5415
+ table: { icon: "\u{1F522}", label: "kindTable" },
5416
+ slides: { icon: "\u{1F4FD}\uFE0F", label: "kindSlides" },
5417
+ pdf: { icon: "\u{1F4D5}", label: "kindPdf" }
3717
5418
  };
3718
5419
  var KIND_META = {
3719
5420
  web: TYPE_META.html,
@@ -3730,9 +5431,10 @@ var KIND_META = {
3730
5431
  function resolveCardMeta(artifact2) {
3731
5432
  const kind = typeof artifact2.meta?.kind === "string" ? artifact2.meta.kind : void 0;
3732
5433
  const byKind = kind ? KIND_META[kind] : void 0;
3733
- return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}", label: artifact2.type };
5434
+ return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}" };
3734
5435
  }
3735
5436
  function ArtifactCard({ artifactId }) {
5437
+ const t = useT();
3736
5438
  const artifact2 = useCanvasStore((s) => s.canvas.artifacts[artifactId]);
3737
5439
  const setActive = useCanvasStore((s) => s.setActiveArtifact);
3738
5440
  if (!artifact2) return null;
@@ -3742,12 +5444,12 @@ function ArtifactCard({ artifactId }) {
3742
5444
  /* @__PURE__ */ jsxs("span", { className: "cv-card__meta", children: [
3743
5445
  /* @__PURE__ */ jsx("b", { children: artifact2.title }),
3744
5446
  /* @__PURE__ */ jsxs("span", { children: [
3745
- meta.label,
5447
+ meta.label ? t(meta.label) : artifact2.type,
3746
5448
  " \xB7 ",
3747
- artifact2.status === "streaming" ? "writing\u2026" : "open \u2192"
5449
+ artifact2.status === "streaming" ? t("writing") : t("open")
3748
5450
  ] })
3749
5451
  ] })
3750
5452
  ] });
3751
5453
  }
3752
5454
 
3753
- export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, PdfRenderer, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasStream, useRenderer, withInspector };
5455
+ export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, PdfRenderer, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasSave, useCanvasStream, useRenderer, withInspector };