50 lines
1.8 KiB
TypeScript
50 lines
1.8 KiB
TypeScript
export const VIEWPORT_DPR_SCHEMA_VERSION = 1 as const;
|
|
export const VIEWPORT_MAX_DPR = 2 as const;
|
|
|
|
export interface ViewportPixelMetrics {
|
|
schemaVersion: typeof VIEWPORT_DPR_SCHEMA_VERSION;
|
|
cssWidth: number;
|
|
cssHeight: number;
|
|
pixelRatio: number;
|
|
backingWidth: number;
|
|
backingHeight: number;
|
|
}
|
|
|
|
export interface ViewportNDC {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
function finitePositive(value: number): boolean {
|
|
return Number.isFinite(value) && value > 0;
|
|
}
|
|
|
|
export function resolveViewportPixelRatio(devicePixelRatio: number | undefined, maximum = VIEWPORT_MAX_DPR): number {
|
|
if (!finitePositive(maximum)) throw new Error("VIEWPORT_DPR_INVALID");
|
|
const observed = finitePositive(devicePixelRatio ?? 1) ? devicePixelRatio! : 1;
|
|
return Math.min(observed, maximum);
|
|
}
|
|
|
|
export function resolveViewportPixelMetrics(cssWidth: number, cssHeight: number, devicePixelRatio: number | undefined, maximum = VIEWPORT_MAX_DPR): ViewportPixelMetrics {
|
|
if (!finitePositive(cssWidth) || !finitePositive(cssHeight)) throw new Error("VIEWPORT_SIZE_INVALID");
|
|
const pixelRatio = resolveViewportPixelRatio(devicePixelRatio, maximum);
|
|
return {
|
|
schemaVersion: VIEWPORT_DPR_SCHEMA_VERSION,
|
|
cssWidth,
|
|
cssHeight,
|
|
pixelRatio,
|
|
backingWidth: Math.max(1, Math.floor(cssWidth * pixelRatio)),
|
|
backingHeight: Math.max(1, Math.floor(cssHeight * pixelRatio)),
|
|
};
|
|
}
|
|
|
|
export function viewportNDC(clientX: number, clientY: number, bounds: { left: number; top: number; width: number; height: number }): ViewportNDC {
|
|
if (![clientX, clientY, bounds.left, bounds.top, bounds.width, bounds.height].every(Number.isFinite) || bounds.width <= 0 || bounds.height <= 0) {
|
|
throw new Error("VIEWPORT_BOUNDS_INVALID");
|
|
}
|
|
return {
|
|
x: ((clientX - bounds.left) / bounds.width) * 2 - 1,
|
|
y: -((clientY - bounds.top) / bounds.height) * 2 + 1,
|
|
};
|
|
}
|