summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/text/layout.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/lib/text/layout.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/lib/text/layout.ts')
-rw-r--r--apps/web/src/lib/text/layout.ts226
1 files changed, 0 insertions, 226 deletions
diff --git a/apps/web/src/lib/text/layout.ts b/apps/web/src/lib/text/layout.ts
deleted file mode 100644
index a3badfdc..00000000
--- a/apps/web/src/lib/text/layout.ts
+++ /dev/null
@@ -1,226 +0,0 @@
-import type { TextBackground, TextElement } from "@/lib/timeline";
-import { DEFAULTS } from "@/lib/timeline/defaults";
-
-type TextRect = {
- left: number;
- top: number;
- width: number;
- height: number;
-};
-
-export interface TextBlockMeasurement {
- visualCenterOffset: number;
- height: number;
- maxWidth: number;
-}
-
-export type TextCanvasContext =
- | CanvasRenderingContext2D
- | OffscreenCanvasRenderingContext2D;
-
-const TEXT_DECORATION_THICKNESS_RATIO = 0.07;
-const STRIKETHROUGH_VERTICAL_RATIO = 0.35;
-
-export function setCanvasLetterSpacing({
- ctx,
- letterSpacingPx,
-}: {
- ctx: TextCanvasContext;
- letterSpacingPx: number;
-}): void {
- if ("letterSpacing" in ctx) {
- (ctx as CanvasRenderingContext2D & { letterSpacing: string }).letterSpacing =
- `${letterSpacingPx}px`;
- }
-}
-
-export function getMetricAscent({
- metrics,
- fallbackFontSize,
-}: {
- metrics: TextMetrics;
- fallbackFontSize: number;
-}): number {
- return metrics.actualBoundingBoxAscent ?? fallbackFontSize * 0.8;
-}
-
-export function getMetricDescent({
- metrics,
- fallbackFontSize,
-}: {
- metrics: TextMetrics;
- fallbackFontSize: number;
-}): number {
- return metrics.actualBoundingBoxDescent ?? fallbackFontSize * 0.2;
-}
-
-export function measureTextBlock({
- lineMetrics,
- lineHeightPx,
-}: {
- lineMetrics: TextMetrics[];
- lineHeightPx: number;
-}): TextBlockMeasurement {
- let maxWidth = 0;
-
- for (const metrics of lineMetrics) {
- maxWidth = Math.max(maxWidth, metrics.width);
- }
-
- const lineCount = lineMetrics.length;
- const height = lineCount * lineHeightPx;
- const visualCenterOffset = ((lineCount - 1) * lineHeightPx) / 2;
-
- return { visualCenterOffset, height, maxWidth };
-}
-
-function getTextRect({
- textAlign,
- block,
-}: {
- textAlign: TextElement["textAlign"];
- block: TextBlockMeasurement;
-}): TextRect {
- const textAlignToLeft: Record<typeof textAlign, number> = {
- left: 0,
- right: -block.maxWidth,
- center: -block.maxWidth / 2,
- };
- const left = textAlignToLeft[textAlign];
-
- return {
- left,
- top: -block.height / 2,
- width: block.maxWidth,
- height: block.height,
- };
-}
-
-function isTextBackgroundVisible({
- background,
-}: {
- background: TextBackground;
-}): boolean {
- return (
- background.enabled &&
- Boolean(background.color) &&
- background.color !== "transparent"
- );
-}
-
-export function getTextBackgroundRect({
- textAlign,
- block,
- background,
- fontSizeRatio = 1,
-}: {
- textAlign: TextElement["textAlign"];
- block: TextBlockMeasurement;
- background: TextBackground;
- fontSizeRatio?: number;
-}): TextRect | null {
- if (!isTextBackgroundVisible({ background })) {
- return null;
- }
-
- const textRect = getTextRect({ textAlign, block });
- const paddingX =
- (background.paddingX ?? DEFAULTS.text.background.paddingX) * fontSizeRatio;
- const paddingY =
- (background.paddingY ?? DEFAULTS.text.background.paddingY) * fontSizeRatio;
- const offsetX = background.offsetX ?? DEFAULTS.text.background.offsetX;
- const offsetY = background.offsetY ?? DEFAULTS.text.background.offsetY;
-
- return {
- left: textRect.left - paddingX + offsetX,
- top: textRect.top - paddingY + offsetY,
- width: textRect.width + paddingX * 2,
- height: textRect.height + paddingY * 2,
- };
-}
-
-export function getTextVisualRect({
- textAlign,
- block,
- background,
- fontSizeRatio = 1,
-}: {
- textAlign: TextElement["textAlign"];
- block: TextBlockMeasurement;
- background: TextBackground;
- fontSizeRatio?: number;
-}): TextRect {
- const textRect = getTextRect({ textAlign, block });
- const backgroundRect = getTextBackgroundRect({
- textAlign,
- block,
- background,
- fontSizeRatio,
- });
-
- if (!backgroundRect) {
- return textRect;
- }
-
- const left = Math.min(textRect.left, backgroundRect.left);
- const top = Math.min(textRect.top, backgroundRect.top);
- const right = Math.max(
- textRect.left + textRect.width,
- backgroundRect.left + backgroundRect.width,
- );
- const bottom = Math.max(
- textRect.top + textRect.height,
- backgroundRect.top + backgroundRect.height,
- );
-
- return {
- left,
- top,
- width: right - left,
- height: bottom - top,
- };
-}
-
-export function drawTextDecoration({
- ctx,
- textDecoration,
- lineWidth,
- lineY,
- metrics,
- scaledFontSize,
- textAlign,
-}: {
- ctx: TextCanvasContext;
- textDecoration: string;
- lineWidth: number;
- lineY: number;
- metrics: TextMetrics;
- scaledFontSize: number;
- textAlign: CanvasTextAlign;
-}): void {
- if (textDecoration === "none" || !textDecoration) return;
-
- const thickness = Math.max(
- 1,
- scaledFontSize * TEXT_DECORATION_THICKNESS_RATIO,
- );
- const ascent = getMetricAscent({ metrics, fallbackFontSize: scaledFontSize });
- const descent = getMetricDescent({
- metrics,
- fallbackFontSize: scaledFontSize,
- });
-
- let xStart = -lineWidth / 2;
- if (textAlign === "left") xStart = 0;
- if (textAlign === "right") xStart = -lineWidth;
-
- if (textDecoration === "underline") {
- const underlineY = lineY + descent + thickness;
- ctx.fillRect(xStart, underlineY, lineWidth, thickness);
- }
-
- if (textDecoration === "line-through") {
- const strikeY = lineY - (ascent - descent) * STRIKETHROUGH_VERTICAL_RATIO;
- ctx.fillRect(xStart, strikeY, lineWidth, thickness);
- }
-}