Files
2026-08-16 14:58:47 +08:00

90 lines
3.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Pure element-geometry resolution for the video-timeline compiler.
*
* Resolves a slide element's percentage geometry (0100 space) so effect
* segments (spotlight/laser) can carry the target's position into the IR. This
* is a faithful reimplementation of the runtime's calculation — the app copy
* (`lib/utils/geometry.ts`) imports a host-app path and the packaged copy
* (`@openmaic/renderer`) pulls a render backend, so both are unreachable under
* this module's purity boundary. The math is ~15 lines and must stay identical
* to the runtime's, so it is mirrored here rather than imported.
*
* The runtime uses a fixed 1000px width base and a 16:9 (0.5625) height ratio,
* independent of a slide's own `viewportSize`/`viewportRatio`, so we do the same
* — the spotlight/laser overlays position against this same base.
*
* Pure: type-only import from `@openmaic/dsl`.
*/
import type { PPTElement } from '@openmaic/dsl';
import type { PercentageGeometry } from './ir';
/** Height ratio the runtime derives the vertical base from (16:9). */
const VIEWPORT_RATIO = 0.5625;
/**
* Percentage geometry (0100) for a single positioned element. Returns null for
* elements without `left/top/width/height` (e.g. some line elements), matching
* the runtime helper.
*/
export function getElementPercentageGeometry(
element: PPTElement,
viewportSize = 1000,
): PercentageGeometry | null {
if (
!('left' in element) ||
!('top' in element) ||
!('width' in element) ||
!('height' in element)
) {
return null;
}
const { left, top, width, height } = element;
const x = (left / viewportSize) * 100;
const y = (top / (viewportSize * VIEWPORT_RATIO)) * 100;
const w = (width / viewportSize) * 100;
const h = (height / (viewportSize * VIEWPORT_RATIO)) * 100;
return { x, y, w, h, centerX: x + w / 2, centerY: y + h / 2 };
}
/**
* Find an element by id in a slide's element list and return its percentage
* geometry, or null when the element is absent or has no position.
*/
export function findElementGeometry(
elements: PPTElement[],
elementId: string,
viewportSize = 1000,
): PercentageGeometry | null {
const element = elements.find((el) => el.id === elementId);
if (!element) return null;
return getElementPercentageGeometry(element, viewportSize);
}
/** An element's placement: percentage geometry + rotation (degrees). */
export interface ElementPlacement {
geometry: PercentageGeometry;
/** Rotation in degrees; 0 for elements that carry no `rotate`. */
rotate: number;
}
/**
* Find an element and return both its percentage geometry and rotation, or null
* when the element is absent or unpositioned. Used to place `play_video` clips
* (position + size + rotation) into the IR so an emitter needs no scene DSL.
*/
export function findElementPlacement(
elements: PPTElement[],
elementId: string,
viewportSize = 1000,
): ElementPlacement | null {
const element = elements.find((el) => el.id === elementId);
if (!element) return null;
const geometry = getElementPercentageGeometry(element, viewportSize);
if (!geometry) return null;
const rotate = 'rotate' in element && typeof element.rotate === 'number' ? element.rotate : 0;
return { geometry, rotate };
}