import type { DocumentMatrix, DocumentValue } from './contracts.js'; /** * Pure path model for `vector` nodes. The document stores it under * `node.properties.vector` as plain JSON: absolute coordinates in the node's * own space, one entry per sub-path, cubic curves only (`M`/`L`/`C`/`Z`). * * Everything here is deterministic and free of DOM access. Editing operations * return new values and never mutate their input, so a canvas gesture can * preview a result and commit or drop it. Precision limits are documented on * the boolean operations, which flatten curves before clipping. */ export type VectorFillRule = 'nonzero' | 'evenodd'; export type VectorBooleanOperation = 'union' | 'subtract' | 'intersect' | 'exclude'; export type VectorLineCap = 'butt' | 'round' | 'square'; export type VectorLineJoin = 'miter' | 'round' | 'bevel'; export type VectorShape = 'rectangle' | 'ellipse' | 'line' | 'polygon' | 'star' | 'arc'; export interface VectorPoint { x: number; y: number; } export type VectorCommand = { type: 'M'; x: number; y: number; } | { type: 'L'; x: number; y: number; } | { type: 'C'; x1: number; y1: number; x2: number; y2: number; x: number; y: number; } | { type: 'Z'; }; export interface VectorPath { closed: boolean; commands: VectorCommand[]; } export interface VectorStroke { width: number; color: string; cap?: VectorLineCap; join?: VectorLineJoin; } export interface VectorProperties { version: 1; paths: VectorPath[]; fillRule?: VectorFillRule; fill?: string; stroke?: VectorStroke; /** Non-destructive corner radius applied to straight corners at render time. */ corners?: number; /** A mask clips the siblings rendered above it; its own paint is not shown. */ mask?: boolean; /** Records the operation that produced this geometry; informational. */ booleanOp?: VectorBooleanOperation; } export interface VectorBounds { x: number; y: number; width: number; height: number; } /** Editing view of a path: anchors with optional absolute handles. `in` leads into the anchor, `out` leaves it. */ export interface VectorAnchor { x: number; y: number; in?: VectorPoint; out?: VectorPoint; } export interface VectorAnchorAddress { path: number; anchor: number; } export interface VectorSegmentAddress { path: number; segment: number; } export type VectorAnchorMode = 'corner' | 'smooth'; export type VectorHandleSide = 'in' | 'out'; export type VectorRing = VectorPoint[]; export interface VectorSegment { from: number; to: number; curve: boolean; /** Cubic control polygon; a line repeats its end points. */ points: [VectorPoint, VectorPoint, VectorPoint, VectorPoint]; } export declare const VECTOR_SHAPES: readonly VectorShape[]; export declare const VECTOR_BOOLEAN_OPERATIONS: readonly VectorBooleanOperation[]; /** Curve flattening tolerance in canvas units for boolean operations and area. */ export declare const VECTOR_FLATNESS = 0.1; /** Vertices closer than this are merged by the polygon clipper. */ export declare const VECTOR_MERGE_DISTANCE = 0.000001; /** Features thinner than twice this distance are not resolved by the polygon clipper. */ export declare const VECTOR_SIDE_SAMPLE = 0.0001; /** Strict structural check of stored path data; unknown keys and non-finite numbers are rejected. */ export declare function isVectorProperties(value: unknown): value is VectorProperties; /** A detached copy of valid stored path data, or null. */ export declare function parseVector(value: unknown): VectorProperties | null; /** Canonical JSON for `node.properties.vector`: known keys only, in a stable order, nothing undefined. */ export declare function serializeVector(vector: VectorProperties): { [key: string]: DocumentValue; }; /** Anchors of a path; a closed path never repeats its first anchor, the closing segment is implicit. */ export declare function vectorPathAnchors(path: VectorPath): VectorAnchor[]; /** Rebuilds commands from anchors; a curved closing segment is written explicitly before `Z`. */ export declare function vectorPathFromAnchors(anchors: readonly VectorAnchor[], closed: boolean): VectorPath; /** Segments of a path in anchor order; the closing segment of a closed path comes last. */ export declare function vectorPathSegments(path: VectorPath): VectorSegment[]; /** Exact bounds of the geometry including curve extrema; null without anchors. Strokes and corners are not included. */ export declare function vectorBounds(vector: VectorProperties): VectorBounds | null; /** Applies an affine matrix to every coordinate, control points included. Style keys are kept. */ export declare function transformVector(vector: VectorProperties, matrix: DocumentMatrix): VectorProperties; export declare function translateVector(vector: VectorProperties, dx: number, dy: number): VectorProperties; /** Matrix that maps the geometry's bounds onto a node box; a zero-extent axis keeps its scale. */ export declare function vectorFitTransform(bounds: VectorBounds, width: number, height: number): DocumentMatrix; /** Moves the geometry so its bounds start at the origin and reports the node size it needs. */ export declare function normalizeVectorOrigin(vector: VectorProperties): { vector: VectorProperties; offset: VectorPoint; width: number; height: number; }; /** Node placement after geometry changed: the local offset travels through the node's linear part. */ export declare function fitVectorNode(vector: VectorProperties, transform: DocumentMatrix): { vector: VectorProperties; transform: DocumentMatrix; width: number; height: number; }; export declare function rectangleVector(width: number, height: number): VectorProperties; export declare function ellipseVector(width: number, height: number): VectorProperties; /** A straight segment from the node origin to its far corner; a zero height gives a horizontal line. */ export declare function lineVector(width: number, height: number): VectorProperties; /** A regular polygon inscribed in the node box, first vertex at the top. */ export declare function polygonVector(width: number, height: number, sides?: number): VectorProperties; /** A star with alternating outer and inner vertices; the inner ratio is relative to the outer radius. */ export declare function starVector(width: number, height: number, points?: number, innerRatio?: number): VectorProperties; /** Elliptical sector or ring sector, with cubic segments no larger than a quarter turn. * Angles are clockwise degrees in the local y-down coordinate system. Cubics approximate * circular arcs with the same quarter-circle convention as ellipseVector. */ export declare function arcVector(width: number, height: number, start?: number, sweep?: number, innerRadius?: number): VectorProperties; export declare function createShapeVector(shape: VectorShape, width: number, height: number): VectorProperties; /** * Replaces each straight corner with a circular arc approximated by one cubic. The tangent * length is clamped to half of each adjacent segment so neighbouring arcs never overlap; two * arcs that meet at the middle of a segment share one anchor (a square with a large radius * becomes a circle, an oblong a pill). Corners between curved segments, path ends, and * collinear anchors are left alone. */ export declare function roundVectorCorners(vector: VectorProperties, radius: number): VectorProperties; /** SVG `d` attribute; corner rounding is applied unless `corners: false`. */ export declare function vectorPathData(vector: VectorProperties, options?: { corners?: boolean; }): string; /** Smooth: both handles present and collinear through the anchor in opposite directions. */ export declare function vectorAnchorMode(anchor: VectorAnchor): VectorAnchorMode; export declare function moveVectorAnchor(vector: VectorProperties, address: VectorAnchorAddress, target: VectorPoint): VectorProperties; /** Moves one handle; with `mirror`, an existing opposite handle keeps the same length on the opposite side. */ export declare function moveVectorHandle(vector: VectorProperties, address: VectorAnchorAddress, side: VectorHandleSide, target: VectorPoint, mirror?: boolean): VectorProperties; /** Corner drops both handles; smooth mirrors handles along the neighbour tangent, one third of the shorter side. */ export declare function setVectorAnchorMode(vector: VectorProperties, address: VectorAnchorAddress, mode: VectorAnchorMode): VectorProperties; /** Splits a segment at parameter `t`; a cubic keeps its shape exactly (de Casteljau). */ export declare function insertVectorAnchor(vector: VectorProperties, address: VectorSegmentAddress, t?: number): VectorProperties; /** Removes an anchor; neighbours keep their handles. A sub-path with fewer than two anchors is removed. */ export declare function deleteVectorAnchor(vector: VectorProperties, address: VectorAnchorAddress): VectorProperties; /** A closed path can open at any anchor; an open path can split at an interior anchor. */ export declare function canSplitVectorPath(vector: VectorProperties, address: VectorAnchorAddress): boolean; /** Opens a closed path at the anchor (it becomes both ends) or splits an open path into two at the anchor. */ export declare function splitVectorPath(vector: VectorProperties, address: VectorAnchorAddress): VectorProperties; export declare function canJoinVectorPaths(vector: VectorProperties, from: number, to: number): boolean; /** * Joins the end of one open path to the start of another with a straight segment (coincident * ends merge into one anchor). Joining a path to itself closes it. */ export declare function joinVectorPaths(vector: VectorProperties, from: number, to: number): VectorProperties; /** * Closest point on any segment; null without segments. Straight segments project exactly, * curves sample 32 steps and then halve the step 24 times around the best sample. */ export declare function nearestVectorSegmentPoint(vector: VectorProperties, target: VectorPoint): { path: number; segment: number; t: number; point: VectorPoint; distance: number; } | null; /** Closed sub-paths as polygons (corner rounding applied); open sub-paths are skipped. */ export declare function flattenVector(vector: VectorProperties, tolerance?: number): { rings: VectorRing[]; fillRule: VectorFillRule; }; /** Point containment using the fill rule, on the flattened geometry. */ export declare function vectorContainsPoint(vector: VectorProperties, target: VectorPoint, tolerance?: number): boolean; /** Absolute sum of signed ring areas: exact for boolean results and simple shapes. */ export declare function vectorArea(vector: VectorProperties, tolerance?: number): number; /** * Polygon clipper on flattened rings. Every edge is split at every intersection (including * collinear overlaps), duplicate edges are merged, and each remaining edge is classified by * sampling a point on each side against both inputs with their fill rules. Edges whose sides * disagree about the result form the boundary, oriented with the result on the left, and * are chained into rings by taking the tightest turn at every vertex. Limits: O(n²) in the * number of flattened edges, vertices within `VECTOR_MERGE_DISTANCE` are merged, and slivers * thinner than twice `VECTOR_SIDE_SAMPLE` can be misclassified. */ export declare function booleanRings(a: readonly VectorRing[], aRule: VectorFillRule, b: readonly VectorRing[], bRule: VectorFillRule, operation: VectorBooleanOperation): VectorRing[]; /** Only identical paths or strictly separated exact bounds currently preserve curves. */ export declare function booleanVectorPrecision(a: VectorProperties, b: VectorProperties): 'curve-preserved' | 'polygon-approximation'; /** Boolean output keeps original cubic commands when equality or separated bounds proves * the set operation. Intersecting or unproven geometry still uses the polygon approximation. * Open sub-paths do not take part. The first operand supplies paint; an empty result has no paths. */ export declare function booleanVectors(a: VectorProperties, b: VectorProperties, operation: VectorBooleanOperation, tolerance?: number): VectorProperties; /** World-space matrix of a node combined with the fit of its stored geometry onto its box. */ export declare function vectorRenderTransform(vector: VectorProperties, width: number, height: number, world?: DocumentMatrix): DocumentMatrix;