@braincrew-lab/langchain-canvas 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  "use client";
2
+ import { projectSheetIntoRows, mergeRowsIntoSheet } from './chunk-IFNRLN4Y.js';
2
3
  import { loadOptional } from './chunk-YZZSJJMQ.js';
3
- import { resolveElements } from './chunk-6L3AL6W4.js';
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';
4
+ import { deckPage, resolveElements } from './chunk-SE6AP3A7.js';
5
+ export { useAssetUrl } from './chunk-FTNRRJ3K.js';
6
+ import { inlineArtifactAssets, inlineHtmlAssets } from './chunk-7T5DRR3F.js';
7
+ export { ASSET_REFERENCE_PREFIXES, fetchAssetDataUri, inlineArtifactAssets, inlineHtmlAssets, isAssetReference, normalizeAssetReference, resolveAssetUrl } from './chunk-7T5DRR3F.js';
8
+ export { useArtifactPatch } from './chunk-K2UZAYW2.js';
9
+ import { useCanvasStoreApi, useCanvasStore } from './chunk-EHW446VF.js';
10
+ export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-EHW446VF.js';
11
+ import { createContext, lazy, useMemo, useRef, useCallback, useEffect, useContext, useState, Suspense, Component } from 'react';
10
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
13
 
12
14
  // src/client/sse-client.ts
@@ -63,9 +65,10 @@ function parseFrame(frame) {
63
65
 
64
66
  // src/client/inspector.ts
65
67
  var INSPECTOR_MARK = "langchain-canvas";
66
- function withInspector(html) {
68
+ function withInspector(html, assetBaseUrl) {
67
69
  let out = withViewport(html);
68
- const injection = `<style data-lcx>${INSPECTOR_CSS}</style><script data-lcx>${INSPECTOR_SCRIPT}</script>`;
70
+ const config = assetBaseUrl ? `<script data-lcx>window.__LCX_ASSET_BASE=${JSON.stringify(assetBaseUrl)}</script>` : "";
71
+ const injection = `<style data-lcx>${INSPECTOR_CSS}</style>${config}<script data-lcx>${INSPECTOR_SCRIPT}</script>`;
69
72
  const marker = "</body>";
70
73
  const at = out.lastIndexOf(marker);
71
74
  out = at === -1 ? out + injection : out.slice(0, at) + injection + out.slice(at);
@@ -133,6 +136,33 @@ var INSPECTOR_SCRIPT = `
133
136
  el.removeAttribute("contenteditable");
134
137
  if (el.classList) { el.classList.remove("lcx-hover"); el.classList.remove("lcx-selected"); }
135
138
  if (el.getAttribute && el.getAttribute("class") === "") el.removeAttribute("class");
139
+ // A display-resolved asset src goes back to its stored relative form.
140
+ var orig = el.getAttribute && el.getAttribute("data-lcx-src");
141
+ if (orig) { el.setAttribute("src", orig); el.removeAttribute("data-lcx-src"); }
142
+ }
143
+ // --- canvas-asset references: resolve for display, keep the source relative ---
144
+ var ASSET_BASE = window.__LCX_ASSET_BASE || "";
145
+ var ASSET_REF = /^(?:\\.\\.?\\/)*(?:assets|sources)\\//;
146
+ // Leading ./ and ../ fold onto the canvas root (assets/ and sources/ exist
147
+ // only there) \u2014 same lenient reading as canvasAssets.normalizeAssetReference.
148
+ function foldAssetRef(s) {
149
+ while (s.lastIndexOf("./", 0) === 0 || s.lastIndexOf("../", 0) === 0) {
150
+ s = s.lastIndexOf("./", 0) === 0 ? s.slice(2) : s.slice(3);
151
+ }
152
+ return s;
153
+ }
154
+ function rewriteAssetSrcs() {
155
+ if (!ASSET_BASE) return;
156
+ var imgs = document.querySelectorAll("img[src]");
157
+ for (var i = 0; i < imgs.length; i++) {
158
+ var el = imgs[i];
159
+ if (el.hasAttribute("data-lcx-src")) continue;
160
+ var src = el.getAttribute("src") || "";
161
+ if (ASSET_REF.test(src)) {
162
+ el.setAttribute("data-lcx-src", src);
163
+ el.setAttribute("src", ASSET_BASE + encodeURIComponent(foldAssetRef(src)));
164
+ }
165
+ }
136
166
  }
137
167
  function emitEdit(el) {
138
168
  // Serialize the *canonical* HTML \u2014 strip the inspector's own injected
@@ -140,7 +170,7 @@ var INSPECTOR_SCRIPT = `
140
170
  var cid = el.getAttribute("data-cid");
141
171
  var clone = el.cloneNode(true);
142
172
  scrub(clone);
143
- var inner = clone.querySelectorAll ? clone.querySelectorAll("[data-cid],[contenteditable],.lcx-hover,.lcx-selected") : [];
173
+ var inner = clone.querySelectorAll ? clone.querySelectorAll("[data-cid],[contenteditable],[data-lcx-src],.lcx-hover,.lcx-selected") : [];
144
174
  for (var i = 0; i < inner.length; i++) scrub(inner[i]);
145
175
  parent.postMessage({ source: MARK, type: "node_edit", cid: cid, html: clone.outerHTML }, "*");
146
176
  }
@@ -155,51 +185,20 @@ var INSPECTOR_SCRIPT = `
155
185
  var clone = document.documentElement.cloneNode(true);
156
186
  var injected = clone.querySelectorAll("[data-lcx]");
157
187
  for (var i = 0; i < injected.length; i++) injected[i].parentNode && injected[i].parentNode.removeChild(injected[i]);
158
- var marked = clone.querySelectorAll("[data-cid],[contenteditable],.lcx-hover,.lcx-selected");
188
+ var marked = clone.querySelectorAll("[data-cid],[contenteditable],[data-lcx-src],.lcx-hover,.lcx-selected");
159
189
  for (var j = 0; j < marked.length; j++) scrub(marked[j]);
160
190
  parent.postMessage({ source: MARK, type: "doc_edit", self: !!selfApplied, html: "<!doctype html>\\n" + clone.outerHTML }, "*");
161
191
  }
162
- // Copy the visual style of an existing element onto a new block, so inserts
163
- // match the page's own design system instead of landing as bare UA-styled
164
- // tags (a default grey <button> on a dark page reads as broken).
165
- function adoptStyleFrom(el, sample) {
166
- if (!sample) return false;
167
- var cs = window.getComputedStyle(sample);
168
- var props = ["background-color", "color", "border", "border-radius", "padding",
169
- "font-family", "font-size", "font-weight", "letter-spacing", "box-shadow", "cursor"];
170
- for (var i = 0; i < props.length; i++) el.style.setProperty(props[i], cs.getPropertyValue(props[i]));
171
- return true;
172
- }
173
192
  function newBlock(tag) {
174
193
  var el = document.createElement(tag);
175
194
  if (tag === "img") {
176
195
  el.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='270'%3E%3Crect width='100%25' height='100%25' fill='%23e5e7eb'/%3E%3Cpath d='M190 155l40-45 35 40 25-25 40 45z' fill='%23c3c8d0'/%3E%3Ccircle cx='300' cy='105' r='16' fill='%23c3c8d0'/%3E%3C/svg%3E";
177
196
  el.alt = "image"; el.style.maxWidth = "100%";
178
197
  }
179
- else if (tag === "button") {
180
- el.textContent = "Button";
181
- // Match an existing button (skipping editor chrome); else a clean accent default.
182
- var sampleBtn = null;
183
- var btns = document.querySelectorAll("button");
184
- for (var sb = 0; sb < btns.length; sb++) {
185
- if (!btns[sb].closest("[data-lcx]")) { sampleBtn = btns[sb]; break; }
186
- }
187
- if (!adoptStyleFrom(el, sampleBtn)) {
188
- el.style.cssText = "padding:10px 18px;border:0;border-radius:9px;background:#6366f1;color:#fff;font:600 15px/1.2 inherit;cursor:pointer";
189
- }
190
- }
198
+ else if (tag === "button") el.textContent = "Button";
191
199
  else if (tag === "hr") { /* no content */ }
192
200
  else if (tag === "section") { var p = document.createElement("p"); p.textContent = "New section"; el.appendChild(p); }
193
- else {
194
- el.textContent = tag === "h1" || tag === "h2" ? "New heading" : "New text";
195
- // Headings/text adopt an existing peer's look too (font, colour) so they
196
- // don't appear in default serif-black on a styled page.
197
- adoptStyleFrom(el, document.querySelector(tag === "p" ? "main p, section p, div p" : tag));
198
- el.style.removeProperty("border");
199
- el.style.removeProperty("box-shadow");
200
- el.style.removeProperty("cursor");
201
- el.style.removeProperty("background-color");
202
- }
201
+ else el.textContent = tag === "h1" || tag === "h2" ? "New heading" : "New text";
203
202
  return el;
204
203
  }
205
204
  // Floating rich-text toolbar shown while a text element is being edited.
@@ -289,6 +288,15 @@ var INSPECTOR_SCRIPT = `
289
288
  }
290
289
  function start() {
291
290
  assign(document.body, "e");
291
+ // Resolve asset references now and after any change (insert_html, set_src,
292
+ // duplicate). Idempotent: rewritten images carry data-lcx-src and are
293
+ // skipped, so the observer settles after one pass.
294
+ rewriteAssetSrcs();
295
+ if (ASSET_BASE) {
296
+ new MutationObserver(rewriteAssetSrcs).observe(document.documentElement, {
297
+ subtree: true, childList: true, attributes: true, attributeFilter: ["src"]
298
+ });
299
+ }
292
300
  var hovered = null;
293
301
  var selected = []; // currently highlighted elements
294
302
  var marquee = null, sx = 0, sy = 0, dragging = false, moved = false, suppressClick = false;
@@ -296,31 +304,16 @@ var INSPECTOR_SCRIPT = `
296
304
  // into absolute positioning inside its own parent, and its final spot + size are
297
305
  // stored as percentages of that parent \u2014 so it stays put proportionally across
298
306
  // responsive breakpoints, instead of a fixed pixel offset that drifts off.
299
- var dragEls = null, dragStart = null, dragBases = null, parentMutated = false;
307
+ var dragEls = null, dragStart = null, dragBases = null, groupSeq = 0;
300
308
 
301
309
  function ensurePositioned(parent) {
302
- if (!parent || parent === document.documentElement) return;
303
- // body included: a static body makes absolute children resolve against the
304
- // initial containing block (html), so a body margin or html padding shifts
305
- // every free-dragged element \u2014 relative pins them to the body box itself.
306
- if (window.getComputedStyle(parent).position === "static") {
307
- parent.style.position = "relative";
308
- parentMutated = true; // the parent must be persisted too, not just the child
309
- }
310
- }
311
- // Persist a completed free-drag. A single element normally commits as a cheap
312
- // node_edit \u2014 but when its parent was pulled to position:relative that parent
313
- // lives only in the live DOM, so a node-only patch would store an absolute
314
- // child inside a still-static parent (it jumps on the next reload). In that
315
- // case persist the whole document instead.
316
- function commitDrag(els) {
317
- if (els.length === 1 && !parentMutated) emitEdit(els[0]); else emitDoc(true);
310
+ if (!parent || parent === document.body || parent === document.documentElement) return;
311
+ if (window.getComputedStyle(parent).position === "static") parent.style.position = "relative";
318
312
  }
319
313
  // Pull each element out into absolute positioning at its current spot (no visual
320
314
  // jump), so it can then be moved freely.
321
315
  function beginFreeDrag(els) {
322
316
  dragBases = [];
323
- parentMutated = false;
324
317
  for (var i = 0; i < els.length; i++) {
325
318
  var el = els[i], parent = el.parentElement || document.body;
326
319
  ensurePositioned(parent);
@@ -343,16 +336,13 @@ var INSPECTOR_SCRIPT = `
343
336
  }
344
337
  // Commit the current position (in px, as set live by moveFree) as % of the
345
338
  // parent \u2014 position and width \u2014 so it scales with the layout. Falls back to px
346
- // only if the parent has collapsed to zero on that axis. The vertical axis is
347
- // an exception: a container's *height* is content-driven and reflows once the
348
- // dragged element leaves the flow, so a top stored as % of the old height
349
- // lands somewhere else after reload \u2014 top always commits in px.
339
+ // only if the parent has collapsed to zero on that axis.
350
340
  function commitFree() {
351
341
  for (var i = 0; i < dragBases.length; i++) {
352
342
  var b = dragBases[i], pr = b.parent.getBoundingClientRect();
353
343
  var curLeft = parseFloat(b.el.style.left) || 0, curTop = parseFloat(b.el.style.top) || 0;
354
344
  b.el.style.left = pr.width ? ((curLeft / pr.width) * 100).toFixed(3) + "%" : curLeft + "px";
355
- b.el.style.top = Math.round(curTop) + "px";
345
+ b.el.style.top = pr.height ? ((curTop / pr.height) * 100).toFixed(3) + "%" : curTop + "px";
356
346
  if (pr.width) b.el.style.width = ((b.w / pr.width) * 100).toFixed(3) + "%";
357
347
  }
358
348
  }
@@ -509,8 +499,8 @@ var INSPECTOR_SCRIPT = `
509
499
  positionResize();
510
500
  // The new position is already shown in the iframe with every cid intact,
511
501
  // so persist without a reload (no flicker): one element \u2192 node_edit,
512
- // several (or a repositioned parent) \u2192 a self-applied doc_edit.
513
- commitDrag(els);
502
+ // several \u2192 a self-applied doc_edit.
503
+ if (els.length === 1) emitEdit(els[0]); else emitDoc(true);
514
504
  }
515
505
  dragBases = null;
516
506
  return;
@@ -561,7 +551,7 @@ var INSPECTOR_SCRIPT = `
561
551
  // Pointer left the frame mid-drag: commit the move at its last position so
562
552
  // it isn't lost (the element is already placed absolutely in the iframe).
563
553
  var els = dragEls; dragEls = null;
564
- if (moved && dragBases) { commitFree(); commitDrag(els); }
554
+ if (moved && dragBases) { commitFree(); if (els.length === 1) emitEdit(els[0]); else emitDoc(true); }
565
555
  dragBases = null;
566
556
  }
567
557
  if (dragging) {
@@ -632,15 +622,20 @@ var INSPECTOR_SCRIPT = `
632
622
  if (root && d.style) { for (var sk in d.style) { try { root.style[sk] = d.style[sk]; } catch (_e) {} } emitDoc(); }
633
623
  return;
634
624
  }
635
- if (d.type === "set_src") { var ei = byCid(d.cid); if (ei) { ei.setAttribute("src", d.value); emitEdit(ei); } return; }
625
+ if (d.type === "set_src") {
626
+ var ei = byCid(d.cid);
627
+ if (ei) {
628
+ // Drop stale asset bookkeeping first, or scrub would restore the old
629
+ // src over the new one. The observer re-resolves if the new value is
630
+ // itself an asset reference.
631
+ ei.removeAttribute("data-lcx-src");
632
+ ei.setAttribute("src", d.value);
633
+ emitEdit(ei);
634
+ }
635
+ return;
636
+ }
636
637
  if (d.type === "commit") { var el2 = byCid(d.cid); if (el2) emitEdit(el2); return; }
637
638
 
638
- // No-selection inserts land in the slide root when there is one \u2014 a slide
639
- // document is body > .slide-container (fixed 720px), so appending to body
640
- // puts content below the visible slide where it silently never shows.
641
- function insertRoot() {
642
- return document.querySelector(".slide-container") || document.body;
643
- }
644
639
  // Structural edits \u2014 mutate the tree, then persist the whole document.
645
640
  if (d.type === "insert") {
646
641
  var block = newBlock(d.block || "p");
@@ -648,7 +643,7 @@ var INSPECTOR_SCRIPT = `
648
643
  if (anchor && anchor.parentNode && anchor.parentNode !== document.documentElement) {
649
644
  anchor.parentNode.insertBefore(block, anchor.nextSibling);
650
645
  } else {
651
- insertRoot().appendChild(block);
646
+ document.body.appendChild(block);
652
647
  }
653
648
  emitDoc();
654
649
  return;
@@ -656,7 +651,7 @@ var INSPECTOR_SCRIPT = `
656
651
  if (d.type === "insert_html") {
657
652
  // A built-in section template (trusted markup from the toolbar).
658
653
  var anc = d.cid ? byCid(d.cid) : null;
659
- var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode : insertRoot();
654
+ var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode : document.body;
660
655
  var ref = (anc && anc.parentNode === container) ? anc.nextSibling : null;
661
656
  var frag = document.createElement("div");
662
657
  frag.innerHTML = d.html || "";
@@ -671,16 +666,7 @@ var INSPECTOR_SCRIPT = `
671
666
  var cids = d.cids || [];
672
667
  for (var g = 0; g < cids.length; g++) { var m = byCid(cids[g]); if (m) members.push(m); }
673
668
  if (members.length < 2) return;
674
- // Next free id is derived from the document, not a counter \u2014 the counter
675
- // reset on every reload, so a second group session reused "g0" and merged
676
- // with the previously-persisted group.
677
- var maxGid = -1;
678
- var existing = document.querySelectorAll("[data-group-id]");
679
- for (var q = 0; q < existing.length; q++) {
680
- var mm = /^g(d+)$/.exec(existing[q].getAttribute("data-group-id") || "");
681
- if (mm && Number(mm[1]) > maxGid) maxGid = Number(mm[1]);
682
- }
683
- var gid = "g" + (maxGid + 1);
669
+ var gid = "g" + (groupSeq++);
684
670
  for (var w = 0; w < members.length; w++) members[w].setAttribute("data-group-id", gid);
685
671
  clearSelected();
686
672
  emitDoc();
@@ -737,8 +723,41 @@ async function* mockStream(events, options = {}) {
737
723
  function sleep(ms) {
738
724
  return new Promise((resolve) => setTimeout(resolve, ms));
739
725
  }
740
- function useCanvasStream(options = {}) {
726
+
727
+ // src/transports/sse.ts
728
+ function sseTransport(options = {}) {
741
729
  const endpoint = options.endpoint ?? "/api/chat";
730
+ return {
731
+ stream(request) {
732
+ const streamOptions = { signal: request.signal, headers: options.headers };
733
+ return streamChat(
734
+ endpoint,
735
+ { threadId: request.threadId, message: request.message, selections: request.selections },
736
+ streamOptions
737
+ );
738
+ }
739
+ };
740
+ }
741
+
742
+ // src/transports/mock.ts
743
+ function mockTransport(script, fallback, options = {}) {
744
+ return {
745
+ stream(request) {
746
+ const events = script(request.message);
747
+ if (events) {
748
+ return mockStream(events, { delayMs: options.delayMs ?? 60, signal: request.signal });
749
+ }
750
+ if (fallback) return fallback.stream(request);
751
+ return mockStream([], { delayMs: 0 });
752
+ }
753
+ };
754
+ }
755
+ function useCanvasStream(options = {}) {
756
+ const { transport: customTransport, endpoint, mock } = options;
757
+ const transport = useMemo(() => {
758
+ const base = customTransport ?? sseTransport({ endpoint });
759
+ return mock ? mockTransport(mock, base) : base;
760
+ }, [customTransport, endpoint, mock]);
742
761
  const threadIdRef = useRef(options.threadId ?? crypto.randomUUID());
743
762
  const abortRef = useRef(null);
744
763
  const api = useCanvasStoreApi();
@@ -777,12 +796,12 @@ function useCanvasStream(options = {}) {
777
796
  const controller = new AbortController();
778
797
  abortRef.current = controller;
779
798
  try {
780
- const mockEvents = options.mock?.(text) ?? null;
781
- const stream = mockEvents ? mockStream(mockEvents, { delayMs: 60, signal: controller.signal }) : streamChat(
782
- endpoint,
783
- { threadId: threadIdRef.current, message: text, selections: withSelections },
784
- { signal: controller.signal }
785
- );
799
+ const stream = transport.stream({
800
+ threadId: threadIdRef.current,
801
+ message: text,
802
+ selections: withSelections,
803
+ signal: controller.signal
804
+ });
786
805
  for await (const event of stream) {
787
806
  enqueue(event);
788
807
  }
@@ -795,7 +814,7 @@ function useCanvasStream(options = {}) {
795
814
  api.getState().setStreaming(false);
796
815
  }
797
816
  },
798
- [api, endpoint, enqueue, flush, options.mock]
817
+ [api, transport, enqueue, flush]
799
818
  );
800
819
  const stop = useCallback(() => abortRef.current?.abort(), []);
801
820
  const reset = useCallback(() => api.getState().reset(), [api]);
@@ -843,7 +862,7 @@ function useCanvasReplay() {
843
862
  api.getState().applyEvent(event);
844
863
  }
845
864
  } finally {
846
- if (abortRef.current === controller) api.getState().setStreaming(false);
865
+ api.getState().setStreaming(false);
847
866
  }
848
867
  },
849
868
  [api]
@@ -1081,82 +1100,7 @@ var versions = {
1081
1100
  { type: "done" }
1082
1101
  ]
1083
1102
  };
1084
- var PDF_DATA_URL = "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9iajw8L1R5cGUvQ2F0YWxvZy9QYWdlcyAyIDAgUj4+ZW5kb2JqCjIgMCBvYmo8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PmVuZG9iagozIDAgb2JqPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgNjEyIDc5Ml0vQ29udGVudHMgNCAwIFIvUmVzb3VyY2VzPDwvRm9udDw8L0YxIDUgMCBSPj4+Pj4+ZW5kb2JqCjQgMCBvYmo8PC9MZW5ndGggODA+PnN0cmVhbQpCVCAvRjEgMjggVGYgNzIgNzAwIFRkIChsYW5nY2hhaW4tY2FudmFzIFBERiB2aWV3ZXIpIFRqIEVUCmVuZHN0cmVhbQplbmRvYmoKNSAwIG9iajw8L1R5cGUvRm9udC9TdWJ0eXBlL1R5cGUxL0Jhc2VGb250L0hlbHZldGljYT4+ZW5kb2JqCnhyZWYKMCA2CnRyYWlsZXI8PC9TaXplIDYvUm9vdCAxIDAgUj4+CiUlRU9GCg==";
1085
- var pdf = {
1086
- id: "pdf",
1087
- title: "PDF viewer",
1088
- description: "A PDF artifact shown in the browser's built-in viewer \u2014 zero dependencies.",
1089
- events: [
1090
- { type: "message.delta", messageId: "m-pdf", text: "Here's the signed report as a PDF." },
1091
- {
1092
- type: "canvas.create",
1093
- artifact: {
1094
- id: "report-pdf",
1095
- type: "pdf",
1096
- title: "Signed report",
1097
- version: 1,
1098
- status: "complete",
1099
- data: { src: PDF_DATA_URL, filename: "signed-report.pdf" }
1100
- }
1101
- },
1102
- { type: "canvas.status", id: "report-pdf", status: "complete" },
1103
- { type: "done" }
1104
- ]
1105
- };
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];
1103
+ var scenarios = [htmlPage, document2, chart, table, slides, versions];
1160
1104
  var RegistryContext = createContext({});
1161
1105
  function CanvasRegistryProvider({ registry, children }) {
1162
1106
  return /* @__PURE__ */ jsx(RegistryContext.Provider, { value: registry, children });
@@ -1302,202 +1246,40 @@ function display(value) {
1302
1246
  if (value == null) return "";
1303
1247
  if (typeof value === "object") {
1304
1248
  if (value.result != null) return display(value.result);
1305
- if (Array.isArray(value.richText)) return value.richText.map((r) => r?.text ?? "").join("");
1306
1249
  if (typeof value.text === "string") return value.text;
1307
1250
  if (value instanceof Date) return formatDate(value, "yyyy-mm-dd");
1308
1251
  return "";
1309
1252
  }
1310
1253
  return String(value);
1311
1254
  }
1312
- function splitSections(fmt) {
1313
- const sections = [];
1314
- let cur = "";
1315
- for (let i = 0; i < fmt.length; i++) {
1316
- const ch = fmt[i];
1317
- if (ch === '"') {
1318
- cur += ch;
1319
- i++;
1320
- while (i < fmt.length && fmt[i] !== '"') cur += fmt[i++];
1321
- if (i < fmt.length) cur += fmt[i];
1322
- continue;
1323
- }
1324
- if (ch === "[") {
1325
- cur += ch;
1326
- i++;
1327
- while (i < fmt.length && fmt[i] !== "]") cur += fmt[i++];
1328
- if (i < fmt.length) cur += fmt[i];
1329
- continue;
1330
- }
1331
- if (ch === "\\") {
1332
- cur += ch + (fmt[i + 1] ?? "");
1333
- i++;
1334
- continue;
1335
- }
1336
- if (ch === ";") {
1337
- sections.push(cur);
1338
- cur = "";
1339
- continue;
1340
- }
1341
- cur += ch;
1342
- }
1343
- sections.push(cur);
1344
- return sections;
1345
- }
1346
- function numberSkeleton(fmt) {
1347
- let currency = "";
1348
- let currencyTrails = false;
1349
- let skeleton = "";
1350
- let seenDigit = false;
1351
- const found = (sym) => {
1352
- if (currency) return;
1353
- currency = sym;
1354
- currencyTrails = seenDigit;
1355
- };
1356
- for (let i = 0; i < fmt.length; i++) {
1357
- const ch = fmt[i];
1358
- if (ch === '"') {
1359
- let lit = "";
1360
- i++;
1361
- while (i < fmt.length && fmt[i] !== '"') lit += fmt[i++];
1362
- if (/[$₩€£¥]/.test(lit)) found(lit);
1363
- continue;
1364
- }
1365
- if (ch === "\\") {
1366
- const c = fmt[i + 1] ?? "";
1367
- if (/[$₩€£¥]/.test(c)) found(c);
1368
- i++;
1369
- continue;
1370
- }
1371
- if (ch === "[") {
1372
- const end = fmt.indexOf("]", i);
1373
- const group = fmt.slice(i + 1, end === -1 ? void 0 : end);
1374
- const cur = /^\$(.*?)-/.exec(group)?.[1] ?? (group.startsWith("$") ? group.slice(1) : "");
1375
- if (cur) found(cur);
1376
- i = end === -1 ? fmt.length : end;
1377
- continue;
1378
- }
1379
- if (ch === "_" || ch === "*") {
1380
- i++;
1381
- continue;
1382
- }
1383
- if (/[$₩€£¥]/.test(ch)) {
1384
- found(ch);
1385
- continue;
1386
- }
1387
- if (ch === "0" || ch === "#") seenDigit = true;
1388
- skeleton += ch;
1389
- }
1390
- return { skeleton, currency, currencyTrails };
1391
- }
1392
1255
  function formatNumber(value, numFmt) {
1393
- const raw = numFmt && numFmt !== "General" ? numFmt : "";
1394
- if (!raw) return String(value);
1395
- const sections = splitSections(raw);
1396
- const negSection = value < 0 && sections.length > 1 ? sections[1] : void 0;
1397
- const section = negSection ?? (value === 0 && sections[2] ? sections[2] : sections[0]);
1398
- const { skeleton, currency, currencyTrails } = numberSkeleton(section);
1399
- if (!/[0#]/.test(skeleton)) return String(value);
1400
- const decimals = (skeleton.match(/\.([0#]+)/)?.[1] ?? "").length;
1401
- const thousands = /[#0],[#0]/.test(skeleton);
1402
- const n = negSection ? Math.abs(value) : value;
1403
- const percents = (skeleton.match(/%/g) ?? []).length;
1404
- const scaled = percents ? n * 100 ** percents : n;
1405
- let out = thousands ? scaled.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : scaled.toFixed(decimals);
1406
- if (percents) out += "%";
1407
- if (currency) {
1408
- if (currencyTrails) out = `${out}${currency}`;
1409
- else if (out.startsWith("-")) out = `-${currency}${out.slice(1)}`;
1410
- else out = `${currency}${out}`;
1411
- }
1412
- if (negSection && /\(/.test(skeleton)) out = `(${out})`;
1256
+ const fmt = numFmt && numFmt !== "General" ? numFmt : "";
1257
+ if (!fmt) return String(value);
1258
+ const decimals = (fmt.match(/\.([0#]+)/)?.[1] ?? "").length;
1259
+ if (fmt.includes("%")) return `${(value * 100).toFixed(decimals)}%`;
1260
+ const thousands = /[#0],[#0]/.test(fmt);
1261
+ let out = thousands ? value.toLocaleString("en-US", { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) : value.toFixed(decimals);
1262
+ const currency = fmt.match(/[$₩€£¥]/);
1263
+ if (currency) out = value < 0 ? `-${currency[0]}${out.slice(1)}` : `${currency[0]}${out}`;
1413
1264
  return out;
1414
1265
  }
1415
- var EXCEL_EPOCH_MS = Date.UTC(1899, 11, 30);
1416
1266
  function formatDate(d, numFmt) {
1417
- const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? splitSections(numFmt)[0] : "yyyy-mm-dd";
1267
+ const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? numFmt : "yyyy-mm-dd";
1418
1268
  const p = (n, w = 2) => String(n).padStart(w, "0");
1419
- const meridiem = /AM\/PM|A\/P/i.test(fmt);
1420
- const H = d.getUTCHours();
1421
- const hour12 = H % 12 === 0 ? 12 : H % 12;
1422
- const hourText = (width) => meridiem ? p(hour12, width) : p(H, width);
1423
- const elapsedMs = d.getTime() - EXCEL_EPOCH_MS;
1424
1269
  const map = {
1425
- yyyy: () => String(d.getUTCFullYear()),
1426
- yy: () => p(d.getUTCFullYear() % 100),
1427
- mmmm: () => d.toLocaleString("en-US", { month: "long", timeZone: "UTC" }),
1428
- mmm: () => d.toLocaleString("en-US", { month: "short", timeZone: "UTC" }),
1429
- dd: () => p(d.getUTCDate()),
1430
- d: () => String(d.getUTCDate()),
1431
- hh: () => hourText(2),
1432
- h: () => hourText(1),
1433
- ss: () => p(d.getUTCSeconds()),
1434
- s: () => String(d.getUTCSeconds())
1270
+ yyyy: String(d.getFullYear()),
1271
+ yy: p(d.getFullYear() % 100),
1272
+ mmmm: d.toLocaleString("en-US", { month: "long" }),
1273
+ mmm: d.toLocaleString("en-US", { month: "short" }),
1274
+ mm: p(d.getMonth() + 1),
1275
+ m: String(d.getMonth() + 1),
1276
+ dd: p(d.getDate()),
1277
+ d: String(d.getDate()),
1278
+ hh: p(d.getHours()),
1279
+ h: String(d.getHours()),
1280
+ ss: p(d.getSeconds())
1435
1281
  };
1436
- const month = (t) => t === "mm" ? p(d.getUTCMonth() + 1) : String(d.getUTCMonth() + 1);
1437
- const minute = (t) => t === "mm" ? p(d.getUTCMinutes()) : String(d.getUTCMinutes());
1438
- const TOKEN = /AM\/PM|A\/P|yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ss|s/iy;
1439
- let out = "";
1440
- let lastWasHours = false;
1441
- for (let i = 0; i < fmt.length; ) {
1442
- const ch = fmt[i];
1443
- if (ch === '"') {
1444
- i++;
1445
- while (i < fmt.length && fmt[i] !== '"') out += fmt[i++];
1446
- i++;
1447
- continue;
1448
- }
1449
- if (ch === "\\") {
1450
- out += fmt[i + 1] ?? "";
1451
- i += 2;
1452
- continue;
1453
- }
1454
- if (ch === "[") {
1455
- const end = fmt.indexOf("]", i);
1456
- const group = fmt.slice(i + 1, end === -1 ? fmt.length : end);
1457
- i = end === -1 ? fmt.length : end + 1;
1458
- if (/^h+$/i.test(group)) {
1459
- out += p(Math.floor(elapsedMs / 36e5), group.length);
1460
- lastWasHours = true;
1461
- } else if (/^m+$/i.test(group)) out += p(Math.floor(elapsedMs / 6e4), group.length);
1462
- else if (/^s+$/i.test(group)) out += p(Math.floor(elapsedMs / 1e3), group.length);
1463
- continue;
1464
- }
1465
- if (ch === "_") {
1466
- out += " ";
1467
- i += 2;
1468
- continue;
1469
- }
1470
- if (ch === "*") {
1471
- i += 2;
1472
- continue;
1473
- }
1474
- TOKEN.lastIndex = i;
1475
- const m = TOKEN.exec(fmt);
1476
- if (m) {
1477
- const tok = m[0].toLowerCase();
1478
- i += m[0].length;
1479
- if (tok === "am/pm") {
1480
- out += H < 12 ? "AM" : "PM";
1481
- continue;
1482
- }
1483
- if (tok === "a/p") {
1484
- out += H < 12 ? "A" : "P";
1485
- continue;
1486
- }
1487
- if (tok === "m" || tok === "mm") {
1488
- const minutes = lastWasHours || /^[\s:.,\-]*s/i.test(fmt.slice(i));
1489
- out += minutes ? minute(tok) : month(tok);
1490
- lastWasHours = false;
1491
- continue;
1492
- }
1493
- lastWasHours = tok === "h" || tok === "hh";
1494
- out += map[tok]();
1495
- continue;
1496
- }
1497
- out += ch;
1498
- i++;
1499
- }
1500
- return out;
1282
+ return fmt.replace(/yyyy|yy|mmmm|mmm|mm|m|dd|d|hh|h|ss/g, (t) => map[t] ?? t);
1501
1283
  }
1502
1284
  function cellValue(cell) {
1503
1285
  const raw = cell.value;
@@ -1696,7 +1478,6 @@ function cellVal(v) {
1696
1478
  if (typeof v === "number" || typeof v === "string") return v;
1697
1479
  if (typeof v === "object") {
1698
1480
  if (v.result != null) return cellVal(v.result);
1699
- if (Array.isArray(v.richText)) return v.richText.map((r) => r?.text ?? "").join("");
1700
1481
  if (typeof v.text === "string") return v.text;
1701
1482
  if (v instanceof Date) return v.toISOString().slice(0, 10);
1702
1483
  }
@@ -1739,1670 +1520,8 @@ function flatten(ws) {
1739
1520
  return { columns, rows };
1740
1521
  }
1741
1522
 
1742
- // src/io/hwpx.ts
1743
- var EOCD_SIG = 101010256;
1744
- var CDIR_SIG = 33639248;
1745
- var LOCAL_SIG = 67324752;
1746
- async function inflateRaw(bytes) {
1747
- if (typeof DecompressionStream === "undefined") {
1748
- throw new Error("\uC774 \uBE0C\uB77C\uC6B0\uC800\uB294 \uC555\uCD95 \uD574\uC81C\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (DecompressionStream unavailable).");
1749
- }
1750
- const ds = new DecompressionStream("deflate-raw");
1751
- const writer = ds.writable.getWriter();
1752
- const wrote = writer.write(bytes).then(() => writer.close());
1753
- const reader = ds.readable.getReader();
1754
- const chunks = [];
1755
- let total = 0;
1756
- for (; ; ) {
1757
- const { done, value } = await reader.read();
1758
- if (done) break;
1759
- chunks.push(value);
1760
- total += value.length;
1761
- }
1762
- await wrote;
1763
- const out = new Uint8Array(total);
1764
- let pos = 0;
1765
- for (const c of chunks) {
1766
- out.set(c, pos);
1767
- pos += c.length;
1768
- }
1769
- return out;
1770
- }
1771
- async function readZip(buffer) {
1772
- const view = new DataView(buffer);
1773
- const bytes = new Uint8Array(buffer);
1774
- let eocd = -1;
1775
- const floor = Math.max(0, buffer.byteLength - 22 - 65535);
1776
- for (let i = buffer.byteLength - 22; i >= floor; i--) {
1777
- if (view.getUint32(i, true) === EOCD_SIG) {
1778
- eocd = i;
1779
- break;
1780
- }
1781
- }
1782
- if (eocd === -1) throw new Error("ZIP \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 (no end-of-central-directory).");
1783
- const count = view.getUint16(eocd + 10, true);
1784
- let offset = view.getUint32(eocd + 16, true);
1785
- const entries = /* @__PURE__ */ new Map();
1786
- const decoder = new TextDecoder();
1787
- for (let i = 0; i < count; i++) {
1788
- if (offset + 46 > buffer.byteLength || view.getUint32(offset, true) !== CDIR_SIG) {
1789
- throw new Error("ZIP \uC911\uC559 \uB514\uB809\uD130\uB9AC\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (corrupt central directory).");
1790
- }
1791
- const method = view.getUint16(offset + 10, true);
1792
- const compressedSize = view.getUint32(offset + 20, true);
1793
- const uncompressedSize = view.getUint32(offset + 24, true);
1794
- const nameLen = view.getUint16(offset + 28, true);
1795
- const extraLen = view.getUint16(offset + 30, true);
1796
- const commentLen = view.getUint16(offset + 32, true);
1797
- const localOffset = view.getUint32(offset + 42, true);
1798
- const name = decoder.decode(bytes.subarray(offset + 46, offset + 46 + nameLen));
1799
- offset += 46 + nameLen + extraLen + commentLen;
1800
- if (compressedSize === 4294967295 || uncompressedSize === 4294967295) {
1801
- throw new Error("Zip64 \uD615\uC2DD\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (Zip64 not supported).");
1802
- }
1803
- if (name.endsWith("/")) continue;
1804
- if (localOffset + 30 > buffer.byteLength || view.getUint32(localOffset, true) !== LOCAL_SIG) {
1805
- throw new Error("ZIP \uB85C\uCEEC \uD5E4\uB354\uAC00 \uC190\uC0C1\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (corrupt local header).");
1806
- }
1807
- const localNameLen = view.getUint16(localOffset + 26, true);
1808
- const localExtraLen = view.getUint16(localOffset + 28, true);
1809
- const start = localOffset + 30 + localNameLen + localExtraLen;
1810
- const data = bytes.subarray(start, start + compressedSize);
1811
- if (method === 0) entries.set(name, data);
1812
- else if (method === 8) entries.set(name, await inflateRaw(data));
1813
- else throw new Error(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC555\uCD95 \uBC29\uC2DD\uC785\uB2C8\uB2E4 (compression method ${method}).`);
1814
- }
1815
- return entries;
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
- }
1921
- function paragraphText(p) {
1922
- let out = "";
1923
- const walk = (el) => {
1924
- for (const child of Array.from(el.children)) {
1925
- const tag = child.localName;
1926
- if (tag === "tbl") continue;
1927
- if (tag === "t") out += child.textContent ?? "";
1928
- else if (tag === "lineBreak") out += "\n";
1929
- else walk(child);
1930
- }
1931
- };
1932
- walk(p);
1933
- return out;
1934
- }
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 = "";
2110
- for (const tr of Array.from(tbl.getElementsByTagNameNS("*", "tr"))) {
2111
- if (tr.closest("tbl") !== tbl) continue;
2112
- let cells = "";
2113
- for (const tc of Array.from(tr.children)) {
2114
- if (tc.localName !== "tc") continue;
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>`;
2139
- }
2140
- if (cells) rowsHtml += `<tr>${cells}</tr>`;
2141
- }
2142
- return rowsHtml ? `<table>${rowsHtml}</table>` : "";
2143
- }
2144
- function sectionToHtml(xml, ctx) {
2145
- const doc = parseXml(xml);
2146
- if (!doc) {
2147
- throw new Error("HWPX \uBCF8\uBB38 XML\uC744 \uD574\uC11D\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (invalid section XML).");
2148
- }
2149
- const blocks = [];
2150
- const walk = (el) => {
2151
- for (const child of Array.from(el.children)) {
2152
- if (child.localName === "p") paragraphToHtmlBlocks(child, blocks, ctx);
2153
- else if (child.localName !== "tbl") walk(child);
2154
- }
2155
- };
2156
- walk(doc.documentElement);
2157
- return blocks;
2158
- }
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
- };
2173
- const blocks = [];
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>`);
2179
- }
2180
- return koreanDocHtml(blocks.join("\n"));
2181
- }
2182
-
2183
- // src/io/hwp.ts
2184
- var ERR = {
2185
- notCfb: "CFB(OLE) \uCEE8\uD14C\uC774\uB108 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uC62C\uBC14\uB978 HWP \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4. / Not a CFB (OLE) compound file container \u2014 not a valid HWP file.",
2186
- notHwp: '"HWP Document File" \uC2DC\uADF8\uB2C8\uCC98\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 HWP \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4. / Missing "HWP Document File" signature \u2014 not an HWP document.',
2187
- encrypted: "\uC554\uD638\uB85C \uBCF4\uD638\uB41C HWP \uBB38\uC11C\uC785\uB2C8\uB2E4 \u2014 \uD55C\uAE00\uC5D0\uC11C \uC554\uD638\uB97C \uD574\uC81C\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694. / This HWP document is password-encrypted \u2014 remove the password in Hangul and try again.",
2188
- drm: "\uBC30\uD3EC\uC6A9(DRM) HWP \uBB38\uC11C\uC785\uB2C8\uB2E4 \u2014 \uBCF8\uBB38\uC774 \uB09C\uB3C5\uD654\uB418\uC5B4 \uC788\uC5B4 \uD14D\uC2A4\uD2B8\uB97C \uCD94\uCD9C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. / This is a distribution (DRM) HWP document \u2014 its body text is obfuscated and cannot be extracted.",
2189
- legacy: (version) => `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 HWP \uBC84\uC804\uC785\uB2C8\uB2E4 (${version} < 5.0) \u2014 \uD55C\uAE00\uC5D0\uC11C HWP 5.0 \uC774\uC0C1\uC73C\uB85C \uC800\uC7A5\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694. / Unsupported HWP version (${version} < 5.0) \u2014 re-save as HWP 5.0+ in Hangul and try again.`,
2190
- corrupt: (detail) => `\uC190\uC0C1\uB41C HWP \uD30C\uC77C\uC785\uB2C8\uB2E4 (${detail}). / Corrupt HWP file (${detail}).`,
2191
- noInflate: "\uC774 \uD658\uACBD\uC5D0\uC11C\uB294 DecompressionStream\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 \uC555\uCD95\uB41C HWP\uB97C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. / DecompressionStream is unavailable in this environment, so compressed HWP files cannot be opened."
2192
- };
2193
- var ENDOFCHAIN = 4294967294;
2194
- var CFB_SIGNATURE = [208, 207, 17, 224, 161, 177, 26, 225];
2195
- function parseCfb(bytes) {
2196
- if (bytes.length < 512 || CFB_SIGNATURE.some((b, i) => bytes[i] !== b)) {
2197
- throw new Error(ERR.notCfb);
2198
- }
2199
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2200
- const sectorSize = 1 << view.getUint16(30, true);
2201
- const miniSectorSize = 1 << view.getUint16(32, true);
2202
- const numFatSectors = view.getUint32(44, true);
2203
- const firstDirSector = view.getUint32(48, true);
2204
- const miniCutoff = view.getUint32(56, true);
2205
- const firstMiniFatSector = view.getUint32(60, true);
2206
- const firstDifatSector = view.getUint32(68, true);
2207
- const totalSectors = Math.max(0, Math.ceil((bytes.length - 512) / sectorSize));
2208
- const sectorBytes = (sector) => {
2209
- const start = 512 + sector * sectorSize;
2210
- if (sector >= totalSectors || start >= bytes.length) {
2211
- throw new Error(ERR.corrupt("sector out of range"));
2212
- }
2213
- return bytes.subarray(start, start + sectorSize);
2214
- };
2215
- const fatSectorIds = [];
2216
- for (let i = 0; i < 109 && fatSectorIds.length < numFatSectors; i++) {
2217
- fatSectorIds.push(view.getUint32(76 + i * 4, true));
2218
- }
2219
- let difatSector = firstDifatSector;
2220
- let difatHops = 0;
2221
- while (difatSector !== ENDOFCHAIN && difatSector < 4294967290) {
2222
- if (difatHops++ > totalSectors) throw new Error(ERR.corrupt("DIFAT chain loops"));
2223
- const s = sectorBytes(difatSector);
2224
- const sv = new DataView(s.buffer, s.byteOffset, s.byteLength);
2225
- const perSector = sectorSize / 4 - 1;
2226
- for (let i = 0; i < perSector && fatSectorIds.length < numFatSectors; i++) {
2227
- fatSectorIds.push(sv.getUint32(i * 4, true));
2228
- }
2229
- difatSector = sv.getUint32(sectorSize - 4, true);
2230
- }
2231
- const fat = new Uint32Array(fatSectorIds.length * (sectorSize / 4));
2232
- fatSectorIds.forEach((id, idx) => {
2233
- const s = sectorBytes(id);
2234
- const sv = new DataView(s.buffer, s.byteOffset, s.byteLength);
2235
- for (let i = 0; i < sectorSize / 4; i++) fat[idx * (sectorSize / 4) + i] = sv.getUint32(i * 4, true);
2236
- });
2237
- const readChain = (start, size) => {
2238
- const out = new Uint8Array(size);
2239
- let sector = start;
2240
- let written = 0;
2241
- let hops = 0;
2242
- while (sector !== ENDOFCHAIN && written < size) {
2243
- if (hops++ > totalSectors || sector >= fat.length) throw new Error(ERR.corrupt("FAT chain loops"));
2244
- const chunk = sectorBytes(sector);
2245
- out.set(chunk.subarray(0, Math.min(sectorSize, size - written)), written);
2246
- written += sectorSize;
2247
- sector = fat[sector];
2248
- }
2249
- return out;
2250
- };
2251
- const dirSectors = [];
2252
- let dirSector = firstDirSector;
2253
- let dirHops = 0;
2254
- while (dirSector !== ENDOFCHAIN) {
2255
- if (dirHops++ > totalSectors || dirSector >= fat.length) throw new Error(ERR.corrupt("directory chain loops"));
2256
- dirSectors.push(sectorBytes(dirSector));
2257
- dirSector = fat[dirSector];
2258
- }
2259
- const entries = [];
2260
- for (const sector of dirSectors) {
2261
- const sv = new DataView(sector.buffer, sector.byteOffset, sector.byteLength);
2262
- for (let off = 0; off + 128 <= sector.length; off += 128) {
2263
- const nameLen = sv.getUint16(off + 64, true);
2264
- const type = sv.getUint8(off + 66);
2265
- if (type === 0 || nameLen < 2 || nameLen > 64) continue;
2266
- let name = "";
2267
- for (let i = 0; i < nameLen - 2; i += 2) name += String.fromCharCode(sv.getUint16(off + i, true));
2268
- entries.push({
2269
- name,
2270
- type,
2271
- startSector: sv.getUint32(off + 116, true),
2272
- // Size is a uint64, but HWP streams are far below 4 GB — the low half suffices.
2273
- size: sv.getUint32(off + 120, true)
2274
- });
2275
- }
2276
- }
2277
- const root = entries.find((e) => e.type === 5);
2278
- if (!root) throw new Error(ERR.corrupt("missing root directory entry"));
2279
- let miniStream = null;
2280
- let miniFat = null;
2281
- const lazyMini = () => {
2282
- if (miniStream && miniFat) return;
2283
- miniStream = readChain(root.startSector, root.size);
2284
- const miniFatSectors = Math.ceil(root.size / miniSectorSize) + 1;
2285
- const raw = readChain(firstMiniFatSector, miniFatSectors * 4 + sectorSize);
2286
- const rv = new DataView(raw.buffer, raw.byteOffset, raw.byteLength);
2287
- miniFat = new Uint32Array(Math.floor(raw.length / 4));
2288
- for (let i = 0; i < miniFat.length; i++) miniFat[i] = rv.getUint32(i * 4, true);
2289
- };
2290
- const readMiniChain = (start, size) => {
2291
- lazyMini();
2292
- const out = new Uint8Array(size);
2293
- let sector = start;
2294
- let written = 0;
2295
- let hops = 0;
2296
- const maxMini = Math.ceil(miniStream.length / miniSectorSize);
2297
- while (sector !== ENDOFCHAIN && written < size) {
2298
- if (hops++ > maxMini || sector >= miniFat.length || sector >= maxMini) {
2299
- throw new Error(ERR.corrupt("mini FAT chain loops"));
2300
- }
2301
- const at = sector * miniSectorSize;
2302
- out.set(miniStream.subarray(at, at + Math.min(miniSectorSize, size - written)), written);
2303
- written += miniSectorSize;
2304
- sector = miniFat[sector];
2305
- }
2306
- return out;
2307
- };
2308
- return {
2309
- entries,
2310
- readStream: (entry) => entry.size < miniCutoff && entry.type !== 5 ? readMiniChain(entry.startSector, entry.size) : readChain(entry.startSector, entry.size)
2311
- };
2312
- }
2313
- var HWP_SIGNATURE = "HWP Document File";
2314
- function parseFileHeader(bytes) {
2315
- if (bytes.length < 40) throw new Error(ERR.notHwp);
2316
- let sig = "";
2317
- for (let i = 0; i < HWP_SIGNATURE.length; i++) sig += String.fromCharCode(bytes[i]);
2318
- if (sig !== HWP_SIGNATURE) throw new Error(ERR.notHwp);
2319
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2320
- const version = view.getUint32(32, true);
2321
- const major = version >>> 24 & 255;
2322
- if (major < 5) {
2323
- throw new Error(ERR.legacy(`${major}.${version >>> 16 & 255}`));
2324
- }
2325
- const flags = view.getUint32(36, true);
2326
- if (flags & 2) throw new Error(ERR.encrypted);
2327
- if (flags & 4) throw new Error(ERR.drm);
2328
- return { compressed: (flags & 1) !== 0 };
2329
- }
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;
2335
- function decodeParaText(view, offset, byteLength) {
2336
- const paragraphs = [];
2337
- let text = "";
2338
- const count = byteLength >> 1;
2339
- for (let i = 0; i < count; i++) {
2340
- const code = view.getUint16(offset + i * 2, true);
2341
- if (code >= 32) {
2342
- text += String.fromCharCode(code);
2343
- continue;
2344
- }
2345
- switch (code) {
2346
- case 13:
2347
- paragraphs.push(text);
2348
- text = "";
2349
- break;
2350
- case 10:
2351
- text += "\n";
2352
- break;
2353
- case 0:
2354
- case 24:
2355
- case 25:
2356
- case 26:
2357
- case 27:
2358
- case 28:
2359
- case 29:
2360
- case 30:
2361
- case 31:
2362
- break;
2363
- // 1-WCHAR char controls with no text representation
2364
- case 9:
2365
- i += 7;
2366
- text += " ";
2367
- break;
2368
- default:
2369
- i += 7;
2370
- break;
2371
- }
2372
- }
2373
- if (text) paragraphs.push(text);
2374
- return paragraphs;
2375
- }
2376
- function parseRecords(section, view) {
2377
- const records = [];
2378
- let off = 0;
2379
- while (off + 4 <= section.length) {
2380
- const header = view.getUint32(off, true);
2381
- off += 4;
2382
- const tag = header & 1023;
2383
- const level = header >>> 10 & 1023;
2384
- let size = header >>> 20 & 4095;
2385
- if (size === 4095) {
2386
- if (off + 4 > section.length) throw new Error(ERR.corrupt("truncated record header"));
2387
- size = view.getUint32(off, true);
2388
- off += 4;
2389
- }
2390
- if (off + size > section.length) throw new Error(ERR.corrupt("record overruns section"));
2391
- records.push({ tag, level, start: off, size });
2392
- off += size;
2393
- }
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;
2458
- }
2459
- async function inflateRaw2(data) {
2460
- if (typeof DecompressionStream === "undefined") throw new Error(ERR.noInflate);
2461
- const stream = new DecompressionStream("deflate-raw");
2462
- const writer = stream.writable.getWriter();
2463
- const writing = writer.write(data).then(() => writer.close());
2464
- writing.catch(() => {
2465
- });
2466
- const reader = stream.readable.getReader();
2467
- const chunks = [];
2468
- let total = 0;
2469
- for (; ; ) {
2470
- const { done, value } = await reader.read();
2471
- if (done) break;
2472
- chunks.push(value);
2473
- total += value.length;
2474
- }
2475
- const out = new Uint8Array(total);
2476
- let at = 0;
2477
- for (const chunk of chunks) {
2478
- out.set(chunk, at);
2479
- at += chunk.length;
2480
- }
2481
- return out;
2482
- }
2483
- async function extractBlocks(buffer) {
2484
- const cfb = parseCfb(new Uint8Array(buffer));
2485
- const headerEntry = cfb.entries.find((e) => e.type === 2 && e.name === "FileHeader");
2486
- if (!headerEntry) throw new Error(ERR.notHwp);
2487
- const header = parseFileHeader(cfb.readStream(headerEntry));
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]));
2489
- const blocks = [];
2490
- for (const { entry } of sections) {
2491
- let bytes = cfb.readStream(entry);
2492
- if (header.compressed) bytes = await inflateRaw2(bytes);
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 += " ";
2536
- }
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 };
3388
- }
3389
-
3390
1523
  // src/io/importers.ts
3391
- var IMPORTABLE_EXTENSIONS = [
3392
- ".csv",
3393
- ".md",
3394
- ".markdown",
3395
- ".txt",
3396
- ".html",
3397
- ".htm",
3398
- ".json",
3399
- ".xlsx",
3400
- ".pdf",
3401
- ".hwpx",
3402
- ".hwp",
3403
- ".docx",
3404
- ".pptx"
3405
- ];
1524
+ var IMPORTABLE_EXTENSIONS = [".csv", ".md", ".markdown", ".txt", ".html", ".htm", ".json", ".xlsx"];
3406
1525
  var extensionOf = (name) => {
3407
1526
  const dot = name.lastIndexOf(".");
3408
1527
  return dot === -1 ? "" : name.slice(dot).toLowerCase();
@@ -3443,26 +1562,6 @@ async function importFile(file) {
3443
1562
  const { sheets, columns, rows } = await xlsxToSheets(await file.arrayBuffer(), () => loadOptional("exceljs", () => import('exceljs')));
3444
1563
  return toEvents(artifact(id, "table", title, { columns, rows, sheet: sheets }));
3445
1564
  }
3446
- case ".pdf": {
3447
- const src = await fileToDataUrl(file);
3448
- return toEvents(artifact(id, "pdf", title, { src, filename: file.name }));
3449
- }
3450
- case ".hwpx": {
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());
3456
- return toEvents(artifact(id, "document", title, { format: "markdown", content }));
3457
- }
3458
- case ".pptx": {
3459
- const data = await pptxToSlides(await file.arrayBuffer());
3460
- return toEvents(artifact(id, "slides", title, data));
3461
- }
3462
- case ".hwp": {
3463
- const html = await hwpToHtml(await file.arrayBuffer());
3464
- return toEvents({ ...artifact(id, "html", title, { html }), meta: { kind: "doc" } });
3465
- }
3466
1565
  default:
3467
1566
  throw new Error(`Unsupported file type "${ext || file.name}". Supported: ${IMPORTABLE_EXTENSIONS.join(", ")}`);
3468
1567
  }
@@ -3470,14 +1569,6 @@ async function importFile(file) {
3470
1569
  function artifact(id, type, title, data) {
3471
1570
  return { id, type, title, version: 1, status: "complete", data };
3472
1571
  }
3473
- function fileToDataUrl(file) {
3474
- return new Promise((resolve, reject) => {
3475
- const reader = new FileReader();
3476
- reader.onload = () => resolve(reader.result);
3477
- reader.onerror = () => reject(reader.error ?? new Error("Failed to read file."));
3478
- reader.readAsDataURL(file);
3479
- });
3480
- }
3481
1572
  function importJson(text, id, title) {
3482
1573
  let parsed;
3483
1574
  try {
@@ -3550,8 +1641,6 @@ var DEVICES = [
3550
1641
  { id: "tablet", label: "Tablet", width: "768px" },
3551
1642
  { id: "mobile", label: "Mobile", width: "390px" }
3552
1643
  ];
3553
- var BLOCK_KEYS = { h2: "heading", p: "text", button: "button", img: "image", hr: "divider" };
3554
- var DEVICE_KEYS = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
3555
1644
  var BLOCKS = [
3556
1645
  { tag: "h2", label: "Heading" },
3557
1646
  { tag: "p", label: "Text" },
@@ -3797,8 +1886,8 @@ function checkA11y(html) {
3797
1886
  if (!doc.querySelector("h1")) issues.push("No <h1> \u2014 every page needs one top-level heading");
3798
1887
  return issues;
3799
1888
  }
3800
- var SLIDE_H = 720;
3801
- var SCROLL_FIX = "<style data-lcx>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
1889
+ var SLIDE_W = 1280;
1890
+ var SCROLL_FIX = "<style>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
3802
1891
  function withScrollableBody(html) {
3803
1892
  const i = html.lastIndexOf("</body>");
3804
1893
  return i === -1 ? html + SCROLL_FIX : html.slice(0, i) + SCROLL_FIX + html.slice(i);
@@ -3806,7 +1895,7 @@ function withScrollableBody(html) {
3806
1895
  function useSlideFit(ratio, boxRef) {
3807
1896
  const [scale, setScale] = useState(1);
3808
1897
  const [rw, rh] = (ratio ?? "16:9").split(/[:x/]/).map(Number);
3809
- const width = rw && rh ? Math.round(SLIDE_H * rw / rh) : 1280;
1898
+ const height = rw && rh ? Math.round(SLIDE_W * rh / rw) : 720;
3810
1899
  useEffect(() => {
3811
1900
  if (!ratio) return;
3812
1901
  const el = boxRef.current;
@@ -3814,17 +1903,16 @@ function useSlideFit(ratio, boxRef) {
3814
1903
  const fit = () => {
3815
1904
  const w = el.clientWidth;
3816
1905
  if (w <= 40) return;
3817
- setScale(Math.min(1, (w - 40) / width));
1906
+ setScale(Math.min(1, (w - 40) / SLIDE_W));
3818
1907
  };
3819
1908
  fit();
3820
1909
  const ro = new ResizeObserver(fit);
3821
1910
  ro.observe(el);
3822
1911
  return () => ro.disconnect();
3823
- }, [ratio, width]);
3824
- return { scale, width, height: SLIDE_H };
1912
+ }, [ratio, height]);
1913
+ return { scale, width: SLIDE_W, height };
3825
1914
  }
3826
1915
  function HtmlRenderer({ artifact: artifact2 }) {
3827
- const t = useT();
3828
1916
  const iframeRef = useRef(null);
3829
1917
  const imgFileRef = useRef(null);
3830
1918
  const bgFileRef = useRef(null);
@@ -3835,6 +1923,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
3835
1923
  const sendIframeCommand = useCanvasStore((s) => s.sendIframeCommand);
3836
1924
  const selections = useCanvasStore((s) => s.selections);
3837
1925
  const iframeCommand = useCanvasStore((s) => s.iframeCommand);
1926
+ const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
3838
1927
  const [device, setDevice] = useState("desktop");
3839
1928
  const [mode, setMode] = useState("design");
3840
1929
  const [a11y, setA11y] = useState(null);
@@ -3843,11 +1932,11 @@ function HtmlRenderer({ artifact: artifact2 }) {
3843
1932
  const isFixedSlide = Boolean(artifact2.meta?.ratio);
3844
1933
  const srcDoc = useMemo(() => {
3845
1934
  if (mode === "design" && artifact2.data.html === lastSelfHtml.current) return srcDocRef.current;
3846
- const base = withInspector(artifact2.data.html);
1935
+ const base = withInspector(artifact2.data.html, assetBaseUrl ?? void 0);
3847
1936
  srcDocRef.current = isFixedSlide ? base : withScrollableBody(base);
3848
1937
  lastSelfHtml.current = null;
3849
1938
  return srcDocRef.current;
3850
- }, [artifact2.data.html, mode, isFixedSlide]);
1939
+ }, [artifact2.data.html, mode, isFixedSlide, assetBaseUrl]);
3851
1940
  const selected = selections.filter((s) => s.artifactId === artifact2.id);
3852
1941
  const single = selected.length === 1 ? selected[0] : null;
3853
1942
  const outline = useMemo(() => {
@@ -3924,7 +2013,6 @@ function HtmlRenderer({ artifact: artifact2 }) {
3924
2013
  };
3925
2014
  const setSlideStyle = (style) => sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style });
3926
2015
  const ratio = artifact2.meta?.ratio;
3927
- const isDoc = !ratio && artifact2.meta?.kind === "doc";
3928
2016
  const slide = useSlideFit(ratio, stageRef);
3929
2017
  return /* @__PURE__ */ jsxs("div", { className: "cv-html-wrap", children: [
3930
2018
  /* @__PURE__ */ jsx("input", { ref: imgFileRef, type: "file", accept: "image/*", hidden: true, onChange: (e) => {
@@ -3937,30 +2025,26 @@ function HtmlRenderer({ artifact: artifact2 }) {
3937
2025
  } }),
3938
2026
  /* @__PURE__ */ jsxs("div", { className: "cv-html-bar cv-chrome", children: [
3939
2027
  mode === "design" && /* @__PURE__ */ jsxs(Fragment, { children: [
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)) }),
2028
+ !ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
2029
+ /* @__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)) }),
3942
2030
  /* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" })
3943
2031
  ] }),
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: [
2032
+ /* @__PURE__ */ jsx("span", { className: "cv-html-bar__label", children: "Add" }),
2033
+ (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)),
2034
+ !ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
3951
2035
  /* @__PURE__ */ jsxs(
3952
2036
  "select",
3953
2037
  {
3954
2038
  className: "cv-html-tpl",
3955
2039
  value: "",
3956
- title: t("pageTemplateTitle"),
2040
+ title: "Start from a full page template (replaces the page)",
3957
2041
  onChange: (e) => {
3958
2042
  const s = STARTERS[e.target.value];
3959
2043
  if (s) applyEvent({ type: "canvas.patch", id: artifact2.id, patch: { html: s.build() } });
3960
2044
  e.currentTarget.value = "";
3961
2045
  },
3962
2046
  children: [
3963
- /* @__PURE__ */ jsx("option", { value: "", children: t("pageMenu") }),
2047
+ /* @__PURE__ */ jsx("option", { value: "", children: "Page\u2026" }),
3964
2048
  Object.entries(STARTERS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
3965
2049
  ]
3966
2050
  }
@@ -3970,14 +2054,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
3970
2054
  {
3971
2055
  className: "cv-html-tpl",
3972
2056
  value: "",
3973
- title: t("sectionTemplateTitle"),
2057
+ title: "Insert a section template",
3974
2058
  onChange: (e) => {
3975
- const t2 = TEMPLATES[e.target.value];
3976
- if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
2059
+ const t = TEMPLATES[e.target.value];
2060
+ if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
3977
2061
  e.currentTarget.value = "";
3978
2062
  },
3979
2063
  children: [
3980
- /* @__PURE__ */ jsx("option", { value: "", children: t("sectionMenu") }),
2064
+ /* @__PURE__ */ jsx("option", { value: "", children: "Section\u2026" }),
3981
2065
  Object.entries(TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
3982
2066
  ]
3983
2067
  }
@@ -3987,51 +2071,34 @@ function HtmlRenderer({ artifact: artifact2 }) {
3987
2071
  {
3988
2072
  className: "cv-html-tpl",
3989
2073
  value: "",
3990
- title: t("jumpToHeading"),
2074
+ title: "Jump to a heading",
3991
2075
  onChange: (e) => {
3992
2076
  const idx = Number(e.target.value);
3993
2077
  if (!Number.isNaN(idx)) sendIframeCommand({ artifactId: artifact2.id, type: "scroll_to", index: idx });
3994
2078
  e.currentTarget.value = "";
3995
2079
  },
3996
2080
  children: [
3997
- /* @__PURE__ */ jsx("option", { value: "", children: t("outlineMenu") }),
2081
+ /* @__PURE__ */ jsx("option", { value: "", children: "Outline\u2026" }),
3998
2082
  outline.map((h, i) => /* @__PURE__ */ jsx("option", { value: i, children: "\xA0".repeat((h.level - 1) * 2) + h.text }, i))
3999
2083
  ]
4000
2084
  }
4001
2085
  ),
4002
- /* @__PURE__ */ jsx("button", { className: "cv-html-add", title: t("a11yTitle"), onClick: () => setA11y(checkA11y(artifact2.data.html)), children: "\u267F Check" })
2086
+ /* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "Accessibility check", onClick: () => setA11y(checkA11y(artifact2.data.html)), children: "\u267F Check" })
4003
2087
  ] }),
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
- ),
4021
2088
  ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
4022
2089
  /* @__PURE__ */ jsxs(
4023
2090
  "select",
4024
2091
  {
4025
2092
  className: "cv-html-tpl",
4026
2093
  value: "",
4027
- title: t("insertSlideLayout"),
2094
+ title: "Insert a slide layout",
4028
2095
  onChange: (e) => {
4029
- const t2 = SLIDE_TEMPLATES[e.target.value];
4030
- if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
2096
+ const t = SLIDE_TEMPLATES[e.target.value];
2097
+ if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
4031
2098
  e.currentTarget.value = "";
4032
2099
  },
4033
2100
  children: [
4034
- /* @__PURE__ */ jsx("option", { value: "", children: t("layoutMenu") }),
2101
+ /* @__PURE__ */ jsx("option", { value: "", children: "Layout\u2026" }),
4035
2102
  Object.entries(SLIDE_TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
4036
2103
  ]
4037
2104
  }
@@ -4041,14 +2108,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
4041
2108
  {
4042
2109
  className: "cv-html-tpl",
4043
2110
  value: "",
4044
- title: t("applySlideTheme"),
2111
+ title: "Apply a slide theme",
4045
2112
  onChange: (e) => {
4046
- const t2 = SLIDE_THEMES[e.target.value];
4047
- if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style: t2.style });
2113
+ const t = SLIDE_THEMES[e.target.value];
2114
+ if (t) sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style: t.style });
4048
2115
  e.currentTarget.value = "";
4049
2116
  },
4050
2117
  children: [
4051
- /* @__PURE__ */ jsx("option", { value: "", children: t("themeMenu") }),
2118
+ /* @__PURE__ */ jsx("option", { value: "", children: "Theme\u2026" }),
4052
2119
  Object.entries(SLIDE_THEMES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
4053
2120
  ]
4054
2121
  }
@@ -4058,14 +2125,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
4058
2125
  {
4059
2126
  className: "cv-html-tpl",
4060
2127
  value: "",
4061
- title: t("insertShape"),
2128
+ title: "Insert a shape",
4062
2129
  onChange: (e) => {
4063
- const t2 = SLIDE_SHAPES[e.target.value];
4064
- if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
2130
+ const t = SLIDE_SHAPES[e.target.value];
2131
+ if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
4065
2132
  e.currentTarget.value = "";
4066
2133
  },
4067
2134
  children: [
4068
- /* @__PURE__ */ jsx("option", { value: "", children: t("shapeMenu") }),
2135
+ /* @__PURE__ */ jsx("option", { value: "", children: "Shape\u2026" }),
4069
2136
  Object.entries(SLIDE_SHAPES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
4070
2137
  ]
4071
2138
  }
@@ -4075,19 +2142,19 @@ function HtmlRenderer({ artifact: artifact2 }) {
4075
2142
  {
4076
2143
  className: "cv-html-tpl",
4077
2144
  value: "",
4078
- title: t("slideFont"),
2145
+ title: "Slide font",
4079
2146
  onChange: (e) => {
4080
2147
  const f = SLIDE_FONTS[e.target.value];
4081
2148
  if (f) setSlideStyle({ fontFamily: f.stack });
4082
2149
  e.currentTarget.value = "";
4083
2150
  },
4084
2151
  children: [
4085
- /* @__PURE__ */ jsx("option", { value: "", children: t("fontMenu") }),
2152
+ /* @__PURE__ */ jsx("option", { value: "", children: "Font\u2026" }),
4086
2153
  Object.entries(SLIDE_FONTS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
4087
2154
  ]
4088
2155
  }
4089
2156
  ),
4090
- /* @__PURE__ */ jsx("button", { className: "cv-html-add", title: t("slideBgImage"), onClick: () => bgFileRef.current?.click(), children: "\u{1F5BC} BG" })
2157
+ /* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "Slide background image", onClick: () => bgFileRef.current?.click(), children: "\u{1F5BC} BG" })
4091
2158
  ] }),
4092
2159
  selected.length >= 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
4093
2160
  /* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" }),
@@ -4117,33 +2184,33 @@ function HtmlRenderer({ artifact: artifact2 }) {
4117
2184
  )
4118
2185
  ] }),
4119
2186
  single && single.tag !== "img" && /* @__PURE__ */ jsxs(Fragment, { children: [
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" })
2187
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align left", onClick: () => command("style_persist", { prop: "textAlign", value: "left" }), children: "\u2B05" }),
2188
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align center", onClick: () => command("style_persist", { prop: "textAlign", value: "center" }), children: "\u2B0C" }),
2189
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align right", onClick: () => command("style_persist", { prop: "textAlign", value: "right" }), children: "\u27A1" }),
2190
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Bold", onClick: () => command("style_persist", { prop: "fontWeight", value: "800" }), children: "B" })
4124
2191
  ] }),
4125
2192
  single && /* @__PURE__ */ jsxs(Fragment, { children: [
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}" })
2193
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Duplicate", onClick: () => command("duplicate"), children: "\u29C9" }),
2194
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Move up", onClick: () => command("move_up"), children: "\u2191" }),
2195
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Move down", onClick: () => command("move_down"), children: "\u2193" }),
2196
+ /* @__PURE__ */ jsx("button", { className: "cv-html-act cv-html-act--del", title: "Delete", onClick: () => command("delete"), children: "\u{1F5D1}" })
4130
2197
  ] })
4131
2198
  ] })
4132
2199
  ] }),
4133
2200
  /* @__PURE__ */ jsx("span", { className: "cv-html-bar__spacer" }),
4134
2201
  /* @__PURE__ */ jsxs("div", { className: "cv-html-seg", role: "group", "aria-label": "View mode", children: [
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") })
2202
+ /* @__PURE__ */ jsx("button", { className: mode === "design" ? "is-on" : "", onClick: () => setMode("design"), children: "Design" }),
2203
+ /* @__PURE__ */ jsx("button", { className: mode === "code" ? "is-on" : "", onClick: () => setMode("code"), children: "Code" })
4137
2204
  ] })
4138
2205
  ] }),
4139
2206
  a11y !== null && /* @__PURE__ */ jsxs("div", { className: "cv-a11y", role: "status", children: [
4140
2207
  /* @__PURE__ */ jsx("button", { className: "cv-a11y__close", onClick: () => setA11y(null), "aria-label": "Dismiss", children: "\xD7" }),
4141
- a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children: t("a11yNone") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2208
+ a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children: "\u267F No accessibility issues found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4142
2209
  /* @__PURE__ */ jsxs("b", { children: [
4143
2210
  "\u267F ",
4144
2211
  a11y.length,
4145
- " ",
4146
- t("a11yIssues")
2212
+ " accessibility issue",
2213
+ a11y.length > 1 ? "s" : ""
4147
2214
  ] }),
4148
2215
  /* @__PURE__ */ jsx("ul", { children: a11y.map((m, i) => /* @__PURE__ */ jsx("li", { children: m }, i)) })
4149
2216
  ] })
@@ -4173,48 +2240,33 @@ function HtmlRenderer({ artifact: artifact2 }) {
4173
2240
  sandbox: "allow-scripts allow-popups allow-modals",
4174
2241
  style: { width: DEVICES.find((d) => d.id === device).width }
4175
2242
  }
4176
- ) }) : /* @__PURE__ */ jsx(CodePane, { html: artifact2.data.html, onCommit: commitCode })
4177
- ] });
4178
- }
4179
- function CodePane({ html, onCommit }) {
4180
- const [draft, setDraft] = useState(html);
4181
- const dirty = useRef(false);
4182
- useEffect(() => {
4183
- if (!dirty.current) setDraft(html);
4184
- }, [html]);
4185
- return /* @__PURE__ */ jsx(
4186
- "textarea",
4187
- {
4188
- className: "cv-html-code",
4189
- value: draft,
4190
- spellCheck: false,
4191
- onChange: (e) => {
4192
- dirty.current = true;
4193
- setDraft(e.target.value);
4194
- },
4195
- onBlur: () => {
4196
- if (!dirty.current) return;
4197
- dirty.current = false;
4198
- onCommit(draft);
2243
+ ) }) : /* @__PURE__ */ jsx(
2244
+ "textarea",
2245
+ {
2246
+ className: "cv-html-code",
2247
+ defaultValue: artifact2.data.html,
2248
+ spellCheck: false,
2249
+ onBlur: (e) => commitCode(e.target.value),
2250
+ "aria-label": "HTML source"
4199
2251
  },
4200
- "aria-label": "HTML source"
4201
- }
4202
- );
2252
+ artifact2.data.html
2253
+ )
2254
+ ] });
4203
2255
  }
4204
2256
 
4205
2257
  // src/components/renderers/index.ts
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 })));
2258
+ var ChartRenderer = lazy(() => import('./ChartRenderer-JGRJ23OB.js').then((m) => ({ default: m.ChartRenderer })));
2259
+ var DocumentRenderer = lazy(() => import('./DocumentRenderer-ZQDQMX7X.js').then((m) => ({ default: m.DocumentRenderer })));
2260
+ var TableRenderer = lazy(() => import('./TableRenderer-3ESF577F.js').then((m) => ({ default: m.TableRenderer })));
2261
+ var SlidesRenderer = lazy(() => import('./SlidesRenderer-4TZNTSEL.js').then((m) => ({ default: m.SlidesRenderer })));
2262
+ var FileRenderer = lazy(() => import('./FileRenderer-3ZHNORZJ.js').then((m) => ({ default: m.FileRenderer })));
4211
2263
  var builtinRenderers = {
4212
2264
  html: HtmlRenderer,
4213
2265
  document: DocumentRenderer,
4214
2266
  chart: ChartRenderer,
4215
2267
  table: TableRenderer,
4216
2268
  slides: SlidesRenderer,
4217
- pdf: PdfRenderer
2269
+ file: FileRenderer
4218
2270
  };
4219
2271
 
4220
2272
  // src/export/download.ts
@@ -4233,232 +2285,6 @@ function slugify(text) {
4233
2285
  return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "artifact";
4234
2286
  }
4235
2287
 
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
-
4462
2288
  // src/export/exporters.ts
4463
2289
  var MIME = {
4464
2290
  md: "text/markdown",
@@ -4471,8 +2297,7 @@ var MIME = {
4471
2297
  var dataExporters = {
4472
2298
  document: [
4473
2299
  { label: "Markdown", extension: "md", mime: MIME.md, build: (a) => a.data.content },
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) }
2300
+ { label: "Word", extension: "docx", mime: MIME.docx, build: (a) => documentToDocx(a.data) }
4476
2301
  ],
4477
2302
  table: [
4478
2303
  { label: "CSV", extension: "csv", mime: MIME.csv, build: (a) => tableToCsv(a.data) },
@@ -4491,7 +2316,7 @@ function toStandaloneHtml(title, renderedHtml) {
4491
2316
  <head>
4492
2317
  <meta charset="utf-8" />
4493
2318
  <meta name="viewport" content="width=device-width, initial-scale=1" />
4494
- <title>${escapeHtml2(title)}</title>
2319
+ <title>${escapeHtml(title)}</title>
4495
2320
  <style>${EXPORT_CSS}</style>
4496
2321
  </head>
4497
2322
  <body>
@@ -4502,43 +2327,38 @@ ${renderedHtml}
4502
2327
  </html>`;
4503
2328
  }
4504
2329
  function tableToCsv(data) {
4505
- if (data.sheet?.length) return fortuneToCsv(data.sheet[0]);
2330
+ const rows = data.sheet?.length ? projectSheetIntoRows(data.columns, data.rows, data.sheet) : data.rows;
4506
2331
  const header = data.columns.map((c) => csvCell(c.label ?? c.key)).join(",");
4507
- const body = data.rows.map((row) => data.columns.map((c) => csvCell(String(row[c.key] ?? ""))).join(",")).join("\n");
2332
+ const body = rows.map((row) => data.columns.map((c) => csvCell(String(row[c.key] ?? ""))).join(",")).join("\n");
4508
2333
  return `${header}
4509
2334
  ${body}`;
4510
2335
  }
4511
- function fortuneToCsv(sheet) {
4512
- let maxR = -1;
4513
- let maxC = -1;
4514
- const grid = /* @__PURE__ */ new Map();
4515
- for (const cell of sheet.celldata ?? []) {
4516
- const v = cell?.v;
4517
- if (v == null) continue;
4518
- if (typeof v === "object" && v.mc && v.v == null && v.m == null) continue;
4519
- const value = typeof v === "object" ? v.v ?? v.m ?? "" : v;
4520
- if (value === "" || value == null) continue;
4521
- grid.set(`${cell.r},${cell.c}`, String(value));
4522
- if (cell.r > maxR) maxR = cell.r;
4523
- if (cell.c > maxC) maxC = cell.c;
4524
- }
4525
- const lines = [];
4526
- for (let r = 0; r <= maxR; r++) {
4527
- const row = [];
4528
- for (let c = 0; c <= maxC; c++) row.push(csvCell(grid.get(`${r},${c}`) ?? ""));
4529
- lines.push(row.join(","));
4530
- }
4531
- return lines.join("\n");
4532
- }
4533
2336
  async function tableToXlsx(data) {
4534
2337
  const { Workbook } = await loadOptional("exceljs", () => import('exceljs'));
4535
2338
  const workbook = new Workbook();
4536
2339
  if (data.sheet?.length) {
4537
- fortuneToWorkbook(workbook, data.sheet);
2340
+ const { computeFormulas } = await import('./formula-27TCEZI5.js');
2341
+ const merged = mergeRowsIntoSheet(
2342
+ data.columns,
2343
+ data.rows,
2344
+ data.sheet,
2345
+ await computeFormulas(data.columns, data.rows)
2346
+ );
2347
+ fortuneToWorkbook(workbook, merged);
4538
2348
  } else {
4539
2349
  const sheet = workbook.addWorksheet("Sheet1");
4540
2350
  sheet.addRow(data.columns.map((c) => c.label ?? c.key));
4541
- for (const row of data.rows) sheet.addRow(data.columns.map((c) => row[c.key] ?? ""));
2351
+ const { computeFormulas } = await import('./formula-27TCEZI5.js');
2352
+ const results = await computeFormulas(data.columns, data.rows);
2353
+ data.rows.forEach((row, dataIdx) => {
2354
+ sheet.addRow(
2355
+ data.columns.map((c, colIdx) => {
2356
+ const v = row[c.key] ?? "";
2357
+ if (typeof v !== "string" || !v.startsWith("=")) return v;
2358
+ return { formula: v.slice(1), result: results.get(`${dataIdx + 1},${colIdx}`) };
2359
+ })
2360
+ );
2361
+ });
4542
2362
  sheet.getRow(1).font = { bold: true };
4543
2363
  }
4544
2364
  return workbook.xlsx.writeBuffer();
@@ -4551,7 +2371,8 @@ function fortuneToWorkbook(workbook, sheets) {
4551
2371
  const v = cell.v;
4552
2372
  const value = v && typeof v === "object" ? v.v ?? v.m ?? null : v;
4553
2373
  const xc = ws.getCell(cell.r + 1, cell.c + 1);
4554
- xc.value = value;
2374
+ const formula = v && typeof v === "object" && typeof v.f === "string" ? v.f : null;
2375
+ xc.value = formula ? { formula: formula.replace(/^=/, ""), result: value ?? void 0 } : value;
4555
2376
  if (v && typeof v === "object") {
4556
2377
  if (v.bl) xc.font = { ...xc.font, bold: true };
4557
2378
  if (v.it) xc.font = { ...xc.font, italic: true };
@@ -4593,10 +2414,9 @@ async function documentToDocx(data) {
4593
2414
  async function slidesToPptx(data, _title) {
4594
2415
  const PptxGenJS = (await loadOptional("pptxgenjs", () => import('pptxgenjs'))).default;
4595
2416
  const pptx = new PptxGenJS();
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";
2417
+ const { widthIn: W, heightIn: H } = deckPage(data);
2418
+ pptx.defineLayout({ name: "CV_PAGE", width: W, height: H });
2419
+ pptx.layout = "CV_PAGE";
4600
2420
  for (const slide of data.slides) {
4601
2421
  const s = pptx.addSlide();
4602
2422
  if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
@@ -4605,28 +2425,18 @@ async function slidesToPptx(data, _title) {
4605
2425
  const inset = (v) => pad + v / 100 * (1 - 2 * pad);
4606
2426
  for (const el of resolveElements(slide)) {
4607
2427
  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 } : {};
4609
2428
  if (el.type === "text") {
4610
2429
  const color = el.color ? el.color.replace("#", "") : tc;
4611
- s.addText(el.text ?? "", { ...box, ...spin, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
2430
+ s.addText(el.text ?? "", { ...box, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
4612
2431
  } else if (el.type === "shape") {
4613
2432
  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 } };
4615
2433
  if (el.shape === "line") {
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 });
2434
+ s.addShape(pptx.ShapeType.line, { ...box, line: { color: fill, width: 2 } });
4619
2435
  } else {
4620
- s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box, ...spin, ...paint });
2436
+ s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box, fill: { color: fill } });
4621
2437
  }
4622
2438
  } else if (el.src) {
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
- });
2439
+ s.addImage({ data: el.src, ...box, sizing: { type: "contain", w: box.w, h: box.h } });
4630
2440
  }
4631
2441
  }
4632
2442
  if (slide.notes) s.addNotes(slide.notes);
@@ -4635,38 +2445,36 @@ async function slidesToPptx(data, _title) {
4635
2445
  }
4636
2446
  function slidesToPrintHtml(data, title) {
4637
2447
  const slides2 = data.slides.length ? data.slides : [{ title: "Empty deck" }];
2448
+ const page = deckPage(data);
2449
+ const pw = Math.round(page.widthIn * 128);
2450
+ const ph = Math.round(page.heightIn * 128);
4638
2451
  const pages = slides2.map((slide) => {
4639
2452
  const bg = slide.background ?? "#ffffff";
4640
2453
  const fg = slide.textColor ?? "#1f2328";
4641
2454
  const els = resolveElements(slide).map((el) => {
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}`;
2455
+ const box = `left:${el.x}%;top:${el.y}%;width:${el.w}%;height:${el.h}%`;
4644
2456
  if (el.type === "text") {
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>`;
2457
+ 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`;
2458
+ return `<div class="el" style="${style}">${escapeXml(el.text ?? "")}</div>`;
4647
2459
  }
4648
2460
  if (el.type === "shape") {
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>`;
2461
+ const fill = escapeAttr(el.fill ?? fg);
2462
+ const radius = el.shape === "ellipse" ? "50%" : el.shape === "line" ? "2px" : "8px";
2463
+ return `<div class="el" style="${box};background:${fill};border-radius:${radius}"></div>`;
4652
2464
  }
4653
2465
  const src = safeSrc(el.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)}"/>` : "";
2466
+ return src ? `<img class="el" style="${box}" src="${escapeAttr(src)}"/>` : "";
4656
2467
  }).join("");
4657
2468
  const pad = slide.padding ?? 0;
4658
2469
  const inner = pad ? `<div style="position:absolute;inset:${pad}%">${els}</div>` : els;
4659
2470
  return `<section class="slide" style="background:${escapeAttr(bg)}">${inner}</section>`;
4660
2471
  }).join("");
4661
- return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml2(title)}</title><style>
4662
- @page { size: 1280px 720px; margin: 0; }
2472
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml(title)}</title><style>
2473
+ @page { size: ${pw}px ${ph}px; margin: 0; }
4663
2474
  * { margin: 0; box-sizing: border-box; }
4664
2475
  body { font-family: Inter, Arial, sans-serif; }
4665
- .slide { position: relative; width: 1280px; height: 720px; overflow: hidden; page-break-after: always; }
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; }
2476
+ .slide { position: relative; width: ${pw}px; height: ${ph}px; overflow: hidden; page-break-after: always; }
2477
+ .el { position: absolute; overflow: hidden; line-height: 1.25; }
4670
2478
  img.el { object-fit: contain; }
4671
2479
  </style></head><body>${pages}</body></html>`;
4672
2480
  }
@@ -4677,7 +2485,7 @@ function htmlSlideToPrintHtml(html, ratio) {
4677
2485
  const i = html.toLowerCase().lastIndexOf("</head>");
4678
2486
  return i === -1 ? style + html : html.slice(0, i) + style + html.slice(i);
4679
2487
  }
4680
- function escapeXml2(value) {
2488
+ function escapeXml(value) {
4681
2489
  return String(value ?? "").replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
4682
2490
  }
4683
2491
  function escapeAttr(value) {
@@ -4690,7 +2498,7 @@ function safeSrc(src) {
4690
2498
  function csvCell(value) {
4691
2499
  return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
4692
2500
  }
4693
- function escapeHtml2(text) {
2501
+ function escapeHtml(text) {
4694
2502
  return text.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
4695
2503
  }
4696
2504
  var EXPORT_CSS = `
@@ -4734,63 +2542,62 @@ function printToPdf(html) {
4734
2542
  document.body.appendChild(iframe);
4735
2543
  }
4736
2544
  var PDF_TYPES = /* @__PURE__ */ new Set(["html", "document", "chart", "slides"]);
4737
- var escapeHtml3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4738
- var escapeAttr2 = (s) => escapeHtml3(s).replace(/"/g, "&quot;");
4739
2545
  function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
4740
- const t = useT();
4741
2546
  const [open, setOpen] = useState(false);
4742
2547
  const stem = slugify(artifact2.title);
4743
2548
  const dataOptions = dataExporters[artifact2.type] ?? [];
4744
- const exportHtml = () => {
2549
+ const assetBaseUrl = useCanvasStore((s) => s.assetBaseUrl);
2550
+ const prepare = () => inlineArtifactAssets(artifact2, assetBaseUrl);
2551
+ const prepareHtml = (html) => assetBaseUrl ? inlineHtmlAssets(html, assetBaseUrl) : Promise.resolve(html);
2552
+ const exportHtml = async () => {
4745
2553
  if (artifact2.type === "html") {
4746
2554
  const ratio = artifact2.meta?.ratio;
4747
- const html = artifact2.data.html;
2555
+ const html = (await prepare()).data.html;
4748
2556
  downloadBlob(`${stem}.html`, "text/html", ratio ? htmlSlideToPrintHtml(html, ratio) : html);
4749
2557
  } else {
4750
2558
  const html = getRenderedHtml();
4751
2559
  if (html == null) return;
4752
- downloadBlob(`${stem}.html`, "text/html", toStandaloneHtml(artifact2.title, html));
2560
+ downloadBlob(`${stem}.html`, "text/html", toStandaloneHtml(artifact2.title, await prepareHtml(html)));
4753
2561
  }
4754
2562
  setOpen(false);
4755
2563
  };
4756
2564
  const exportData = async (option) => {
4757
- const content = await option.build(artifact2);
2565
+ const content = await option.build(await prepare());
4758
2566
  downloadBlob(`${stem}.${option.extension}`, option.mime, content);
4759
2567
  setOpen(false);
4760
2568
  };
4761
- const exportPdf = () => {
2569
+ const exportPdf = async () => {
4762
2570
  if (artifact2.type === "slides") {
4763
- printToPdf(slidesToPrintHtml(artifact2.data, artifact2.title));
2571
+ printToPdf(slidesToPrintHtml((await prepare()).data, artifact2.title));
4764
2572
  } else if (artifact2.type === "html") {
4765
2573
  const ratio = artifact2.meta?.ratio;
4766
- const html = artifact2.data.html;
2574
+ const html = (await prepare()).data.html;
4767
2575
  printToPdf(ratio ? htmlSlideToPrintHtml(html, ratio) : html);
4768
2576
  } else {
4769
2577
  const html = getRenderedHtml();
4770
2578
  if (html == null) return;
4771
- printToPdf(toStandaloneHtml(artifact2.title, html));
2579
+ printToPdf(toStandaloneHtml(artifact2.title, await prepareHtml(html)));
4772
2580
  }
4773
2581
  setOpen(false);
4774
2582
  };
4775
- const openInTab = () => {
4776
- const html = artifact2.type === "html" ? artifact2.data.html : artifact2.type === "slides" ? slidesToPrintHtml(artifact2.data, artifact2.title) : (() => {
2583
+ const openInTab = async () => {
2584
+ const html = artifact2.type === "html" ? (await prepare()).data.html : artifact2.type === "slides" ? slidesToPrintHtml((await prepare()).data, artifact2.title) : await (async () => {
4777
2585
  const h = getRenderedHtml();
4778
- return h == null ? null : toStandaloneHtml(artifact2.title, h);
2586
+ return h == null ? null : toStandaloneHtml(artifact2.title, await prepareHtml(h));
4779
2587
  })();
4780
2588
  if (html == null) return;
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>`;
4782
- const url = URL.createObjectURL(new Blob([wrapper], { type: "text/html" }));
2589
+ const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
4783
2590
  window.open(url, "_blank", "noopener");
4784
2591
  setTimeout(() => URL.revokeObjectURL(url), 1e4);
4785
2592
  setOpen(false);
4786
2593
  };
4787
2594
  const [copied, setCopied] = useState(false);
4788
2595
  const copyHtml = async () => {
4789
- const html = artifact2.type === "html" ? artifact2.data.html : getRenderedHtml();
2596
+ const html = artifact2.type === "html" ? (await prepare()).data.html : getRenderedHtml();
4790
2597
  if (html == null) return;
4791
2598
  try {
4792
2599
  await navigator.clipboard.writeText(
4793
- artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, html)
2600
+ artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, await prepareHtml(html))
4794
2601
  );
4795
2602
  setCopied(true);
4796
2603
  setTimeout(() => setCopied(false), 1400);
@@ -4805,14 +2612,14 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
4805
2612
  onClick: () => setOpen((o) => !o),
4806
2613
  "aria-haspopup": "menu",
4807
2614
  "aria-expanded": open,
4808
- children: t("export")
2615
+ children: "Export \u25BE"
4809
2616
  }
4810
2617
  ),
4811
2618
  open && /* @__PURE__ */ jsxs(Fragment, { children: [
4812
2619
  /* @__PURE__ */ jsx("div", { className: "cv-export__scrim", onClick: () => setOpen(false) }),
4813
2620
  /* @__PURE__ */ jsxs("div", { className: "cv-export__menu", role: "menu", children: [
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") }),
2621
+ /* @__PURE__ */ jsx("button", { role: "menuitem", onClick: openInTab, children: "Open in new tab \u2197" }),
2622
+ /* @__PURE__ */ jsx("button", { role: "menuitem", onClick: copyHtml, children: copied ? "Copied \u2713" : "Copy HTML" }),
4816
2623
  /* @__PURE__ */ jsxs("button", { role: "menuitem", onClick: exportHtml, children: [
4817
2624
  "HTML ",
4818
2625
  /* @__PURE__ */ jsx("span", { className: "cv-export__ext", children: ".html" })
@@ -4901,17 +2708,17 @@ var GRADIENTS = [
4901
2708
  function StylePanel({ selection }) {
4902
2709
  const send = useCanvasStore((s) => s.sendIframeCommand);
4903
2710
  const setSelections = useCanvasStore((s) => s.setSelections);
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));
2711
+ const styles = selection.styles ?? {};
2712
+ const [color, setColor] = useState(toHex2(styles.color));
2713
+ const [background, setBackground] = useState(toHex2(styles.backgroundColor));
2714
+ const [fontSize, setFontSize] = useState(px(styles.fontSize, 16));
2715
+ const [fontWeight, setFontWeight] = useState(String(styles.fontWeight ?? "400"));
2716
+ const [textAlign, setTextAlign] = useState(styles.textAlign ?? "left");
2717
+ const [lineHeight, setLineHeight] = useState(px(styles.lineHeight, 0));
2718
+ const [letterSpacing, setLetterSpacing] = useState(px(styles.letterSpacing, 0));
2719
+ const [padding, setPadding] = useState(px(styles.padding, 0));
2720
+ const [radius, setRadius] = useState(px(styles.borderRadius, 0));
2721
+ const [width, setWidth] = useState(px(styles.width, 0));
4915
2722
  const bgFileRef = useRef(null);
4916
2723
  const dirty = useRef(false);
4917
2724
  const setStyle = (prop, value) => {
@@ -5127,8 +2934,10 @@ function toHex2(value) {
5127
2934
  if (!parts || parts.length < 3) return "#000000";
5128
2935
  return "#" + parts.slice(0, 3).map((n) => Number(n).toString(16).padStart(2, "0")).join("");
5129
2936
  }
5130
- function useCanvasImport() {
2937
+ function useCanvasImport({ onImported } = {}) {
5131
2938
  const api = useCanvasStoreApi();
2939
+ const imported = useRef(onImported);
2940
+ imported.current = onImported;
5132
2941
  const importFiles = useCallback(
5133
2942
  async (files) => {
5134
2943
  let lastId = null;
@@ -5141,6 +2950,7 @@ function useCanvasImport() {
5141
2950
  if (created && created.type === "canvas.create") {
5142
2951
  lastId = created.artifact.id;
5143
2952
  api.getState().setActiveArtifact(lastId);
2953
+ imported.current?.(created.artifact, file);
5144
2954
  }
5145
2955
  } catch (err) {
5146
2956
  console.error("[langchain-canvas] import failed:", file.name, err);
@@ -5153,16 +2963,38 @@ function useCanvasImport() {
5153
2963
  return { importFiles, canImport };
5154
2964
  }
5155
2965
  var ACCEPT = IMPORTABLE_EXTENSIONS.join(",");
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 }) }) });
2966
+ function Canvas({
2967
+ registry = builtinRenderers,
2968
+ emptyState,
2969
+ onEditElement,
2970
+ onUserEdit,
2971
+ onSave,
2972
+ onFilesOpened,
2973
+ onImported,
2974
+ assetBaseUrl
2975
+ }) {
2976
+ return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(
2977
+ CanvasPanel,
2978
+ {
2979
+ emptyState,
2980
+ onEditElement,
2981
+ onUserEdit,
2982
+ onSave,
2983
+ onFilesOpened,
2984
+ onImported,
2985
+ assetBaseUrl
2986
+ }
2987
+ ) });
5158
2988
  }
5159
2989
  function CanvasPanel({
5160
2990
  emptyState,
5161
2991
  onEditElement,
5162
2992
  onUserEdit,
5163
- onSave
2993
+ onSave,
2994
+ onFilesOpened,
2995
+ onImported,
2996
+ assetBaseUrl
5164
2997
  }) {
5165
- const t = useT();
5166
2998
  const debouncedSave = useCanvasSave(onSave);
5167
2999
  const { artifacts, order, activeId } = useCanvasStore((s) => s.canvas);
5168
3000
  const history = useCanvasStore((s) => s.canvas.history);
@@ -5170,8 +3002,16 @@ function CanvasPanel({
5170
3002
  const selections = useCanvasStore((s) => s.selections);
5171
3003
  const setSelections = useCanvasStore((s) => s.setSelections);
5172
3004
  const setOnUserEdit = useCanvasStore((s) => s.setOnUserEdit);
5173
- const { importFiles } = useCanvasImport();
3005
+ const setAssetBaseUrl = useCanvasStore((s) => s.setAssetBaseUrl);
3006
+ const { importFiles } = useCanvasImport({ onImported });
5174
3007
  const [dropping, setDropping] = useState(false);
3008
+ useEffect(() => {
3009
+ setAssetBaseUrl(assetBaseUrl ?? null);
3010
+ }, [assetBaseUrl, setAssetBaseUrl]);
3011
+ const openFiles = (files) => {
3012
+ onFilesOpened?.(Array.from(files));
3013
+ void importFiles(files);
3014
+ };
5175
3015
  useEffect(() => {
5176
3016
  if (!onUserEdit && !debouncedSave) {
5177
3017
  setOnUserEdit(null);
@@ -5205,13 +3045,13 @@ function CanvasPanel({
5205
3045
  onDrop: (e) => {
5206
3046
  e.preventDefault();
5207
3047
  setDropping(false);
5208
- if (e.dataTransfer.files.length) void importFiles(e.dataTransfer.files);
3048
+ if (e.dataTransfer.files.length) openFiles(e.dataTransfer.files);
5209
3049
  }
5210
3050
  };
5211
- const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: t("dropToOpen") }) : null;
3051
+ const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: "Drop to open on the canvas" }) : null;
5212
3052
  if (!active) {
5213
3053
  return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas cv-canvas--empty", ...dropProps, children: [
5214
- emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles: importFiles }),
3054
+ emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles: openFiles, acceptAll: Boolean(onFilesOpened) }),
5215
3055
  dropOverlay
5216
3056
  ] });
5217
3057
  }
@@ -5238,11 +3078,10 @@ function CanvasPanel({
5238
3078
  ] });
5239
3079
  }
5240
3080
  function ArtifactView({ artifact: artifact2, versions: versions2 }) {
5241
- const t = useT();
5242
3081
  const [viewIndex, setViewIndex] = useState(null);
5243
3082
  const bodyRef = useRef(null);
5244
- const shown = viewIndex === null ? artifact2 : versions2[Math.min(viewIndex, versions2.length - 1)] ?? artifact2;
5245
- const viewingHistory = viewIndex !== null && shown !== artifact2;
3083
+ const shown = viewIndex === null ? artifact2 : versions2[viewIndex];
3084
+ const viewingHistory = viewIndex !== null && viewIndex !== versions2.length - 1;
5246
3085
  const Renderer = useRenderer(shown.type);
5247
3086
  const getRenderedHtml = () => {
5248
3087
  const node = bodyRef.current;
@@ -5272,24 +3111,21 @@ function ArtifactView({ artifact: artifact2, versions: versions2 }) {
5272
3111
  ] })
5273
3112
  ] }),
5274
3113
  viewingHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-banner", role: "status", children: [
5275
- "v",
5276
- Math.min((viewIndex ?? 0) + 1, versions2.length),
5277
- " / ",
3114
+ "Viewing v",
3115
+ (viewIndex ?? 0) + 1,
3116
+ " of ",
5278
3117
  versions2.length,
5279
- " \xB7 ",
5280
- t("historyNote"),
3118
+ " \u2014 read-only.",
5281
3119
  " ",
5282
- /* @__PURE__ */ jsx("button", { onClick: () => setViewIndex(null), children: t("backToLatest") })
3120
+ /* @__PURE__ */ jsx("button", { onClick: () => setViewIndex(null), children: "Back to latest" })
5283
3121
  ] }),
5284
3122
  /* @__PURE__ */ jsx(
5285
3123
  "div",
5286
3124
  {
5287
- className: `cv-body${shown.type === "table" || shown.type === "pdf" ? " cv-body--flush" : ""}${viewingHistory ? " cv-body--history" : ""}`,
3125
+ className: `cv-body${shown.type === "table" ? " cv-body--flush" : ""}${viewingHistory ? " cv-body--history" : ""}`,
5288
3126
  ref: bodyRef,
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",
3127
+ children: 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: [
3128
+ "No renderer registered for type \u201C",
5293
3129
  shown.type,
5294
3130
  "\u201D."
5295
3131
  ] })
@@ -5298,7 +3134,6 @@ function ArtifactView({ artifact: artifact2, versions: versions2 }) {
5298
3134
  ] });
5299
3135
  }
5300
3136
  function UndoRedo() {
5301
- const t = useT();
5302
3137
  const undo = useCanvasStore((s) => s.undo);
5303
3138
  const redo = useCanvasStore((s) => s.redo);
5304
3139
  const canUndo = useCanvasStore((s) => s.undoStack.length > 0);
@@ -5318,8 +3153,8 @@ function UndoRedo() {
5318
3153
  return () => window.removeEventListener("keydown", onKey);
5319
3154
  }, [undo, redo]);
5320
3155
  return /* @__PURE__ */ jsxs("div", { className: "cv-undo", role: "group", "aria-label": "Undo and redo", children: [
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" })
3156
+ /* @__PURE__ */ jsx("button", { onClick: undo, disabled: !canUndo, title: "Undo (\u2318Z)", "aria-label": "Undo", children: "\u21B6" }),
3157
+ /* @__PURE__ */ jsx("button", { onClick: redo, disabled: !canRedo, title: "Redo (\u2318\u21E7Z)", "aria-label": "Redo", children: "\u21B7" })
5323
3158
  ] });
5324
3159
  }
5325
3160
  function VersionHistory({
@@ -5327,15 +3162,14 @@ function VersionHistory({
5327
3162
  index,
5328
3163
  onSelect
5329
3164
  }) {
5330
- const t = useT();
5331
3165
  const [open, setOpen] = useState(false);
5332
3166
  const total = versions2.length;
5333
3167
  const pick = (i) => {
5334
3168
  setOpen(false);
5335
3169
  onSelect(i);
5336
3170
  };
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" }),
3171
+ return /* @__PURE__ */ jsxs("div", { className: "cv-versions", role: "group", "aria-label": "Version history", children: [
3172
+ /* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => pick(index - 1), "aria-label": "Previous version", children: "\u2039" }),
5339
3173
  /* @__PURE__ */ jsxs(
5340
3174
  "button",
5341
3175
  {
@@ -5357,7 +3191,7 @@ function VersionHistory({
5357
3191
  className: "cv-versions__nav",
5358
3192
  disabled: index === total - 1,
5359
3193
  onClick: () => pick(index + 1),
5360
- "aria-label": t("nextVersion"),
3194
+ "aria-label": "Next version",
5361
3195
  children: "\u203A"
5362
3196
  }
5363
3197
  ),
@@ -5383,7 +3217,10 @@ function StatusBadge({ status }) {
5383
3217
  const label = status === "streaming" ? "Writing\u2026" : status === "error" ? "Error" : "Ready";
5384
3218
  return /* @__PURE__ */ jsx("span", { className: `cv-badge cv-badge--${status}`, children: label });
5385
3219
  }
5386
- function EmptyState({ onOpenFiles }) {
3220
+ function EmptyState({
3221
+ onOpenFiles,
3222
+ acceptAll = false
3223
+ }) {
5387
3224
  const inputRef = useRef(null);
5388
3225
  return /* @__PURE__ */ jsxs("div", { className: "cv-empty", children: [
5389
3226
  /* @__PURE__ */ jsx("p", { className: "cv-empty__title", children: "Nothing on the canvas yet" }),
@@ -5395,7 +3232,7 @@ function EmptyState({ onOpenFiles }) {
5395
3232
  {
5396
3233
  ref: inputRef,
5397
3234
  type: "file",
5398
- accept: ACCEPT,
3235
+ accept: acceptAll ? void 0 : ACCEPT,
5399
3236
  multiple: true,
5400
3237
  hidden: true,
5401
3238
  onChange: (e) => {
@@ -5404,17 +3241,17 @@ function EmptyState({ onOpenFiles }) {
5404
3241
  }
5405
3242
  }
5406
3243
  ),
5407
- /* @__PURE__ */ jsx("p", { className: "cv-empty__formats", children: "CSV \xB7 Excel \xB7 Markdown \xB7 HTML \xB7 JSON" })
3244
+ /* @__PURE__ */ jsx("p", { className: "cv-empty__formats", children: acceptAll ? "Any file \u2014 tables and pages open here, the rest goes to the agent" : "CSV \xB7 Excel \xB7 Markdown \xB7 HTML \xB7 JSON" })
5408
3245
  ] })
5409
3246
  ] });
5410
3247
  }
5411
3248
  var TYPE_META = {
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" }
3249
+ html: { icon: "\u{1F310}", label: "Web page" },
3250
+ document: { icon: "\u{1F4C4}", label: "Word document" },
3251
+ chart: { icon: "\u{1F4CA}", label: "Chart" },
3252
+ table: { icon: "\u{1F522}", label: "Excel sheet" },
3253
+ slides: { icon: "\u{1F4FD}\uFE0F", label: "PowerPoint deck" },
3254
+ file: { icon: "\u{1F4CE}", label: "File" }
5418
3255
  };
5419
3256
  var KIND_META = {
5420
3257
  web: TYPE_META.html,
@@ -5425,16 +3262,14 @@ var KIND_META = {
5425
3262
  table: TYPE_META.table,
5426
3263
  sheet: TYPE_META.table,
5427
3264
  slide: TYPE_META.slides,
5428
- slides: TYPE_META.slides,
5429
- pdf: TYPE_META.pdf
3265
+ slides: TYPE_META.slides
5430
3266
  };
5431
3267
  function resolveCardMeta(artifact2) {
5432
3268
  const kind = typeof artifact2.meta?.kind === "string" ? artifact2.meta.kind : void 0;
5433
3269
  const byKind = kind ? KIND_META[kind] : void 0;
5434
- return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}" };
3270
+ return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}", label: artifact2.type };
5435
3271
  }
5436
3272
  function ArtifactCard({ artifactId }) {
5437
- const t = useT();
5438
3273
  const artifact2 = useCanvasStore((s) => s.canvas.artifacts[artifactId]);
5439
3274
  const setActive = useCanvasStore((s) => s.setActiveArtifact);
5440
3275
  if (!artifact2) return null;
@@ -5444,12 +3279,12 @@ function ArtifactCard({ artifactId }) {
5444
3279
  /* @__PURE__ */ jsxs("span", { className: "cv-card__meta", children: [
5445
3280
  /* @__PURE__ */ jsx("b", { children: artifact2.title }),
5446
3281
  /* @__PURE__ */ jsxs("span", { children: [
5447
- meta.label ? t(meta.label) : artifact2.type,
3282
+ meta.label,
5448
3283
  " \xB7 ",
5449
- artifact2.status === "streaming" ? t("writing") : t("open")
3284
+ artifact2.status === "streaming" ? "writing\u2026" : "open \u2192"
5450
3285
  ] })
5451
3286
  ] })
5452
3287
  ] });
5453
3288
  }
5454
3289
 
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 };
3290
+ export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, FileRenderer, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, mockTransport, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, sseTransport, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasSave, useCanvasStream, useRenderer, withInspector };