30 lines
3.9 KiB
TypeScript
30 lines
3.9 KiB
TypeScript
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>;
|
||
}
|