gongxiangp2/lib/detail-generator/layer-rendering.ts

52 lines
4.4 KiB
TypeScript

import type { LabLayer } from '../../prototypes/detail-template-lab/model'
import { imageGeometry } from '../../prototypes/detail-template-lab/model'
/** Shared by the existing Web template lab, desktop preview and native exporter. */
export function detailLayerTextStyles(layer: LabLayer) {
const styles: Record<number, Record<number, object>> = {}
let row = 0, column = 0
for (let index = 0; index < (layer.text || '').length; index++) {
if (layer.text![index] === '\n') { row++; column = 0; continue }
const run = layer.runs?.find(run => index >= run.start && index < run.end)
if (run) (styles[row] ||= {})[column] = { fontFamily: run.font, fontSize: run.size, fill: run.color }
column++
}
return styles
}
export async function createDetailLayerObject(layer: LabLayer, fabric: typeof import('fabric'), resolveSource: (source: string) => Promise<string>, measure: Pick<CanvasRenderingContext2D, 'font' | 'measureText'>) {
const base = { originX: 'left' as const, originY: 'top' as const, left: layer.x, top: layer.y, opacity: layer.opacity ?? 1, objectCaching: false, selectable: true, evented: true, borderColor: '#1677ff', cornerColor: '#1677ff', cornerSize: 12, transparentCorners: false, lockRotation: true, hasControls: false }
let object: import('fabric').FabricObject
if (layer.kind === 'image') {
const image = await fabric.FabricImage.fromURL(await resolveSource(layer.source!), { crossOrigin: 'anonymous' })
const geometry = imageGeometry(image.width!, image.height!, layer.width, layer.height, layer.crop)
image.set({ ...base, width: geometry.width, height: geometry.height, cropX: geometry.cropX, cropY: geometry.cropY, scaleX: geometry.scale, scaleY: geometry.scale, left: layer.x + geometry.offsetX, top: layer.y + geometry.offsetY, lockMovementX: true, lockMovementY: true })
object = image
} else if (layer.kind === 'text') {
const angle = layer.angle || 0
let advance = 0, maximum = 0, measuringFont = ''
const original = layer.originalText || layer.text || ''
for (let index = 0; index < original.length; index++) {
if (original[index] === '\n') { maximum = Math.max(maximum, advance); advance = 0; continue }
const run = layer.runs?.find(run => index >= run.start && index < run.end)
const font = `${run?.size || layer.fontSize}px "${run?.font || layer.font}"`
if (font !== measuringFont) { measure.font = font; measuringFont = font }
advance += measure.measureText(original[index]).width
}
const originalLines = layer.originalLines || Math.max(original.split('\n').length, Math.abs(angle) < 1 ? Math.round(layer.height / ((layer.fontSize || 40) * 1.1)) : 1)
const wraps = originalLines > original.split('\n').length
const width = layer.textLayout==='box'||wraps ? layer.width : Math.max(Math.abs(angle) > 1 ? Math.hypot(layer.width, layer.height) : layer.width + (layer.fontSize || 40) * .35, maximum + 2, advance + 2)
object = new fabric.Textbox(layer.text || '', { ...base, top: layer.y - (layer.fontSize || 40) * .13, width, fontFamily: layer.font, fontSize: layer.fontSize, fill: layer.color, lineHeight: layer.lineHeight || 1.1, splitByGrapheme: true, padding: 0, strokeWidth: 0, styles: detailLayerTextStyles(layer), editable: !layer.locked, lockMovementX: layer.locked, lockMovementY: layer.locked, cursorColor: layer.color, editingBorderColor: '#1677ff' })
if (Math.abs(angle) > 1) object.set({ originX: 'center', originY: 'center', left: layer.x + layer.width / 2, top: layer.y + layer.height / 2, angle })
} else if (layer.shape === 'line') {
object = new fabric.Line((layer.points || [0, 0, layer.width, layer.height]) as [number, number, number, number], { ...base, stroke: layer.color, strokeWidth: Math.max(1, Math.min(3, Math.min(layer.width, layer.height))), lockMovementX: true, lockMovementY: true })
} else if (layer.shape?.startsWith('bracket')) {
const points = layer.shape === 'bracket-left' ? `M ${layer.width} 0 L 0 0 L 0 ${layer.height}` : `M 0 ${layer.height} L ${layer.width} ${layer.height} L ${layer.width} 0`
object = new fabric.Path(points, { ...base, fill: '', stroke: layer.color, strokeWidth: 8, lockMovementX: true, lockMovementY: true })
} else {
object = new fabric.Rect({ ...base, width: layer.width, height: layer.height, fill: layer.color, rx: layer.radius || 0, ry: layer.radius || 0, lockMovementX: true, lockMovementY: true })
}
object.set('data', { layerId: layer.id, layoutWidth: object.width })
return object
}