@idle-screens/schema 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/FORMAT.md CHANGED
@@ -122,7 +122,7 @@ trails, long-exposure light). 0.85–0.95 is the useful range; 0 (default) is of
122
122
  | `blend` | `lighter` \| `screen` \| `multiply` | source-over | additive glow / gentle additive / darkening |
123
123
  | `region` | `{x?, y?}` ranges 0..1 | full viewport | fractional spawn window (placement only, not travel) |
124
124
  | `pulse` | `{amp ≤ 0.5, period ≥ 500, wave?}` | none | opacity breathing; `wave: {wavelength, angle?}` turns it into a traveling wave across the field |
125
- | `spin` | number ±360 deg/sec | none | per-entity rotation (seeded start angle) |
125
+ | `spin` | number \| `[min,max]` ±360 deg/sec | none | per-entity rotation (seeded start angle); a range gives each entity a seeded speed (confetti, tumbling debris) |
126
126
  | `grow` | `{amp ≤ 0.8, period ≥ 500}` | none | size breathing (seeded phase) |
127
127
  | `trail` | `{length ≤ 5000, fade?}` | none | analytic afterglow trail (ms of history) |
128
128
  | `links` | see below | none | inter-entity lines |
@@ -225,23 +225,38 @@ zero-dependency, renderer-free analysis tool.
225
225
  (background gradient + entities + link lines, blend-aware), with coverage,
226
226
  visual-mass centroid, and **row/column deviation profiles** (1D transects of
227
227
  the composition).
228
- - `renderBrailleMap(grid)` — the picture: 12 lines × 40 braille chars, each
229
- char a 2×4 dot cell, ordered-dithered with auto-exposure. An agent reads the
230
- whole frame at once instead of reconstructing it entity by entity.
228
+ - `renderBrailleMap(grid)` — the compact picture: 12 lines × 40 braille chars,
229
+ each char a 2×4 dot cell, ordered-dithered with auto-exposure. An agent reads
230
+ the whole frame at once instead of reconstructing it entity by entity.
231
+ - `renderDensityMap(grid)` — a **higher-fidelity** picture: one ASCII density
232
+ char (` .:-=+*#%@`) per grid cell, so it's 1:1 with the grid. Pair with a
233
+ larger `cols`/`rows` (e.g. `{cols: 120, rows: 48}`) to resolve structure the
234
+ braille map smears — concentric rings, grids, text blocks. This is the sharper
235
+ read when composition detail matters.
231
236
  - `dominanceRanking(spec, opts)` — layers ranked by estimated visual weight
232
237
  (area × alpha × contrast × glow/motion boosts; links count) — *where the eye
233
- goes*, normalized to shares.
238
+ goes*, normalized to shares. Thin-but-bright structures (rings, streaks, link
239
+ lines) get a line-salience boost so they aren't crushed by filled discs.
234
240
  - `motionStats(spec, opts)` — per-layer mean/max on-screen speed from analytic
235
241
  displacement — choreography as numbers.
242
+ - `textSprites(spec, opts)` — the literal strings and rendered sizes of every
243
+ text layer. Glyphs are invisible in the luminance maps, so this is the only
244
+ way to confirm *what words* are on screen and how big. (Also on
245
+ `perceiveScene().text`.)
236
246
  - `diffScenes(a, b, opts)` — **relative sight**: coverage/luminance deltas,
237
247
  visual-balance shift, 3×3 region deltas, dominance-rank movement, and
238
248
  advisory codes added/removed. Agents judge "is B better than A" far more
239
249
  reliably than "is A good"; this is the edit loop's primary instrument.
240
250
 
241
- Known approximations (deliberate): trails and ghosting are not sampled, soft
242
- circles use a linear falloff, text ink is estimated from font size × string
243
- length. Good enough to perceive composition, focus, balance, and motion —
244
- not a substitute for a human (or VLM) judgement of beauty.
251
+ Coverage is **additive-glow-calibrated**: soft circles drawn with
252
+ `blend: lighter`/`screen` bloom past their radius (as they do on the real
253
+ canvas), so `coverage` tracks what the audience sees instead of under-reporting
254
+ glow-heavy scenes by an order of magnitude.
255
+
256
+ Known approximations (deliberate): trails and ghosting are not sampled, text ink
257
+ is estimated from font size × string length. Good enough to perceive
258
+ composition, focus, balance, and motion — not a substitute for a human (or VLM)
259
+ judgement of beauty.
245
260
 
246
261
  ## Examples
247
262
 
package/dist/index.d.ts CHANGED
@@ -115,8 +115,12 @@ interface LayerSpec {
115
115
  /**
116
116
  * Per-entity rotation speed in degrees/sec (positive = clockwise).
117
117
  * Each entity gets a seeded starting angle. Composes with any motion type.
118
+ * A scalar applies one speed to every entity; a `[min, max]` range gives each
119
+ * entity a seeded speed within the range (like `speed`/`alpha`) — confetti,
120
+ * tumbling debris, foliage. Existing scalar specs are unaffected (the range
121
+ * form draws one extra seeded value, only when present).
118
122
  */
119
- spin?: number;
123
+ spin?: number | [number, number];
120
124
  /**
121
125
  * Sinusoidal size breathing, parallel to `pulse` for opacity. `amp` is a
122
126
  * fraction of base size (0.3 = ±30 %). `period` in ms with the same
@@ -703,12 +707,33 @@ declare function luminanceGrid(spec: SaverSpec, opts?: LuminanceGridOptions): Lu
703
707
  * Encode a luminance grid as a braille "image": each output character is a 2×4
704
708
  * pixel cell, ordered-dithered so mid-tones render as dot density. A grid of
705
709
  * 80×48 becomes 12 lines of 40 chars — a picture a text model can read whole.
706
- *
707
- * Auto-exposure: most savers live in the bottom of the luminance range, so the
708
- * cells are normalized (min → 98th percentile) before dithering — like a
709
- * camera exposing for the scene. Stats on the grid itself stay raw.
710
710
  */
711
711
  declare function renderBrailleMap(grid: LuminanceGrid): string;
712
+ /**
713
+ * Encode a luminance grid as a one-char-per-cell ASCII density map. Unlike the
714
+ * braille map (compact, 2×4 dots/char) this is 1:1 with the grid, so pairing it
715
+ * with a larger `cols`/`rows` gives a non-vision agent a genuinely higher-detail
716
+ * read: 120×48 resolves concentric rings and text blocks braille smears into a
717
+ * blob. Shares the braille map's auto-exposure so both views agree.
718
+ */
719
+ declare function renderDensityMap(grid: LuminanceGrid): string;
720
+ interface TextSpriteInfo {
721
+ layerIndex: number;
722
+ key: string | undefined;
723
+ /** The literal strings this layer draws (glyphs are invisible in the maps). */
724
+ strings: string[];
725
+ /** How many entities carry text. */
726
+ count: number;
727
+ /** Representative rendered glyph height in px at the sample viewport. */
728
+ sizePx: number;
729
+ }
730
+ /**
731
+ * List every text layer's actual strings and rendered size. No grid resolution
732
+ * renders glyphs legibly, so a non-vision agent otherwise can't tell whether the
733
+ * words it wrote are on screen, how big, or whether they'll overlap. This closes
734
+ * that blind spot analytically.
735
+ */
736
+ declare function textSprites(spec: SaverSpec, opts?: PerceiveOptions): TextSpriteInfo[];
712
737
  interface DominanceEntry {
713
738
  rank: number;
714
739
  layerIndex: number;
@@ -789,8 +814,14 @@ interface SceneDiff {
789
814
  declare function diffScenes(a: SaverSpec, b: SaverSpec, opts?: LuminanceGridOptions): SceneDiff;
790
815
  interface ScenePerception {
791
816
  t: number;
792
- /** The picture: a braille luminance map (each char = 2×4 pixels). */
817
+ /** The picture: a braille luminance map (each char = 2×4 pixels), compact. */
793
818
  braille: string;
819
+ /**
820
+ * A one-char-per-cell ASCII density map at the grid resolution — higher
821
+ * fidelity than braille for structure (rings, grids, text blocks). Pair with
822
+ * a larger `cols`/`rows` for a sharper read.
823
+ */
824
+ density: string;
794
825
  coverage: number;
795
826
  meanLuminance: number;
796
827
  centroid: {
@@ -801,12 +832,15 @@ interface ScenePerception {
801
832
  colProfile: number[];
802
833
  dominance: DominanceEntry[];
803
834
  motion: LayerMotionStats[];
835
+ /** Literal strings + sizes of any text layers (glyphs don't show in the maps). */
836
+ text: TextSpriteInfo[];
804
837
  advisories: ReturnType<typeof adviseSpec>;
805
838
  }
806
839
  /**
807
840
  * Everything a non-vision agent needs to "see" a spec in one deterministic
808
- * call: the braille picture, composition transects, dominance ranking, motion
809
- * stats, and advisories. Intended as the payload behind an MCP previewScene.
841
+ * call: the braille picture, a higher-res density map, composition transects,
842
+ * dominance ranking, motion stats, the literal text on screen, and advisories.
843
+ * Intended as the payload behind an MCP previewScene.
810
844
  */
811
845
  declare function perceiveScene(spec: SaverSpec, opts?: LuminanceGridOptions): ScenePerception;
812
846
 
@@ -929,4 +963,4 @@ declare const EXAMPLE_SPECS: SaverSpec[];
929
963
  /** Lookup by saver id. */
930
964
  declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
931
965
 
932
- export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COMETS_SPEC, CONSTELLATION_SPEC, type CycleSpec, DASHBOARD_SPEC, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SPECS, type Entity, type GradientStop, LANTERNS_SPEC, LIMITS, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, type MotionSpec, ORRERY_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PerceiveOptions, type Placed, RAIN_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SNOWFALL_SPEC, type SaverSpec, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type SpecError, type SpecWarning, type SpriteSpec, type SteerDelta, type ValidationResult, WARP_NEAR, WARP_TUNNEL_SPEC, type WanderOsc, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSpec, buildEntities, compileSaver, describeScene, diffScenes, dominanceRanking, easeSmooth, headingAt, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, motionStats, perceiveScene, positionAt, renderBrailleMap, resolveSpecPath, rotationAt, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, validateSpec };
966
+ export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COMETS_SPEC, CONSTELLATION_SPEC, type CycleSpec, DASHBOARD_SPEC, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SPECS, type Entity, type GradientStop, LANTERNS_SPEC, LIMITS, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, type MotionSpec, ORRERY_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PerceiveOptions, type Placed, RAIN_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SNOWFALL_SPEC, type SaverSpec, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type SpecError, type SpecWarning, type SpriteSpec, type SteerDelta, type TextSpriteInfo, type ValidationResult, WARP_NEAR, WARP_TUNNEL_SPEC, type WanderOsc, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSpec, buildEntities, compileSaver, describeScene, diffScenes, dominanceRanking, easeSmooth, headingAt, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, motionStats, perceiveScene, positionAt, renderBrailleMap, renderDensityMap, resolveSpecPath, rotationAt, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textSprites, validateSpec };
package/dist/index.js CHANGED
@@ -250,8 +250,15 @@ function validateLayer(layer, path, err, warn, spec) {
250
250
  }
251
251
  }
252
252
  if (layer.spin !== void 0) {
253
- if (!isNum(layer.spin)) err(`${path}.spin`, "must be a number (degrees/sec)");
254
- else if (Math.abs(layer.spin) > LIMITS.maxSpin) err(`${path}.spin`, `must be within \xB1${LIMITS.maxSpin} deg/sec`);
253
+ if (isRange(layer.spin)) {
254
+ if (Math.abs(layer.spin[0]) > LIMITS.maxSpin || Math.abs(layer.spin[1]) > LIMITS.maxSpin) {
255
+ err(`${path}.spin`, `each end of the range must be within \xB1${LIMITS.maxSpin} deg/sec`);
256
+ }
257
+ } else if (!isNum(layer.spin)) {
258
+ err(`${path}.spin`, "must be a number or a [min,max] range (degrees/sec)");
259
+ } else if (Math.abs(layer.spin) > LIMITS.maxSpin) {
260
+ err(`${path}.spin`, `must be within \xB1${LIMITS.maxSpin} deg/sec`);
261
+ }
255
262
  }
256
263
  if (layer.grow !== void 0) {
257
264
  if (!isObj(layer.grow)) err(`${path}.grow`, "must be an object");
@@ -749,8 +756,10 @@ function buildEntities(layer, rng, w, h, scale = 1, countScale = 1) {
749
756
  pulsePeriod: layer.pulse?.period ?? 1e3,
750
757
  pulsePhase: layer.pulse ? rng.range(0, Math.PI * 2) : 0,
751
758
  // New draws AFTER pulsePhase — guarded so existing specs keep identical streams.
752
- spinSpeed: layer.spin ? layer.spin * Math.PI / 180 : 0,
753
- // deg/sec → rad/sec
759
+ // Scalar spin: no draw here, one phase draw below (unchanged). Range spin
760
+ // (new): one seeded speed draw here, then the phase draw — array form is new
761
+ // so the extra draw can't perturb any existing spec's entity stream.
762
+ spinSpeed: typeof layer.spin === "number" ? layer.spin * Math.PI / 180 : Array.isArray(layer.spin) ? rng.range(layer.spin[0], layer.spin[1]) * Math.PI / 180 : 0,
754
763
  spinPhase: layer.spin ? rng.range(0, Math.PI * 2) : 0,
755
764
  growAmp: layer.grow?.amp ?? 0,
756
765
  growPeriod: layer.grow?.period ?? 1e3,
@@ -1875,6 +1884,8 @@ function describeScene(spec, options = {}) {
1875
1884
 
1876
1885
  // src/perceive.ts
1877
1886
  import { createRng as createRng4 } from "@idle-screens/core";
1887
+ var GLOW_SPREAD = 2.4;
1888
+ var LINE_SALIENCE = 3.5;
1878
1889
  function buildScene(spec, opts) {
1879
1890
  const { width: w, height: h } = opts.viewport ?? { width: 1920, height: 1080 };
1880
1891
  const seed = opts.seed ?? spec.seed ?? 42;
@@ -2013,12 +2024,16 @@ function luminanceGrid(spec, opts = {}) {
2013
2024
  halfX = box.halfX;
2014
2025
  halfY = box.halfY;
2015
2026
  }
2016
- const c0 = Math.max(0, Math.floor((centerX - halfX) / cellW));
2017
- const c1 = Math.min(cols - 1, Math.floor((centerX + halfX) / cellW));
2018
- const r0 = Math.max(0, Math.floor((centerY - halfY) / cellH));
2019
- const r1 = Math.min(rows - 1, Math.floor((centerY + halfY) / cellH));
2020
2027
  const circular = s.kind === "circle" || s.kind === "ring";
2021
- const soft = s.kind === "circle" && s.soft;
2028
+ const soft = s.kind === "circle" && !!s.soft;
2029
+ const additive = layer.blend === "lighter" || layer.blend === "screen";
2030
+ const glow = soft && additive;
2031
+ const reachX = glow ? halfX * GLOW_SPREAD : halfX;
2032
+ const reachY = glow ? halfY * GLOW_SPREAD : halfY;
2033
+ const c0 = Math.max(0, Math.floor((centerX - reachX) / cellW));
2034
+ const c1 = Math.min(cols - 1, Math.floor((centerX + reachX) / cellW));
2035
+ const r0 = Math.max(0, Math.floor((centerY - reachY) / cellH));
2036
+ const r1 = Math.min(rows - 1, Math.floor((centerY + reachY) / cellH));
2022
2037
  const inkWeight = s.kind === "text" || s.kind === "emoji" ? 0.55 : 1;
2023
2038
  for (let r = r0; r <= r1; r++) {
2024
2039
  for (let c = c0; c <= c1; c++) {
@@ -2026,12 +2041,23 @@ function luminanceGrid(spec, opts = {}) {
2026
2041
  const dy = (r + 0.5) * cellH - centerY;
2027
2042
  if (circular) {
2028
2043
  const d = Math.sqrt(dx * dx + dy * dy);
2029
- if (d > halfX + Math.min(cellW, cellH) / 2) continue;
2044
+ const reach = glow ? reachX : halfX;
2045
+ if (d > reach + Math.min(cellW, cellH) / 2) continue;
2030
2046
  if (s.kind === "ring") {
2031
2047
  const strokeW = (s.width ?? (scale === 1 ? 2 : 2e-3)) * scale;
2032
2048
  if (d < halfX - strokeW) continue;
2033
2049
  }
2034
- const wgt = soft ? Math.max(0.1, 1 - d / Math.max(halfX, 1e-6)) : 1;
2050
+ let wgt;
2051
+ if (glow) {
2052
+ const core = halfX;
2053
+ if (d <= core) wgt = 1;
2054
+ else {
2055
+ const f = Math.max(0, 1 - (d - core) / Math.max(reachX - core, 1e-6));
2056
+ wgt = f * f;
2057
+ }
2058
+ } else {
2059
+ wgt = soft ? Math.max(0.1, 1 - d / Math.max(halfX, 1e-6)) : 1;
2060
+ }
2035
2061
  compose(r * cols + c, lum, a * wgt, layer.blend);
2036
2062
  } else {
2037
2063
  compose(r * cols + c, lum, a * inkWeight, layer.blend);
@@ -2107,11 +2133,14 @@ var DOT_BIT = [
2107
2133
  [4, 32],
2108
2134
  [64, 128]
2109
2135
  ];
2110
- function renderBrailleMap(grid) {
2136
+ function autoExpose(grid) {
2111
2137
  const sorted = [...grid.cells].sort((a, b) => a - b);
2112
2138
  const lo = sorted[0];
2113
2139
  const hi = Math.max(sorted[Math.floor(sorted.length * 0.98)], lo + 0.08);
2114
- const expose = (v) => Math.pow(Math.max(0, Math.min(1, (v - lo) / (hi - lo))), 0.8);
2140
+ return (v) => Math.pow(Math.max(0, Math.min(1, (v - lo) / (hi - lo))), 0.8);
2141
+ }
2142
+ function renderBrailleMap(grid) {
2143
+ const expose = autoExpose(grid);
2115
2144
  const outRows = [];
2116
2145
  for (let br = 0; br < Math.floor(grid.rows / 4); br++) {
2117
2146
  let line = "";
@@ -2129,6 +2158,34 @@ function renderBrailleMap(grid) {
2129
2158
  }
2130
2159
  return outRows.join("\n");
2131
2160
  }
2161
+ var DENSITY_RAMP = " .:-=+*#%@";
2162
+ function renderDensityMap(grid) {
2163
+ const expose = autoExpose(grid);
2164
+ const ramp = DENSITY_RAMP;
2165
+ const lines = [];
2166
+ for (let r = 0; r < grid.rows; r++) {
2167
+ let line = "";
2168
+ for (let c = 0; c < grid.cols; c++) {
2169
+ const v = expose(grid.cells[r * grid.cols + c]);
2170
+ line += ramp[Math.min(ramp.length - 1, Math.floor(v * ramp.length))];
2171
+ }
2172
+ lines.push(line);
2173
+ }
2174
+ return lines.join("\n");
2175
+ }
2176
+ function textSprites(spec, opts = {}) {
2177
+ const scene = buildScene(spec, opts);
2178
+ const t = opts.t ?? 5e3;
2179
+ const out = [];
2180
+ scene.layers.forEach(({ layer, entities }, layerIndex) => {
2181
+ if (layer.sprite.kind !== "text") return;
2182
+ const s = layer.sprite;
2183
+ const m = s.font ? /(\d+(?:\.\d+)?)px/.exec(s.font) : null;
2184
+ const sizePx = m ? Number(m[1]) : entities[0] ? sizeAt(entities[0], t) : 0;
2185
+ out.push({ layerIndex, key: layer.key, strings: [...s.strings], count: entities.length, sizePx: Math.round(sizePx) });
2186
+ });
2187
+ return out;
2188
+ }
2132
2189
  function dominanceRanking(spec, opts = {}) {
2133
2190
  const scene = buildScene(spec, opts);
2134
2191
  const t = opts.t ?? 5e3;
@@ -2148,8 +2205,8 @@ function dominanceRanking(spec, opts = {}) {
2148
2205
  const s = layer.sprite;
2149
2206
  let entArea;
2150
2207
  if (s.kind === "circle") entArea = Math.PI * (sz / 2) * (sz / 2);
2151
- else if (s.kind === "ring") entArea = Math.PI * sz * ((s.width ?? (scale === 1 ? 2 : 2e-3)) * scale);
2152
- else if (s.kind === "streak") entArea = sz * ((s.width ?? (scale === 1 ? 2 : 2e-3)) * scale);
2208
+ else if (s.kind === "ring") entArea = Math.PI * sz * ((s.width ?? (scale === 1 ? 2 : 2e-3)) * scale) * LINE_SALIENCE;
2209
+ else if (s.kind === "streak") entArea = sz * ((s.width ?? (scale === 1 ? 2 : 2e-3)) * scale) * LINE_SALIENCE;
2153
2210
  else if (s.kind === "rect") {
2154
2211
  const h2 = e.size2 !== void 0 ? e.size2 * (e.size > 0 ? sz / e.size : 1) : sz;
2155
2212
  entArea = sz * h2;
@@ -2166,7 +2223,7 @@ function dominanceRanking(spec, opts = {}) {
2166
2223
  const edges = linkEdges(layer.links, positions, layer.links.maxDist * scale, layer.wrap !== false && motionWraps, w, h);
2167
2224
  const la = (layer.links.alpha ?? 0.6) * lifeA;
2168
2225
  const lw = (layer.links.width ?? 1) * scale;
2169
- for (const edge of edges) area += edge.dist * lw * la;
2226
+ for (const edge of edges) area += edge.dist * lw * la * LINE_SALIENCE;
2170
2227
  }
2171
2228
  const meanLuma = entities.length ? lumAcc / entities.length : 0;
2172
2229
  const contrast = Math.abs(meanLuma - bgMean) + 0.1;
@@ -2274,6 +2331,7 @@ function perceiveScene(spec, opts = {}) {
2274
2331
  return {
2275
2332
  t: opts.t ?? 5e3,
2276
2333
  braille: renderBrailleMap(grid),
2334
+ density: renderDensityMap(grid),
2277
2335
  coverage: grid.coverage,
2278
2336
  meanLuminance: grid.meanLuminance,
2279
2337
  centroid: grid.centroid,
@@ -2281,6 +2339,7 @@ function perceiveScene(spec, opts = {}) {
2281
2339
  colProfile: grid.colProfile,
2282
2340
  dominance: dominanceRanking(spec, opts),
2283
2341
  motion: motionStats(spec, opts),
2342
+ text: textSprites(spec, opts),
2284
2343
  advisories: adviseSpec(spec, opts.viewport ?? { width: 1920, height: 1080 })
2285
2344
  };
2286
2345
  }
@@ -3616,6 +3675,7 @@ export {
3616
3675
  perceiveScene,
3617
3676
  positionAt,
3618
3677
  renderBrailleMap,
3678
+ renderDensityMap,
3619
3679
  resolveSpecPath,
3620
3680
  rotationAt,
3621
3681
  sizeAt,
@@ -3623,6 +3683,7 @@ export {
3623
3683
  spriteVariants,
3624
3684
  steerablePaths,
3625
3685
  structuralSignature,
3686
+ textSprites,
3626
3687
  validateSpec
3627
3688
  };
3628
3689
  //# sourceMappingURL=index.js.map