Files
probably-rendering-one-more…/apps/studio/src/components/SourceCover.tsx
T
codex-bot ff6b6d29c2
studio CI and deployment / verify-and-deploy (push) Successful in 3m58s
Render curved typography portably in standalone SVG exports
2026-10-26 21:20:05 +08:00

30 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {useId} from 'react';
import selections from '../content/templates/selection.json';
export type SourceLayout=typeof selections[number];
export type CoverValues={fields:Record<string,string>;color:string;accent:string};
export {selections};
export function SourceCover({layout,values,guides=false}:{layout:SourceLayout;values:CoverValues;guides?:boolean}){
const uid=useId().replace(/:/g,''),W=layout.width*10,H=layout.height*10;
return <svg xmlns="http://www.w3.org/2000/svg" viewBox={`0 0 ${W} ${H}`} width={W} height={H} role="img" aria-label={layout.id+' '+layout.name} data-layout-id={layout.id} style={{display:'block',width:'100%',height:'auto'}}>
<title>{layout.name+' · 封面试版'}</title><desc>文字为可编辑占位,图位和装饰按原图构件重建为矢量,原始分层数据保留在模板 JSON 中。来源 {layout.tutorialUrl}</desc><rect width={W} height={H} fill="#f4f2eb"/>
{['image','decoration','text'].flatMap(kind=>layout.slots.filter(s=>s.kind===kind).map(s=>{
const [x,y,w,h]=s.box.map(n=>n*10);
if(kind==='image')return <g data-slot={s.id} key={s.id}>{layout.id==='ref-07'?<ellipse cx={x+w/2} cy={y+h/2} rx={w/2} ry={h/2} fill={values.color}/>:<rect x={x} y={y} width={w} height={h} fill={s.id==='I01'?values.color:values.accent}/>}</g>;
if(kind==='decoration'){
if(layout.id==='ref-07')return <g data-slot={s.id} key={s.id} fill="none" stroke="#b8c2b8" strokeWidth="9"><defs><clipPath id={uid+s.id}><rect x={x} y={y} width={w} height={h}/></clipPath></defs><g clipPath={'url(#'+uid+s.id+')'}>{[550,595,640,685,730,775,820].map(r=><ellipse key={r} cx="640" cy="560" rx={r} ry={r*1.02}/>)}</g></g>;
if(layout.id==='ref-26'&&s.id==='D04')return <path data-slot={s.id} key={s.id} d={`M${x} ${y+h} C${x+w*.28} ${y+h*.93} ${x+w*.43} ${y+h*.64} ${x+w*.30} ${y+h*.56} C${x+w*.16} ${y+h*.47} ${x+w*.19} ${y+h*.89} ${x+w*.38} ${y+h*.68} S${x+w*.69} ${y+h*.52} ${x+w*.65} ${y+h*.34} C${x+w*.59} ${y+h*.15} ${x+w*.52} ${y+h*.48} ${x+w*.75} ${y+h*.37} S${x+w*.90} ${y+h*.15} ${x+w} ${y}`} fill="none" stroke="#526459" strokeWidth="4"/>;
return <path data-slot={s.id} key={s.id} d={`M${x} ${y+h/2}H${x+w}`} stroke="#526459" strokeWidth={layout.id==='ref-26'&&s.id==='D03'?4:2}/>;
}
const text=values.fields[s.id]||'',lines=text.split('\n').slice(0,4),length=(str:string)=>Array.from(str).reduce((sum,c)=>sum+(/[\u3400-\u9fff]/.test(c)?1:.6),0),size=Math.min(h/(lines.length*1.25),w/Math.max(1,...lines.map(length))*.94);
if(s.direction==='vertical'){const letters=Array.from(text.replace(/\n/g,'')),font=Math.min(w*.88,h/Math.max(1,letters.length));return <g data-slot={s.id} key={s.id} fill="#26362f" fontFamily="'PingFang SC',sans-serif">{letters.map((c,i)=><text key={i} x={x+w/2} y={y+font*(i+.85)} textAnchor="middle" fontSize={font}>{c}</text>)}</g>}
if(s.direction==='curved'){
const letters=Array.from(text),font=Math.min(36,w/Math.max(1,letters.length*.9));
return <g data-slot={s.id} key={s.id} fill="#26362f" fontFamily="Arial,sans-serif">{letters.map((letter,i)=>{const t=.08+.84*(i+.5)/letters.length,px=x+w*t,py=y+3.9*h*t*(1-t),angle=Math.atan(3.9*h*(1-2*t)/w)*180/Math.PI;return <text key={i} transform={`translate(${px} ${py}) rotate(${angle})`} fontSize={font} textAnchor="middle">{letter}</text>})}</g>;
}
return <g data-slot={s.id} key={s.id} fill="#26362f" fontFamily={h>100?"Georgia,'PingFang SC',serif":"Arial,'PingFang SC',sans-serif"}>{lines.map((line,i)=><text key={i} x={x} y={y+size*(.88+i*1.25)} fontSize={size}>{line}</text>)}</g>;
}))}
{guides&&<g aria-hidden="true" fontFamily="Arial,sans-serif">{layout.slots.map(s=>{const[x,y,w,h]=s.box.map(n=>n*10),color=({text:'#2c64d8',image:'#d36b30',decoration:'#08875d'} as Record<string,string>)[s.kind];return <g key={s.id}><rect x={x} y={y} width={w} height={h} stroke={color} strokeWidth="2" fill="none"/><rect x={x} y={y} width="66" height="29" fill={color}/><text x={x+4} y={y+22} fontSize="24" fill="white">{s.id}</text></g>})}</g>}
</svg>;
}