export type GraphicItem = { id:string;kind:'text'|'rect'|'ellipse'|'image';name:string;text:string; x:number;y:number;w:number;h:number;fill:string;size:number;hidden:boolean;locked:boolean;src?:string; }; export type GraphicDocument = {name:string;background:string;items:GraphicItem[]}; const color=(value:unknown)=>typeof value==='string'&&/^#[a-f0-9]{6}$/i.test(value); /** Accept only bounded editable documents; never execute or load external assets. */ export function parseGraphicDocument(raw:string):GraphicDocument { if(raw.length>5*1024*1024)throw Error('设计文件不得超过 5MB'); const d=JSON.parse(raw); if(!d||typeof d.name!=='string'||d.name.length>100||!color(d.background)||!Array.isArray(d.items)||d.items.length>500)throw Error('设计文件格式不正确'); const ids=new Set(); for(const i of d.items){ if(!i||typeof i.id!=='string'||i.id.length>100||ids.has(i.id)||!['text','rect','ellipse','image'].includes(i.kind)||typeof i.name!=='string'||i.name.length>200||typeof i.text!=='string'||i.text.length>4000||!color(i.fill)||typeof i.hidden!=='boolean'||typeof i.locked!=='boolean'||![i.x,i.y,i.w,i.h,i.size].every(Number.isFinite)||Math.abs(i.x)>4000||Math.abs(i.y)>4000||i.w<1||i.h<1||i.w>4000||i.h>4000||i.size<8||i.size>240)throw Error('设计图层格式不正确'); if(i.kind==='image'&&(typeof i.src!=='string'||!/^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/=]+$/.test(i.src)))throw Error('图片必须为嵌入式 PNG、JPEG 或 WebP'); ids.add(i.id); } return {name:d.name,background:d.background,items:d.items.map((i:GraphicItem)=>({id:i.id,kind:i.kind,name:i.name,text:i.text,x:i.x,y:i.y,w:i.w,h:i.h,fill:i.fill,size:i.size,hidden:i.hidden,locked:i.locked,...(i.kind==='image'?{src:i.src}:{})}))}; }