Files
Pier_v1/도면작성/svg.ts
T
2026-11-04 13:34:51 +09:00

44 lines
3.8 KiB
TypeScript

import { referenceLayer, REFERENCE_ACI, aciPaper } from './cad-style';
import { LAYERS, type DrawingDocument, type Entity, type Layer, type ViewId } from './types';
import { sheetFrame, viewPaperEntities } from './sheet';
import { entityBox } from './occupancy';
export { paperPoint } from './presentation';
export const escapeXml=(value:string):string=>value.replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&apos;'}[c]!));
function renderEntity(e:Entity,height:number):string {
const coord=(p:readonly number[])=>`${p[0].toFixed(4)},${(height-p[1]).toFixed(4)}`;
if(e.kind==='text'){
const [x,y]=[e.point[0],height-e.point[1]];
return `<text x="${x}" y="${y}" stroke="none" fill="${aciPaper(e.cad?.color??REFERENCE_ACI[referenceLayer(e)]??7)}" font-size="${e.height}" text-anchor="${e.align==='center'?'middle':e.align==='right'?'end':'start'}"${e.rotation?` transform="rotate(${-e.rotation} ${x} ${y})"`:''}>${escapeXml(e.text)}</text>`;
}
if(e.kind==='circle')return `<circle cx="${e.center[0]}" cy="${height-e.center[1]}" r="${e.radius}"/>`;
if(e.kind==='line')return `<polyline points="${coord(e.a)} ${coord(e.b)}"/>`;
if(e.kind==='polyline')return `<${e.closed?'polygon':'polyline'} points="${e.points.map(coord).join(' ')}"/>`;
if(e.kind==='hatch'){
const fill=e.color&&/^#[0-9a-f]{6}$/i.test(e.color)?e.color:'#cbd1d6';
const d=[e.points,...e.holes??[]].map(loop=>`M${loop.map(coord).join('L')}Z`).join('');
if(e.pattern==='SOLID')return `<path d="${d}" fill-rule="evenodd" fill="${fill}" stroke="none"/>`;
const id=`hz-${Math.abs(Math.round((e.points[0]?.[0]??0)*10000))}-${Math.abs(Math.round((e.points[0]?.[1]??0)*10000))}-${e.source.member}-${e.source.index}`.replace(/[^a-z0-9-]/gi,'');
const spacing=Math.max(.1,e.spacing);
return `<defs><pattern id="${id}" width="${spacing}" height="${spacing}" patternUnits="userSpaceOnUse" patternTransform="rotate(${-e.angle})"><path d="M0 0H${spacing}" stroke="${fill}" stroke-width=".13"/></pattern></defs><path d="${d}" fill-rule="evenodd" fill="url(#${id})" stroke="none"/>`;
}
throw new Error('Expand CAD dimensions before paper rendering');
}
export function drawingSvg(doc:DrawingDocument,visible:ReadonlySet<Layer>,interactive=false,selected?:ViewId):string {
const {width,height}=doc.paper;
const render=(e:Entity,view:string,index:number)=>{
if(!visible.has(e.layer))return'';
const style={...LAYERS[e.layer],color:aciPaper(e.cad?.color??REFERENCE_ACI[referenceLayer(e)]??7)};
const issue=interactive&&doc.quality?.issues.some(i=>i.view===view&&i.first===index),b=issue?entityBox(e):undefined;
const marker=b?`<rect class="ga-issue-box" x="${b.minX}" y="${height-b.maxY}" width="${b.maxX-b.minX}" height="${b.maxY-b.minY}" fill="#ffeded88" stroke="#d52d2d" stroke-width=".2"/>`:'';
return `<g fill="none" stroke="${style.color}" stroke-width="${style.weight}"${style.dash?` stroke-dasharray="${style.dash}"`:''}${interactive&&index>=0?` data-entity="${view}:${index}"`:''}><title>${escapeXml(`${e.source.pier} / ${e.source.member} ${e.source.index+1} / ${e.layer}`)}</title>${marker}${renderEntity(e,height)}</g>`;
};
let body=`<rect width="${width}" height="${height}" fill="white"/>`;
if(!selected)body+=sheetFrame(doc).map(e=>render(e,'sheet',-1)).join('');
for(const view of doc.views){
if(selected?view.id!==selected:['section','heights'].includes(view.id))continue;
body+=`<g data-view="${view.id}" aria-label="${escapeXml(view.title)}">${viewPaperEntities(doc,view).map(({entity,index})=>render(entity,view.id,index)).join('')}</g>`;
}
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}mm" height="${height}mm" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(doc.pier)} 교각 일반도" style="font-family:'Gulim','굴림','Malgun Gothic','Noto Sans KR',sans-serif">${body}</svg>`;
}