39 lines
2.0 KiB
TypeScript

import type { DetailScreen } from './types'
const combiningMark = new RegExp('\\p{Mark}', 'u')
/** Conservative grapheme wrapping matches the fixed font and Fabric splitByGrapheme layout. */
export function estimateDetailTextHeight(text: string, width: number, fontSize: number, lineHeight: number): number {
if (!text) return Math.ceil(fontSize * 1.13)
let lines = 0
for (const paragraph of text.split('\n')) {
let occupied = 0; let paragraphLines = 1
for (const character of Array.from(paragraph)) {
if (combiningMark.test(character)) continue
const factor = /[MW@%]/.test(character) ? 1 : /[\x20-\x7e]/.test(character) ? 0.65 : 1.05
const next = fontSize * factor
if (occupied > 0 && occupied + next > width) { paragraphLines++; occupied = 0 }
occupied += next
}
lines += paragraphLines
}
return Math.ceil(lines * fontSize * 1.13 * lineHeight)
}
/** v1 preserves the template while letting copy grow without covering the product image. */
export function reflowDetailScreen(screen: DetailScreen): DetailScreen {
const [title, body] = screen.texts
if (!title || !body || !title.id.endsWith('-title') || !body.id.endsWith('-body')) return screen
const titleY = screen.logo ? Math.max(58, screen.logo.y + screen.logo.height + 24) : 58
const titleHeight = estimateDetailTextHeight(screen.title, title.width, title.fontSize, title.lineHeight)
const bodyY = titleY + titleHeight + 24
const bodyHeight = estimateDetailTextHeight(screen.body, body.width, body.fontSize, body.lineHeight)
const imageArea = { x: 0, y: bodyY + bodyHeight + 32, width: 750, height: 750 }
const height = Math.max(1200, imageArea.y + imageArea.height + 48)
if (height > 2400) throw new Error('文案过长,请精简后确认;每屏高度最多 2400px')
return {
...screen, height, imageArea,
...(screen.image ? { image: { assetId: screen.image.assetId, ...imageArea } } : {}),
texts: [{ ...title, text: screen.title, y: titleY }, { ...body, text: screen.body, y: bodyY }, ...screen.texts.slice(2)],
}
}