/** 文本控件在元素框内的垂直对齐(config.verticalAlign) */ export type TextVerticalAlign = 'top' | 'center' | 'bottom' export function readVerticalAlign( config: Record | undefined | null, ): TextVerticalAlign { const raw = config?.verticalAlign ?? config?.VerticalAlign const v = String(raw ?? 'top').trim().toLowerCase() if (v === 'center' || v === 'middle') return 'center' if (v === 'bottom') return 'bottom' return 'top' } export function computeVerticalTextBlockOffset( innerHeight: number, blockHeight: number, verticalAlign: TextVerticalAlign, ): number { const extra = Math.max(0, innerHeight - blockHeight) if (verticalAlign === 'center') return Math.floor(extra / 2) if (verticalAlign === 'bottom') return extra return 0 } export function readFontWeight( config: Record | undefined | null, ): 'normal' | 'bold' { const raw = config?.fontWeight ?? config?.FontWeight const v = String(raw ?? 'normal').trim().toLowerCase() return v === 'bold' || v === '700' || v === 'bolder' ? 'bold' : 'normal' } export function readFontStyle( config: Record | undefined | null, ): 'normal' | 'italic' { const raw = config?.fontStyle ?? config?.FontStyle const v = String(raw ?? 'normal').trim().toLowerCase() return v === 'italic' || v === 'oblique' ? 'italic' : 'normal' } export function readTextDecoration( config: Record | undefined | null, ): 'none' | 'underline' { const raw = config?.textDecoration ?? config?.TextDecoration const v = String(raw ?? 'none').trim().toLowerCase() return v.includes('underline') ? 'underline' : 'none' } export function readElementBorder( el: { border?: string | null Border?: string | null BorderType?: string | null borderType?: string | null }, ): string { const raw = el.border ?? el.Border ?? el.BorderType ?? el.borderType ?? 'none' return String(raw).trim().toLowerCase() || 'none' } /** 元素 rotation(兼容大小写) */ export function readElementRotation( el: { rotation?: string | null; Rotation?: string | null }, ): 'horizontal' | 'vertical' | 'right' | 'rotate180' { const v = String(el.rotation ?? el.Rotation ?? 'horizontal').trim().toLowerCase() if (v === 'vertical' || v === 'left' || v === 'rotate-left' || v === 'left90') return 'vertical' if (v === 'right' || v === 'rotate-right' || v === 'right90') return 'right' if (v === 'rotate180' || v === '180' || v === 'down' || v === 'inverted') return 'rotate180' return 'horizontal' } export function elementRotationDegrees( el: { rotation?: string | null; Rotation?: string | null }, ): 0 | -90 | 90 | 180 { const rotation = readElementRotation(el) if (rotation === 'vertical') return -90 if (rotation === 'right') return 90 if (rotation === 'rotate180') return 180 return 0 } /** 与 renderLabelPreviewCanvas lineHeightForTextElement 一致 */ export function textElementLineHeightPx(fontSize: number, isDateTimeType = false): number { return isDateTimeType ? Math.max(fontSize + 2, Math.round(fontSize * 1.2)) : Math.max(fontSize + 2, Math.round(fontSize * 1.25)) } /** 矮框内固定文案:强制单行,避免折行后仅显示第一行 */ export function isShortSingleLineTextBox( boxHeightPx: number, fontSize: number, text: string, opts?: { breakAll?: boolean }, ): boolean { if (opts?.breakAll) return false if (String(text ?? '').includes('\n')) return false const lineH = textElementLineHeightPx(fontSize, false) const descentPad = Math.max(2, Math.round(fontSize * 0.14)) const maxLines = Math.max( 1, Math.floor((Math.max(0, boxHeightPx) + descentPad) / lineH), ) return maxLines <= 1 }