89 lines
2.6 KiB
TypeScript
89 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,
|
|
}
|
|
}
|