90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
/**
|
||
* Pure element-geometry resolution for the video-timeline compiler.
|
||
*
|
||
* Resolves a slide element's percentage geometry (0–100 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 (0–100) 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 };
|
||
}
|