@braincrew-lab/langchain-canvas 0.7.5 → 0.7.7
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.
|
@@ -141,7 +141,222 @@ function docxStats(root) {
|
|
|
141
141
|
substitutedFonts: substitutedFonts(root)
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
+
|
|
145
|
+
// src/io/docxShapes.ts
|
|
146
|
+
var VML_NS = "urn:schemas-microsoft-com:vml";
|
|
147
|
+
var VML_SHAPES = /* @__PURE__ */ new Set([
|
|
148
|
+
"oval",
|
|
149
|
+
"rect",
|
|
150
|
+
"roundrect",
|
|
151
|
+
"line",
|
|
152
|
+
"polyline",
|
|
153
|
+
"arc",
|
|
154
|
+
"curve",
|
|
155
|
+
"shape",
|
|
156
|
+
"group"
|
|
157
|
+
]);
|
|
158
|
+
var PAINTABLE = "ellipse,rect,circle,path,line,polygon,polyline,image,foreignObject";
|
|
159
|
+
function isVml(node) {
|
|
160
|
+
return node.namespaceURI === VML_NS || node.tagName.startsWith("v:");
|
|
161
|
+
}
|
|
162
|
+
function alphaOf(paint) {
|
|
163
|
+
const inside = /^rgba?\(([^)]*)\)$/.exec(paint);
|
|
164
|
+
if (!inside) return null;
|
|
165
|
+
const [channels, slashAlpha] = inside[1].split("/");
|
|
166
|
+
const raw = slashAlpha !== void 0 ? slashAlpha : (() => {
|
|
167
|
+
const parts = channels.split(",").map((part) => part.trim());
|
|
168
|
+
return parts.length === 4 ? parts[3] : void 0;
|
|
169
|
+
})();
|
|
170
|
+
if (raw === void 0) return null;
|
|
171
|
+
const value = raw.trim();
|
|
172
|
+
const number = value.endsWith("%") ? Number(value.slice(0, -1)) / 100 : Number(value);
|
|
173
|
+
return Number.isFinite(number) ? number : null;
|
|
174
|
+
}
|
|
175
|
+
function invisible(paint) {
|
|
176
|
+
const value = paint.trim();
|
|
177
|
+
if (!value || value === "none" || value === "transparent") return true;
|
|
178
|
+
return alphaOf(value) === 0;
|
|
179
|
+
}
|
|
180
|
+
function paints(element) {
|
|
181
|
+
const tag = element.tagName.toLowerCase();
|
|
182
|
+
if (tag === "image" || tag === "foreignobject") return true;
|
|
183
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
184
|
+
const resolved = (name) => style?.getPropertyValue(name) || element.getAttribute(name) || "";
|
|
185
|
+
return !invisible(resolved("fill")) || !invisible(resolved("stroke"));
|
|
186
|
+
}
|
|
187
|
+
function drawsNothing(shape) {
|
|
188
|
+
return shape.getAttribute("filled") === "f" && shape.getAttribute("stroked") === "f";
|
|
189
|
+
}
|
|
190
|
+
function vmlShapeIn(node) {
|
|
191
|
+
if (isVml(node) && VML_SHAPES.has(node.localName)) return node;
|
|
192
|
+
for (const child of Array.from(node.children)) {
|
|
193
|
+
const found = vmlShapeIn(child);
|
|
194
|
+
if (found) return found;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
function drawingIsShape(drawing) {
|
|
199
|
+
const data = drawing.getElementsByTagName("*");
|
|
200
|
+
for (const node of Array.from(data)) {
|
|
201
|
+
if (node.localName === "graphicData") {
|
|
202
|
+
return !Array.from(node.children).some((child) => child.localName === "pic");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
function insideAlternateContent(node) {
|
|
208
|
+
let parent = node.parentElement;
|
|
209
|
+
while (parent) {
|
|
210
|
+
if (parent.localName === "AlternateContent") return true;
|
|
211
|
+
parent = parent.parentElement;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
function shapesAskedFor(xml) {
|
|
216
|
+
let count = 0;
|
|
217
|
+
const seenTwins = /* @__PURE__ */ new Set();
|
|
218
|
+
for (const node of Array.from(xml.getElementsByTagName("*"))) {
|
|
219
|
+
if (node.localName !== "AlternateContent") continue;
|
|
220
|
+
const twin = vmlShapeIn(node);
|
|
221
|
+
const drawing = Array.from(node.getElementsByTagName("*")).find(
|
|
222
|
+
(child) => child.localName === "drawing"
|
|
223
|
+
);
|
|
224
|
+
const isShape = twin !== null || (drawing ? drawingIsShape(drawing) : false);
|
|
225
|
+
if (isShape && !(twin && drawsNothing(twin))) count += 1;
|
|
226
|
+
seenTwins.add(node);
|
|
227
|
+
}
|
|
228
|
+
for (const node of Array.from(xml.getElementsByTagName("*"))) {
|
|
229
|
+
if (insideAlternateContent(node)) continue;
|
|
230
|
+
if (isVml(node) && VML_SHAPES.has(node.localName)) {
|
|
231
|
+
if (node.parentElement && vmlShapeIn(node.parentElement) === node.parentElement) continue;
|
|
232
|
+
if (!drawsNothing(node)) count += 1;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (node.localName === "drawing" && drawingIsShape(node)) count += 1;
|
|
236
|
+
}
|
|
237
|
+
return count;
|
|
238
|
+
}
|
|
239
|
+
function shapesShown(root) {
|
|
240
|
+
let count = 0;
|
|
241
|
+
for (const svg of Array.from(root.querySelectorAll("svg"))) {
|
|
242
|
+
if (Array.from(svg.querySelectorAll(PAINTABLE)).some(paints)) count += 1;
|
|
243
|
+
}
|
|
244
|
+
return count;
|
|
245
|
+
}
|
|
246
|
+
function tallyShapes(root, xmls) {
|
|
247
|
+
if (xmls.length === 0) return { askedFor: 0, shown: 0, missing: 0 };
|
|
248
|
+
const askedFor = xmls.reduce((total, xml) => total + shapesAskedFor(xml), 0);
|
|
249
|
+
const shown = shapesShown(root);
|
|
250
|
+
return { askedFor, shown, missing: Math.max(0, askedFor - shown) };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/io/symbolBullets.ts
|
|
254
|
+
var SYMBOL_FONT_BULLETS = {
|
|
255
|
+
symbol: {
|
|
256
|
+
61623: "\u2022"
|
|
257
|
+
// • bullet — Word's default list bullet
|
|
258
|
+
},
|
|
259
|
+
wingdings: {
|
|
260
|
+
61548: "\u25CF",
|
|
261
|
+
// ● black circle
|
|
262
|
+
61549: "\u274D",
|
|
263
|
+
// ❍ shadowed white circle
|
|
264
|
+
61550: "\u25A0",
|
|
265
|
+
// ■ black square
|
|
266
|
+
61551: "\u25A1",
|
|
267
|
+
// □ white square
|
|
268
|
+
61553: "\u2751",
|
|
269
|
+
// ❑ lower-right shadowed white square
|
|
270
|
+
61557: "\u25C6",
|
|
271
|
+
// ◆ black diamond
|
|
272
|
+
61607: "\u25AA",
|
|
273
|
+
// ▪ black small square
|
|
274
|
+
61656: "\u27A2",
|
|
275
|
+
// ➢ three-d top-lighted right arrowhead
|
|
276
|
+
61692: "\u2714",
|
|
277
|
+
// ✔ heavy check mark
|
|
278
|
+
61694: "\u2611"
|
|
279
|
+
// ☑ ballot box with check
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
function isPrivateUse(codePoint) {
|
|
283
|
+
return codePoint >= 57344 && codePoint <= 63743;
|
|
284
|
+
}
|
|
285
|
+
function primaryFont(fontFamily) {
|
|
286
|
+
const first = fontFamily.split(",")[0] ?? "";
|
|
287
|
+
return first.trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
288
|
+
}
|
|
289
|
+
function standardBullet(codePoint, fontFamily) {
|
|
290
|
+
const table = SYMBOL_FONT_BULLETS[primaryFont(fontFamily)];
|
|
291
|
+
return table?.[codePoint] ?? null;
|
|
292
|
+
}
|
|
293
|
+
function drawnOnPage(root, selectorText) {
|
|
294
|
+
return selectorText.split(",").some((part) => {
|
|
295
|
+
const base = part.trim().replace(/::?(before|after)\s*$/i, "").trim();
|
|
296
|
+
if (!base) return false;
|
|
297
|
+
try {
|
|
298
|
+
return root.querySelector(base) !== null;
|
|
299
|
+
} catch {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function styleRules(sheet) {
|
|
305
|
+
const found = [];
|
|
306
|
+
const stack = [...sheet.cssRules];
|
|
307
|
+
while (stack.length) {
|
|
308
|
+
const rule = stack.pop();
|
|
309
|
+
if (rule.cssRules?.length) {
|
|
310
|
+
stack.push(...rule.cssRules);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (rule.style && rule.selectorText) found.push(rule);
|
|
314
|
+
}
|
|
315
|
+
return found;
|
|
316
|
+
}
|
|
317
|
+
function restoreSymbolBullets(root) {
|
|
318
|
+
const swaps = [];
|
|
319
|
+
for (const style of Array.from(root.querySelectorAll("style"))) {
|
|
320
|
+
let rules;
|
|
321
|
+
try {
|
|
322
|
+
rules = style.sheet ? styleRules(style.sheet) : [];
|
|
323
|
+
} catch {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
for (const rule of rules) {
|
|
327
|
+
const content = rule.style.getPropertyValue("content");
|
|
328
|
+
if (!content) continue;
|
|
329
|
+
const font = rule.style.getPropertyValue("font-family");
|
|
330
|
+
if (!font) continue;
|
|
331
|
+
if (!drawnOnPage(root, rule.selectorText)) continue;
|
|
332
|
+
let next = "";
|
|
333
|
+
let changed = false;
|
|
334
|
+
for (const character of content) {
|
|
335
|
+
const code = character.codePointAt(0) ?? 0;
|
|
336
|
+
const standard = isPrivateUse(code) ? standardBullet(code, font) : null;
|
|
337
|
+
if (standard === null) {
|
|
338
|
+
next += character;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
next += standard;
|
|
342
|
+
changed = true;
|
|
343
|
+
swaps.push({ from: character, to: standard, font: primaryFont(font) });
|
|
344
|
+
}
|
|
345
|
+
if (changed) rule.style.setProperty("content", next);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return swaps;
|
|
349
|
+
}
|
|
350
|
+
function redrawnFonts(swaps) {
|
|
351
|
+
return [...new Set(swaps.map((swap) => swap.font))];
|
|
352
|
+
}
|
|
144
353
|
var BANNER = "Preview only \u2014 to change it, ask in chat or select some text.";
|
|
354
|
+
var CONTENT_PART = /^word\/(document|header\d*|footer\d*)\.xml$/;
|
|
355
|
+
function storedParts(parsed) {
|
|
356
|
+
const parts = parsed?.parts;
|
|
357
|
+
if (!Array.isArray(parts)) return [];
|
|
358
|
+
return parts.filter((part) => typeof part?.path === "string" && CONTENT_PART.test(part.path)).map((part) => part._xmlDocument).filter((xml) => Boolean(xml));
|
|
359
|
+
}
|
|
145
360
|
function DocxPreview({
|
|
146
361
|
artifactId,
|
|
147
362
|
href,
|
|
@@ -151,6 +366,8 @@ function DocxPreview({
|
|
|
151
366
|
const hostRef = useRef(null);
|
|
152
367
|
const [status, setStatus] = useState("loading");
|
|
153
368
|
const [stats, setStats] = useState(null);
|
|
369
|
+
const [redrawn, setRedrawn] = useState([]);
|
|
370
|
+
const [missingShapes, setMissingShapes] = useState(0);
|
|
154
371
|
const [picked, setPicked] = useState(null);
|
|
155
372
|
const setSelections = useCanvasStore((s) => s.setSelections);
|
|
156
373
|
useEffect(() => {
|
|
@@ -160,6 +377,8 @@ function DocxPreview({
|
|
|
160
377
|
if (!host) return;
|
|
161
378
|
setStatus("loading");
|
|
162
379
|
setStats(null);
|
|
380
|
+
setRedrawn([]);
|
|
381
|
+
setMissingShapes(0);
|
|
163
382
|
setPicked(null);
|
|
164
383
|
(async () => {
|
|
165
384
|
const { renderAsync } = await loadOptional(
|
|
@@ -172,15 +391,18 @@ function DocxPreview({
|
|
|
172
391
|
const data = await response.arrayBuffer();
|
|
173
392
|
if (!live) return;
|
|
174
393
|
host.replaceChildren();
|
|
175
|
-
await renderAsync(data, host, void 0, {
|
|
394
|
+
const parsed = await renderAsync(data, host, void 0, {
|
|
176
395
|
inWrapper: true,
|
|
177
396
|
breakPages: true,
|
|
178
397
|
renderHeaders: true,
|
|
179
398
|
renderFooters: true,
|
|
180
|
-
useBase64URL: true
|
|
399
|
+
useBase64URL: true,
|
|
400
|
+
keepOrigin: true
|
|
181
401
|
});
|
|
182
402
|
if (!live) return;
|
|
183
403
|
stampDocxAddresses(host);
|
|
404
|
+
setRedrawn(redrawnFonts(restoreSymbolBullets(host)));
|
|
405
|
+
setMissingShapes(tallyShapes(host, storedParts(parsed)).missing);
|
|
184
406
|
setStats(docxStats(host));
|
|
185
407
|
setStatus("ready");
|
|
186
408
|
let fittedFor = host.clientWidth;
|
|
@@ -262,7 +484,32 @@ function DocxPreview({
|
|
|
262
484
|
stats.substitutedFonts.length > 0 && /* @__PURE__ */ jsxs("span", { className: "cv-docx__fonts", children: [
|
|
263
485
|
"substituted: ",
|
|
264
486
|
stats.substitutedFonts.join(", ")
|
|
265
|
-
] })
|
|
487
|
+
] }),
|
|
488
|
+
missingShapes > 0 && /* @__PURE__ */ jsxs(
|
|
489
|
+
"span",
|
|
490
|
+
{
|
|
491
|
+
className: "cv-docx__missing",
|
|
492
|
+
title: "This document draws shapes the preview cannot show. They are in the file \u2014 a download opens with them in place.",
|
|
493
|
+
children: [
|
|
494
|
+
missingShapes,
|
|
495
|
+
" shape",
|
|
496
|
+
missingShapes === 1 ? "" : "s",
|
|
497
|
+
" not shown \u2014 download the file to see ",
|
|
498
|
+
missingShapes === 1 ? "it" : "them"
|
|
499
|
+
]
|
|
500
|
+
}
|
|
501
|
+
),
|
|
502
|
+
redrawn.length > 0 && /* @__PURE__ */ jsxs(
|
|
503
|
+
"span",
|
|
504
|
+
{
|
|
505
|
+
className: "cv-docx__redrawn",
|
|
506
|
+
title: "This document writes its list bullets as characters in a symbol font's own private area, which no other font can draw. They are shown here as the standard characters that mean the same mark; the stored file is unchanged.",
|
|
507
|
+
children: [
|
|
508
|
+
"bullets redrawn: ",
|
|
509
|
+
redrawn.join(", ")
|
|
510
|
+
]
|
|
511
|
+
}
|
|
512
|
+
)
|
|
266
513
|
]
|
|
267
514
|
}
|
|
268
515
|
)
|
|
@@ -10,7 +10,7 @@ function formatSize(size) {
|
|
|
10
10
|
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
11
11
|
}
|
|
12
12
|
var DocxPreview = lazy(
|
|
13
|
-
() => import('./DocxPreview-
|
|
13
|
+
() => import('./DocxPreview-TXEOVBVR.js').then((m) => ({ default: m.DocxPreview }))
|
|
14
14
|
);
|
|
15
15
|
function iconFor(mediaType, name) {
|
|
16
16
|
if (mediaType?.startsWith("image/")) return "\u{1F5BC}\uFE0F";
|
package/dist/index.js
CHANGED
|
@@ -2264,7 +2264,7 @@ var ChartRenderer = lazy(() => import('./ChartRenderer-TBTWNPV7.js').then((m) =>
|
|
|
2264
2264
|
var DocumentRenderer = lazy(() => import('./DocumentRenderer-ZGJXDVKG.js').then((m) => ({ default: m.DocumentRenderer })));
|
|
2265
2265
|
var TableRenderer = lazy(() => import('./TableRenderer-YTOF2ZIC.js').then((m) => ({ default: m.TableRenderer })));
|
|
2266
2266
|
var SlidesRenderer = lazy(() => import('./SlidesRenderer-4QWZVWWD.js').then((m) => ({ default: m.SlidesRenderer })));
|
|
2267
|
-
var FileRenderer = lazy(() => import('./FileRenderer-
|
|
2267
|
+
var FileRenderer = lazy(() => import('./FileRenderer-C4IHXUV4.js').then((m) => ({ default: m.FileRenderer })));
|
|
2268
2268
|
var builtinRenderers = {
|
|
2269
2269
|
html: HtmlRenderer,
|
|
2270
2270
|
document: DocumentRenderer,
|
package/dist/styles.css
CHANGED
|
@@ -1309,6 +1309,8 @@
|
|
|
1309
1309
|
}
|
|
1310
1310
|
.cv-docx__picked { color: var(--cv-accent); }
|
|
1311
1311
|
.cv-docx__fonts { color: var(--cv-warn, #b45309); }
|
|
1312
|
+
.cv-docx__redrawn { color: var(--cv-warn, #b45309); }
|
|
1313
|
+
.cv-docx__missing { color: var(--cv-warn, #b45309); }
|
|
1312
1314
|
|
|
1313
1315
|
/* Tablet / narrow desktop: tighten paddings, let toolbars wrap. */
|
|
1314
1316
|
@media (max-width: 900px) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braincrew-lab/langchain-canvas",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"description": "A live canvas for LangChain agents — stream documents, charts, and rich artifacts into a React panel.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Brain Crew (https://github.com/braincrew-lab)",
|