import * as figrest from "@figma/rest-api-spec"; import { ExportSetting } from "@figma/rest-api-spec"; //#region ../grida-canvas-color/dist/index.d.ts //#region color-parse.d.ts /** * @fileoverview * @see https://github.com/colorjs/color-parse/blob/master/index.js * @license MIT */ type Space = "rgb" | "hsl" | "hwb" | "cmyk" | "lab" | "lch" | "oklab" | "oklch" | string; interface ParseResult { space: Space | undefined; values: number[]; alpha: number; } //#endregion //#region color-name.d.ts /** * @fileoverview * * @see https://github.com/colorjs/color-name/blob/master/index.js * @license MIT */ type RGBVec3 = [number, number, number]; declare const _default: Readonly<{ readonly aliceblue: RGBVec3; readonly antiquewhite: RGBVec3; readonly aqua: RGBVec3; readonly aquamarine: RGBVec3; readonly azure: RGBVec3; readonly beige: RGBVec3; readonly bisque: RGBVec3; readonly black: RGBVec3; readonly blanchedalmond: RGBVec3; readonly blue: RGBVec3; readonly blueviolet: RGBVec3; readonly brown: RGBVec3; readonly burlywood: RGBVec3; readonly cadetblue: RGBVec3; readonly chartreuse: RGBVec3; readonly chocolate: RGBVec3; readonly coral: RGBVec3; readonly cornflowerblue: RGBVec3; readonly cornsilk: RGBVec3; readonly crimson: RGBVec3; readonly cyan: RGBVec3; readonly darkblue: RGBVec3; readonly darkcyan: RGBVec3; readonly darkgoldenrod: RGBVec3; readonly darkgray: RGBVec3; readonly darkgreen: RGBVec3; readonly darkgrey: RGBVec3; readonly darkkhaki: RGBVec3; readonly darkmagenta: RGBVec3; readonly darkolivegreen: RGBVec3; readonly darkorange: RGBVec3; readonly darkorchid: RGBVec3; readonly darkred: RGBVec3; readonly darksalmon: RGBVec3; readonly darkseagreen: RGBVec3; readonly darkslateblue: RGBVec3; readonly darkslategray: RGBVec3; readonly darkslategrey: RGBVec3; readonly darkturquoise: RGBVec3; readonly darkviolet: RGBVec3; readonly deeppink: RGBVec3; readonly deepskyblue: RGBVec3; readonly dimgray: RGBVec3; readonly dimgrey: RGBVec3; readonly dodgerblue: RGBVec3; readonly firebrick: RGBVec3; readonly floralwhite: RGBVec3; readonly forestgreen: RGBVec3; readonly fuchsia: RGBVec3; readonly gainsboro: RGBVec3; readonly ghostwhite: RGBVec3; readonly gold: RGBVec3; readonly goldenrod: RGBVec3; readonly gray: RGBVec3; readonly green: RGBVec3; readonly greenyellow: RGBVec3; readonly grey: RGBVec3; readonly honeydew: RGBVec3; readonly hotpink: RGBVec3; readonly indianred: RGBVec3; readonly indigo: RGBVec3; readonly ivory: RGBVec3; readonly khaki: RGBVec3; readonly lavender: RGBVec3; readonly lavenderblush: RGBVec3; readonly lawngreen: RGBVec3; readonly lemonchiffon: RGBVec3; readonly lightblue: RGBVec3; readonly lightcoral: RGBVec3; readonly lightcyan: RGBVec3; readonly lightgoldenrodyellow: RGBVec3; readonly lightgray: RGBVec3; readonly lightgreen: RGBVec3; readonly lightgrey: RGBVec3; readonly lightpink: RGBVec3; readonly lightsalmon: RGBVec3; readonly lightseagreen: RGBVec3; readonly lightskyblue: RGBVec3; readonly lightslategray: RGBVec3; readonly lightslategrey: RGBVec3; readonly lightsteelblue: RGBVec3; readonly lightyellow: RGBVec3; readonly lime: RGBVec3; readonly limegreen: RGBVec3; readonly linen: RGBVec3; readonly magenta: RGBVec3; readonly maroon: RGBVec3; readonly mediumaquamarine: RGBVec3; readonly mediumblue: RGBVec3; readonly mediumorchid: RGBVec3; readonly mediumpurple: RGBVec3; readonly mediumseagreen: RGBVec3; readonly mediumslateblue: RGBVec3; readonly mediumspringgreen: RGBVec3; readonly mediumturquoise: RGBVec3; readonly mediumvioletred: RGBVec3; readonly midnightblue: RGBVec3; readonly mintcream: RGBVec3; readonly mistyrose: RGBVec3; readonly moccasin: RGBVec3; readonly navajowhite: RGBVec3; readonly navy: RGBVec3; readonly oldlace: RGBVec3; readonly olive: RGBVec3; readonly olivedrab: RGBVec3; readonly orange: RGBVec3; readonly orangered: RGBVec3; readonly orchid: RGBVec3; readonly palegoldenrod: RGBVec3; readonly palegreen: RGBVec3; readonly paleturquoise: RGBVec3; readonly palevioletred: RGBVec3; readonly papayawhip: RGBVec3; readonly peachpuff: RGBVec3; readonly peru: RGBVec3; readonly pink: RGBVec3; readonly plum: RGBVec3; readonly powderblue: RGBVec3; readonly purple: RGBVec3; readonly rebeccapurple: RGBVec3; readonly red: RGBVec3; readonly rosybrown: RGBVec3; readonly royalblue: RGBVec3; readonly saddlebrown: RGBVec3; readonly salmon: RGBVec3; readonly sandybrown: RGBVec3; readonly seagreen: RGBVec3; readonly seashell: RGBVec3; readonly sienna: RGBVec3; readonly silver: RGBVec3; readonly skyblue: RGBVec3; readonly slateblue: RGBVec3; readonly slategray: RGBVec3; readonly slategrey: RGBVec3; readonly snow: RGBVec3; readonly springgreen: RGBVec3; readonly steelblue: RGBVec3; readonly tan: RGBVec3; readonly teal: RGBVec3; readonly thistle: RGBVec3; readonly tomato: RGBVec3; readonly turquoise: RGBVec3; readonly violet: RGBVec3; readonly wheat: RGBVec3; readonly white: RGBVec3; readonly whitesmoke: RGBVec3; readonly yellow: RGBVec3; readonly yellowgreen: RGBVec3; }>; //#endregion //#region lib.d.ts declare namespace color { function parse(cstr: string | number): ParseResult; const names: typeof _default; /** * Resolves a CSS `` string (or `0xRRGGBB` number) to its canonical * sRGB form as a branded {@link colorformats.RGB888A32F} — without a DOM * or canvas. Sits on top of {@link parse}; parse behavior is the source * of truth for what each *accepted* syntax means. * * **Resolvable** (returns a value): * - named colors (`"red"`, `"REBECCAPURPLE"`) and `"transparent"` * (resolves to `{ r: 0, g: 0, b: 0, a: 0 }` per CSS) * - 3/4/6/8-digit hex (`"#f00"`, `"#ff000080"`) * - `rgb()` / `rgba()` (number or percentage channels, comma or slash syntax) * - `hsl()` / `hsla()` — converted per CSS Color 4 §7 * - `hwb()` — converted per CSS Color 4 §8 * - numbers, read as `0xRRGGBB` * * **Not resolvable** (returns `null`, never a guess): * - `currentColor` and other context-dependent keywords * - `lab()` / `lch()` / `oklab()` / `oklch()` / `color()` — gamut mapping * is out of scope * - `cmyk` / `gray` / other non-CSS spaces {@link parse} understands * - malformed or unparseable input * * **Strict input gate.** The null-never-guess contract is enforced by * `resolve` itself, up front; the underlying {@link parse} stays * permissive (it coerces, truncates and guesses, as vendored). A string * must take one of these *forms* (after trimming, case-insensitive): * - a named color present in {@link names} (own keys only — prototype * keys like `"constructor"` are not colors), or `"transparent"` * - `#` + exactly 3/4/6/8 hex digits — `"#zzz"`, `"###"`, `"#12345"`, * `"#1234567"` are all `null` * - `rgb()` / `rgba()` / `hsl()` / `hsla()` / `hwb()` with the `(` * directly after the function name (no whitespace, per CSS) and a * properly closed `)` — `"rgb(255,0,0"` is `null` * * Bare channel lists (`"1 2 3"`, `"255, 0, 0"`) are not CSS colors and * resolve to `null`. * * **Separator discipline.** Inside an accepted function form, the * argument body must follow a single separator style — legacy * all-comma (`N, N, N[, A]`) or modern all-space (`N N N [/ A]`) — * never a mix: `"rgb(25 5, 0, 0)"` is `null` (the permissive parser * would smuggle the 4th token into alpha). Alpha in the modern form * requires the slash (`"rgb(255 0 0 0.5)"` is `null`); slash alpha in * the legacy form is likewise rejected (`"rgb(255, 0, 0 / 0.5)"`). * `hwb()` takes the modern form only — it postdates the comma syntax * and `"hwb(0, 0%, 0%)"` is invalid in every browser. * * **Channel tokens.** Every channel token must be a CSS `` * (optional sign, digits required after a decimal dot, scientific * notation with *required* exponent digits — `"rgb(1e 0 0)"` is * `null`) with an optional `%` suffix. Stray units reject in every * position — channel (`"rgb(1px 0 0)"`, `"rgb(1px, 0, 0)"`) and * alpha (`"rgb(255 0 0 / 1px)"`) alike — exactly as browsers reject * the declaration; the permissive parser would `parseFloat` the unit * away. `none` is a valid *missing* channel in any position, alpha * included, but in the modern syntax only (CSS Color 4): * `"rgb(none 0 0)"` resolves (missing reads as 0, as in browsers), * `"rgb(none, 0, 0)"` is `null`. * * **Numeric hue.** The hue channel of `hsl()` / `hsla()` / `hwb()` * keeps its own token rule in place of the generic one: a CSS * number, optionally with one of the angle units the vendored * parser actually converts — `deg`, `grad`, `rad`, `turn` — or * `none` (modern form only). Named hues (`"hsl(red 100% 50%)"`) * are rejected: `` comes from a long-dropped css-color * draft, no browser accepts it, and the parser's table values are * not CSS hue degrees. Percentage hue (`"hsl(50% 100% 50%)"`) is * rejected — the parser would misread it as a 0-100 hue. A bare * trailing dot is not a CSS number (`"hsl(120. 50% 50%)"` is * `null`). * * Channel arity is enforced by the discipline patterns themselves * (`"rgb(1, 2)"`, `"rgb(1, 2, 3, 4, 5)"` are `null`). Token * *interpretation* within the validated shape still defers to * {@link parse}: cross-token grammar is not enforced, so legacy * bodies mixing `` and `` (`"rgb(50%, 0, 0)"`) * or unitless hsl saturation/lightness (read as percent) resolve * permissively rather than `null`. * * A number must be an integer in `[0x000000, 0xFFFFFF]` — negative, * fractional, `NaN`, non-finite, or `> 0xFFFFFF` values are `null` * (never truncated or wrapped). * * Edge semantics (matching browser behavior): * - input is trimmed and case-insensitive * - hue wraps mod 360 (negative hues allowed) * - saturation / lightness / whiteness / blackness clamp to [0, 100] * - rgb channels clamp to [0, 255] and round to integers * - alpha clamps to [0, 1] * - never throws; invalid input resolves to `null` * * @param cstr - CSS color string, or a number read as `0xRRGGBB`. * @returns The resolved color, or `null` when resolution requires a * rendering context or the space is out of scope. * * @example * ```typescript * resolve("hsl(217 91% 60%)"); // { r: 60, g: 131, b: 246, a: 1 } * resolve("rgba(255, 0, 0, 0.5)"); // { r: 255, g: 0, b: 0, a: 0.5 } * resolve("transparent"); // { r: 0, g: 0, b: 0, a: 0 } * resolve("currentColor"); // null * resolve("oklch(0.7 0.1 200)"); // null * ``` */ function resolve(cstr: string | number): colorformats.RGB888A32F | null; /** * Resolves a CSS `` string (or `0xRRGGBB` number) to a lowercase * hex string. Sugar over {@link resolve} — same resolvable set, same * sentinel. * * Alpha is quantized to 8 bits, and the 6-vs-8-digit decision is made on * the *quantized* byte so the canonical form is unique per quantized * color: `#rrggbb` whenever the alpha byte is `0xff`, `#rrggbbaa` * otherwise. (`resolveHEX("rgba(255, 0, 0, 0.999)")` is `"#ff0000"` — * never `"#ff0000ff"`, which would be a second spelling of the same * quantized color.) * * @param cstr - CSS color string, or a number read as `0xRRGGBB`. * @returns `"#rrggbb"` | `"#rrggbbaa"`, or `null` when not resolvable. * * @example * ```typescript * resolveHEX("hsl(217 91% 60%)"); // "#3c83f6" * resolveHEX("red"); // "#ff0000" * resolveHEX("rgba(255, 0, 0, 0.5)"); // "#ff000080" * resolveHEX("rgba(255, 0, 0, 0.999)"); // "#ff0000" (alpha byte rounds to 0xff) * resolveHEX("definitely-not-a-color"); // null * ``` */ function resolveHEX(cstr: string | number): string | null; namespace colorformats { export type ColorComponentFormat = "u8" | "f32"; export type RGB_UNKNOWN = { r: number; g: number; b: number; }; /** * the RGBA structure itself. the rgb value may differ as it could both represent 0-1 or 0-255 by the context. */ export type RGBA_UNKNOWN = { r: number; g: number; b: number; a: number; }; const RGBA32F_BRAND: unique symbol; const RGBA8888_BRAND: unique symbol; const RGB888A32F_BRAND: unique symbol; export type RGBA32F = { /** * 0.0-1.0 */ r: number; /** * 0.0-1.0 */ g: number; /** * 0.0-1.0 */ b: number; /** * 0.0-1.0 */ a: number; } & { [RGBA32F_BRAND]: true; }; export type RGBA8888 = { /** * 0-255 */ r: number; /** * 0-255 */ g: number; /** * 0-255 */ b: number; /** * 0-255 */ a: number; } & { [RGBA8888_BRAND]: true; }; export type RGB888A32F = { /** * 0-255 */ r: number; /** * 0-255 */ g: number; /** * 0-255 */ b: number; /** * 0.0-1.0 */ a: number; } & { [RGB888A32F_BRAND]: true; }; export function newRGBA32F(r: number, g: number, b: number, a: number): RGBA32F; export function newRGBA8888(r: number, g: number, b: number, a: number): RGBA8888; export function newRGB888A32F(r: number, g: number, b: number, a: number): RGB888A32F; export function intoCSSRGB(color: RGB_UNKNOWN, unit: ColorComponentFormat): string; /** * @param color - The input color to convert. * @param format - The input format to convert from. * @returns The output color as a tuple of [r, g, b] or [r, g, b, a]. (always in u8 range) */ export function intoU8Chunk(color: RGB_UNKNOWN | RGBA_UNKNOWN, format: ColorComponentFormat): [number, number, number] | [number, number, number, number]; export function fromHEXIntoU8Chunk(hex: string): [number, number, number] | [number, number, number, number]; export namespace RGBA32F { const TRANSPARENT: RGBA32F; const BLACK: RGBA32F; const WHITE: RGBA32F; const GRAY: RGBA32F; /** * #F5F5F5 */ const WHITESMOKE: RGBA32F; function intoHEX(color: RGBA32F): string; /** * Converts a HEX color string to an RGB888A32F object. * * Supports both short (`#RGB`) and long (`#RRGGBB`) HEX formats. * * @param hex - The HEX color string to convert. Must start with `#` and be 3 or 6 characters long after the `#`. * @returns An object containing `r`, `g`, `b`, and `a` properties. * * @throws {Error} If the input HEX string is invalid. * * @example * ```typescript * fromHEX("#F80"); // { r: 255, g: 136, b: 0, a: 1 } * fromHEX("#FF8800"); // { r: 255, g: 136, b: 0, a: 1 } * ``` */ function fromHEX(hex: string): RGBA32F; function intoCSSRGBA(color: RGBA32F): string; function intoRGBA8888(color: RGBA32F): RGBA8888; function intoRGB888F32A(color: RGBA32F): RGB888A32F; function multiplyA32(color: RGBA32F, alpha?: number): RGBA32F; } export namespace RGBA8888 { const BLACK: RGBA8888; const WHITE: RGBA8888; function intoHEX(color: RGBA8888): string; function intoRGB888F32A(color: RGBA8888): RGB888A32F; } export namespace RGB888A32F { const TRANSPARENT: RGB888A32F; const BLACK: RGB888A32F; const WHITE: RGB888A32F; const GRAY: RGB888A32F; function intoHEX(color: RGB888A32F): string; /** * Converts a HEX color string to an RGB888A32F object. * * Supports both short (`#RGB`) and long (`#RRGGBB`) HEX formats. * * @param hex - The HEX color string to convert. Must start with `#` and be 3 or 6 characters long after the `#`. * @returns An object containing `r`, `g`, `b`, and `a` properties. * * @throws {Error} If the input HEX string is invalid. * * @example * ```typescript * fromHEX("#F80"); // { r: 255, g: 136, b: 0, a: 1 } * fromHEX("#FF8800"); // { r: 255, g: 136, b: 0, a: 1 } * ``` */ function fromHEX(hex: string): RGB888A32F; function intoCSSRGBA(color: RGB888A32F): string; function intoRGBA8888(color: RGB888A32F): RGBA8888; function intoRGBA32F(color: RGB888A32F): RGBA32F; function multiplyA32(color: RGB888A32F, alpha?: number): RGB888A32F; } export {}; } } //#endregion //#region ../grida-canvas-cg/dist/index.d.ts //#region lib.d.ts /** * Core Graphics */ declare namespace cg { type Vector2 = [number, number]; /** * A 2D Affine Transformation Matrix. * * This matrix is used to perform linear transformations (e.g., scaling, rotation, shearing) * and translations in a two-dimensional coordinate space. It is a compact representation * of a transformation that maps points, lines, or shapes from one space to another while * preserving straight lines and parallelism. * * ### Matrix Structure: * The affine transform matrix is a 2x3 matrix, represented as: * * ``` * [ * [a, b, tx], * [c, d, ty] * ] * ``` * * ### Components: * - **a, d**: Scaling factors along the x-axis and y-axis, respectively. * - `a`: Horizontal scale (x-axis stretch/shrink factor). * - `d`: Vertical scale (y-axis stretch/shrink factor). * - **b, c**: Shearing (skewing) factors. * - `b`: Horizontal skewing (how much the y-axis tilts along the x-axis). * - `c`: Vertical skewing (how much the x-axis tilts along the y-axis). * - **tx, ty**: Translation offsets. * - `tx`: Horizontal translation (movement along the x-axis). * - `ty`: Vertical translation (movement along the y-axis). * * ### Transformations: * Affine transforms combine multiple transformations into a single operation. Examples include: * - **Translation**: Moving a shape by tx and ty. * - **Scaling**: Resizing along x and y axes. * - **Rotation**: Rotating around the origin by combining scaling and skewing. * - **Shearing**: Slanting a shape along one or both axes. * * ### Applying the Transformation: * To transform a 2D point `[x, y]`, append a constant `1` to form `[x, y, 1]`, * then multiply by the matrix: * * ``` * [x', y', 1] = [ * [a, b, tx], * [c, d, ty] * ] * [x, y, 1] * * Result: * x' = a * x + b * y + tx * y' = c * x + d * y + ty * ``` * * The transformed point `[x', y']` represents the new coordinates. * * ### Notes: * - This matrix supports 2D transformations only. * - It assumes homogeneous coordinates for points (i.e., the constant `1` in `[x, y, 1]`). * - For transformations in 3D space, a 4x4 matrix must be used instead. */ type AffineTransform = [[number, number, number], [number, number, number]]; /** * the RGBA structure itself. the rgb value may differ as it could both represent 0-1 or 0-255 by the context. */ type RGBA_UNKNOWN = { r: number; g: number; b: number; a: number; }; /** * Standard f32 RGBA (Normalized RGBA) * Used in computer graphics pipelines, shading, and rendering. */ type RGBA32F = color.colorformats.RGBA32F; /** * 8-bit Integer RGB, f32 alpha (CSS' rgba format) */ type RGB888A32F = color.colorformats.RGB888A32F; type LayerMaskType = "geometry" | ImageMaskType; type ImageMaskType = "alpha" | "luminance"; /** * only applicable to layers, not paints. * if this is used for non supported, it will fallback to "normal". */ type LayerBlendMode = "pass-through" | BlendMode; /** * @see https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode */ type BlendMode = "normal" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity"; /** * Defines a single path * * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path */ type Path = { /** * This attribute defines the shape of the path. */ d: string; /** * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule */ fill_rule: FillRule; }; /** * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule * @see https://www.figma.com/plugin-docs/api/properties/VectorPath-windingrule/ */ type FillRule = "nonzero" | "evenodd"; /** * @see https://skia.org/docs/dev/present/pathops/ * @see https://www.figma.com/plugin-docs/api/BooleanOperationNode/ */ type BooleanOperation = "difference" | "intersection" | "union" | "xor"; /** * * Supported fit modes * * Only `contain` and `cover`, `none` are supported in the current version. * * - `none` may have unexpected results by the environment * * @see https://api.flutter.dev/flutter/painting/BoxFit.html * @see https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit */ type BoxFit = "contain" | "cover" | "fill" | "none"; /** * Supported stoke cap modes * * - `butt` * - `round` * - `square` * * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap * @see https://api.flutter.dev/flutter/dart-ui/StrokeCap.html */ type StrokeCap = "butt" | "round" | "square"; /** * Built-in marker presets placed at stroke endpoints or vector vertices. * * Unlike {@link StrokeCap} (which maps to native backend caps like Skia PaintCap), * StrokeMarkerPreset represents explicit marker geometry drawn on top of the * stroke path. When a preset is present at an endpoint, the renderer * uses Butt cap at that endpoint and draws the marker geometry instead. * * @see docs/wg/feat-2d/curve-decoration.md */ type StrokeMarkerPreset = "none" | "right_triangle_open" | "equilateral_triangle" | "circle" | "square" | "diamond" | "vertical_bar"; /** * Supported stroke join modes * * - `miter` * - `round` * - `bevel` * * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linejoin * @see https://api.flutter.dev/flutter/dart-ui/StrokeJoin.html */ type StrokeJoin = "miter" | "round" | "bevel"; /** * @see https://www.figma.com/plugin-docs/api/properties/nodes-strokealign/ * @see https://api.flutter.dev/flutter/painting/BorderSide/strokeAlign.html */ type StrokeAlign = "center" | "inside" | "outside"; /** * * Supported text decoration modes * * Only `underline` and `none` are supported in the current version. * * @see https://api.flutter.dev/flutter/dart-ui/TextDecoration-class.html * @see https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration */ type TextDecorationLine = "none" | "underline" | "overline" | "line-through"; /** * @see https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-style */ type TextDecorationStyle = "solid" | "double" | "dotted" | "dashed" | "wavy"; /** * @see https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-color */ type TextDecorationColor = "currentcolor" | cg.RGBA32F; type TextDecorationColorValue = cg.RGBA32F; /** * @see https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-skip-ink */ type TextDecorationSkipInk = "auto" | "none"; type TextDecorationSkipInkFlag = boolean; /** * @see https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-thickness * * auto or thickness in percentage */ type TextDecorationThicknessPercentage = "auto" | number; /** * Text transform modes * * - `none` * - `uppercase` * - `lowercase` * - `capitalize` * * @see https://developer.mozilla.org/en-US/docs/Web/CSS/text-transform */ type TextTransform = "none" | "uppercase" | "lowercase" | "capitalize"; /** * Supported text align modes * * Does not support `start` and `end` as they are not supported in the current version. * * @see https://developer.mozilla.org/en-US/docs/Web/CSS/text-align * @see https://api.flutter.dev/flutter/dart-ui/TextAlign.html */ type TextAlign = "left" | "right" | "center" | "justify"; /** * Vertical text align modes * * - [Env:css] in css, uses `align-content` * * @see https://developer.mozilla.org/en-US/docs/Web/CSS/align-content * @see https://konvajs.org/api/Konva.Text.html#verticalAlign */ type TextAlignVertical = "top" | "center" | "bottom"; /** * A styled text run within an attributed string. * * Offsets (`start`, `end`) are **JS character indices** (UTF-16 code units) * into the parent text string. The FBS codec converts these to/from the * UTF-8 byte offsets that the Rust side expects. */ interface StyledTextRun { /** Inclusive start character index. */ start: number; /** Exclusive end character index. */ end: number; /** Per-run fill paints override. When absent, node-level fills are used. */ fill_paints?: cg.Paint[]; /** Per-run stroke paints override. */ stroke_paints?: cg.Paint[]; /** Per-run stroke width override. */ stroke_width?: number; /** Per-run stroke alignment override. */ stroke_align?: cg.StrokeAlign; } /** * Open type feature tags * * @see https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist * @see https://learn.microsoft.com/en-us/typography/opentype/spec/featuretags * @see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/OpenType_fonts_guide */ type OpenTypeFeature = "aalt" | "abrv" | "abvm" | "abvs" | "afrc" | "akhn" | "blwf" | "blwm" | "blws" | "c2pc" | "calt" | "case" | "ccmp" | "cfar" | "cjct" | "clig" | "cpct" | "cpsp" | "cswh" | "curs" | "cv01" | "cv02" | "cv03" | "cv04" | "cv05" | "cv06" | "cv07" | "cv08" | "cv09" | "cv10" | "cv11" | "cv12" | "cv13" | "cv14" | "cv15" | "cv16" | "cv17" | "cv18" | "cv19" | "cv20" | "cv21" | "cv22" | "cv23" | "cv24" | "cv25" | "cv26" | "cv27" | "cv28" | "cv29" | "cv30" | "cv31" | "cv32" | "cv33" | "cv34" | "cv35" | "cv36" | "cv37" | "cv38" | "cv39" | "cv40" | "cv41" | "cv42" | "cv43" | "cv44" | "cv45" | "cv46" | "cv47" | "cv48" | "cv49" | "cv50" | "cv51" | "cv52" | "cv53" | "cv54" | "cv55" | "cv56" | "cv57" | "cv58" | "cv59" | "cv60" | "cv61" | "cv62" | "cv63" | "cv64" | "cv65" | "cv66" | "cv67" | "cv68" | "cv69" | "cv70" | "cv71" | "cv72" | "cv73" | "cv74" | "cv75" | "cv76" | "cv77" | "cv78" | "cv79" | "cv80" | "cv81" | "cv82" | "cv83" | "cv84" | "cv85" | "cv86" | "cv87" | "cv88" | "cv89" | "cv90" | "cv91" | "cv92" | "cv93" | "cv94" | "cv95" | "cv96" | "cv97" | "cv98" | "cv99" | "dist" | "dlig" | "dnom" | "dtls" | "expt" | "falt" | "fin2" | "fin3" | "fina" | "flac" | "fwid" | "half" | "haln" | "halt" | "hist" | "hkna" | "hlig" | "hngl" | "hojo" | "hwid" | "init" | "isol" | "ital" | "jp04" | "jp78" | "jp83" | "jp90" | "just" | "kern" | "lfbd" | "liga" | "ljmo" | "locl" | "ltra" | "ltrm" | "mark" | "med2" | "medi" | "mgrk" | "mkmk" | "nalt" | "nlck" | "nukt" | "numr" | "opbd" | "ordn" | "ornm" | "palt" | "pcap" | "pkna" | "pref" | "pres" | "pstf" | "psts" | "pwid" | "qwid" | "rand" | "rclt" | "rkrf" | "rlig" | "rphf" | "rtbd" | "rtla" | "rtlm" | "ruby" | "rvrn" | "salt" | "sinf" | "size" | "smpl" | "ss01" | "ss02" | "ss03" | "ss04" | "ss05" | "ss06" | "ss07" | "ss08" | "ss09" | "ss10" | "ss11" | "ss12" | "ss13" | "ss14" | "ss15" | "ss16" | "ss17" | "ss18" | "ss19" | "ss20" | "ssty" | "stch" | "swsh" | "titl" | "tjmo" | "tnam" | "trad" | "twid" | "unic" | "valt" | "vatu" | "vert" | "vhal" | "vjmo" | "vkna" | "vkrn" | "vpal" | "vrt2" | "vrtr" | "zero"; /** * Supported font weights in numeric values * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight * @see https://api.flutter.dev/flutter/dart-ui/FontWeight-class.html * @see https://learn.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass */ type NFontWeight = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; /** * css font-style values */ type CSSFontStyle = "normal" | "italic" | "oblique"; /** * Optical sizing modes * * - `"auto"` links optical size to `fontSize` * - `"none"` disables optical sizing * - `number` sets a fixed optical size value * * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font-optical-sizing */ type OpticalSizing = "auto" | "none" | number; /** * Font kerning modes * * - `"normal"` enables kerning * - `"none"` disables kerning * * @remark we don't support `auto`. use `normal` instead. * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font-kerning * @default `auto` */ type FontKerning = "normal" | "none"; /** * Font kerning flag * * @see {@link FontKerning} * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font-kerning * @default `true` */ type FontKerningFlag = boolean; /** * @see https://api.flutter.dev/flutter/painting/Axis.html */ type Axis = "horizontal" | "vertical"; /** * @see https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content * @see https://developer.mozilla.org/en-US/docs/Glossary/Main_Axis * @see https://api.flutter.dev/flutter/rendering/MainAxisAlignment.html */ type MainAxisAlignment = "start" | "end" | "center" | "space-between" | "space-around" | "space-evenly" | "stretch"; /** * @see https://developer.mozilla.org/en-US/docs/Web/CSS/align-items * @see https://developer.mozilla.org/en-US/docs/Glossary/Cross_Axis * @see https://api.flutter.dev/flutter/rendering/CrossAxisAlignment.html */ type CrossAxisAlignment = "start" | "end" | "center" | "stretch"; /** * @see https://developer.mozilla.org/en-US/docs/Web/CSS/cursor * @see https://api.flutter.dev/flutter/services/SystemMouseCursors-class.html */ type SystemMouseCursor = "alias" | "all-scroll" | "auto" | "cell" | "col-resize" | "context-menu" | "copy" | "crosshair" | "default" | "e-resize" | "ew-resize" | "grab" | "grabbing" | "help" | "move" | "n-resize" | "ne-resize" | "nesw-resize" | "no-drop" | "none" | "not-allowed" | "ns-resize" | "nw-resize" | "nwse-resize" | "pointer" | "progress" | "row-resize" | "s-resize" | "se-resize" | "sw-resize" | "text" | "vertical-text" | "w-resize" | "wait" | "zoom-in" | "zoom-out"; /** * A line segment in 2D space defined by two points. * * This type provides a generic structure for representing a line segment. * The coordinate system and scale of the values depend on the usage context: * - May represent **normalized coordinates** (e.g., 0.0 to 1.0 or -1.0 to 1.0) * - May represent **relative coordinates** within a parent container * - May represent **absolute pixel values** * * Always refer to the specific API documentation to understand which coordinate * system is being used. */ type Line2D = { /** * X coordinate of the line's start point */ x1: number; /** * Y coordinate of the line's start point */ y1: number; /** * X coordinate of the line's end point */ x2: number; /** * Y coordinate of the line's end point */ y2: number; }; /** * A point within a rectangle using a normalized coordinate system. * * `Alignment(0.0, 0.0)` represents the center of the rectangle. The distance from -1.0 to +1.0 * is the distance from one side of the rectangle to the other side of the rectangle. Therefore, * 2.0 units horizontally (or vertically) is equivalent to the width (or height) of the rectangle. * * ### Coordinate System: * - `Alignment(-1.0, -1.0)` represents the top left of the rectangle. * - `Alignment(1.0, 1.0)` represents the bottom right of the rectangle. * - `Alignment(0.0, 0.0)` represents the center of the rectangle. * * ### Examples: * - `Alignment(0.0, 3.0)` represents a point that is horizontally centered with respect to * the rectangle and vertically below the bottom of the rectangle by the height of the rectangle. * - `Alignment(0.0, -0.5)` represents a point that is horizontally centered with respect to * the rectangle and vertically half way between the top edge and the center. * * ### Mapping to Pixel Coordinates: * `Alignment(x, y)` in a rectangle with height `h` and width `w` describes the point * `(x * w/2 + w/2, y * h/2 + h/2)` in the coordinate system of the rectangle. * * @see https://api.flutter.dev/flutter/painting/Alignment-class.html * @see https://developer.mozilla.org/en-US/docs/Web/CSS/percentage */ type Alignment = { /** * Horizontal alignment value. * - `-1.0` = left edge * - `0.0` = horizontal center * - `1.0` = right edge */ x: number; /** * Vertical alignment value. * - `-1.0` = top edge * - `0.0` = vertical center * - `1.0` = bottom edge */ y: number; }; type Paint = SolidPaint | LinearGradientPaint | RadialGradientPaint | SweepGradientPaint | DiamondGradientPaint | ImagePaint; namespace paints { const transparent: Paint; const black: Paint; const white: Paint; } type AnyPaint = Omit & Partial & Partial & Partial & Partial & Partial, "type"> & { type: Paint["type"]; }; type SolidPaint = { type: "solid"; color: cg.RGBA32F; blend_mode?: cg.BlendMode; active: boolean; }; type GradientPaint = LinearGradientPaint | RadialGradientPaint | SweepGradientPaint | DiamondGradientPaint; function isSolidPaint(paint?: cg.Paint): paint is cg.SolidPaint; function isImagePaint(paint?: cg.Paint): paint is cg.ImagePaint; function isGradientPaint(paint?: cg.Paint): paint is cg.GradientPaint; type LinearGradientPaint = { type: "linear_gradient"; /** * Gradient start point in UV [0,1] space. * @default [0, 0.5] (center-left) */ xy1?: [number, number]; /** * Gradient end point in UV [0,1] space. * @default [1, 0.5] (center-right) */ xy2?: [number, number]; transform: AffineTransform; stops: Array; /** * @default "normal" {@link cg.def.BLENDMODE} */ blend_mode: cg.BlendMode; /** * @default 1 */ opacity: number; active: boolean; }; type RadialGradientPaint = { type: "radial_gradient"; transform: AffineTransform; stops: Array; /** * @default "normal" {@link cg.def.BLENDMODE} */ blend_mode: cg.BlendMode; /** * @default 1 */ opacity: number; active: boolean; }; type SweepGradientPaint = { type: "sweep_gradient"; transform: AffineTransform; stops: Array; /** * @default "normal" {@link cg.def.BLENDMODE} */ blend_mode: cg.BlendMode; /** * @default 1 */ opacity: number; active: boolean; }; type DiamondGradientPaint = { type: "diamond_gradient"; transform: AffineTransform; stops: Array; /** * @default "normal" {@link cg.def.BLENDMODE} */ blend_mode: cg.BlendMode; /** * @default 1 */ opacity: number; active: boolean; }; type ImagePaint = { type: "image"; src: string; /** * box fit or custom transform */ fit: "contain" | "cover" | "fill" | "none" | "transform" | "tile"; /** * transform will only take effect if fit is "transform" * * @default identity */ transform?: AffineTransform; /** * Number of clockwise quarter turns to apply to the decoded image before layout math. * The value is normalized modulo 4 (`0` = 0°, `1` = 90° CW, `2` = 180°, `3` = 270° CW). * This discrete rotation keeps pixels on the integer grid without resampling. * * @default 0 */ quarter_turns?: number; /** * when mode is "tile", scale the image to the given value. * * @default 1 */ scale?: number; filters: ImageFilters; /** * @default "normal" {@link cg.def.BLENDMODE} */ blend_mode: cg.BlendMode; /** * @default 1 */ opacity: number; active: boolean; }; interface ImageFilters { /** * Exposure adjustment (-1.0 to 1.0, default: 0.0) * Controls the overall brightness of the image. * - -1.0 = very dark * - 0.0 = original (no change) * - 1.0 = very bright */ exposure: number; /** * Contrast adjustment (-0.3 to 0.3, default: 0.0) * Controls the difference between light and dark areas. * - -0.3 = low contrast (UI cap) * - 0.0 = original contrast * - 0.3 = high contrast (UI cap) */ contrast: number; /** * Saturation adjustment (-1.0 to 1.0, default: 0.0) * Controls the intensity of colors. * - -1.0 = grayscale (no color) * - 0.0 = original saturation * - 1.0 = highly oversaturated */ saturation: number; /** * Temperature adjustment (-1.0 to 1.0, default: 0.0) * Controls the warm/cool color balance. * - -1.0 = very cool (blue tint) * - 0.0 = neutral (no change) * - 1.0 = very warm (orange tint) */ temperature: number; /** * Tint adjustment (-1.0 to 1.0, default: 0.0) * Controls the green/magenta color balance. * - -1.0 = strong magenta tint * - 0.0 = neutral (no change) * - 1.0 = strong green tint */ tint: number; /** * Highlights adjustment (-1.0 to 1.0, default: 0.0) * Controls the brightness of highlight areas. * - -1.0 = darken highlights * - 0.0 = no change * - 1.0 = brighten highlights */ highlights: number; /** * Shadows adjustment (-1.0 to 1.0, default: 0.0) * Controls the brightness of shadow areas. * - -1.0 = darken shadows * - 0.0 = no change * - 1.0 = brighten shadows */ shadows: number; } type GradientStop = { /** * 0-1 * 0 - start (0%) * 1 - end (100%) */ offset: number; color: cg.RGBA32F; }; /** * Box shadow definition compatible with both CSS and advanced blur configurations. * * @see https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow * @see https://api.flutter.dev/flutter/painting/BoxShadow-class.html */ type BoxShadow = { /** * The color of the shadow. * Defaults to the current color if not provided. */ color: RGBA32F; /** * The horizontal and vertical offset of the shadow. * Example: `[x: number, y: number]` or for no shadow offset. * * @default [0, 0] */ offset: Vector2; /** * The blur radius of the shadow. * - Specifies the amount of blur applied to the shadow. * - Must be >= 0. * * @default 0 */ blur: number; /** * The spread radius of the shadow. * - Positive values expand the shadow. * - Negative values shrink the shadow. * - Defaults to 0. * * @default 0 */ spread: number; }; /** * * * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow */ interface IFeShadow { /** * offset-x */ dx: number; /** * offset-y */ dy: number; /** * blur radius * * a.k.a. stdDeviation in SVG */ blur: number; spread: number; color: RGBA32F; /** * Whether this effect is active * @default true */ active?: boolean; } interface IFeGaussianBlur { /** * blur radius * * a.k.a. stdDeviation in SVG */ radius: number; } type FeGaussianBlur = IFeGaussianBlur & { type: "blur"; }; /** * Progressive blur effect that interpolates blur radius along a line. * * Extends {@link Line2D} using **normalized coordinates** (-1.0 to 1.0). * The blur gradually transitions from `radius` at the start point to `radius2` at the end point. * * ### Coordinate System: * - `x1`, `y1`: Start point in normalized coordinates * - `x2`, `y2`: End point in normalized coordinates * - The coordinate system follows the same convention as {@link Alignment}: * - `-1.0` to `1.0` spans the full width/height * - `(0.0, 0.0)` is the center */ interface IFeProgressiveBlur extends Line2D { /** * Blur radius at the start point (x1, y1) in pixels */ radius: number; /** * Blur radius at the end point (x2, y2) in pixels */ radius2: number; } type FeProgressiveBlur = IFeProgressiveBlur & { type: "progressive-blur"; }; type FilterEffect = FeShadow | FeLayerBlur | FeBackdropBlur | FeLiquidGlass | FeNoise; type FeShadow = IFeShadow & { type: "shadow"; inset?: boolean; }; type FeBlur = FeGaussianBlur | FeProgressiveBlur; type FeLayerBlur = { type: "filter-blur"; blur: FeBlur; /** * Whether this effect is active * @default true */ active?: boolean; }; type FeBackdropBlur = { type: "backdrop-filter-blur"; blur: FeBlur; /** * Whether this effect is active * @default true */ active?: boolean; }; /** * Liquid Glass Effect * * A visual effect that creates a realistic glass appearance with light refraction, * highlights, and chromatic aberration. */ type FeLiquidGlass = { type: "glass"; /** * The intensity of specular highlights. * Must be between 0 and 1. Higher values create brighter highlights. * * Adjust the angle and intensity of the light illuminating your glass containers * to change where the highlight appears on the container's edge. * * @default 0.9 */ light_intensity: number; /** * The angle of the specular light in degrees. * Controls the direction of highlights on the glass surface. * * @default 45.0 */ light_angle: number; /** * Refraction strength [0.0-1.0] * 0.0 = no refraction, 0.5 = typical glass, 1.0 = maximum refraction * Internally mapped to IOR range [1.0-2.0] * * Control the way light bends along the edge of your glass container. * The higher the refraction value, the more your glass containers will * distort the elements around them. * * @default 0.8 */ refraction: number; /** * Glass thickness/depth for 3D surface effect in pixels [1.0+] * Controls the curvature height of the glass surface * Higher values create more pronounced lens curvature and stronger refraction * Typical values: 20-100 pixels * * @default 20.0 */ depth: number; /** * Chromatic aberration strength [0.0-1.0] * Controls color separation at edges (rainbow effect) * Higher values create more rainbow-like distortion at edges. * * @default 0.5 */ dispersion: number; /** * Blur radius for frosted glass effect [0.0+] in pixels * Applied via Skia's native blur before refraction shader * * @default 4.0 */ radius: number; /** * Whether this effect is active * @default true */ active?: boolean; }; /** * Procedural Noise Effect * * Generates Perlin noise patterns with configurable grain size, density, and coloring. * Supports three coloring modes: single-color (mono), dual-color (duo), and multi-color (multi). * * Uses fractal Perlin noise for natural-looking texture with configurable detail through octaves. * * @example * ```typescript * // Black film grain overlay * const grain: FeNoise = { * type: "noise", * mode: "mono", * noise_size: 0.3, * density: 0.8, * num_octaves: 6, * seed: 42, * color: { r: 0, g: 0, b: 0, a: 0.15 } * }; * * // Red noise pattern on white background * const duo: FeNoise = { * type: "noise", * mode: "duo", * noise_size: 2.0, * density: 0.5, * num_octaves: 3, * seed: 8539, * color1: { r: 255, g: 0, b: 0, a: 1 }, * color2: { r: 255, g: 255, b: 255, a: 0.25 } * }; * * // Colorful noise with full opacity * const multi: FeNoise = { * type: "noise", * mode: "multi", * noise_size: 1.5, * density: 0.7, * num_octaves: 4, * seed: 1234, * opacity: 1.0 * }; * ``` */ type FeNoise = { type: "noise"; /** * Noise coloring mode * - `"mono"`: Single-color noise pattern * - `"duo"`: Dual-color with background and pattern colors * - `"multi"`: Multi-color noise (uses noise RGB directly) */ mode: "mono" | "duo" | "multi"; /** * Controls noise grain size (smaller = finer grains) * Range: 0.001+ * Typical values: 0.3 for fine grain, 2.0 for coarse grain * UI limit: 100 * * @default 0.5 */ noise_size: number; /** * Controls pattern visibility (0 = sparse, 1 = dense) * Range: 0.0 - 1.0 * * @default 0.5 */ density: number; /** * Number of fractal octaves for detail level * Range: 1+ * More octaves = finer detail but more computation * UI limit: 8 * * @default 3 */ num_octaves?: number; /** * Random seed for reproducibility * Different seeds produce different noise patterns * * @default 0 */ seed?: number; /** * Blend mode for compositing noise with fill * * @default "normal" {@link cg.def.BLENDMODE} */ blend_mode?: cg.BlendMode; /** * Color of noise pixels (mono mode only) * Includes alpha for opacity control */ color?: RGBA32F; /** * Pattern color (duo mode only) * Applied where noise is visible */ color1?: RGBA32F; /** * Background color (duo mode only) * Base layer behind the noise pattern */ color2?: RGBA32F; /** * Overall opacity (multi mode only) * Range: 0.0 - 1.0 * * @default 1.0 */ opacity?: number; /** * Whether this effect is active * @default true */ active?: boolean; }; /** * * * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow */ type SVGFeDropShadow = { type: "drop_shadow"; /** * offset-x */ dx: number; /** * offset-y */ dy: number; /** * blur radius * * a.k.a. stdDeviation in SVG */ blur: number; }; type SVGFeGaussianBlur = { type: "blur"; /** * blur radius * * a.k.a. stdDeviation in SVG */ radius: number; }; /** * * [top-left | top-right | bottom-right | bottom-left] */ type CornerRadius4 = [number, number, number, number]; /** * all | <[top-left | top-right | bottom-right | bottom-left]> */ type CornerRadius = number | CornerRadius4; function cornerRadius4Identical(value: CornerRadius4): boolean; type VariableWidthStop = { /** * u(t) * 0-1 * 0 - start (0%) * 1 - end (100%) */ u: number; /** * r px */ r: number; }; type VariableWidthProfile = { stops: VariableWidthStop[]; }; /** * strict, solid default values. * not all types have default values. * * only struct, widely aknowledged, de-facto standard defaults will be set. * * this is aligned in grida crate's default values. */ namespace def { const LAYER_BLENDMODE: cg.LayerBlendMode; const BLENDMODE: cg.BlendMode; const IMAGE_FILTERS: cg.ImageFilters; const ALIGNMENT: { CENTER: { x: number; y: number; }; TOP_LEFT: { x: number; y: number; }; TOP_CENTER: { x: number; y: number; }; TOP_RIGHT: { x: number; y: number; }; CENTER_LEFT: { x: number; y: number; }; CENTER_RIGHT: { x: number; y: number; }; BOTTOM_LEFT: { x: number; y: number; }; BOTTOM_CENTER: { x: number; y: number; }; BOTTOM_RIGHT: { x: number; y: number; }; }; } } //#endregion //#region ../grida-tokens/dist/tokens-x6EP2Gjm.d.ts //#region src/tokens.d.ts declare namespace tokens { export type Token = Primitive | TValueExpression | JSONRef | Literal | ShorthandBooleanBinaryExpression | ShorthandBinaryExpression | BooleanValueExpression | Identifier | PropertyAccessExpression | StringLiteral | TemplateSpan | TemplateExpression | StringValueExpression; /** * Represents a primitive value. * Can be a string, number, or boolean. */ export type Primitive = string | number | boolean; /** * Represents a literal value, which can be any primitive type. */ type Literal = Primitive; export type TValueExpression = JSONRef | StringValueExpression | BooleanValueExpression | NumericValueExpression; /** * Represents a reference to a JSON field. * Depending on usage, the reference can be a name (key) or id. * - When stored in the database, it should be an id. * - When used in the JSON, it should be a key. */ export type JSONRef = { $ref: `#/${PREFIX}${string}`; }; /** * Represents the shorthand syntax supported operators for a expression. */ export type BinaryOperator = NumericBinaryOperator | BooleanBinaryOperator | CoalescingOperator; export const BINARY_OPERATORS: readonly ["==", "!=", ">", "<", ">=", "<=", "&&", "||", "+", "-", "*", "/", "%", "??"]; export const BOOLEAN_BINARY_OPERATORS: readonly ["==", "!=", ">", "<", ">=", "<=", "&&", "||"]; export type BooleanBinaryOperator = "==" | "!=" | ">" | "<" | ">=" | "<=" | "&&" | "||"; export type NumericBinaryOperator = "+" | "-" | "*" | "/" | "%"; export type CoalescingOperator = "??"; export type ShorthandBinaryExpressionLHS = TValueExpression; export type ShorthandBinaryExpressionRHS = TValueExpression; export type ShorthandBinaryExpression = [LHS, BinaryOperator, RHS]; /** * Represents the left-hand side of a condition. * Can be either a field reference or a literal value. */ type ShorthandBooleanBinaryExpressionLHS = JSONRef | Literal; /** * Represents the right-hand side of a condition. * Can be either a field reference or a literal value. */ type ShorthandBooleanBinaryExpressionRHS = JSONRef | Literal; /** * Represents a condition expression, which is a tuple consisting of: * - A left-hand side (ConditionLHS) * - An operator (ConditionOperator) * - A right-hand side (ConditionRHS) */ export type ShorthandBooleanBinaryExpression = [LHS, BooleanBinaryOperator, RHS]; /** * Represents a boolean value descriptor. * Can be either a simple boolean or a condition expression. */ export type BooleanValueExpression = boolean | ShorthandBooleanBinaryExpression; /** * Represents an identifier (variable) in a template. */ export type Identifier = { kind: "Identifier"; name: string; }; /** * Represents a shorthand for accessing properties. * This encapsulates the concept of a property path more effectively. */ export type PropertyAccessExpression = { kind: "PropertyAccessExpression"; expression: Array; }; /** * Represents a string literal. */ export type StringLiteral = { kind: "StringLiteral"; text: string; }; /** * Represents a span in a template, which can be a string literal, an identifier, or a property path literal. */ export type TemplateSpan = StringLiteral | Identifier | PropertyAccessExpression; /** * Represents a template expression consisting of template spans. */ export type TemplateExpression = { kind: "TemplateExpression"; templateSpans: TemplateSpan[]; }; /** * Represents a string value expression, which can be any of the defined types. */ export type StringValueExpression = string | StringLiteral | PropertyAccessExpression | Identifier | TemplateExpression | ShorthandBooleanBinaryExpression; export type NumericLiteral = { kind: "NumericLiteral"; value: number; }; export type NumericValueExpression = number | NumericLiteral | PropertyAccessExpression | ShorthandBinaryExpression | Identifier; export namespace is { /** * Recursively checks if a value is tokenized (an expression). * * A value is considered tokenized if it matches one of the `Token` types defined in `tokens.Token`. * * @template T - The type of the value being checked. * @param value - The value to check. * @returns `true` if the value is a tokenized expression, otherwise `false`. * * @example * const value: tokens.Token = { kind: "Identifier", name: "myVariable" }; * console.log(tokens.is.tokenized(value)); // Output: true * * const plainValue = 42; * console.log(tokens.is.tokenized(plainValue)); // Output: false */ function tokenized(value: T): boolean; function primitive(value?: unknown, checknull?: boolean): value is Primitive; function jsonRef(exp?: tokens.Token | unknown): exp is tokens.JSONRef; function propertyAccessExpression(value?: tokens.Token | unknown): value is tokens.PropertyAccessExpression; function templateExpression(value?: tokens.StringValueExpression | unknown): value is tokens.TemplateExpression; /** * can't be trusted 100%. use this in the safe context. */ function inferredShorthandBinaryExpression(exp: tokens.Token | unknown): exp is tokens.ShorthandBooleanBinaryExpression | tokens.ShorthandBinaryExpression; } export {}; } //#endregion //#endregion //#region ../grida-tokens/dist/utils.d.ts //#region src/utils.d.ts /** * Utility type to create a new type from `T` where: * - Properties specified in the keys `K` retain their original types from `T`. * - All other properties are replaced with the `tokens.Token` type. * * This is useful for scenarios where certain properties in an object should remain fixed, * while others can be dynamic or "tokenizable." * * @template T - The base object type. * @template K - The keys in `T` that should retain their original types. * * @example * // Example 1: Basic Usage * type Original = { * type: "static"; * value: number; * description: string; * }; * * type Tokenized = TokenizableExcept; * // Resulting type: * // { * // type: "static"; // Retained original type * // value: tokens.Token; // Tokenized * // description: tokens.Token; // Tokenized * // } * * @example * // Example 2: Union Types * type UnionType = * | { kind: "a"; data: string } * | { kind: "b"; data: number }; * * type TokenizedUnion = TokenizableExcept; * // Resulting type: * // { * // kind: "a"; // Retained * // data: tokens.Token; // Tokenized * // } | { * // kind: "b"; // Retained * // data: tokens.Token; // Tokenized * // } * * @note * This utility only operates at the top level. It does not recursively tokenize nested properties. */ type TokenizableExcept = { [P in keyof T]: P extends K ? T[P] : tokens.Token }; //#endregion //#endregion //#region ../grida-cmath/dist/index.d.ts //#region index.d.ts /** * cmath, a 2D canvas math module. */ declare namespace cmath { const PI: number; const abs: (x: number) => number; const sqrt: (x: number) => number; const cos: (x: number) => number; const sin: (x: number) => number; const asin: (x: number) => number; const tan: (x: number) => number; /** * Computes the greatest common divisor (GCD) of two integers. * * - Inputs are treated as integers and reduced via `Math.abs`. * - If one input is `0`, this returns `abs(other)` (and returns `1` for `(0, 0)`). * - Throws `TypeError` if either input is not finite. * * @example * ```ts * cmath.gcd(12, 8); // 4 * cmath.gcd(-12, 8); // 4 * ``` */ function gcd(a: number, b: number): number; /** * Computes a normalized integer aspect ratio pair from `(width, height)`. * * This is useful for stable aspect ratios, e.g. `(1920, 1080)` → `[16, 9]`. * * @param width - Width-like quantity (must be finite and > 0). * @param height - Height-like quantity (must be finite and > 0). * @param max_denominator - Upper bound for the returned denominator (default: 1000). */ function aspectRatio(width: number, height: number, max_denominator?: number): [number, number] | undefined; /** * Approximation constant used to convert a circular arc into a cubic Bézier * curve. Commonly known as KAPPA, defined as `4 * (sqrt(2) - 1) / 3`. * * When bending a right angle corner to approximate a quarter circle, the * tangents will have a magnitude of `KAPPA * radius` where the radius is * half of the chosen reference segment's length. */ const KAPPA: number; /** * Represents a single axis in 2D space. * * Also known as horizontal (x-axis) or vertical (y-axis) direction. */ type Axis = "x" | "y"; const counterAxis: (axis: Axis) => "x" | "y"; /** * Represents a single numerical value, often referred to as a scalar in mathematics and computer science. * * Scalars are used to denote quantities without direction, such as magnitude, intensity, or single-dimensional values. * They are fundamental in computations involving vectors, matrices, and transformations. * * @typedef Scalar * * @example * // A scalar value representing magnitude * const magnitude: cmath.Scalar = 5; * * @remarks * Scalars are essential for defining operations on higher-dimensional objects, such as scaling vectors or matrices. * They are typically implemented as `number` in most programming contexts. */ type Scalar = number; /** * A 2-dimensional vector. (commonly used for positions, sizes, segment, etc.) */ type Vector2 = [number, number]; /** * Represents a 1D range (line segment). where a <= b * * - [a] start * - [b] end * - [length] b - a * - [center] (a + b / 2) or (a + length / 2) = mean of a and b */ type Range = [number, number]; /** * A 4-dimensional vector. (commonly used for areas, colors, etc.) */ type Vector4 = [number, number, number, number]; /** * Represents a 2D affine transformation matrix. * * A 2D affine transform is used to perform linear transformations (e.g., scaling, rotation, skewing) * and translations (shifting position) in 2D space. The matrix is represented as a 2x3 matrix: * * ``` * [ a, b, tx ] * [ c, d, ty ] * ``` * * Where: * - `a` and `d` are scaling factors along the x and y axes, respectively. * - `b` and `c` are the skewing (shearing) factors. * - `tx` and `ty` are translation (movement) along the x and y axes, respectively. * * ### Mathematical Formulation * When applied to a vector `[x, y]`, the transform produces a new vector `[x', y']` as follows: * * ``` * x' = a * x + b * y + tx * y' = c * x + d * y + ty * ``` * * ### Example Transformations * - **Translation**: * ``` * [ 1, 0, tx ] * [ 0, 1, ty ] * ``` * Moves a point by `tx` along the x-axis and `ty` along the y-axis. * * - **Scaling**: * ``` * [ sx, 0, 0 ] * [ 0, sy, 0 ] * ``` * Scales a point by `sx` along the x-axis and `sy` along the y-axis. * * - **Rotation (θ degrees)**: * ``` * [ cos(θ), -sin(θ), 0 ] * [ sin(θ), cos(θ), 0 ] * ``` * Rotates a point counterclockwise by `θ` degrees about the origin. * * - **Skewing**: * ``` * [ 1, tan(α), 0 ] * [ tan(β), 1, 0 ] * ``` * Skews a point horizontally by angle `α` and vertically by angle `β`. * * ### Common Use Cases * - Transforming shapes or points in 2D graphics. * - Applying geometric transformations in computer graphics or simulations. * - Modeling affine transformations in coordinate systems. * * @example * // Rotate a vector [1, 0] by 90 degrees and translate by [2, 3] * const transform: Transform = [ * [0, -1, 2], // Rotation and translation * [1, 0, 3], * ]; * ``` */ type Transform = [[number, number, number], [number, number, number]]; /** * A Rectangle specifies an area that is enclosed by it's top-left point (x, y), its width, and its height. * * width and height are non-negative values. */ type Rectangle = { x: number; y: number; /** * The width of the rectangle. Must be non-negative. */ width: number; /** * The height of the rectangle. Must be non-negative. */ height: number; }; type RectangleSide = "top" | "right" | "bottom" | "left"; type RectangleDimension = "width" | "height"; type CardinalDirection = "n" | "e" | "s" | "w" | "ne" | "se" | "sw" | "nw"; type IntercardinalDirection = "ne" | "se" | "sw" | "nw"; type Alignment9 = "bottom-center" | "bottom-left" | "bottom-right" | "center" | "center-left" | "center-right" | "top-center" | "top-left" | "top-right"; /** * Quantizes a value to the nearest multiple of a specified step. * * This function maps a continuous value to a discrete set of steps, * making it useful for rounding, grid alignment, or discretization in * mathematical and graphical computations. * * @param value - The value to quantize. * @param step - The step size for quantization. Must be a positive number. * @returns The quantized value, snapped to the nearest multiple of the step. * * @example * // Quantize to the nearest multiple of 10 * cmath.quantize(15, 10); // Returns 20 * * // Quantize to a decimal step size * cmath.quantize(0.1123, 0.1); // Returns 0.1 * * // Quantize to a finer step * cmath.quantize(7.35, 0.25); // Returns 7.25 */ function quantize(value: Scalar, step: number): Scalar; function clamp(value: Scalar, min: Scalar, max: Scalar): Scalar; /** * Clamps a value between 0 and 1. * * @param t - The value to clamp. * @returns The clamped value. */ function clamp01(t: number): number; /** * Clamps a value between 0 and 255. * * @param value - The value to clamp. * @returns The clamped value. */ function clamp255(value: number): number; /** * Finds the nearest value to a given number from a list of target numbers. * * This function calculates the absolute difference between the given value and each target, * and returns the target with the smallest difference. * * @param value - The reference number to which the nearest target is determined. * @param points - A list of numbers to compare against the reference number. * @returns The number from the list of targets that is closest to the given value. * If the list is empty, `Infinity` is returned. * * @example * const nearestValue = nearest(10, 3, 7, 15, 20); * console.log(nearestValue); // Outputs: 7 * * const nearestValueEmpty = nearest(10); * console.log(nearestValueEmpty); // Outputs: Infinity * * @remarks * If multiple targets have the same absolute difference to the given value, the first * one encountered in the list will be returned. */ function nearest(value: Scalar, ...points: Scalar[]): Scalar; /** * Converts an angle from radians to degrees. * * @param rad - The angle in radians. * @returns The angle in degrees. * * @example * const angleInDegrees = rad2deg(Math.PI / 2); // Returns 90 */ function rad2deg(rad: number): number; /** * Converts an angle to its principal angle within the range [-180, 180). * * A principal angle is the equivalent angle that falls within a standard range. * This function ensures the input angle is normalized to the range [-180, 180), * where negative values represent clockwise rotation and positive values represent counterclockwise rotation. * * @param angle - The input angle in degrees, which can be any value (positive, negative, or greater than 360). * @returns The equivalent principal angle within the range [-180, 180). * * @example * // Normalize angles greater than 180 degrees * const angle1 = toPrincipalAngle(270); // Returns -90 * * @example * // Normalize angles less than -180 degrees * const angle2 = toPrincipalAngle(-450); // Returns -90 * * @example * // Handle angles within the range [-180, 180) * const angle3 = toPrincipalAngle(45); // Returns 45 * * @example * // Normalize angles greater than 360 degrees * const angle4 = toPrincipalAngle(540); // Returns 180 * * @remarks * - Uses modular arithmetic to ensure the angle is wrapped to the desired range. * - This is commonly used in applications like computer graphics, navigation, and physics to standardize angle measurements. */ function principalAngle(angle: number): number; /** * Determines whether an angle (in degrees) is closer to the x-axis (horizontal) * or the y-axis (vertical). * * - "x" if the angle is closer to 0° or 180° * - "y" if the angle is closer to 90° or 270° * * @param angle - The angle in degrees (can be any real number). * @returns `"x"` if closer to horizontal, `"y"` if closer to vertical. * * @example * closestAxis(10); // "x" * closestAxis(85); // "y" * closestAxis(179); // "x" * closestAxis(270); // "y" */ function angleToAxis(angle: number): Axis; /** * Move an array item to a different position. Returns a new array with the item moved to the new position. */ function arrayMove(array: T[], from: number, to: number): T[]; /** * Checks if all elements in an array are equal, with optional tolerance. * * @param arr - The array of numbers to check. * @param tolerance - The allowable difference for values to be considered equal. Defaults to 0 (strict equality). * @returns `true` if all elements in the array are equal within the given tolerance, otherwise `false`. * * @example * isUniform([1, 1, 1]); // true * isUniform([1.001, 1.002, 1.0009], 0.01); // true * isUniform([1, 2, 3]); // false */ function isUniform(arr: number[], tolerance?: number): boolean; /** * Finds the mode (most frequent value) in an array of numbers. * * The mode is the value that appears most often in the array. If the array is empty, `undefined` is returned. * * @param arr - An array of numbers to find the mode from. * @returns The most frequent number in the array, or `undefined` if the array is empty. * * @example * // Single mode * const result1 = mode([1, 2, 2, 3]); * console.log(result1); // 2 * * @example * // Multiple modes (returns the first encountered) * const result2 = mode([1, 2, 2, 3, 3]); * console.log(result2); // 2 or 3 * * @example * // Empty array * const result3 = mode([]); * console.log(result3); // undefined * * @remarks * - The function uses a frequency map to count occurrences and identifies the most frequent value. * - In the case of ties (multiple numbers with the same highest frequency), the first number encountered is returned. */ function mode(arr: number[]): number | undefined; /** * Calculates the mean (average) of an array of numbers. * * The mean is computed by summing all elements in the array and dividing by the number of elements. * * @param values - An array of numbers for which the mean is to be calculated. * @returns The mean (average) of the provided numbers. * * @throws {Error} If the input array is empty. * * @example * ```typescript * const data = [5, 10, 15, 20]; * const avg = cmath.stats.mean(data); * console.log(avg); // Outputs: 12.5 * ``` */ function mean(...values: cmath.Scalar[]): cmath.Scalar; /** * Generates all combinations of size `k` from the given array. * * @param arr - The input array. * @param k - The size of each combination. * @returns An array of combinations (each combination is an array). * * @see https://en.wikipedia.org/wiki/Combination */ function combinations(arr: T[], k: number): T[][]; /** * Generates all permutations of size `k` from the given array. * * @param arr - The input array. * @param k - The size of each permutation. * @returns An array of permutations (each permutation is an array). * * @see https://en.wikipedia.org/wiki/Permutation */ function permutations(arr: T[], k: number): T[][]; /** * Generates the power set or subsets of a given array. * * If `k` is not specified, the function returns the full power set, which includes all subsets * of all possible sizes (from 0 to `n`, where `n` is the length of the input array). * If `k` is specified, the function returns only the subsets of size `k`. * * @param arr - The input array for which the subsets are to be generated. * @param k - (Optional) The size of subsets to generate. If -1, all subsets are generated. * @returns An array of arrays representing the subsets of the input array. * - If `k` is omitted, returns the full power set. * - If `k` is specified, returns only the subsets of size `k`. * * @example * ```typescript * // Generate the full power set * const powerSet = cmath.powerset([1, 2, 3]); * console.log(powerSet); * // Output: * // [ * // [], * // [1], [2], [3], * // [1, 2], [1, 3], [2, 3], * // [1, 2, 3] * // ] * ``` * * @example * ```typescript * // Generate all subsets of size 2 * const subsetsOfSize2 = cmath.powerset([1, 2, 3], 2); * console.log(subsetsOfSize2); * // Output: * // [ [1, 2], [1, 3], [2, 3] ] * ``` * * @remarks * - If `k` is negative or greater than the length of the array, an empty array is returned. * - Includes the empty set ([]), which is part of the standard mathematical definition of a powerset. * - The number of subsets returned when `k` is specified is \( \binom{n}{k} \), where \( n \) is the length of the input array. * - This function utilizes the `cmath.combinations` function internally to generate subsets of specific sizes. * * @see https://en.wikipedia.org/wiki/Power_set */ function powerset(arr: T[], k?: number): T[][]; namespace delta { /** * Projects a scalar delta along the given axis through a 2D affine transform. * * @param offset - The delta along the 'x' or 'y' axis. * @param axis - The axis ('x' or 'y') of the delta. * @param transform - The 2×3 affine transform matrix. * @returns The transformed scalar offset in surface space. */ function transform(offset: number, axis: cmath.Axis, transform: cmath.Transform): number; } /** * Rational / fraction helpers. */ namespace rational { /** * Approximates a real number with a reduced fraction using continued fractions. * * @param x - Real value to approximate. * @param max_denominator - Upper bound for the returned denominator (default: 1000). * @param epsilon - Convergence threshold (default: 1e-12). * @returns `[numerator, denominator]` (denominator > 0), or `undefined` if input is invalid. */ function approximateFraction(x: number, max_denominator?: number, epsilon?: number): [number, number] | undefined; } /** * Vector2 computations. */ namespace vector2 { /** * The zero vector `[0, 0]`. */ const zero: Vector2; /** * Constructs a 2D vector where one of the components (`a` or `b`) is assigned to the main axis (`x` or `y`). * * This function allows flexible assignment of scalar values to specific axes in 2D space * based on the specified main axis. * * @param a - The scalar value to assign to the main axis. * @param b - The scalar value to assign to the counter axis. * @param mainAxis - The primary axis (`"x"` or `"y"`) to which `a` should be assigned. * * @returns A 2D vector `[a, b]` if `mainAxis` is `"x"`, or `[b, a]` if `mainAxis` is `"y"`. * * @example * ```typescript * // Assign 5 to the x-axis and 10 to the y-axis * const vector1 = cmath.vector2.withMainAxis(5, 10, "x"); * console.log(vector1); // [5, 10] * * // Assign 5 to the y-axis and 10 to the x-axis * const vector2 = cmath.vector2.withMainAxis(5, 10, "y"); * console.log(vector2); // [10, 5] * ``` * * @remarks * This is particularly useful in scenarios where the primary axis needs to be specified dynamically, * such as when configuring flexible layouts or computations in 2D space. */ function axisOriented(a: Scalar, b: Scalar, mainAxis: Axis): Vector2; function isZero(vector: Vector2): boolean; function add(...vectors: Vector2[]): Vector2; function sub(...vectors: Vector2[]): Vector2; function quantize(vector: Vector2, step: number | Vector2): Vector2; function multiply(...vectors: Vector2[]): Vector2; /** * Inverts a 2D vector by negating both its components. * * @param vector - The vector to invert, in the format `[x, y]`. * @returns A new vector `[−x, −y]` with both components inverted. * * @example * const v: cmath.Vector2 = [3, -4]; * const inverted = cmath.vector2.invert(v); * console.log(inverted); // [-3, 4] */ function invert(vector: Vector2): Vector2; /** * Calculates the angle in degrees of a 2D point relative to an origin. * * This function computes the angle formed by the vector from the origin to the point * relative to the positive x-axis, measured counterclockwise. * * @param pointA - The origin point [x0, y0]. * @param pointB - The target point [x, y]. * @returns The angle in degrees, normalized to [0, 360). * * @example * const origin: cmath.Vector2 = [0, 0]; * const point: cmath.Vector2 = [1, 1]; * const angle = cmath.vector2.angle(origin, point); * console.log(angle); // 45 */ function angle(pointA: Vector2, pointB: Vector2): number; /** * Rotates a 2D vector by a specified angle. * * This function applies a rotation transformation to a given vector `[x, y]`, * rotating it counterclockwise around the origin by the specified angle in degrees. * * @param vector - The 2D vector to rotate, represented as `[x, y]`. * @param angle - The rotation angle in degrees. Positive values indicate counterclockwise rotation. * @returns A new vector `[rotatedX, rotatedY]` representing the rotated vector. * * @example * // Rotate the vector [1, 0] by 90 degrees * const vector: cmath.Vector2 = [1, 0]; * const rotated = cmath.vector2.rotate(vector, 90); * console.log(rotated); // Outputs: [0, 1] * * @example * // Rotate the vector [1, 1] by -45 degrees * const vector: cmath.Vector2 = [1, 1]; * const rotated = cmath.vector2.rotate(vector, -45); * console.log(rotated); // Outputs: [~1.414, 0] * * @remarks * - The angle is converted from degrees to radians internally, as trigonometric functions in JavaScript operate in radians. * - The rotation is performed around the origin `(0, 0)`. * - If the angle is `0`, the input vector is returned unchanged. */ function rotate(vector: Vector2, angle: number): Vector2; /** * Performs linear interpolation between two 2D vectors. * * This function calculates a point that lies on the straight line between two given vectors, * at a specified interpolation factor. When t = 0, the result is equal to vector a. * When t = 1, the result is equal to vector b. * * @param a - The starting vector [x, y]. * @param b - The ending vector [x, y]. * @param t - The interpolation factor, typically in the range [0, 1]. * @returns A new vector representing the interpolated point. * * @example * const start: cmath.Vector2 = [0, 0]; * const end: cmath.Vector2 = [10, 20]; * const mid = cmath.vector2.lerp(start, end, 0.5); * console.log(mid); // [5, 10] * * @example * // Interpolate at 25% from start to end * const quarter = cmath.vector2.lerp([0, 0], [100, 50], 0.25); * console.log(quarter); // [25, 12.5] */ function lerp(a: Vector2, b: Vector2, t: number): Vector2; /** * Checks if two 1D segments intersect or overlap. * * @param segmentA - The first segment [startA, endA]. * @param segmentB - The second segment [startB, endB]. * @returns `true` if the segments intersect or overlap, otherwise `false`. */ const intersects: (segmentA: Vector2, segmentB: Vector2) => boolean; /** * Calculates the intersection of two 1D segments. * * @param segmentA - The first segment as [startA, endA]. * @param segmentB - The second segment as [startB, endB]. * @returns A `Vector2` representing the intersection segment, or `null` if the segments do not intersect. * * @example * // Overlapping segments * const intersection = cmath.vector2.intersection([1, 5], [3, 7]); * console.log(intersection); // [3, 5] * * // Non-overlapping segments * const noIntersection = cmath.vector2.intersection([1, 5], [6, 8]); * console.log(noIntersection); // null */ function intersection(segmentA: cmath.Vector2, segmentB: cmath.Vector2): cmath.Vector2 | null; function min(...vectors: Vector2[]): Vector2; function max(...vectors: Vector2[]): Vector2; /** * Clamps each component of a 2D vector within the corresponding min and max bounds. * * @param vector - The input vector [x, y]. * @param min - The minimum allowed values [minX, minY]. * @param max - The maximum allowed values [maxX, maxY]. * @returns A new vector [clampedX, clampedY] where each component is between the corresponding min and max. * * @example * ```ts * // [5, 15] clamped between [0, 10] and [10, 20] remains [5, 15] * console.log(clamp([5, 15], [0, 10], [10, 20])); // [5, 15] * * // [15, 5] clamped between [0, 10] and [10, 20] becomes [10, 10] * console.log(clamp([15, 5], [0, 10], [10, 20])); // [10, 10] * ``` */ function clamp(vector: Vector2, min: Vector2, max: Vector2): Vector2; /** * Calculates the Euclidean distance between two 2D vectors. * * The Euclidean distance is the straight-line distance between two points in a 2D space. * It is computed using the formula: * \[ * d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2} * \] * * @param a - The first 2D vector `[x1, y1]`. * @param b - The second 2D vector `[x2, y2]`. * @returns The Euclidean distance as a number. * * @example * // Distance between two points * const a: cmath.Vector2 = [3, 4]; * const b: cmath.Vector2 = [6, 8]; * const dist = cmath.vector2.distance(a, b); * console.log(dist); // Outputs: 5 * * @remarks * - Uses `Math.hypot` for precision and efficiency. * - The distance is always a non-negative scalar value. */ function distance(a: Vector2, b: Vector2): number; /** * Applies a 2D transformation matrix to a vector. * * This function takes a 2D vector `[x, y]` and applies an affine transformation * using the provided transformation matrix. The transformation includes * scaling, rotation, and translation. * * The transformation matrix is in the format: * ``` * [[a, b, tx], * [c, d, ty]] * ``` * where: * - `a` and `d` represent scaling along the x-axis and y-axis, respectively. * - `b` and `c` represent rotation. * - `tx` and `ty` represent translation along the x-axis and y-axis. * * @param vector - The input 2D vector `[x, y]` to transform. * @param transform - The 2D transformation matrix. * @returns The transformed vector `[x', y']` after applying the transformation. * * @example * // Rotate a vector [1, 0] by 90 degrees and translate by [2, 3] * const vector: cmath.Vector2 = [1, 0]; * const transform: cmath.Transform = [ * [0, -1, 2], * [1, 0, 3], * ]; * const result = cmath.vector2.transform(vector, transform); * console.log(result); // [2, 4] * * @example * // Apply scaling transformation * const vector: cmath.Vector2 = [2, 3]; * const transform: cmath.Transform = [ * [2, 0, 0], * [0, 3, 0], * ]; * const result = cmath.vector2.transform(vector, transform); * console.log(result); // [4, 9] * * @remarks * - This function is useful in computer graphics, physics simulations, and other * mathematical computations where 2D transformations are required. * - The transformation matrix must be well-formed; otherwise, the behavior is undefined. */ function transform(vector: Vector2, transform: cmath.Transform): Vector2; function identical(a: Vector2, b: Vector2): boolean; /** * Computes the dot product of two vectors. * * The dot product is a scalar value that represents the magnitude of the projection * of one vector onto another, multiplied by the magnitude of the target vector. * It's fundamental for many vector operations including projection, angle calculation, * and determining orthogonality. * * @param a - The first vector. * @param b - The second vector. * @returns The dot product of vectors `a` and `b`. * * @example * ```typescript * const a: cmath.Vector2 = [3, 4]; * const b: cmath.Vector2 = [1, 2]; * const result = cmath.vector2.dot(a, b); * console.log(result); // 11 (3*1 + 4*2) * ``` * * @example * ```typescript * // Check if vectors are perpendicular (dot product = 0) * const a: cmath.Vector2 = [1, 0]; * const b: cmath.Vector2 = [0, 1]; * const isPerpendicular = cmath.vector2.dot(a, b) === 0; * console.log(isPerpendicular); // true * ``` * * @remarks * - The mathematical formula is: `a · b = a₁b₁ + a₂b₂` * - Dot product of 0 indicates perpendicular vectors * - Positive dot product indicates acute angle between vectors * - Negative dot product indicates obtuse angle between vectors */ function dot(a: Vector2, b: Vector2): number; /** * Computes the cross product of two vectors. * * The cross product in 2D returns a scalar value representing the signed area * of the parallelogram formed by the two vectors. It's useful for determining * orientation, calculating areas, and checking if vectors are parallel. * * @param a - The first vector. * @param b - The second vector. * @returns The cross product of vectors `a` and `b`. * * @example * ```typescript * const a: cmath.Vector2 = [3, 4]; * const b: cmath.Vector2 = [1, 2]; * const result = cmath.vector2.cross(a, b); * console.log(result); // 2 (3*2 - 4*1) * ``` * * @example * ```typescript * // Check if vectors are parallel (cross product = 0) * const a: cmath.Vector2 = [2, 4]; * const b: cmath.Vector2 = [1, 2]; // parallel to a * const isParallel = cmath.vector2.cross(a, b) === 0; * console.log(isParallel); // true * ``` * * @remarks * - The mathematical formula is: `a × b = a₁b₂ - a₂b₁` * - Cross product of 0 indicates parallel vectors * - Positive cross product indicates counter-clockwise rotation from a to b * - Negative cross product indicates clockwise rotation from a to b * - The magnitude equals the area of the parallelogram formed by the vectors */ function cross(a: Vector2, b: Vector2): number; /** * Projects one vector onto another vector. * * This function computes the orthogonal projection of `vector` onto `axis`. * The result is a vector that lies on the line defined by `axis` and represents * the closest point on that line to the original `vector`. * * @param vector - The vector to project. * @param axis - The target vector/axis to project onto. * @returns The projection of `vector` onto `axis`. * * @example * ```typescript * const vector: cmath.Vector2 = [3, 4]; * const axis: cmath.Vector2 = [1, 0]; * const projection = cmath.vector2.project(vector, axis); * console.log(projection); // [3, 0] - projected onto x-axis * ``` * * @example * ```typescript * const vector: cmath.Vector2 = [2, 3]; * const axis: cmath.Vector2 = [1, 1]; * const projection = cmath.vector2.project(vector, axis); * console.log(projection); // [2.5, 2.5] - projected onto diagonal * ``` * * @remarks * - If `axis` is the zero vector, returns `[0, 0]`. * - The mathematical formula is: `proj_axis(vector) = (vector · axis / |axis|²) × axis` * - This is a fundamental operation in linear algebra for vector decomposition. */ function project(vector: Vector2, axis: Vector2): Vector2; } namespace vector4 { function identical(a: Vector4, b: Vector4): boolean; } namespace compass { /** * Cardinal direction vector * * - `n -> [0, -1]` * - `e -> [1, 0]` * - `s -> [0, 1]` * - `w -> [-1, 0]` * - ... and so on */ const cardinal_direction_vector: { readonly nw: cmath.Vector2; readonly ne: cmath.Vector2; readonly sw: cmath.Vector2; readonly se: cmath.Vector2; readonly n: cmath.Vector2; readonly e: cmath.Vector2; readonly s: cmath.Vector2; readonly w: cmath.Vector2; }; /** * Inverts a cardinal direction to its opposite. * * This function takes a cardinal direction (e.g., "n", "e", "nw") and returns * its opposite direction (e.g., "n" becomes "s", "ne" becomes "sw"). * * @param direction - The cardinal direction to invert. Must be one of the following: * - `"n"`: North * - `"e"`: East * - `"s"`: South * - `"w"`: West * - `"ne"`: North-East * - `"se"`: South-East * - `"sw"`: South-West * - `"nw"`: North-West * @returns The inverted cardinal direction, as follows: * - `"n"` -> `"s"` * - `"e"` -> `"w"` * - `"s"` -> `"n"` * - `"w"` -> `"e"` * - `"ne"` -> `"sw"` * - `"se"` -> `"nw"` * - `"sw"` -> `"ne"` * - `"nw"` -> `"se"` * * @example * const inverted = cmath.invertCardinalDirection("n"); * console.log(inverted); // "s" * * const invertedDiagonal = cmath.invertCardinalDirection("ne"); * console.log(invertedDiagonal); // "sw" * * @remarks * - This function is useful for geometric computations or UI layouts where * directional relationships need to be reversed. */ function invertDirection(direction: CardinalDirection): CardinalDirection; /** * Converts a 9-point alignment to a cardinal direction. * * @param alignment - The 9-point alignment to convert. * @returns The corresponding cardinal direction, or "center" if the alignment is "center". */ function fromAlignment9(alignment: Alignment9): CardinalDirection | "center"; /** * Converts a strictly orthogonal cardinal direction (n, e, s, w) to the corresponding * rectangle side (top, right, bottom, left). * * Diagonal directions (ne, nw, se, sw) return `undefined`. * * @param direction - The cardinal direction to convert, one of: * - `"n"` (north) * - `"e"` (east) * - `"s"` (south) * - `"w"` (west) * - or a diagonal (e.g. `"ne"`) which yields `undefined`. * * @returns The corresponding `RectangleSide` ("top", "right", "bottom", "left") * if the direction is orthogonal, otherwise `undefined`. * * @example * ``` * const side1 = toRectangleSide("n"); * // side1 === "top" * * const side2 = toRectangleSide("ne"); * // side2 === undefined * ``` * * @remarks * This is often used for translating a directional label (`"n"`, `"s"`, etc.) * to an actual rectangle edge in UI layouts or alignment logic. */ function toRectangleSide(direction: CardinalDirection): RectangleSide | undefined; /** * The 8 cardinal/diagonal directions of a resize handle. * * Structurally identical to {@link CardinalDirection} — this alias is * the single source of truth for the resize-handle direction union that * `@grida/hud` and `@grida/svg-editor` previously each re-declared. Named * for the resize-handle context at the call sites. */ type ResizeDirection = CardinalDirection; /** * Decompose a cardinal direction into its N/S/E/W membership. * * A diagonal belongs to two components (`"ne"` → north + east); an * orthogonal direction to one. This is the shared primitive behind * resize-handle math — the HUD's preview rect and svg-editor's * direction mask both build on it instead of re-deriving the four * boolean ORs. * * Derived from {@link cardinal_direction_vector} so the direction set * has a single source of truth: the unit vector's sign *is* the axis * membership (`x > 0` → east, `y < 0` → north). * * @example * cmath.compass.cardinalComponents("ne"); * // { north: true, south: false, east: true, west: false } */ function cardinalComponents(dir: CardinalDirection): { north: boolean; south: boolean; east: boolean; west: boolean; }; } namespace rect { /** * get size of the rectangle in the given axis * * - `x` -> `width` * - `y` -> `height` * * @param rect * @param axis * @returns size of the rectangle in the given axis */ function getAxisDimension(rect: Rectangle, axis: Axis): number; function getOppositeSide(side: RectangleSide): RectangleSide; /** * Quantizes the position and size of a rectangle by snapping its coordinates and dimensions * to the nearest multiples of the given step. * * @remarks * This function may lose precision and distort the rectangle's original geometry * because it applies quantization to each property individually. * * @param rect - The rectangle to quantize. * @param step - A single step value or a 2D vector ([xStep, yStep]). * @returns A new rectangle with quantized `x`, `y`, `width`, and `height`. */ function quantize(rect: Rectangle, step: Scalar | Vector2): Rectangle; /** * Translates a rectangle by a given vector. * * This function adjusts the position of a rectangle (`x` and `y`) by adding a translation vector. * The dimensions (`width` and `height`) remain unchanged. * * @param rect - The rectangle to be translated, defined by its position (`x`, `y`) and dimensions (`width`, `height`). * @param t - The translation vector `[tx, ty]`, where `tx` is the horizontal translation and `ty` is the vertical translation. * @returns A new rectangle with updated `x` and `y` values, and the same `width` and `height` as the original. * * @example * // Translate a rectangle by a positive vector * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const translation = [5, 10]; * const result = cmath.rect.translate(rect, translation); * console.log(result); // { x: 15, y: 30, width: 30, height: 40 } * * @example * // Translate a rectangle by a negative vector * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const translation = [-5, -10]; * const result = cmath.rect.translate(rect, translation); * console.log(result); // { x: 5, y: 10, width: 30, height: 40 } * * @example * // Translate a rectangle by a zero vector (no movement) * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const translation = [0, 0]; * const result = cmath.rect.translate(rect, translation); * console.log(result); // { x: 10, y: 20, width: 30, height: 40 } * * @remarks * - The translation vector affects only the `x` and `y` properties of the rectangle. * - The rectangle's dimensions (`width` and `height`) are preserved. */ function translate(rect: Rectangle, t: Vector2): Rectangle; /** * Applies a 2D scaling transformation to a rectangle relative to a given origin. * * This function modifies the rectangle's position and dimensions by applying * scaling factors along the x-axis and y-axis. Negative scaling factors * result in a reflection across the respective axis. * * @param rect - The rectangle to be transformed, defined by its position (x, y) and dimensions (width, height). * @param origin - The point ([originX, originY]) relative to which the scaling is applied. * @param scale - The scaling factors ([scaleX, scaleY]) for the x-axis and y-axis. * @returns A new rectangle transformed by the specified scaling operation. * * @remarks * - Scaling is performed as an affine transformation: `newX = originX + (x - originX) * scaleX` and similarly for `y`. * - The width and height are directly scaled by `scaleX` and `scaleY`, respectively. * - Negative scale factors reflect the rectangle across the corresponding axis. * * @example * // Uniform scaling * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const origin = [0, 0]; * const scale = [2, 2]; * cmath.rect.scale(rect, origin, scale); // { x: 20, y: 40, width: 60, height: 80 } * * @example * // Non-uniform scaling * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const origin = [0, 0]; * const scale = [2, 1.5]; * cmath.rect.scale(rect, origin, scale); // { x: 20, y: 30, width: 60, height: 60 } * * @example * // Reflection using negative scaling * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const origin = [0, 0]; * const scale = [-1, -1]; * cmath.rect.scale(rect, origin, scale); // { x: -10, y: -20, width: -30, height: -40 } */ function scale(rect: cmath.Rectangle, origin: cmath.Vector2, scale: cmath.Vector2): cmath.Rectangle; /** * Normalizes a rectangle to ensure positive width and height. * * This function adjusts the rectangle's `x` and `y` coordinates * to ensure that the rectangle is defined with non-negative `width` and `height`. * If the original rectangle has negative width or height, the function modifies * the rectangle's position (`x` and `y`) and dimensions to maintain equivalence. * * @param rect - The input rectangle, which may have negative width or height. * @returns A new rectangle with positive `width` and `height`, * and adjusted `x` and `y` coordinates. * * @example * // Rectangle with negative width * const rect = { x: 10, y: 20, width: -30, height: 40 }; * const normalized = cmath.rect.positive(rect); * console.log(normalized); * // { x: -20, y: 20, width: 30, height: 40 } * * @example * // Rectangle with negative height * const rect = { x: 10, y: 20, width: 30, height: -40 }; * const normalized = cmath.rect.positive(rect); * console.log(normalized); * // { x: 10, y: -20, width: 30, height: 40 } * * @example * // Rectangle with both negative width and height * const rect = { x: 10, y: 20, width: -30, height: -40 }; * const normalized = cmath.rect.positive(rect); * console.log(normalized); * // { x: -20, y: -20, width: 30, height: 40 } * * @remarks * - This function is useful for ensuring that rectangles are represented * with positive dimensions, especially in computations where negative * dimensions can cause unexpected results. * - The `x` and `y` coordinates are adjusted to maintain the rectangle's * equivalent area. */ function positive(rect: Rectangle): Rectangle; function aspectratio(rect: cmath.Rectangle): cmath.Scalar; /** * Computes the scale factors required to transform rectangle `a` to rectangle `b`. * * The position of the rectangles is not considered. * * @param a - The original rectangle. * @param b - The target rectangle. * @returns The scale factors [scaleX, scaleY]. * * @example * const a = { x: 10, y: 20, width: 100, height: 100 }; * const b = { x: 10, y: 20, width: 200, height: 300 }; * const scale = getScaleFactors(a, b); // [2, 3] */ function getScaleFactors(a: cmath.Rectangle, b: cmath.Rectangle): cmath.Vector2; /** * Computes the 2D transform matrix that maps rectangle `a` onto rectangle `b`. * * Essentially, we want a transform `T` such that: * `point_in_b = T * point_in_a` * * The result is an affine transform (2×3 matrix) that: * 1. Translates `a` to the origin, * 2. Scales it by the ratio of widths and heights of `b` / `a`, * 3. Translates it to the position of `b`. * * @param a - The source rectangle. * @param b - The target rectangle. * * @returns A 2D transform matrix in the format: * ``` * [ * [scaleX, 0, translateX], * [0, scaleY, translateY], * ] * ``` * * @example * ```ts * const a = { x: 0, y: 0, width: 100, height: 50 }; * const b = { x: 200, y: 300, width: 400, height: 200 }; * * const t = cmath.rect.getRelativeTransform(a, b); * const result = cmath.rect.transform(a, t); * * // Now `result` should be identical to `b`. * console.log(result); * // => { x: 200, y: 300, width: 400, height: 200 } * ``` */ function getRelativeTransform(a: cmath.Rectangle, b: cmath.Rectangle): cmath.Transform; /** * Applies a general 2D affine transform (including translate, scale, rotate, and skew) * to each corner of a rectangle, then returns the bounding box of the transformed corners. * * @param rect - The source rectangle `{ x, y, width, height }`. * @param transform - The 2D transform matrix: * ``` * [ * [a, b, tx], * [c, d, ty], * ] * ``` * @returns A new rectangle representing the bounding box of the transformed corners. * * @example * const rect: cmath.Rectangle = { x: 10, y: 20, width: 30, height: 40 }; * // Affine transform matrix with skew * const t: cmath.Transform = [ * [1, 0.2, 100], * [0.3, 1, 50], * ]; * const result = cmath.rect.transform(rect, t); * console.log(result); * // Example => { x: 110, y: 73, width: 42, height: 53 } */ function transform(rect: cmath.Rectangle, transform: cmath.Transform): cmath.Rectangle; /** * Returns the 4 corners of a rectangle in TL → TR → BR → BL order. * * Winding is clockwise in screen-space (y-down). The order matches the * inline literal used by `rect.transform` above; callers needing to * transform corners through an affine matrix should reuse this helper * with `cmath.vector2.transform` to keep the corner order canonical. * * @example * cmath.rect.toCorners({ x: 0, y: 0, width: 10, height: 5 }); * // [[0, 0], [10, 0], [10, 5], [0, 5]] */ function toCorners(r: Rectangle): [cmath.Vector2, cmath.Vector2, cmath.Vector2, cmath.Vector2]; /** * Rotates a rectangle and computes the bounding box of the rotated rectangle. * * @param rect - The rectangle to rotate. * @param rotate - The rotation angle in degrees (similar to CSS rotate). * @returns A new rectangle representing the axis-aligned bounding box of the rotated rectangle. * * @example * const rect = { x: 10, y: 10, width: 50, height: 30 }; * const rotated = cmath.rect.rotate(rect, 45); * console.log(rotated); // { x: 3.03, y: -7.32, width: 70.71, height: 56.57 } */ function rotate(rect: Rectangle, rotate: number): Rectangle; /** * Creates a rectangle that fully bounds the given points. * * This function computes the minimum bounding rectangle that encloses all the input points. * At least 1 point is required. * * @param points - An array of points (at least 1) to calculate the bounding rectangle. * @returns A rectangle with `x`, `y`, `width`, and `height`. * * @example * const rect = cmath.rect.fromPoints([[10, 20], [30, 40], [15, 25]]); * console.log(rect); // { x: 10, y: 20, width: 20, height: 20 } * * const pointRect = cmath.rect.fromPoints([[10, 20]]); * console.log(pointRect); // { x: 10, y: 20, width: 0, height: 0 } */ function fromPoints(points: cmath.Vector2[]): cmath.Rectangle; /** * Computes the minimum bounding rectangle that encloses all the input points. * This is a safe version of `fromPoints` that returns a zero rectangle instead of throwing * when the input array is empty. * * This function computes the minimum bounding rectangle that encloses all the input points. * If no points are provided, returns a zero rectangle at origin (0, 0). * * @param points - An array of points to calculate the bounding rectangle from. * @returns A rectangle with `x`, `y`, `width`, and `height`. Returns zero rectangle if no points. * * @example * const rect = cmath.rect.fromPointsOrZero([[10, 20], [30, 40], [15, 25]]); * console.log(rect); // { x: 10, y: 20, width: 20, height: 20 } * * const emptyRect = cmath.rect.fromPointsOrZero([]); * console.log(emptyRect); // { x: 0, y: 0, width: 0, height: 0 } * * const pointRect = cmath.rect.fromPointsOrZero([[10, 20]]); * console.log(pointRect); // { x: 10, y: 20, width: 0, height: 0 } */ function fromPointsOrZero(points: cmath.Vector2[]): cmath.Rectangle; type Rectangle9Points = { topLeft: Vector2; topRight: Vector2; bottomRight: Vector2; bottomLeft: Vector2; topCenter: Vector2; rightCenter: Vector2; bottomCenter: Vector2; leftCenter: Vector2; center: Vector2; }; /** * Returns an object containing 9 control points of a rectangle: 4 corners, 4 midpoints, and the center. * * @param rect - The rectangle to compute points from. * @returns An object with properties for each control point. * * @example * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const points = cmath.rect.to9Points(rect); * console.log(points); * // Outputs: * // { * // topLeft: [10, 20], * // topRight: [40, 20], * // bottomRight: [40, 60], * // bottomLeft: [10, 60], * // topCenter: [25, 20], * // rightCenter: [40, 40], * // bottomCenter: [25, 60], * // leftCenter: [10, 40], * // center: [25, 40], * // } */ function to9Points(rect: Rectangle): Rectangle9Points; /** * A chunk array of 9 control points of a rectangle, with the exact order: * * `[topLeft, topRight, bottomRight, bottomLeft, topCenter, rightCenter, bottomCenter, leftCenter, center]` */ type TRectangle9PointsChunk = [T, T, T, T, T, T, T, T, T]; function to9PointsChunk(r: cmath.Rectangle): TRectangle9PointsChunk; function getCardinalPoint(rect: cmath.Rectangle, point: CardinalDirection): Vector2; /** * Calculates the center point of a rectangle. * * The center point is defined as the midpoint of the rectangle's width and height. * * @param rect - The rectangle to calculate the center for, defined by its position (`x`, `y`) and dimensions (`width`, `height`). * @returns A `Vector2` representing the center point `[centerX, centerY]`. * * @example * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const center = cmath.rect.getCenter(rect); * console.log(center); // [25, 40] * * @example * // Handles rectangles with zero width or height * const rect = { x: 10, y: 20, width: 0, height: 40 }; * const center = cmath.rect.getCenter(rect); * console.log(center); // [10, 40] */ function getCenter(rect: cmath.Rectangle): cmath.Vector2; /** * Checks if rectangle `a` fully contains rectangle `b`. * * A rectangle `target` is considered contained within rectangle `container` if: * - The top-left corner of `target` lies within `container`. * - The bottom-right corner of `target` lies within `container`. * * @param a - The containing rectangle. * @param b - The rectangle to test for containment. * @returns `true` if rectangle `target` is fully contained within rectangle `container`, otherwise `false`. * * @example * const container = { x: 10, y: 10, width: 100, height: 100 }; * const target = { x: 20, y: 20, width: 30, height: 30 }; * cmath.rect.contains(container, target); // Returns true. */ function contains(a: Rectangle, b: Rectangle): boolean; /** * Checks if a rectangle contains a point. * * @param point - The point to check, as a `[x, y]` tuple. * @param rect - The rectangle in `{ x, y, width, height }` format. * @returns `true` if the point is inside the rectangle, otherwise `false`. * * @example * const point = [15, 25]; * const rect = { x: 10, y: 20, width: 30, height: 40 }; * const isInside = cmath.rect.containsPoint(point, rect); * console.log(isInside); // true */ function containsPoint(rect: cmath.Rectangle, point: cmath.Vector2): boolean; /** * Returns the signed offset from the given point to the nearest edge of the rectangle. * Negative values indicate the point is left or above the rectangle's boundary. * * @param rect - The rectangle defined by { x, y, width, height }. * @param point - The point as a tuple [x, y]. * @returns A tuple [dx, dy] representing the signed offset. * * @example * ```ts * const rect = { x: 10, y: 10, width: 100, height: 50 }; * console.log(offset(rect, [5, 30])); // Output: [-5, 0] * console.log(offset(rect, [50, 30])); // Output: [0, 0] * console.log(offset(rect, [120, 70])); // Output: [10, 10] * ``` */ function offset(rect: { x: number; y: number; width: number; height: number; }, point: [number, number]): [number, number]; /** * Checks if rectangle `a` intersects with rectangle `b`. * * Two rectangles are considered intersecting if they overlap, either partially or fully. Edges and corners touching * are also considered as an intersection. * * @param a - The first rectangle. * @param b - The second rectangle. * @returns `true` if rectangle `a` intersects with rectangle `b`, otherwise `false`. * * @example * const a = { x: 50, y: 50, width: 30, height: 30 }; * const b = { x: 60, y: 60, width: 40, height: 40 }; * cmath.rect.intersects(a, b); // Returns true. * * const c = { x: 0, y: 0, width: 20, height: 20 }; * cmath.rect.intersects(a, c); // Returns false. */ function intersects(a: Rectangle, b: Rectangle): boolean; /** * Calculates the intersection of two rectangles in the { x, y, width, height } format. * * @param a - The first rectangle. * @param b - The second rectangle. * @returns A new rectangle representing the intersection of the two rectangles. * If the rectangles do not intersect, returns `null`. * * @example * const a = { x: 10, y: 10, width: 30, height: 30 }; * const b = { x: 20, y: 20, width: 30, height: 30 }; * const result = intersection(a, b); * console.log(result); // { x: 20, y: 20, width: 20, height: 20 } */ function intersection(a: cmath.Rectangle, b: cmath.Rectangle): cmath.Rectangle | null; /** * Calculates the overlapping projection of an array of rectangles along the **counter-axis** of the specified projection axis. * * The projection axis determines which axis to analyze for overlap: * - `"x"`: Projects along the `y` axis (compares vertical ranges). * - `"y"`: Projects along the `x` axis (compares horizontal ranges). * * This function determines if all rectangles have overlapping projections along the **counter-axis** of the specified projection axis. It returns the intersecting range as a `Vector2` or `null` if no intersection exists. * * @param rectangles - An array of rectangles to project. * @param projectionAxis - The axis to calculate projections on ("x" for comparing vertical ranges, "y" for comparing horizontal ranges). * @returns {Range} representing the overlapping range of projections along the counter-axis, or `null` if the rectangles do not overlap. * * * ### Visual Explanation * * This illustrates the projection: projectionAxis = "y" (compare x ranges) * ``` * | 0 1 2 3 4 5 6 7 8 9 10 * | +--------------+ | * | | A | | * | | | | * | +--------------+ | * | [2 — 7] | * | | * | +--------+ | * | | B | | * | | | | * | +--------+ | * | [5 — 8] | * | | * | +--------+ | * | | C | | * | | | | * | +--------+ | * | [6 — 9] | * | | * | +--+ | * | [6 — 7] | * | (Overlapping) | * ``` * * @example * ```typescript * const rectangles = [ * { x: 10, y: 20, width: 30, height: 40 }, * { x: 20, y: 25, width: 40, height: 35 }, * { x: 25, y: 15, width: 30, height: 50 }, * ]; * const projection = cmath.rect.axisAlignedProjection(rectangles, "x"); * console.log(projection); // Outputs: [25, 40] (overlapping vertical range) * ``` * * @remarks * - The function reduces the projections of all rectangles along the counter-axis into a single overlapping range. * - Returns `null` if no overlap exists between the projections. * - The rectangles are treated as axis-aligned (non-rotated). */ function axisProjectionIntersection(rectangles: cmath.Rectangle[], projectionAxis: Axis): Range | null; /** * Checks if two rectangles are identical. * * Two rectangles are considered identical if their positions (`x`, `y`) and dimensions (`width`, `height`) * are exactly the same. * * @param rectA - The first rectangle to compare. * @param rectB - The second rectangle to compare. * @returns `true` if the rectangles are identical, otherwise `false`. * * @example * const rect1 = { x: 10, y: 20, width: 30, height: 40 }; * const rect2 = { x: 10, y: 20, width: 30, height: 40 }; * const isIdentical = cmath.rect.identical(rect1, rect2); * console.log(isIdentical); // true * * @example * const rect1 = { x: 10, y: 20, width: 30, height: 40 }; * const rect2 = { x: 10, y: 25, width: 30, height: 40 }; * const isIdentical = cmath.rect.identical(rect1, rect2); * console.log(isIdentical); // false * * @remarks * - The function performs a strict equality check on the `x`, `y`, `width`, and `height` properties. */ function isIdentical(rectA: cmath.Rectangle, rectB: cmath.Rectangle): boolean; /** * Checks if all rectangles in the given array are uniform (identical in position and dimensions). * * Two rectangles are considered uniform if their `x`, `y`, `width`, and `height` * properties are exactly the same. * * @param rects - An array of rectangles to check. * @returns `true` if all rectangles are uniform, or if the array contains zero or one rectangle. Otherwise, `false`. * * @example * const rect1 = { x: 10, y: 20, width: 30, height: 40 }; * const rect2 = { x: 10, y: 20, width: 30, height: 40 }; * const rect3 = { x: 15, y: 25, width: 35, height: 45 }; * * isUniform(rect1, rect2); // true * isUniform(rect1, rect2, rect3); // false * isUniform(); // true (empty input is considered uniform) */ function isUniform(...rects: cmath.Rectangle[]): boolean; /** * Computes the bounding rectangle that fully encloses an array of rectangles. * * @param rectangles - An array of rectangles to compute the bounding box for. * @returns A new rectangle that bounds all the input rectangles. * * @example * const rectangles = [ * { x: 10, y: 10, width: 30, height: 40 }, * { x: 50, y: 20, width: 20, height: 30 }, * ]; * const rect = cmath.rect.union(rectangles); * console.log(rect); // { x: 10, y: 10, width: 60, height: 50 } */ function union(rectangles: Rectangle[]): Rectangle; /** * Applies padding to a rectangle, expanding it while preserving its center. * * The padding can be specified as a uniform number (applied to all sides) or as an object with optional * properties: `top`, `right`, `bottom`, and `left`. The resulting rectangle has its center unchanged, * with its width increased by the sum of the left and right paddings, and its height increased by the sum * of the top and bottom paddings. * * @param rect - The original rectangle. * @param padding - A uniform padding number or an object specifying padding for each side. * @returns A new rectangle with the padding applied and the same center as the original. * * @example * // Uniform padding of 10 on all sides: * const rect = { x: 50, y: 50, width: 100, height: 80 }; * const padded = cmath.rect.pad(rect, 10); * // Result: { x: 40, y: 40, width: 120, height: 100 } * * @example * // Different padding for each side: * const rect = { x: 50, y: 50, width: 100, height: 80 }; * const padded = cmath.rect.pad(rect, { top: 5, right: 15, bottom: 10, left: 20 }); * // The center of `padded` is the same as the center of `rect`. */ function pad(rect: Rectangle, padding: number | { top?: number; right?: number; bottom?: number; left?: number; }): Rectangle; /** * Insets (shrinks) a rectangle by the given margin(s) while preserving its center. * * The margin can be specified as a uniform number (applied to all sides) or as an object with optional * properties: `top`, `right`, `bottom`, and `left`. The resulting rectangle's width and height are clamped * to be non-negative. * * @param rect - The original rectangle. * @param margin - A uniform margin number or an object specifying margins for each side. * @returns A new rectangle with the margin applied inward and the same center as the original. * * @example * // Uniform inset of 10 on all sides: * const rect = { x: 50, y: 50, width: 100, height: 80 }; * const insetRect = cmath.rect.inset(rect, 10); * // New dimensions: width = 100 - 20 = 80, height = 80 - 20 = 60, center remains at (100, 90) * // Result: { x: 100 - 40, y: 90 - 30, width: 80, height: 60 } → { x: 60, y: 60, width: 80, height: 60 } * * @example * // Non-uniform inset: * const rect = { x: 50, y: 50, width: 100, height: 80 }; * const insetRect = cmath.rect.inset(rect, { top: 5, right: 15, bottom: 10, left: 20 }); * // New width = 100 - (20+15) = 65, new height = 80 - (5+10) = 65, * // Center remains at (100, 90), so new x = 100 - 65/2 = 67.5, new y = 90 - 65/2 = 57.5 * // Result: { x: 67.5, y: 57.5, width: 65, height: 65 } */ function inset(rect: Rectangle, margin: number | { top?: number; right?: number; bottom?: number; left?: number; }): Rectangle; /** * Aligns an array of rectangles along a specified axis and alignment type. * * @param rectangles - An array of rectangles to align. * @param alignment - The alignment type of each axis (horizontal and vertical). * @returns A new array of rectangles with updated positions. */ function align(rectangles: cmath.Rectangle[], alignment: { horizontal?: "none" | "min" | "max" | "center"; vertical?: "none" | "min" | "max" | "center"; }): cmath.Rectangle[]; /** * Aligns rectangle `a` relative to rectangle `b` along a specified axis. * * @param a the rectangle to align * @param b the rectangle to align to * @param alignment - The alignment type of each axis (horizontal and vertical). */ function alignA(a: Rectangle, b: Rectangle, alignment: { horizontal?: "none" | "min" | "max" | "center"; vertical?: "none" | "min" | "max" | "center"; }): Rectangle; /** * Calculates the gaps (spaces) between adjacent rectangles along a specified axis. * * @param rectangles - An array of rectangles to calculate the gaps for. * @param axis - The axis to calculate the gaps along ("x" or "y"). * @returns An array of numbers representing the gaps between adjacent rectangles, excluding the last rectangle. * * @example * const rectangles = [ * { x: 10, y: 20, width: 30, height: 40 }, * { x: 50, y: 20, width: 30, height: 40 }, * { x: 90, y: 20, width: 30, height: 40 }, * ]; * const gaps = getDistribution(rectangles, "x"); * console.log(gaps); // [10, 10] */ function getGaps(rectangles: cmath.Rectangle[], axis: Axis): number[]; /** * Calculates the uniform gap between adjacent rectangles along a specified axis. * * @param rectangles - An array of rectangles to calculate the uniform gap for. * @param axis - The axis to calculate the gap along ("x" or "y"). * @param tolerance - The maximum allowed deviation from a uniform gap. * * @returns `[unfiorm, gaps]` A tuple containing the uniform gap (if found, most present or biggest) and an array of gaps between adjacent rectangles. * */ function getUniformGap(rectangles: cmath.Rectangle[], axis: Axis, tolerance?: number): [unfiorm: number | undefined, gaps: number[]]; /** * Distributes rectangles evenly along a specified axis within the bounding box of the rectangles, * while respecting the original input/output index order. * * The rectangles are repositioned to maintain equal spacing between their edges, calculated relative * to their sorted positions along the specified axis (`x` or `y`). However, the output preserves * the input order of rectangles, ensuring that the final output array corresponds 1:1 to the input. * * @param rectangles - An array of rectangles to distribute. * @param axis - The axis to distribute along ("x" or "y"). * @returns A new array of rectangles with updated positions, respecting the original input/output order. * * ### Behaviour * - **Input Order Respected**: The output array maintains the same indexing as the input array, * even though the internal logic calculates new positions based on the sorted positions of rectangles. * - **Even Spacing**: Rectangles are repositioned to have equal gaps between their edges, distributed * within the bounding box of the original rectangles. * - **No Shape Modification**: The width, height, and other properties of each rectangle remain unchanged. * * @example * const rectangles = [ * { x: 90, y: 20, width: 30, height: 40 }, * { x: 10, y: 20, width: 30, height: 40 }, * { x: 50, y: 20, width: 30, height: 40 }, * ]; * * const distributed = cmath.rect.distributeEvenly(rectangles, "x"); * console.log(distributed); * // Output: * // [ * // { x: 10, y: 20, width: 30, height: 40 }, // Moved to correct position for first * // { x: 50, y: 20, width: 30, height: 40 }, // Moved to correct position for second * // { x: 90, y: 20, width: 30, height: 40 }, // Moved to correct position for third * // ] * * @remarks * - The function ensures rectangles are repositioned evenly but retains their original order in the output array. * - The repositioning logic calculates gaps based on the sorted positions of rectangles along the specified axis. * - If fewer than 2 rectangles are provided, the input is returned unchanged. */ function distributeEvenly(rectangles: cmath.Rectangle[], axis: Axis): cmath.Rectangle[]; /** * Boolean operations on rectangles for vector graphics calculations. * * This module provides functions to perform basic boolean operations on rectangles, * tailored for vector graphics use cases where precise layout and bounding calculations * are required. * * - **intersect: A ∩ B** * Computes the intersecting region of A and B, returning a single rectangle (or null if there is no overlap). * * - **subtract: A - B** * Subtracts B from A, returning an array of rectangles that represent the area of A excluding the overlapping region with B. * * - **exclude: A ⊖ B** * Computes the symmetric difference, defined as (A ∪ B) minus (A ∩ B), returning an array of rectangles that represent the non-overlapping portions of A and B. */ namespace boolean { /** * Subtracts rectangle `b` from rectangle `a`, returning the remaining disjoint subregions. * * In the context of vector graphics calculations, this function computes the boolean * difference \(A - B\) by removing the overlapping area of `b` (if any) from `a`. The operation * returns an array of rectangles representing the parts of `a` that are not covered by `b`. * Only the portion of `b` that overlaps with `a` is subtracted; the resulting regions will always be * confined within `a`. * * @param a - The rectangle from which to subtract. * @param b - The rectangle to subtract. * @returns An array of rectangles representing the area of `a` after subtracting the overlap with `b`. * * @example * ```typescript * const a: Rectangle = { x: 10, y: 10, width: 30, height: 30 }; * const b: Rectangle = { x: 20, y: 20, width: 10, height: 10 }; * const result = cmath.rect.boolean.subtract(a, b); * // result: * // [ * // { x: 10, y: 10, width: 30, height: 10 }, // top region of A above B * // { x: 10, y: 30, width: 30, height: 10 }, // bottom region of A below B * // { x: 10, y: 20, width: 10, height: 10 }, // left region of A left of B * // { x: 30, y: 20, width: 10, height: 10 } // right region of A right of B * // ] * ``` */ function subtract(a: Rectangle, b: Rectangle): Rectangle[]; } } /** * Alignment utilities for mathematical and graphical computations. * * @example * - Align scalar values to a grid * - Align 2D vectors to the nearest target positions */ namespace align { /** * Aligns a scalar value to the nearest value in an array of scalars if it is within a specified threshold. * * This function is useful for aligning scalar values (e.g., positions, sizes, or grid alignment) to a discrete set of * target values while ensuring the alignment occurs only within a defined threshold. * * @param point - The scalar value to align. * @param targets - An array of existing scalar values to align to. * @param threshold - The maximum allowed distance for alignment. Must be non-negative. * * @returns A tuple `[value, distance, indices]` where: * - `value`: is the nearest scalar (if within threshold) or the original `point` (if not). * - `distance`: is the signed distance `point - value` to that nearest scalar. (or `Infinity` if not aligned). * - `indices` are all target indices whose distance matches the minimum distance exactly. * * @throws If `threshold` is negative or if `targets` is empty. * * @example * ```ts * // Suppose we have targets [10, 20, 20, 40], and point=22 with threshold=5. * // The minimal distance is 2 (to '20'), and note there are two '20's. * // So the function returns value=20, distance=2 (signed=22 - 20), indices=[1,2]. * * const [value, dist, indices] = cmath.align.scalar(22, [10, 20, 20, 40], 5); * console.log(value); // 20 * console.log(dist); // 2 * console.log(indices); // [1, 2] * ``` */ function scalar(point: Scalar, targets: Scalar[], threshold: number): [value: Scalar, distance: number, indicies: number[]]; /** * Aligns a 2D vector to the nearest vector(s) from an array of target vectors if it is within a specified threshold. * * This function is useful for aligning 2D points (e.g., grid or object alignment), * ensuring that alignment only occurs if the distance to the target is within a given threshold. * * @param point - The 2D vector `[x, y]` to align. * @param targets - An array of 2D vectors to which the point might align. * @param threshold - The maximum allowed Euclidean distance for alignment. Must be non-negative. * @returns A tuple `[value, distance, indices]` where: * - `value`: The nearest target vector if within threshold, otherwise the original `point`. * - `distance`: The Euclidean distance `dist(point, value)` if alignment occurs; otherwise `Infinity`. * - `indices`: **All** target indices whose distance from `point` is exactly equal to the minimum distance (ties). * * @throws If `threshold` is negative or if `targets` is empty. * * @example * // Suppose we have multiple points at the same nearest distance: * // targets = [[0, 0], [5, 5], [5, 5], [10, 10]] * // point = [6, 6], threshold = 3 * // The minimal distance (~1.414) is to both [5, 5] entries (indices 1 and 2). * // * // The function returns: * // value = [5, 5] * // distance = ~1.41421356237 * // indices = [1, 2] * * const [snappedVec, dist, tiedIndices] = cmath.align.vector2([6, 6], [[0,0],[5,5],[5,5],[10,10]], 3); * console.log(snappedVec); // [5, 5] * console.log(dist); // ~1.41421356237 * console.log(tiedIndices); // [1, 2] */ function vector2(point: cmath.Vector2, targets: cmath.Vector2[], threshold: number): [value: cmath.Vector2, distance: number, indices: number[]]; } namespace segment { /** * Returns the orientation of the ordered triplet (a, b, c). * Positive for counter-clockwise, negative for clockwise, and zero for collinear. */ function orientation(a: Vector2, b: Vector2, c: Vector2): number; /** * Checks if point `c` lies on segment `ab`. */ function onSegment(a: Vector2, c: Vector2, b: Vector2): boolean; /** * Tests whether two line segments intersect. */ function intersects(p1: Vector2, p2: Vector2, q1: Vector2, q2: Vector2): boolean; /** * Tests whether a line segment intersects with an axis-aligned rectangle. */ function intersectsRect(p0: Vector2, p1: Vector2, rect: Rectangle): boolean; /** * Project point `p` onto segment `[a, b]`. Returns the clamped * parameter `t ∈ [0, 1]` (0 at `a`, 1 at `b`) and the resulting * position on the segment — the perpendicular foot when it falls * inside `[a, b]`, otherwise the nearer endpoint. * * Degenerate segment (`a == b`) returns `{ t: 0, position: a }`; * the `t` is conventional (there's no parameter to recover) but the * `position` is honest. * * Companion to {@link point_distance}; the two share the same * clamped-foot algorithm — `point_distance` returns only the * Euclidean distance, this one returns the foot and `t`. */ function project_point(a: Vector2, b: Vector2, p: Vector2): { t: number; position: Vector2; }; /** * Shortest distance from point `p` to segment `ab`. The closest point * on the segment is the perpendicular foot when it falls inside `[a, b]`, * otherwise the nearer endpoint. For a degenerate segment (a == b) this * is the point-to-point distance. */ function point_distance(p: Vector2, a: Vector2, b: Vector2): number; } /** * Polyline primitives — open or closed chains of points. Distinct from * `polygon` (which is closed-by-definition and concerned with interior * containment); these functions measure proximity to the chain itself. */ namespace polyline { /** * Shortest distance from `p` to the polyline `pts`. Each adjacent pair * `pts[i], pts[i+1]` contributes a segment; when `closed` is true the * last→first edge is included. * * For empty `pts` returns `Infinity`. For a single point returns the * point-to-point distance. Note: for a closed ring, this is the * distance to the *edges*, not to the interior — a point inside the * ring returns the distance to the nearest edge, not 0. Use * `polygon.pointInPolygon` for interior tests. */ function point_distance(p: Vector2, pts: ReadonlyArray, closed: boolean): number; } namespace bezier { /** * Represents a cubic Bézier curve segment. * * A cubic Bézier curve is defined by a start point, an end point, and two control points. * These control points determine the curvature of the segment. * * The parametric equation for a cubic Bézier curve is: * \[ * B(t) = (1-t)^3 P_0 + 3(1-t)^2 t P_1 + 3(1-t) t^2 P_2 + t^3 P_3 \quad \text{where } t \in [0, 1] * \] * * Where: * - \( P_0 \): Start point of the curve. * - \( P_1 \): First control point. * - \( P_2 \): Second control point. * - \( P_3 \): End point of the curve. * * @property x1 - The x-coordinate of the first control point (absolute). * @property y1 - The y-coordinate of the first control point (absolute). * @property x2 - The x-coordinate of the second control point (absolute). * @property y2 - The y-coordinate of the second control point (absolute). * @property x - The x-coordinate of the end point (absolute). * @property y - The y-coordinate of the end point (absolute). * * @example * ```typescript * const bezier: CubicBezier = { * x1: 20, // First control point * y1: 40, * x2: 60, // Second control point * y2: 80, * x: 100, // End point * y: 120, * }; * ``` * * @remarks * - Cubic Bézier curves are commonly used in vector graphics, animation, and UI design to create smooth transitions and shapes. * - This structure assumes absolute positioning for all points. */ type CubicBezier = { x1: number; y1: number; x2: number; y2: number; x: number; y: number; }; /** * @property a - Position of the starting vertex. * @property b - Position of the ending vertex. * @property ta - Tangent at the starting vertex (relative to the vertex). * @property tb - Tangent at the ending vertex (relative to the vertex). */ type CubicBezierWithTangents = { a: Vector2; b: Vector2; ta: Vector2; tb: Vector2; }; /** * Arc Length Lookup Table (ArcLUT) for efficient arc length parameterization of cubic Bézier curves. * * An ArcLUT pre-computes and stores the relationship between curve parameter `t` and arc length `s` * to enable fast conversion between normalized arc length parameter `u` and curve parameter `t`. * This is essential for operations requiring uniform motion along the curve, such as animation * and path following. * * The lookup table contains: * - `t`: Array of curve parameters sampled at regular intervals ∈ [0, 1] * - `s`: Corresponding cumulative arc lengths from the start of the curve * - `total`: The total arc length of the curve * * Mathematical relationship: * - `t[i]` represents the i-th sample point on the curve (0 ≤ i ≤ sampleCount) * - `s[i]` represents the arc length from curve start to point `t[i]` * - `s[0] = 0` and `s[sampleCount] = total` * - For any `u` ∈ [0, 1], the corresponding `t` can be found by interpolating between * the nearest samples where `s[i] / total ≈ u` * * @property t - Array of curve parameters `t` ∈ [0, 1] sampled at regular intervals * @property s - Array of cumulative arc lengths corresponding to each `t` value * @property total - Total arc length of the curve * * @remarks * - The lookup table enables O(log n) conversion from arc length to curve parameter * - Precision depends on the number of samples used to create the LUT * - For curves with high curvature, more samples may be needed for accuracy * - The LUT can be cached and reused for multiple operations on the same curve * - Float32Array is used for memory efficiency and performance */ type ArcLUT = { t: Float32Array; s: Float32Array; total: number; }; /** * Projects a point onto a cubic Bézier segment and returns its normalized parametric position. * * This function performs a closest-point projection from a canvas-space point `p` * onto the Bézier curve defined by endpoints `a`, `b` and control tangents `ta`, `tb`. * The output is a parametric scalar `t \in [0, 1]` such that the evaluated curve * point `B(t)` is as close as possible to `p` in Euclidean distance. * * The algorithm works in two phases: * * 1. **Coarse sampling** – the curve is sampled at several uniformly spaced * parameters to obtain a good initial guess. * 2. **Newton–Raphson refinement** – starting from the best sample, the method * iteratively minimizes the squared distance function * `f(t) = |B(t) - p|^2`. The derivative `f'(t)` and second derivative * `f''(t)` are evaluated analytically, yielding quadratic and linear terms * respectively. The iteration continues for a small fixed number of steps * while clamping `t` to `[0,1]` after each update. * * Degenerate segments where both tangents are zero are handled as simple * linear projections onto the line segment `ab`. * * This operation is known as: * - "parametric projection" * - "inverse Bézier evaluation" * - "closest-point projection on a Bézier curve" * * It is commonly used in interactive vector editing to determine: * - Where a user clicked or dragged on the curve * - How to distribute deformation across curve tangents * - Where to insert or split the curve * * The resulting `t` does not correspond to arc length, but to Bézier parameter space. * For accurate geometric interpretation, `B(t)` can be evaluated and used in * follow-up logic. * * @param a - Start point of the segment. * @param b - End point of the segment. * @param ta - Tangent vector at start (relative to `a`). * @param tb - Tangent vector at end (relative to `b`). * @param p - The canvas-space point to project onto the curve. * @returns The normalized parametric scalar `t \in [0, 1]` along the Bézier segment. */ function project(a: Vector2, b: Vector2, ta: Vector2, tb: Vector2, p: Vector2): number; /** * Evaluates a cubic Bézier curve at a given parametric position `t` ∈ [0,1]. * * The curve is defined by four absolute control points: * - `P0`: Start point * - `P1`: First control point * - `P2`: Second control point * - `P3`: End point * * Uses the Bernstein polynomial form of the cubic Bézier equation: * ``` * B(t) = (1−t)³ * P0 * + 3(1−t)² t * P1 * + 3(1−t) t² * P2 * + t³ * P3 * ``` * * @param P0 - Start point of the curve. * @param P1 - First control point. * @param P2 - Second control point. * @param P3 - End point of the curve. * @param t - Parametric value in the range [0, 1] where 0 is `P0` and 1 is `P3`. * @returns A 2D vector `[x, y]` representing the point on the cubic Bézier at parameter `t`. * * @example * ```ts * const p0: Vector2 = [0, 0]; * const p1: Vector2 = [50, 100]; * const p2: Vector2 = [150, 100]; * const p3: Vector2 = [200, 0]; * * // Get the midpoint of the curve * const midpoint = evalC(p0, p1, p2, p3, 0.5); * console.log(midpoint); // → e.g., [100, 75] * ``` */ function evalC(P0: Vector2, P1: Vector2, P2: Vector2, P3: Vector2, t: number): Vector2; /** * Computes the first derivative (tangent vector) of a cubic Bézier curve * at a given parametric position `t` ∈ [0, 1]. * * The cubic Bézier is defined by four absolute control points: * - `P0`: Start point * - `P1`: First control point * - `P2`: Second control point * - `P3`: End point * * Uses the standard derivative of the cubic Bézier equation: * ``` * B'(t) = 3(1−t)² (P1 − P0) * + 6(1−t)t (P2 − P1) * + 3t² (P3 − P2) * ``` * * @param P0 - Start point of the cubic Bézier curve. * @param P1 - First control point of the curve. * @param P2 - Second control point of the curve. * @param P3 - End point of the curve. * @param t - Parametric value in the range [0, 1] where 0 is `P0` and 1 is `P3`. * @returns A 2D vector `[dx, dy]` representing the tangent vector at parameter `t`. * * @example * ```ts * const p0: Vector2 = [0, 0]; * const p1: Vector2 = [50, 100]; * const p2: Vector2 = [150, 100]; * const p3: Vector2 = [200, 0]; * * // Tangent at the midpoint (t = 0.5) * const tangent = derivC(p0, p1, p2, p3, 0.5); * console.log(tangent); // → e.g., [150, 0] * ``` */ function derivC(P0: Vector2, P1: Vector2, P2: Vector2, P3: Vector2, t: number): Vector2; /** * Evaluates a cubic Bézier curve at a given parametric position. * * The curve is defined by two endpoints and their tangent vectors. * The parametric equation for a cubic Bézier curve is: * \[ * B(t) = (1-t)^3 P_0 + 3(1-t)^2 t P_1 + 3(1-t) t^2 P_2 + t^3 P_3 \quad \text{where } t \in [0, 1] * \] * * Where: * - \( P_0 \): Start point (a) * - \( P_1 \): First control point (a + ta) * - \( P_2 \): Second control point (b + tb) * - \( P_3 \): End point (b) * * @param a - Start point of the curve \([x, y]\) * @param b - End point of the curve \([x, y]\) * @param ta - Tangent vector at start point (relative to a) \([x, y]\) * @param tb - Tangent vector at end point (relative to b) \([x, y]\) * @param t - Parametric position along the curve (0 ≤ t ≤ 1) * @returns The point on the curve at parametric position t \([x, y]\) * * @example * ```typescript * // Evaluate a straight line segment * const start: cmath.Vector2 = [0, 0]; * const end: cmath.Vector2 = [100, 100]; * const startTangent: cmath.Vector2 = [0, 0]; * const endTangent: cmath.Vector2 = [0, 0]; * const point = cmath.bezier.evaluate(start, end, startTangent, endTangent, 0.5); * console.log(point); // [50, 50] * ``` * * @example * ```typescript * // Evaluate a curved segment * const start: cmath.Vector2 = [0, 0]; * const end: cmath.Vector2 = [100, 0]; * const startTangent: cmath.Vector2 = [50, 50]; * const endTangent: cmath.Vector2 = [-50, 50]; * const point = cmath.bezier.evaluate(start, end, startTangent, endTangent, 0.5); * // Returns a point above the line due to upward tangents * ``` * * @remarks * - The function clamps the parameter t to the range [0, 1] for robustness. * - For t = 0, the function returns the start point a. * - For t = 1, the function returns the end point b. * - The function is mathematically equivalent to the standard cubic Bézier formula. * - Performance is O(1) with constant memory usage. * - Input validation ensures the function handles edge cases gracefully. */ function evaluate(a: Vector2, b: Vector2, ta: Vector2, tb: Vector2, t: number): Vector2; /** * Calculates the exact bounding box of a single cubic Bézier segment by finding all extrema. * @param a - The start vertex \([x, y]\). * @param ta - The start tangent (relative to `a`). * @param b - The end vertex \([x, y]\). * @param tb - The end tangent (relative to `b`). * @returns The bounding box \(\{ x, y, width, height \}\) that encloses the entire cubic. */ function getBBox(segment: CubicBezierWithTangents): Rectangle; /** * Tests whether a single cubic Bézier curve segment intersects with a given axis-aligned rectangle. * * A Bézier segment is defined by two endpoints `a`, `b`, and their corresponding control points `ta`, `tb`. * The rectangle is defined by its top-left `(x, y)` and dimensions `(width, height)`. * * This function performs fast hierarchical rejection and adaptive subdivision to avoid unnecessary computation. * * @param a - Start point of the Bézier curve. * @param b - End point of the Bézier curve. * @param ta - Tangent control point relative to `a`. * @param tb - Tangent control point relative to `b`. * @param rect - Target rectangle to test intersection against. * @param tolerance - Optional tolerance threshold for curve flatness (default: `0.5`). * @returns `true` if the curve intersects the rectangle, otherwise `false`. */ function intersectsRect(a: Vector2, b: Vector2, ta: Vector2, tb: Vector2, rect: Rectangle, tolerance?: number): boolean; /** * Checks whether a single cubic Bézier curve segment is fully contained * within an axis-aligned rectangle. * * The segment is defined by start and end points `a`, `b` and their * respective tangents `ta`, `tb`. The function computes the exact bounding * box of the curve and verifies that it lies entirely inside the provided * rectangle. * * @param a - Start point of the Bézier curve. * @param b - End point of the Bézier curve. * @param ta - Tangent control point relative to `a`. * @param tb - Tangent control point relative to `b`. * @param rect - Rectangle to test containment against. * @returns `true` if the entire curve is contained within `rect`, otherwise `false`. */ function containedByRect(a: Vector2, b: Vector2, ta: Vector2, tb: Vector2, rect: Rectangle): boolean; /** * Converts an SVG elliptical arc to one or more cubic Bézier curve segments. * * The `a2c` function transforms the parameters of an SVG elliptical arc into a series of cubic Bézier curves, * which are widely used in vector graphics for their smoothness and compatibility with various rendering engines. * * This implementation is inspired by Snap.svg's `a2c` function and adheres to the SVG specification's * [Arc Implementation Notes](https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes). * * **Note:** The function does not include the start point (`x1`, `y1`) in its output. It is assumed that the * start point is managed externally. The returned array consists of control points and end points for cubic Bézier curves. * * @param x1 - The x-coordinate of the starting point of the arc. * @param y1 - The y-coordinate of the starting point of the arc. * @param rx - The x-axis radius of the ellipse. Must be a non-negative number. * @param ry - The y-axis radius of the ellipse. Must be a non-negative number. * @param angle - The rotation angle of the ellipse in degrees, indicating how the ellipse is rotated relative to the x-axis. * @param large_arc_flag - A flag indicating whether the arc spans greater than 180 degrees (`1`) or not (`0`). * @param sweep_flag - A flag indicating the direction of the arc sweep. `1` for clockwise, `0` for counterclockwise. * @param x2 - The x-coordinate of the ending point of the arc. * @param y2 - The y-coordinate of the ending point of the arc. * @param recursive - (Optional) Internal parameter used for recursive splitting of the arc into smaller segments. * This parameter should not be provided by external callers. * * @returns An array of numbers representing one or more cubic Bézier curve segments. * Each cubic Bézier segment is represented by six consecutive numbers in the format: * `[c1x, c1y, c2x, c2y, x, y]`, where: * - `c1x, c1y` are the coordinates of the first control point. * - `c2x, c2y` are the coordinates of the second control point. * - `x, y` are the coordinates of the end point of the curve. * * If the arc spans more than 120 degrees, the function splits it into multiple cubic Bézier segments to maintain accuracy. * * @example * // Convert a simple arc to a single cubic Bézier curve * const bezierSegments = a2c(0, 0, 50, 50, 0, 0, 1, 50, 50); * console.log(bezierSegments); * // Output: [27.614237491539665, 0, 50, 22.385762508460335, 50, 50] * * @example * // Convert a large arc (270 degrees) into multiple cubic Bézier curves * const bezierSegments = a2c(0, 0, 100, 100, 0, 1, 1, 100, 0); * console.log(bezierSegments); * // Output: [c1x, c1y, c2x, c2y, x, y, ...] * * @example * // Handle a rotated ellipse arc * const bezierSegments = a2c(0, 0, 50, 100, 45, 0, 1, 50, 100); * console.log(bezierSegments); * // Output: [rotated_c1x, rotated_c1y, rotated_c2x, rotated_c2y, rotated_x, rotated_y] * * @throws {Error} If either `rx` or `ry` is negative. * @throws {Error} If non-numeric values are provided as inputs. * * @remarks * - The function assumes that the input radii (`rx`, `ry`) are non-negative. Negative radii will cause the function to throw an error. * - The rotation angle (`angle`) is measured in degrees and is converted internally to radians for calculations. * - If the input arc spans more than 120 degrees, the function recursively splits it into smaller segments to ensure that each cubic Bézier curve accurately represents the arc. * - The function handles cases where the arc needs to be adjusted based on the large arc and sweep flags, adhering to the SVG specification. * - The `recursive` parameter is intended for internal use only and should not be supplied by external callers. * * @see [SVG 1.1 Implementation Notes - Elliptical Arcs](https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes) * @see [Snap.svg's a2c Function](https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js#L752) */ function a2c(x1: number, y1: number, rx: number, ry: number, angle: number, large_arc_flag: 0 | 1, sweep_flag: 0 | 1, x2: number, y2: number, recursive?: [number, number, number, number]): number[]; /** * Solves for tangent values that make a cubic Bézier curve pass through a specific point * at a given parametric position. * * This function solves the constrained optimization problem: * - Minimize the change from original tangents * - Subject to the constraint that the curve passes through the target point at the specified parametric position * * The solution uses Lagrange multipliers to find tangent values that satisfy: * B(t) = targetPoint, where B(t) is the cubic Bézier curve evaluated at parametric position t. * * @param a - Start point of the curve * @param b - End point of the curve * @param ta - Original tangent at start point (relative to a) * @param tb - Original tangent at end point (relative to b) * @param t - Parametric position (0-1) where the curve should pass through the target point * @param targetPoint - The point that the curve should pass through at parametric position t * @returns New tangent values [ta, tb] that make the curve pass through the target point */ function solveTangentsForPoint(a: Vector2, b: Vector2, ta: Vector2, tb: Vector2, t: number, targetPoint: Vector2): [Vector2, Vector2]; /** * Cubic Bézier tangent (derivative) at parametric t ∈ [0,1]. * * Points are given as: * P0 = a * P1 = a + ta * P2 = b + tb * P3 = b * * Uses the standard cubic Bézier derivative formula: * B'(t) = 3(1-t)²(P1-P0) + 6(1-t)t(P2-P1) + 3t²(P3-P2) * * Where: * - P1-P0 = ta * - P2-P1 = (b+tb) - (a+ta) = b-a+tb-ta * - P3-P2 = b - (b+tb) = -tb * * @param a - Start point * @param b - End point * @param ta - Tangent at start point * @param tb - Tangent at end point * @param t - Parametric position ∈ [0,1] * @returns Tangent vector [dx, dy] */ function tangentAt(a: Vector2, b: Vector2, ta: Vector2, tb: Vector2, t: number): Vector2; /** * Subdivides a cubic Bézier curve at a given parametric position using de Casteljau's algorithm. * * This function splits a cubic Bézier curve into two sub-curves at parameter t. * The algorithm uses de Casteljau's method, which is numerically stable and geometrically intuitive. * * The result contains: * - `l`: The left sub-curve (segment over [0, t]) * - `r`: The right sub-curve (segment over [t, 1]) * - `s`: The split point B(t) on the original curve * - `t`: The clamped parameter value actually used * * @param c - The cubic Bézier curve to subdivide * @param t - The parametric position where to split (0 ≤ t ≤ 1) * @returns Object containing the two sub-curves, split point, and used parameter * * @example * ```typescript * const curve: cmath.bezier.CubicBezierWithTangents = { * a: [0, 0], * b: [100, 0], * ta: [50, 50], * tb: [-50, 50] * }; * * const result = cmath.bezier.subdivide(curve, 0.5); * // result.l is the curve from t=0 to t=0.5 * // result.r is the curve from t=0.5 to t=1 * // result.s is the point at t=0.5 on the original curve * ``` * * @remarks * - The parameter t is clamped to [0, 1] for robustness * - For t = 0, the left curve is degenerate (zero length) and the right curve equals the original * - For t = 1, the right curve is degenerate (zero length) and the left curve equals the original * - The split point s is always a point on the original curve * - Both sub-curves maintain the same geometric properties as the original curve */ function subdivide(c: CubicBezierWithTangents, t: number): { l: CubicBezierWithTangents; r: CubicBezierWithTangents; s: Vector2; t: number; }; /** * Refines an approximate intersection between two cubic Bézier curves using Newton's method. * * This function implements Newton's method to find the precise intersection point between * two cubic Bézier curves given an initial approximation. The method solves the system * of equations F(t,u) = A(t) - B(u) = 0 using iterative refinement. * * **Mathematical Background:** * * The Newton iteration solves the linear system: * ``` * J * [dt, du]^T = -F * ``` * Where: * - F(t,u) = A(t) - B(u) is the residual vector * - J = [∂A/∂t, -∂B/∂u] is the Jacobian matrix * - [dt, du]^T is the parameter update * * The solution is computed using Cramer's rule: * ``` * dt = (-Fₓ * (-∂B/∂u)ᵧ - (-Fᵧ) * (-∂B/∂u)ₓ) / det(J) * du = (∂A/∂t)ₓ * (-Fᵧ) - (∂A/∂t)ᵧ * (-Fₓ) / det(J) * ``` * * **Convergence Properties:** * - Quadratic convergence when the initial guess is sufficiently close * - Typically converges in 2-4 iterations for well-conditioned problems * - Automatic termination if the Jacobian becomes singular (det < 1e-18) * - Parameters are clamped to [0,1] to ensure validity * * @param A0 - Start point of the first cubic Bézier curve * @param A1 - First control point of the first curve * @param A2 - Second control point of the first curve * @param A3 - End point of the first curve * @param B0 - Start point of the second cubic Bézier curve * @param B1 - First control point of the second curve * @param B2 - Second control point of the second curve * @param B3 - End point of the second curve * @param t0 - Initial parameter guess for the first curve (∈ [0,1]) * @param u0 - Initial parameter guess for the second curve (∈ [0,1]) * @param iters - Maximum number of Newton iterations (typically 3-10) * * @returns An object containing: * - `t`: Refined parameter for the first curve (∈ [0,1]) * - `u`: Refined parameter for the second curve (∈ [0,1]) * - `p`: Intersection point in ℝ² (A(t) ≈ B(u)) * * @example * ```ts * // Two curves that intersect near their midpoints * const A0: Vector2 = [0, 0]; * const A1: Vector2 = [1, 0]; * const A2: Vector2 = [1, 1]; * const A3: Vector2 = [2, 1]; * * const B0: Vector2 = [0, 1]; * const B1: Vector2 = [1, 1]; * const B2: Vector2 = [1, 0]; * const B3: Vector2 = [2, 0]; * * // Initial guess at midpoints * const result = newtonRefine(A0, A1, A2, A3, B0, B1, B2, B3, 0.5, 0.5, 5); * console.log(result); * // → { t: 0.5, u: 0.5, p: [1, 0.5] } * * // Verify the intersection * const pointA = bezier.evalC(A0, A1, A2, A3, result.t); * const pointB = bezier.evalC(B0, B1, B2, B3, result.u); * const distance = Math.sqrt((pointA[0] - pointB[0])**2 + (pointA[1] - pointB[1])**2); * console.log('Intersection accuracy:', distance); // → ~1e-15 * ``` * * @see {@link bezier.evalC} - For evaluating Bézier curves * @see {@link bezier.derivC} - For computing curve derivatives * @see {@link cmath.clamp01} - For parameter clamping */ function newtonRefine(A0: Vector2, A1: Vector2, A2: Vector2, A3: Vector2, B0: Vector2, B1: Vector2, B2: Vector2, B3: Vector2, t0: number, u0: number, iters: number): { t: number; u: number; p: Vector2; }; namespace intersection { /** * test/resolve the intersection of a single curve */ namespace single { /** * Fast boolean test for cubic Bézier self-intersection existence. * * **Use Case**: When you only need to know if a curve self-intersects, without * needing the exact intersection details. This is significantly faster than * `intersection()` for boolean queries. * * **Algorithm**: Uses a simplified discriminant check that avoids the full * parameter computation and curve evaluation. Based on the mathematical property * that a cubic Bézier curve self-intersects if and only if its control polygon * has a specific geometric configuration. * * **Performance**: O(1) constant time, optimized for boolean queries. * Approximately 3x faster than `intersection()` for existence checks. * * **Key Optimizations**: * - **No Square Root**: Avoids `Math.sqrt()` computation * - **No Curve Evaluation**: Skips Bézier curve point calculation * - **Parameter Bounds Check**: Uses sum and product of roots instead of solving * - **Scale-Aware Tolerance**: Adaptive numerical tolerance based on curve scale * - **Conservative Bounds**: Robust parameter validation with geometric conditions * * **Trade-offs**: * - ✅ **Ultra Fast**: Only essential discriminant calculations * - ✅ **Memory Efficient**: No temporary arrays or complex calculations * - ✅ **Numerically Stable**: Robust handling of edge cases * - ❌ **No Details**: Returns only boolean, no intersection parameters * - ❌ **No Point**: Does not compute the actual intersection point * * **Mathematical Foundation**: * * For a cubic Bézier curve with control points P0, P1, P2, P3, the self-intersection * condition is determined by the discriminant delta = 4D0*D2 - D1^2 where: * - D0 = (P2 - P1) × (P3 - P2) * - D1 = (P1 - P0) × (P3 - P2) * - D2 = (P1 - P0) × (P2 - P1) * * The curve self-intersects if and only if: * 1. Q = D0 - D1 + D2 ≠ 0 (non-degenerate case) * 2. delta > 0 (real distinct roots exist) * 3. Both roots are in (0,1) (intersection within curve domain) * * **Parameter Conditions**: For roots u, v in (0,1): * - s = u + v ∈ (0, 2) * - p = u * v ∈ (0, 1) * - 1 - s + p > 0 (geometric constraint) * * @param C - The cubic Bézier curve to check for self-intersection * @param eps - Base numerical tolerance for floating-point comparisons (default: 1e-12). * The actual tolerance is scale-aware and adapts to curve size. * @returns `true` if the curve has a self-intersection, `false` otherwise * * @example * ```ts * // Fast existence check for UI feedback * const curve: cmath.bezier.CubicBezierWithTangents = { * a: [0, 0], b: [100, 0], * ta: [100, 100], tb: [-100, 100] * }; * * if (cmath.bezier.intersection.single.is_intersecting(curve)) { * console.log("Warning: Curve has self-intersection"); * // Show visual indicator without computing exact intersection * } * ``` * * @example * ```ts * // Performance-critical batch processing * const curves: cmath.bezier.CubicBezierWithTangents[] = [ *many curves* ]; * const selfIntersecting = curves.filter(c => cmath.bezier.intersection.single.is_intersecting(c)); * console.log(`${selfIntersecting.length} curves have self-intersections`); * ``` * * @see bezier.intersection - For exact intersection computation * @see bezier.intersection.intersections - For intersections between different curves * * @remarks * This function is designed for performance-critical boolean queries where * exact intersection details are not needed. For precise intersection data, * use `intersection()` instead. The two functions are independent and * optimized for their respective use cases: * * - **`is_intersecting()`**: Fast boolean check for UI feedback, validation, filtering * - **`intersection()`**: Precise computation for geometric operations, splitting * * Choose based on your specific needs: speed vs. precision. */ function is_intersecting(C: CubicBezierWithTangents, eps?: number): boolean; /** * Detects self-intersections of a cubic Bézier curve using adaptive subdivision * and pairwise intersection testing. * * **Mathematical Background:** * * A cubic Bézier curve B(t) with control points P₀, P₁, P₂, P₃ is defined as: * ``` * B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃, t ∈ [0,1] * ``` * * A self-intersection occurs when B(t₁) = B(t₂) for distinct parameters t₁ ≠ t₂. * This creates a system of two equations in two unknowns: * ``` * Bₓ(t₁) = Bₓ(t₂) and Bᵧ(t₁) = Bᵧ(t₂) * ``` * * **Algorithm Overview:** * * Uses a closed-form solution based on the cubic Bézier self-intersection equation: * * 1. **Compute Differences**: Calculate Δ₀ = P₁ - P₀, Δ₁ = P₂ - P₁, Δ₂ = P₃ - P₂ * 2. **Cross Products**: Compute D₀ = Δ₁ × Δ₂, D₁ = Δ₀ × Δ₂, D₂ = Δ₀ × Δ₁ * 3. **Discriminant**: Check if Δ = 4D₀D₂ - D₁² > 0 (existence condition) * 4. **Root Finding**: Solve quadratic equation for intersection parameters * 5. **Validation**: Ensure parameters are in [0,1] and distinct * * **Mathematical Properties:** * * - **Existence**: A cubic Bézier curve can have at most 1 self-intersection * - **Parameter Ordering**: Results are returned with t₁ < t₂ for consistency * - **Transverse Nature**: Self-intersections are always transverse (not tangent) * - **Closed Form**: Direct solution without iterative subdivision * * **Complexity Analysis:** * * - **Time Complexity**: O(1) constant time for the closed-form solution * - **Space Complexity**: O(1) constant space * - **Numerical Stability**: Robust closed-form solution * * @param C - The cubic Bézier curve to analyze for self-intersections * @returns Either a single self-intersection point or null if no self-intersection exists: * - `t1`, `t2`: Parameter values where the curve intersects itself (t₁ < t₂) * - `point`: The intersection point in ℝ² * * @example * ```ts * // Figure-8 curve with self-intersection * const curve: cmath.bezier.CubicBezierWithTangents = { * a: [0, 0], // P₀ * b: [100, 0], // P₃ * ta: [100, 100], // P₁ - P₀ (creates upward loop) * tb: [-100, 100] // P₃ - P₂ (creates downward loop) * }; * * const selfIntersection = cmath.bezier.intersection.single.intersection(curve); * console.log(selfIntersection); * // → { * // t1: 0.25, t2: 0.75, * // point: [50, 50] * // } * * // Verify the intersection * const point1 = cmath.bezier.evaluate(curve.a, curve.b, curve.ta, curve.tb, 0.25); * const point2 = cmath.bezier.evaluate(curve.a, curve.b, curve.ta, curve.tb, 0.75); * const distance = Math.hypot(point1[0] - point2[0], point1[1] - point2[1]); * console.log('Self-intersection accuracy:', distance); // → ~1e-12 * ``` * * @example * ```ts * // Simple curve with no self-intersections * const simpleCurve: cmath.bezier.CubicBezierWithTangents = { * a: [0, 0], * b: [100, 0], * ta: [0, 0], * tb: [0, 0] * }; * * const intersection = cmath.bezier.intersection.single.intersection(simpleCurve); * console.log(intersection); // → null * ``` * * **Mathematical Notes:** * * - **Cusp Detection**: The algorithm can detect cusps (where B'(t) = 0) but these * are not considered self-intersections * - **Numerical Precision**: Uses adaptive tolerances based on curve scale * - **Edge Cases**: Handles degenerate curves (zero-length, coincident control points) * - **Robustness**: Tolerant to floating-point arithmetic errors * * **Performance Considerations:** * * - **Constant Time**: O(1) performance regardless of curve complexity * - **No Iteration**: Direct mathematical solution without loops or recursion * - **Memory Efficient**: Only a few temporary variables needed * - **Scale-Aware**: Adaptive tolerance prevents numerical issues with large/small curves * - **Conservative**: Robust handling of degenerate cases and edge conditions * * @see bezier.intersection.intersections - For intersections between different curves * @see bezier.subdivide - For curve subdivision algorithm * * @remarks * This function is essential for planarization algorithms, as self-intersecting * curves must be split at their self-intersection points to create planar networks. * The closed-form solution provides both accuracy and performance for geometric * consistency in vector graphics operations. */ function intersection(C: CubicBezierWithTangents): { t1: number; t2: number; point: Vector2; } | null; } /** Local type of an intersection useful for planarization/boolean ops. */ enum IntersectionKind { /** Intersection lies at an endpoint (t≈0|1 or u≈0|1). */ Endpoint = "endpoint", /** Curves touch without crossing (A'(t) ∥ B'(u)). */ Tangent = "tangent", /** Proper crossing (transverse). */ Transverse = "transverse" } type BezierIntersectionPoint = Readonly<{ /** Parameter on curve A (∈[0,1]). */a_t: number; /** Parameter on curve B (∈[0,1]). */ b_t: number; /** Intersection point in ℝ² (A(a_t) ≈ B(b_t)). */ p: Vector2; /** Local classification for downstream topology decisions. */ kind: IntersectionKind; /** Optional residual ‖A(a_t)-B(b_t)‖ (post-refine). */ residual?: number; }>; type BezierIntersectionOverlap = Readonly<{ /** Param interval on A (sorted, closed). */a_range: [number, number]; /** Param interval on B (sorted, closed). */ b_range: [number, number]; }>; type BezierIntersectionResult = Readonly<{ /** Discrete intersection points. */points: ReadonlyArray; /** Continuous coincidences (rare; minimal detection). */ overlaps: ReadonlyArray; /** Optional stats for debugging/tuning. */ stats?: Readonly<{ eps: number; paramEps: number; maxDepth: number; refine: boolean; candidates: number; emitted: number; }>; }>; type IntersectionOptions = { /** Spatial tolerance (canvas units) used for bbox/flatness/termination. Default: 1e-3. */eps?: number; /** Param-space tolerance for dedup/endpoint snap. Default: 1e-3. */ paramEps?: number; /** Maximum subdivision depth. Default: 32. */ maxDepth?: number; /** Apply Newton refinement for subpixel accuracy. Default: true. */ refine?: boolean; }; /** * Compute cubic–cubic Bézier intersections (A with B) using * conservative bbox subdivision (de Casteljau) with optional Newton refinement. * * - Input curves are in `(a,b,ta,tb)` form used by your engine: * - P0 = a * - P1 = a + ta * - P2 = b + tb * - P3 = b * - Returns param pairs `(a_t, b_t)` and the point `p = A(a_t) ≈ B(b_t)`. * - Classifies each hit as `Endpoint`, `Tangent`, or `Transverse` (cheap; uses derivatives). * - Performs param-space dedup and endpoint snapping. * - Detects simple overlaps (near-coincident boxes + near-parallel) as param intervals. * * @remarks * Robustness tips: * - Tune `eps` to your canvas scale (e.g., 1e-3..1e-2). * - `refine` improves accuracy; keep small iteration count for perf. * - For planarization, use the returned params to split curves; for tangent hits you may choose to skip splitting. * * @see https://rosettacode.org/wiki/B%C3%A9zier_curves/Intersections */ function intersections(A: CubicBezierWithTangents, B: CubicBezierWithTangents, opts?: IntersectionOptions): BezierIntersectionResult; } /** * Piecewise Bézier curve operations for chains of cubic Bézier segments. * * The `piecewise` namespace provides optimized mathematical operations for working with * sequences of connected cubic Bézier curves (piecewise curves). These operations treat * the entire chain as a single mathematical entity while providing efficient algorithms * for common operations like arc length parameterization, point evaluation, and projection. * * A piecewise curve is defined as an ordered sequence of cubic Bézier segments where * each segment's end point connects to the next segment's start point, forming a * continuous curve. The global parameter `u` ∈ [0, 1] represents the normalized position * along the entire curve chain, while local parameters `t` ∈ [0, 1] represent positions * within individual segments. * * Key concepts: * - **Global parameter `u`**: Normalized position along the entire curve chain (0 = start, 1 = end) * - **Local parameter `t`**: Parameter within a specific segment (0 = segment start, 1 = segment end) * - **Segment index**: Zero-based index identifying which segment contains a given global parameter * - **Arc length parameterization**: Uniform motion along the curve chain regardless of segment curvature * * Common use cases: * - Path following with uniform speed * - Animation along complex multi-segment curves * - Interactive curve editing and manipulation * - Geometric operations on complex shapes * * @example * ```typescript * // Define a piecewise curve with multiple segments * const segments: cmath.bezier.CubicBezierWithTangents[] = [ * { a: [0, 0], b: [100, 0], ta: [50, 50], tb: [-50, 50] }, * { a: [100, 0], b: [200, 100], ta: [50, 50], tb: [50, -50] }, * { a: [200, 100], b: [300, 100], ta: [50, -50], tb: [-50, -50] } * ]; * * // Evaluate point at 60% along the entire curve chain * const point = cmath.bezier.piecewise.evaluate(segments, 0.6); * * // Get the segment index and local parameter for a global position * const { segmentIndex, localT } = cmath.bezier.piecewise.resolveGlobalU(segments, 0.6); * * // Project a point onto the entire curve chain * const globalU = cmath.bezier.piecewise.project(segments, [150, 50]); * ``` * * @remarks * - All operations assume segments are properly connected (end point of segment i = start point of segment i+1) * - Global parameter `u` is automatically clamped to [0, 1] for robustness * - Arc length parameterization provides uniform motion along the curve chain * - Operations are optimized for chains with many segments * - Caching mechanisms are used internally for performance-critical operations */ namespace piecewise { /** * Compact representation of a piecewise Bézier curve network. * * The `Network` type provides a memory-efficient structure for representing chains of * connected cubic Bézier segments. It separates vertex positions from segment connectivity * and tangent information, allowing for efficient storage and manipulation of complex * curve networks. * * Structure: * - `vertices`: Array of unique vertex positions in 2D space * - `segments`: Array of segment definitions, each referencing vertices by index * * Each segment is defined by: * - `a`: Index of the start vertex in the `vertices` array * - `b`: Index of the end vertex in the `vertices` array * - `ta`: Tangent vector at the start vertex (relative to vertex position) * - `tb`: Tangent vector at the end vertex (relative to vertex position) * * This structure enables: * - Memory efficiency through vertex sharing * - Fast vertex position updates (change once, affects all connected segments) * - Efficient serialization and network transmission * - Support for complex topologies (branches, loops, etc.) * * @property vertices - Array of unique vertex positions in 2D space * @property segments - Array of segment definitions referencing vertices by index * * @example * ```typescript * // Define a simple curve with 3 segments forming a path * const network: cmath.bezier.piecewise.Network = { * vertices: [ * [0, 0], // vertex 0 * [100, 0], // vertex 1 * [200, 100], // vertex 2 * [300, 100] // vertex 3 * ], * segments: [ * { a: 0, b: 1, ta: [50, 50], tb: [-50, 50] }, // segment 0: vertex 0 → 1 * { a: 1, b: 2, ta: [50, 50], tb: [50, -50] }, // segment 1: vertex 1 → 2 * { a: 2, b: 3, ta: [50, -50], tb: [-50, -50] } // segment 2: vertex 2 → 3 * ] * }; * * // Evaluate point at 60% along the entire network * const point = cmath.bezier.piecewise.evaluate(network, 0.6); * ``` * * @example * ```typescript * // Network with branching structure * const branchedNetwork: cmath.bezier.piecewise.Network = { * vertices: [ * [0, 0], // root vertex * [100, 0], // branch point * [200, 50], // left branch end * [200, -50] // right branch end * ], * segments: [ * { a: 0, b: 1, ta: [50, 0], tb: [-50, 0] }, // main path * { a: 1, b: 2, ta: [50, 50], tb: [-50, 50] }, // left branch * { a: 1, b: 3, ta: [50, -50], tb: [-50, -50] } // right branch * ] * }; * ``` * * @remarks * - **No validation**: This structure does not ensure data validity or connectivity * - **Index bounds**: Segment indices must be valid within the vertices array * - **Vertex sharing**: Multiple segments can reference the same vertex * - **Memory efficiency**: Vertex positions are stored once and shared * - **Topology flexibility**: Supports complex networks with branches and loops * - **Performance**: Optimized for operations on large networks * - **Serialization**: Compact representation suitable for storage/transmission */ type Network = { vertices: Vector2[]; segments: { a: number; b: number; ta: Vector2; tb: Vector2; }[]; }; /** * Resolves a global parameter `t` to the corresponding segment index and local parameter `t`. * * This function maps a global parameter `t` ∈ [0, 1] representing position along the entire * network to the specific segment that contains that position and the local parameter `t` * within that segment. * * The mapping is based on uniform distribution across segments, where each segment * contributes equally to the global parameter space regardless of its actual length. * This is different from arc length parameterization where segments contribute based on their actual arc length. * * @param network - The piecewise Bézier network * @param t - Global parameter in range [0, 1] representing position along the entire network * @returns Object containing the segment index and local parameter `t` ∈ [0, 1] * * @example * ```typescript * const network: cmath.bezier.piecewise.Network = { * vertices: [[0, 0], [100, 0], [200, 100]], * segments: [ * { a: 0, b: 1, ta: [50, 50], tb: [-50, 50] }, * { a: 1, b: 2, ta: [50, 50], tb: [-50, 50] } * ] * }; * * const result = cmath.bezier.piecewise.resolveGlobalT(network, 0.6); * // result = { segmentIndex: 1, localT: 0.2 } * // This means 60% along the network is 20% into the second segment * ``` */ function resolveGlobalT(network: Network, t: number): { segmentIndex: number; localT: number; }; /** * Evaluates a point on the piecewise Bézier network at global parameter `t`. * * This function computes the point on the network at the specified global parameter `t` ∈ [0, 1]. * It internally resolves the global parameter to the appropriate segment and local parameter, * then evaluates that segment using the standard cubic Bézier formula. * * The global parameter `t` represents uniform distribution across segments, where each segment * contributes equally to the parameter space. This is different from arc length-based evaluation * where segments contribute based on their actual arc length. * * @param network - The piecewise Bézier network * @param t - Global parameter in range [0, 1] representing position along the entire network * @returns The point on the network at parameter `t` [x, y] * * @example * ```typescript * const network: cmath.bezier.piecewise.Network = { * vertices: [[0, 0], [100, 0], [200, 100]], * segments: [ * { a: 0, b: 1, ta: [50, 50], tb: [-50, 50] }, * { a: 1, b: 2, ta: [50, 50], tb: [-50, 50] } * ] * }; * * const point = cmath.bezier.piecewise.evaluate(network, 0.5); * console.log(point); // [x, y] coordinates at 50% along the network * ``` * * @remarks * - Performance is O(1) after resolving the segment * - Global parameter `t` is automatically clamped to [0, 1] * - Reuses the single-segment `evaluate` function for consistency * - Handles edge cases like empty networks gracefully */ function evaluate(network: Network, t: number): Vector2; /** * Projects a point onto the piecewise Bézier network and returns the global parameter `t`. * * This function finds the closest point on the network to the given input point and returns * the global parameter `t` that corresponds to that location. It performs projection on each * segment and selects the result with the minimum distance. * * The algorithm: * 1. Projects the point onto each segment using the single-segment `project` function * 2. Evaluates the projected point on each segment * 3. Computes distances to find the closest segment * 4. Converts the local parameter to global parameter `t` * * @param network - The piecewise Bézier network * @param point - The point to project onto the network [x, y] * @returns Global parameter `t` ∈ [0, 1] representing the closest point on the network * * @example * ```typescript * const network: cmath.bezier.piecewise.Network = { * vertices: [[0, 0], [100, 0], [200, 100]], * segments: [ * { a: 0, b: 1, ta: [50, 50], tb: [-50, 50] }, * { a: 1, b: 2, ta: [50, 50], tb: [-50, 50] } * ] * }; * * const t = cmath.bezier.piecewise.project(network, [150, 50]); * console.log(t); // Global parameter of the closest point on the network * ``` * * @remarks * - Performance is O(n) where n is the number of segments * - Reuses the single-segment `project` function for consistency * - Returns the global parameter that minimizes distance across all segments * - Handles edge cases like empty networks gracefully */ function project(network: Network, point: Vector2): number; } } namespace transform { const identity: Transform; /** * Multiplies two 2×3 affine transforms: `C = A · B`. * * **Order (column-vector convention):** * - `C = A · B` means **apply `B` first, then `A`**. * - This matches CSS/SVG/Canvas2D/Skia semantics. */ function multiply(A: Transform, B: Transform): Transform; /** * Applies a **scaling** to an existing 2D transform **about an absolute (world-space) origin**. * * Internally builds `T(origin) · S(scaleX, scaleY) · T(−origin)` and **pre-multiplies** it: * `M' = T(origin) · S · T(−origin) · M`. * * @param M - The original 2D transform to update. * @param scale - Uniform scale (number) or non-uniform scale `[scaleX, scaleY]`. * @param origin - World-space pivot `[ox, oy]` about which the scaling is performed. * @returns A new transform with scaling applied about `origin`. * * @example * // World-space scaling about (50, 50), applied to a translated object * const M: Transform = [ * [1, 0, 10], // a, c, tx * [0, 1, 20], // b, d, ty * ]; * const scaled = scale(M, 2, [50, 50]); * console.log(scaled); * // => * // [ * // [2, 0, -30], * // [0, 2, -10], * // ] * * @remarks * - **Coordinate space:** `origin` is **absolute/world** coordinates. If you need * **local/object** pivot (e.g., intrinsic image center), you should post-multiply: * `M' = M · T(o) · S · T(−o)`. * - **Order:** Pre-multiplication means the scale happens **in world space** on the * already-placed object. */ function scale(M: Transform, scale: number | Vector2, origin: Vector2): Transform; /** * Applies a **rotation** to an existing 2D transform **about an absolute (world-space) origin**. * * Internally builds `T(origin) · R(θ) · T(−origin)` in closed form and **pre-multiplies** it: * `M' = T(origin) · R · T(−origin) · M`. * * @param M - The original 2D transform to update. * @param deg - Rotation in degrees (positive = counter-clockwise). * @param origin - World-space pivot `[ox, oy]` about which the rotation is performed. * @returns A new transform with rotation applied about `origin`. * * @example * // Rotate 90° about (200, 100) in world space * const M: Transform = [ * [1, 0, 200], * [0, 1, 100], * ]; * const R = rotate(M, 90, [200, 100]); * console.log(R); * // One valid result is: * // [ * // [ 0, -1, 300 ], * // [ 1, 0, -100 ], * // ] * * @remarks * - **Coordinate space:** `origin` is **absolute/world** coordinates. For a **local/object** * pivot, use post-multiplication: `M' = M · T(o) · R · T(−o)`. * - The closed-form embeds the pivot into `tx, ty`; no extra “origin” is stored in the matrix. */ function rotate(M: Transform, deg: number, origin: Vector2): Transform; /** * Applies a translation to a 2D transform matrix. * * @param transform - The original 2D transform matrix. * @param delta - The translation vector `[deltaX, deltaY]` to apply. * @returns A new transform matrix with the translation applied. * * @example * const transform: cmath.Transform = [ * [1, 0, 10], // ScaleX, ShearY, TranslateX * [0, 1, 20], // ShearX, ScaleY, TranslateY * ]; * const delta: cmath.Vector2 = [5, -10]; * const translated = cmath.transform.translate(transform, delta); * console.log(translated); * // Output: * // [ * // [1, 0, 15], // TranslateX becomes 10 + 5 = 15 * // [0, 1, 10], // TranslateY becomes 20 - 10 = 10 * // ] */ function translate(transform: Transform, delta: cmath.Vector2): Transform; /** * Computes the inverse of a 2D affine transform matrix. * * The inverse transform will undo the original transform, such that: * `invert(T) * T * point = point` * * @param transform - The 2D transform matrix to invert: * ``` * [ * [a, b, tx], * [c, d, ty] * ] * ``` * @returns The inverse transform matrix, or throws an error if the transform is not invertible. * * @example * const transform: cmath.Transform = [ * [2, 0, 10], // Scale by 2 and translate by 10 * [0, 2, 20], // Scale by 2 and translate by 20 * ]; * const inverse = cmath.transform.invert(transform); * // inverse = [ * // [0.5, 0, -5], // Scale by 0.5 and translate by -5 * // [0, 0.5, -10], // Scale by 0.5 and translate by -10 * // ] * * @remarks * - The transform must be invertible (determinant must be non-zero). * - For a transform matrix [[a,b,tx],[c,d,ty]], the determinant is (a*d - b*c). * - If the determinant is zero, the transform is not invertible and an error is thrown. */ function invert(transform: Transform): Transform; /** * Extracts the scaling factors from a 2D transformation matrix. * * @param transform - The transformation matrix to extract the scale from. * @returns A `Vector2` containing the scaling factors `[scaleX, scaleY]`. * * @example * const transform: cmath.Transform = [ * [2, 0, 10], // ScaleX, ShearY, TranslateX * [0, 3, 20], // ShearX, ScaleY, TranslateY * ]; * const scale = cmath.transform.getScale(transform); * console.log(scale); // Output: [2, 3] */ function getScale(transform: cmath.Transform): cmath.Vector2; /** * Extracts the translation components (translateX, translateY) from a 2D transformation matrix. * * @param transform - The transformation matrix to extract the translation from. * @returns A `Vector2` containing the translation `[translateX, translateY]`. * * @example * const transform: cmath.Transform = [ * [1, 0, 10], // ScaleX, ShearY, TranslateX * [0, 1, 20], // ShearX, ScaleY, TranslateY * ]; * const translate = cmath.transform.getTranslate(transform); * console.log(translate); // Output: [10, 20] */ function getTranslate(transform: cmath.Transform): cmath.Vector2; /** * Extracts the approximate rotation angle (in degrees) from a 2×3 affine transform matrix. * * @param transform - The 2D transform matrix: * [ * [a, b, tx], * [c, d, ty] * ] * @returns The rotation angle in degrees, derived via `atan2(c, a)`. * * @remarks * - This assumes the matrix is primarily rotation (plus optional uniform scaling). * If there's skew or non-uniform scaling, the computed angle might be off. * - The returned angle is in the range (-180, 180]. */ function angle(transform: Transform): number; } /** * Algorithms and utilities for rectangle packing, bin packing, and layout optimization. * * Provides core implementations for various rectangle packing algorithms useful for graphical canvas * applications, UI layout management, and efficient spatial utilization scenarios. * * ### Common Use Cases: * - Arranging graphical elements dynamically on a canvas. * - Sprite sheet generation. * - UI element placement and responsive layout systems. * - Spatial optimization tasks such as texture atlas management. * * @remarks * These algorithms are optimized for performance and clarity, suitable for real-time graphical * applications where efficiency is critical. * */ namespace packing { /** * Calculates the next viable placement for a rectangular agent within a bounded domain. * * Given a rectangular domain V, an agent of dimensions (w, h), and a set of occupied regions A, * this function computes a candidate placement R ⊆ V such that R ∩ a = ∅ for every a ∈ A. * * @param view - The domain rectangle V defined by (x, y, width, height). * @param agent - An object with dimensions {width: number, height: number} for the agent. * @param anchors - An array of rectangles representing occupied regions A (which may extend beyond V). * @returns A rectangle R = (x, y, w, h) representing the placement of the agent, or null if none exists. * * @remarks * This function implements a foundational variation of the MaxRects algorithm: * 1. Initialize F = {V}. * 2. For each a ∈ A, replace each r ∈ F with r \ a. * 3. Select a candidate r ∈ F such that r.width ≥ w and r.height ≥ h, using a lexicographical minimality criterion. */ function fit(view: Rectangle, agent: { width: number; height: number; }, anchors: Rectangle[]): Rectangle | null; namespace ext { /** * TODO: can be optimized * Attempts to find a valid placement for the agent by "walking" outward from the view, * expanding the search towards the right and down directions. This function is used when * a fit within the initial view cannot be found. * * The algorithm iteratively expands the search region (starting at the view's top-left corner) * and tests candidate placements on a grid. The first candidate that does not overlap any anchor * is returned. The search is performed only in the positive (right and down) directions. * * @param view - The original view rectangle. * @param agent - The dimensions of the agent rectangle (width and height). * @param anchors - An array of rectangles representing occupied regions. * @returns A rectangle representing the placement of the agent that does not overlap any anchor. * * @example * ```typescript * const view = { x: 0, y: 0, width: 100, height: 100 }; * const agent = { width: 50, height: 50 }; * const anchors = [{ x: 0, y: 0, width: 100, height: 100 }]; * // Since no fit is found within view, walk_to_fit searches outward. * const placement = cmath.packing.walk_to_fit(view, agent, anchors); * // placement might be something like { x: 100, y: 0, width: 50, height: 50 }. * ``` */ function walk_to_fit(view: Rectangle, agent: { width: number; height: number; }, anchors: Rectangle[]): Rectangle; } } /** * @namespace miter * Geometric utilities for computing the relationship between join angle * and corner extension ratio (1 / sin(θ/2)). * * Used to determine how sharply two stroke segments can meet before * transitioning from a pointed to a beveled corner. * * Provides pure math functions for stroke join geometry — not tied to * any rendering engine or UI concept. */ namespace miter { /** * Returns the geometric corner extension ratio (1 / sin(θ/2)) * for a given interior angle. * * @param angleDeg - The interior join angle in degrees (e.g., 30) * @returns Ratio of corner extension (e.g., 3.86 for 30°) */ function ratio(angleDeg: number): number; /** * Returns the interior angle (in degrees) for a given corner extension ratio. * This is the inverse of `ratio()`. * * @param ratio - The corner extension ratio (e.g., 4) * @returns The equivalent join angle in degrees (e.g., 28.96°) */ function angle(ratio: number): number; } /** * Rasterization utilities for drawing lines between points (e.g., "connect the dots") * in integer pixel coordinates, returning the set of covered pixels. */ namespace raster { /** * Returns the fractional part of a number. * * @param x - The input number. * @returns The fractional part of x. * * @example * ```ts * const frac = fract(3.14); // 0.14 * ``` */ function fract(x: number): number; /** * Computes a pseudo-random noise value for the given 2D coordinates. * * This function generates white noise based on the input coordinates by using a simple hash * based on the sine function and a set of pre-determined constants. The constants * `12.9898`, `78.233`, and `43758.5453` are used as "magic numbers" that have been empirically * chosen to produce a good spread of values. They ensure that small differences in input coordinates * yield significant changes in the output, a technique commonly seen in GLSL noise implementations. * * The calculation performed is: * noise(x, y) = fract(sin(x * 12.9898 + y * 78.233) * 43758.5453) * where `fract` returns the fractional part of a number. * * @param x - The x-coordinate. * @param y - The y-coordinate. * @returns A pseudo-random noise value in the range [0, 1]. * * @example * ```ts * const value = cmath.raster.noise(12.34, 56.78); * console.log(value); // e.g., 0.8453 * ``` * * @remarks * While this method is fast and useful for generating grain or noise patterns in graphics applications, * it is not suitable for high-quality noise generation. */ function noise(x: number, y: number): number; /** * A Bitmap represents a raw grid of pixels. * * This is a foundational model for 2D raster-based graphics. * It contains the width, height, and a flat array of pixel data (RGBA). * * @example * const bmp: Bitmap = { * width: 256, * height: 256, * data: new Uint8ClampedArray(256 * 256 * 4), * }; */ type Bitmap = { width: number; height: number; data: Uint8ClampedArray; }; /** * Tiles a source bitmap over a specified target area. * * This function creates a new bitmap by repeating (tiling) the source bitmap * to cover the target dimensions. The source image is repeated both horizontally * and vertically using modulo arithmetic. * * @param source - The source bitmap to tile. * @param width - The desired width of the output bitmap. * @param height - The desired height of the output bitmap. * @returns A new Bitmap object with the given target dimensions, filled by tiling the source. * * @example * const sourceBitmap: Bitmap = { width: 100, height: 100, data: sourceData }; * const tiledBitmap = cmath.raster.tile(sourceBitmap, 300, 200); * // tiledBitmap now has width 300 and height 200, with the 100x100 source repeated. */ function tile(source: Bitmap, width: number, height: number): Bitmap; /** * Scales a Bitmap by separate factors along the x and y axes. * * This function creates a new Bitmap that is a scaled version of the source Bitmap. * The scaling factors are applied independently to the width and height. * * When scaling up (i.e. when either factor > 1), each destination pixel is mapped * back to a source pixel using a nearest-neighbor approach (via Math.floor). This * means that multiple destination pixels may be filled with the same source pixel's * value, resulting in a blocky, pixelated appearance. No interpolation or smoothing * is performed by this algorithm. * * @param bitmap - The source Bitmap to scale. * @param factor - The scaling factors as a 2D vector [factorX, factorY]. Both values must be positive. * @returns A new Bitmap with its dimensions scaled by the specified factors. * * @example * ```ts * const originalBitmap: Bitmap = { width: 100, height: 100, data: originalData }; * const scaledBitmap = cmath.raster.scale(originalBitmap, [2, 1.5]); * // scaledBitmap.width ≈ 200, scaledBitmap.height ≈ 150. * ``` * * @remarks * When scaling up, each destination pixel is computed by mapping its coordinate back to * the source image using nearest-neighbor sampling (via Math.floor). This approach replicates * source pixels over multiple destination pixels, which can result in a blocky or pixelated * appearance when the image is enlarged. */ function scale(bitmap: Bitmap, factor: [number, number]): Bitmap; /** * Resizes a bitmap to the specified dimensions using nearest-neighbor scaling. * Internally, this computes scaling factors and delegates to {@link scale}. * * @param bitmap - The source bitmap to resize. * @param dst - The destination dimensions as [width, height]. * @returns A new bitmap with the resized dimensions. * * @example * ```ts * const resized = cmath.raster.resize(originalBitmap, [200, 150]); * ``` */ function resize(bitmap: Bitmap, dst: cmath.Vector2): Bitmap; /** * Pads a Bitmap to the specified dimensions without scaling the source. * The source image is centered on a new canvas filled with a background color. * * @param bitmap - The source Bitmap. * @param dst - The destination dimensions as [width, height]. * @param bg - The background color as an RGBA array. Default is transparent [0, 0, 0, 0]. * @returns A new Bitmap with the source image centered on a padded canvas. * * @example * ```ts * const padded = cmath.raster.pad(originalBitmap, [300, 300], [255, 255, 255, 255]); * ``` */ function pad(bitmap: Bitmap, dst: cmath.Vector2, bg?: cmath.Vector4): Bitmap; /** * Computes a Gaussian weight based on a normalized distance. * * This function calculates a weight using a Gaussian function: * * f(x) = exp(-x² / (2σ²)) * * In this implementation, we reformulate it using a parameter `k` (where k = 1/(2σ²)). * The `hardness` parameter, ranging from 0 to 1, interpolates between two preset k values, * controlling the steepness of the falloff: * * - When hardness is 0, a higher k (e.g., 10) is used, resulting in a very steep decay. * - When hardness is 1, a lower k (e.g., 2) is used, producing a gentler decay. * * This mathematical function is useful for simulating gradual transitions. * For example, in a digital painting application, you might use this function to compute * the per-pixel opacity of a painting stroke—pixels near the center of the stroke receive a * higher weight, while those further away fade out rapidly. * * @param normDist - The normalized distance from the center (typically in the range [0, 1]). * @param hardness - A value between 0 and 1 that adjusts the steepness of the falloff. * A value of 0 produces a rapid decay (concentrating the effect near the center), * whereas 1 produces a gentler decay. * @returns The computed weight. * * @example * // In a digital painting tool, calculate the opacity weight for a pixel: * const normDist = 0.5; // e.g., pixel is halfway from the stroke's center to its edge. * const hardness = 0.0; // soft setting: steep decay * const opacityWeight = gaussian(normDist, hardness); * * // Use `opacityWeight` to modulate the alpha channel when blending the stroke. */ function gaussian(normDist: number, hardness: number): number; /** * Generalized smoothstep function. * * This function is analogous to GLSL's built-in smoothstep (and its variant "smootherstep"), * but allows you to specify the order (N) for a customizable falloff curve. * * @param N - The order of the smoothStep (e.g., N=2 corresponds to the common "smootherstep"). * @param x - A value in the range [0, 1]. * @returns The smoothed value. */ function smoothstep(N: number, x: number): number; /** * Computes the binomial coefficient using a generalized formulation of Pascal's Triangle. * * This function calculates the value of the binomial coefficient (often read as "a choose b") without * explicitly using factorials. It supports cases where `a` may be negative by using a generalized * formulation derived from Pascal's Triangle. * * @param a - The upper parameter in the binomial coefficient expression; can be negative. * @param b - The lower parameter (a non-negative integer) representing the number of selections. * @returns The computed binomial coefficient. * * @remarks * The calculation performed is equivalent to: * \[ * \binom{a}{b} = \frac{a \cdot (a-1) \cdot \ldots \cdot (a-b+1)}{b!} * \] * This iterative approach avoids direct factorial computation, which allows for handling negative values for `a`. */ function pascaltriangle(a: number, b: number): number; /** * Returns all integer pixel coordinates (x, y) along a straight line * between (x0, y0) and (x1, y1) using Bresenham's algorithm. * * @param a - start point in integer pixel coordinates (x, y). * @param b - end point in integer pixel coordinates (x, y). * @returns An array of {@link cmath.Vector2} objects for each pixel along the line. * * @example * ```ts * const linePixels = cmath.raster.bresenhamLine([10, 10], [15, 20]); * // linePixels => [ * // [10, 10], * // [10, 11], * // [10, 12], * // ... * // ] * ``` */ function bresenham(a: cmath.Vector2, b: cmath.Vector2): Array; /** * Computes all integer pixel coordinates within a circle centered at `center` with radius `radius`. * Optionally, clips the points to the bounding box specified by `clipRect`. * * @param center - The center of the circle `[cx, cy]`. * @param radius - The radius of the circle in pixels. * @param clipRect - An optional `{ x, y, width, height }` rectangle for clipping. * If omitted, no clipping is applied. * @returns A list of `[x, y]` pixel coordinates inside the circle. * * @example * // Circle fill with no clipping * const pixels = circle([50, 50], 10); * * @example * // Circle fill, clipped to a rectangle at (40,40) of size 100x100 * const clippedPixels = circle([50, 50], 10, { x: 40, y: 40, width: 100, height: 100 }); */ function circle(center: cmath.Vector2, radius: number, clipRect?: cmath.Rectangle): Array; function ellipse(center: cmath.Vector2, radius: cmath.Vector2): cmath.Vector2[]; /** * Generates an array of integer pixel coordinates within a given rectangle. * * This function returns all pixel coordinates contained within the specified rectangle. * The rectangle is defined by its top-left corner (`x`, `y`) and its dimensions (`width`, `height`). * * @param rect - A rectangle defined by `{ x, y, width, height }`. * @returns An array of `[x, y]` tuples, where each tuple represents an integer pixel coordinate inside the rectangle. * * @example * ```ts * const rect = { x: 40, y: 35, width: 20, height: 30 }; * const points = cmath.raster.rectangle(rect); * // points will contain coordinates for pixels within the rectangle spanning: * // x from 40 to 60 and y from 35 to 65. * ``` */ function rectangle(rect: cmath.Rectangle): cmath.Vector2[]; /** * Performs a flood fill on a bitmap starting from the given coordinate. * * The algorithm fills contiguous pixels that match the target color (the color at the starting point) * with the provided fillColor using an iterative stack-based approach. * * **Note:** This function modifies the input bitmap's data directly. * * @param bitmap - The bitmap to fill. * @param pos - The x y coordinate to start filling. * @param fill - The color to fill with, as [r, g, b, a]. * * @remarks * The function first checks whether the starting pixel's color is already identical to the fillColor. * If they match, it returns immediately without performing any fill operations. */ function floodfill(bitmap: Bitmap, pos: cmath.Vector2, fill: Vector4): void; } namespace range { /** * Calculates the mean (average center) of multiple numerical ranges. * * @param ranges - A variable number of ranges, each represented as a `[start, end]` tuple. * @returns The mean center as a single number. * * @example * ```typescript * const meanCenter = cmath.range.mean([0, 10], [20, 30], [40, 50]); * console.log(meanCenter); // Output: 25 * ``` */ function mean(...ranges: Range[]): number; function fromRectangle(rect: Rectangle, axis: cmath.Axis): Range; function length(range: Range): number; /** * returns 3 point chunk, [start, mid, end] * @param range * @returns */ function to3PointsChunk(range: Range): [number, number, number]; /** * Groups ranges by their uniform gaps. * * This function identifies subsets of ranges where the gaps between consecutive ranges * are consistent within a specified tolerance. Gaps are calculated as the distance * between the end of one range and the start of the next. Overlapping ranges are ignored. * * @param ranges - An array of numerical ranges, each represented as a `[start, end]` tuple. * @param tolerance - The allowable deviation for gaps to be considered uniform. Defaults to `0`. * @returns An array of grouped ranges with uniform gaps. Each group contains: * - `loop`: The indices of the ranges in the group. * - `min`: The minimum start value among the grouped ranges. * - `max`: The maximum end value among the grouped ranges. * - `gap`: The uniform gap between consecutive ranges (always non-negative). 0 when only one range is present. * * @example * ```typescript * const ranges: cmath.Range[] = [ * [0, 10], * [15, 25], * [30, 40], * ]; * const result = cmath.range.groupRangesByUniformGap(ranges); * console.log(result); * // Output: * // [ * // { loop: [0], min: 0, max: 10, gap: 0 }, * // { loop: [1], min: 15, max: 25, gap: 0 }, * // { loop: [2], min: 30, max: 40, gap: 0 }, * // { loop: [0, 1], min: 0, max: 25, gap: 5 }, * // { loop: [0, 2], min: 0, max: 40, gap: 20 }, * // { loop: [1, 2], min: 15, max: 40, gap: 5 }, * // { loop: [0, 1, 2], min: 0, max: 40, gap: 5 }, * // ] * ``` * * @remarks * - The function uses the power set approach, which has exponential time complexity. It's recommended to use it with a reasonable number of ranges. * - Overlapping ranges are allowed as long as the gaps between their end and start points are consistent. * - The `tolerance` parameter allows for slight variations in gaps, which is useful in scenarios with floating-point precision issues. */ function groupRangesByUniformGap(ranges: Range[], k?: number, tolerance?: number): { loop: number[]; min: number; max: number; gap: number; }[]; } namespace ext { namespace snap { /** * A Vector2 that can take null values for each axis. * * This is for representing snap points that is infinity (or ignore) in counter axis. * * E.g. for 2D snapping, but where each axis are snapped independently. */ type AxisAlignedPoint = [number, number] | [number, null] | [null, number]; type Snap1DResult = { /** * the distance (delta) needs to be applied to the agents to snap within the threshold. * * `Infinity` if no snap. * * @example * * const translated = agents.map((p) => p + distance); */ distance: Scalar; /** * the indices of the agents that satisfied the snap. */ hit_agent_indices: Scalar[]; /** * the indices of the anchors that the agents snapped to. */ hit_anchor_indices: Scalar[]; }; /** * Snaps an array of scalar points to the nearest target points within a specified threshold. * * @param agents - An array of scalar points to snap. * @param anchors - An array of existing scalar points to snap to. * @param threshold - The maximum allowed distance for snapping. * @param tolerance - The tolerance for delta matching. * @returns {Snap1DResult} The result of the snapping operation. */ function snap1D(agents: Scalar[], anchors: Scalar[], threshold: Scalar, tolerance?: number): Snap1DResult; type Snap2DAxisonfig = { /** * false: no snap, otherwise threshold value. */ x: false | number; /** * false: no snap, otherwise threshold value. */ y: false | number; }; type Sanp2DAxisAlignedResult = { x: cmath.ext.snap.Snap1DResult | null; y: cmath.ext.snap.Snap1DResult | null; }; /** * Snaps an array of points to the nearest target point along each axis independently. * The snapping delta is computed for each axis separately and applied to all points. * * @param agents - An array of 2D points (Vector2) to snap. * @param anchors - An array of existing 2D points to snap to. * @param threshold - The maximum allowed single-axis distance for snapping. * @returns The snapped points and the delta applied: * - `value`: The translated points. * - `distance`: The delta vector applied to align the points. */ function snap2DAxisAligned(agents: cmath.Vector2[], anchors: cmath.ext.snap.AxisAlignedPoint[], config: Snap2DAxisonfig, tolerance?: number): Sanp2DAxisAlignedResult; /** * Namespace for spacing-related snapping and range calculations. * * This module provides utilities for working with 1D ranges, calculating spaces between them, * and projecting new ranges based on existing ones. * * **Definitions & Design** * - loops * - are aligned ranges with identical gaps (2 or more ranges). but for simplicity, we do this by combinations of ranges (exactly 2 ranges) * - each loop has a projected snap extension, `next` (`a`) and `center` (virtually a, b, and center, where b being mirror of a) * - a projected loop data will contain multiple delta (space) * - one is from 'this' loop, others from other loops' space, but within the same direction. * - the delta can be interpreted as ... * - a = loop[-1] + delta (the a point is last loop item (biggest) plus delta. * - b = loop[0] - delta (b is mirrored a) * - center = mean(loop[0].a, loop[-1].b) * - the hit test of the range will take direction 1 or -1 (mirrored) * - the `a` testing is used for testing hit between `a` and input's `a` * - the mirrored testing is used for testing hit between `b` (mirrored a) and input's `b` * - how to tell why it's snapped * - when input's a, b or c is hit, it will contain to which loop it's hit. and the space (except c). * - since the space can be originated from other loops, and multiple loops can have identical spaces, we can return all loops that contains that delta as original space, plus the hit one's loop * * * ``` * 1 2 * range | [-----] [-----] | * space | |----| | * projections | |----| |----| |----| | * targets | | | | | * align | (b) (c) (a) | * ``` * * In above example, the new segment (range) `ab` have virtually 4 possible snap points to be evenly spaced. * - (a) point the `a` of the new segment can snap to. * - (b) point the `b` of the new segment can snap to. * - (c) point the `center` of the new segment can snap to. (if the ab is smaller than the space) * - the sub-virtual `cb`, `ca` point will be calculated as new segment is determined. * - (cb) `c - length / 2` point the `b` of the new segment can snap to. (if smaller than the space) * - (ca) `c + length / 2` point the `a` of the new segment can snap to. (if smaller than the space) * * Additionally, in more than 2 segment cases, the space between certain combination can also be registered as a or b point. * For example, * * ``` * 1 2 3 * range | [-----] [-----] [-----] | * space | |----| |-------------| | * | (1_2) (2_3) | * projections | |----| |----| |----| |----| | * reason | (1_2) (1_2) (1_2) ^(1_2) | // the ^(1_2) is applied to 3rd * ``` * * This way, we can provide additional ux-friendly snapping points for the user. */ namespace spacing { type ProjectionPoint = { /** * position */ p: number; /** * origin position */ o: number; /** * forwared loop (gap) index (including self) * * -1 if not forwarded */ fwd: number; }; type DistributionGeometry1D = { /** * the ranges to calculate the space from */ ranges: cmath.Range[]; /** * combinations of ranges (overlapping ignored) * @example * ``` * ranges = [[0, 10], [20, 30], [40, 50]]; * loops = [[0, 1], [0, 2], [1, 2]]; * ``` */ loops: number[][]; /** * index-aligned gaps of each loops * * @example * * ``` * // gaps[0] is the gap between loops[0][0] and loops[0][1] * ranges = [[0, 10], [20, 30], [40, 50]]; * loops = [[0, 1], [0, 2], [1, 2]]; * gaps = [10, 30, 10]; * ``` */ gaps: number[]; /** * index-aligned projections of `a` points and the gap value applied to this point * * this is not conidered as a "range" since [0] and [1] is not ensured to be in the same direction. ([1] can be smaller than [0]) * * from [1] anchor, the delta is applied, resulting in the [0] point. */ a: ProjectionPoint[][]; /** * index-aligned projections of `b` points * * this is not conidered as a "range" since [0] and [1] is not ensured to be in the same direction. ([1] can be smaller than [0]) * * from [1] anchor, the delta is applied, resulting in the [0] point. */ b: ProjectionPoint[][]; }; /** * calculates the space between two ranges, returns a set of projections of the next range for each combination. * * @param ranges the ranges to calculate the space from * @param agentLength optional agent input. the size of this agent will be used for plotting center-originated points. (if the agent fits into the gap) * * @remarks * - ignores the combination if overlaps (to ensure positive space) */ function plotDistributionGeometry(ranges: cmath.Range[], agentLength?: Scalar): DistributionGeometry1D; } } namespace movement { /** * indicates a movement within 2D space. * * each can be null, when null, it treated as 0 or ignored depending on the context. * * this is to indicate the context of the movement vector, where 0 means no movement, but null means to ignore that axis. */ type Movement = [number | null, number | null]; /** * normalizes the movement vector. null is treated as 0. * @returns a signed {@link Vector2} */ function normalize(m: Movement): Vector2; /** * returns a new movement vector with single axis locked by dominance. * * the other axis will be null. * * @param m * @returns */ function axisLockedByDominance(m: Movement): Movement; } namespace viewport { /** * Opinionated transform-to-fit for a single canvas zoom. * Supports uniform or per-side margin, using the smaller scale to fully fit. * * @param viewport - The viewport rectangle: { x, y, width, height } * @param target - The bounding box of the contents: { x, y, width, height } * @param margin - Margin can be a single number (uniform) or [top, right, bottom, left]. * @returns A 2D transform matrix [[scale, 0, tx], [0, scale, ty]] that fits `target` into `viewport`. * * @example * const viewport = { x: 0, y: 0, width: 800, height: 600 }; * const target = { x: 100, y: 50, width: 400, height: 400 }; * const t = transformToFit(viewport, target, [50, 20, 50, 20]); * // => e.g. [ * // [0.75, 0, 60], * // [0, 0.75, 40], * // ] */ function transformToFit(viewport: cmath.Rectangle, target: cmath.Rectangle, margin?: number | [number, number, number, number]): cmath.Transform; } } namespace ui { /** * `['x', 100]` will draw a y-axis line at x=100 */ type Rule = [axis: "x" | "y", offset: number]; type Point = { label?: string; x: number; y: number; }; type Line = { label?: string; x1: number; y1: number; x2: number; y2: number; }; function transformPoint(point: cmath.ui.Point, transform: cmath.Transform): cmath.ui.Point; function transformLine(line: cmath.ui.Line, transform: cmath.Transform): cmath.ui.Line; /** * Continuous parameterized 1D curve — a continuous map from * `t ∈ [0, 1]` to `R²`. Two kinds today: * * - `segment` — straight line between two points. * - `arc` — circular arc parameterized by `from` → `to` angles in * radians. * * Each carries one scalar parameter `t ∈ [0, 1]` — 0 at the * curve's start, 1 at its end. Position recovery: {@link * evaluateCurve}; inverse projection: {@link projectPointOnCurve}. * * For DISCRETE constraint sets — a finite set of valid positions * with no interpolation between them, technically NOT a curve — * see {@link PointSet}. */ type Curve = { kind: "segment"; a: cmath.Vector2; b: cmath.Vector2; } | { kind: "arc"; center: cmath.Vector2; radius: number; /** Start angle in radians (CCW from +x). `t = 0` lands here. */ from: number; /** End angle in radians. `t = 1` lands here. Sweep is linear * in `(to - from)`; for a well-formed arc supply `to >= from` * so the parameterization runs counter-clockwise. A negative * sweep returns the start position from * {@link projectPointOnCurve} (degenerate). */ to: number; }; /** * Finite, ORDERED set of points — the discrete sibling of * {@link Curve}. NOT a curve: a point set has no continuous * parameterization, no tangent, no length in the geometric sense. * * Conceptually `t ∈ [0, 1]` maps to a point by index: * `points[round(t · (n − 1))]`. The semantic is "index", not * "fraction along the curve". Projection snaps to the nearest * point by Euclidean distance. */ type PointSet = { kind: "points"; points: readonly cmath.Vector2[]; }; /** * Evaluate a curve at `t ∈ [0, 1]`. Out-of-range `t` is clamped — * `t = 1.5` returns the curve's endpoint, not an extrapolation. * * - segment: linear interpolation between `a` and `b`. * - arc: angle is `from + t · (to - from)`, position on the circle * of radius `radius` around `center`. * * Pure. Pair with {@link projectPointOnCurve} for the inverse. */ function evaluateCurve(curve: cmath.ui.Curve, t: number): cmath.Vector2; /** * Evaluate a point set at `t ∈ [0, 1]` — round to the nearest * index and return that point. `t` is clamped before rounding. * Empty set returns `[0, 0]` (degenerate convention). */ function evaluatePointSet(set: cmath.ui.PointSet, t: number): cmath.Vector2; /** * Project a doc-space point onto a curve. Returns the closest * parametric position `t ∈ [0, 1]` and the corresponding `position` * on the curve. A point past either endpoint clamps to that * endpoint — no extrapolation. * * - segment: delegates to {@link cmath.segment.project_point} * (perpendicular foot clamped to `[a, b]`). * - arc: angle of `point` relative to `center`, normalized into * `[from, from + 2π)`, then clamped to `[from, to]` so points * outside the arc's sweep clamp to the nearer endpoint. * * Degenerate curves (zero-length segment, zero-radius arc, * non-positive arc sweep) return `{ t: 0, position: }`. */ function projectPointOnCurve(curve: cmath.ui.Curve, point: cmath.Vector2): { t: number; position: cmath.Vector2; }; /** * Project a point onto a {@link PointSet} — find the nearest * point by Euclidean distance. Returns the snapped position and a * normalized index `t = i / (n − 1)`, matching the `t ∈ [0, 1]` * convention of {@link evaluatePointSet}. * * Linear scan; n is assumed small (no spatial index). */ function projectPointOnSet(set: cmath.ui.PointSet, point: cmath.Vector2): { t: number; position: cmath.Vector2; }; /** * Returns one of `rect`'s two diagonals, selected by `direction`. * * For corner directions the diagonal runs from the *opposite* corner * to the named corner (the named corner is the line's `(x2, y2)`): * * - `"ne"` → BL → TR * - `"se"` → TL → BR * - `"nw"` → BR → TL * - `"sw"` → TR → BL * * Edge directions canonicalize to the diagonal of an adjacent corner-pair: * * - `"n"` → BL → TR (the `"ne"` diagonal) * - `"s"` → TL → BR (the `"se"` diagonal) * - `"e"` → TL → BR (the `"se"` diagonal) * - `"w"` → TR → BL (the `"sw"` diagonal) * * Axis-aligned. For a rotated rect, apply the rect's transform to the * returned line. * * @param rect - the rectangle * @param direction - which corner / edge selects the diagonal * @returns the diagonal as a `ui.Line` (no label) */ function diagonalForDirection(rect: cmath.Rectangle, direction: cmath.CardinalDirection): cmath.ui.Line; /** * Ensures that (x1, y1) <= (x2, y2) in a canonical way. * * - If `line.x1 > line.x2`, swaps the endpoints. * - If `line.x1 === line.x2` but `y1 > y2`, swaps the endpoints. * * This is often useful so that two line segments describing the * "same" geometric positions will have identical (x1, y1, x2, y2). * * @param line - The line to be normalized, e.g. `{ x1, y1, x2, y2, label? }`. * @returns A new `Line` object with possibly swapped endpoints, ensuring * `(x1 < x2)` or `(x1 === x2 && y1 <= y2)`. */ function normalizeLine(line: T): T; /** * Formats a number to the specified precision only when needed. * * If the number, after rounding, is an integer (i.e. no meaningful fractional part remains), * the function returns the integer as a string without trailing zeros. Otherwise, it formats the * number to the provided decimal precision using `toFixed()`. * * @param num - The number to format. * @param precision - The number of decimal places to round to. * @returns The formatted number as a string. For example: * - `formatNumber(1, 1)` returns `"1"`. * - `formatNumber(1.2222, 1)` returns `"1.2"`. * - `formatNumber(9.0001, 2)` returns `"9"`. * * @example * // Returns "1" because 9.0001 rounds to 9 with 2 decimal precision and no fractional part remains. * formatNumber(9.0001, 2); * * @example * // Returns "9.12" because the rounded value has a non-zero fractional part. * formatNumber(9.1234, 2); */ function formatNumber(num: number, precision: number): string; namespace gradient { /** * Control points defining a gradient in normalized space. * * The points `A`, `B`, and `C` correspond to the gradient handle * positions and are defined in a unit square where the top-left is (0,0) * and the bottom-right is (1,1). */ interface ControlPoints { A: cmath.Vector2; B: cmath.Vector2; C: cmath.Vector2; } /** * Supported gradient paint types. */ type GradientType = "linear_gradient" | "radial_gradient" | "sweep_gradient" | "diamond_gradient"; /** * Returns the canonical control points for a gradient type. * * These points define the identity transform for the gradient. */ function baseControlPoints(type: GradientType): ControlPoints; /** * Computes an affine transform matrix that maps the canonical control * points of a gradient to the provided control points. */ function transformFromControlPoints(points: ControlPoints, type: GradientType): cmath.Transform; /** * Produces a relative 2D transform matrix for a linear gradient at `deg` degrees * in a normalized 1x1 space (Figma-like behavior). */ function computeRelativeLinearGradientTransform(deg: number): Transform; } } /** * Parametric handle — a scalar `value` constrained to a 1D track * (a {@link cmath.ui.Curve} or {@link cmath.ui.PointSet}), and the * collection / grouping primitives that compose them. * * Conventions: * * - Strictly 1D `value` on a 1D track. Multi-axis affordances * (Bezier tangents, gradient stops) are a different shape. * - Coincidence groups are opt-in via {@link ParametricHandleGroup}. * No built-in collapsing. * - Discretization (stepped values) is `domain.step`, not a separate * primitive. */ namespace parametric { /** * A scalar `value` constrained to a 1D `track`, optionally bounded * and quantized by `domain`, optionally floored away from `t = 0` * by `inset`. * * The mapping is `value → t ∈ [0, 1]` via `domain`, and `t → * position` via {@link cmath.ui.evaluateCurve} (continuous tracks) * or {@link cmath.ui.evaluatePointSet} (discrete tracks). The * inverse direction projects an external point back to `t`, then * back to `value`, so the stored scalar stays in its own units * (radii, angles, counts, …) without the track's parameterization * leaking out. */ interface ParametricHandle { /** Stable identifier within the set this handle belongs to. */ id: string; /** The 1D track the handle rides — a continuous curve OR a * discrete point set. */ track: cmath.ui.Curve | cmath.ui.PointSet; /** Current scalar value. Mapped to `t ∈ [0, 1]` via `domain`. */ value: number; /** Optional value bounds + quantization. Defaults: `min = 0`, * `max = 1`. `step` quantizes the emitted value (`step: 1` * snaps to integers, etc.). */ domain?: { min?: number; max?: number; step?: number; }; /** Optional minimum distance from `t = 0` along the track, in * the track's own units (the same units as `track.a` / `track.b` * for a segment, `track.radius` for an arc). When `value` would * place the handle inside this neighborhood of `t = 0`, the * rendered position is floored to the inset. Consumers * expressing the floor in another unit system (e.g. screen-px) * convert to track-units before populating this field. Omit to * disable. */ inset?: number; } /** * Optional grouping declaration. When two or more handles in the * same group coincide in track-space (the segments / arcs collapse * to one point), a consumer may treat them as one hit target and * disambiguate the intended handle by `policy`. * * `direction-resolved` — pick the handle whose track tangent at * the coincident position is most aligned with the disambiguating * direction. */ interface ParametricHandleGroup { ids: readonly string[]; policy: "direction-resolved"; } /** * A set of parametric handles, optional groupings, and an * optional `transform`. When `transform` is set, handle tracks are * expressed in local coordinates; consumers apply the transform to * obtain positions in the outer frame. */ interface ParametricHandleSet { handles: readonly ParametricHandle[]; groups?: readonly ParametricHandleGroup[]; transform?: cmath.Transform; } } namespace polygon { type Polygon = Vector2[]; /** * Determines whether a point lies inside a polygon using the ray-casting algorithm. * * Points on the boundary are considered inside. */ function pointInPolygon(point: Vector2, polygon: Polygon): boolean; } } //#endregion //#region ../grida-canvas-vn/dist/index.d.ts //#region vn.d.ts type Vector2$1 = cmath.Vector2; declare namespace vn { /** * Represents a loop as an array of segment indices. */ type Loop = number[]; /** * Represents a vertex in the vector network. * * For basic vertices, this is a simple `[x, y]` tuple (Vector2). * For vertices with decoration overrides, use {@link VectorNetworkVertexWithOverrides}. */ type VectorNetworkVertex = Vector2$1; /** * Represents a point on a specific segment of the vector network. * * This type provides a minimal representation of a location on a curve segment, * using parametric coordinates that can be evaluated to get the actual position * on the Bézier curve. * * @example * ```ts * const pointOnSegment: PointOnSegment = { * segment: 0, // Index of the segment in the vector network * t: 0.5 // Parametric value: 0 = start, 1 = end, 0.5 = middle * }; * * // Evaluate to get the actual position * const position = cmath.bezier.evaluate( * vertices[segments[0].a], * vertices[segments[0].b], * segments[0].ta, * segments[0].tb, * pointOnSegment.t * ); * ``` * * @see {@link cmath.bezier.evaluate} - For evaluating the point to get its position * @see {@link cmath.bezier.project} - For projecting a point onto a segment */ type PointOnSegment = { /** * Index of the segment in the vector network's segments array. * * This references a specific segment within the vector network, * allowing the point to be located on a particular curve. * * @example * ```ts * const segment = vectorNetwork.segments[pointOnSegment.segment]; * ``` */ segment: number; /** * Parametric value along the segment, ranging from 0 to 1. * * - `0` represents the start of the segment (vertex `a`) * - `1` represents the end of the segment (vertex `b`) * - `0.5` represents the middle of the segment * - Values outside [0, 1] are valid but represent points beyond the segment * * This value is used with Bézier curve evaluation functions to determine * the exact position on the curve. * * @example * ```ts * // Get the position at 25% along the segment * const position = cmath.bezier.evaluate( * vertices[segment.a], * vertices[segment.b], * segment.ta, * segment.tb, * 0.25 * ); * ``` */ t: number; }; /** * Represents a point on a segment with its evaluated position. * * This type extends `PointOnSegment` to include the computed position * on the curve, eliminating the need to re-evaluate the curve when * the point is needed for measurements or display. * * @example * ```ts * const evaluatedPoint: EvaluatedPointOnSegment = { * segment: 0, // Index of the segment * t: 0.5, // Parametric value (middle of segment) * point: [10, 20] // Evaluated position in local coordinates * }; * ``` */ type EvaluatedPointOnSegment = PointOnSegment & { /** * Resolved point on the curve in local coordinates (relative to vector network origin). * * This is the actual position on the curve at the parametric value `t`. * Storing this eliminates the need to re-evaluate the curve when the point * is needed for measurements or display. * * @example * ```ts * // Use the pre-computed position directly * const position = evaluatedPoint.point; * ``` */ point: Vector2$1; }; type AbsoluteTangentControlOnSegment = { segment: number; control: "ta" | "tb"; point: Vector2$1; }; /** * Represents a segment in the vector network, connecting two vertices. * * @property a - Index of the starting vertex. * @property b - Index of the ending vertex. * @property ta - Tangent at the starting vertex (relative to the vertex). * @property tb - Tangent at the ending vertex (relative to the vertex). */ type VectorNetworkSegment = { a: number; b: number; ta: Vector2$1; tb: Vector2$1; }; /** * Represents the vector network, consisting of vertices and segments. * * @example * ```ts * const example: VectorNetwork = { * vertices: [ * { x: 10, y: 10 }, // Index 0: Starting point * { x: 50, y: 10 } // Index 1: End point * ], * segments: [ * { * a: 0, // Start at vertices[0] (10, 10) * b: 1, // End at vertices[1] (50, 10) * ta: { x: 10, y: 10 }, // Tangent relative to start (absolute: 20, 20) * tb: { x: -10, y: 10 } // Tangent relative to end (absolute: 40, 20) * } * ] * }; * ``` * This example corresponds to the SVG path: * `M 10 10 C 20 20, 40 20, 50 10` * * ---- * * ## Guidelines for Manipulating a Vector Network * * A vector network represents a graph-like structure of vertices and segments. * Proper manipulation ensures correct rendering and functionality. * * ## Requirements * * 1. **Order of Vertices and Segments**: * - **Sequential Paths**: The order of vertices and segments must be respected for rendering strokes or paths. * - Example: Open or closed paths where the visual flow depends on the sequence. * - **Graph-Like Structures**: Order does not matter if the network is used as a graph with arbitrary connections. * * 2. **Region Construction**: * - Loops in regions (`VectorRegion.loops`) must maintain a consistent order. * - Ensure correct winding rules (clockwise or counterclockwise) for proper fill rendering. * * 3. **Adding/Removing Vertices or Segments**: * - After adding or removing elements, normalize the data: * - Update any dependent structures, such as `VectorRegion.loops`. * - Ensure indices in segments (`a`, `b`) remain valid. * * 4. **Consistency in Indices**: * - Segments (`a`, `b`) must reference valid indices in the `vertices` array. * - Avoid leaving unused or orphaned vertices. * * 5. **Control Points (Tangents)**: * - Ensure that control points (`ta`, `tb`) are relative to their respective vertices. * - Invalid or mismatched control points can lead to incorrect Bézier curve rendering. * * 6. **Validation After Manipulation**: * - Check for disconnected segments or invalid references to vertices. * - Ensure all closed regions are properly defined, with no overlapping or missing paths. * * 7. **Rendering Context**: * - Understand the context of the vector network: * - Sequential paths (order matters). * - Arbitrary connections (order may not matter). * * ## Best Practices * * - Always maintain a consistent order for paths or regions. * - Normalize the vector network data after edits to avoid rendering issues. * - Use explicit indexing (`a`, `b`) to define segment connections instead of relying on order alone. * - Validate data integrity before rendering or exporting to formats like SVG. */ interface VectorNetwork { vertices: VectorNetworkVertex[]; segments: VectorNetworkSegment[]; } /** * Result of flattening a shape node to a vector network. * * When `corner_radius` is present, the vector network contains straight * segments and corner radius should be applied as a rendering effect * (e.g. polygon, star). * * When `corner_radius` is absent, corner geometry is already baked into * the vector network as Bézier curves (e.g. rectangle, ellipse). */ interface FlattenResult extends VectorNetwork { corner_radius?: number; } /** * tangent mirroring mode * * **description based on moving ta (applies the same vice versa for tb)** * - `none` - no mirroring * - moving ta will not affect tb at all. * - `angle` - mirror the angle of the tangent, but keep the length * - moving ta will affect tb, but only the **exact (inverted)** angle will be mirrored. * - `all` - mirror the angle and length of the tangent * - moving ta will affect tb, and mirror the **exact (inverted)** value of ta, not by the delta of angle/length. * - `auto` - automatically decide whether to mirror based on the current * relationship of the tangents. If they are already mirrored, behaves like * `all`, if only the angle is mirrored behaves like `angle`, otherwise acts as `none`. */ type StrictTangentMirroringMode = "none" | "angle" | "all"; type TangentMirroringMode = StrictTangentMirroringMode | "auto"; /** * Configuration options for segment splitting operations. */ interface SplitSegmentConfig { /** * Whether to preserve zero tangents when possible. * * When `true`, if the original segment has zero tangents, the split segments * will also have zero tangents, avoiding the introduction of unnecessary * tangent controls that might confuse users. * * When `false`, the mathematically accurate de Casteljau's algorithm is used, * which may introduce non-zero tangents even for originally straight segments. * * @defaultValue true */ preserveZero?: boolean; } /** * infer the mirroring mode from two tangents * * @param ta tangent a * @param tb tangent b * * @remarks * When either tangent is the zero vector, there is no meaningful direction * or length to mirror, so this returns `"none"`. */ function inferMirroringMode(ta: Vector2$1, tb: Vector2$1): StrictTangentMirroringMode; interface OptimizationConfig { /** * Maximum distance between two vertices for them to be considered identical. * * @defaultValue 0 */ vertex_tolerance: number; /** * Remove vertices that are not referenced by any segment. * * @defaultValue true */ remove_unused_verticies: boolean; } /** * Open chain of `n` vertices joined by `n - 1` zero-tangent segments. * * Constructor used by `` and `` — * a line is a degenerate polyline with `n = 2`. Vertices are kept in * input order; coordinates are not deduped, so callers wanting unique * vertices should clean inputs first. */ function fromPolyline(points: ReadonlyArray): VectorNetwork; /** * Closed chain of `n` vertices joined by `n` zero-tangent segments — the * `n`-th segment closes the loop (`last → 0`). * * Constructor used by ``. The closing segment is a * regular `VectorNetworkSegment`; callers that need to round-trip back to * `points="…"` (where the close is implicit, not enumerated) tag it * outside this layer (see `path-edit/model.ts` `SegmentMeta.is_close_segment`). */ function fromPolygon(points: ReadonlyArray): VectorNetwork; /** * @deprecated Use {@link fromPolyline}. Kept as an alias for existing * call sites; remove once they migrate. */ function polyline(points: Vector2$1[]): VectorNetwork; /** * @deprecated Use {@link fromPolygon}. Kept as an alias for existing * call sites; remove once they migrate. */ function polygon(points: Vector2$1[]): VectorNetwork; class VectorNetworkEditor { /** * Optimizes a {@link VectorNetwork} by removing duplicate vertices and * segments, optionally removing unused vertices. * * Also known as *simplify* or *deduplicate*, this method normalizes the * network by merging vertices that share the same coordinates and by * collapsing segments with identical endpoints and tangents. The input * network is not mutated; a new, optimized network is returned instead. * * ## Deduplication Criteria * * - **Vertices**: Two vertices are considered duplicates if they have identical coordinates. * - **Segments**: Two segments are considered duplicates if they have: * - Identical endpoint indices (after vertex deduplication and index remapping) * - Identical tangent control points (`ta` and `tb` values) * * Segment orientation is preserved - a segment `a -> b` is considered different from `b -> a`. * The deduplication process first remaps segment indices to account for merged vertices, then * removes segments with identical geometry. * * @param net - The network to optimize. * @param config - Optimization options. * @returns A new {@link VectorNetwork} without duplicate or unused * vertices and segments. */ static optimize(net: VectorNetwork, config?: OptimizationConfig): VectorNetwork; /** * Creates a new {@link VectorNetwork} by combining two networks into one. * * The resulting network is optimized via {@link VectorNetworkEditor.optimize} * so that duplicate vertices or segments are removed. Segment orientation * is respected, meaning that a segment `a -> b` is considered different * from `b -> a`. * * @param a The base network. * @param b The network to merge into {@code a}. * @returns A new network containing the union of {@code a} and {@code b}. */ static union(a: VectorNetwork, b: VectorNetwork, config?: OptimizationConfig | null): VectorNetwork; /** * Translates every vertex in a {@link VectorNetwork} by the given delta. * * The returned network is a new object with all vertex positions shifted by * the provided offset. Segments and their tangents are preserved. * * @param net - The network to translate. * @param delta - The `[dx, dy]` offset applied to each vertex. * @returns A new {@link VectorNetwork} with translated vertices. */ static translate(net: VectorNetwork, delta: Vector2$1): VectorNetwork; /** * Converts a non-planar vector network into a planar network by detecting and resolving * segment intersections. * * **Planarization** is the process of transforming a vector network that contains * crossing segments into one where all segments only intersect at their endpoints * (vertices). This is essential for proper rendering, boolean operations, and * fill rule calculations. * * ## What is Planarization? * * A **planar network** is one where: * - All segments lie in a single 2D plane * - Segments only intersect at shared vertices * - No segments cross in the middle (transverse intersections) * * A **non-planar network** may have: * - Segments that cross each other at non-vertex points * - Ambiguous fill regions due to overlapping segments * - Inconsistent rendering behavior * * ## How Planarization Works * * 1. **Self-Intersection Detection**: Finds self-intersections within individual segments * using the {@link cmath.bezier.intersection.single.intersection} algorithm * 2. **Cross-Intersection Detection**: Finds all pairwise intersections between segments * using the {@link cmath.bezier.intersection.intersections} algorithm * 3. **Segment Splitting**: At each intersection point, splits the crossing segments * into multiple sub-segments * 4. **Vertex Creation**: Creates new vertices at intersection points * 5. **Topology Update**: Updates segment connectivity to maintain the network structure * * ## Intersection Types Handled * * - **Self-Intersections**: A single curve intersects itself (figure-8 curves, loops) * - **Transverse**: Segments cross at a single point (most common) * - **Tangent**: Segments touch without crossing (may be preserved or split) * - **Endpoint**: Segments meet at a shared vertex (no action needed) * - **Overlap**: Segments share a common portion (creates multiple intersection points) * * ## Use Cases * * - **Boolean Operations**: Union, intersection, difference operations require planar networks * - **Fill Rendering**: Ensures consistent fill behavior with even-odd or non-zero rules * - **Path Simplification**: Removes redundant crossings for cleaner geometry * - **SVG Export**: Many SVG renderers expect planar paths * * ## Performance Considerations * * - Time complexity: O(n²) where n is the number of segments * - Uses spatial indexing for efficiency when possible * - Intersection detection is the most computationally expensive step * - Consider using {@link VectorNetworkEditor.optimize} after planarization * * ## Example * * ```ts * // Create a network with crossing segments * const network = new VectorNetworkEditor([ * [0, 0], [100, 100], // diagonal segment * [0, 100], [100, 0] // crossing diagonal * ]); * * // Before planarization: segments cross in the middle * console.log(network.segments.length); // 2 segments * * // After planarization: segments split at intersection * network.planarize(); * console.log(network.segments.length); // 4 segments (split at crossing) * ``` * * ## Related Methods * * - {@link VectorNetworkEditor.optimize} - Removes duplicates after planarization * - {@link VectorNetworkEditor.union} - Boolean operations on planar networks * - {@link cmath.bezier.intersection.intersections} - Core intersection detection algorithm * * @remarks * This method modifies the vector network in-place. If you need to preserve * the original network, create a copy before calling this method. * * The planarization process may significantly increase the number of vertices * and segments, especially for networks with many crossings. Consider the * memory and performance implications for large networks. * * @param net - The vector network to planarize * @param config - Configuration options for segment splitting operations * @returns A new planarized {@link VectorNetwork} * * @see {@link cmath.bezier.intersection.single.intersection} - For self-intersection detection * @see {@link cmath.bezier.intersection.intersections} - For cross-intersection detection * @see {@link VectorNetworkEditor.optimize} - For post-planarization cleanup */ static planarize(net: VectorNetwork, config?: SplitSegmentConfig): VectorNetwork; private _vertices; private _segments; constructor(value?: VectorNetwork); get vertices(): VectorNetworkVertex[]; get segments(): VectorNetworkSegment[]; get value(): VectorNetwork; findVertex(p: Vector2$1): number | null; /** * Optimizes the current network in-place by removing duplicate vertices and * segments. * * This is the instance counterpart to * {@link VectorNetworkEditor.optimize | VectorNetworkEditor.optimize()} and * is sometimes referred to as *simplify* or *deduplicate*. * * @param config - Optimization options. * @returns The optimized {@link VectorNetwork}. */ optimize(config?: OptimizationConfig): VectorNetwork; /** * Converts the current vector network into a planar network by detecting and resolving * segment intersections in-place. * * This is the instance counterpart to * {@link VectorNetworkEditor.planarize | VectorNetworkEditor.planarize()}. * Unlike the static method, this modifies the current network instance directly. * * **Planarization** transforms a network with crossing segments into one where * all segments only intersect at their endpoints (vertices). This is essential * for proper rendering, boolean operations, and fill rule calculations. * * ## What Happens During Planarization * * 1. **Self-Intersection Detection**: Finds self-intersections within individual segments * using the {@link cmath.bezier.intersection.single.intersection} algorithm * 2. **Cross-Intersection Detection**: Finds all pairwise intersections between segments * using the {@link cmath.bezier.intersections} algorithm * 3. **Segment Splitting**: At each intersection point, splits the crossing segments * into multiple sub-segments * 4. **Vertex Creation**: Creates new vertices at intersection points * 5. **Topology Update**: Updates segment connectivity to maintain the network structure * 6. **Optimization**: Automatically optimizes the result to remove duplicates * * ## Example * * ```ts * const editor = new VectorNetworkEditor(network); * * // Before: network has crossing segments and self-intersecting curves * console.log(editor.segments.length); // 2 segments * * // After: segments split at intersections (including self-intersections) * editor.planarize(); * console.log(editor.segments.length); // 4+ segments (split at crossings) * ``` * * @param config - Configuration options for segment splitting operations * @returns The planarized {@link VectorNetwork} (same as the modified instance) * * @remarks * This method modifies the current network instance in-place. If you need to * preserve the original network, create a copy before calling this method. * * The planarization process may significantly increase the number of vertices * and segments, especially for networks with many crossings or self-intersections. * Consider the memory and performance implications for large networks. * * @see {@link VectorNetworkEditor.planarize} - Static version that returns a new network * @see {@link cmath.bezier.intersection.single.intersection} - For self-intersection detection * @see {@link cmath.bezier.intersections} - For cross-intersection detection */ planarize(config?: SplitSegmentConfig): VectorNetwork; /** * Creates a packed {@link VectorNetwork} containing only the specified * vertices and segments from the current network. * * The copied network is normalized through * {@link VectorNetworkEditor.optimize}, ensuring that vertex indices are * re-packed starting from zero and that duplicate vertices or segments are * removed. Vertices referenced by the copied segments are automatically * included. Vertices explicitly provided in the selection are preserved * even if they are not part of any segment unless * `config.remove_unused_verticies` is set to `true`. * * @param selection - Indices of elements to copy. * @param selection.vertices - Vertex indices to include. These vertices are * always copied and are kept even if no segment references them. * @param selection.segments - Segment indices to include. The endpoint * vertices for these segments are automatically added to the selection. * @param config - Optimization options applied to the resulting network. * Defaults to keeping unused vertices. * @returns A new, packed {@link VectorNetwork} containing the selected * subset of this editor's network. */ copy(selection: { vertices?: number[]; segments?: number[]; }, config?: OptimizationConfig): VectorNetwork; /** * finds the segment that contains the given vertex index * @param v * @returns */ findSegments(v: number, point?: "a" | "b" | "any"): number[]; /** * Returns the vertices that are directly connected to the specified * vertex via segments. * * A vertex is considered a neighbour when it shares a segment with the * input vertex. The result does not include the input vertex itself and * duplicates are removed. * * @param vertexIndex - index of the vertex to query * @returns array of neighbouring vertex indices */ getNeighboringVerticies(vertexIndex: number): number[]; /** * Returns the length of the segment at the given index. */ segmentLength(segmentIndex: number): number; /** * Determines whether the specified segments form a closed (valid) loop. * * The segments must be provided in order such that each segment's * ending vertex (`b`) matches the starting vertex (`a`) of the next * segment. A loop is considered closed when all segments connect in * sequence and the final segment links back to the first. * * @param segmentIndices - indices of segments to check * @returns `true` if the segments form a closed loop, otherwise `false` */ isLoopClosed(loop: Loop): boolean; /** * Enumerates all bounded, minimal, non-overlapping loops (faces) in a planar {@link VectorNetwork}. * * ## Overview * This function takes a vector network (vertices + segments), treats it as a * planar straight-line graph, and returns an array of loops. Each loop is an * array of segment indices that form a closed, counter-clockwise cycle * representing a bounded face in the network. * * The unbounded (outer) face is detected and excluded from the results. * The algorithm respects Euler's characteristic for planar graphs: * * ``` * loops.length = E - V + C * ``` * * where: * - `E` = number of segments * - `V` = number of vertices * - `C` = number of connected components in the graph * * ## Algorithm * 1. Builds **half-edges** (two directed edges per segment) with twin pointers. * 2. Groups outgoing half-edges per vertex and sorts them in **clockwise** order * using the geometric ray angle `atan2(P[v] - P[u])`. This avoids reliance on * curve tangents, so it works for straight-line segments and zero tangents. * 3. Uses the **left-hand rule**: from a half-edge, go to its twin, then to the * previous half-edge in the CW rotation around that vertex — this walks the * boundary of the face to the left of the original half-edge. * 4. Marks visited half-edges so each bounded face is traversed exactly once. * 5. Computes the signed area of each face to: * - Ensure counter-clockwise orientation for bounded faces. * - Identify and remove the outer face (largest absolute area). * * ## Notes * - Assumes the input is already **planarized**: all intersections occur at vertices, * and segment endpoints reference valid vertex indices. * - Degree-1 vertices (bridges or dangling edges) do not contribute to bounded faces. * - Works for multiple connected components. * - Robust for parallel or collinear edges — tie-broken by edge length and index. * * ## Complexity * - Time: `O(E log d_max)` from sorting edges at each vertex (`d_max` = max degree). * - Space: `O(E)` for half-edge structures and auxiliary arrays. * * @returns An array of loops, where each loop is an array of segment indices forming a bounded face in CCW order. * * @example * ```ts * const loops = getLoops.call({ value: vectorNetwork }); * // loops might be: * // [ * // [0, 1, 2], // triangle * // [3, 4, 5, 6] // rectangle * // ] * ``` */ getLoops(): Loop[]; getLoopPathData(loop: Loop): string; /** * checks whether the vertex is connected to exactly two segments */ isExactCorner(vertex: number): boolean; /** * Sets tangents for a corner by assigning the given tangent to one * connected segment and its mirrored counterpart to the other. When `t` * is `0`, all tangents at the corner are cleared. */ setCornerTangents(vertex: number, t: Vector2$1 | 0): void; /** * Bends a sharp corner into a smooth one by assigning mirrored tangents. * * The tangent length is derived from the length of the reference segment * and the {@link cmath.KAPPA} constant so that bending four equal-length sides * yields a perfect circle. * * @param vertex index of the vertex to bend * @param ref optional reference control (`"ta"` or `"tb"`) used only to * determine which connected segment provides the length used for * both tangents. The angle of the tangents is computed from the * geometry of the two connected segments and is unaffected by * the reference. */ bendCorner(vertex: number, ref?: "ta" | "tb"): void; /** * Bends a straight segment so that the resulting cubic Bézier curve passes * through the given target point while maintaining the relative offset of * the cursor from the segment's start. * * The offset point `ca` represents where the bending gesture started and is * used to derive the parametric location along the segment. The point `cb` * is the current cursor position that the curve should pass through. Both * coordinates are in the vector network's local space. * * The function handles coordinate space transformations to ensure consistency * when the vector network is repositioned by the editor. * * If the cursor lies on the original straight line (i.e. `cb` equals the * linear interpolation of the segment at the computed offset), the tangents * are cleared and the segment remains straight. * * @param segment index of the segment to bend * @param ca parametric position (0-1) where the bend gesture started * @param cb vector network local space point the segment should pass through * @param frozen frozen segment state containing original coordinates and tangents */ bendSegment(segment: number, ca: number, cb: Vector2$1, frozen: { a: Vector2$1; b: Vector2$1; ta: Vector2$1; tb: Vector2$1; }): void; /** * adds a vertex to the network (optionally connecting it to the selected vertex) * @param p the position of the new vertex * @param origin the index of the vertex to connect the new vertex to * @param ta if origin is provided, and the new segment is to be created, this is the `ta` value of the new segment. - use {@link getNextMirroredTangent} to get the mirrored value of the previous segment's tb * @param tb if origin is provided, and the new segment is to be created, this is the `tb` value of the new segment. * @returns the index of the added vertex */ addVertex(p: Vector2$1, origin?: number | null, ta?: Vector2$1, tb?: Vector2$1): number; /** * virtually, if the `origin` were to be connected to another vertex, it returns the new segment's ta value, based on previous segment's tb value, inverted * @param origin */ getNextMirroredTangent(origin: number): Vector2$1; /** * deletes the vertex at the given index. * associated segments are also deleted and re-assigned. * @param i * * @example * ``` * => vertices: [ A, B, C, D ] * => segments: [ AB, BC, CD ] * => deleteVertex(B) * => vertices: [ A, C, D ] * => segments: [ AC, CD ] * ``` */ deleteVertex(i: number): void; /** * Removes the vertex at the given index if no segments reference it. * * If the vertex has any dependent segments, the network is left unchanged * and `false` is returned. When the vertex is removed, all segment indices * greater than the removed index are decremented to reflect the new * positions of vertices. * * @param i - Index of the vertex to remove * @returns `true` if the vertex was removed, `false` otherwise */ removeUnusedVertex(i: number): boolean; moveVertex(i: number, p: Vector2$1): void; translateVertex(i: number, delta: Vector2$1): void; translateSegment(i: number, delta: Vector2$1): void; translate(delta: Vector2$1): void; scale(factor: Vector2$1): void; addSegment(a: number, b: number, ta?: Vector2$1, tb?: Vector2$1): void; /** * Deletes the segment at the given index. * * @param segmentIndex - Index of the segment to delete * * @example * ``` * => vertices: [ A, B, C, D ] * => segments: [ AB, BC, CD ] * => deleteSegment(1) // delete BC * => vertices: [ A, B, C, D ] * => segments: [ AB, CD ] * ``` */ deleteSegment(segmentIndex: number): void; deleteTangent(segmentIndex: number, control: "ta" | "tb"): void; /** * Splits a segment at a given parametric position and inserts a new vertex. * * This function works with both straight and curved segments. When splitting, * it maintains visual continuity by calculating appropriate tangent values * for the new segments that preserve the original curve shape. * * The splitting process uses Bézier curve subdivision to ensure the composite * curve is mathematically identical to the original: * 1. Evaluates the curve at the given parametric position to get the split point * 2. Uses Bézier subdivision to calculate the correct tangent values for both segments * 3. Creates two new segments that maintain the original curve shape exactly * 4. Inserts a new vertex at the split point * * When `preserveZero` is enabled in the config (default: true), segments with zero tangents * will maintain zero tangents after splitting, avoiding the introduction of * unnecessary tangent controls that might confuse users. This is particularly * useful for straight line segments where the mathematical accuracy of de * Casteljau's algorithm would introduce non-zero tangents. * * @param p parametric position on the segment (0 ≤ t ≤ 1) * @param config optional configuration for the split operation * @returns index of the newly inserted vertex */ splitSegment(p: PointOnSegment, config?: SplitSegmentConfig): number; /** * * @param segment the index of the segment to update * @param control the control point to update (ta or tb) * @param value the new tangent value * @param mirroring the tangent mirroring mode * */ updateTangent(segmentIndex: number, control: "ta" | "tb", value: Vector2$1, mirroring?: TangentMirroringMode): void; getBBox(): cmath.Rectangle; /** * Use with Pencil tool * * extends the polyline by adding a new vertex at the end * - assumes the the verteces are in order * @param p */ extendPolyline(p: Vector2$1): void; /** * Use with Line tool * extends the line by moving the last vertex to the new position * - assumes the the verteces are in order * - assumes there are exactly 1 or 2 vertices * @param p */ extendLine(p: Vector2$1): void; /** * Returns the vertex index associated with a segment's tangent control. * * @param segmentIndex the index of the segment * @param control which tangent control of the segment ("ta" or "tb") * @returns the index of the vertex owning the specified control */ getTangentVertex(segmentIndex: number, control: "ta" | "tb"): number; /** * Returns absolute positions of vertices. * @param offset translate offset * @param indices optional subset of vertex indices */ getVerticesAbsolute(offset?: Vector2$1, indices?: number[]): Vector2$1[]; /** * Returns absolute positions of tangent control points. * @param offset translate offset */ getControlPointsAbsolute(offset?: Vector2$1, targets?: [number, "ta" | "tb"][]): AbsoluteTangentControlOnSegment[]; } /** * Returns an approximate bounding box for the entire vector network * by collecting vertices and their tangent (control) points. */ function getBBoxApprox(vn: VectorNetwork): cmath.Rectangle; /** * Exact bounding box of a vector network by summing each segment's cubic bounding box. **/ function getBBox(vn: vn.VectorNetwork): any; /** * Converts a vector network to SVG path data. * * @param vn - Vector network to convert. * @returns The SVG path data string representing the vector network. */ function toSVGPathData(vn: vn.VectorNetwork): any; function fromSVGPathData(d: string): vn.VectorNetwork; function fromRect(shape: cmath.Rectangle): vn.VectorNetwork; /** * @param shape ellipse shape defined with reactable (xywh) * @returns vector network with 4 cubic bezier curves */ function fromEllipse(shape: cmath.Rectangle): vn.VectorNetwork; function fromRegularPolygon(shape: cmath.Rectangle & { points: number; }): vn.VectorNetwork; function fromRegularStarPolygon(shape: cmath.Rectangle & { points: number; innerRadius: number; }): vn.VectorNetwork; } //#endregion //#region ../grida-tree/src/lib.d.ts declare namespace tree { type Key = string; type RmResult = Key[]; /** * flat tree ops * * flat tree represents the structure, that each nodes are flattened via kye, yet each node has a pointer to its children. * * ```ts * flat_tree = { * "root": { * "children": ["a", "b"] * }, * "a": { * "children": ["c"] * } * } * ``` */ namespace flat_with_children { /** * Move one or more nodes under a target node (mutates the input map in-place). * @param nodes – Record mapping node IDs to objects that each have a children array. * @param sources – Single node ID or array of node IDs to move. * @param target – Target parent node ID to move into. * @param index – Desired insertion index in the target's children array; -1 (default) appends at end. * @param key – The key name for the children array property (defaults to "children"). * @returns The same `nodes` map, now mutated. * @mutates nodes – the input map's children arrays are modified directly. * * @example * ```ts * const nodes: Record = { * a: { children: ["b","c"] }, * b: { children: [] }, * c: { children: [] } * }; * * mv(nodes, "c", "b", 0); * // now nodes.b.children === ["c"] * * // Custom key example: * const customNodes: Record = { * a: { items: ["b","c"] }, * b: { items: [] }, * c: { items: [] } * }; * mv(customNodes, "c", "b", 0, "items"); * ``` * * @remark * - This function is not recursive. It only moves direct children of the target node. * - This function does not check for cycles. */ function mv = Record>(nodes: Record, sources: string | string[], target: string, index?: number, key?: K): Record; /** * Recursively remove a node and its subtree, delegating actual removal to `unlink`, * and return a list of all IDs that were removed. * * @typeParam K – The key name for the children array property. * @typeParam T – Node shape with optional children array at key K. * @param nodes – Record mapping node IDs to node objects. * @param id – ID of the root node to remove. * @param key – The key name for the children array property (defaults to "children"). * @returns Array of removed IDs, in removal order (children first, then the node itself). * @mutates nodes – the input map's children arrays are modified directly. * @throws {Error} If `id` does not exist in `nodes`. * @example * ```ts * const nodes = { * a: { children: ['b'] }, * b: { children: [] } * }; * rm(nodes, 'a'); * // now nodes is {} * * // Custom key example: * const customNodes = { * a: { items: ['b'] }, * b: { items: [] } * }; * rm(customNodes, 'a', 'items'); * ``` */ function rm> = Partial>>(nodes: Record, id: string, key?: K): string[]; /** * Remove a node from the flat tree and unlink it from any parent's children array. * * @typeParam K – The key name for the children array property. * @typeParam T – Node shape with a children array at key K. * @param nodes – Record mapping node IDs to node objects. * @param id – ID of the node to remove. * @param key – The key name for the children array property (defaults to "children"). * @returns void * @mutates nodes – the input map's children arrays are modified directly. * @throws {Error} If `id` does not exist in `nodes`. * @example * ```ts * const nodes = { * parent: { children: ['child'] }, * child: { children: [] } * }; * unlink(nodes, 'child'); * // now nodes.parent.children === [] * * // Custom key example: * const customNodes = { * parent: { items: ['child'] }, * child: { items: [] } * }; * unlink(customNodes, 'child', 'items'); * ``` */ function unlink> = Partial>>(nodes: Record, id: string, key?: K): void; } namespace lut { /** * Tree lookup table interface for efficient hierarchical queries on flat tree structures. * * This interface is designed for **in-memory, runtime-only** use and should not be used for persisting data. * It exists to provide efficient access to the parent and child relationships within a tree structure without * modifying the core node structure directly. * * ## Why We Use This Interface * This interface allows for a structured, performant way to manage tree hierarchy relationships without introducing * a `parent_id` property on each node. By using an in-memory lookup table, we avoid potential issues with nullable * `parent_id` fields, which could lead to unpredictable coding experiences. Additionally, maintaining these * relationships within a dedicated lookup table promotes separation of concerns, keeping core node definitions * stable and interface-compatible. * * ## Functionality * - **Get Parent Node by Child ID**: Efficiently map a node's ID to its parent node ID with O(1) lookup. * - **Get Child Nodes by Parent ID**: Access a list of child node IDs for any given parent node with O(1) lookup. * - **Traverse Ancestors**: Walk up the tree from any node to its root. * - **Query Depth**: Calculate how deep a node is in the hierarchy. * - **Find Siblings**: Locate nodes that share the same parent. * * ## Management Notes * - This interface should be populated and managed only during runtime. * - It is recommended to initialize the lookup table during tree loading or initial rendering using {@link TreeLUT.from_flat_with_children}. * - If the tree hierarchy is updated (e.g., nodes are added, removed, or moved), this lookup table should be * refreshed to reflect the current relationships. * - For optimal performance, the lookup table should be created once and reused for multiple queries. * * @see {@link TreeLUT} for methods to query and traverse the tree efficiently. * @see {@link TreeLUT.from_flat_with_children} for creating a lookup table from a flat tree structure. */ interface ITreeLUT { /** * Array of all node IDs in the tree, facilitating traversal and lookup. */ readonly lu_keys: Array; /** * Maps each node ID to its respective parent node ID, facilitating upward traversal. * Root nodes have a parent value of `null`. */ readonly lu_parent: Record; /** * Maps each node ID to an array of its child node IDs, enabling efficient downward traversal. * * Note: This does NOT guarantee the order of children. For ordered traversal, refer to the * original node's `children` array in your tree structure. */ readonly lu_children: Record>; } /** * Tree lookup table for efficient hierarchical queries on flat tree structures. * Provides methods for traversing and querying tree relationships. * * @example * ```ts * const lut: ITreeLUT = { * lu_keys: ["root", "a", "b", "c"], * lu_parent: { root: null, a: "root", b: "root", c: "a" }, * lu_children: { root: ["a", "b"], a: ["c"], b: [], c: [] } * }; * const tree = new TreeLUT(lut); * console.log(tree.depthOf("c")); // 2 * console.log(tree.parentOf("c")); // "a" * ``` */ class TreeLUT { readonly lut: ITreeLUT; constructor(lut: ITreeLUT); /** * Creates a TreeLUT from a flat tree structure. * Builds the lookup table by analyzing parent-child relationships from the nodes' children arrays. * * @typeParam K - The key name for the children array property * @typeParam T - Node shape with a children array at key K * @param nodes - Record mapping node IDs to node objects * @param key - The key name for the children array property (defaults to "children") * @returns A new TreeLUT instance with computed lookup tables * * @example * ```ts * const nodes = { * root: { children: ["a", "b"] }, * a: { children: ["c"] }, * b: { children: [] }, * c: { children: [] } * }; * const tree = TreeLUT.from(nodes); * console.log(tree.depthOf("c")); // 2 * * // Custom key example: * const customNodes = { * root: { items: ["a", "b"] }, * a: { items: ["c"] }, * b: { items: [] }, * c: { items: [] } * }; * const customTree = TreeLUT.from(customNodes, "items"); * console.log(customTree.depthOf("c")); // 2 * ``` */ static from_flat_with_children> = Partial>>(nodes: Record, key?: K): TreeLUT; /** * Creates a snapshot of the current lookup table state. * * @returns A deep copy of the lookup table * @example * ```ts * const tree = new TreeLUT(lut); * const snapshot = tree.snapshot(); * // snapshot is independent of tree.lut * ``` */ snapshot(): ITreeLUT; /** * Gets the depth (level) of a node in the tree. * The root node has depth 0, its children have depth 1, etc. * * @param id - The node ID to query * @returns The depth of the node (number of ancestors) * @example * ```ts * // Tree: root -> a -> c * tree.depthOf("root"); // 0 * tree.depthOf("a"); // 1 * tree.depthOf("c"); // 2 * ``` */ depthOf(id: string): number; /** * Gets all ancestor node IDs for a given node, in root-first order. * Uses a generator for memory-efficient traversal. * * @param id - The node ID to query * @returns Generator yielding ancestor IDs from root to immediate parent * @example * ```ts * // Tree: root -> a -> b -> c * [...tree.ancestorsOf("c")]; // ["root", "a", "b"] * [...tree.ancestorsOf("a")]; // ["root"] * [...tree.ancestorsOf("root")]; // [] * ``` */ ancestorsOf(id: string): Generator; /** * Gets the immediate parent node ID of a given node. * * @param id - The node ID to query * @returns The parent node ID, or null if the node is a root node * @example * ```ts * tree.parentOf("child"); // "parent" * tree.parentOf("root"); // null * ``` */ parentOf(id: string): string | null; /** * Gets the topmost ancestor (root) of a given node. * If the node itself is a root, returns the node ID. * * @param id - The node ID to query * @returns The root ancestor node ID, or null if node doesn't exist * @example * ```ts * // Tree: root -> a -> b -> c * tree.topmostOf("c"); // "root" * tree.topmostOf("root"); // "root" * tree.topmostOf("invalid"); // null * ``` */ topmostOf(id: string): string | null; /** * Gets all sibling node IDs of a given node. * Siblings are nodes that share the same parent, excluding the node itself. * * @param id - The node ID to query * @returns Array of sibling node IDs * @example * ```ts * // Tree: parent -> [a, b, c] * tree.siblingsOf("b"); // ["a", "c"] * tree.siblingsOf("a"); // ["b", "c"] * ``` */ siblingsOf(id: string): string[]; /** * Gets all child node IDs of a given node. * * @param id - The node ID to query * @returns Array of child node IDs (empty array if no children) * @example * ```ts * // Tree: parent -> [a, b] * tree.childrenOf("parent"); // ["a", "b"] * tree.childrenOf("a"); // [] * ``` */ childrenOf(id: string): string[]; /** * Checks if a node is an ancestor of another node. * A node is an ancestor if it appears in the parent chain. * * @param ancestor - The potential ancestor node ID * @param id - The node ID to check * @returns True if ancestor is in the parent chain of id * @example * ```ts * // Tree: root -> a -> b -> c * tree.isAncestorOf("root", "c"); // true * tree.isAncestorOf("a", "c"); // true * tree.isAncestorOf("c", "a"); // false * tree.isAncestorOf("b", "b"); // false (node is not its own ancestor) * ``` */ isAncestorOf(ancestor: string, id: string): boolean; } } /** * Graph distance calculations for tree hierarchies. * * The `distance` namespace provides pure, mathematical functions for calculating * graph distance (shortest path) between nodes in a tree. These functions are * designed to be reusable, testable, and independent of any specific tree * implementation. * * ## Core Concepts * * **Graph Distance**: The shortest path between two nodes in a tree, measured as * the number of edges that must be traversed. For example: * - Parent to child: distance 1 * - Siblings: distance 2 (through their common parent) * - Same node: distance 0 * * **Lowest Common Ancestor (LCA)**: The deepest node that is an ancestor of both * nodes. Used to calculate graph distance efficiently. * * ## Design Principles * * - **Pure & Mathematical**: All functions are pure, deterministic, and side-effect free * - **Minimal Interface**: Works with `tree.lut.ITreeLUT` interface (no custom types needed) * - **Performance**: Efficient algorithms with minimal allocations * - **Composable**: Functions can be combined for complex selection logic * * @example * ```ts * import { tree } from "@grida/tree"; * * const lut: tree.lut.ITreeLUT = { * lu_keys: ["root", "a", "b", "a1", "a2"], * lu_parent: { root: null, a: "root", b: "root", a1: "a", a2: "a" }, * lu_children: { root: ["a", "b"], a: ["a1", "a2"], b: [], a1: [], a2: [] } * }; * * // Calculate distance between siblings * const dist = tree.distance.getGraphDistance(lut, "a1", "a2"); * console.log(dist); // 2 * * // Find nearest node by graph distance * const nearest = tree.distance.findNearestByGraphDistance( * lut, * ["a1", "a2", "b"], * ["a"] * ); * console.log(nearest); // "a1" or "a2" (children, distance 1) * ``` */ namespace distance { /** * Options for finding the nearest node by graph distance. */ interface IFindNearestOptions { /** * If true, prefer children of selected nodes when distances are equal. * Useful for "nested first" selection behavior (e.g., double-click to go deeper). */ preferChildren?: boolean; /** * Weights to use for graph distance calculation. * Providing this enables weighted distance mode. * If not provided, standard unweighted graph distance is used. */ weights?: GraphWeights; /** * Optional filter function to exclude candidates. * Return false to exclude a candidate from consideration. */ filter?: (candidate: Key, lut: lut.ITreeLUT) => boolean; } /** * Find the Lowest Common Ancestor (LCA) of two nodes in a tree. * * @param lut - The tree lookup table * @param nodeA - First node ID * @param nodeB - Second node ID * @returns The LCA node ID, or null if nodes are not in the same tree * * @example * ```ts * // Tree: root -> a -> a1, root -> b * const lca = tree.distance.getLowestCommonAncestor(lut, "a1", "b"); * console.log(lca); // "root" * ``` */ function getLowestCommonAncestor(lut: lut.ITreeLUT, nodeA: Key, nodeB: Key): Key | null; /** * Calculate the graph distance (shortest path) between two nodes in a tree. * * Algorithm: distance = depth(A) + depth(B) - 2 * depth(LCA) * * @param lut - The tree lookup table * @param nodeA - First node ID * @param nodeB - Second node ID * @returns The graph distance between nodes, or Infinity if nodes are not in the same tree * * @example * ```ts * // Tree: root -> a -> a1 * const dist1 = tree.distance.getGraphDistance(lut, "root", "a"); // 1 * const dist2 = tree.distance.getGraphDistance(lut, "a", "a1"); // 1 * const dist3 = tree.distance.getGraphDistance(lut, "root", "a1"); // 2 * ``` */ function getGraphDistance(lut: lut.ITreeLUT, nodeA: Key, nodeB: Key): number; interface GraphWeights { /** * Weight for the edge between two siblings. * If provided, siblings are considered to have a direct connection with this weight. * If undefined, siblings are connected via their parent (weight = parentChild * 2). * * @recommendation Use a fractional value (e.g., 0.9) to make siblings implicitly "closer" * than parent/child nodes (distance 1.0) without needing tie-breakers. */ sibling?: number; /** * Weight for the edge between a parent and its child. * Defaults to 1. */ parentChild?: number; } /** * Calculate weighted graph distance between two nodes. * * Allows customizing the weights of graph edges (parent-child, siblings) * to adjust distance calculations for specific use cases (e.g., measurement). * * Algorithm: * - If nodes are siblings and sibling weight is provided: return sibling weight * - Otherwise: use standard graph distance * parentChild weight * * @param lut - The tree lookup table * @param nodeA - First node ID * @param nodeB - Second node ID * @param weights - Configuration for edge weights * @returns The weighted distance * * @example * ```ts * // Tree: root -> a -> [a1, a2] * // With sibling weight 1 (measurement mode): * const w = { sibling: 1 }; * const d1 = tree.distance.getWeightedGraphDistance(lut, "a1", "a2", w); // 1 * const d2 = tree.distance.getWeightedGraphDistance(lut, "a", "a1", w); // 1 * ``` */ function getWeightedGraphDistance(lut: lut.ITreeLUT, nodeA: Key, nodeB: Key, weights?: GraphWeights): number; /** * Find the nearest node by graph distance from the current selection. * * Given candidate nodes and current selection, finds the candidate with minimum * graph distance. For multiple selections, uses the minimum distance from any * selected node. * * @param lut - The tree lookup table * @param candidates - Array of candidate node IDs to choose from * @param selection - Array of currently selected node IDs * @param options - Optional configuration for selection behavior * @returns The nearest candidate node ID, or null if no valid candidate found * * @example * ```ts * // Tree: root -> a -> [a1, a2], root -> b * // Selection: ["a"] * // Candidates: ["a1", "a2", "b"] * const nearest = tree.distance.findNearestByGraphDistance( * lut, * ["a1", "a2", "b"], * ["a"], * { preferChildren: true } * ); * // Returns "a1" or "a2" (children, distance 1) instead of "b" (sibling, distance 2) * ``` */ function findNearestByGraphDistance(lut: lut.ITreeLUT, candidates: Key[], selection: Key[], options?: IFindNearestOptions): Key | null; } /** * Graph-based tree structure with explicit node and link separation. * * The `graph` namespace provides a data structure and system for managing tree hierarchies * where **nodes** (the actual data) and **links** (the relationships between nodes) are * stored and managed separately. This design provides a clean, efficient way to work with * large tree structures without the complexity of nested objects or the fragility of * parent-pointer approaches. * * ## Core Design Principles * * ### 1. Explicit Separation of Data and Structure * Unlike traditional tree implementations, `graph` keeps data and relationships separate: * - **Nodes**: A flat record of your actual data `Record` * - **Links**: A separate record of parent-child relationships `Record` * * This separation provides several benefits: * - No need to re-wrap or re-map data when structure changes * - Data remains immutable while structure can be modified * - Easy to maintain different views of the same data * - Clear separation of concerns * * ### 2. Single Source of Truth * The graph serves as your **main source of truth** for hierarchical data. Instead of: * - Maintaining duplicate structures (e.g., flat + nested) * - Re-computing relationships on every operation * - Storing derived or redundant parent/child references * * You work directly with the graph, which keeps everything synchronized and consistent. * * ### 3. Safe, Predictable Operations * All tree manipulation methods maintain graph integrity: * - Moving nodes updates all relevant links automatically * - Removing nodes cleans up orphaned references * - Operations are atomic and don't leave the graph in an invalid state * * ## When to Use This * * Use `tree.graph` when you need: * - A robust, manipulable tree structure as your primary data model * - Frequent structural changes (add, move, remove, reorder) * - To work with large trees (thousands of nodes) * - A single source of truth for hierarchical data * - Type-safe operations on your domain objects * * For read-only querying, consider {@link lut.TreeLUT} which provides efficient lookups. * For simple operations on existing structures, see {@link flat_with_children}. * * ## Example * * ```ts * import { tree } from "@grida/tree"; * * interface PageNode { * id: string; * name: string; * type: "frame" | "text" | "image"; * } * * // Create a graph with explicit nodes and links * const graph = new tree.graph.Graph({ * nodes: { * "page": { id: "page", name: "Page 1", type: "frame" }, * "header": { id: "header", name: "Header", type: "frame" }, * "title": { id: "title", name: "Title", type: "text" }, * }, * links: { * "page": ["header"], // page contains header * "header": ["title"], // header contains title * "title": undefined, // title has no children * } * }); * * // Move title from header to page * graph.mv("title", "page"); * * // Get current state * const state = graph.snapshot(); * console.log(state.links["page"]); // ["header", "title"] * console.log(state.links["header"]); // [] * ``` * * @see {@link IGraph} for the graph data structure interface * @see {@link Graph} for graph manipulation methods */ namespace graph { /** * Graph data structure interface with explicit node and link separation. * * This interface represents a tree structure where: * - `nodes` contains the actual data objects * - `links` contains the parent-child relationships * * The separation allows for efficient tree operations without modifying node data, * and enables the same node data to be used in multiple graph structures if needed. * * @typeParam T - The type of data stored in each node * * @example * ```ts * interface MyData { * name: string; * value: number; * } * * const graph: IGraph = { * nodes: { * "root": { name: "Root", value: 0 }, * "child": { name: "Child", value: 1 }, * }, * links: { * "root": ["child"], // root has one child * "child": undefined, // child has no children * } * }; * ``` * * @remarks * - Node keys in `nodes` and `links` must be synchronized * - A link value of `undefined` or empty array `[]` means no children * - Links are stored as arrays to preserve child order * - The graph does not enforce referential integrity automatically; * use the {@link Graph} class for safe operations */ interface IGraph { /** * The actual data nodes, keyed by node ID. * Contains your domain objects without any tree-specific properties. */ nodes: Record; /** * The hierarchical relationships between nodes. * Each key maps to an array of child node IDs, or undefined if no children. * The order of children in the array represents their visual/logical order. */ links: Record; } /** * Graph Policy Interface - Universal constraints for tree structure validation. * * `IGraphPolicy` provides a flexible, extensible system for defining structural rules * and constraints on tree operations. Policies are independent of node data shape (`T`) * and operate purely on structural relationships, making them composable and reusable * across different tree types. * * ## Design Philosophy * * **Separation of Concerns:** * - Policies define WHAT is allowed (constraints) * - Graph methods define HOW to execute (operations) * - Node data defines content (domain objects) * * **Flexibility:** * - All methods are optional (defaults to permissive behavior) * - Policies can be node-specific (different rules for different node types) * - Composable and extensible without modifying core graph code * * **Performance:** * - Policies are queried only when needed (before mutations) * - Simple checks (O(1)) per operation * - No overhead when using default policy * * ## When to Use Policies * * Use policies to enforce: * - **Type constraints**: "Text nodes cannot have children" * - **Capacity limits**: "Containers can have max 100 children" * - **Relationship rules**: "Images cannot contain frames" * - **Hierarchy rules**: "Root nodes cannot be children" * - **Business logic**: "Locked nodes cannot be moved" * * ## Integration with Graph Operations * * When integrated, policies will be checked before mutations: * - `mv()` - Check `can_be_child`, `can_be_parent`, `can_link`, `max_out_degree` * - `order()` - No policy checks (internal reordering) * - `rm()` / `unlink()` - No policy checks (removal always allowed) * * Failed policy checks will throw descriptive errors instead of performing invalid operations. * * @typeParam T - The type of node data (your domain objects) * * @example * ```ts * // Example 1: Design tool with scene/frame/text/image hierarchy * interface DesignNode { * type: "scene" | "frame" | "text" | "image" | "group"; * } * * const designPolicy: IGraphPolicy = { * // Leaf nodes (text, image) cannot have children * max_out_degree: (node) => { * if (node.type === "text" || node.type === "image") return 0; * return Infinity; // Containers (scene, frame, group) have unlimited children * }, * * // Only container types can be parents * can_be_parent: (node) => { * return ["scene", "frame", "group"].includes(node.type); * }, * * // Scenes are top-level only (cannot be nested) * can_be_child: (node) => node.type !== "scene", * * // Prevent self-parenting and group nesting * can_link: (parent, parent_id, child, child_id) => { * if (parent_id === child_id) return false; // No self-parenting * if (parent.type === "group" && child.type === "group") return false; * return true; * } * }; * * // Use with graph: * const graph = new Graph(graphData, designPolicy); * graph.mv("scene", "frame"); // ❌ Throws: Scenes cannot be children * graph.mv("frame", "text"); // ❌ Throws: Text cannot be a parent * ``` * * @example * ```ts * // Example 2: Capacity limits * interface ListNode { * type: "list" | "item"; * maxItems?: number; * } * * const listPolicy: IGraphPolicy = { * max_out_degree: (node) => { * if (node.type === "list") { * return node.maxItems ?? 10; // Default max 10 items * } * return 0; // Items cannot have children * } * }; * ``` * * @example * ```ts * // Example 3: Root node constraints * interface DocumentNode { * id: string; * isRoot?: boolean; * } * * const rootPolicy: IGraphPolicy = { * // Root nodes cannot become children * can_be_child: (node) => !node.isRoot, * * // All nodes can be parents (even roots) * can_be_parent: () => true * }; * ``` * * @remarks * **All methods are optional** - If not provided, defaults to permissive behavior: * - `max_out_degree` → `Infinity` (unlimited children) * - `can_link` → `true` (all links allowed) * - `can_be_parent` → `true` (any node can be parent) * - `can_be_child` → `true` (any node can be child) * * **Performance Considerations:** * - Keep policy checks simple and fast (O(1) preferred) * - Avoid expensive operations (database queries, deep traversals) * - Cache computed values when possible * * **Error Handling:** * - Policy violations will throw descriptive errors * - Graph remains unchanged when policy check fails * - No partial updates (atomic operations) * * @see {@link DEFAULT_POLICY_INFINITE} for the default permissive policy * @see {@link Graph} for graph operations (integration coming soon) */ interface IGraphPolicy { /** * Maximum number of children a node can have. * * This constraint is checked before adding children to a node. Use this to enforce: * - **Leaf nodes**: Return `0` for nodes that cannot have children * - **Capacity limits**: Return a number for maximum children allowed * - **Containers**: Return `Infinity` for unlimited children (default) * * @param node - The node data that would become the parent * @param id - The node's ID in the graph * @returns Maximum children allowed, or `Infinity` for unlimited * * @example * ```ts * // Basic: Leaf vs container nodes * max_out_degree: (node) => { * if (node.type === "leaf") return 0; * if (node.type === "container") return Infinity; * return 10; // Default max * } * ``` * * @example * ```ts * // Dynamic limits based on node properties * max_out_degree: (node) => { * if (node.isLeaf) return 0; * return node.maxChildren ?? Infinity; * } * ``` * * @example * ```ts * // Type-specific constraints * max_out_degree: (node) => { * switch (node.type) { * case "text": * case "image": * return 0; // Cannot have children * case "list": * return 100; // Max 100 items * case "frame": * return Infinity; // Unlimited * default: * return Infinity; * } * } * ``` * * @remarks * - Checked before `mv()` when adding a child * - **Not checked** when node already has max children (use `can_link` for that) * - Return `0` to create leaf nodes (no children allowed) * - Return `Infinity` for containers (unlimited children) * - Default (when not provided): `Infinity` */ max_out_degree?(node: T, id: Key): number | typeof Infinity; /** * Whether a specific parent-child link is allowed. * * This is the most granular constraint, checked before creating any parent-child * relationship. Use this for: * - **Type compatibility**: "Images cannot contain frames" * - **Relationship rules**: "Locked parents cannot accept children" * - **Capacity enforcement**: Check current child count vs limit * - **Business logic**: Custom domain-specific rules * * @param parent - The parent node data * @param parent_id - The parent node's ID * @param child - The child node data * @param child_id - The child node's ID * @returns `true` if the link is allowed, `false` otherwise * * @example * ```ts * // Basic type compatibility * can_link: (parent, parent_id, child, child_id) => { * // Text nodes cannot contain anything * if (parent.type === "text") return false; * // Images can only contain text * if (parent.type === "image") return child.type === "text"; * return true; * } * ``` * * @example * ```ts * // Enforce capacity limits with current child count * can_link: (parent, parent_id, child, child_id) => { * // Assuming you have access to the graph's links * const currentChildren = graph.links[parent_id]?.length ?? 0; * const maxChildren = parent.maxChildren ?? Infinity; * return currentChildren < maxChildren; * } * ``` * * @example * ```ts * // Prevent cycles (if you have ancestry info) * can_link: (parent, parent_id, child, child_id) => { * // Prevent self-parenting * if (parent_id === child_id) return false; * // Prevent adding ancestor as child (requires lut or cache) * // if (isAncestor(child_id, parent_id)) return false; * return true; * } * ``` * * @example * ```ts * // Type-based relationship constraints * can_link: (parent, parent_id, child, child_id) => { * // Scenes can only contain frames and groups (not text/image directly) * if (parent.type === "scene") { * return child.type === "frame" || child.type === "group"; * } * // Groups cannot contain other groups (flat organization) * if (parent.type === "group" && child.type === "group") { * return false; * } * // All other combinations allowed * return true; * } * ``` * * @remarks * - **Most specific check** - called after `can_be_parent`, `can_be_child`, `max_out_degree` * - Checked before every `mv()` operation * - Receives both parent and child data for maximum flexibility * - Use for complex business logic and relationship rules * - Default (when not provided): `true` (all links allowed) * * @see {@link can_be_parent} for parent-only constraints * @see {@link can_be_child} for child-only constraints * @see {@link max_out_degree} for simple capacity limits */ can_link?(parent: T, parent_id: Key, child: T, child_id: Key): boolean; /** * Whether a node can be a parent (have children). * * This is a node-level constraint checked before allowing a node to accept children. * Use this for: * - **Type constraints**: "Text nodes cannot be parents" * - **State constraints**: "Disabled nodes cannot have children" * - **Role constraints**: "Leaf nodes cannot become containers" * * @param node - The node data that would become a parent * @param id - The node's ID in the graph * @returns `true` if the node can be a parent, `false` otherwise * * @example * ```ts * // Type-based parent constraints * can_be_parent: (node) => { * // Only container types can be parents * return ["frame", "group", "container"].includes(node.type); * } * ``` * * @example * ```ts * // Container vs content type constraints * can_be_parent: (node) => { * // Containers can have children * if (node.type === "container" || node.type === "folder") return true; * // Content types cannot have children * if (node.type === "content" || node.type === "asset") return false; * return true; * } * ``` * * @example * ```ts * // Role-based constraints * can_be_parent: (node) => { * // Only certain roles can have children * return node.role === "container" || node.role === "folder"; * } * ``` * * @remarks * - Checked before `mv()` when node would receive a child * - More efficient than `can_link` for simple "can/cannot be parent" checks * - Use when constraint depends only on the parent node * - Use `can_link` if you need to check parent-child compatibility * - Default (when not provided): `true` (any node can be parent) * * @see {@link can_be_child} for child constraints * @see {@link can_link} for relationship-specific constraints * @see {@link max_out_degree} for capacity constraints */ can_be_parent?(node: T, id: Key): boolean; /** * Whether a node can be a child (have a parent). * * This is a node-level constraint checked before allowing a node to be moved. * Use this for: * - **Type constraints**: "Root nodes cannot be children" * - **State constraints**: "Locked nodes cannot be moved" * - **Role constraints**: "Top-level containers must stay at root" * * @param node - The node data that would become a child * @param id - The node's ID in the graph * @returns `true` if the node can be a child, `false` otherwise * * @example * ```ts * // Root node constraints * can_be_child: (node) => { * // Root nodes cannot be children * return !node.isRoot; * } * ``` * * @example * ```ts * // Type-based movement constraints * can_be_child: (node) => { * // Root-level types cannot be nested (like pages, artboards, scenes) * if (node.type === "page" || node.type === "artboard") return false; * // Master components stay at top level * if (node.type === "master") return false; * return true; * } * ``` * * @example * ```ts * // Category-based constraints * can_be_child: (node) => { * // System nodes cannot be moved * if (node.category === "system") return false; * // Only user-created content can be repositioned * return node.category === "user" || node.category === "template"; * } * ``` * * @example * ```ts * // Type-based constraints * can_be_child: (node) => { * // Pages and artboards must stay at root level * return node.type !== "page" && node.type !== "artboard"; * } * ``` * * @remarks * - Checked before `mv()` when moving a node * - More efficient than `can_link` for simple "can/cannot be moved" checks * - Use when constraint depends only on the child node * - Use `can_link` if you need to check parent-child compatibility * - Default (when not provided): `true` (any node can be child) * * @see {@link can_be_parent} for parent constraints * @see {@link can_link} for relationship-specific constraints */ can_be_child?(node: T, id: Key): boolean; } /** * Default permissive policy that allows all operations. * * This policy imposes **no constraints** on tree structure, allowing: * - Any node to be a parent (unlimited children) * - Any node to be a child (freely movable) * - Any parent-child relationship * - Unlimited tree depth and breadth * * ## When to Use * * Use the default policy when: * - You don't need structural constraints * - Building a general-purpose tree without type-specific rules * - Prototyping before adding constraints * - Maximum flexibility is required * * ## Performance * * The default policy has **zero overhead**: * - All checks return immediately (O(1)) * - No function calls in hot paths (can be inlined) * - Equivalent to having no policy at all * * @example * ```ts * // Explicit use of default policy * const graph = new Graph(graphData, DEFAULT_POLICY_INFINITE); * * // Equivalent to not providing a policy (when supported): * const graph = new Graph(graphData); * ``` * * @example * ```ts * // Extend the default policy for partial constraints * const myPolicy: IGraphPolicy = { * ...DEFAULT_POLICY_INFINITE, * // Only override what you need * max_out_degree: (node) => node.type === "leaf" ? 0 : Infinity, * }; * ``` * * @remarks * - **Type-safe**: Works with any node type via `any` * - **Composable**: Can be spread and overridden * - **Zero cost**: No runtime overhead when used * - **Default behavior**: Matches graph behavior before policies * * @see {@link IGraphPolicy} for creating custom policies */ const DEFAULT_POLICY_INFINITE: IGraphPolicy; /** * Graph manipulation class providing safe tree operations with optional policy constraints. * * This class wraps an {@link IGraph} and provides methods to safely manipulate * the tree structure while maintaining data integrity. All structural changes * (move, remove, reorder) are handled through this class to ensure the graph * remains in a consistent state. * * @typeParam T - The type of data stored in each node * * @example * ```ts * interface DocumentNode { * id: string; * type: string; * name: string; * } * * const graph = new Graph({ * nodes: { * "doc": { id: "doc", type: "document", name: "My Doc" }, * "section1": { id: "section1", type: "section", name: "Section 1" }, * "section2": { id: "section2", type: "section", name: "Section 2" }, * }, * links: { * "doc": ["section1", "section2"], * "section1": undefined, * "section2": undefined, * } * }); * * // Reorder sections * graph.order("section2", "front"); * * // Move section1 inside section2 * graph.mv("section1", "section2", 0); * * // Remove section2 and its subtree (including section1) * const removed = graph.rm("section2"); * console.log(removed); // ["section1", "section2"] * ``` * * @example * ```ts * // Using with policy constraints * interface DesignNode { * type: "scene" | "frame" | "text" | "image"; * } * * const policy: IGraphPolicy = { * max_out_degree: (node) => { * if (node.type === "text" || node.type === "image") return 0; * return Infinity; * }, * can_be_child: (node) => node.type !== "scene", * }; * * const graph = new Graph(graphData, policy); * graph.mv("frame", "text"); // ❌ Throws: text cannot have children * graph.mv("scene", "frame"); // ❌ Throws: scene cannot be a child * ``` * * @remarks * **Mutability Pattern:** * - This class **modifies the constructor data directly** (in-place mutation) * - All methods mutate the graph reference passed to the constructor * - This design works seamlessly with immutability libraries like Immer: * you can pass a draft to the constructor and mutations will apply to that draft * - Use {@link snapshot} to get a copy of the current state when needed * * **Policy Constraints:** * - Optional {@link IGraphPolicy} can be provided to enforce structural rules * - Policies are checked before mutations (atomic operations) * - Failed policy checks throw descriptive errors * - Use {@link DEFAULT_POLICY_INFINITE} for no constraints (default) * * **Validation:** * - The graph validates node existence; invalid operations will throw * - Error messages follow file-system conventions for familiarity * - Policy violations provide specific error messages * * @example * ```ts * // Using with Immer (or similar immutability patterns) * import { produce } from "immer"; * * const state = { * graph: { * nodes: { root: {...}, child: {...} }, * links: { root: ["child"], child: undefined } * } * }; * * const nextState = produce(state, draft => { * // Pass draft.graph to constructor - mutations apply to draft * const graph = new Graph(draft.graph); * graph.rm("child"); * // draft.graph is now modified, producing new immutable state * }); * ``` */ class Graph { private readonly graph; private readonly policy; private _generation; private _cachedLUT; private _cachedLUTGeneration; constructor(graph: IGraph, policy?: IGraphPolicy); /** * Generation counter that increments on every effective mutation. * * This counter is used to track changes to the graph structure. It increments * whenever an operation modifies the graph (nodes or links), enabling efficient * cache invalidation for derived data structures like LUT. * * @returns Current generation number (starts at 0) * * @example * ```ts * const graph = new Graph(data); * const gen1 = graph.generation; // 0 * * graph.mv("a", "b"); * const gen2 = graph.generation; // 1 (or higher) * * graph.rm("c"); * const gen3 = graph.generation; // 2+ (or higher) * ``` * * @remarks * - Starts at 0 on construction * - Increments on: mv(), rm(), unlink(), order(), import() * - Does not increment on failed operations or no-ops * - May increment multiple times per operation (e.g., recursive rm()) * - Used internally for LUT caching */ get generation(): number; /** * Get or compute the lookup table (LUT) for efficient hierarchy queries. * * This getter provides a {@link lut.ITreeLUT} structure for O(1) parent/child * lookups. The LUT is computed once and cached until the graph structure changes * (tracked via generation counter). * * @returns Lookup table with lu_keys, lu_parent, and lu_children * * @example * ```ts * const graph = new Graph(data); * const lut = graph.lut; * * // Use with TreeLUT for advanced queries * const tree = new TreeLUT(lut); * console.log(tree.depthOf("node-id")); * console.log(tree.ancestorsOf("node-id")); * ``` * * @remarks * **Caching:** * - First access computes LUT from current graph state * - Subsequent accesses return cached LUT (O(1)) * - Cache invalidates when generation changes (any mutation) * - Recomputes automatically on next access after mutation * * **Performance:** * - Computation: O(n + e) where n = nodes, e = edges * - Cached access: O(1) * - Memory: O(n + e) for cached LUT * * **Use Cases:** * - Frequent parent lookups (O(1) vs O(n)) * - Depth calculations * - Ancestor/descendant checks * - Sibling queries * * @see {@link lut.TreeLUT} for query methods * @see {@link generation} for cache invalidation tracking */ get lut(): lut.ITreeLUT; /** * Ensures a node has a children array in links, initializing if needed. * * @param id - The node ID to ensure children for * @returns The children array for the node (guaranteed to be defined) * @internal */ private enschildren; /** * Creates a snapshot of the current graph state. * * This method returns a copy of the graph structure, useful for: * - Implementing undo/redo functionality * - Creating checkpoints before operations * - Comparing graph states * - Exporting graph data * * @returns A new IGraph object with copied nodes and links * * @example * ```ts * const graph = new Graph({ nodes: {...}, links: {...} }); * const before = graph.snapshot(); * * graph.mv("child", "newParent"); * * const after = graph.snapshot(); * // before and after are independent objects * ``` * * @remarks * - The `nodes` record is shallow-copied (node objects are not cloned) * - The `links` record and all child arrays are deep-copied for independence * - If you need deep copies of node data, clone the nodes separately */ snapshot(): IGraph; /** * Recursively remove a node and its entire subtree from the graph. * * This method removes the specified node along with all of its descendants, * cleaning up all associated links. The removal is recursive, so if the node * has children, those children and their children are also removed. * * @param key - The node ID to remove * @returns Array of removed node IDs in removal order (children first, then parent) * @throws {Error} If the node does not exist * @mutates Modifies the graph in-place by removing nodes and updating links * * @example * ```ts * // Tree: root -> [a -> [b, c], d] * const removed = graph.rm("a"); * console.log(removed); // ["b", "c", "a"] * // Now the tree is: root -> [d] * ``` * * @remarks * - Children are removed before parents (depth-first) * - All links referencing the removed nodes are cleaned up * - For removing a single node without its children, use {@link unlink} * - The return value can be used to implement undo functionality * * @see {@link unlink} for removing a node without its children */ rm(key: Key): RmResult; /** * Unlink (delete) a single node from the graph without removing its children. * * This method removes only the specified node from the graph. If the node has children, * those children become orphaned (detached from the tree). This is useful when you want * to remove a node but preserve its subtree for re-attachment elsewhere. * * @param key - The node ID to unlink * @throws {Error} If the node does not exist * @mutates Modifies the graph in-place by removing the node and updating parent links * * @example * ```ts * // Tree: root -> a -> b * graph.unlink("a"); * // Now: root (b exists but is orphaned) * * // b can be re-attached * graph.mv("b", "root"); * // Now: root -> b * ``` * * @remarks * - Only the specified node is removed; children are not affected * - Children of the removed node become orphaned and may need re-attachment * - For removing a node and its entire subtree, use {@link rm} * - The node's entry is removed from both `nodes` and `links` * * @see {@link rm} for recursive removal including children * @see {@link mv} for re-attaching orphaned nodes */ unlink(key: Key): void; /** * Move one or more nodes to a new parent at a specific position. * * This method relocates nodes within the tree, updating all relevant links automatically. * Nodes are detached from their current parent (if any) and attached to the target parent * at the specified index. * * @param sources - Single node ID or array of node IDs to move * @param target - Target parent node ID to move into * @param index - Insertion index in target's children array. -1 (default) appends at end. * @throws {Error} If any source node or target node does not exist * @mutates Modifies the graph in-place by updating links * * @example * ```ts * // Tree: root -> [a, b], b -> [c] * graph.mv("c", "root", 0); * // Now: root -> [c, a, b], b -> [] * * // Move multiple nodes * graph.mv(["a", "b"], "c"); * // Now: root -> [], c -> [a, b] * * // Insert at specific position * graph.mv("a", "root", 1); * // Now: root -> [c, a], b -> [] * ``` * * @remarks * - Nodes are automatically detached from their current parent * - If moving multiple nodes, they maintain their relative order * - Index is clamped to valid range (0 to children.length) * - Moving a node to its current parent changes its position among siblings * - Cycle detection is NOT implemented; avoid moving ancestors into descendants * * **Behavioral Note:** * Unlike {@link flat_with_children.mv} which throws an error when the target lacks * a children array, this method **auto-initializes** missing or undefined link entries. * This is intentional: `graph.IGraph` explicitly allows `undefined` in its type signature * (`links: Record`), making this behavior consistent * with the graph's design philosophy of handling sparse structures gracefully. * * @see {@link order} for reordering within the same parent */ mv(sources: Key | Key[], target: Key, index?: number): void; /** * Import an external sub-graph into the current graph, attaching specified roots to a parent. * * This method merges an external graph structure into the current one, handling both * data (nodes) and structure (links) atomically. It's designed for scenarios like: * - Inserting sub-documents (copy/paste, templates, components) * - Merging separate graph structures * - Batch insertion of hierarchical data * * The operation is **atomic**: either all validations pass and the entire subgraph * is imported, or an error is thrown and the graph remains unchanged. * * @param subgraph - External graph to import (contains nodes and links) * @param roots - Node IDs from subgraph to attach as children of parent * @param parent - Parent node ID in the current graph to attach roots to * @param index - Insertion index in parent's children array (default: -1 = append) * @throws {Error} If any node ID from subgraph conflicts with existing nodes * @throws {Error} If parent node doesn't exist in current graph * @throws {Error} If any root ID doesn't exist in subgraph * @throws {Error} If policy constraints are violated * @mutates Modifies the graph in-place by adding nodes/links and attaching roots * * @example * ```ts * // Import a component sub-graph * const mainGraph = new tree.graph.Graph({ * nodes: { page: { name: "Page" } }, * links: { page: [] } * }); * * const component = { * nodes: { * header: { name: "Header" }, * logo: { name: "Logo" }, * title: { name: "Title" } * }, * links: { * header: ["logo", "title"], * logo: undefined, * title: undefined * } * }; * * // Import component into page at index 0 * mainGraph.import(component, ["header"], "page", 0); * // Now: page -> [header], header -> [logo, title] * ``` * * @example * ```ts * // Import multiple root nodes (like paste operation) * const clipboard = { * nodes: { * box1: { type: "frame" }, * box2: { type: "frame" }, * text1: { type: "text" } * }, * links: { * box1: undefined, * box2: undefined, * text1: undefined * } * }; * * graph.import(clipboard, ["box1", "box2", "text1"], "container"); * // Now: container -> [...existing, box1, box2, text1] * ``` * * @remarks * **ID Conflict Handling:** * - All node IDs in subgraph must be unique vs. existing nodes * - Conflicts are detected before any mutations occur * - Consider using a key remapping strategy if conflicts are expected * * **Orphan Nodes:** * - All nodes in subgraph are added, even if not reachable from roots * - Orphaned nodes remain unlinked (not attached to any parent) * - Useful for preserving entire document structure * * **Link Preservation:** * - All internal links within subgraph are preserved exactly * - Only roots get new parent links (to the specified parent) * - Order is preserved via the index parameter * * **Policy Integration:** * - Root attachment goes through mv(), so all policy checks apply * - If any root violates policy, entire import fails (atomic) * * @see {@link mv} for moving existing nodes (used internally for root attachment) */ import(subgraph: IGraph, roots: Key[], parent: Key, index?: number): void; /** * Reorder a node within its current parent's children array. * * This method changes the position of a node among its siblings without changing * its parent. Useful for z-index type operations or list reordering. * * @param key - The node ID to reorder * @param order - Ordering directive: * - `"front"` - Move to the end (highest z-index) * - `"back"` - Move to the start (lowest z-index) * - `"forward"` - Move one position toward the end * - `"backward"` - Move one position toward the start * - `number` - Move to specific index * @throws {Error} If the node does not exist * @mutates Modifies the graph in-place by reordering links * * @example * ```ts * // Tree: parent -> [a, b, c, d] * graph.order("b", "front"); * // Now: parent -> [a, c, d, b] * * graph.order("d", "back"); * // Now: parent -> [d, a, c, b] * * graph.order("c", "forward"); * // Now: parent -> [d, a, b, c] * * graph.order("b", 0); * // Now: parent -> [b, d, a, c] * ``` * * @remarks * - Only affects the node's position among siblings * - Does not change the node's parent * - Numeric indices are clamped to valid range * - "forward" and "backward" are relative to current position * - Has no effect if the node has no parent (orphan node) * * @see {@link mv} for moving nodes to a different parent */ order(key: Key, order: "back" | "front" | "backward" | "forward" | number): void; } /** * Remap all keys in a graph, producing a new graph with fresh keys. * * This is essential when importing external documents (SVG, clipboard, * drag-drop) where the source graph may contain keys that conflict * with the current document. * * The function: * 1. Builds a mapping from old keys to new keys via the `keygen` callback * 2. Rewrites `nodes`, `links`, and `roots` using the mapping * 3. Returns the remapped graph and the key mapping (old → new) * * @param subgraph - The graph to remap (nodes + links) * @param roots - The root keys of the subgraph * @param keygen - Function that generates a fresh key for each old key * @returns `{ graph, roots, keyMap }` with all keys replaced * * @example * ```ts * const { graph, roots, keyMap } = tree.graph.remapKeys( * imported, * imported.scene.children_refs, * (oldKey) => idgen.next() * ); * ``` */ function remapKeys(subgraph: IGraph, roots: tree.Key[], keygen: (oldKey: tree.Key) => tree.Key): { graph: IGraph; roots: tree.Key[]; keyMap: Record; }; } } //#endregion //#region ../../node_modules/.pnpm/csstype@3.1.0/node_modules/csstype/index.d.ts interface StandardLonghandProperties { /** * The **`accent-color`** CSS property sets the accent color for user-interface controls generated by some elements. * * **Syntax**: `auto | ` * * **Initial value**: `auto` * * | Chrome | Firefox | Safari | Edge | IE | * | :----: | :-----: | :------: | :----: | :-: | * | **93** | **92** | **15.4** | **93** | No | * * @see https://developer.mozilla.org/docs/Web/CSS/accent-color */ accentColor?: Property.AccentColor | undefined; /** * The CSS **`align-content`** property sets the distribution of space between and around content items along a flexbox's cross-axis or a grid's block axis. * * **Syntax**: `normal | | | ? ` * * **Initial value**: `normal` * * --- * * _Supported in Flex Layout_ * * | Chrome | Firefox | Safari | Edge | IE | * | :------: | :-----: | :-----: | :----: | :----: | * | **29** | **28** | **9** | **12** | **11** | * | 21 _-x-_ | | 7 _-x-_ | | | * * --- * * _Supported in Grid Layout_ * * | Chrome | Firefox | Safari | Edge | IE | * | :----: | :-----: | :------: | :----: | :-: | * | **57** | **52** | **10.1** | **16** | No | * * --- * * @see https://developer.mozilla.org/docs/Web/CSS/align-content */ alignContent?: Property.AlignContent | undefined; /** * The CSS **`align-items`** property sets the `align-self` value on all direct children as a group. In Flexbox, it controls the alignment of items on the Cross Axis. In Grid Layout, it controls the alignment of items on the Block Axis within their grid area. * * **Syntax**: `normal | stretch | | [ ? ]` * * **Initial value**: `normal` * * --- * * _Supported in Flex Layout_ * * | Chrome | Firefox | Safari | Edge | IE | * | :------: | :-----: | :-----: | :----: | :----: | * | **52** | **20** | **9** | **12** | **11** | * | 21 _-x-_ | | 7 _-x-_ | | | * * --- * * _Supported in Grid Layout_ * * | Chrome | Firefox | Safari | Edge | IE | * | :----: | :-----: | :------: | :----: | :-: | * | **57** | **52** | **10.1** | **16** | No | * * --- * * @see https://developer.mozilla.org/docs/Web/CSS/align-items */ alignItems?: Property.AlignItems | undefined; /** * The **`align-self`** CSS property overrides a grid or flex item's `align-items` value. In Grid, it aligns the item inside the grid area. In Flexbox, it aligns the item on the cross axis. * * **Syntax**: `auto | normal | stretch | | ? ` * * **Initial value**: `auto` * * --- * * _Supported in Flex Layout_ * * | Chrome | Firefox | Safari | Edge | IE | * | :------: | :-----: | :-----: | :----: | :----: | * | **36** | **20** | **9** | **12** | **11** | * | 21 _-x-_ | | 7 _-x-_ | | | * * --- * * _Supported in Grid Layout_ * * | Chrome | Firefox | Safari | Edge | IE | * | :----: | :-----: | :------: | :----: | :----------: | * | **57** | **52** | **10.1** | **16** | **10** _-x-_ | * * --- * * @see https://developer.mozilla.org/docs/Web/CSS/align-self */ alignSelf?: Property.AlignSelf | undefined; /** * The **`align-tracks`** CSS property sets the alignment in the masonry axis for grid containers that have masonry in their block axis. * * **Syntax**: `[ normal | | | ? ]#` * * **Initial value**: `normal` * * | Chrome | Firefox | Safari | Edge | IE | * | :----: | :-----: | :----: | :--: | :-: | * | No | n/a | No | No | No | * * @see https://developer.mozilla.org/docs/Web/CSS/align-tracks */ alignTracks?: Property.AlignTracks | undefined; /** * The **`animation-delay`** CSS property specifies the amount of time to wait from applying the animation to an element before beginning to perform the animation. The animation can start later, immediately from its beginning, or immediately and partway through the animation. * * **Syntax**: `