figureone 1.7.0 → 1.8.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/llms-full.txt CHANGED
@@ -20,6 +20,10 @@ Colors are `[r, g, b, a]` with values 0-1. Default scene is `[-1, -1, 2, 2]` (x,
20
20
  | lineWidth | number | | Default line width |
21
21
  | length | number | | Default primary dimension |
22
22
  | backgroundColor | TypeColor | | Background color |
23
+ | textStyle | `'italic'` \| `'normal'` | `'italic'` | Default equation text style (also settable on FigurePrimitives and Equation) |
24
+ | antialias | boolean | `true` | Enable WebGL anti-aliasing |
25
+ | atlasScale | number | `2` | GL text atlas texture resolution relative to 1:1 pixel mapping |
26
+ | onWebGLUnavailable | () => void | | Callback fired once during construction if no WebGL context is available (figure is still created but renders nothing) |
23
27
 
24
28
  ### Key Figure Methods
25
29
 
@@ -45,6 +49,11 @@ figure.resize(); // Resize after container change
45
49
  figure.animateNextFrame(); // Request next animation frame
46
50
  figure.isAnimating(); // Check if any animation running
47
51
 
52
+ // WebGL context state
53
+ figure.webglAvailable; // false if no live WebGL context (e.g. context lost)
54
+ figure.notifications.add('contextLost', () => { /* context removed by browser */ });
55
+ figure.notifications.add('contextRestored', () => { /* context returned */ });
56
+
48
57
  // Coordinate transforms
49
58
  figure.transformPoint(point, fromSpace, toSpace);
50
59
  figure.spaceTransformMatrix(fromSpace, toSpace);
@@ -92,6 +101,7 @@ Path to figure element(s) within a collection. Supports multiple formats:
92
101
  | underline | boolean \| object | `false` | Underline options |
93
102
  | outline | boolean \| object | `false` | Outline options |
94
103
  | render | `'gl'` \| `'2d'` \| `'html'` | `'gl'` | Render target |
104
+ | atlasId | string | | Share a single GL atlas texture across text elements with different sizes (elements with the same `atlasId` reuse one atlas) |
95
105
 
96
106
  ### OBJ_Texture
97
107
 
@@ -119,6 +129,11 @@ Path to figure element(s) within a collection. Supports multiple formats:
119
129
  | defaultColor | TypeColor | | Color when undimmed |
120
130
  | scenarios | OBJ_Scenarios | | Named presets |
121
131
  | scene | Scene \| OBJ_Scene | | Custom scene |
132
+ | isFormIgnored | boolean | `false` | Exclude from equation form changes — skipped by form layout, show/hide, transform/color sets, and `elementMods`, so its user-applied transform, color, and visibility persist across `showForm` / `goToForm` (contributes zero size to layout) |
133
+ | allowSetColor | `'all'` \| `'opacity'` \| `'none'` | `'all'` | Freeze color: `'opacity'` allows only the alpha channel to change; `'none'` blocks all `setColor` |
134
+ | ignoreSetColor | string \| string[] | `[]` | Source label(s) whose tagged `setColor` commands are ignored (e.g. the equation's default `'form'` color cascade), while explicit/untagged commands (`dim`/`undim`, direct `setColor`) are still honored |
135
+
136
+ These element properties are also settable at runtime; `setColor(color, setDefault?, from?)` takes an optional `from` source label that is matched against `ignoreSetColor`.
122
137
 
123
138
  ### OBJ_Collection
124
139
 
@@ -1252,6 +1267,26 @@ Array: `{ color: [content, [r, g, b, a]] }`
1252
1267
  | color | TypeColor | | Color to apply |
1253
1268
  | fullContentBounds | boolean | `false` | Use full bounds |
1254
1269
 
1270
+ #### EQN_Opacity (`opacity`)
1271
+ Array: `{ opacity: [content, opacity_value] }`. Wraps a phrase in an opacity multiplier that cascades multiplicatively through nested `opacity` wrappers (outer `0.5` × inner `0.5` = `0.25` on inner content). The cascaded opacity is assigned to each wrapped element whenever the form is shown, overriding externally-set element opacity — set `ignoreOpacity: true` on the form to suppress this and preserve external opacities.
1272
+
1273
+ | Property | Type | Default | Description |
1274
+ |---|---|---|---|
1275
+ | content | TypeEquationPhrase | | Content |
1276
+ | opacity | number | | Opacity multiplier in range `[0, 1]` |
1277
+ | fullContentBounds | boolean | `false` | Use full bounds |
1278
+
1279
+ #### EQN_Front (`front`) / EQN_Back (`back`)
1280
+ Array: `{ front: [content, num] }` / `{ back: [content, num] }`. Reorder a phrase's elements within the equation's draw stack, per-form. All elements in `content` move together as a group, keeping their relative draw order. Applied whenever the form is shown, relative to the equation's natural definition order, so each form deterministically defines its own stacking. The same operations are available as `front` / `back` element mods (e.g. `elementMods: { a: { back: {} } }`).
1281
+
1282
+ | Property | Type | Default | Description |
1283
+ |---|---|---|---|
1284
+ | content | TypeEquationPhrase | | Content |
1285
+ | num | number | | `front`: places to move forward (positive) or `\|num\|` places behind the front (negative). `back`: places to move back (positive) or `\|num\|` places ahead of the back (negative). With `before`/`after`, it's the offset from the anchor (default `0`). If omitted with no anchor, moves fully to front/back |
1286
+ | before | string \| string[] | | Anchor element name(s) — position the group just before (behind) the most-back anchor |
1287
+ | after | string \| string[] | | Anchor element name(s) — position the group just after (in front of) the most-front anchor |
1288
+ | fullContentBounds | boolean | `false` | Use full bounds |
1289
+
1255
1290
  #### EQN_Container (`container`)
1256
1291
  Array: `{ container: [content, width] }`
1257
1292
 
@@ -1452,10 +1487,17 @@ forms: {
1452
1487
  scale: { elementName: 1.5 }, // Per-element target scales during animation
1453
1488
  fromPrev: { elementName: 'otherElement' }, // Animate from another element's position
1454
1489
  fromNext: { elementName: 'otherElement' }, // Animate from another element's position (reverse)
1490
+ ignoreColor: false, // true preserves externally-set element colors (suppresses color cascade)
1491
+ ignoreOpacity: false, // true preserves externally-set element opacities (suppresses opacity cascade)
1492
+ elementMods: { // Per-element property mods applied when form shows
1493
+ elementName: { back: {} }, // e.g. front/back reorder mods (see EQN_Front/EQN_Back)
1494
+ },
1455
1495
  },
1456
1496
  }
1457
1497
  ```
1458
1498
 
1499
+ Every equation layout function (container, frac, matrix, brac, annotate, color, opacity, front, back, etc.) also accepts an optional `name` property — purely a label with no layout effect, used to address the function's sub-tree via `eqn.getFunctionElements(name)`.
1500
+
1459
1501
  ### Forms and Animation
1460
1502
 
1461
1503
  ```js
@@ -1474,9 +1516,24 @@ const eqn = figure.add({
1474
1516
  });
1475
1517
 
1476
1518
  eqn.showForm('1');
1519
+ eqn.showForm('1', false); // notify=false suppresses the formChanged event for this call
1477
1520
  eqn.goToForm({ form: '2', delay: 1, duration: 1.5, animate: 'move' });
1478
1521
  eqn.nextForm(); // Navigate formSeries
1479
1522
  eqn.prevForm();
1523
+
1524
+ // Element lookup within forms
1525
+ eqn.getFormElements(formName, includeHidden?); // All elements in a form
1526
+ eqn.getPhraseElements(phrase); // All elements in a phrase
1527
+ // Elements inside a named layout function (any function tagged with `name`).
1528
+ // (name, formName? = current, mode? = 'all', includeHidden? = false).
1529
+ // mode 'first' = first match; 'all' = de-duplicated union across all matches.
1530
+ eqn.getFunctionElements('myFrac');
1531
+ ```
1532
+
1533
+ `Equation` publishes a `formChanged` notification whenever the displayed form may have changed, with payload `{ phase, form, fromForm?, progress? }`. `phase` is one of: `'showForm'` (form set via `showForm`; suppress with `notify: false`), `'goToFormStart'`, `'goToFormStep'` (per animation frame, with `progress` 0–1; a final step with `progress: 1` precedes the end), `'goToFormEnd'`. `fromForm` is present only on the `goToForm*` phases.
1534
+
1535
+ ```js
1536
+ eqn.notifications.add('formChanged', ({ phase, form, progress }) => { /* drive UI */ });
1480
1537
  ```
1481
1538
 
1482
1539
  ### Phrases (reusable sub-expressions)
@@ -1488,6 +1545,14 @@ eqn.prevForm();
1488
1545
  }
1489
1546
  ```
1490
1547
 
1548
+ ### LaTeX Parsing
1549
+ `Fig.latexToFigureOne(latex)` converts a LaTeX math expression into `{ elements, form }` for use in an equation definition (basic support).
1550
+
1551
+ ```js
1552
+ const { elements, form } = Fig.latexToFigureOne('\\frac{a}{b} = c');
1553
+ figure.add({ make: 'equation', elements, forms: { base: form } });
1554
+ ```
1555
+
1491
1556
  ---
1492
1557
 
1493
1558
  ## 7. Animation System
package/llms.txt CHANGED
@@ -24,6 +24,8 @@ figure.add({ make: 'triangle', width: 0.5, height: 0.5, color: [1, 0, 0, 1] });
24
24
 
25
25
  Colors are `[r, g, b, a]` with values 0-1. Coordinates default to a scene of `[-1, -1, 2, 2]` (x, y, width, height). Override with `new Fig.Figure({ scene: [-2, -2, 4, 4] })`.
26
26
 
27
+ Other `Fig.Figure` options: `textStyle` (`'italic'` default | `'normal'` — default equation text style), `antialias` (default `true`), `atlasScale` (GL text atlas resolution, default `2`), `onWebGLUnavailable` (callback if no WebGL context). Check `figure.webglAvailable` and subscribe to `contextLost` / `contextRestored` notifications for runtime WebGL context transitions.
28
+
27
29
  ## Core Concepts
28
30
 
29
31
  - **Figure** — top-level container that manages all elements and rendering
@@ -105,6 +107,9 @@ All elements accept these common options alongside shape-specific ones:
105
107
  - `color` — `[r, g, b, a]`
106
108
  - `position` — `[x, y]` shorthand for translation
107
109
  - `rotation` — rotation in radians
110
+ - `isFormIgnored` — exclude from equation form changes (its transform/color/visibility persist across `showForm`/`goToForm`)
111
+ - `allowSetColor` — `'all'` (default), `'opacity'` (alpha only), or `'none'` to freeze color
112
+ - `ignoreSetColor` — source label(s) whose tagged `setColor` is ignored (e.g. the equation's `'form'` color cascade), while explicit commands still apply
108
113
 
109
114
  When `options` is used, shape-specific properties go inside it:
110
115
 
@@ -256,9 +261,14 @@ Any element type can be created inline in forms using `make`:
256
261
  | `prodOf` | `{ prodOf: { symbol, content, from, to } }` | Product |
257
262
  | `scale` | `{ scale: [content, scale_factor] }` | Scale content |
258
263
  | `color` | `{ color: [content, [r,g,b,a]] }` | Color content |
264
+ | `opacity` | `{ opacity: [content, 0.3] }` | Opacity multiplier (cascades multiplicatively through nesting) |
265
+ | `front` | `{ front: [content, num] }` | Bring content forward in draw stack (per-form); supports `before`/`after` anchors |
266
+ | `back` | `{ back: [content, num] }` | Send content back in draw stack (per-form); supports `before`/`after` anchors |
259
267
  | `container` | `{ container: { content, width, ... } }` | Fixed-size container |
260
268
  | `lines` | `{ lines: { content: [...], justify } }` | Multi-line layout |
261
269
 
270
+ Any layout function also accepts a `name` label to address its elements via `eqn.getFunctionElements(name)`. Form objects accept `ignoreColor` / `ignoreOpacity` to preserve externally-set element color/opacity (suppress the cascade).
271
+
262
272
  ### Symbols
263
273
 
264
274
  Define symbols in `elements` with `{ symbol: 'type' }`:
@@ -302,9 +312,12 @@ const eqn = figure.add({
302
312
  });
303
313
 
304
314
  eqn.showForm('1');
315
+ eqn.showForm('1', false); // notify=false suppresses the formChanged event
305
316
  eqn.goToForm({ form: '2', delay: 1, duration: 1.5, animate: 'move' });
306
317
  ```
307
318
 
319
+ Equation publishes a `formChanged` notification with payload `{ phase, form, fromForm?, progress? }`, where `phase` is `'showForm'`, `'goToFormStart'`, `'goToFormStep'` (with `progress` 0–1), or `'goToFormEnd'`. Use `eqn.getFunctionElements(name)` to fetch elements inside a named layout function.
320
+
308
321
  Use `formSeries` to define an ordered list of forms, then navigate with `eqn.nextForm()` and `eqn.prevForm()`.
309
322
 
310
323
  Use `phrases` to define reusable sub-expressions:
@@ -529,6 +542,10 @@ new Fig.Transform() // Transform builder
529
542
  .scale(sx, sy)
530
543
  Fig.round(value, precision) // Round to N decimal places
531
544
 
545
+ // Convert a LaTeX math expression to an equation form (basic support)
546
+ const { elements, form } = Fig.latexToFigureOne('\\frac{a}{b} = c');
547
+ figure.add({ make: 'equation', elements, forms: { base: form } });
548
+
532
549
  // Color format: [r, g, b, a] with values 0 to 1
533
550
  // [1, 0, 0, 1] = red, [0, 0, 1, 0.5] = semi-transparent blue
534
551
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "figureone",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Draw, animate and interact with shapes, text, plots and equations in Javascript. Create interactive slide shows, and interactive videos.",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -42,6 +42,12 @@ declare class GLObject extends DrawingObject {
42
42
  loadColor: TypeColor;
43
43
  [key: string]: any;
44
44
  } | null;
45
+ maskTextures: Array<{
46
+ id: string;
47
+ src: string;
48
+ loadColor: TypeColor;
49
+ uniformName: string;
50
+ }>;
45
51
  attributes: {
46
52
  [attributeName: string]: {
47
53
  buffer: WebGLBuffer | null;
@@ -85,6 +91,18 @@ declare class GLObject extends DrawingObject {
85
91
  * Buffer a texture for the shape to be painted with.
86
92
  */
87
93
  addTexture(location: string, mapFrom?: Rect, mapTo?: Rect, mapToBuffer?: string, points?: Array<number>, repeat?: boolean, onLoad?: null | (() => void), loadColor?: TypeColor): void;
94
+ /**
95
+ * Buffer a mask texture for the `textureMap` color mode. Each call appends a
96
+ * mask, bound to the next u_mask{i} sampler. Masks share the base texture's
97
+ * coordinates, so only a source and load color are needed. The default load
98
+ * color is fully transparent so that, until the mask loads, no region is
99
+ * recolored and the base texture shows through unchanged.
100
+ *
101
+ * An empty `location` registers a transparent placeholder, which keeps the
102
+ * u_mask{i} indexing aligned with the tint blocks when a caller supplies an
103
+ * invalid/missing mask in a positional list (the slot becomes a no-op).
104
+ */
105
+ addMaskTexture(location?: string, loadColor?: TypeColor): void;
88
106
  updateTexture(data: HTMLImageElement): void;
89
107
  initTexture(force?: boolean): void;
90
108
  createTextureMap(xMinGL?: number, xMaxGL?: number, yMinGL?: number, yMaxGL?: number, xMinTex?: number, xMaxTex?: number, yMinTex?: number, yMaxTex?: number): void;
@@ -224,6 +224,30 @@ export type OBJ_Texture = {
224
224
  repeat?: boolean;
225
225
  onLoad?: () => void;
226
226
  };
227
+ /**
228
+ * Mask texture used by the `gl` primitive to recolor regions of a base
229
+ * `texture`. A mask shares the base texture's coordinates, so it must be the
230
+ * same dimensions and aligned with the base image. Each of a mask's `r`, `g`,
231
+ * `b` and `a` channels selects a region recolored by an entry of the
232
+ * primitive's `tints` option.
233
+ *
234
+ * Supply a single mask with `mask`, or several with `masks` (an array). Mask `m`
235
+ * (0-based) uses `tints[4m]`, `tints[4m + 1]`, `tints[4m + 2]` and
236
+ * `tints[4m + 3]` for its r, g, b and a channels - so each mask adds four
237
+ * recolorable regions. A single mask costs one extra texture fetch and four
238
+ * mixes; each additional mask adds one fetch and four mixes.
239
+ *
240
+ * @property {string} [src] url of the mask image
241
+ * @property {TypeColor} [loadColor] color shown while the mask loads
242
+ * (`[0, 0, 0, 0]` - fully transparent, so nothing is recolored until the mask
243
+ * has loaded)
244
+ * @interface
245
+ * @group Shaders
246
+ */
247
+ export type OBJ_TextureMask = {
248
+ src?: string;
249
+ loadColor?: TypeColor;
250
+ };
227
251
  /**
228
252
  * Pulse options object
229
253
  *
@@ -443,6 +467,12 @@ export type TypeText = 'gl' | '2d';
443
467
  * {@link OBJ_FragmentShader} for names of attributes and uniforms used in the
444
468
  * shaders, and when they are used.
445
469
  *
470
+ * A texture can be recolored by region using one or more masks (see the mask
471
+ * examples below):
472
+ *
473
+ * ![](./apiassets/gl_mask.png)
474
+ * ![](./apiassets/gl_mask2.png)
475
+ *
446
476
  * @property {TypeGLPrimitive} [glPrimitive]
447
477
  * @property {TypeVertexShader} [vertexShader]
448
478
  * @property {TypeFragmentShader} [fragmentShader]
@@ -585,6 +615,46 @@ export type TypeText = 'gl' | '2d';
585
615
  * },
586
616
  * ],
587
617
  * });
618
+ *
619
+ * @example
620
+ * // Recolor regions of a texture with a mask. The mask image marks regions to
621
+ * // recolor in its red, green, blue and alpha channels, which map to tints 0,
622
+ * // 1, 2 and 3. Unmasked pixels keep the base texture's color.
623
+ * const p = figure.add({
624
+ * make: 'gl',
625
+ * vertices: [-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5],
626
+ * numVertices: 6,
627
+ * texture: {
628
+ * src: './base.png',
629
+ * coords: [0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1],
630
+ * loadColor: [0, 0, 0, 0],
631
+ * },
632
+ * mask: { src: './mask.png' },
633
+ * tints: [[1, 0, 0, 1], [0, 0, 1, 1]],
634
+ * });
635
+ * // Change the first region's color at runtime
636
+ * p.custom.setTint(0, [0, 1, 0, 1]);
637
+ *
638
+ * @example
639
+ * // Recolor with two masks. Each mask adds four regions (its r, g, b, a
640
+ * // channels), so mask 0 uses tints 0-3 and mask 1 uses tints 4-7. Here mask 0
641
+ * // recolors three circles (tints 0, 1, 2) and mask 1 recolors a bar (tint 4).
642
+ * figure.add({
643
+ * make: 'gl',
644
+ * vertices: [-0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5],
645
+ * numVertices: 6,
646
+ * texture: {
647
+ * src: './base.png',
648
+ * coords: [0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1],
649
+ * loadColor: [0, 0, 0, 0],
650
+ * },
651
+ * masks: [{ src: './mask.png' }, { src: './mask1.png' }],
652
+ * tints: [
653
+ * [1, 0, 0, 1], [0, 0.6, 0, 1], [0, 0, 1, 1], null, // mask 0: circles
654
+ * [0.6, 0, 0.8, 1], // mask 1: bar
655
+ * ],
656
+ * });
657
+ *
588
658
  * @interface
589
659
  * @group Shaders
590
660
  */
@@ -595,6 +665,9 @@ export type OBJ_GenericGL = {
595
665
  attributes?: Array<OBJ_GLAttribute>;
596
666
  uniforms?: Array<OBJ_GLUniform>;
597
667
  texture?: OBJ_Texture;
668
+ mask?: OBJ_TextureMask;
669
+ masks?: Array<OBJ_TextureMask>;
670
+ tints?: Array<TypeColor | null>;
598
671
  dimension?: 2 | 3;
599
672
  light?: 'directional' | 'point' | null;
600
673
  vertices?: OBJ_GLVertexBuffer;
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Number of recolorable regions a single mask texture provides in the
3
+ * `textureMap` color mode - one per channel (r, g, b, a). N masks therefore
4
+ * define `CHANNELS_PER_MASK * N` tints.
5
+ * @group Shaders
6
+ */
7
+ export declare const CHANNELS_PER_MASK = 4;
1
8
  /**
2
9
  * Options used to compose vertex shader source code.
3
10
  *
@@ -53,14 +60,14 @@
53
60
  * to fragment shader used when `light = 'point'`
54
61
  *
55
62
  * @property {2 | 3} [dimension] (`2`)
56
- * @property {'vertex' | 'uniform' | 'texture'} [color] (`uniform`)
63
+ * @property {'vertex' | 'uniform' | 'texture' | 'textureMap'} [color] (`uniform`)
57
64
  * @property {'point' | 'directional' | null} [light] (`null`)
58
65
  * @interface
59
66
  * @group Shaders
60
67
  */
61
68
  export type OBJ_VertexShader = {
62
69
  light?: 'point' | 'directional' | null;
63
- color?: 'vertex' | 'uniform' | 'texture';
70
+ color?: 'vertex' | 'uniform' | 'texture' | 'textureMap';
64
71
  dimension?: 2 | 3;
65
72
  };
66
73
  /**
@@ -93,7 +100,16 @@ export type TypeVertexShader = string | {
93
100
  * - `vec4 u_color`: global color for all vertices used all times. When
94
101
  * `color = 'texture'` or `color = 'vertex'`, only the alpha channel of
95
102
  * `u_color` is used.
96
- * - `sampler2D u_texture`: texture used when `color = 'texture'`.
103
+ * - `sampler2D u_texture`: texture used when `color = 'texture'`,
104
+ * `color = 'textureAlpha'` or `color = 'textureMap'`.
105
+ * - `sampler2D u_mask0`, `u_mask1`, ...: mask textures used when
106
+ * `color = 'textureMap'` (one per mask, set by the `masks` count). Each mask's
107
+ * `r`, `g`, `b` and `a` channels select four regions of `u_texture` to
108
+ * recolor. Mask `m`'s four channels map to tints `u_tint{4m+0..3}`.
109
+ * - `vec4 u_tint0`, `u_tint1`, ...: region tint colors used when
110
+ * `color = 'textureMap'` (four per mask). The `rgb` channels are the tint
111
+ * color and the `a` channel is the tint strength (`0` leaves the base texture
112
+ * unchanged).
97
113
  * - `vec3 u_directionalLight`: world space position of directional light
98
114
  * source used when `light = 'directional'`
99
115
  * - `float u_ambientLight`: ambient light used when `light = 'directional'` or
@@ -113,14 +129,15 @@ export type TypeVertexShader = string | {
113
129
  * from vertex shader used when `light = 'point'`
114
130
  *
115
131
  * @property {2 | 3} [dimension] (`2`)
116
- * @property {'vertex' | 'uniform' | 'texture'} [color] (`uniform`)
132
+ * @property {'vertex' | 'uniform' | 'texture' | 'textureMap'} [color] (`uniform`)
117
133
  * @property {'point' | 'directional' | null} [light] (`null`)
118
134
  * @interface
119
135
  * @group Shaders
120
136
  */
121
137
  export type OBJ_FragmentShader = {
122
138
  light?: 'point' | 'directional' | null;
123
- color?: 'vertex' | 'uniform' | 'texture';
139
+ color?: 'vertex' | 'uniform' | 'texture' | 'textureMap';
140
+ masks?: number;
124
141
  };
125
142
  /**
126
143
  * A fragment shader can be defined with either: