textElementLayout.ts 3.1 KB
/** 文本控件在元素框内的垂直对齐(config.verticalAlign) */
export type TextVerticalAlign = 'top' | 'center' | 'bottom'

export function readVerticalAlign(
  config: Record<string, unknown> | 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<string, unknown> | 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<string, unknown> | 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<string, unknown> | 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' {
  const v = String(el.rotation ?? el.Rotation ?? 'horizontal').trim().toLowerCase()
  return v === 'vertical' ? 'vertical' : 'horizontal'
}

/** 与 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
}