@strategicprojects/rpic 0.12.1 → 0.14.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/README.md CHANGED
@@ -76,13 +76,52 @@ call that asks for the other build rejects instead of silently reusing the
76
76
  first build. In Node, pass the math artifact's bytes:
77
77
  `ready(readFileSync(new URL('…/pkg/rpic_wasm_math_bg.wasm', import.meta.url)), { math: true })`.
78
78
 
79
+ ### Maps (geopic: drawings as GeoJSON)
80
+
81
+ A third, opt-in build georeferences a drawing in the browser: the result is an
82
+ RFC 7946 `FeatureCollection` (WGS84) for Leaflet, MapLibre, OpenLayers…
83
+ The drawing is placed by `geo_lon`/`geo_lat`/`geo_scale`/`geo_bearing` in the
84
+ source, by control points, or by the options below (which override them) —
85
+ the contract is [docs/geopic.md](https://github.com/milkway/rpic-lang/blob/main/docs/geopic.md).
86
+
87
+ ```js
88
+ await rpic.ready(undefined, { geo: true }); // browser: fetches pkg/rpic_wasm_geo_bg.wasm
89
+
90
+ const { geojson, warnings, registration } = rpic.compileGeoJSON(
91
+ 'geo_scale = 1000\narrow from Recife to Olinda\n"Recife" at Recife below',
92
+ {
93
+ anchor: [-34.8711, -8.0631],
94
+ features: { Recife: [-34.8711, -8.0631], Olinda: [-34.8553, -8.0089] },
95
+ }
96
+ );
97
+ L.geoJSON(geojson).addTo(map); // e.g. Leaflet
98
+ ```
99
+
100
+ `features` names points the source may use without defining them: names the
101
+ source defines are control points of its own geometry, the others are
102
+ injected as places (or invisible boxes, given a `bbox: [w, s, e, n]`, so
103
+ `Zone.n` follows the extent). Names must be pic labels (`/^[A-Z][A-Za-z0-9_]*$/`)
104
+ and are validated before anything compiles. Options: `frame` (`'enu'` |
105
+ `'mercator'`), `anchor`, `scale` (m per inch), `bearing` (° clockwise from
106
+ north), `features`, `flatten`, `viewScale`, `screenClasses`, `circuits`,
107
+ `texlabels`. A georeferencing failure throws a `GeoError` whose `info` is
108
+ `{ kind, message, hint }` (`no_registration`, `invalid_anchor`, …); pic errors
109
+ throw the usual error with `errorInfo`. Nothing is dropped silently: every loss
110
+ (an unsupported style, ignored `animate` timelines…) is in `warnings`, next to
111
+ the engine's own warnings; pic `print` lines are in `diagnostics`. Numeric
112
+ options must be finite (`scale`/`flatten`/`viewScale` > 0) or the call throws.
113
+ `geoToCanvas(registration, [[lon, lat], …])` maps points back onto the canvas.
114
+ In Node, pass the bytes of `pkg/rpic_wasm_geo_bg.wasm` to `ready(…, { geo: true })`.
115
+
79
116
  ## API
80
117
 
81
118
  | Function | Description |
82
119
  |----------|-------------|
83
- | `ready(wasmInput?, {math?})` | Initialize WASM. Browser: no arg. Node: pass `.wasm` bytes/URL. `math: true` loads the math-enabled build; conflicting later calls reject. |
84
- | `compile(src, {circuits?, texlabels?, scene?, flatten?, outline?})` | → `{ svg, animations, diagnostics, warnings, objects, scene? }` (throws on a pic error with `errorInfo`). `scene: true` adds the device-independent resolved draw list ([docs/scene.md](https://github.com/milkway/rpic-lang/blob/main/docs/scene.md)); `flatten` (inches) delivers its curves as polylines. |
120
+ | `ready(wasmInput?, {math?, geo?})` | Initialize WASM. Browser: no arg. Node: pass `.wasm` bytes/URL. `math: true` loads the math-enabled build, `geo: true` the geo-enabled one; conflicting later calls reject. |
121
+ | `compile(src, {circuits?, texlabels?, scene?, flatten?, outline?})` | → `{ svg, animations, diagnostics, warnings, objects, places?, passthrough?, scene? }` (throws on a pic error with `errorInfo`; `places` = named `Label: position` points in SVG units, `passthrough` = troff `.` lines, each present only when the source has some). `scene: true` adds the device-independent resolved draw list ([docs/scene.md](https://github.com/milkway/rpic-lang/blob/main/docs/scene.md)); `flatten` (inches) delivers its curves as polylines. |
85
122
  | `renderSvg(src, {circuits?, texlabels?})` | → SVG string. |
123
+ | `compileGeoJSON(src, {anchor?, scale?, bearing?, frame?, features?, …})` | Geo build only. → `{ geojson, warnings, diagnostics, registration, canvas }`; throws `GeoError` (`info.kind`) or a pic error. |
124
+ | `geoToCanvas(registration, points)` | Geo build only. WGS84 `[lon, lat]` points → canvas `[x, y]` inches. |
86
125
  | `animate(root, animations, gsap)` | Build/play a GSAP timeline (`draw`/`fade`/`pop`). Browser only. |
87
126
 
88
127
  Compile errors keep a readable `message` and expose structured data for editors:
package/index.d.ts CHANGED
@@ -108,6 +108,54 @@ export interface SceneItem {
108
108
  }[];
109
109
  /** block items: the child item ids */
110
110
  items?: string[];
111
+ /**
112
+ * block items: where the block came from — the outermost macro call that
113
+ * expanded to it, or its opening `[` when written inline
114
+ */
115
+ span?: SourceSpan;
116
+ /** block items made by a macro: that outermost call */
117
+ call?: { name: string; args: string[] };
118
+ }
119
+
120
+ /** A source position, relative to its own source unit (like diagnostics). */
121
+ export interface SourceSpan {
122
+ line: number;
123
+ col: number;
124
+ end_col: number;
125
+ /** absent = the user's input; `"circuits"` = the `-c` library; else the `copy` name */
126
+ file?: string;
127
+ }
128
+
129
+ /** A named place (`Label: position`) in the scene. */
130
+ export interface ScenePlace {
131
+ /** the resolved label, subscripts included (`R[2]`) */
132
+ label: string;
133
+ /** scene units, the same frame as the items */
134
+ at: [number, number];
135
+ /** enclosing block item ids, outermost first */
136
+ block?: string[];
137
+ span?: SourceSpan;
138
+ }
139
+
140
+ /** A named place in the compile bundle: SVG user units, the `objects` frame. */
141
+ export interface Place {
142
+ label: string;
143
+ x: number;
144
+ y: number;
145
+ line?: number;
146
+ col?: number;
147
+ end_col?: number;
148
+ file?: string;
149
+ }
150
+
151
+ /** A troff request line (`.` in column 1) set aside before lexing. */
152
+ export interface Passthrough {
153
+ /** the line as written, leading `.` included */
154
+ text: string;
155
+ line: number;
156
+ col: number;
157
+ end_col: number;
158
+ file?: string;
111
159
  }
112
160
 
113
161
  /**
@@ -127,8 +175,15 @@ export interface Scene {
127
175
  origin: [number, number];
128
176
  };
129
177
  source?: string;
178
+ /**
179
+ * the uniform factor the whole-picture rescale (`.PS <w>`, `maxpswid`/`maxpsht`)
180
+ * applied to the geometry; 1 when none acted. `vars` are pre-rescale.
181
+ */
182
+ rescale: number;
130
183
  /** final values of every variable (user + pic built-ins) */
131
184
  vars: Record<string, number>;
185
+ /** named places (`Label: position`), evaluation order; empty when none */
186
+ places: ScenePlace[];
132
187
  items: SceneItem[];
133
188
  }
134
189
 
@@ -143,6 +198,10 @@ export interface Bundle {
143
198
  warnings: Diagnostic[];
144
199
  /** per-object geometry, index-aligned with the `<g id="sN">` groups */
145
200
  objects: ObjectGeometry[];
201
+ /** named places (`Label: position`) in SVG user units. Present only when the source defines some. */
202
+ places?: Place[];
203
+ /** troff request lines set aside before lexing. Present only when the source has some. */
204
+ passthrough?: Passthrough[];
146
205
  /**
147
206
  * `true` when the source used `animate scroll`, hinting the host to scrub
148
207
  * the timeline on scroll rather than autoplay. Present only when set.
@@ -186,6 +245,13 @@ export interface ReadyOptions {
186
245
  * pass the math wasm bytes as `wasmInput`.
187
246
  */
188
247
  math?: boolean;
248
+ /**
249
+ * Load the geo-enabled wasm build (`pkg/rpic_wasm_geo_bg.wasm`), which adds
250
+ * `compileGeoJSON` / `geoToCanvas` (geopic: drawings as GeoJSON). Fetched
251
+ * only when requested; cannot be combined with `math`. In Node, pass the
252
+ * geo wasm bytes as `wasmInput`.
253
+ */
254
+ geo?: boolean;
189
255
  }
190
256
 
191
257
  /**
@@ -203,3 +269,167 @@ export function compile(src: string, opts?: CompileOptions): Bundle;
203
269
 
204
270
  /** Compile and return only the SVG string. */
205
271
  export function renderSvg(src: string, opts?: CompileOptions): string;
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // geopic: pic drawings as GeoJSON (docs/geopic.md). Needs the geo build:
275
+ // `await ready(undefined, { geo: true })`.
276
+ // ---------------------------------------------------------------------------
277
+
278
+ /** `[west, south, east, north]`, degrees. */
279
+ export type GeoBBox = [number, number, number, number];
280
+
281
+ /**
282
+ * A named geographic feature the host knows about. The name must be a pic
283
+ * label (`/^[A-Z][A-Za-z0-9_]*$/`); anything else is rejected before
284
+ * compiling. With a `bbox` it is injected as an invisible box, so
285
+ * `Name.n`/`Name.e`/… follow its extent (compass corners are canvas
286
+ * directions).
287
+ */
288
+ export interface GeoFeatureInput {
289
+ name: string;
290
+ lon: number;
291
+ lat: number;
292
+ bbox?: GeoBBox | null;
293
+ }
294
+
295
+ /**
296
+ * `features` in any of its accepted forms: `{ Name: [lon, lat] }`,
297
+ * `{ Name: [lon, lat, bbox] }`, `{ Name: { lon, lat, bbox? } }`, or an array
298
+ * of `{ name, lon, lat, bbox? }` objects / `[name, lon, lat, bbox?]` tuples.
299
+ */
300
+ export type GeoFeatures =
301
+ | Record<string, [number, number] | [number, number, GeoBBox | null] | { lon: number; lat: number; bbox?: GeoBBox | null }>
302
+ | Array<GeoFeatureInput | [string, number, number] | [string, number, number, GeoBBox | null]>;
303
+
304
+ export interface GeoOptions {
305
+ /** load the native circuit-element library */
306
+ circuits?: boolean;
307
+ /** set `texlabels = 1` (the geo build has no math renderer: literal labels + a diagnostic) */
308
+ texlabels?: boolean;
309
+ /** projection frame; overrides `geo_frame` */
310
+ frame?: 'enu' | 'mercator';
311
+ /** `[lon, lat]` of the pic origin; overrides `geo_lon`/`geo_lat` */
312
+ anchor?: [number, number];
313
+ /** metres per authored inch; overrides `geo_scale` (default 100) */
314
+ scale?: number;
315
+ /** azimuth of the canvas +y axis, degrees clockwise from north; overrides `geo_bearing` */
316
+ bearing?: number;
317
+ /**
318
+ * Named features: those the source defines (places or labelled objects)
319
+ * are control points of its geometry; the others are injected as places
320
+ * (two-pass), so `arrow from Recife to Olinda` works without the source
321
+ * defining them.
322
+ */
323
+ features?: GeoFeatures;
324
+ /** flattening tolerance for curves, inches (default `1e-3`) */
325
+ flatten?: number;
326
+ /** pixels per inch when the source sets `geo_px = 1` */
327
+ viewScale?: number;
328
+ /** `class` words marking screen-anchored items (default `['screen']`) */
329
+ screenClasses?: string[];
330
+ }
331
+
332
+ /** A non-fatal loss or substitution while georeferencing (never silent). */
333
+ export interface GeoWarning {
334
+ /**
335
+ * `unsupported_style`, `ignored`, `registration_residual`,
336
+ * `ignored_with_control_points`, `control_point_unresolved`,
337
+ * `control_point_overridden`, `unsupported_kind`, `densification_budget`,
338
+ * …; engine `scene_unsupported` diagnostics ride here too (with the
339
+ * `Diagnostic` keys instead of `id`/`span`).
340
+ */
341
+ kind: string;
342
+ message: string;
343
+ /** the scene item concerned (`s<N>`), when there is one */
344
+ id?: string;
345
+ /**
346
+ * its source span, relative to your own source; `file: 'features'` marks a
347
+ * position inside the lines `compileGeoJSON` injected for `features`
348
+ */
349
+ span?: { line: number; col: number; end_col: number; file?: string };
350
+ }
351
+
352
+ /** The similarity canvas (inches) → Earth. */
353
+ export interface GeoRegistration {
354
+ frame: 'enu' | 'mercator';
355
+ /** geographic position of `origin` */
356
+ anchor: { lon: number; lat: number };
357
+ /** the canvas point (inches) that sits at `anchor` */
358
+ origin: [number, number];
359
+ /** metres per inch of the emitted geometry */
360
+ scale_m_per_in: number;
361
+ /** azimuth of the canvas +y axis, degrees clockwise from north */
362
+ bearing_deg: number;
363
+ method: 'anchor' | 'two_points' | 'helmert';
364
+ /** control points used by the fit (0 for a declared anchor) */
365
+ control_points: number;
366
+ /** RMS residual of the fit, metres (fits only) */
367
+ rms_m?: number;
368
+ }
369
+
370
+ export interface GeoJSONGeometry {
371
+ type: 'Point' | 'LineString' | 'Polygon' | 'MultiPoint' | 'MultiLineString' | 'MultiPolygon';
372
+ coordinates: unknown;
373
+ }
374
+
375
+ export interface GeoJSONFeature {
376
+ type: 'Feature';
377
+ geometry: GeoJSONGeometry | null;
378
+ /** simplestyle keys (`stroke`, `fill`, …) and `geopic:*` (docs/geopic.md) */
379
+ properties: Record<string, unknown>;
380
+ }
381
+
382
+ export interface GeoJSONFeatureCollection {
383
+ type: 'FeatureCollection';
384
+ features: GeoJSONFeature[];
385
+ }
386
+
387
+ export interface GeoResult {
388
+ /** RFC 7946 FeatureCollection, WGS84 */
389
+ geojson: GeoJSONFeatureCollection;
390
+ /** engine warnings, then scene, then geopic ones — never silent */
391
+ warnings: (GeoWarning | Diagnostic)[];
392
+ /** lines emitted by pic `print` statements, plus engine notices (as in `Bundle`) */
393
+ diagnostics: string[];
394
+ registration: GeoRegistration;
395
+ /** what a host needs to write canvas positions back as source coordinates */
396
+ canvas: {
397
+ /** the whole-picture rescale (`.PS <width>`, `maxpswid`); 1 when none */
398
+ rescale: number;
399
+ /** the pic `scale` variable (source units per inch) */
400
+ scale: number;
401
+ };
402
+ }
403
+
404
+ /** A georeferencing error; `info.kind` is machine-readable. */
405
+ export class GeoError extends Error {
406
+ constructor(message: string, info: { kind: string; message: string; hint?: string });
407
+ info: {
408
+ /**
409
+ * `no_registration`, `invalid_scale`, `invalid_anchor`, `invalid_frame`,
410
+ * `invalid_scene`, `invalid_flatten`, `view_scale_required`,
411
+ * `degenerate_control_points`, `invalid_options` (option JSON of the
412
+ * wrong shape).
413
+ */
414
+ kind: string;
415
+ message: string;
416
+ hint?: string;
417
+ };
418
+ }
419
+
420
+ /**
421
+ * Georeference pic source as a GeoJSON FeatureCollection (WGS84). Needs the
422
+ * geo build (`ready(…, { geo: true })`). Throws a `CompileError` on a pic
423
+ * error, a `GeoError` on a georeferencing error, and a plain `Error` for an
424
+ * invalid feature name/shape (checked before anything compiles).
425
+ */
426
+ export function compileGeoJSON(src: string, opts?: GeoOptions): GeoResult;
427
+
428
+ /**
429
+ * Canvas positions (inches) of WGS84 `[lon, lat]` points under a
430
+ * registration (as `compileGeoJSON` returned it). Needs the geo build.
431
+ */
432
+ export function geoToCanvas(
433
+ registration: GeoRegistration | string,
434
+ points: [number, number][]
435
+ ): [number, number][];
package/index.js CHANGED
@@ -3,11 +3,15 @@
3
3
  // Browser: await ready(); // wasm fetched automatically
4
4
  // Node: await ready(fs.readFileSync(url)); // pass the .wasm bytes/URL
5
5
  // Math: await ready(undefined, { math: true }); // texlabels typeset in-browser
6
+ // Maps: await ready(undefined, { geo: true }); // compileGeoJSON (drawing → GeoJSON)
6
7
  import * as leanModule from './pkg/rpic_wasm.js';
7
8
 
9
+ // display names of the three builds, keyed by ready()'s choice
10
+ const BUILDS = { lean: 'lean', math: 'math-enabled', geo: 'geo-enabled' };
11
+
8
12
  let initPromise = null;
9
- let wasm = null; // the initialized glue module (lean or math)
10
- let initMath = null;
13
+ let wasm = null; // the initialized glue module (lean, math or geo)
14
+ let initBuild = null; // 'lean' | 'math' | 'geo' while an init is pending/done
11
15
 
12
16
  /**
13
17
  * Initialize the WebAssembly module (idempotent; concurrent calls share one
@@ -16,29 +20,38 @@ let initMath = null;
16
20
  *
17
21
  * Pass `{ math: true }` to load the math-enabled build instead: it bundles
18
22
  * the RaTeX renderer so `texlabels` sources typeset `$…$` labels exactly like
19
- * the native CLI. The math glue + wasm are only fetched when requested, so
20
- * the lean fast path stays untouched. The choice is fixed by the first
21
- * successful (or in-flight) call; later calls asking for the other build
23
+ * the native CLI. Pass `{ geo: true }` for the geo-enabled build, which adds
24
+ * `compileGeoJSON`/`geoToCanvas` (geopic: drawings as GeoJSON). Either extra
25
+ * glue + wasm is only fetched when requested, so the lean fast path stays
26
+ * untouched; the two cannot be combined. The choice is fixed by the first
27
+ * successful (or in-flight) call; later calls asking for another build
22
28
  * reject. A *failed* init (bad bytes, fetch error) clears the slot so
23
29
  * `ready()` can be retried. In Node, pass the bytes of
24
- * `pkg/rpic_wasm_math_bg.wasm` along with it.
30
+ * `pkg/rpic_wasm_math_bg.wasm` / `pkg/rpic_wasm_geo_bg.wasm` along with it.
25
31
  */
26
32
  export function ready(wasmInput, opts = {}) {
27
- const wantsMath = !!opts.math;
33
+ if (opts.math && opts.geo) {
34
+ return Promise.reject(new Error('rpic: ready() cannot load the math and geo builds together; pick one'));
35
+ }
36
+ const wanted = opts.math ? 'math' : opts.geo ? 'geo' : 'lean';
28
37
  if (initPromise) {
29
- if (initMath !== wantsMath) {
30
- const current = initMath ? 'math-enabled' : 'lean';
31
- const requested = wantsMath ? 'math-enabled' : 'lean';
38
+ if (initBuild !== wanted) {
32
39
  return Promise.reject(
33
- new Error(`rpic: ready() already initialized the ${current} build; cannot switch to ${requested}`)
40
+ new Error(
41
+ `rpic: ready() already initialized the ${BUILDS[initBuild]} build; cannot switch to ${BUILDS[wanted]}`
42
+ )
34
43
  );
35
44
  }
36
45
  return initPromise;
37
46
  }
38
- initMath = wantsMath;
39
- const attempt = (
40
- wantsMath ? import('./pkg/rpic_wasm_math.js') : Promise.resolve(leanModule)
41
- )
47
+ initBuild = wanted;
48
+ const load =
49
+ wanted === 'math'
50
+ ? import('./pkg/rpic_wasm_math.js')
51
+ : wanted === 'geo'
52
+ ? import('./pkg/rpic_wasm_geo.js')
53
+ : Promise.resolve(leanModule);
54
+ const attempt = load
42
55
  .then(async (mod) => {
43
56
  await mod.default(wasmInput === undefined ? undefined : { module_or_path: wasmInput });
44
57
  wasm = mod;
@@ -48,7 +61,7 @@ export function ready(wasmInput, opts = {}) {
48
61
  // (only if it is still ours) so the next ready() starts fresh
49
62
  if (initPromise === attempt) {
50
63
  initPromise = null;
51
- initMath = null;
64
+ initBuild = null;
52
65
  }
53
66
  throw e;
54
67
  });
@@ -88,19 +101,317 @@ export function compile(src, opts = {}) {
88
101
  : wasm.compile(src);
89
102
  const out = JSON.parse(json);
90
103
  if (out.error) {
91
- const err = new Error(out.error);
92
- err.errorInfo = out.error_info;
93
- err.warnings = out.warnings ?? [];
94
- throw err;
104
+ throw compileError(out);
95
105
  }
96
106
  return out;
97
107
  }
98
108
 
109
+ /** The Error a pic error becomes: `.errorInfo` (a Diagnostic) + `.warnings`. */
110
+ function compileError(out) {
111
+ const err = new Error(out.error);
112
+ err.errorInfo = out.error_info;
113
+ err.warnings = out.warnings ?? [];
114
+ return err;
115
+ }
116
+
99
117
  /** Compile and return just the SVG string. */
100
118
  export function renderSvg(src, opts) {
101
119
  return compile(src, opts).svg;
102
120
  }
103
121
 
122
+ // ---------------------------------------------------------------------------
123
+ // geopic: pic drawings as GeoJSON (docs/geopic.md). Needs the geo build:
124
+ // `await ready(undefined, { geo: true })`. Mirrors Python's `rpic.geo`.
125
+ // ---------------------------------------------------------------------------
126
+
127
+ /**
128
+ * A georeferencing error. `.message` is the readable text; `.info` is the
129
+ * structured `{ kind, message, hint? }` (`no_registration`, `invalid_anchor`,
130
+ * `invalid_frame`, `invalid_options`, …).
131
+ */
132
+ export class GeoError extends Error {
133
+ constructor(message, info) {
134
+ super(message);
135
+ this.name = 'GeoError';
136
+ this.info = info;
137
+ }
138
+ }
139
+
140
+ // pic labels: an uppercase letter, then letters, digits or underscores.
141
+ // Feature names are written into pic source, so anything else is rejected
142
+ // outright — never interpolated.
143
+ const LABEL = /^[A-Z][A-Za-z0-9_]*$/;
144
+ const FORMS =
145
+ 'rpic: features must be a {name: [lon, lat]} / {name: {lon, lat, bbox?}} object, ' +
146
+ 'or an array of {name, lon, lat, bbox?} objects or [name, lon, lat, bbox?] tuples';
147
+
148
+ function featureLabel(name) {
149
+ const s = String(name);
150
+ if (!LABEL.test(s)) {
151
+ throw new Error(
152
+ `rpic: feature name ${JSON.stringify(s)} is not a pic label (an uppercase letter followed by letters, digits or underscores)`
153
+ );
154
+ }
155
+ return s;
156
+ }
157
+
158
+ function featureCoord(v, what) {
159
+ if (typeof v !== 'number' || !Number.isFinite(v)) {
160
+ throw new Error(`rpic: feature ${what} must be a finite number, got ${JSON.stringify(v) ?? String(v)}`);
161
+ }
162
+ return v;
163
+ }
164
+
165
+ function featureBbox(name, bbox) {
166
+ if (bbox === undefined || bbox === null) return null;
167
+ if (!Array.isArray(bbox) || bbox.length !== 4) {
168
+ throw new Error(`rpic: feature ${JSON.stringify(name)}: bbox must be [west, south, east, north]`);
169
+ }
170
+ return bbox.map((v) => featureCoord(v, `${name} bbox`));
171
+ }
172
+
173
+ function oneFeature(name, pos) {
174
+ const n = featureLabel(name);
175
+ if (Array.isArray(pos)) {
176
+ if (pos.length !== 2 && pos.length !== 3) throw new Error(FORMS);
177
+ return { name: n, lon: featureCoord(pos[0], `${n} lon`), lat: featureCoord(pos[1], `${n} lat`), bbox: featureBbox(n, pos[2]) };
178
+ }
179
+ if (pos && typeof pos === 'object') {
180
+ return { name: n, lon: featureCoord(pos.lon, `${n} lon`), lat: featureCoord(pos.lat, `${n} lat`), bbox: featureBbox(n, pos.bbox) };
181
+ }
182
+ throw new Error(FORMS);
183
+ }
184
+
185
+ /** `features` in any accepted form → `[{name, lon, lat, bbox|null}]`. */
186
+ function normalizeFeatures(features) {
187
+ if (features === undefined || features === null) return [];
188
+ if (Array.isArray(features)) {
189
+ const out = features.map((item) => {
190
+ if (Array.isArray(item)) {
191
+ if (item.length !== 3 && item.length !== 4) throw new Error(FORMS);
192
+ if (typeof item[0] !== 'string') throw new Error(`${FORMS} (name is required)`);
193
+ return oneFeature(item[0], item.slice(1));
194
+ }
195
+ if (item && typeof item === 'object') {
196
+ if (typeof item.name !== 'string') throw new Error(`${FORMS} (name is required)`);
197
+ return oneFeature(item.name, item);
198
+ }
199
+ throw new Error(FORMS);
200
+ });
201
+ return unique(out);
202
+ }
203
+ if (typeof features === 'object') {
204
+ return Object.entries(features).map(([name, pos]) => oneFeature(name, pos));
205
+ }
206
+ throw new Error(FORMS);
207
+ }
208
+
209
+ /** Reject a name given twice: which position would win is not obvious. */
210
+ function unique(feats) {
211
+ const seen = new Set();
212
+ for (const f of feats) {
213
+ if (seen.has(f.name)) throw new Error(`rpic: feature ${JSON.stringify(f.name)} is given more than once`);
214
+ seen.add(f.name);
215
+ }
216
+ return feats;
217
+ }
218
+
219
+ /**
220
+ * Re-base one position (`line`/`file`) past the `k` prelude lines. A
221
+ * position *inside* the prelude is not the caller's: it is tagged
222
+ * `file: 'features'` (`line` stays relative to the prelude).
223
+ */
224
+ function rebaseLine(d, k) {
225
+ if (!k || !d || typeof d !== 'object' || d.file != null) return;
226
+ if (!Number.isInteger(d.line) || d.line < 1) return;
227
+ if (d.line > k) d.line -= k;
228
+ else d.file = 'features';
229
+ }
230
+
231
+ /**
232
+ * Shift warnings back by the `k` prelude lines the host prepended, so
233
+ * positions refer to the caller's own source (positions inside an
234
+ * include/library carry a `file` and are left alone).
235
+ */
236
+ function rebase(out, k) {
237
+ if (!k) return out;
238
+ for (const w of out.warnings ?? []) {
239
+ if (w.span && typeof w.span === 'object') rebaseLine(w.span, k);
240
+ else rebaseLine(w, k);
241
+ }
242
+ return out;
243
+ }
244
+
245
+ function geoWasm() {
246
+ ensure();
247
+ if (typeof wasm.compile_geo !== 'function') {
248
+ throw new Error('rpic: compileGeoJSON needs the geo-enabled build: `await ready(undefined, { geo: true })`');
249
+ }
250
+ return wasm;
251
+ }
252
+
253
+ /** One native pass, diagnostics re-based by `k` lines. */
254
+ function geoPass(src, k, geoOpts, opts) {
255
+ const out = JSON.parse(geoWasm().compile_geo(src, !!opts.circuits, !!opts.texlabels, JSON.stringify(geoOpts)));
256
+ if (out.error_info) {
257
+ const err = compileError(out);
258
+ rebaseLine(err.errorInfo, k);
259
+ throw err;
260
+ }
261
+ if (out.geo_error) throw new GeoError(out.error, out.geo_error);
262
+ return rebase(out, k);
263
+ }
264
+
265
+ /** 9 significant digits, like `%.9g` (the pic lexer reads exponents). */
266
+ const num = (x) => String(Number(x.toPrecision(9)));
267
+
268
+ /**
269
+ * pic statements defining every feature at its position, in the source's own
270
+ * units (pic `scale`, before the whole-picture `rescale`). A feature with a
271
+ * bbox becomes an invisible labelled box spanning the axis-aligned canvas
272
+ * extent of its four corners; a point feature a plain place. Names were
273
+ * validated by `featureLabel` — nothing else reaches the source.
274
+ */
275
+ function prelude(registration, canvas, feats) {
276
+ const units = (canvas.scale ?? 1) / (canvas.rescale ?? 1); // source units per emitted inch
277
+ const pts = [];
278
+ for (const f of feats) {
279
+ pts.push([f.lon, f.lat]);
280
+ if (f.bbox) {
281
+ const [w, s, e, n] = f.bbox;
282
+ pts.push([w, s], [e, s], [e, n], [w, n]);
283
+ }
284
+ }
285
+ const canvasPts = geoToCanvas(registration, pts);
286
+ let i = 0;
287
+ const lines = feats.map((f) => {
288
+ const [x, y] = canvasPts[i++];
289
+ const at = `(${num(x * units)}, ${num(y * units)})`;
290
+ if (!f.bbox) return `${f.name}: ${at}`;
291
+ const corners = canvasPts.slice(i, i + 4);
292
+ i += 4;
293
+ const xs = corners.map((c) => c[0]);
294
+ const ys = corners.map((c) => c[1]);
295
+ const wid = (Math.max(...xs) - Math.min(...xs)) * units;
296
+ const ht = (Math.max(...ys) - Math.min(...ys)) * units;
297
+ return `${f.name}: box invis wid ${num(wid)} ht ${num(ht)} at ${at}`;
298
+ });
299
+ return lines.join('\n') + '\n';
300
+ }
301
+
302
+ /**
303
+ * Two passes. Features the source itself defines (as places or labelled
304
+ * objects) are control points of the source's own geometry; the others are
305
+ * injected — placed by the registration of a first pass — as places or
306
+ * invisible boxes, then everything is georeferenced together.
307
+ */
308
+ function compileWithFeatures(src, feats, base, opts) {
309
+ const names = new Set(feats.map((f) => f.name));
310
+ const k = feats.length;
311
+ const placeholders = feats.map((f) => `${f.name}: (0, 0)\n`).join('');
312
+ // which names does the source define? their definitions sit after the
313
+ // placeholder lines (a span is relative to the caller's text + prelude)
314
+ let scene;
315
+ try {
316
+ scene = compile(placeholders + src, { circuits: opts.circuits, texlabels: opts.texlabels, scene: true }).scene;
317
+ } catch (e) {
318
+ rebaseLine(e.errorInfo, k);
319
+ throw e;
320
+ }
321
+ const defined = new Set();
322
+ for (const p of [...(scene.places ?? []), ...(scene.items ?? [])]) {
323
+ if (names.has(p.label) && (p.span?.line ?? 0) > k) defined.add(p.label);
324
+ }
325
+ const own = feats.filter((f) => defined.has(f.name));
326
+ const inject = feats.filter((f) => !defined.has(f.name));
327
+ // pass 1: the registration — declared anchor, or the source's control
328
+ // points, the source-defined features among them
329
+ const first = geoPass(placeholders + src, k, { ...base, features: own }, opts);
330
+ if (!inject.length) return first;
331
+ const pre = prelude(first.registration, first.canvas, inject);
332
+ const out = geoPass(pre + src, inject.length, { ...base, features: feats }, opts);
333
+ if (first.registration.method === 'anchor' && first.registration.control_points === 0) {
334
+ // the injected features were placed *by* the declared values; the second
335
+ // pass's "declared values ignored" would blame the source
336
+ out.warnings = out.warnings.filter((w) => w.kind !== 'ignored_with_control_points');
337
+ }
338
+ return out;
339
+ }
340
+
341
+ /**
342
+ * Numeric options are checked here: `JSON.stringify` would turn NaN/Infinity
343
+ * into `null`, which the wasm reads as "not given" — a silent fallback.
344
+ */
345
+ function checkGeoOptions(opts) {
346
+ const finite = (v) => typeof v === 'number' && Number.isFinite(v);
347
+ for (const key of ['scale', 'flatten', 'viewScale']) {
348
+ if (opts[key] !== undefined && !(finite(opts[key]) && opts[key] > 0)) {
349
+ throw new Error(`rpic: ${key} must be a positive finite number`);
350
+ }
351
+ }
352
+ if (opts.bearing !== undefined && !finite(opts.bearing)) {
353
+ throw new Error('rpic: bearing must be a finite number (degrees)');
354
+ }
355
+ if (
356
+ opts.anchor !== undefined &&
357
+ !(Array.isArray(opts.anchor) && opts.anchor.length === 2 && opts.anchor.every(finite))
358
+ ) {
359
+ throw new Error('rpic: anchor must be [lon, lat], two finite numbers');
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Georeference pic source: `{ geojson, warnings, diagnostics, registration,
365
+ * canvas }`.
366
+ * Needs the geo build (`await ready(undefined, { geo: true })`).
367
+ *
368
+ * `frame` (`'enu'` | `'mercator'`), `anchor` `[lon, lat]`, `scale` (metres per
369
+ * inch), `bearing` (degrees clockwise from north), `viewScale` (px per inch
370
+ * under `geo_px = 1`) and `screenClasses` override the source's `geo_*`
371
+ * variables; `flatten` is the curve tolerance (inches, default `1e-3`).
372
+ *
373
+ * `features` names points the source may use without defining them
374
+ * (`arrow from Recife to Olinda`): features the source does define act as
375
+ * control points of its own geometry; the others are projected by a first
376
+ * pass's registration and injected as places — invisible labelled boxes when
377
+ * they have a `bbox`, so `Name.n`/`Name.e` follow its extent — before the
378
+ * final pass. Diagnostics keep the caller's line numbers.
379
+ *
380
+ * Throws an `Error` with `.errorInfo` on a pic error (like `compile`) and a
381
+ * `GeoError` (`.info = { kind, message, hint }`) on a georeferencing error.
382
+ */
383
+ export function compileGeoJSON(src, opts = {}) {
384
+ geoWasm();
385
+ checkGeoOptions(opts);
386
+ const feats = normalizeFeatures(opts.features);
387
+ const base = {
388
+ frame: opts.frame,
389
+ anchor: opts.anchor,
390
+ scale: opts.scale,
391
+ bearing: opts.bearing,
392
+ flatten: opts.flatten,
393
+ view_scale: opts.viewScale,
394
+ screen_classes: opts.screenClasses,
395
+ };
396
+ return feats.length
397
+ ? compileWithFeatures(src, feats, base, opts)
398
+ : geoPass(src, 0, { ...base, features: [] }, opts);
399
+ }
400
+
401
+ /**
402
+ * Canvas positions (inches) of WGS84 `[lon, lat]` points under a
403
+ * registration (as `compileGeoJSON` returned it). Needs the geo build.
404
+ * @param {object | string} registration
405
+ * @param {[number, number][]} points
406
+ * @returns {[number, number][]}
407
+ */
408
+ export function geoToCanvas(registration, points) {
409
+ const reg = typeof registration === 'string' ? registration : JSON.stringify(registration);
410
+ const out = JSON.parse(geoWasm().geo_to_canvas(reg, JSON.stringify(points)));
411
+ if (!Array.isArray(out)) throw new GeoError(out.error, out.geo_error);
412
+ return out;
413
+ }
414
+
104
415
  // The GSAP player lives in player.js — a zero-import module, so consumers
105
416
  // with pre-rendered SVG can `import { animate } from '…/player.js'` and pull
106
417
  // in nothing wasm-related. Re-exported here to keep the one-stop API.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strategicprojects/rpic",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "The pic graphics language compiled to SVG with animation manifests, via WebAssembly. Browser + Node.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -16,7 +16,8 @@
16
16
  "default": "./player.js"
17
17
  },
18
18
  "./pkg/rpic_wasm_bg.wasm": "./pkg/rpic_wasm_bg.wasm",
19
- "./pkg/rpic_wasm_math_bg.wasm": "./pkg/rpic_wasm_math_bg.wasm"
19
+ "./pkg/rpic_wasm_math_bg.wasm": "./pkg/rpic_wasm_math_bg.wasm",
20
+ "./pkg/rpic_wasm_geo_bg.wasm": "./pkg/rpic_wasm_geo_bg.wasm"
20
21
  },
21
22
  "files": [
22
23
  "index.js",
@@ -31,16 +32,21 @@
31
32
  "pkg/rpic_wasm_math.d.ts",
32
33
  "pkg/rpic_wasm_math_bg.wasm",
33
34
  "pkg/rpic_wasm_math_bg.wasm.d.ts",
35
+ "pkg/rpic_wasm_geo.js",
36
+ "pkg/rpic_wasm_geo.d.ts",
37
+ "pkg/rpic_wasm_geo_bg.wasm",
38
+ "pkg/rpic_wasm_geo_bg.wasm.d.ts",
34
39
  "README.md"
35
40
  ],
36
41
  "scripts": {
37
42
  "build:wasm:lean": "wasm-pack build ../../crates/wasm --target web --out-dir ../../bindings/js/pkg",
38
43
  "build:wasm:math": "wasm-pack build ../../crates/wasm --target web --out-dir ../../bindings/js/pkg --out-name rpic_wasm_math -- --features math",
39
- "build:wasm": "npm run build:wasm:lean && npm run build:wasm:math",
44
+ "build:wasm:geo": "wasm-pack build ../../crates/wasm --target web --out-dir ../../bindings/js/pkg --out-name rpic_wasm_geo -- --features geo",
45
+ "build:wasm": "npm run build:wasm:lean && npm run build:wasm:math && npm run build:wasm:geo",
40
46
  "prepack": "npm run build:wasm",
41
- "test": "node test/smoke.mjs && node test/smoke-math.mjs"
47
+ "test": "node test/smoke.mjs && node test/smoke-math.mjs && node test/smoke-geo.mjs"
42
48
  },
43
- "keywords": ["pic", "svg", "diagram", "graphics", "wasm", "gsap", "animation", "circuits"],
49
+ "keywords": ["pic", "svg", "diagram", "graphics", "wasm", "gsap", "animation", "circuits", "geojson", "maps"],
44
50
  "author": "André Leite <leite@de.ufpe.br>",
45
51
  "license": "BSD-2-Clause",
46
52
  "repository": {
Binary file
@@ -0,0 +1,99 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Compile pic source to a JSON `{svg, animations, diagnostics, warnings}`
6
+ * bundle (or `{error, error_info}`).
7
+ */
8
+ export function compile(src: string): string;
9
+
10
+ /**
11
+ * Like [`compile`], but with the native circuit-element library loaded
12
+ * (so `resistor`, `and_gate`, … are available). Loaded as an option, not
13
+ * prepended text: diagnostic positions stay relative to `src`.
14
+ */
15
+ export function compile_circuits(src: string): string;
16
+
17
+ /**
18
+ * Georeference pic source: `{"geojson": FeatureCollection, "warnings":
19
+ * [...], "diagnostics": [...], "registration": {...}, "canvas": {"rescale",
20
+ * "scale"}}`, or
21
+ * `{"error", "error_info"}` on a pic error, or `{"error", "geo_error":
22
+ * {kind, message, hint}}` on a georeferencing error (bad options included).
23
+ *
24
+ * `opts_json` is a JSON object (or empty string) with the optional keys
25
+ * `frame` (`"enu"` | `"mercator"`), `anchor` (`[lon, lat]`), `scale`
26
+ * (metres per inch), `bearing` (degrees clockwise from north), `features`
27
+ * (`[{name, lon, lat, bbox?}]`, control points by label), `flatten`
28
+ * (inches), `view_scale` (px per inch under `geo_px = 1`) and
29
+ * `screen_classes`. Each overrides the corresponding `geo_*` variable of
30
+ * the source. Animations cannot ride into GeoJSON: a source with `animate`
31
+ * statements adds an `ignored` warning.
32
+ */
33
+ export function compile_geo(src: string, circuits: boolean, texlabels: boolean, opts_json: string): string;
34
+
35
+ /**
36
+ * Like [`compile_with`], but the bundle also carries the device-independent
37
+ * `scene` draw list (docs/scene.md); `flatten` (inches) delivers curves as
38
+ * polylines. The plain entry points above stay byte-identical — the scene is
39
+ * computed only here.
40
+ */
41
+ export function compile_scene(src: string, circuits: boolean, texlabels: boolean, flatten: number | null | undefined, outline: boolean): string;
42
+
43
+ /**
44
+ * Like [`compile`], with options: `circuits` loads the circuit-element
45
+ * library; `texlabels` sets `texlabels = 1` so `$…$` labels are typeset as
46
+ * TeX math. Note: the default wasm build ships without the math renderer
47
+ * (size budget), so with `texlabels` the labels fall back to literal text
48
+ * and a diagnostic — use the math-enabled build (`--features math`, the
49
+ * `rpic_wasm_math` npm artifact) to typeset them.
50
+ */
51
+ export function compile_with(src: string, circuits: boolean, texlabels: boolean): string;
52
+
53
+ /**
54
+ * Canvas positions (inches) of WGS84 points under a registration (as
55
+ * `compile_geo` returned it): `points_json` is `[[lon, lat], …]`; returns
56
+ * `[[x, y], …]`, or `{"error", "geo_error"}` when either input does not
57
+ * parse. The host uses it to place features before compiling (two-pass
58
+ * feature injection, see `bindings/js/index.js`).
59
+ */
60
+ export function geo_to_canvas(registration_json: string, points_json: string): string;
61
+
62
+ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
63
+
64
+ export interface InitOutput {
65
+ readonly memory: WebAssembly.Memory;
66
+ readonly compile_geo: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number];
67
+ readonly geo_to_canvas: (a: number, b: number, c: number, d: number) => [number, number];
68
+ readonly compile: (a: number, b: number) => [number, number];
69
+ readonly compile_circuits: (a: number, b: number) => [number, number];
70
+ readonly compile_scene: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number];
71
+ readonly compile_with: (a: number, b: number, c: number, d: number) => [number, number];
72
+ readonly __wbindgen_externrefs: WebAssembly.Table;
73
+ readonly __wbindgen_malloc: (a: number, b: number) => number;
74
+ readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
75
+ readonly __wbindgen_free: (a: number, b: number, c: number) => void;
76
+ readonly __wbindgen_start: () => void;
77
+ }
78
+
79
+ export type SyncInitInput = BufferSource | WebAssembly.Module;
80
+
81
+ /**
82
+ * Instantiates the given `module`, which can either be bytes or
83
+ * a precompiled `WebAssembly.Module`.
84
+ *
85
+ * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
86
+ *
87
+ * @returns {InitOutput}
88
+ */
89
+ export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
90
+
91
+ /**
92
+ * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
93
+ * for everything else, calls `WebAssembly.instantiate` directly.
94
+ *
95
+ * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
96
+ *
97
+ * @returns {Promise<InitOutput>}
98
+ */
99
+ export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
@@ -0,0 +1,356 @@
1
+ /* @ts-self-types="./rpic_wasm_geo.d.ts" */
2
+
3
+ /**
4
+ * Compile pic source to a JSON `{svg, animations, diagnostics, warnings}`
5
+ * bundle (or `{error, error_info}`).
6
+ * @param {string} src
7
+ * @returns {string}
8
+ */
9
+ export function compile(src) {
10
+ let deferred2_0;
11
+ let deferred2_1;
12
+ try {
13
+ const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
14
+ const len0 = WASM_VECTOR_LEN;
15
+ const ret = wasm.compile(ptr0, len0);
16
+ deferred2_0 = ret[0];
17
+ deferred2_1 = ret[1];
18
+ return getStringFromWasm0(ret[0], ret[1]);
19
+ } finally {
20
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Like [`compile`], but with the native circuit-element library loaded
26
+ * (so `resistor`, `and_gate`, … are available). Loaded as an option, not
27
+ * prepended text: diagnostic positions stay relative to `src`.
28
+ * @param {string} src
29
+ * @returns {string}
30
+ */
31
+ export function compile_circuits(src) {
32
+ let deferred2_0;
33
+ let deferred2_1;
34
+ try {
35
+ const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
36
+ const len0 = WASM_VECTOR_LEN;
37
+ const ret = wasm.compile_circuits(ptr0, len0);
38
+ deferred2_0 = ret[0];
39
+ deferred2_1 = ret[1];
40
+ return getStringFromWasm0(ret[0], ret[1]);
41
+ } finally {
42
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Georeference pic source: `{"geojson": FeatureCollection, "warnings":
48
+ * [...], "diagnostics": [...], "registration": {...}, "canvas": {"rescale",
49
+ * "scale"}}`, or
50
+ * `{"error", "error_info"}` on a pic error, or `{"error", "geo_error":
51
+ * {kind, message, hint}}` on a georeferencing error (bad options included).
52
+ *
53
+ * `opts_json` is a JSON object (or empty string) with the optional keys
54
+ * `frame` (`"enu"` | `"mercator"`), `anchor` (`[lon, lat]`), `scale`
55
+ * (metres per inch), `bearing` (degrees clockwise from north), `features`
56
+ * (`[{name, lon, lat, bbox?}]`, control points by label), `flatten`
57
+ * (inches), `view_scale` (px per inch under `geo_px = 1`) and
58
+ * `screen_classes`. Each overrides the corresponding `geo_*` variable of
59
+ * the source. Animations cannot ride into GeoJSON: a source with `animate`
60
+ * statements adds an `ignored` warning.
61
+ * @param {string} src
62
+ * @param {boolean} circuits
63
+ * @param {boolean} texlabels
64
+ * @param {string} opts_json
65
+ * @returns {string}
66
+ */
67
+ export function compile_geo(src, circuits, texlabels, opts_json) {
68
+ let deferred3_0;
69
+ let deferred3_1;
70
+ try {
71
+ const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
72
+ const len0 = WASM_VECTOR_LEN;
73
+ const ptr1 = passStringToWasm0(opts_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
74
+ const len1 = WASM_VECTOR_LEN;
75
+ const ret = wasm.compile_geo(ptr0, len0, circuits, texlabels, ptr1, len1);
76
+ deferred3_0 = ret[0];
77
+ deferred3_1 = ret[1];
78
+ return getStringFromWasm0(ret[0], ret[1]);
79
+ } finally {
80
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Like [`compile_with`], but the bundle also carries the device-independent
86
+ * `scene` draw list (docs/scene.md); `flatten` (inches) delivers curves as
87
+ * polylines. The plain entry points above stay byte-identical — the scene is
88
+ * computed only here.
89
+ * @param {string} src
90
+ * @param {boolean} circuits
91
+ * @param {boolean} texlabels
92
+ * @param {number | null | undefined} flatten
93
+ * @param {boolean} outline
94
+ * @returns {string}
95
+ */
96
+ export function compile_scene(src, circuits, texlabels, flatten, outline) {
97
+ let deferred2_0;
98
+ let deferred2_1;
99
+ try {
100
+ const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
101
+ const len0 = WASM_VECTOR_LEN;
102
+ const ret = wasm.compile_scene(ptr0, len0, circuits, texlabels, !isLikeNone(flatten), isLikeNone(flatten) ? 0 : flatten, outline);
103
+ deferred2_0 = ret[0];
104
+ deferred2_1 = ret[1];
105
+ return getStringFromWasm0(ret[0], ret[1]);
106
+ } finally {
107
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Like [`compile`], with options: `circuits` loads the circuit-element
113
+ * library; `texlabels` sets `texlabels = 1` so `$…$` labels are typeset as
114
+ * TeX math. Note: the default wasm build ships without the math renderer
115
+ * (size budget), so with `texlabels` the labels fall back to literal text
116
+ * and a diagnostic — use the math-enabled build (`--features math`, the
117
+ * `rpic_wasm_math` npm artifact) to typeset them.
118
+ * @param {string} src
119
+ * @param {boolean} circuits
120
+ * @param {boolean} texlabels
121
+ * @returns {string}
122
+ */
123
+ export function compile_with(src, circuits, texlabels) {
124
+ let deferred2_0;
125
+ let deferred2_1;
126
+ try {
127
+ const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
128
+ const len0 = WASM_VECTOR_LEN;
129
+ const ret = wasm.compile_with(ptr0, len0, circuits, texlabels);
130
+ deferred2_0 = ret[0];
131
+ deferred2_1 = ret[1];
132
+ return getStringFromWasm0(ret[0], ret[1]);
133
+ } finally {
134
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Canvas positions (inches) of WGS84 points under a registration (as
140
+ * `compile_geo` returned it): `points_json` is `[[lon, lat], …]`; returns
141
+ * `[[x, y], …]`, or `{"error", "geo_error"}` when either input does not
142
+ * parse. The host uses it to place features before compiling (two-pass
143
+ * feature injection, see `bindings/js/index.js`).
144
+ * @param {string} registration_json
145
+ * @param {string} points_json
146
+ * @returns {string}
147
+ */
148
+ export function geo_to_canvas(registration_json, points_json) {
149
+ let deferred3_0;
150
+ let deferred3_1;
151
+ try {
152
+ const ptr0 = passStringToWasm0(registration_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
153
+ const len0 = WASM_VECTOR_LEN;
154
+ const ptr1 = passStringToWasm0(points_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
155
+ const len1 = WASM_VECTOR_LEN;
156
+ const ret = wasm.geo_to_canvas(ptr0, len0, ptr1, len1);
157
+ deferred3_0 = ret[0];
158
+ deferred3_1 = ret[1];
159
+ return getStringFromWasm0(ret[0], ret[1]);
160
+ } finally {
161
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
162
+ }
163
+ }
164
+ function __wbg_get_imports() {
165
+ const import0 = {
166
+ __proto__: null,
167
+ __wbindgen_init_externref_table: function() {
168
+ const table = wasm.__wbindgen_externrefs;
169
+ const offset = table.grow(4);
170
+ table.set(0, undefined);
171
+ table.set(offset + 0, undefined);
172
+ table.set(offset + 1, null);
173
+ table.set(offset + 2, true);
174
+ table.set(offset + 3, false);
175
+ },
176
+ };
177
+ return {
178
+ __proto__: null,
179
+ "./rpic_wasm_geo_bg.js": import0,
180
+ };
181
+ }
182
+
183
+ function getStringFromWasm0(ptr, len) {
184
+ return decodeText(ptr >>> 0, len);
185
+ }
186
+
187
+ let cachedUint8ArrayMemory0 = null;
188
+ function getUint8ArrayMemory0() {
189
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
190
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
191
+ }
192
+ return cachedUint8ArrayMemory0;
193
+ }
194
+
195
+ function isLikeNone(x) {
196
+ return x === undefined || x === null;
197
+ }
198
+
199
+ function passStringToWasm0(arg, malloc, realloc) {
200
+ if (realloc === undefined) {
201
+ const buf = cachedTextEncoder.encode(arg);
202
+ const ptr = malloc(buf.length, 1) >>> 0;
203
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
204
+ WASM_VECTOR_LEN = buf.length;
205
+ return ptr;
206
+ }
207
+
208
+ let len = arg.length;
209
+ let ptr = malloc(len, 1) >>> 0;
210
+
211
+ const mem = getUint8ArrayMemory0();
212
+
213
+ let offset = 0;
214
+
215
+ for (; offset < len; offset++) {
216
+ const code = arg.charCodeAt(offset);
217
+ if (code > 0x7F) break;
218
+ mem[ptr + offset] = code;
219
+ }
220
+ if (offset !== len) {
221
+ if (offset !== 0) {
222
+ arg = arg.slice(offset);
223
+ }
224
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
225
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
226
+ const ret = cachedTextEncoder.encodeInto(arg, view);
227
+
228
+ offset += ret.written;
229
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
230
+ }
231
+
232
+ WASM_VECTOR_LEN = offset;
233
+ return ptr;
234
+ }
235
+
236
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
237
+ cachedTextDecoder.decode();
238
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
239
+ let numBytesDecoded = 0;
240
+ function decodeText(ptr, len) {
241
+ numBytesDecoded += len;
242
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
243
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
244
+ cachedTextDecoder.decode();
245
+ numBytesDecoded = len;
246
+ }
247
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
248
+ }
249
+
250
+ const cachedTextEncoder = new TextEncoder();
251
+
252
+ if (!('encodeInto' in cachedTextEncoder)) {
253
+ cachedTextEncoder.encodeInto = function (arg, view) {
254
+ const buf = cachedTextEncoder.encode(arg);
255
+ view.set(buf);
256
+ return {
257
+ read: arg.length,
258
+ written: buf.length
259
+ };
260
+ };
261
+ }
262
+
263
+ let WASM_VECTOR_LEN = 0;
264
+
265
+ let wasmModule, wasmInstance, wasm;
266
+ function __wbg_finalize_init(instance, module) {
267
+ wasmInstance = instance;
268
+ wasm = instance.exports;
269
+ wasmModule = module;
270
+ cachedUint8ArrayMemory0 = null;
271
+ wasm.__wbindgen_start();
272
+ return wasm;
273
+ }
274
+
275
+ async function __wbg_load(module, imports) {
276
+ if (typeof Response === 'function' && module instanceof Response) {
277
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
278
+ try {
279
+ return await WebAssembly.instantiateStreaming(module, imports);
280
+ } catch (e) {
281
+ const validResponse = module.ok && expectedResponseType(module.type);
282
+
283
+ if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
284
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
285
+
286
+ } else { throw e; }
287
+ }
288
+ }
289
+
290
+ const bytes = await module.arrayBuffer();
291
+ return await WebAssembly.instantiate(bytes, imports);
292
+ } else {
293
+ const instance = await WebAssembly.instantiate(module, imports);
294
+
295
+ if (instance instanceof WebAssembly.Instance) {
296
+ return { instance, module };
297
+ } else {
298
+ return instance;
299
+ }
300
+ }
301
+
302
+ function expectedResponseType(type) {
303
+ switch (type) {
304
+ case 'basic': case 'cors': case 'default': return true;
305
+ }
306
+ return false;
307
+ }
308
+ }
309
+
310
+ function initSync(module) {
311
+ if (wasm !== undefined) return wasm;
312
+
313
+
314
+ if (module !== undefined) {
315
+ if (Object.getPrototypeOf(module) === Object.prototype) {
316
+ ({module} = module)
317
+ } else {
318
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
319
+ }
320
+ }
321
+
322
+ const imports = __wbg_get_imports();
323
+ if (!(module instanceof WebAssembly.Module)) {
324
+ module = new WebAssembly.Module(module);
325
+ }
326
+ const instance = new WebAssembly.Instance(module, imports);
327
+ return __wbg_finalize_init(instance, module);
328
+ }
329
+
330
+ async function __wbg_init(module_or_path) {
331
+ if (wasm !== undefined) return wasm;
332
+
333
+
334
+ if (module_or_path !== undefined) {
335
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
336
+ ({module_or_path} = module_or_path)
337
+ } else {
338
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead')
339
+ }
340
+ }
341
+
342
+ if (module_or_path === undefined) {
343
+ module_or_path = new URL('rpic_wasm_geo_bg.wasm', import.meta.url);
344
+ }
345
+ const imports = __wbg_get_imports();
346
+
347
+ if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
348
+ module_or_path = fetch(module_or_path);
349
+ }
350
+
351
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
352
+
353
+ return __wbg_finalize_init(instance, module);
354
+ }
355
+
356
+ export { initSync, __wbg_init as default };
Binary file
@@ -0,0 +1,14 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ export const memory: WebAssembly.Memory;
4
+ export const compile_geo: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number];
5
+ export const geo_to_canvas: (a: number, b: number, c: number, d: number) => [number, number];
6
+ export const compile: (a: number, b: number) => [number, number];
7
+ export const compile_circuits: (a: number, b: number) => [number, number];
8
+ export const compile_scene: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number];
9
+ export const compile_with: (a: number, b: number, c: number, d: number) => [number, number];
10
+ export const __wbindgen_externrefs: WebAssembly.Table;
11
+ export const __wbindgen_malloc: (a: number, b: number) => number;
12
+ export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
13
+ export const __wbindgen_free: (a: number, b: number, c: number) => void;
14
+ export const __wbindgen_start: () => void;
Binary file