import React from 'react'; import type {CSSProperties} from 'react'; import layouts from './manifest.json'; export const layoutIds = ['ref-01','ref-02','ref-03','ref-04','ref-05','ref-06','ref-07','ref-08','ref-09','ref-10','ref-11','ref-12','ref-13','ref-14','ref-15','ref-16','ref-17','ref-18','ref-19','ref-20','ref-21','ref-22','ref-23','ref-24','ref-25','ref-26','ref-27'] as const; export type LayoutId=typeof layoutIds[number]; export type Replacements={text?:Record;images?:Record}; export function getLayout(id:string){return layouts.find(layout=>layout.id===id)} export function validateReplacements(id:string,replacements:Replacements){ const layout=getLayout(id);if(!layout)throw Error(`Unknown layout ID: ${id}`); for(const[kind,values]of [['text',replacements.text],['image',replacements.images]] as const)for(const key of Object.keys(values||{})){ if(!layout.elements.some(e=>key===id+'/'+e.id&&e.kind===kind))throw Error(`Component does not belong to ${id} or has the wrong kind: ${key}`); }return layout; } function textStyle(text:string,box:number[],width:number,direction:string):CSSProperties{ const vertical=direction==='vertical'||direction==='rotated',lines=text.split('\n'),units=Math.max(...lines.map(line=>Array.from(line).reduce((n,c)=>n+(/[\u3400-\u9fff]/.test(c)?1:.56),0)),1); const size=vertical?Math.min(box[2],box[3]/Math.max(text.length,1)):Math.min(box[3]/(lines.length*1.1),box[2]/units); return {fontSize:`${size/width*100}cqw`,lineHeight:1.1,whiteSpace:'pre-wrap',writingMode:vertical?'vertical-rl':undefined,textOrientation:direction==='rotated'?'sideways':undefined,transform:direction==='angled'?'rotate(-12deg)':undefined,transformOrigin:'left center',color:'#222',fontFamily:'"Songti SC", "Noto Serif CJK SC", serif'}; } /** Untouched mode reconstructs typed source layers; replacement mode keeps the exact preset geometry. */ export default function ReferenceLayout({id,text={},images={},assetBase=''}:{id:LayoutId;text?:Record;images?:Record;assetBase?:string}){ const layout=validateReplacements(id,{text,images}),src=(url:string)=>assetBase+url; return
{layout.elements.map(e=>{ const[x,y,w,h]=e.box,key=id+'/'+e.id,position:CSSProperties={position:'absolute',left:x/layout.width*100+'%',top:y/layout.height*100+'%',width:w/layout.width*100+'%',height:h/layout.height*100+'%',zIndex:e.kind==='text'?3:e.kind==='decoration'?2:1}; if(e.kind==='text'&&Object.hasOwn(text,key)){ if(e.direction==='curved')throw Error(`Curved text requires the source-specific SVG path: ${key}`); return {'underlay' in e&&} {text[key]} ; } if(e.kind==='image'&&Object.hasOwn(images,key))return {e.role}; return {e.kind==='text'?e.text||`${e.role}:原字形保真,内容未辨识`:''}; })}
; }