134 lines
3.4 KiB
TypeScript
134 lines
3.4 KiB
TypeScript
import { CORNER_RADIUS_MIN } from "@/text/background";
|
|
import { DEFAULTS } from "@/timeline/defaults";
|
|
import type { TextBackground, TextElement } from "@/timeline";
|
|
import { resolveNumberAtTime } from "@/animation/values";
|
|
import {
|
|
getTextVisualRect,
|
|
} from "./layout";
|
|
import {
|
|
measureTextLayout,
|
|
type MeasuredTextLayout,
|
|
} from "./primitives";
|
|
|
|
export interface ResolvedTextBackground extends TextBackground {
|
|
paddingX: number;
|
|
paddingY: number;
|
|
offsetX: number;
|
|
offsetY: number;
|
|
cornerRadius: number;
|
|
}
|
|
|
|
export interface MeasuredTextElement extends MeasuredTextLayout {
|
|
resolvedBackground: ResolvedTextBackground;
|
|
visualRect: { left: number; top: number; width: number; height: number };
|
|
}
|
|
|
|
let textMeasurementContext:
|
|
| CanvasRenderingContext2D
|
|
| OffscreenCanvasRenderingContext2D
|
|
| null = null;
|
|
|
|
export function getTextMeasurementContext():
|
|
| CanvasRenderingContext2D
|
|
| OffscreenCanvasRenderingContext2D {
|
|
if (textMeasurementContext) {
|
|
return textMeasurementContext;
|
|
}
|
|
|
|
if (typeof OffscreenCanvas !== "undefined") {
|
|
const canvas = new OffscreenCanvas(1, 1);
|
|
const context = canvas.getContext("2d");
|
|
if (context) {
|
|
textMeasurementContext = context;
|
|
return context;
|
|
}
|
|
}
|
|
|
|
if (typeof document !== "undefined") {
|
|
const canvas = document.createElement("canvas");
|
|
const context = canvas.getContext("2d");
|
|
if (context) {
|
|
textMeasurementContext = context;
|
|
return context;
|
|
}
|
|
}
|
|
|
|
throw new Error("Failed to create text measurement context");
|
|
}
|
|
|
|
export function measureTextElement({
|
|
element,
|
|
canvasHeight,
|
|
localTime,
|
|
ctx,
|
|
}: {
|
|
element: TextElement;
|
|
canvasHeight: number;
|
|
localTime: number;
|
|
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
|
}): MeasuredTextElement {
|
|
const measuredLayout = measureTextLayout({
|
|
text: {
|
|
content: element.content,
|
|
fontSize: element.fontSize,
|
|
fontFamily: element.fontFamily,
|
|
fontWeight: element.fontWeight,
|
|
fontStyle: element.fontStyle,
|
|
textAlign: element.textAlign,
|
|
textDecoration: element.textDecoration,
|
|
letterSpacing: element.letterSpacing,
|
|
lineHeight: element.lineHeight,
|
|
},
|
|
canvasHeight,
|
|
ctx,
|
|
});
|
|
|
|
const bg = element.background;
|
|
const resolvedBackground: ResolvedTextBackground = {
|
|
...bg,
|
|
paddingX: resolveNumberAtTime({
|
|
baseValue: bg.paddingX ?? DEFAULTS.text.background.paddingX,
|
|
animations: element.animations,
|
|
propertyPath: "background.paddingX",
|
|
localTime,
|
|
}),
|
|
paddingY: resolveNumberAtTime({
|
|
baseValue: bg.paddingY ?? DEFAULTS.text.background.paddingY,
|
|
animations: element.animations,
|
|
propertyPath: "background.paddingY",
|
|
localTime,
|
|
}),
|
|
offsetX: resolveNumberAtTime({
|
|
baseValue: bg.offsetX ?? DEFAULTS.text.background.offsetX,
|
|
animations: element.animations,
|
|
propertyPath: "background.offsetX",
|
|
localTime,
|
|
}),
|
|
offsetY: resolveNumberAtTime({
|
|
baseValue: bg.offsetY ?? DEFAULTS.text.background.offsetY,
|
|
animations: element.animations,
|
|
propertyPath: "background.offsetY",
|
|
localTime,
|
|
}),
|
|
cornerRadius: resolveNumberAtTime({
|
|
baseValue: bg.cornerRadius ?? CORNER_RADIUS_MIN,
|
|
animations: element.animations,
|
|
propertyPath: "background.cornerRadius",
|
|
localTime,
|
|
}),
|
|
};
|
|
|
|
const visualRect = getTextVisualRect({
|
|
textAlign: element.textAlign,
|
|
block: measuredLayout.block,
|
|
background: resolvedBackground,
|
|
fontSizeRatio: measuredLayout.fontSizeRatio,
|
|
});
|
|
|
|
return {
|
|
...measuredLayout,
|
|
resolvedBackground,
|
|
visualRect,
|
|
};
|
|
}
|