summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/text/primitives.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/primitives.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/primitives.ts')
-rw-r--r--apps/web/src/lib/text/primitives.ts241
1 files changed, 0 insertions, 241 deletions
diff --git a/apps/web/src/lib/text/primitives.ts b/apps/web/src/lib/text/primitives.ts
deleted file mode 100644
index 20674c3c..00000000
--- a/apps/web/src/lib/text/primitives.ts
+++ /dev/null
@@ -1,241 +0,0 @@
-import type { TextCanvasContext, TextBlockMeasurement } from "@/lib/text/layout";
-import { DEFAULTS } from "@/lib/timeline/defaults";
-import { clamp } from "@/utils/math";
-import { CORNER_RADIUS_MAX, CORNER_RADIUS_MIN } from "./background";
-import {
- drawTextDecoration,
- getTextBackgroundRect,
- measureTextBlock,
- setCanvasLetterSpacing,
-} from "./layout";
-import { FONT_SIZE_SCALE_REFERENCE } from "./typography";
-
-export type TextAlign = "left" | "center" | "right";
-export type TextFontWeight = "normal" | "bold";
-export type TextFontStyle = "normal" | "italic";
-export type TextDecoration = "none" | "underline" | "line-through";
-
-export interface TextLayoutParams {
- content: string;
- fontSize: number;
- fontFamily: string;
- fontWeight: TextFontWeight;
- fontStyle: TextFontStyle;
- textAlign: TextAlign;
- textDecoration?: TextDecoration;
- letterSpacing?: number;
- lineHeight?: number;
-}
-
-export interface ResolvedTextLayout {
- scaledFontSize: number;
- fontString: string;
- letterSpacing: number;
- lineHeightPx: number;
- fontSizeRatio: number;
- textAlign: TextAlign;
- textDecoration: TextDecoration;
-}
-
-export interface MeasuredTextLayout extends ResolvedTextLayout {
- lines: string[];
- lineMetrics: TextMetrics[];
- block: TextBlockMeasurement;
-}
-
-export interface ResolvedTextBackgroundLike {
- enabled: boolean;
- color: string;
- paddingX: number;
- paddingY: number;
- offsetX: number;
- offsetY: number;
- cornerRadius: number;
-}
-
-export function quoteFontFamily({ fontFamily }: { fontFamily: string }): string {
- return `"${fontFamily.replace(/"/g, '\\"')}"`;
-}
-
-export function buildTextFontString({
- fontFamily,
- fontWeight,
- fontStyle,
- scaledFontSize,
-}: {
- fontFamily: string;
- fontWeight: TextFontWeight;
- fontStyle: TextFontStyle;
- scaledFontSize: number;
-}): string {
- return `${fontStyle} ${fontWeight} ${scaledFontSize}px ${quoteFontFamily({ fontFamily })}, sans-serif`;
-}
-
-export function resolveTextLayout({
- text,
- canvasHeight,
-}: {
- text: TextLayoutParams;
- canvasHeight: number;
-}): ResolvedTextLayout {
- const scaledFontSize =
- text.fontSize * (canvasHeight / FONT_SIZE_SCALE_REFERENCE);
- const fontWeight = text.fontWeight === "bold" ? "bold" : "normal";
- const fontStyle = text.fontStyle === "italic" ? "italic" : "normal";
- const letterSpacing = text.letterSpacing ?? DEFAULTS.text.letterSpacing;
- const lineHeightPx =
- scaledFontSize * (text.lineHeight ?? DEFAULTS.text.lineHeight);
- const fontSizeRatio = text.fontSize / DEFAULTS.text.element.fontSize;
-
- return {
- scaledFontSize,
- fontString: buildTextFontString({
- fontFamily: text.fontFamily,
- fontWeight,
- fontStyle,
- scaledFontSize,
- }),
- letterSpacing,
- lineHeightPx,
- fontSizeRatio,
- textAlign: text.textAlign,
- textDecoration: text.textDecoration ?? "none",
- };
-}
-
-export function measureTextLayout({
- text,
- canvasHeight,
- ctx,
-}: {
- text: TextLayoutParams;
- canvasHeight: number;
- ctx: TextCanvasContext;
-}): MeasuredTextLayout {
- const resolvedLayout = resolveTextLayout({ text, canvasHeight });
- const lines = text.content.split("\n");
-
- ctx.save();
- ctx.font = resolvedLayout.fontString;
- ctx.textBaseline = "middle";
- setCanvasLetterSpacing({
- ctx,
- letterSpacingPx: resolvedLayout.letterSpacing,
- });
- const lineMetrics = lines.map((line) => ctx.measureText(line));
- ctx.restore();
-
- const block = measureTextBlock({
- lineMetrics,
- lineHeightPx: resolvedLayout.lineHeightPx,
- });
-
- return {
- ...resolvedLayout,
- lines,
- lineMetrics,
- block,
- };
-}
-
-export function drawMeasuredTextLayout({
- ctx,
- layout,
- textColor,
- background,
- backgroundColor,
- textBaseline = "middle",
-}: {
- ctx: TextCanvasContext;
- layout: MeasuredTextLayout;
- textColor: string;
- background?: ResolvedTextBackgroundLike | null;
- backgroundColor?: string;
- textBaseline?: CanvasTextBaseline;
-}): void {
- ctx.font = layout.fontString;
- ctx.textAlign = layout.textAlign;
- ctx.textBaseline = textBaseline;
- ctx.fillStyle = textColor;
- setCanvasLetterSpacing({ ctx, letterSpacingPx: layout.letterSpacing });
-
- if (
- background?.enabled &&
- backgroundColor &&
- backgroundColor !== "transparent" &&
- layout.lines.length > 0
- ) {
- const backgroundRect = getTextBackgroundRect({
- textAlign: layout.textAlign,
- block: layout.block,
- background: {
- ...background,
- color: backgroundColor,
- },
- fontSizeRatio: layout.fontSizeRatio,
- });
- if (backgroundRect) {
- const p =
- clamp({
- value: background.cornerRadius,
- min: CORNER_RADIUS_MIN,
- max: CORNER_RADIUS_MAX,
- }) / 100;
- const radius =
- (Math.min(backgroundRect.width, backgroundRect.height) / 2) * p;
- ctx.fillStyle = backgroundColor;
- ctx.beginPath();
- ctx.roundRect(
- backgroundRect.left,
- backgroundRect.top,
- backgroundRect.width,
- backgroundRect.height,
- radius,
- );
- ctx.fill();
- ctx.fillStyle = textColor;
- }
- }
-
- for (let index = 0; index < layout.lines.length; index++) {
- const lineY = index * layout.lineHeightPx - layout.block.visualCenterOffset;
- ctx.fillText(layout.lines[index], 0, lineY);
- drawTextDecoration({
- ctx,
- textDecoration: layout.textDecoration,
- lineWidth: layout.lineMetrics[index].width,
- lineY,
- metrics: layout.lineMetrics[index],
- scaledFontSize: layout.scaledFontSize,
- textAlign: layout.textAlign,
- });
- }
-}
-
-export function strokeMeasuredTextLayout({
- ctx,
- layout,
- strokeColor,
- strokeWidth,
- textBaseline = "middle",
-}: {
- ctx: TextCanvasContext;
- layout: MeasuredTextLayout;
- strokeColor: string;
- strokeWidth: number;
- textBaseline?: CanvasTextBaseline;
-}): void {
- ctx.font = layout.fontString;
- ctx.textAlign = layout.textAlign;
- ctx.textBaseline = textBaseline;
- ctx.strokeStyle = strokeColor;
- ctx.lineWidth = strokeWidth;
- ctx.lineJoin = "round";
- ctx.lineCap = "round";
- setCanvasLetterSpacing({ ctx, letterSpacingPx: layout.letterSpacing });
-
- for (let index = 0; index < layout.lines.length; index++) {
- const lineY = index * layout.lineHeightPx - layout.block.visualCenterOffset;
- ctx.strokeText(layout.lines[index], 0, lineY);
- }
-}