返回 DeepSeek-Reasonix
svgDocument.ts
根目录 / desktop / frontend / src / lib / svgDocument.ts
1 // Recognition helpers for model-authored SVG. They are deliberately shallow:
2 // they decide whether a fenced block is worth handing to the host, never
3 // whether the markup is safe. The host's sanitizer owns that verdict.
4
5 /**
6 * True when the text *starts* like a single SVG document. A body that only
7 * looks like one — mixed HTML, several roots, a truncated fragment — passes
8 * here and is still refused by the host's strict parse, which is why this is
9 * used to skip work rather than to authorize a preview.
10 */
11 export function looksLikeSvgDocument(value: string): boolean {
12 const head = value.replace(/^/, "").trimStart();
13 let offset = 0;
14 const skipWhitespace = () => {
15 while (offset < head.length && /\s/u.test(head[offset] ?? "")) offset += 1;
16 };
17 if (head.slice(0, 5).toLowerCase() === "<?xml") {
18 const end = head.indexOf("?>", 5);
19 if (end < 0) return false;
20 offset = end + 2;
21 skipWhitespace();
22 }
23 while (head.startsWith("<!--", offset)) {
24 const end = head.indexOf("-->", offset + 4);
25 if (end < 0) return false;
26 offset = end + 3;
27 skipWhitespace();
28 }
29 return head.slice(offset, offset + 4).toLowerCase() === "<svg"
30 && /[\s/>]/u.test(head[offset + 4] ?? "");
31 }
32
33 /** The picture's own ratio, so the block reserves space before the image loads. */
34 export function svgAspectRatio(svg: string): number | undefined {
35 const root = /<svg[^>]*>/i.exec(svg)?.[0];
36 if (!root) return undefined;
37 const viewBox = /viewBox\s*=\s*"([^"]*)"/i.exec(root)?.[1]?.trim().split(/[\s,]+/).map(Number);
38 if (viewBox && viewBox.length === 4 && viewBox.every(Number.isFinite) && viewBox[2] > 0 && viewBox[3] > 0) {
39 return viewBox[2] / viewBox[3];
40 }
41 const width = unitless(/width\s*=\s*"([^"]*)"/i.exec(root)?.[1]);
42 const height = unitless(/height\s*=\s*"([^"]*)"/i.exec(root)?.[1]);
43 return width && height ? width / height : undefined;
44 }
45
46 function unitless(raw?: string): number | undefined {
47 if (!raw) return undefined;
48 const match = /^\s*([0-9]*\.?[0-9]+)\s*(?:px)?\s*$/i.exec(raw);
49 const value = match ? Number(match[1]) : NaN;
50 return Number.isFinite(value) && value > 0 ? value : undefined;
51 }
52
52 lines TYPESCRIPT