summaryrefslogtreecommitdiff
path: root/apps/web/src/text/measure-element.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/text/measure-element.ts
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-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.ts133
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,
+ };
+}