summaryrefslogtreecommitdiff
path: root/apps/web/src/services/renderer/nodes/text-node.ts
diff options
context:
space:
mode:
authorMaze <167211895+mazeincoding@users.noreply.github.com>2026-01-31 00:20:04 +0100
committerGitHub <noreply@github.com>2026-01-31 00:20:04 +0100
commit7bf0984698e43b9aa2dd879f65951318ed410390 (patch)
treeccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/services/renderer/nodes/text-node.ts
parent0173db99448d1c4c6415dcf15272e17551b48b04 (diff)
downloadopencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/services/renderer/nodes/text-node.ts')
-rw-r--r--apps/web/src/services/renderer/nodes/text-node.ts74
1 files changed, 74 insertions, 0 deletions
diff --git a/apps/web/src/services/renderer/nodes/text-node.ts b/apps/web/src/services/renderer/nodes/text-node.ts
new file mode 100644
index 00000000..2cbf3d2a
--- /dev/null
+++ b/apps/web/src/services/renderer/nodes/text-node.ts
@@ -0,0 +1,74 @@
+import type { CanvasRenderer } from "../canvas-renderer";
+import { BaseNode } from "./base-node";
+import type { TextElement } from "@/types/timeline";
+
+export type TextNodeParams = TextElement & {
+ canvasCenter: { x: number; y: number };
+ textBaseline?: CanvasTextBaseline;
+};
+
+export class TextNode extends BaseNode<TextNodeParams> {
+ isInRange({ time }: { time: number }) {
+ return (
+ time >= this.params.startTime &&
+ time < this.params.startTime + this.params.duration
+ );
+ }
+
+ async render({ renderer, time }: { renderer: CanvasRenderer; time: number }) {
+ if (!this.isInRange({ time })) {
+ return;
+ }
+
+ renderer.context.save();
+
+ const x = this.params.transform.position.x + this.params.canvasCenter.x;
+ const y = this.params.transform.position.y + this.params.canvasCenter.y;
+
+ renderer.context.translate(x, y);
+ if (this.params.transform.rotate) {
+ renderer.context.rotate((this.params.transform.rotate * Math.PI) / 180);
+ }
+
+ const fontWeight = this.params.fontWeight === "bold" ? "bold" : "normal";
+ const fontStyle = this.params.fontStyle === "italic" ? "italic" : "normal";
+ renderer.context.font = `${fontStyle} ${fontWeight} ${this.params.fontSize}px ${this.params.fontFamily}`;
+ renderer.context.textAlign = this.params.textAlign;
+ renderer.context.textBaseline = this.params.textBaseline || "middle";
+ renderer.context.fillStyle = this.params.color;
+
+ const prevAlpha = renderer.context.globalAlpha;
+ renderer.context.globalAlpha = this.params.opacity;
+
+ if (this.params.backgroundColor) {
+ const metrics = renderer.context.measureText(this.params.content);
+ const ascent =
+ metrics.actualBoundingBoxAscent ?? this.params.fontSize * 0.8;
+ const descent =
+ metrics.actualBoundingBoxDescent ?? this.params.fontSize * 0.2;
+ const textW = metrics.width;
+ const textH = ascent + descent;
+ const padX = 8;
+ const padY = 4;
+
+ renderer.context.fillStyle = this.params.backgroundColor;
+ let bgLeft = -textW / 2;
+ if (renderer.context.textAlign === "left") bgLeft = 0;
+ if (renderer.context.textAlign === "right") bgLeft = -textW;
+
+ renderer.context.fillRect(
+ bgLeft - padX,
+ -textH / 2 - padY,
+ textW + padX * 2,
+ textH + padY * 2,
+ );
+
+ renderer.context.fillStyle = this.params.color;
+ }
+
+ renderer.context.fillText(this.params.content, 0, 0);
+
+ renderer.context.globalAlpha = prevAlpha;
+ renderer.context.restore();
+ }
+}