Files
waveform-analysis/src/components/core/title.ts
2026-07-20 15:35:19 +08:00

87 lines
2.6 KiB
TypeScript

export const TITLE_AREA_MIN_HEIGHT = 44
export const TITLE_AREA_MAX_HEIGHT = 160
export const TITLE_AREA_HORIZONTAL_PADDING = 24
export const TITLE_AREA_VERTICAL_PADDING = 18
const TRIGONOMETRY_EPSILON = 1e-6
export interface RotatedTitleLayout {
textWidth: number
textHeight: number
visualWidth: number
visualHeight: number
areaHeight: number
scale: number
wrapped: boolean
}
export interface RotatedTitleLayoutOptions {
naturalWidth: number
naturalHeight: number
availableWidth: number
rotation: number
}
function clampPositive(value: number): number {
return Number.isFinite(value) ? Math.max(1, value) : 1
}
export function calculateRotatedTitleLayout({
naturalWidth,
naturalHeight,
availableWidth,
rotation,
}: RotatedTitleLayoutOptions): RotatedTitleLayout {
const safeNaturalWidth = clampPositive(naturalWidth)
const safeNaturalHeight = clampPositive(naturalHeight)
const safeAvailableWidth = clampPositive(availableWidth)
const safeRotation = Number.isFinite(rotation) ? rotation : 0
const radians = (safeRotation * Math.PI) / 180
const absoluteCosine = Math.abs(Math.cos(radians))
const absoluteSine = Math.abs(Math.sin(radians))
const normalizedRotation = ((safeRotation % 360) + 360) % 360
const isRotated =
normalizedRotation > TRIGONOMETRY_EPSILON &&
Math.abs(normalizedRotation - 360) > TRIGONOMETRY_EPSILON
if (!isRotated) {
const lineCount = Math.max(1, Math.ceil(safeNaturalWidth / safeAvailableWidth))
const textWidth = safeAvailableWidth
const textHeight = safeNaturalHeight * lineCount
return {
textWidth,
textHeight,
visualWidth: textWidth,
visualHeight: textHeight,
areaHeight: Math.max(TITLE_AREA_MIN_HEIGHT, textHeight + TITLE_AREA_VERTICAL_PADDING),
scale: 1,
wrapped: lineCount > 1,
}
}
const maximumVisualHeight = TITLE_AREA_MAX_HEIGHT - TITLE_AREA_VERTICAL_PADDING
const naturalVisualWidth = safeNaturalWidth * absoluteCosine + safeNaturalHeight * absoluteSine
const naturalVisualHeight = safeNaturalWidth * absoluteSine + safeNaturalHeight * absoluteCosine
const scale = Math.min(
1,
safeAvailableWidth / naturalVisualWidth,
maximumVisualHeight / naturalVisualHeight,
)
const visualWidth = naturalVisualWidth * scale
const visualHeight = naturalVisualHeight * scale
const areaHeight = Math.min(
TITLE_AREA_MAX_HEIGHT,
Math.max(TITLE_AREA_MIN_HEIGHT, visualHeight + TITLE_AREA_VERTICAL_PADDING),
)
return {
textWidth: safeNaturalWidth,
textHeight: safeNaturalHeight,
visualWidth,
visualHeight,
areaHeight,
scale,
wrapped: false,
}
}