@braincrew-lab/langchain-canvas 0.1.14 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +23 -11
- package/README.md +23 -11
- package/dist/{ChartRenderer-FX6ZN4NC.js → ChartRenderer-ABRQ5YFN.js} +67 -39
- package/dist/{DocumentRenderer-KJ6FTGKH.js → DocumentRenderer-YTEMC3Z4.js} +17 -12
- package/dist/PdfRenderer-GDA67MES.js +98 -0
- package/dist/SlidesRenderer-JNPXNTNJ.js +964 -0
- package/dist/TableRenderer-BJQE2HMO.js +636 -0
- package/dist/{chunk-TERWLUW3.js → chunk-FSFOURG5.js} +1 -1
- package/dist/chunk-QMOJEGRH.js +207 -0
- package/dist/{chunk-KKLWKR5G.js → chunk-ZLXAWRUP.js} +52 -20
- package/dist/index.d.ts +779 -6
- package/dist/index.js +2623 -183
- package/dist/styles.css +477 -4
- package/package.json +26 -2
- package/dist/SlidesRenderer-SK5VQDIL.js +0 -492
- package/dist/TableRenderer-Y4JFRSLU.js +0 -301
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { loadOptional } from './chunk-YZZSJJMQ.js';
|
|
3
3
|
import { resolveElements } from './chunk-6L3AL6W4.js';
|
|
4
|
-
export { useArtifactPatch } from './chunk-
|
|
5
|
-
import { useCanvasStoreApi, useCanvasStore } from './chunk-
|
|
6
|
-
export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-
|
|
7
|
-
import {
|
|
4
|
+
export { useArtifactPatch } from './chunk-FSFOURG5.js';
|
|
5
|
+
import { useCanvasStoreApi, useCanvasStore } from './chunk-ZLXAWRUP.js';
|
|
6
|
+
export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-ZLXAWRUP.js';
|
|
7
|
+
import { useT, CanvasLocaleProvider } from './chunk-QMOJEGRH.js';
|
|
8
|
+
export { CanvasLocaleProvider, useLocale, useT } from './chunk-QMOJEGRH.js';
|
|
9
|
+
import { createContext, lazy, useRef, useCallback, useEffect, useMemo, useContext, useState, Suspense, Component } from 'react';
|
|
8
10
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
11
|
|
|
10
12
|
// src/client/sse-client.ts
|
|
@@ -157,16 +159,47 @@ var INSPECTOR_SCRIPT = `
|
|
|
157
159
|
for (var j = 0; j < marked.length; j++) scrub(marked[j]);
|
|
158
160
|
parent.postMessage({ source: MARK, type: "doc_edit", self: !!selfApplied, html: "<!doctype html>\\n" + clone.outerHTML }, "*");
|
|
159
161
|
}
|
|
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
|
+
}
|
|
160
173
|
function newBlock(tag) {
|
|
161
174
|
var el = document.createElement(tag);
|
|
162
175
|
if (tag === "img") {
|
|
163
176
|
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";
|
|
164
177
|
el.alt = "image"; el.style.maxWidth = "100%";
|
|
165
178
|
}
|
|
166
|
-
else if (tag === "button")
|
|
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
|
+
}
|
|
167
191
|
else if (tag === "hr") { /* no content */ }
|
|
168
192
|
else if (tag === "section") { var p = document.createElement("p"); p.textContent = "New section"; el.appendChild(p); }
|
|
169
|
-
else
|
|
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
|
+
}
|
|
170
203
|
return el;
|
|
171
204
|
}
|
|
172
205
|
// Floating rich-text toolbar shown while a text element is being edited.
|
|
@@ -263,16 +296,31 @@ var INSPECTOR_SCRIPT = `
|
|
|
263
296
|
// into absolute positioning inside its own parent, and its final spot + size are
|
|
264
297
|
// stored as percentages of that parent \u2014 so it stays put proportionally across
|
|
265
298
|
// responsive breakpoints, instead of a fixed pixel offset that drifts off.
|
|
266
|
-
var dragEls = null, dragStart = null, dragBases = null,
|
|
299
|
+
var dragEls = null, dragStart = null, dragBases = null, parentMutated = false;
|
|
267
300
|
|
|
268
301
|
function ensurePositioned(parent) {
|
|
269
|
-
if (!parent || parent === document.
|
|
270
|
-
|
|
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);
|
|
271
318
|
}
|
|
272
319
|
// Pull each element out into absolute positioning at its current spot (no visual
|
|
273
320
|
// jump), so it can then be moved freely.
|
|
274
321
|
function beginFreeDrag(els) {
|
|
275
322
|
dragBases = [];
|
|
323
|
+
parentMutated = false;
|
|
276
324
|
for (var i = 0; i < els.length; i++) {
|
|
277
325
|
var el = els[i], parent = el.parentElement || document.body;
|
|
278
326
|
ensurePositioned(parent);
|
|
@@ -295,13 +343,16 @@ var INSPECTOR_SCRIPT = `
|
|
|
295
343
|
}
|
|
296
344
|
// Commit the current position (in px, as set live by moveFree) as % of the
|
|
297
345
|
// parent \u2014 position and width \u2014 so it scales with the layout. Falls back to px
|
|
298
|
-
// only if the parent has collapsed to zero on that axis.
|
|
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.
|
|
299
350
|
function commitFree() {
|
|
300
351
|
for (var i = 0; i < dragBases.length; i++) {
|
|
301
352
|
var b = dragBases[i], pr = b.parent.getBoundingClientRect();
|
|
302
353
|
var curLeft = parseFloat(b.el.style.left) || 0, curTop = parseFloat(b.el.style.top) || 0;
|
|
303
354
|
b.el.style.left = pr.width ? ((curLeft / pr.width) * 100).toFixed(3) + "%" : curLeft + "px";
|
|
304
|
-
b.el.style.top =
|
|
355
|
+
b.el.style.top = Math.round(curTop) + "px";
|
|
305
356
|
if (pr.width) b.el.style.width = ((b.w / pr.width) * 100).toFixed(3) + "%";
|
|
306
357
|
}
|
|
307
358
|
}
|
|
@@ -458,8 +509,8 @@ var INSPECTOR_SCRIPT = `
|
|
|
458
509
|
positionResize();
|
|
459
510
|
// The new position is already shown in the iframe with every cid intact,
|
|
460
511
|
// so persist without a reload (no flicker): one element \u2192 node_edit,
|
|
461
|
-
// several \u2192 a self-applied doc_edit.
|
|
462
|
-
|
|
512
|
+
// several (or a repositioned parent) \u2192 a self-applied doc_edit.
|
|
513
|
+
commitDrag(els);
|
|
463
514
|
}
|
|
464
515
|
dragBases = null;
|
|
465
516
|
return;
|
|
@@ -510,7 +561,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
510
561
|
// Pointer left the frame mid-drag: commit the move at its last position so
|
|
511
562
|
// it isn't lost (the element is already placed absolutely in the iframe).
|
|
512
563
|
var els = dragEls; dragEls = null;
|
|
513
|
-
if (moved && dragBases) { commitFree();
|
|
564
|
+
if (moved && dragBases) { commitFree(); commitDrag(els); }
|
|
514
565
|
dragBases = null;
|
|
515
566
|
}
|
|
516
567
|
if (dragging) {
|
|
@@ -584,6 +635,12 @@ var INSPECTOR_SCRIPT = `
|
|
|
584
635
|
if (d.type === "set_src") { var ei = byCid(d.cid); if (ei) { ei.setAttribute("src", d.value); emitEdit(ei); } return; }
|
|
585
636
|
if (d.type === "commit") { var el2 = byCid(d.cid); if (el2) emitEdit(el2); return; }
|
|
586
637
|
|
|
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
|
+
}
|
|
587
644
|
// Structural edits \u2014 mutate the tree, then persist the whole document.
|
|
588
645
|
if (d.type === "insert") {
|
|
589
646
|
var block = newBlock(d.block || "p");
|
|
@@ -591,7 +648,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
591
648
|
if (anchor && anchor.parentNode && anchor.parentNode !== document.documentElement) {
|
|
592
649
|
anchor.parentNode.insertBefore(block, anchor.nextSibling);
|
|
593
650
|
} else {
|
|
594
|
-
|
|
651
|
+
insertRoot().appendChild(block);
|
|
595
652
|
}
|
|
596
653
|
emitDoc();
|
|
597
654
|
return;
|
|
@@ -599,7 +656,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
599
656
|
if (d.type === "insert_html") {
|
|
600
657
|
// A built-in section template (trusted markup from the toolbar).
|
|
601
658
|
var anc = d.cid ? byCid(d.cid) : null;
|
|
602
|
-
var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode :
|
|
659
|
+
var container = (anc && anc.parentNode && anc.parentNode !== document.documentElement) ? anc.parentNode : insertRoot();
|
|
603
660
|
var ref = (anc && anc.parentNode === container) ? anc.nextSibling : null;
|
|
604
661
|
var frag = document.createElement("div");
|
|
605
662
|
frag.innerHTML = d.html || "";
|
|
@@ -614,7 +671,16 @@ var INSPECTOR_SCRIPT = `
|
|
|
614
671
|
var cids = d.cids || [];
|
|
615
672
|
for (var g = 0; g < cids.length; g++) { var m = byCid(cids[g]); if (m) members.push(m); }
|
|
616
673
|
if (members.length < 2) return;
|
|
617
|
-
|
|
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);
|
|
618
684
|
for (var w = 0; w < members.length; w++) members[w].setAttribute("data-group-id", gid);
|
|
619
685
|
clearSelected();
|
|
620
686
|
emitDoc();
|
|
@@ -777,7 +843,7 @@ function useCanvasReplay() {
|
|
|
777
843
|
api.getState().applyEvent(event);
|
|
778
844
|
}
|
|
779
845
|
} finally {
|
|
780
|
-
api.getState().setStreaming(false);
|
|
846
|
+
if (abortRef.current === controller) api.getState().setStreaming(false);
|
|
781
847
|
}
|
|
782
848
|
},
|
|
783
849
|
[api]
|
|
@@ -786,6 +852,39 @@ function useCanvasReplay() {
|
|
|
786
852
|
const reset = useCallback(() => api.getState().reset(), [api]);
|
|
787
853
|
return { play, stop, reset, canvas, isPlaying };
|
|
788
854
|
}
|
|
855
|
+
var DEFAULT_DEBOUNCE_MS = 800;
|
|
856
|
+
function useCanvasSave(onSave, debounceMs = DEFAULT_DEBOUNCE_MS) {
|
|
857
|
+
const timers = useRef(/* @__PURE__ */ new Map());
|
|
858
|
+
const latest = useRef(/* @__PURE__ */ new Map());
|
|
859
|
+
const handler = useRef(onSave);
|
|
860
|
+
handler.current = onSave;
|
|
861
|
+
const enabled = Boolean(onSave);
|
|
862
|
+
useEffect(() => {
|
|
863
|
+
const pending = timers.current;
|
|
864
|
+
return () => pending.forEach((t) => clearTimeout(t));
|
|
865
|
+
}, []);
|
|
866
|
+
return useMemo(() => {
|
|
867
|
+
if (!enabled) return null;
|
|
868
|
+
return (artifact2) => {
|
|
869
|
+
latest.current.set(artifact2.id, artifact2);
|
|
870
|
+
const existing = timers.current.get(artifact2.id);
|
|
871
|
+
if (existing) clearTimeout(existing);
|
|
872
|
+
timers.current.set(
|
|
873
|
+
artifact2.id,
|
|
874
|
+
setTimeout(() => {
|
|
875
|
+
timers.current.delete(artifact2.id);
|
|
876
|
+
const current = latest.current.get(artifact2.id);
|
|
877
|
+
if (!current || !handler.current) return;
|
|
878
|
+
void handler.current({
|
|
879
|
+
artifactId: current.id,
|
|
880
|
+
artifact: current,
|
|
881
|
+
baseRevision: typeof current.meta?.revision === "string" ? current.meta.revision : null
|
|
882
|
+
});
|
|
883
|
+
}, debounceMs)
|
|
884
|
+
);
|
|
885
|
+
};
|
|
886
|
+
}, [enabled, debounceMs]);
|
|
887
|
+
}
|
|
789
888
|
|
|
790
889
|
// src/fixtures/scenarios.ts
|
|
791
890
|
var PRICING_HTML = `<!doctype html>
|
|
@@ -957,7 +1056,107 @@ var slides = {
|
|
|
957
1056
|
{ type: "done" }
|
|
958
1057
|
]
|
|
959
1058
|
};
|
|
960
|
-
var
|
|
1059
|
+
var VERSIONED_HTML = `<!doctype html><html><body style="font-family:sans-serif;padding:40px">
|
|
1060
|
+
<h1>Coffee history</h1><p>From the Ethiopian highlands to the espresso bar.</p>
|
|
1061
|
+
</body></html>`;
|
|
1062
|
+
var versions = {
|
|
1063
|
+
id: "versions",
|
|
1064
|
+
title: "Version history",
|
|
1065
|
+
description: "An agent builds a page, a user edit and an agent edit each land as described commits \u2014 open the version rail to browse and restore-view snapshots.",
|
|
1066
|
+
events: [
|
|
1067
|
+
{ type: "message.delta", messageId: "m1", text: "Built the page \u2014 every change now lands in the version history." },
|
|
1068
|
+
{ type: "message.end", messageId: "m1" },
|
|
1069
|
+
{
|
|
1070
|
+
type: "canvas.create",
|
|
1071
|
+
artifact: { id: "page", type: "html", title: "Coffee history", version: 1, status: "streaming", data: { html: VERSIONED_HTML } }
|
|
1072
|
+
},
|
|
1073
|
+
{ type: "canvas.status", id: "page", status: "complete" },
|
|
1074
|
+
{ type: "canvas.commit", id: "page", description: "Create page", revision: "v1" },
|
|
1075
|
+
// A human tweaks the headline by hand, then saves — one described commit.
|
|
1076
|
+
{ type: "canvas.patch", id: "page", patch: { html: VERSIONED_HTML.replace("Coffee history", "A short history of coffee") } },
|
|
1077
|
+
{ type: "canvas.commit", id: "page", description: "Manual edit: 1 change", revision: "v2" },
|
|
1078
|
+
// The agent applies a targeted follow-up edit on the current state.
|
|
1079
|
+
{ type: "canvas.patch", id: "page", patch: { html: VERSIONED_HTML.replace("Coffee history", "A short history of coffee").replace("espresso bar", "third-wave caf\xE9") } },
|
|
1080
|
+
{ type: "canvas.commit", id: "page", description: "Update closing phrase", revision: "v3" },
|
|
1081
|
+
{ type: "done" }
|
|
1082
|
+
]
|
|
1083
|
+
};
|
|
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];
|
|
961
1160
|
var RegistryContext = createContext({});
|
|
962
1161
|
function CanvasRegistryProvider({ registry, children }) {
|
|
963
1162
|
return /* @__PURE__ */ jsx(RegistryContext.Provider, { value: registry, children });
|
|
@@ -1103,40 +1302,202 @@ function display(value) {
|
|
|
1103
1302
|
if (value == null) return "";
|
|
1104
1303
|
if (typeof value === "object") {
|
|
1105
1304
|
if (value.result != null) return display(value.result);
|
|
1305
|
+
if (Array.isArray(value.richText)) return value.richText.map((r) => r?.text ?? "").join("");
|
|
1106
1306
|
if (typeof value.text === "string") return value.text;
|
|
1107
1307
|
if (value instanceof Date) return formatDate(value, "yyyy-mm-dd");
|
|
1108
1308
|
return "";
|
|
1109
1309
|
}
|
|
1110
1310
|
return String(value);
|
|
1111
1311
|
}
|
|
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
|
+
}
|
|
1112
1392
|
function formatNumber(value, numFmt) {
|
|
1113
|
-
const
|
|
1114
|
-
if (!
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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})`;
|
|
1121
1413
|
return out;
|
|
1122
1414
|
}
|
|
1415
|
+
var EXCEL_EPOCH_MS = Date.UTC(1899, 11, 30);
|
|
1123
1416
|
function formatDate(d, numFmt) {
|
|
1124
|
-
const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? numFmt : "yyyy-mm-dd";
|
|
1417
|
+
const fmt = numFmt && numFmt !== "General" && /[ymdhs]/i.test(numFmt) ? splitSections(numFmt)[0] : "yyyy-mm-dd";
|
|
1125
1418
|
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;
|
|
1126
1424
|
const map = {
|
|
1127
|
-
yyyy: String(d.
|
|
1128
|
-
yy: p(d.
|
|
1129
|
-
mmmm: d.toLocaleString("en-US", { month: "long" }),
|
|
1130
|
-
mmm: d.toLocaleString("en-US", { month: "short" }),
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
ss: p(d.getSeconds())
|
|
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())
|
|
1138
1435
|
};
|
|
1139
|
-
|
|
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;
|
|
1140
1501
|
}
|
|
1141
1502
|
function cellValue(cell) {
|
|
1142
1503
|
const raw = cell.value;
|
|
@@ -1335,6 +1696,7 @@ function cellVal(v) {
|
|
|
1335
1696
|
if (typeof v === "number" || typeof v === "string") return v;
|
|
1336
1697
|
if (typeof v === "object") {
|
|
1337
1698
|
if (v.result != null) return cellVal(v.result);
|
|
1699
|
+
if (Array.isArray(v.richText)) return v.richText.map((r) => r?.text ?? "").join("");
|
|
1338
1700
|
if (typeof v.text === "string") return v.text;
|
|
1339
1701
|
if (v instanceof Date) return v.toISOString().slice(0, 10);
|
|
1340
1702
|
}
|
|
@@ -1377,8 +1739,1670 @@ function flatten(ws) {
|
|
|
1377
1739
|
return { columns, rows };
|
|
1378
1740
|
}
|
|
1379
1741
|
|
|
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 ? `` : 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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
+
|
|
1380
3390
|
// src/io/importers.ts
|
|
1381
|
-
var IMPORTABLE_EXTENSIONS = [
|
|
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
|
+
];
|
|
1382
3406
|
var extensionOf = (name) => {
|
|
1383
3407
|
const dot = name.lastIndexOf(".");
|
|
1384
3408
|
return dot === -1 ? "" : name.slice(dot).toLowerCase();
|
|
@@ -1419,6 +3443,26 @@ async function importFile(file) {
|
|
|
1419
3443
|
const { sheets, columns, rows } = await xlsxToSheets(await file.arrayBuffer(), () => loadOptional("exceljs", () => import('exceljs')));
|
|
1420
3444
|
return toEvents(artifact(id, "table", title, { columns, rows, sheet: sheets }));
|
|
1421
3445
|
}
|
|
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
|
+
}
|
|
1422
3466
|
default:
|
|
1423
3467
|
throw new Error(`Unsupported file type "${ext || file.name}". Supported: ${IMPORTABLE_EXTENSIONS.join(", ")}`);
|
|
1424
3468
|
}
|
|
@@ -1426,6 +3470,14 @@ async function importFile(file) {
|
|
|
1426
3470
|
function artifact(id, type, title, data) {
|
|
1427
3471
|
return { id, type, title, version: 1, status: "complete", data };
|
|
1428
3472
|
}
|
|
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
|
+
}
|
|
1429
3481
|
function importJson(text, id, title) {
|
|
1430
3482
|
let parsed;
|
|
1431
3483
|
try {
|
|
@@ -1498,6 +3550,8 @@ var DEVICES = [
|
|
|
1498
3550
|
{ id: "tablet", label: "Tablet", width: "768px" },
|
|
1499
3551
|
{ id: "mobile", label: "Mobile", width: "390px" }
|
|
1500
3552
|
];
|
|
3553
|
+
var BLOCK_KEYS = { h2: "heading", p: "text", button: "button", img: "image", hr: "divider" };
|
|
3554
|
+
var DEVICE_KEYS = { desktop: "desktop", tablet: "tablet", mobile: "mobile" };
|
|
1501
3555
|
var BLOCKS = [
|
|
1502
3556
|
{ tag: "h2", label: "Heading" },
|
|
1503
3557
|
{ tag: "p", label: "Text" },
|
|
@@ -1743,8 +3797,8 @@ function checkA11y(html) {
|
|
|
1743
3797
|
if (!doc.querySelector("h1")) issues.push("No <h1> \u2014 every page needs one top-level heading");
|
|
1744
3798
|
return issues;
|
|
1745
3799
|
}
|
|
1746
|
-
var
|
|
1747
|
-
var SCROLL_FIX = "<style>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
|
|
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>";
|
|
1748
3802
|
function withScrollableBody(html) {
|
|
1749
3803
|
const i = html.lastIndexOf("</body>");
|
|
1750
3804
|
return i === -1 ? html + SCROLL_FIX : html.slice(0, i) + SCROLL_FIX + html.slice(i);
|
|
@@ -1752,7 +3806,7 @@ function withScrollableBody(html) {
|
|
|
1752
3806
|
function useSlideFit(ratio, boxRef) {
|
|
1753
3807
|
const [scale, setScale] = useState(1);
|
|
1754
3808
|
const [rw, rh] = (ratio ?? "16:9").split(/[:x/]/).map(Number);
|
|
1755
|
-
const
|
|
3809
|
+
const width = rw && rh ? Math.round(SLIDE_H * rw / rh) : 1280;
|
|
1756
3810
|
useEffect(() => {
|
|
1757
3811
|
if (!ratio) return;
|
|
1758
3812
|
const el = boxRef.current;
|
|
@@ -1760,16 +3814,17 @@ function useSlideFit(ratio, boxRef) {
|
|
|
1760
3814
|
const fit = () => {
|
|
1761
3815
|
const w = el.clientWidth;
|
|
1762
3816
|
if (w <= 40) return;
|
|
1763
|
-
setScale(Math.min(1, (w - 40) /
|
|
3817
|
+
setScale(Math.min(1, (w - 40) / width));
|
|
1764
3818
|
};
|
|
1765
3819
|
fit();
|
|
1766
3820
|
const ro = new ResizeObserver(fit);
|
|
1767
3821
|
ro.observe(el);
|
|
1768
3822
|
return () => ro.disconnect();
|
|
1769
|
-
}, [ratio,
|
|
1770
|
-
return { scale, width
|
|
3823
|
+
}, [ratio, width]);
|
|
3824
|
+
return { scale, width, height: SLIDE_H };
|
|
1771
3825
|
}
|
|
1772
3826
|
function HtmlRenderer({ artifact: artifact2 }) {
|
|
3827
|
+
const t = useT();
|
|
1773
3828
|
const iframeRef = useRef(null);
|
|
1774
3829
|
const imgFileRef = useRef(null);
|
|
1775
3830
|
const bgFileRef = useRef(null);
|
|
@@ -1869,6 +3924,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1869
3924
|
};
|
|
1870
3925
|
const setSlideStyle = (style) => sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style });
|
|
1871
3926
|
const ratio = artifact2.meta?.ratio;
|
|
3927
|
+
const isDoc = !ratio && artifact2.meta?.kind === "doc";
|
|
1872
3928
|
const slide = useSlideFit(ratio, stageRef);
|
|
1873
3929
|
return /* @__PURE__ */ jsxs("div", { className: "cv-html-wrap", children: [
|
|
1874
3930
|
/* @__PURE__ */ jsx("input", { ref: imgFileRef, type: "file", accept: "image/*", hidden: true, onChange: (e) => {
|
|
@@ -1881,26 +3937,30 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1881
3937
|
} }),
|
|
1882
3938
|
/* @__PURE__ */ jsxs("div", { className: "cv-html-bar cv-chrome", children: [
|
|
1883
3939
|
mode === "design" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1884
|
-
!ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1885
|
-
/* @__PURE__ */ jsx("div", { className: "cv-html-seg", role: "group", "aria-label": "Preview width", children: DEVICES.map((d) => /* @__PURE__ */ jsx("button", { className: device === d.id ? "is-on" : "", onClick: () => setDevice(d.id), children: d.label }, d.id)) }),
|
|
3940
|
+
!ratio && !isDoc && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3941
|
+
/* @__PURE__ */ jsx("div", { className: "cv-html-seg", role: "group", "aria-label": "Preview width", children: DEVICES.map((d) => /* @__PURE__ */ jsx("button", { className: device === d.id ? "is-on" : "", onClick: () => setDevice(d.id), children: DEVICE_KEYS[d.id] ? t(DEVICE_KEYS[d.id]) : d.label }, d.id)) }),
|
|
1886
3942
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" })
|
|
1887
3943
|
] }),
|
|
1888
|
-
/* @__PURE__ */
|
|
1889
|
-
|
|
1890
|
-
|
|
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: [
|
|
1891
3951
|
/* @__PURE__ */ jsxs(
|
|
1892
3952
|
"select",
|
|
1893
3953
|
{
|
|
1894
3954
|
className: "cv-html-tpl",
|
|
1895
3955
|
value: "",
|
|
1896
|
-
title: "
|
|
3956
|
+
title: t("pageTemplateTitle"),
|
|
1897
3957
|
onChange: (e) => {
|
|
1898
3958
|
const s = STARTERS[e.target.value];
|
|
1899
3959
|
if (s) applyEvent({ type: "canvas.patch", id: artifact2.id, patch: { html: s.build() } });
|
|
1900
3960
|
e.currentTarget.value = "";
|
|
1901
3961
|
},
|
|
1902
3962
|
children: [
|
|
1903
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "
|
|
3963
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("pageMenu") }),
|
|
1904
3964
|
Object.entries(STARTERS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1905
3965
|
]
|
|
1906
3966
|
}
|
|
@@ -1910,14 +3970,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1910
3970
|
{
|
|
1911
3971
|
className: "cv-html-tpl",
|
|
1912
3972
|
value: "",
|
|
1913
|
-
title: "
|
|
3973
|
+
title: t("sectionTemplateTitle"),
|
|
1914
3974
|
onChange: (e) => {
|
|
1915
|
-
const
|
|
1916
|
-
if (
|
|
3975
|
+
const t2 = TEMPLATES[e.target.value];
|
|
3976
|
+
if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
|
|
1917
3977
|
e.currentTarget.value = "";
|
|
1918
3978
|
},
|
|
1919
3979
|
children: [
|
|
1920
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "
|
|
3980
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("sectionMenu") }),
|
|
1921
3981
|
Object.entries(TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1922
3982
|
]
|
|
1923
3983
|
}
|
|
@@ -1927,34 +3987,51 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1927
3987
|
{
|
|
1928
3988
|
className: "cv-html-tpl",
|
|
1929
3989
|
value: "",
|
|
1930
|
-
title: "
|
|
3990
|
+
title: t("jumpToHeading"),
|
|
1931
3991
|
onChange: (e) => {
|
|
1932
3992
|
const idx = Number(e.target.value);
|
|
1933
3993
|
if (!Number.isNaN(idx)) sendIframeCommand({ artifactId: artifact2.id, type: "scroll_to", index: idx });
|
|
1934
3994
|
e.currentTarget.value = "";
|
|
1935
3995
|
},
|
|
1936
3996
|
children: [
|
|
1937
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "
|
|
3997
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("outlineMenu") }),
|
|
1938
3998
|
outline.map((h, i) => /* @__PURE__ */ jsx("option", { value: i, children: "\xA0".repeat((h.level - 1) * 2) + h.text }, i))
|
|
1939
3999
|
]
|
|
1940
4000
|
}
|
|
1941
4001
|
),
|
|
1942
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "
|
|
4002
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title: t("a11yTitle"), onClick: () => setA11y(checkA11y(artifact2.data.html)), children: "\u267F Check" })
|
|
1943
4003
|
] }),
|
|
4004
|
+
isDoc && outline.length > 0 && /* @__PURE__ */ jsxs(
|
|
4005
|
+
"select",
|
|
4006
|
+
{
|
|
4007
|
+
className: "cv-html-tpl",
|
|
4008
|
+
value: "",
|
|
4009
|
+
title: t("jumpToHeading"),
|
|
4010
|
+
onChange: (e) => {
|
|
4011
|
+
const idx = Number(e.target.value);
|
|
4012
|
+
if (!Number.isNaN(idx)) sendIframeCommand({ artifactId: artifact2.id, type: "scroll_to", index: idx });
|
|
4013
|
+
e.currentTarget.value = "";
|
|
4014
|
+
},
|
|
4015
|
+
children: [
|
|
4016
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("outlineMenu") }),
|
|
4017
|
+
outline.map((h, i) => /* @__PURE__ */ jsx("option", { value: i, children: h.text }, `d${i}`))
|
|
4018
|
+
]
|
|
4019
|
+
}
|
|
4020
|
+
),
|
|
1944
4021
|
ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1945
4022
|
/* @__PURE__ */ jsxs(
|
|
1946
4023
|
"select",
|
|
1947
4024
|
{
|
|
1948
4025
|
className: "cv-html-tpl",
|
|
1949
4026
|
value: "",
|
|
1950
|
-
title: "
|
|
4027
|
+
title: t("insertSlideLayout"),
|
|
1951
4028
|
onChange: (e) => {
|
|
1952
|
-
const
|
|
1953
|
-
if (
|
|
4029
|
+
const t2 = SLIDE_TEMPLATES[e.target.value];
|
|
4030
|
+
if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
|
|
1954
4031
|
e.currentTarget.value = "";
|
|
1955
4032
|
},
|
|
1956
4033
|
children: [
|
|
1957
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "
|
|
4034
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("layoutMenu") }),
|
|
1958
4035
|
Object.entries(SLIDE_TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1959
4036
|
]
|
|
1960
4037
|
}
|
|
@@ -1964,14 +4041,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1964
4041
|
{
|
|
1965
4042
|
className: "cv-html-tpl",
|
|
1966
4043
|
value: "",
|
|
1967
|
-
title: "
|
|
4044
|
+
title: t("applySlideTheme"),
|
|
1968
4045
|
onChange: (e) => {
|
|
1969
|
-
const
|
|
1970
|
-
if (
|
|
4046
|
+
const t2 = SLIDE_THEMES[e.target.value];
|
|
4047
|
+
if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style: t2.style });
|
|
1971
4048
|
e.currentTarget.value = "";
|
|
1972
4049
|
},
|
|
1973
4050
|
children: [
|
|
1974
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "
|
|
4051
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("themeMenu") }),
|
|
1975
4052
|
Object.entries(SLIDE_THEMES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1976
4053
|
]
|
|
1977
4054
|
}
|
|
@@ -1981,14 +4058,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1981
4058
|
{
|
|
1982
4059
|
className: "cv-html-tpl",
|
|
1983
4060
|
value: "",
|
|
1984
|
-
title: "
|
|
4061
|
+
title: t("insertShape"),
|
|
1985
4062
|
onChange: (e) => {
|
|
1986
|
-
const
|
|
1987
|
-
if (
|
|
4063
|
+
const t2 = SLIDE_SHAPES[e.target.value];
|
|
4064
|
+
if (t2) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t2.html });
|
|
1988
4065
|
e.currentTarget.value = "";
|
|
1989
4066
|
},
|
|
1990
4067
|
children: [
|
|
1991
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "
|
|
4068
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("shapeMenu") }),
|
|
1992
4069
|
Object.entries(SLIDE_SHAPES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1993
4070
|
]
|
|
1994
4071
|
}
|
|
@@ -1998,19 +4075,19 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1998
4075
|
{
|
|
1999
4076
|
className: "cv-html-tpl",
|
|
2000
4077
|
value: "",
|
|
2001
|
-
title: "
|
|
4078
|
+
title: t("slideFont"),
|
|
2002
4079
|
onChange: (e) => {
|
|
2003
4080
|
const f = SLIDE_FONTS[e.target.value];
|
|
2004
4081
|
if (f) setSlideStyle({ fontFamily: f.stack });
|
|
2005
4082
|
e.currentTarget.value = "";
|
|
2006
4083
|
},
|
|
2007
4084
|
children: [
|
|
2008
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "
|
|
4085
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("fontMenu") }),
|
|
2009
4086
|
Object.entries(SLIDE_FONTS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
2010
4087
|
]
|
|
2011
4088
|
}
|
|
2012
4089
|
),
|
|
2013
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "
|
|
4090
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title: t("slideBgImage"), onClick: () => bgFileRef.current?.click(), children: "\u{1F5BC} BG" })
|
|
2014
4091
|
] }),
|
|
2015
4092
|
selected.length >= 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2016
4093
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" }),
|
|
@@ -2040,33 +4117,33 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
2040
4117
|
)
|
|
2041
4118
|
] }),
|
|
2042
4119
|
single && single.tag !== "img" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2043
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "
|
|
2044
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "
|
|
2045
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "
|
|
2046
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "
|
|
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" })
|
|
2047
4124
|
] }),
|
|
2048
4125
|
single && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2049
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "
|
|
2050
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "
|
|
2051
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "
|
|
2052
|
-
/* @__PURE__ */ jsx("button", { className: "cv-html-act cv-html-act--del", title: "
|
|
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}" })
|
|
2053
4130
|
] })
|
|
2054
4131
|
] })
|
|
2055
4132
|
] }),
|
|
2056
4133
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__spacer" }),
|
|
2057
4134
|
/* @__PURE__ */ jsxs("div", { className: "cv-html-seg", role: "group", "aria-label": "View mode", children: [
|
|
2058
|
-
/* @__PURE__ */ jsx("button", { className: mode === "design" ? "is-on" : "", onClick: () => setMode("design"), children: "
|
|
2059
|
-
/* @__PURE__ */ jsx("button", { className: mode === "code" ? "is-on" : "", onClick: () => setMode("code"), 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") })
|
|
2060
4137
|
] })
|
|
2061
4138
|
] }),
|
|
2062
4139
|
a11y !== null && /* @__PURE__ */ jsxs("div", { className: "cv-a11y", role: "status", children: [
|
|
2063
4140
|
/* @__PURE__ */ jsx("button", { className: "cv-a11y__close", onClick: () => setA11y(null), "aria-label": "Dismiss", children: "\xD7" }),
|
|
2064
|
-
a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children: "
|
|
4141
|
+
a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children: t("a11yNone") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2065
4142
|
/* @__PURE__ */ jsxs("b", { children: [
|
|
2066
4143
|
"\u267F ",
|
|
2067
4144
|
a11y.length,
|
|
2068
|
-
"
|
|
2069
|
-
|
|
4145
|
+
" ",
|
|
4146
|
+
t("a11yIssues")
|
|
2070
4147
|
] }),
|
|
2071
4148
|
/* @__PURE__ */ jsx("ul", { children: a11y.map((m, i) => /* @__PURE__ */ jsx("li", { children: m }, i)) })
|
|
2072
4149
|
] })
|
|
@@ -2096,31 +4173,48 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
2096
4173
|
sandbox: "allow-scripts allow-popups allow-modals",
|
|
2097
4174
|
style: { width: DEVICES.find((d) => d.id === device).width }
|
|
2098
4175
|
}
|
|
2099
|
-
) }) : /* @__PURE__ */ jsx(
|
|
2100
|
-
"textarea",
|
|
2101
|
-
{
|
|
2102
|
-
className: "cv-html-code",
|
|
2103
|
-
defaultValue: artifact2.data.html,
|
|
2104
|
-
spellCheck: false,
|
|
2105
|
-
onBlur: (e) => commitCode(e.target.value),
|
|
2106
|
-
"aria-label": "HTML source"
|
|
2107
|
-
},
|
|
2108
|
-
artifact2.data.html
|
|
2109
|
-
)
|
|
4176
|
+
) }) : /* @__PURE__ */ jsx(CodePane, { html: artifact2.data.html, onCommit: commitCode })
|
|
2110
4177
|
] });
|
|
2111
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);
|
|
4199
|
+
},
|
|
4200
|
+
"aria-label": "HTML source"
|
|
4201
|
+
}
|
|
4202
|
+
);
|
|
4203
|
+
}
|
|
2112
4204
|
|
|
2113
4205
|
// src/components/renderers/index.ts
|
|
2114
|
-
var ChartRenderer = lazy(() => import('./ChartRenderer-
|
|
2115
|
-
var DocumentRenderer = lazy(() => import('./DocumentRenderer-
|
|
2116
|
-
var TableRenderer = lazy(() => import('./TableRenderer-
|
|
2117
|
-
var SlidesRenderer = lazy(() => import('./SlidesRenderer-
|
|
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 })));
|
|
2118
4211
|
var builtinRenderers = {
|
|
2119
4212
|
html: HtmlRenderer,
|
|
2120
4213
|
document: DocumentRenderer,
|
|
2121
4214
|
chart: ChartRenderer,
|
|
2122
4215
|
table: TableRenderer,
|
|
2123
|
-
slides: SlidesRenderer
|
|
4216
|
+
slides: SlidesRenderer,
|
|
4217
|
+
pdf: PdfRenderer
|
|
2124
4218
|
};
|
|
2125
4219
|
|
|
2126
4220
|
// src/export/download.ts
|
|
@@ -2139,6 +4233,232 @@ function slugify(text) {
|
|
|
2139
4233
|
return text.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "artifact";
|
|
2140
4234
|
}
|
|
2141
4235
|
|
|
4236
|
+
// src/io/hwpxWrite.ts
|
|
4237
|
+
var CRC_TABLE = (() => {
|
|
4238
|
+
const table2 = new Uint32Array(256);
|
|
4239
|
+
for (let n = 0; n < 256; n++) {
|
|
4240
|
+
let c = n;
|
|
4241
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
4242
|
+
table2[n] = c >>> 0;
|
|
4243
|
+
}
|
|
4244
|
+
return table2;
|
|
4245
|
+
})();
|
|
4246
|
+
function crc32(bytes) {
|
|
4247
|
+
let c = 4294967295;
|
|
4248
|
+
for (let i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
4249
|
+
return (c ^ 4294967295) >>> 0;
|
|
4250
|
+
}
|
|
4251
|
+
function storeZip(parts) {
|
|
4252
|
+
const dosTime = 0;
|
|
4253
|
+
const dosDate = 2026 - 1980 << 9 | 1 << 5 | 1;
|
|
4254
|
+
const enc = new TextEncoder();
|
|
4255
|
+
const chunks = [];
|
|
4256
|
+
const central = [];
|
|
4257
|
+
let offset = 0;
|
|
4258
|
+
for (const [name, data] of parts) {
|
|
4259
|
+
const nameBytes = enc.encode(name);
|
|
4260
|
+
const crc = crc32(data);
|
|
4261
|
+
const local = new Uint8Array(30 + nameBytes.length);
|
|
4262
|
+
const lv = new DataView(local.buffer);
|
|
4263
|
+
lv.setUint32(0, 67324752, true);
|
|
4264
|
+
lv.setUint16(4, 20, true);
|
|
4265
|
+
lv.setUint16(8, 0, true);
|
|
4266
|
+
lv.setUint16(10, dosTime, true);
|
|
4267
|
+
lv.setUint16(12, dosDate, true);
|
|
4268
|
+
lv.setUint32(14, crc, true);
|
|
4269
|
+
lv.setUint32(18, data.length, true);
|
|
4270
|
+
lv.setUint32(22, data.length, true);
|
|
4271
|
+
lv.setUint16(26, nameBytes.length, true);
|
|
4272
|
+
local.set(nameBytes, 30);
|
|
4273
|
+
chunks.push(local, data);
|
|
4274
|
+
const cdir = new Uint8Array(46 + nameBytes.length);
|
|
4275
|
+
const cv = new DataView(cdir.buffer);
|
|
4276
|
+
cv.setUint32(0, 33639248, true);
|
|
4277
|
+
cv.setUint16(4, 20, true);
|
|
4278
|
+
cv.setUint16(6, 20, true);
|
|
4279
|
+
cv.setUint16(10, 0, true);
|
|
4280
|
+
cv.setUint16(12, dosTime, true);
|
|
4281
|
+
cv.setUint16(14, dosDate, true);
|
|
4282
|
+
cv.setUint32(16, crc, true);
|
|
4283
|
+
cv.setUint32(20, data.length, true);
|
|
4284
|
+
cv.setUint32(24, data.length, true);
|
|
4285
|
+
cv.setUint16(28, nameBytes.length, true);
|
|
4286
|
+
cv.setUint32(42, offset, true);
|
|
4287
|
+
cdir.set(nameBytes, 46);
|
|
4288
|
+
central.push(cdir);
|
|
4289
|
+
offset += local.length + data.length;
|
|
4290
|
+
}
|
|
4291
|
+
const cdirSize = central.reduce((n, c) => n + c.length, 0);
|
|
4292
|
+
const eocd = new Uint8Array(22);
|
|
4293
|
+
const ev = new DataView(eocd.buffer);
|
|
4294
|
+
ev.setUint32(0, 101010256, true);
|
|
4295
|
+
ev.setUint16(8, parts.length, true);
|
|
4296
|
+
ev.setUint16(10, parts.length, true);
|
|
4297
|
+
ev.setUint32(12, cdirSize, true);
|
|
4298
|
+
ev.setUint32(16, offset, true);
|
|
4299
|
+
const all = [...chunks, ...central, eocd];
|
|
4300
|
+
const out = new Uint8Array(all.reduce((n, c) => n + c.length, 0));
|
|
4301
|
+
let pos = 0;
|
|
4302
|
+
for (const c of all) {
|
|
4303
|
+
out.set(c, pos);
|
|
4304
|
+
pos += c.length;
|
|
4305
|
+
}
|
|
4306
|
+
return out;
|
|
4307
|
+
}
|
|
4308
|
+
var XML_DECL = `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>`;
|
|
4309
|
+
var HWPML_NS = [
|
|
4310
|
+
`xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app"`,
|
|
4311
|
+
`xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph"`,
|
|
4312
|
+
`xmlns:hp10="http://www.hancom.co.kr/hwpml/2016/paragraph"`,
|
|
4313
|
+
`xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section"`,
|
|
4314
|
+
`xmlns:hc="http://www.hancom.co.kr/hwpml/2011/core"`,
|
|
4315
|
+
`xmlns:hh="http://www.hancom.co.kr/hwpml/2011/head"`,
|
|
4316
|
+
`xmlns:hhs="http://www.hancom.co.kr/hwpml/2011/history"`,
|
|
4317
|
+
`xmlns:hm="http://www.hancom.co.kr/hwpml/2011/master-page"`,
|
|
4318
|
+
`xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf"`,
|
|
4319
|
+
`xmlns:dc="http://purl.org/dc/elements/1.1/"`,
|
|
4320
|
+
`xmlns:opf="http://www.idpf.org/2007/opf/"`,
|
|
4321
|
+
`xmlns:ooxmlchart="http://www.hancom.co.kr/hwpml/2016/ooxmlchart"`,
|
|
4322
|
+
`xmlns:hwpunitchar="http://www.hancom.co.kr/hwpml/2016/HwpUnitChar"`,
|
|
4323
|
+
`xmlns:epub="http://www.idpf.org/2007/ops"`,
|
|
4324
|
+
`xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"`
|
|
4325
|
+
].join(" ");
|
|
4326
|
+
var VERSION_XML = XML_DECL + `<hv:HCFVersion xmlns:hv="http://www.hancom.co.kr/hwpml/2011/version" tagetApplication="WORDPROCESSOR" major="5" minor="0" micro="5" buildNumber="0" xmlVersion="1.4" application="langchain-canvas" appVersion="1.0"/>`;
|
|
4327
|
+
var CONTAINER_XML = XML_DECL + `<ocf:container xmlns:ocf="urn:oasis:names:tc:opendocument:xmlns:container" xmlns:hpf="http://www.hancom.co.kr/schema/2011/hpf"><ocf:rootfiles><ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/></ocf:rootfiles></ocf:container>`;
|
|
4328
|
+
var MANIFEST_XML = XML_DECL + `<odf:manifest xmlns:odf="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"/>`;
|
|
4329
|
+
var CONTENT_HPF = XML_DECL + `<opf:package ${HWPML_NS} version="" unique-identifier="" id=""><opf:metadata><opf:title/><opf:language>ko</opf:language></opf:metadata><opf:manifest><opf:item id="header" href="Contents/header.xml" media-type="application/xml"/><opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/><opf:item id="settings" href="settings.xml" media-type="application/xml"/></opf:manifest><opf:spine><opf:itemref idref="header"/><opf:itemref idref="section0"/></opf:spine></opf:package>`;
|
|
4330
|
+
var SETTINGS_XML = XML_DECL + `<ha:HWPApplicationSetting xmlns:ha="http://www.hancom.co.kr/hwpml/2011/app" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0"><ha:CaretPosition listIDRef="0" paraIDRef="0" pos="0"/></ha:HWPApplicationSetting>`;
|
|
4331
|
+
function fontfaces() {
|
|
4332
|
+
const langs = ["HANGUL", "LATIN", "HANJA", "JAPANESE", "OTHER", "SYMBOL", "USER"];
|
|
4333
|
+
const face = (lang) => `<hh:fontface lang="${lang}" fontCnt="1"><hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0"><hh:typeInfo familyType="FCAT_GOTHIC" weight="8" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/></hh:font></hh:fontface>`;
|
|
4334
|
+
return `<hh:fontfaces itemCnt="7">${langs.map(face).join("")}</hh:fontfaces>`;
|
|
4335
|
+
}
|
|
4336
|
+
function borderFills() {
|
|
4337
|
+
const fill = (id) => `<hh:borderFill id="${id}" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0"><hh:slash type="NONE" Crooked="0" isCounter="0"/><hh:backSlash type="NONE" Crooked="0" isCounter="0"/><hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/><hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/><hh:topBorder type="NONE" width="0.1 mm" color="#000000"/><hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/><hh:diagonal type="SOLID" width="0.1 mm" color="#000000"/></hh:borderFill>`;
|
|
4338
|
+
return `<hh:borderFills itemCnt="2">${fill(1)}${fill(2)}</hh:borderFills>`;
|
|
4339
|
+
}
|
|
4340
|
+
var CHAR_PR = [
|
|
4341
|
+
{ id: 0, height: 1e3, bold: false, italic: false },
|
|
4342
|
+
// body
|
|
4343
|
+
{ id: 1, height: 1800, bold: true, italic: false },
|
|
4344
|
+
// heading 1
|
|
4345
|
+
{ id: 2, height: 1500, bold: true, italic: false },
|
|
4346
|
+
// heading 2
|
|
4347
|
+
{ id: 3, height: 1200, bold: true, italic: false },
|
|
4348
|
+
// heading 3
|
|
4349
|
+
{ id: 4, height: 1e3, bold: true, italic: false },
|
|
4350
|
+
// inline bold
|
|
4351
|
+
{ id: 5, height: 1e3, bold: false, italic: true }
|
|
4352
|
+
// inline italic
|
|
4353
|
+
];
|
|
4354
|
+
function charProperties() {
|
|
4355
|
+
const one = (c) => `<hh:charPr id="${c.id}" height="${c.height}" textColor="#000000" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2"><hh:fontRef hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/><hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/><hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>` + (c.bold ? `<hh:bold/>` : "") + (c.italic ? `<hh:italic/>` : "") + `<hh:underline type="NONE" shape="SOLID" color="#000000"/><hh:strikeout shape="NONE" color="#000000"/><hh:outline type="NONE"/><hh:shadow type="NONE" color="#B2B2B2" offsetX="10" offsetY="10"/></hh:charPr>`;
|
|
4356
|
+
return `<hh:charProperties itemCnt="${CHAR_PR.length}">${CHAR_PR.map(one).join("")}</hh:charProperties>`;
|
|
4357
|
+
}
|
|
4358
|
+
function numberings() {
|
|
4359
|
+
const heads = Array.from(
|
|
4360
|
+
{ length: 7 },
|
|
4361
|
+
(_, i) => `<hh:paraHead start="1" level="${i + 1}" align="LEFT" useInstWidth="1" autoIndent="1" widthAdjust="0" textOffsetType="PERCENT" textOffset="50" numFormat="DIGIT" charPrIDRef="4294967295" checkable="1">^${i + 1}.</hh:paraHead>`
|
|
4362
|
+
).join("");
|
|
4363
|
+
return `<hh:numberings itemCnt="1"><hh:numbering id="1" start="0">${heads}</hh:numbering></hh:numberings>`;
|
|
4364
|
+
}
|
|
4365
|
+
function paraProperties() {
|
|
4366
|
+
return `<hh:paraProperties itemCnt="1"><hh:paraPr id="0" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0"><hh:align horizontal="JUSTIFY" vertical="BASELINE"/><hh:heading type="NONE" idRef="0" level="0"/><hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="KEEP_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/><hh:autoSpacing eAsianEng="0" eAsianNum="0"/><hh:margin><hc:intent value="0" unit="HWPUNIT"/><hc:left value="0" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="0" unit="HWPUNIT"/><hc:next value="0" unit="HWPUNIT"/></hh:margin><hh:lineSpacing type="PERCENT" value="160" unit="HWPUNIT"/><hh:border borderFillIDRef="2" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/></hh:paraPr></hh:paraProperties>`;
|
|
4367
|
+
}
|
|
4368
|
+
var STYLES = [
|
|
4369
|
+
{ id: 0, name: "\uBC14\uD0D5\uAE00", engName: "Normal", charPr: 0, next: 0 },
|
|
4370
|
+
{ id: 1, name: "\uAC1C\uC694 1", engName: "Outline 1", charPr: 1, next: 1 },
|
|
4371
|
+
{ id: 2, name: "\uAC1C\uC694 2", engName: "Outline 2", charPr: 2, next: 2 },
|
|
4372
|
+
{ id: 3, name: "\uAC1C\uC694 3", engName: "Outline 3", charPr: 3, next: 3 }
|
|
4373
|
+
];
|
|
4374
|
+
function styles() {
|
|
4375
|
+
const one = (s) => `<hh:style id="${s.id}" type="PARA" name="${s.name}" engName="${s.engName}" paraPrIDRef="0" charPrIDRef="${s.charPr}" nextStyleIDRef="${s.next}" langID="1042" lockForm="0"/>`;
|
|
4376
|
+
return `<hh:styles itemCnt="${STYLES.length}">${STYLES.map(one).join("")}</hh:styles>`;
|
|
4377
|
+
}
|
|
4378
|
+
function headerXml() {
|
|
4379
|
+
return XML_DECL + `<hh:head ${HWPML_NS} version="1.4" secCnt="1"><hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/><hh:refList>` + fontfaces() + borderFills() + charProperties() + `<hh:tabProperties itemCnt="2"><hh:tabPr id="0" autoTabLeft="0" autoTabRight="0"/><hh:tabPr id="1" autoTabLeft="1" autoTabRight="0"/></hh:tabProperties>` + numberings() + paraProperties() + styles() + `</hh:refList><hh:compatibleDocument targetProgram="HWP201X"><hh:layoutCompatibility/></hh:compatibleDocument><hh:docOption><hh:linkinfo path="" pageInherit="0" footnoteInherit="0"/></hh:docOption><hh:trackchageConfig flags="56"/></hh:head>`;
|
|
4380
|
+
}
|
|
4381
|
+
var SEC_PR = `<hp:secPr id="" textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" tabStopVal="4000" tabStopUnit="HWPUNIT" outlineShapeIDRef="1" memoShapeIDRef="0" textVerticalWidthHead="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:lineNumberShape restartType="0" countBy="0" distance="0" startNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="4252" footer="4252" gutter="0" left="8504" right="8504" top="5668" bottom="4252"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr><hp:pageBorderFill type="BOTH" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill><hp:pageBorderFill type="EVEN" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill><hp:pageBorderFill type="ODD" borderFillIDRef="1" textBorder="PAPER" headerInside="0" footerInside="0" fillArea="PAPER"><hp:offset left="1417" right="1417" top="1417" bottom="1417"/></hp:pageBorderFill></hp:secPr><hp:ctrl><hp:colPr id="" type="NEWSPAPER" layout="LEFT" colCount="1" sameSz="1" sameGap="0"/></hp:ctrl>`;
|
|
4382
|
+
function escapeXml(value) {
|
|
4383
|
+
return String(value ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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
|
+
|
|
2142
4462
|
// src/export/exporters.ts
|
|
2143
4463
|
var MIME = {
|
|
2144
4464
|
md: "text/markdown",
|
|
@@ -2151,7 +4471,8 @@ var MIME = {
|
|
|
2151
4471
|
var dataExporters = {
|
|
2152
4472
|
document: [
|
|
2153
4473
|
{ label: "Markdown", extension: "md", mime: MIME.md, build: (a) => a.data.content },
|
|
2154
|
-
{ label: "Word", extension: "docx", mime: MIME.docx, build: (a) => documentToDocx(a.data) }
|
|
4474
|
+
{ label: "Word", extension: "docx", mime: MIME.docx, build: (a) => documentToDocx(a.data) },
|
|
4475
|
+
{ label: "\uD55C\uAE00 (HWPX)", extension: "hwpx", mime: "application/vnd.hancom.hwpx", build: (a) => documentToHwpx(a.data) }
|
|
2155
4476
|
],
|
|
2156
4477
|
table: [
|
|
2157
4478
|
{ label: "CSV", extension: "csv", mime: MIME.csv, build: (a) => tableToCsv(a.data) },
|
|
@@ -2170,7 +4491,7 @@ function toStandaloneHtml(title, renderedHtml) {
|
|
|
2170
4491
|
<head>
|
|
2171
4492
|
<meta charset="utf-8" />
|
|
2172
4493
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
2173
|
-
<title>${
|
|
4494
|
+
<title>${escapeHtml2(title)}</title>
|
|
2174
4495
|
<style>${EXPORT_CSS}</style>
|
|
2175
4496
|
</head>
|
|
2176
4497
|
<body>
|
|
@@ -2181,11 +4502,34 @@ ${renderedHtml}
|
|
|
2181
4502
|
</html>`;
|
|
2182
4503
|
}
|
|
2183
4504
|
function tableToCsv(data) {
|
|
4505
|
+
if (data.sheet?.length) return fortuneToCsv(data.sheet[0]);
|
|
2184
4506
|
const header = data.columns.map((c) => csvCell(c.label ?? c.key)).join(",");
|
|
2185
4507
|
const body = data.rows.map((row) => data.columns.map((c) => csvCell(String(row[c.key] ?? ""))).join(",")).join("\n");
|
|
2186
4508
|
return `${header}
|
|
2187
4509
|
${body}`;
|
|
2188
4510
|
}
|
|
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
|
+
}
|
|
2189
4533
|
async function tableToXlsx(data) {
|
|
2190
4534
|
const { Workbook } = await loadOptional("exceljs", () => import('exceljs'));
|
|
2191
4535
|
const workbook = new Workbook();
|
|
@@ -2249,8 +4593,10 @@ async function documentToDocx(data) {
|
|
|
2249
4593
|
async function slidesToPptx(data, _title) {
|
|
2250
4594
|
const PptxGenJS = (await loadOptional("pptxgenjs", () => import('pptxgenjs'))).default;
|
|
2251
4595
|
const pptx = new PptxGenJS();
|
|
2252
|
-
const W =
|
|
2253
|
-
const H = 5
|
|
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";
|
|
2254
4600
|
for (const slide of data.slides) {
|
|
2255
4601
|
const s = pptx.addSlide();
|
|
2256
4602
|
if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
|
|
@@ -2259,18 +4605,28 @@ async function slidesToPptx(data, _title) {
|
|
|
2259
4605
|
const inset = (v) => pad + v / 100 * (1 - 2 * pad);
|
|
2260
4606
|
for (const el of resolveElements(slide)) {
|
|
2261
4607
|
const box = { x: inset(el.x) * W, y: inset(el.y) * H, w: el.w / 100 * (1 - 2 * pad) * W, h: el.h / 100 * (1 - 2 * pad) * H };
|
|
4608
|
+
const spin = el.rotate ? { rotate: (Math.round(el.rotate) % 360 + 360) % 360 } : {};
|
|
2262
4609
|
if (el.type === "text") {
|
|
2263
4610
|
const color = el.color ? el.color.replace("#", "") : tc;
|
|
2264
|
-
s.addText(el.text ?? "", { ...box, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
|
|
4611
|
+
s.addText(el.text ?? "", { ...box, ...spin, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
|
|
2265
4612
|
} else if (el.type === "shape") {
|
|
2266
4613
|
const fill = (el.fill ?? "#5b5bd6").replace("#", "");
|
|
4614
|
+
const paint = el.fill ? { fill: { color: fill } } : { fill: { color: "FFFFFF", transparency: 100 }, line: { color: (tc || "1f2328").replace("#", ""), width: 1 } };
|
|
2267
4615
|
if (el.shape === "line") {
|
|
2268
|
-
s.addShape(pptx.ShapeType.line, { ...box, line: { color: fill, width: 2 } });
|
|
4616
|
+
s.addShape(pptx.ShapeType.line, { ...box, ...spin, line: { color: fill, width: 2 } });
|
|
4617
|
+
} else if (el.shape !== "ellipse" && el.radius) {
|
|
4618
|
+
s.addShape(pptx.ShapeType.roundRect, { ...box, ...spin, ...paint, rectRadius: el.radius * W / 1280 });
|
|
2269
4619
|
} else {
|
|
2270
|
-
s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box,
|
|
4620
|
+
s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box, ...spin, ...paint });
|
|
2271
4621
|
}
|
|
2272
4622
|
} else if (el.src) {
|
|
2273
|
-
s.addImage({
|
|
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
|
+
});
|
|
2274
4630
|
}
|
|
2275
4631
|
}
|
|
2276
4632
|
if (slide.notes) s.addNotes(slide.notes);
|
|
@@ -2283,29 +4639,34 @@ function slidesToPrintHtml(data, title) {
|
|
|
2283
4639
|
const bg = slide.background ?? "#ffffff";
|
|
2284
4640
|
const fg = slide.textColor ?? "#1f2328";
|
|
2285
4641
|
const els = resolveElements(slide).map((el) => {
|
|
2286
|
-
const
|
|
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}`;
|
|
2287
4644
|
if (el.type === "text") {
|
|
2288
|
-
const style = `${box};font-size:${
|
|
2289
|
-
return `<div class="el" style="${style}">${
|
|
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>`;
|
|
2290
4647
|
}
|
|
2291
4648
|
if (el.type === "shape") {
|
|
2292
|
-
const
|
|
2293
|
-
const
|
|
2294
|
-
return `<div class="el" style="${box}
|
|
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>`;
|
|
2295
4652
|
}
|
|
2296
4653
|
const src = safeSrc(el.src);
|
|
2297
|
-
|
|
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)}"/>` : "";
|
|
2298
4656
|
}).join("");
|
|
2299
4657
|
const pad = slide.padding ?? 0;
|
|
2300
4658
|
const inner = pad ? `<div style="position:absolute;inset:${pad}%">${els}</div>` : els;
|
|
2301
4659
|
return `<section class="slide" style="background:${escapeAttr(bg)}">${inner}</section>`;
|
|
2302
4660
|
}).join("");
|
|
2303
|
-
return `<!doctype html><html><head><meta charset="utf-8"><title>${
|
|
4661
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml2(title)}</title><style>
|
|
2304
4662
|
@page { size: 1280px 720px; margin: 0; }
|
|
2305
4663
|
* { margin: 0; box-sizing: border-box; }
|
|
2306
4664
|
body { font-family: Inter, Arial, sans-serif; }
|
|
2307
4665
|
.slide { position: relative; width: 1280px; height: 720px; overflow: hidden; page-break-after: always; }
|
|
2308
|
-
.el { position: absolute; overflow: hidden; line-height: 1.
|
|
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; }
|
|
2309
4670
|
img.el { object-fit: contain; }
|
|
2310
4671
|
</style></head><body>${pages}</body></html>`;
|
|
2311
4672
|
}
|
|
@@ -2316,7 +4677,7 @@ function htmlSlideToPrintHtml(html, ratio) {
|
|
|
2316
4677
|
const i = html.toLowerCase().lastIndexOf("</head>");
|
|
2317
4678
|
return i === -1 ? style + html : html.slice(0, i) + style + html.slice(i);
|
|
2318
4679
|
}
|
|
2319
|
-
function
|
|
4680
|
+
function escapeXml2(value) {
|
|
2320
4681
|
return String(value ?? "").replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
2321
4682
|
}
|
|
2322
4683
|
function escapeAttr(value) {
|
|
@@ -2329,7 +4690,7 @@ function safeSrc(src) {
|
|
|
2329
4690
|
function csvCell(value) {
|
|
2330
4691
|
return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
2331
4692
|
}
|
|
2332
|
-
function
|
|
4693
|
+
function escapeHtml2(text) {
|
|
2333
4694
|
return text.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
2334
4695
|
}
|
|
2335
4696
|
var EXPORT_CSS = `
|
|
@@ -2373,7 +4734,10 @@ function printToPdf(html) {
|
|
|
2373
4734
|
document.body.appendChild(iframe);
|
|
2374
4735
|
}
|
|
2375
4736
|
var PDF_TYPES = /* @__PURE__ */ new Set(["html", "document", "chart", "slides"]);
|
|
4737
|
+
var escapeHtml3 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4738
|
+
var escapeAttr2 = (s) => escapeHtml3(s).replace(/"/g, """);
|
|
2376
4739
|
function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
4740
|
+
const t = useT();
|
|
2377
4741
|
const [open, setOpen] = useState(false);
|
|
2378
4742
|
const stem = slugify(artifact2.title);
|
|
2379
4743
|
const dataOptions = dataExporters[artifact2.type] ?? [];
|
|
@@ -2414,7 +4778,8 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
2414
4778
|
return h == null ? null : toStandaloneHtml(artifact2.title, h);
|
|
2415
4779
|
})();
|
|
2416
4780
|
if (html == null) return;
|
|
2417
|
-
const
|
|
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" }));
|
|
2418
4783
|
window.open(url, "_blank", "noopener");
|
|
2419
4784
|
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
2420
4785
|
setOpen(false);
|
|
@@ -2440,14 +4805,14 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
2440
4805
|
onClick: () => setOpen((o) => !o),
|
|
2441
4806
|
"aria-haspopup": "menu",
|
|
2442
4807
|
"aria-expanded": open,
|
|
2443
|
-
children: "
|
|
4808
|
+
children: t("export")
|
|
2444
4809
|
}
|
|
2445
4810
|
),
|
|
2446
4811
|
open && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2447
4812
|
/* @__PURE__ */ jsx("div", { className: "cv-export__scrim", onClick: () => setOpen(false) }),
|
|
2448
4813
|
/* @__PURE__ */ jsxs("div", { className: "cv-export__menu", role: "menu", children: [
|
|
2449
|
-
/* @__PURE__ */ jsx("button", { role: "menuitem", onClick: openInTab, children: "
|
|
2450
|
-
/* @__PURE__ */ jsx("button", { role: "menuitem", onClick: copyHtml, children: copied ? "
|
|
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") }),
|
|
2451
4816
|
/* @__PURE__ */ jsxs("button", { role: "menuitem", onClick: exportHtml, children: [
|
|
2452
4817
|
"HTML ",
|
|
2453
4818
|
/* @__PURE__ */ jsx("span", { className: "cv-export__ext", children: ".html" })
|
|
@@ -2536,17 +4901,17 @@ var GRADIENTS = [
|
|
|
2536
4901
|
function StylePanel({ selection }) {
|
|
2537
4902
|
const send = useCanvasStore((s) => s.sendIframeCommand);
|
|
2538
4903
|
const setSelections = useCanvasStore((s) => s.setSelections);
|
|
2539
|
-
const
|
|
2540
|
-
const [color, setColor] = useState(toHex2(
|
|
2541
|
-
const [background, setBackground] = useState(toHex2(
|
|
2542
|
-
const [fontSize, setFontSize] = useState(px(
|
|
2543
|
-
const [fontWeight, setFontWeight] = useState(String(
|
|
2544
|
-
const [textAlign, setTextAlign] = useState(
|
|
2545
|
-
const [lineHeight, setLineHeight] = useState(px(
|
|
2546
|
-
const [letterSpacing, setLetterSpacing] = useState(px(
|
|
2547
|
-
const [padding, setPadding] = useState(px(
|
|
2548
|
-
const [radius, setRadius] = useState(px(
|
|
2549
|
-
const [width, setWidth] = useState(px(
|
|
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));
|
|
2550
4915
|
const bgFileRef = useRef(null);
|
|
2551
4916
|
const dirty = useRef(false);
|
|
2552
4917
|
const setStyle = (prop, value) => {
|
|
@@ -2788,10 +5153,17 @@ function useCanvasImport() {
|
|
|
2788
5153
|
return { importFiles, canImport };
|
|
2789
5154
|
}
|
|
2790
5155
|
var ACCEPT = IMPORTABLE_EXTENSIONS.join(",");
|
|
2791
|
-
function Canvas({ registry = builtinRenderers, emptyState, onEditElement, onUserEdit }) {
|
|
2792
|
-
return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(CanvasPanel, { emptyState, onEditElement, onUserEdit }) });
|
|
5156
|
+
function Canvas({ registry = builtinRenderers, locale, emptyState, onEditElement, onUserEdit, onSave }) {
|
|
5157
|
+
return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(CanvasLocaleProvider, { locale, children: /* @__PURE__ */ jsx(CanvasPanel, { emptyState, onEditElement, onUserEdit, onSave }) }) });
|
|
2793
5158
|
}
|
|
2794
|
-
function CanvasPanel({
|
|
5159
|
+
function CanvasPanel({
|
|
5160
|
+
emptyState,
|
|
5161
|
+
onEditElement,
|
|
5162
|
+
onUserEdit,
|
|
5163
|
+
onSave
|
|
5164
|
+
}) {
|
|
5165
|
+
const t = useT();
|
|
5166
|
+
const debouncedSave = useCanvasSave(onSave);
|
|
2795
5167
|
const { artifacts, order, activeId } = useCanvasStore((s) => s.canvas);
|
|
2796
5168
|
const history = useCanvasStore((s) => s.canvas.history);
|
|
2797
5169
|
const setActive = useCanvasStore((s) => s.setActiveArtifact);
|
|
@@ -2801,9 +5173,16 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
|
|
|
2801
5173
|
const { importFiles } = useCanvasImport();
|
|
2802
5174
|
const [dropping, setDropping] = useState(false);
|
|
2803
5175
|
useEffect(() => {
|
|
2804
|
-
|
|
5176
|
+
if (!onUserEdit && !debouncedSave) {
|
|
5177
|
+
setOnUserEdit(null);
|
|
5178
|
+
return;
|
|
5179
|
+
}
|
|
5180
|
+
setOnUserEdit((artifact2) => {
|
|
5181
|
+
onUserEdit?.(artifact2);
|
|
5182
|
+
debouncedSave?.(artifact2);
|
|
5183
|
+
});
|
|
2805
5184
|
return () => setOnUserEdit(null);
|
|
2806
|
-
}, [onUserEdit, setOnUserEdit]);
|
|
5185
|
+
}, [onUserEdit, debouncedSave, setOnUserEdit]);
|
|
2807
5186
|
useEffect(() => {
|
|
2808
5187
|
if (!selections.length) return;
|
|
2809
5188
|
const onKey = (e) => {
|
|
@@ -2829,14 +5208,14 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
|
|
|
2829
5208
|
if (e.dataTransfer.files.length) void importFiles(e.dataTransfer.files);
|
|
2830
5209
|
}
|
|
2831
5210
|
};
|
|
2832
|
-
const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: "
|
|
5211
|
+
const dropOverlay = dropping ? /* @__PURE__ */ jsx("div", { className: "cv-canvas__drop", children: t("dropToOpen") }) : null;
|
|
2833
5212
|
if (!active) {
|
|
2834
5213
|
return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas cv-canvas--empty", ...dropProps, children: [
|
|
2835
5214
|
emptyState ?? /* @__PURE__ */ jsx(EmptyState, { onOpenFiles: importFiles }),
|
|
2836
5215
|
dropOverlay
|
|
2837
5216
|
] });
|
|
2838
5217
|
}
|
|
2839
|
-
const
|
|
5218
|
+
const versions2 = history[active.id] ?? [active];
|
|
2840
5219
|
const showSelection = Boolean(onEditElement) && selections.length > 0 && selections[0].artifactId === active.id;
|
|
2841
5220
|
return /* @__PURE__ */ jsxs("aside", { className: "cv-canvas", ...dropProps, children: [
|
|
2842
5221
|
dropOverlay,
|
|
@@ -2851,17 +5230,19 @@ function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
|
|
|
2851
5230
|
},
|
|
2852
5231
|
id
|
|
2853
5232
|
)) }),
|
|
2854
|
-
/* @__PURE__ */ jsx(ArtifactView, { artifact: active, versions }, active.id),
|
|
5233
|
+
/* @__PURE__ */ jsx(ArtifactView, { artifact: active, versions: versions2 }, active.id),
|
|
2855
5234
|
showSelection && onEditElement && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2856
5235
|
active.type === "html" && selections.length === 1 && /* @__PURE__ */ jsx(StylePanel, { selection: selections[0] }, selections[0].cid),
|
|
2857
5236
|
/* @__PURE__ */ jsx(SelectionBar, { selections, onEdit: onEditElement, onClear: () => setSelections([]) })
|
|
2858
5237
|
] })
|
|
2859
5238
|
] });
|
|
2860
5239
|
}
|
|
2861
|
-
function ArtifactView({ artifact: artifact2, versions }) {
|
|
5240
|
+
function ArtifactView({ artifact: artifact2, versions: versions2 }) {
|
|
5241
|
+
const t = useT();
|
|
2862
5242
|
const [viewIndex, setViewIndex] = useState(null);
|
|
2863
5243
|
const bodyRef = useRef(null);
|
|
2864
|
-
const shown = viewIndex === null ? artifact2 :
|
|
5244
|
+
const shown = viewIndex === null ? artifact2 : versions2[Math.min(viewIndex, versions2.length - 1)] ?? artifact2;
|
|
5245
|
+
const viewingHistory = viewIndex !== null && shown !== artifact2;
|
|
2865
5246
|
const Renderer = useRenderer(shown.type);
|
|
2866
5247
|
const getRenderedHtml = () => {
|
|
2867
5248
|
const node = bodyRef.current;
|
|
@@ -2879,25 +5260,45 @@ function ArtifactView({ artifact: artifact2, versions }) {
|
|
|
2879
5260
|
] }),
|
|
2880
5261
|
/* @__PURE__ */ jsxs("div", { className: "cv-header__actions", children: [
|
|
2881
5262
|
/* @__PURE__ */ jsx(UndoRedo, {}),
|
|
2882
|
-
|
|
2883
|
-
|
|
5263
|
+
versions2.length > 1 && /* @__PURE__ */ jsx(
|
|
5264
|
+
VersionHistory,
|
|
2884
5265
|
{
|
|
2885
|
-
|
|
2886
|
-
index: viewIndex ??
|
|
2887
|
-
onSelect: (i) => setViewIndex(i ===
|
|
5266
|
+
versions: versions2,
|
|
5267
|
+
index: viewIndex ?? versions2.length - 1,
|
|
5268
|
+
onSelect: (i) => setViewIndex(i === versions2.length - 1 ? null : i)
|
|
2888
5269
|
}
|
|
2889
5270
|
),
|
|
2890
5271
|
/* @__PURE__ */ jsx(ExportMenu, { artifact: shown, getRenderedHtml })
|
|
2891
5272
|
] })
|
|
2892
5273
|
] }),
|
|
2893
|
-
|
|
2894
|
-
"
|
|
2895
|
-
|
|
2896
|
-
"
|
|
2897
|
-
|
|
5274
|
+
viewingHistory && /* @__PURE__ */ jsxs("div", { className: "cv-history-banner", role: "status", children: [
|
|
5275
|
+
"v",
|
|
5276
|
+
Math.min((viewIndex ?? 0) + 1, versions2.length),
|
|
5277
|
+
" / ",
|
|
5278
|
+
versions2.length,
|
|
5279
|
+
" \xB7 ",
|
|
5280
|
+
t("historyNote"),
|
|
5281
|
+
" ",
|
|
5282
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setViewIndex(null), children: t("backToLatest") })
|
|
5283
|
+
] }),
|
|
5284
|
+
/* @__PURE__ */ jsx(
|
|
5285
|
+
"div",
|
|
5286
|
+
{
|
|
5287
|
+
className: `cv-body${shown.type === "table" || shown.type === "pdf" ? " cv-body--flush" : ""}${viewingHistory ? " cv-body--history" : ""}`,
|
|
5288
|
+
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",
|
|
5293
|
+
shown.type,
|
|
5294
|
+
"\u201D."
|
|
5295
|
+
] })
|
|
5296
|
+
}
|
|
5297
|
+
)
|
|
2898
5298
|
] });
|
|
2899
5299
|
}
|
|
2900
5300
|
function UndoRedo() {
|
|
5301
|
+
const t = useT();
|
|
2901
5302
|
const undo = useCanvasStore((s) => s.undo);
|
|
2902
5303
|
const redo = useCanvasStore((s) => s.redo);
|
|
2903
5304
|
const canUndo = useCanvasStore((s) => s.undoStack.length > 0);
|
|
@@ -2917,29 +5318,65 @@ function UndoRedo() {
|
|
|
2917
5318
|
return () => window.removeEventListener("keydown", onKey);
|
|
2918
5319
|
}, [undo, redo]);
|
|
2919
5320
|
return /* @__PURE__ */ jsxs("div", { className: "cv-undo", role: "group", "aria-label": "Undo and redo", children: [
|
|
2920
|
-
/* @__PURE__ */ jsx("button", { onClick: undo, disabled: !canUndo, title: "
|
|
2921
|
-
/* @__PURE__ */ jsx("button", { onClick: redo, disabled: !canRedo, title: "
|
|
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" })
|
|
2922
5323
|
] });
|
|
2923
5324
|
}
|
|
2924
|
-
function
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
5325
|
+
function VersionHistory({
|
|
5326
|
+
versions: versions2,
|
|
5327
|
+
index,
|
|
5328
|
+
onSelect
|
|
5329
|
+
}) {
|
|
5330
|
+
const t = useT();
|
|
5331
|
+
const [open, setOpen] = useState(false);
|
|
5332
|
+
const total = versions2.length;
|
|
5333
|
+
const pick = (i) => {
|
|
5334
|
+
setOpen(false);
|
|
5335
|
+
onSelect(i);
|
|
5336
|
+
};
|
|
5337
|
+
return /* @__PURE__ */ jsxs("div", { className: "cv-versions", role: "group", "aria-label": t("versionHistory"), children: [
|
|
5338
|
+
/* @__PURE__ */ jsx("button", { className: "cv-versions__nav", disabled: index === 0, onClick: () => pick(index - 1), "aria-label": t("prevVersion"), children: "\u2039" }),
|
|
5339
|
+
/* @__PURE__ */ jsxs(
|
|
5340
|
+
"button",
|
|
5341
|
+
{
|
|
5342
|
+
className: "cv-versions__label",
|
|
5343
|
+
"aria-expanded": open,
|
|
5344
|
+
"aria-label": "Open version history",
|
|
5345
|
+
onClick: () => setOpen((v) => !v),
|
|
5346
|
+
children: [
|
|
5347
|
+
"v",
|
|
5348
|
+
index + 1,
|
|
5349
|
+
" / ",
|
|
5350
|
+
total
|
|
5351
|
+
]
|
|
5352
|
+
}
|
|
5353
|
+
),
|
|
2933
5354
|
/* @__PURE__ */ jsx(
|
|
2934
5355
|
"button",
|
|
2935
5356
|
{
|
|
2936
5357
|
className: "cv-versions__nav",
|
|
2937
5358
|
disabled: index === total - 1,
|
|
2938
|
-
onClick: () =>
|
|
2939
|
-
"aria-label": "
|
|
5359
|
+
onClick: () => pick(index + 1),
|
|
5360
|
+
"aria-label": t("nextVersion"),
|
|
2940
5361
|
children: "\u203A"
|
|
2941
5362
|
}
|
|
2942
|
-
)
|
|
5363
|
+
),
|
|
5364
|
+
open && /* @__PURE__ */ jsx("ul", { className: "cv-versions__list", role: "listbox", "aria-label": "Versions", children: versions2.map((snapshot, i) => ({ snapshot, i })).reverse().map(({ snapshot, i }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
5365
|
+
"button",
|
|
5366
|
+
{
|
|
5367
|
+
role: "option",
|
|
5368
|
+
"aria-selected": i === index,
|
|
5369
|
+
className: i === index ? "is-current" : void 0,
|
|
5370
|
+
onClick: () => pick(i),
|
|
5371
|
+
children: [
|
|
5372
|
+
/* @__PURE__ */ jsxs("span", { className: "cv-versions__v", children: [
|
|
5373
|
+
"v",
|
|
5374
|
+
i + 1
|
|
5375
|
+
] }),
|
|
5376
|
+
/* @__PURE__ */ jsx("span", { className: "cv-versions__desc", children: typeof snapshot.meta?.commitDescription === "string" ? snapshot.meta.commitDescription : "Snapshot" })
|
|
5377
|
+
]
|
|
5378
|
+
}
|
|
5379
|
+
) }, i)) })
|
|
2943
5380
|
] });
|
|
2944
5381
|
}
|
|
2945
5382
|
function StatusBadge({ status }) {
|
|
@@ -2972,11 +5409,12 @@ function EmptyState({ onOpenFiles }) {
|
|
|
2972
5409
|
] });
|
|
2973
5410
|
}
|
|
2974
5411
|
var TYPE_META = {
|
|
2975
|
-
html: { icon: "\u{1F310}", label: "
|
|
2976
|
-
document: { icon: "\u{1F4C4}", label: "
|
|
2977
|
-
chart: { icon: "\u{1F4CA}", label: "
|
|
2978
|
-
table: { icon: "\u{1F522}", label: "
|
|
2979
|
-
slides: { icon: "\u{1F4FD}\uFE0F", label: "
|
|
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" }
|
|
2980
5418
|
};
|
|
2981
5419
|
var KIND_META = {
|
|
2982
5420
|
web: TYPE_META.html,
|
|
@@ -2987,14 +5425,16 @@ var KIND_META = {
|
|
|
2987
5425
|
table: TYPE_META.table,
|
|
2988
5426
|
sheet: TYPE_META.table,
|
|
2989
5427
|
slide: TYPE_META.slides,
|
|
2990
|
-
slides: TYPE_META.slides
|
|
5428
|
+
slides: TYPE_META.slides,
|
|
5429
|
+
pdf: TYPE_META.pdf
|
|
2991
5430
|
};
|
|
2992
5431
|
function resolveCardMeta(artifact2) {
|
|
2993
5432
|
const kind = typeof artifact2.meta?.kind === "string" ? artifact2.meta.kind : void 0;
|
|
2994
5433
|
const byKind = kind ? KIND_META[kind] : void 0;
|
|
2995
|
-
return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}"
|
|
5434
|
+
return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}" };
|
|
2996
5435
|
}
|
|
2997
5436
|
function ArtifactCard({ artifactId }) {
|
|
5437
|
+
const t = useT();
|
|
2998
5438
|
const artifact2 = useCanvasStore((s) => s.canvas.artifacts[artifactId]);
|
|
2999
5439
|
const setActive = useCanvasStore((s) => s.setActiveArtifact);
|
|
3000
5440
|
if (!artifact2) return null;
|
|
@@ -3004,12 +5444,12 @@ function ArtifactCard({ artifactId }) {
|
|
|
3004
5444
|
/* @__PURE__ */ jsxs("span", { className: "cv-card__meta", children: [
|
|
3005
5445
|
/* @__PURE__ */ jsx("b", { children: artifact2.title }),
|
|
3006
5446
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
3007
|
-
meta.label,
|
|
5447
|
+
meta.label ? t(meta.label) : artifact2.type,
|
|
3008
5448
|
" \xB7 ",
|
|
3009
|
-
artifact2.status === "streaming" ? "writing
|
|
5449
|
+
artifact2.status === "streaming" ? t("writing") : t("open")
|
|
3010
5450
|
] })
|
|
3011
5451
|
] })
|
|
3012
5452
|
] });
|
|
3013
5453
|
}
|
|
3014
5454
|
|
|
3015
|
-
export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasStream, useRenderer, withInspector };
|
|
5455
|
+
export { ArtifactCard, Canvas, CanvasRegistryProvider, ChartRenderer, DocumentRenderer, ExportMenu, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, PdfRenderer, STYLE_PROPS, SelectionBar, SlidesRenderer, StylePanel, TableRenderer, builtinRenderers, canImport, dataExporters, downloadBlob, importFile, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useCanvasImport, useCanvasReplay, useCanvasSave, useCanvasStream, useRenderer, withInspector };
|