Render curved typography portably in standalone SVG exports
studio CI and deployment / verify-and-deploy (push) Successful in 3m58s
studio CI and deployment / verify-and-deploy (push) Successful in 3m58s
This commit is contained in:
@@ -6,7 +6,7 @@ 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"/>
|
||||
<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>;
|
||||
@@ -17,7 +17,11 @@ export function SourceCover({layout,values,guides=false}:{layout:SourceLayout;va
|
||||
}
|
||||
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')return <g data-slot={s.id} key={s.id}><defs><path id={uid+s.id} d={`M${x} ${y} Q${x+w/2} ${y+h*1.8} ${x+w} ${y}`}/></defs><text fill="#26362f" fontFamily="Arial,sans-serif" fontSize={Math.min(36,w/Math.max(1,text.length*.6))} letterSpacing="1"><textPath href={'#'+uid+s.id} startOffset="50%" textAnchor="middle">{text}</textPath></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>}
|
||||
|
||||
Reference in New Issue
Block a user