core/evaluators/card.fragment.evaluator.ts
core/evaluators/card.fragment.evaluator.ts is a file in Bane's Lab Social Share. 102 lines of code and 31 definitions.
import type {
Curve,
FrameContext,
LayoutSlot,
Orientation,
OrientedLayout,
Placement,
SlotOptions,
TextAlign,
} from "#types/card.types";
import { DEFAULT_ALIGN, WIDE_RATIO } from "#configuration/constants/card.constants";
import { SUBJECT_SEPARATOR, UNKNOWN_CURVE } from "#configuration/strings/card.strings";
const CUBE = 3;
const HALF = 0.5;
const BACK = 1.70158;
const TURN = Math.PI * 2;
const NOISE_A = 12.9898;
const NOISE_B = 78.233;
const NOISE_SCALE = 43_758.5453;
const PIXEL_PRECISION = 100;
const PIXEL_UNIT = "px";
const CURVES: ReadonlyMap<Curve, (x: number) => number> = new Map<Curve, (x: number) => number>([
["linear", (x) => x],
["inCubic", (x) => x ** CUBE],
["outCubic", (x) => 1 - (1 - x) ** CUBE],
["inOutCubic", (x) => (x < HALF ? 4 * x ** CUBE : 1 - (-2 * x + 2) ** CUBE / 2)],
["inOutSine", (x) => -(Math.cos(Math.PI * x) - 1) / 2],
["outBack", (x) => 1 + (BACK + 1) * (x - 1) ** CUBE + BACK * (x - 1) ** 2],
]);
export const clamp = function clamp(value: number, low = 0, high = 1): number {
return Math.min(high, Math.max(low, value));
};
export const lerp = function lerp(from: number, to: number, amount: number): number {
return from + (to - from) * amount;
};
export const ease = function ease(progress: number, curve: Curve = "inOutCubic"): number {
const shape = CURVES.get(curve);
if (shape === undefined) {
throw new Error(UNKNOWN_CURVE + SUBJECT_SEPARATOR + curve);
}
return shape(clamp(progress));
};
export const between = function between(progress: number, start: number, end: number): number {
return end <= start ? Number(progress >= end) : clamp((progress - start) / (end - start));
};
export const wave = function wave(progress: number, cycles = 1, phase = 0): number {
return (Math.sin((progress * cycles + phase) * TURN) + 1) / 2;
};
export const stagger = function stagger(progress: number, index: number, step: number, span: number): number {
return between(progress, index * step, index * step + span);
};
export const noise = function noise(seed: number, x = 0): number {
const value = Math.sin(seed * NOISE_A + x * NOISE_B) * NOISE_SCALE;
return value - Math.floor(value);
};
export const pixels = function pixels(value: number): string {
return `${String(Math.round(value * PIXEL_PRECISION) / PIXEL_PRECISION)}${PIXEL_UNIT}`;
};
export const pixelValue = function pixelValue(text: string): number {
const bare = (text.endsWith(PIXEL_UNIT) ? text.slice(0, -PIXEL_UNIT.length) : text).trim();
return bare === "" ? Number.NaN : Number(bare);
};
export const percent = function percent(value: number): string {
return `${String(value)}%`;
};
export const swing = function swing(progress: number, cycles = 1, phase = 0): number {
return Math.sin((progress * cycles + phase) * TURN);
};
export const orientationOf = function orientationOf(frame: FrameContext): Orientation {
return frame.profile.width / frame.profile.height >= WIDE_RATIO ? "wide" : "square";
};
export const slotAt = function slotAt<K extends string>(
layout: OrientedLayout<K>,
key: K,
frame: FrameContext,
): LayoutSlot {
return layout[orientationOf(frame)][key];
};
export const squareHeight = function squareHeight(width: number, frame: FrameContext): number {
return (width * frame.profile.width) / frame.profile.height;
};
export const placeAt = function placeAt<K extends string>(
layout: OrientedLayout<K>,
key: K,
options: SlotOptions = {},
): Placement {
const grown = (frame: FrameContext): number => slotAt(layout, key, frame).width * (options.grow?.(frame) ?? 1);
return {
anchor: (frame) => slotAt(layout, key, frame).anchor,
height: (frame) =>
options.square === true ? squareHeight(grown(frame), frame) : (slotAt(layout, key, frame).height ?? 0),
width: grown,
x: (frame) => slotAt(layout, key, frame).x,
y: (frame) => slotAt(layout, key, frame).y + (options.lift?.(frame) ?? 0),
};
};
export const fontAt = function fontAt<K extends string>(layout: OrientedLayout<K>, key: K) {
return (frame: FrameContext): string => pixels(frame.profile.width * (slotAt(layout, key, frame).size ?? 0));
};
export const alignAt = function alignAt<K extends string>(layout: OrientedLayout<K>, key: K) {
return (frame: FrameContext): TextAlign => slotAt(layout, key, frame).align ?? DEFAULT_ALIGN;
};