summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/preview
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/lib/preview')
-rw-r--r--apps/web/src/lib/preview/element-bounds.ts94
1 files changed, 77 insertions, 17 deletions
diff --git a/apps/web/src/lib/preview/element-bounds.ts b/apps/web/src/lib/preview/element-bounds.ts
index 9e58ec52..08ffbfa8 100644
--- a/apps/web/src/lib/preview/element-bounds.ts
+++ b/apps/web/src/lib/preview/element-bounds.ts
@@ -4,9 +4,15 @@ import { isMainTrack } from "@/lib/timeline";
import {
DEFAULT_TEXT_ELEMENT,
DEFAULT_LINE_HEIGHT,
+ DEFAULT_TEXT_BACKGROUND,
FONT_SIZE_SCALE_REFERENCE,
} from "@/constants/text-constants";
import { getTextVisualRect, measureTextBlock } from "@/lib/text/layout";
+import {
+ getElementLocalTime,
+ resolveTransformAtTime,
+ resolveNumberAtTime,
+} from "@/lib/animation";
export interface ElementBounds {
cx: number;
@@ -62,17 +68,24 @@ export function getElementBounds({
element,
canvasSize,
mediaAsset,
+ localTime,
}: {
element: TimelineElement;
canvasSize: { width: number; height: number };
mediaAsset?: MediaAsset | null;
+ localTime: number;
}): ElementBounds | null {
- if (element.type === "audio") return null;
+ if (element.type === "audio" || element.type === "effect") return null;
if ("hidden" in element && element.hidden) return null;
const { width: canvasWidth, height: canvasHeight } = canvasSize;
if (element.type === "video" || element.type === "image") {
+ const transform = resolveTransformAtTime({
+ baseTransform: element.transform,
+ animations: element.animations,
+ localTime,
+ });
const sourceWidth = mediaAsset?.width ?? canvasWidth;
const sourceHeight = mediaAsset?.height ?? canvasHeight;
return getVisualElementBounds({
@@ -80,21 +93,31 @@ export function getElementBounds({
canvasHeight,
sourceWidth,
sourceHeight,
- transform: element.transform,
+ transform,
});
}
if (element.type === "sticker") {
+ const transform = resolveTransformAtTime({
+ baseTransform: element.transform,
+ animations: element.animations,
+ localTime,
+ });
return getVisualElementBounds({
canvasWidth,
canvasHeight,
sourceWidth: 200,
sourceHeight: 200,
- transform: element.transform,
+ transform,
});
}
if (element.type === "text") {
+ const transform = resolveTransformAtTime({
+ baseTransform: element.transform,
+ animations: element.animations,
+ localTime,
+ });
const scaledFontSize =
element.fontSize * (canvasHeight / FONT_SIZE_SCALE_REFERENCE);
const letterSpacing = element.letterSpacing ?? 0;
@@ -129,40 +152,71 @@ export function getElementBounds({
fallbackFontSize: scaledFontSize,
});
const fontSizeRatio = element.fontSize / DEFAULT_TEXT_ELEMENT.fontSize;
+ const resolvedBackground = {
+ ...element.background,
+ paddingX: resolveNumberAtTime({
+ baseValue:
+ element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,
+ animations: element.animations,
+ propertyPath: "background.paddingX",
+ localTime,
+ }),
+ paddingY: resolveNumberAtTime({
+ baseValue:
+ element.background.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY,
+ animations: element.animations,
+ propertyPath: "background.paddingY",
+ localTime,
+ }),
+ offsetX: resolveNumberAtTime({
+ baseValue:
+ element.background.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX,
+ animations: element.animations,
+ propertyPath: "background.offsetX",
+ localTime,
+ }),
+ offsetY: resolveNumberAtTime({
+ baseValue:
+ element.background.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY,
+ animations: element.animations,
+ propertyPath: "background.offsetY",
+ localTime,
+ }),
+ };
const visualRect = getTextVisualRect({
textAlign: element.textAlign,
block,
- background: element.background,
+ background: resolvedBackground,
fontSizeRatio,
});
measuredWidth = visualRect.width;
measuredHeight = visualRect.height;
const localCenterX = visualRect.left + visualRect.width / 2;
const localCenterY = visualRect.top + visualRect.height / 2;
- const scaledCenterX = localCenterX * element.transform.scale;
- const scaledCenterY = localCenterY * element.transform.scale;
- const rotationRad = (element.transform.rotate * Math.PI) / 180;
+ const scaledCenterX = localCenterX * transform.scale;
+ const scaledCenterY = localCenterY * transform.scale;
+ const rotationRad = (transform.rotate * Math.PI) / 180;
const cos = Math.cos(rotationRad);
const sin = Math.sin(rotationRad);
const rotatedCenterX = scaledCenterX * cos - scaledCenterY * sin;
const rotatedCenterY = scaledCenterX * sin + scaledCenterY * cos;
return {
- cx: canvasWidth / 2 + element.transform.position.x + rotatedCenterX,
- cy: canvasHeight / 2 + element.transform.position.y + rotatedCenterY,
- width: measuredWidth * element.transform.scale,
- height: measuredHeight * element.transform.scale,
- rotation: element.transform.rotate,
+ cx: canvasWidth / 2 + transform.position.x + rotatedCenterX,
+ cy: canvasHeight / 2 + transform.position.y + rotatedCenterY,
+ width: measuredWidth * transform.scale,
+ height: measuredHeight * transform.scale,
+ rotation: transform.rotate,
};
}
- const width = measuredWidth * element.transform.scale;
- const height = measuredHeight * element.transform.scale;
+ const width = measuredWidth * transform.scale;
+ const height = measuredHeight * transform.scale;
return {
- cx: canvasWidth / 2 + element.transform.position.x,
- cy: canvasHeight / 2 + element.transform.position.y,
+ cx: canvasWidth / 2 + transform.position.x,
+ cy: canvasHeight / 2 + transform.position.y,
width,
height,
- rotation: element.transform.rotate,
+ rotation: transform.rotate,
};
}
@@ -206,6 +260,11 @@ export function getVisibleElementsWithBounds({
});
for (const element of elements) {
+ const localTime = getElementLocalTime({
+ timelineTime: currentTime,
+ elementStartTime: element.startTime,
+ elementDuration: element.duration,
+ });
const mediaAsset =
element.type === "video" || element.type === "image"
? mediaMap.get(element.mediaId)
@@ -214,6 +273,7 @@ export function getVisibleElementsWithBounds({
element,
canvasSize,
mediaAsset,
+ localTime,
});
if (bounds) {
result.push({