UNPKG

bliss-svg-builder

Version:

Compose, render, and manipulate Blissymbolics SVG using a compact DSL and a programmatic mutation API.

1,032 lines (904 loc) 43.7 kB
/** * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ // --- Options --- /** Known option keys accepted by BlissSVGBuilder (kebab-case). */ export interface BlissOptions { // Stroke and spacing 'stroke-width'?: number; 'dot-extra-width'?: number; 'sdot-extra-width'?: number; 'dot-width'?: number; 'sdot-width'?: number; 'char-space'?: number; 'word-space'?: number; 'external-glyph-space'?: number; // Margins (builder-level) margin?: number; 'margin-top'?: number; 'margin-bottom'?: number; 'margin-left'?: number; 'margin-right'?: number; // Sizing 'min-width'?: number; center?: boolean; // Cropping (builder-level) crop?: number | 'auto' | 'auto-vertical' | 'compact'; 'crop-top'?: number | 'auto'; 'crop-bottom'?: number | 'auto'; 'crop-left'?: number | 'auto'; 'crop-right'?: number | 'auto'; // Grid (builder-level) grid?: boolean; 'grid-color'?: string; 'grid-major-color'?: string; 'grid-medium-color'?: string; 'grid-minor-color'?: string; 'grid-sky-color'?: string; 'grid-earth-color'?: string; 'grid-stroke-width'?: number; 'grid-major-stroke-width'?: number; 'grid-medium-stroke-width'?: number; 'grid-minor-stroke-width'?: number; 'grid-sky-stroke-width'?: number; 'grid-earth-stroke-width'?: number; // Colors and background color?: string; background?: string; 'background-top'?: string; 'background-mid'?: string; 'background-bottom'?: string; // Text and metadata text?: string; 'svg-desc'?: string; 'svg-title'?: string; 'svg-height'?: number; // Error handling 'error-placeholder'?: boolean; // SVG pass-through attributes (any key not in the known set). Includes // `boolean` so the boolean options (`grid`, `center`, `error-placeholder`) // conform to the index signature; without it `tsc` rejects this interface. [key: string]: string | number | boolean | undefined; } /** Cascading option layers: defaults (lowest priority) and overrides (highest priority). */ export interface OptionLayers { defaults?: BlissOptions; overrides?: BlissOptions; } // --- Element snapshots --- /** Bounding box of an element in absolute SVG coordinates. */ export interface ElementBounds { readonly minX: number; readonly maxX: number; readonly minY: number; readonly maxY: number; readonly width: number; readonly height: number; } /** A frozen, read-only snapshot of an element in the composition tree. */ export interface ElementSnapshot { readonly key: string; /** * The input code that produces this element (e.g. `'B431'`, `'Xa'`, `'H'`). * At part level, the structural lookup key the user would write (`'B81'`, * `'H'`, `'Xa'`, `'TSP'`, `'Xα'`, `'Xhαllo'`). At glyph level, the input * code only when the glyph is actually a glyph: B-codes (`'B431'`), single * X-codes (`'Xa'`, `'Xα'`), or `define()`d `type:'glyph'` aliases * (`'LOVE'`); `''` for composites, bare shape primitives, and * multi-character text fallback. Always `''` at group level. Note: this is * the live identity; what serialized output emits for a custom name is * governed by `toString()`/`toJSON()` and their `preserve` option. */ readonly codeName: string; /** * The rendered Unicode character for an external glyph (e.g. `'a'` for * `Xa`, `'α'` for `Xα`). `''` for B-codes, composites, shape primitives, * multi-character text fallback, and non-glyph levels. */ readonly char: string; readonly x: number; readonly y: number; readonly offsetX: number; readonly offsetY: number; readonly width: number; readonly height: number; readonly advanceX: number; readonly baseWidth: number; readonly level: number; readonly isRoot: boolean; readonly isGroup: boolean; readonly isGlyph: boolean; readonly isPart: boolean; readonly bounds: ElementBounds; readonly isIndicator: boolean; /** * For an indicator part, whether it originates from a character-level * marker (single `;`, `'character'`) or a word-level overlay (`;;`, * `'word'`). `null` for a non-indicator, a non-part level, or an indicator * whose definition cannot be resolved. The `'word'` value appears only on * the resolved snapshot: a word overlay has no raw node, so a part handle * can never reach one. Like `isIndicator`, a composite indicator's internal * sub-parts also classify (as `'character'`), so an enumeration that * recurses `children` should de-duplicate by depth. */ readonly indicatorLevel: 'character' | 'word' | null; /** * For an indicator part, `'semantic'` when its definition carries a * semantic indicator (e.g. THING), otherwise `'grammatical'`. `null` for a * non-indicator, a non-part level, or an unresolved definition. */ readonly indicatorKind: 'semantic' | 'grammatical' | null; readonly isShape: boolean; readonly isBlissGlyph: boolean; readonly isExternalGlyph: boolean; readonly isHeadGlyph: boolean; /** * Whether this group is a space separator (TSP/QSP). A content-empty group * (`{ glyphs: [] }`) is never a space group. */ readonly isSpaceGroup: boolean; readonly index: number; readonly parentKey: string | null; readonly children: readonly ElementSnapshot[]; } // --- Element handle (live mutation API) --- /** * A live handle referencing a node in the raw composition object. * Returned by `getElementByKey()`, `group()`, `glyph()`, and `part()`. * Mutations through a handle trigger a rebuild of the composition. * * A handle whose element has been removed is stale; most operations on a stale * handle throw (see Handle Lifetime in the docs). */ export declare class ElementHandle { /** Structural depth: 1 = group, 2 = glyph, 3+ = part. */ readonly level: number; /** True when level === 1 (a word group). */ readonly isGroup: boolean; /** True when level === 2 (a Bliss character). */ readonly isGlyph: boolean; /** True when level >= 3 (a part within a character). */ readonly isPart: boolean; /** * The input code that produces this element (e.g. `'B431'`, `'Xa'`, `'H'`). * At part level, the structural lookup key the user would write (`'B81'`, * `'H'`, `'Xa'`, `'TSP'`, `'Xα'`, `'Xhαllo'`). At glyph level, the input * code only when the glyph is actually a glyph: B-codes (`'B431'`), single * X-codes (`'Xa'`, `'Xα'`), or `define()`d `type:'glyph'` aliases * (`'LOVE'`); `''` for composites, bare shape primitives, and * multi-character text fallback. Always `''` at group level. Note: this is * the live identity; what serialized output emits for a custom name is * governed by `toString()`/`toJSON()` and their `preserve` option. */ readonly codeName: string; /** * The rendered Unicode character for an external glyph (e.g. `'a'` for * `Xa`, `'α'` for `Xα`). `''` for B-codes, composites, shape primitives, * multi-character text fallback, and non-glyph levels. */ readonly char: string; /** Stable across mutations. Use with `getElementByKey(key)` to recover a handle to this same node later. */ readonly key: string; /** Whether this part is an indicator. Only true on part-level handles. */ readonly isIndicator: boolean; /** * Indicator origin level: `'character'` for a single-`;` indicator part, or * `null` for a non-indicator or non-part handle. A word-level overlay (`;;`) * has no raw node, so a part handle can never reference one — it reads as * `'word'` on the resolved `snapshot()` tree instead (see * `ElementSnapshot.indicatorLevel`). */ readonly indicatorLevel: 'character' | null; /** * Indicator kind: `'semantic'` when the definition carries a semantic * indicator, `'grammatical'` otherwise, or `null` for a non-indicator part, * a non-part handle, or an unresolved definition. */ readonly indicatorKind: 'semantic' | 'grammatical' | null; /** Whether this part is a shape primitive. */ readonly isShape: boolean; /** Whether this glyph is a B-code Bliss character. */ readonly isBlissGlyph: boolean; /** Whether this glyph is an external font character. */ readonly isExternalGlyph: boolean; /** Whether this glyph is the head of its word group. */ readonly isHeadGlyph: boolean; /** * Whether this group is a space separator (TSP/QSP). A content-empty group * (`{ glyphs: [] }`) is never a space group. */ readonly isSpaceGroup: boolean; // --- Dimensions (read-only, from snapshot) --- /** Absolute x position of this element's origin. */ readonly x: number; /** Absolute y position of this element's origin. */ readonly y: number; /** Position offset relative to the parent. */ readonly offsetX: number; /** Position offset relative to the parent. */ readonly offsetY: number; /** Total width including indicator overhang. */ readonly width: number; /** Total height. */ readonly height: number; /** Absolute bounding box. */ readonly bounds: ElementBounds; /** Horizontal spacing step to next sibling. */ readonly advanceX: number; /** Width excluding indicators. Equals width when no indicators present. */ readonly baseWidth: number; /** Returns all dimension properties at once. */ measure(): { x: number; y: number; offsetX: number; offsetY: number; width: number; height: number; bounds: ElementBounds; advanceX: number; baseWidth: number; }; // --- Navigation --- /** Returns the head glyph handle within this group. Only valid on group handles. */ headGlyph(): ElementHandle | null; /** Returns the glyph at the given index within this group. Negative indices count from the end (-1 = last). Only valid on group handles. */ glyph(index: number): ElementHandle | null; /** * Returns the part at the given index. Negative indices count from the end (-1 = last). * Valid on glyph handles (returns a part of the glyph) and * part handles (returns a nested sub-part). */ part(index: number): ElementHandle | null; // --- Mutation: add/insert --- /** * Appends a glyph to this group. Only valid on group handles. An omitted, * empty, or whitespace-only `code` appends an empty glyph (`{parts: []}`); * it renders nothing and round-trips through `toJSON()`. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` parses to anything but exactly one glyph: * multi-glyph codes (including defined word names) and multi-group codes * throw, as do artifacts from above the glyph (document options * `[opts]||`, word options `[opts]|`, a word indicator list `;;`, or a * code that fails to parse as a word). Use `addGroup()` for word content. */ addGlyph(code?: string, opts?: BlissOptions | OptionLayers): this; /** * Inserts a glyph at the given index in this group. Only valid on group * handles. An omitted, empty, or whitespace-only `code` inserts an empty * glyph (`{parts: []}`). * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` parses to anything but exactly one glyph (see * `addGlyph`). */ insertGlyph(index: number, code?: string, opts?: BlissOptions | OptionLayers): this; /** * Appends a part to this glyph. On group handles, delegates to the last * glyph; if the group has no glyphs, the part is wrapped in a new glyph * instead of being dropped. A part references a shape, so `code` is * required: to reserve an empty slot, use `addGlyph('')`. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` is empty or whitespace-only (a part cannot be * empty), parses to anything but exactly one part (a multi-part * composition takes one call per part; a word is kept as a failed part * with a `WORD_AS_PART` warning instead of throwing), or carries an * artifact from above the part: document options `[opts]||`, word * options `[opts]|`, a word indicator list `;;`, a head marker `^`, or * a code that fails to parse as a word. */ addPart(code: string, opts?: BlissOptions | OptionLayers): this; /** * Inserts a part at the given index in this glyph. On group handles, * delegates to the last glyph; if the group has no glyphs, the part is * wrapped in a new glyph. A part references a shape, so `code` is * required. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` is empty or parses to anything but exactly one * part (see `addPart`). */ insertPart(index: number, code: string, opts?: BlissOptions | OptionLayers): this; // --- Mutation: remove/replace (self) --- /** Removes this element. Cascades: removing the last part removes its glyph, etc. */ remove(): undefined; /** Disconnects this element from its parent without cascade cleanup. May leave empty containers. */ detach(): undefined; /** * Replaces this element with a new one. Valid on glyph and part handles. * On a glyph handle an omitted, empty, or whitespace-only `code` swaps this * glyph for an empty glyph (`{parts: []}`), destructively discarding the * old content (a head designation dies with it). * @throws {TypeError} On glyph and part handles, if `code` is provided and * is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} On a glyph handle, if `code` parses to anything but * exactly one glyph (see `addGlyph`). On a part handle, if `code` is * omitted or empty (a part references a shape and cannot be empty; swap * in an empty slot with the glyph handle's `replace('')` instead) or * parses to anything but a single part (see `addPart`). */ replace(code?: string, opts?: BlissOptions | OptionLayers): this; // --- Mutation: remove/replace (parent-centric, by index) --- /** Removes the glyph at the given index in this group. Only valid on group handles. */ removeGlyph(index: number): this; /** * Replaces the glyph at the given index in this group. Only valid on group * handles. An omitted, empty, or whitespace-only `code` swaps the target * for an empty glyph (`{parts: []}`), destructively discarding the old * content (a head designation dies with it). Out-of-range indices remain a * silent no-op (checked before the code is parsed). * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` parses to anything but exactly one glyph (see * `addGlyph`). */ replaceGlyph(index: number, code?: string, opts?: BlissOptions | OptionLayers): this; /** Removes the part at the given index in this glyph. Only valid on glyph handles. */ removePart(index: number): this; /** * Replaces the part at the given index in this glyph. Only valid on glyph * handles. Out-of-range indices remain a silent no-op (checked before the * code is parsed). A part references a shape, so `code` is required. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` is empty or parses to anything but exactly one * part (see `addPart`). */ replacePart(index: number, code: string, opts?: BlissOptions | OptionLayers): this; // --- Mutation: indicators --- /** * SETS the indicator state on this element from the given indicator codes, * preserving an existing semantic indicator unless `{ stripSemantic: true }`. * * Polymorphic by handle level: * - **Glyph handle** — character-level: bakes the indicators into the * glyph's parts. `flatten` has no effect (glyph indicators are already flat). * - **Group handle** — word-level: sets the reversible `wordIndicators` * overlay (the DSL `;;` channel), leaving the base glyphs intact so a later * `clearIndicators()` restores them. `{ flatten: true }` opts out of the * overlay and bakes onto the head glyph as character-level parts instead * (the pre-overlay, character-level shape). A `flatten` apply keeps the * overlay when its code applies no indicator (it would bake nothing), rather * than silently dropping the overlay. * * An EMPTY code (omitted, `''`, or whitespace-only) is allowed as the * deliberate empty indicator set: on a group handle it stores the empty `;;` * overlay (render-significant — it hides the head's own character-level * indicators and adds none; with `{ stripSemantic: true }` it stores the * `;;!` strip overlay); on a glyph handle it has the same state effect as * `clearIndicators()`, with `stripSemantic` additionally removing the * semantic (a bare base stays a harmless silent no-op, like a trailing `;`). * * A NON-empty code whose codes are ALL invalid (non-indicators or unknown) * REFUSES: nothing mutates (no clearing, no stripping) and each rejected * code warns (`NON_INDICATOR_AS_CHARACTER_INDICATOR` / * `NON_INDICATOR_AS_WORD_INDICATOR` / `UNKNOWN_CODE`). A mixed list applies * the valid subset and warns per rejected code. One deliberate exception: * on a group handle, `{ stripSemantic: true }` is itself valid overlay * content, so an all-invalid apply WITH it still stores the `;;!` strip * overlay (DSL parity with `WORD;;!ZZ9` — the bad code drops, the `!` * stays). A non-string code (other than `null`, accepted as the deliberate * empty set) throws a `TypeError`. * * The per-surface `flatten` asymmetry is deliberate: it applies only to the * group/word-level overlay, the only surface with a portable `;;` form to * collapse. * * On a glyph handle the first part is always the base, so applying onto a lone * indicator or a detach-emptied glyph attaches the indicator (matching * `addPart`). An apply that targets a glyph that cannot carry an indicator * (a space glyph, or an invalid part pattern) adds an * `NOOP_INDICATOR_MUTATION` warning to `warnings` instead of silently doing * nothing. * @throws {TypeError} If `code` is provided, is not a string, and is not `null`. */ applyIndicators(code?: string | null, opts?: { stripSemantic?: boolean; flatten?: boolean }): this; /** * The PURE UNDO: removes indicators, always preserving an existing semantic * indicator. Clear takes no `stripSemantic` — that option lives on apply: * `applyIndicators('', { stripSemantic: true })` REMOVES the baked semantic * on a glyph handle, but on a group handle it STORES the reversible `;;!` * strip overlay (hides the head's indicators at render, removes nothing). * * Polymorphic by handle level (see `applyIndicators`): * - **Glyph handle** — removes the glyph's grammatical indicator parts. * - **Group handle** — removes the word-level `wordIndicators` overlay, and * thereby UN-HIDES the head glyph's own character-level indicators: while * the overlay existed it replaced them at render, so clearing it restores * the original characters. `{ flatten: true }` bakes the cleared state * onto the head glyph instead of leaving an overlay. * * A clear that finds nothing to remove (a glyph with no indicators; a group * with no overlay) adds an `NOOP_INDICATOR_MUTATION` warning to `warnings`. */ clearIndicators(opts?: { flatten?: boolean }): this; // --- Mutation: space/word structure --- /** * Splits this word group into two at the glyph boundary, inserting a space * between. Only valid on group handles. A word-level (`;;`) indicator overlay * is a word property: the first (left) part always keeps it. A `^` head * marker is kept only if its glyph lands in the first part; otherwise the * second part re-derives its head. * @throws {Error} On a group handle, if `glyphIndex` is out of range (must be * 1 to glyphs.length-1 inclusive). Returns `this` with no effect on non-group * handles (no throw). */ splitAt(glyphIndex: number): this; /** * Merges this word group with the next one, removing spaces between them. * Only valid on group handles. The merged word keeps this group's word-level * (`;;`) indicator overlay; an overlay on the absorbed word is dropped and a * `DROPPED_WORD_INDICATOR` warning is added to `warnings`. The absorbed * word's `^` head marker is also dropped (silently); the merged word * resolves a single head. */ mergeWithNext(): this; // --- Mutation: options --- /** * Sets or merges options on this element. Accepts flat options (treated as * overrides) or { defaults, overrides }. * @throws {TypeError} If `opts` is provided and is not an object. */ setOptions(opts: BlissOptions | OptionLayers): this; /** Removes specific option keys from this element. */ removeOptions(...keys: string[]): this; } // --- Definition types --- /** Context object passed to custom `getPath` functions. */ export interface ShapeContext { [key: string]: any; } /** * Definition for a custom glyph: a base character or a compound indicator, * composed from existing codes. A glyph definition may not bake in an indicator * part unless it is itself a compound indicator (`isIndicator: true`); define a * base+indicator combination as a {@link BareDefinition} alias instead (the * indicator attaches at the use site). `define()` reports a violation in its * `errors` result rather than throwing. */ export interface GlyphDefinition { type: 'glyph'; codeString: string; /** * Marks an all-indicator definition as a compound indicator. Valid only on a * `type: 'glyph'` definition; on a bare alias, shape, or external glyph * `define()` reports a violation in its `errors` result. Indicator-ness still * rides through an unflagged bare alias whose target is itself an indicator * (e.g. `{ codeString: 'B81' }`). */ isIndicator?: boolean; anchorOffsetX?: number; anchorOffsetY?: number; width?: number; shrinksPrecedingWordSpace?: boolean; kerningRules?: Record<string, any>; /** * Default options merged into each use of the definition. Keys must be * well-formed option names (letters, digits, hyphens; camelCase accepted; * names beginning with `on` are rejected, as for any attribute) and may not * include a global-only (builder-canvas) option key such as `margin` or * `grid` — those configure the whole SVG and would be inert on a * definition; `define()` reports a violation in its `errors` result, * `patchDefinition()` throws. */ defaultOptions?: BlissOptions; } /** Definition for a custom shape (rendered via getPath or codeString). */ export interface ShapeDefinition { type?: 'shape'; /** `isIndicator` is glyph-only; forbidding it makes the union reject a typeless flagged literal at compile time (the runtime rejects it too). */ isIndicator?: never; getPath?: (ctx: ShapeContext) => string; codeString?: string; width?: number; height?: number; x?: number; y?: number; extraPathOptions?: Record<string, any>; /** Default options merged into each use; global-only option keys are rejected (see {@link GlyphDefinition.defaultOptions}). */ defaultOptions?: BlissOptions; } /** Definition for an external glyph (custom rendering around a Unicode character). */ export interface ExternalGlyphDefinition { type: 'externalGlyph'; /** `isIndicator` is glyph-only; forbidding it makes the union reject a typeless flagged literal at compile time (the runtime rejects it too). */ isIndicator?: never; getPath: (ctx: ShapeContext) => string; width: number; /** The rendered Unicode character (e.g. `'a'` for the external glyph registered as `'Xa'`). */ char: string; y?: number; height?: number; kerningRules?: Record<string, any>; /** Default options merged into each use; global-only option keys are rejected (see {@link GlyphDefinition.defaultOptions}). */ defaultOptions?: BlissOptions; } /** Bare alias definition (maps a code name to a code string). */ export interface BareDefinition { codeString: string; /** `isIndicator` is glyph-only; forbidding it makes the union reject a typeless flagged literal at compile time (the runtime rejects it too). */ isIndicator?: never; /** Default options merged into each use; global-only option keys are rejected (see {@link GlyphDefinition.defaultOptions}). */ defaultOptions?: BlissOptions; } /** Union of all definition types accepted by `BlissSVGBuilder.define()`. */ export type CodeDefinition = | GlyphDefinition | ShapeDefinition | ExternalGlyphDefinition | BareDefinition; /** Definition type identifiers. */ export type DefinitionType = 'shape' | 'glyph' | 'externalGlyph' | 'bare' | 'space'; /** Result returned by `BlissSVGBuilder.define()`. */ export interface DefineResult { defined: string[]; skipped: string[]; errors: string[]; } /** Frozen metadata returned by `BlissSVGBuilder.getDefinition()`. */ export interface DefinitionMetadata { readonly type: DefinitionType; readonly isBuiltIn: boolean; readonly [key: string]: any; } // --- Serialization output --- /** * Normalized parsed structure returned by `toJSON()`. Describes the default * (authoring) shape: the composition the user wrote, plus `isIndicator`/`width` * on indicator parts. Definition-derived metadata (glyph * `isBlissGlyph`/`isExternalGlyph`/`char`/`kerningRules`/ * `shrinksPrecedingWordSpace` and the glyph-level `isIndicator` flag, plus part * `anchorOffsetX`/`anchorOffsetY`) is omitted — it is fully re-derived from the * code when this object is passed back to the constructor. `{ deep: true }` * retains that metadata plus nested sub-parts for `toString()`/`merge()`; those * extra fields are not part of this type. * * A word the parser rejects outright (a malformed `;;` word indicator) is * dropped from `groups` entirely; its parse warning is the only trace. */ export interface BlissJSON { options?: Record<string, string | boolean>; groups: Array<{ options?: Record<string, string | boolean>; /** * Word-level indicator overlay (the DSL `;;` form): stored on the word and * resolved onto the head glyph at render. Present in default output (kept * by default); absent when there is no word-level indicator. */ wordIndicators?: { codes: string[]; stripSemantic: boolean }; glyphs?: Array<{ codeName?: string; options?: Record<string, string | boolean>; isHeadGlyph?: boolean; parts?: Array<{ codeName: string; options?: Record<string, string | boolean>; /** Present (true) only on an indicator part. */ isIndicator?: boolean; /** Rendered indicator width; present only on an indicator part. */ width?: number; x?: number; y?: number; /** * Fail metadata retained when a known composition sits in a `;`-part * slot (`COMPOSITE_AS_PART` / `WORD_AS_PART`): the part is kept and * warned, and both fields round-trip through the constructor. */ error?: string; errorCode?: WarningCode; parts?: Array<any>; }>; }>; }>; } // --- Warnings --- /** * Every warning code the current builder version can emit. The named literals * mirror the `WARNING_CODES` registry in `bliss-constants.js` (the runtime * source of truth). * * This is an OPEN union: minor releases may add new warning codes (existing * code meanings never change within 1.x), so the type also accepts any string * while keeping autocomplete for the known codes. When switching on a warning * code, always include a default branch for codes added after your build. */ export type WarningCode = | 'MALFORMED_GLOBAL_OPTIONS' | 'MALFORMED_GROUP_OPTIONS' | 'MALFORMED_WORD_INDICATOR' | 'MALFORMED_COORDINATES' | 'MALFORMED_KERNING_VALUE' | 'MISPLACED_HEAD_MARKER' | 'MISPLACED_CHARACTER_INDICATOR' | 'MISPLACED_WORD_INDICATOR' | 'MISPLACED_PART_OPTION' | 'MISPLACED_CHARACTER_OPTION' | 'MISPLACED_GROUP_OPTION' | 'MISPLACED_GLOBAL_OPTION' | 'MISPLACED_SPACE_DECORATION' | 'MISPLACED_INDICATOR_PART' | 'MISPLACED_SPACE' | 'MULTIPLE_HEAD_MARKERS' | 'MULTIPLE_OPTION_BRACKETS' | 'DUPLICATE_KEY' | 'UNKNOWN_CODE' | 'DROPPED_WORD_INDICATOR' | 'UNSUPPORTED_TEXT_BLOCKS' | 'NOOP_INDICATOR_MUTATION' | 'COMPOSITE_AS_PART' | 'WORD_AS_PART' | 'NON_INDICATOR_AS_WORD_INDICATOR' | 'NON_INDICATOR_AS_CHARACTER_INDICATOR' | (string & {}); /** A warning generated when the builder encounters a problem it can recover from. */ export interface Warning { /** Warning type identifier (see `WarningCode`). */ readonly code: WarningCode; /** Human-readable description of the issue. */ readonly message: string; /** The problematic DSL code that triggered the warning. */ readonly source: string; } // --- Builder stats --- export interface BuilderStats { groupCount: number; glyphCount: number; } // --- Main class --- export declare class BlissSVGBuilder { /** * Creates an instance of BlissSVGBuilder. * @param input - A DSL string, a plain object from `toJSON()`, or omitted for an empty builder * @param options - Defaults/overrides to merge, or flat options treated as overrides * @throws {Error} If `input` is neither a DSL string nor a plain `toJSON()` object * (for example a number, `null`, or an array). Recoverable DSL problems (unknown codes, * invalid syntax) do NOT throw; they are reported in `warnings`. * @throws {TypeError} If `options` is provided and is not an object. */ constructor(input?: string | BlissJSON, options?: BlissOptions | OptionLayers); /** * Library version string (set at build time). * Also exported as the named `LIB_VERSION` constant. */ static readonly LIB_VERSION: string; // --- SVG output (getters) --- /** SVG content (path elements and groups) without the outer `<svg>` wrapper. */ readonly svgContent: string; /** Parsed SVG as a DOM element. Requires a DOM environment. */ readonly svgElement: SVGSVGElement; /** Complete SVG markup without XML declaration. */ readonly svgCode: string; /** Complete SVG markup with XML declaration. */ readonly standaloneSvg: string; // --- Warnings --- /** * Warnings generated during parsing/rendering (unknown codes, invalid syntax, etc.). * Populated at construction and re-derived on each rebuild. NOT exhaustive: a few * structural operations discard data without a warning (e.g. `mergeWithNext()` drops * the absorbed word's options and `^` head marker), so an empty array is not a * guarantee of zero data loss across every mutation path. */ readonly warnings: readonly Warning[]; // --- Element tree (getters) --- /** Root element snapshot (frozen tree of all elements). */ readonly elements: ElementSnapshot; /** Non-space group snapshots. */ readonly groups: readonly ElementSnapshot[]; /** Group and glyph counts. */ readonly stats: BuilderStats; // --- Traversal and querying --- /** Depth-first traversal of all element snapshots. Return `false` to stop early. */ traverse(callback: (el: ElementSnapshot) => boolean | void): void; /** Returns all element snapshots matching the predicate. */ query(predicate: (el: ElementSnapshot) => boolean): ElementSnapshot[]; /** Looks up an element handle by its snapshot key. */ getElementByKey(key: string): ElementHandle | null; /** Returns a handle to the non-space group at the given index. Negative indices count from the end (-1 = last). */ group(index: number): ElementHandle | null; /** Returns a handle to any group (including spaces) at the given raw index. Negative indices count from the end (-1 = last). */ element(index: number): ElementHandle | null; /** Total number of raw groups (including space groups). */ readonly elementCount: number; /** Returns a handle to the glyph at the given flat index across all groups. Negative indices count from the end (-1 = last). */ glyph(flatIndex: number): ElementHandle | null; /** Returns a handle to the part at the given flat index across all glyphs. Negative indices count from the end (-1 = last). */ part(flatIndex: number): ElementHandle | null; /** Returns the root element snapshot (alias for `elements`). */ snapshot(): ElementSnapshot; // --- Building and manipulation --- /** * Appends a new glyph group with automatic space management. An omitted, * empty, or whitespace-only `code` appends an empty group. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` does not parse to exactly one group, or * carries document-level options (`[opts]||`). */ addGroup(code?: string, opts?: BlissOptions | OptionLayers): this; /** * Appends a glyph to the last non-space group (creates one if empty). An * omitted, empty, or whitespace-only `code` appends an empty glyph * (`{parts: []}`). On an empty builder the glyph is validated first, then * wrapped in a new group, so a rejected code leaves the builder untouched * and `opts` land on the glyph itself. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` parses to anything but exactly one glyph: * multi-glyph codes (including defined word names) and multi-group codes * throw, as do artifacts from above the glyph (document options * `[opts]||`, word options `[opts]|`, a word indicator list `;;`, or a * code that fails to parse as a word). Use `addGroup()` for word content. */ addGlyph(code?: string, opts?: BlissOptions | OptionLayers): this; /** * Appends a part to the last non-space group's last glyph. If that group * has no glyphs, the part is wrapped in a new glyph; on an empty builder * the part is validated first, then wrapped in a new group and glyph, so * a rejected code leaves the builder untouched and `opts` land on the * part itself. A part references a shape, so `code` is required: to * reserve an empty slot, use `addGlyph('')`. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` is empty or whitespace-only (a part cannot be * empty), parses to anything but exactly one part (a multi-part * composition takes one call per part; a word is kept as a failed part * with a `WORD_AS_PART` warning instead of throwing), or carries an * artifact from above the part: document options `[opts]||`, word * options `[opts]|`, a word indicator list `;;`, a head marker `^`, or * a code that fails to parse as a word. */ addPart(code: string, opts?: BlissOptions | OptionLayers): this; /** * Inserts a group at the given index. Negative indices count from the end. * An omitted, empty, or whitespace-only `code` inserts an empty group. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` does not parse to exactly one group, or * carries document-level options (`[opts]||`). */ insertGroup(index: number, code?: string, opts?: BlissOptions | OptionLayers): this; /** Removes the group at the given index. Negative indices count from the end. */ removeGroup(index: number): this; /** * Replaces the group at the given index with new content. Negative indices * count from the end. An omitted, empty, or whitespace-only `code` swaps the target for an * empty group. Out-of-range indices remain a silent no-op (checked before * the code is parsed). * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` does not parse to exactly one group, or * carries document-level options (`[opts]||`). */ replaceGroup(index: number, code?: string, opts?: BlissOptions | OptionLayers): this; /** * Merges another builder's content into this one. Appends the other builder's groups * with a space between. The other builder's global options are discarded. * @throws {Error} If `other` is not a `BlissSVGBuilder` instance. */ merge(other: BlissSVGBuilder): this; /** * Splits this builder at the given group index. This builder keeps the left half; * a new builder with the right half is returned. Both share the same global options. * @throws {Error} If the builder has fewer than 2 groups, or `groupIndex` is out of * range (must be 1 to groupCount-1 inclusive). */ splitAt(groupIndex: number): BlissSVGBuilder; /** * Appends a raw group with no automatic space management. SP auto-resolves * to TSP/QSP. An omitted, empty, or whitespace-only `code` appends an empty group. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` does not parse to exactly one group, or * carries document-level options (`[opts]||`). */ addElement(code?: string, opts?: BlissOptions | OptionLayers): this; /** * Inserts a raw group at the given index with no automatic space management. * SP auto-resolves. An omitted, empty, or whitespace-only `code` inserts an empty group. * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` does not parse to exactly one group, or * carries document-level options (`[opts]||`). */ insertElement(index: number, code?: string, opts?: BlissOptions | OptionLayers): this; /** Removes the raw group at the given index (plain splice, no space cleanup). */ removeElement(index: number): this; /** * Replaces the raw group at the given index with new content. An omitted, * empty, or whitespace-only `code` swaps the target for an empty group. * Out-of-range indices * remain a silent no-op (checked before the code is parsed). * @throws {TypeError} If `code` is provided and is not a string. * @throws {TypeError} If `opts` is provided and is not an object. * @throws {Error} If `code` does not parse to exactly one group, or * carries document-level options (`[opts]||`). */ replaceElement(index: number, code?: string, opts?: BlissOptions | OptionLayers): this; /** Removes all content from the builder. */ clear(): this; // --- Serialization --- /** * Returns a portable DSL string. Custom codes are decomposed to built-in * codes by default. `{ preserve: true }` keeps a custom name that stands * for a single glyph, indicator, or shape: every `type`d definition * (whatever its anatomy) and every bare alias renaming one existing code; * multi-code shorthand always serializes expanded. * * `flattenIndicators` collapses word-level (`;;`) indicators onto the head * glyph as character-level `;`, reproducing the pre-overlay output. It is the * same concept as `applyIndicators(code, { flatten })`; the name is qualified * here only because `toString`/`toJSON` lack the "indicator" context the * method name provides. */ toString(options?: { preserve?: boolean; flattenIndicators?: boolean }): string; /** * Returns a normalized parsed structure (plain object). Custom codes are * decomposed to built-in codes by default; `{ preserve: true }` keeps * custom names under the same rule as `toString()`. * * `flattenIndicators` bakes word-level (`;;`) indicators onto the head and * omits the `wordIndicators` field (see `toString`). * * `deep` keeps nested sub-parts and all definition-derived metadata (consumed * by `toString()`/`merge()`); the default output omits those (they are * re-derived from the code on reconstruction) and is the authoring shape * described by {@link BlissJSON}. */ toJSON(options?: { preserve?: boolean; deep?: boolean; flattenIndicators?: boolean }): BlissJSON; // --- Static: definition management --- /** * Defines one or more custom codes (glyphs, shapes, external glyphs, or bare aliases). * @param definitions - Map of code names to their definitions * @param options - Pass `{ overwrite: true }` to replace existing custom definitions. * Built-in definitions cannot be overwritten (the entry lands in `errors`). * * Does NOT throw: each entry is validated independently, and any rejection is reported * in `result.errors` (other entries in the same call still register). Always inspect * the returned `{ defined, skipped, errors }`. Rejected entries include names with * invisible characters, the reserved names (`X` + letters, `RK`, `AK`, `SP`), and * a define that would make an existing glyph definition bake an indicator (deferred * validation of a forward reference). */ static define( definitions: Record<string, CodeDefinition>, options?: { overwrite?: boolean } ): DefineResult; /** Returns `true` if a code is defined (built-in or custom). */ static isDefined(code: string): boolean; /** Returns frozen metadata for a code, or `null` if not found. */ static getDefinition(code: string): DefinitionMetadata | null; /** Lists all defined codes, optionally filtered by type. */ static listDefinitions(filter?: { type?: DefinitionType }): string[]; /** * Removes a custom definition. Returns `false` (no throw) if the code does not exist. * @throws {Error} If `code` is a built-in definition (built-ins cannot be removed). */ static removeDefinition(code: string): boolean; /** * Patches properties on an existing custom definition. * Only keys valid for the definition's type are accepted. * Built-in definitions cannot be patched. * @throws {Error} If `code` is not defined or is built-in, `changes` is not an object, * or a change violates the definition rules: an unknown property or internal/type * flag (e.g. `type`, `isBuiltIn`) for the definition; * `getPath` not a function; empty, whitespace-only, or non-string `codeString`; a `;;` in `codeString`; * a `/` in a glyph-or-shape `codeString`; a disallowed reference type; a circular * reference; a `;`-part that is itself a composition; a glyph `codeString` that * bakes an indicator (or an `isIndicator` change that leaves one baked); internal * coordinates in a multi-character bare `codeString` (kerning markers allowed); * a patch that would turn an already-referenced code into an indicator; or a * global-only `defaultOptions` key. */ static patchDefinition(code: string, changes: Partial<CodeDefinition>): { patched: true }; } /** * Library version string (set at build time). * Also accessible as the `BlissSVGBuilder.LIB_VERSION` static. */ export declare const LIB_VERSION: string; export default BlissSVGBuilder;