diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-20 11:31:17 +0200 |
| commit | 3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch) | |
| tree | 57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/text/measure-element.ts | |
| parent | 729d10592f4e802f91c555c309ff3ba4b119f843 (diff) | |
| download | opencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz | |
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/text/measure-element.ts')
| -rw-r--r-- | apps/web/src/text/measure-element.ts | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/apps/web/src/text/measure-element.ts b/apps/web/src/text/measure-element.ts new file mode 100644 index 00000000..3a49696f --- /dev/null +++ b/apps/web/src/text/measure-element.ts @@ -0,0 +1,133 @@ +import { CORNER_RADIUS_MIN } from "@/text/background"; +import { resolveNumberAtTime } from "@/animation"; +import { DEFAULTS } from "@/timeline/defaults"; +import type { TextBackground, TextElement } from "@/timeline"; +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, + }; +} |
