@knowvah/dot-engine 2.6.11 → 2.7.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 +1 -1
- package/dist/api.js +18 -4
- package/dist/api.js.map +3 -3
- package/dist/async/render-async.d.ts.map +1 -1
- package/dist/common/agerr.d.ts +37 -0
- package/dist/common/agerr.d.ts.map +1 -0
- package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
- package/dist/common/color.d.ts.map +1 -1
- package/dist/common/crand.d.ts +0 -20
- package/dist/common/crand.d.ts.map +1 -1
- package/dist/common/emit-once.d.ts +6 -0
- package/dist/common/emit-once.d.ts.map +1 -0
- package/dist/common/graph-init.d.ts.map +1 -1
- package/dist/common/htmltable-attr-warn.d.ts.map +1 -1
- package/dist/common/htmltable-emit-fill.d.ts +29 -2
- package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
- package/dist/common/htmltable-emit-rules.d.ts.map +1 -1
- package/dist/common/htmltable-emit.d.ts.map +1 -1
- package/dist/common/htmltable-lex-expat.d.ts +1 -6
- package/dist/common/htmltable-lex-expat.d.ts.map +1 -1
- package/dist/common/htmltable-pos-runs.d.ts +7 -4
- package/dist/common/htmltable-pos-runs.d.ts.map +1 -1
- package/dist/common/htmltable.d.ts +1 -7
- package/dist/common/htmltable.d.ts.map +1 -1
- package/dist/common/libm-trig.d.ts +5 -0
- package/dist/common/libm-trig.d.ts.map +1 -0
- package/dist/common/multicolor.d.ts +21 -13
- package/dist/common/multicolor.d.ts.map +1 -1
- package/dist/common/poly-gencode.d.ts +1 -2
- package/dist/common/poly-gencode.d.ts.map +1 -1
- package/dist/common/poly-init.d.ts.map +1 -1
- package/dist/common/postproc.d.ts.map +1 -1
- package/dist/common/random.d.ts.map +1 -1
- package/dist/common/render-state.d.ts +8 -0
- package/dist/common/render-state.d.ts.map +1 -0
- package/dist/common/splines-clip.d.ts +7 -0
- package/dist/common/splines-clip.d.ts.map +1 -1
- package/dist/common/splines-geom.d.ts +8 -0
- package/dist/common/splines-geom.d.ts.map +1 -1
- package/dist/common/splines-routespl.d.ts.map +1 -1
- package/dist/common/textmeasure-lookup.d.ts.map +1 -1
- package/dist/common/textmeasure.d.ts.map +1 -1
- package/dist/gvc/anchor.d.ts.map +1 -1
- package/dist/gvc/context.d.ts +6 -0
- package/dist/gvc/context.d.ts.map +1 -1
- package/dist/gvc/device-cluster.d.ts +15 -0
- package/dist/gvc/device-cluster.d.ts.map +1 -1
- package/dist/gvc/device-labels.d.ts +14 -0
- package/dist/gvc/device-labels.d.ts.map +1 -1
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/first-layer.d.ts +9 -0
- package/dist/gvc/first-layer.d.ts.map +1 -0
- package/dist/gvc/gvrender-set-style.d.ts +28 -0
- package/dist/gvc/gvrender-set-style.d.ts.map +1 -0
- package/dist/gvc/usershape.d.ts +18 -0
- package/dist/gvc/usershape.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3169 -1296
- package/dist/index.js.map +4 -4
- package/dist/layout/dot/cluster.d.ts.map +1 -1
- package/dist/layout/dot/compound.d.ts.map +1 -1
- package/dist/layout/dot/conc.d.ts.map +1 -1
- package/dist/layout/dot/decomp.d.ts.map +1 -1
- package/dist/layout/dot/edge-route-clip.d.ts +32 -14
- package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
- package/dist/layout/dot/edge-route-routing.d.ts.map +1 -1
- package/dist/layout/dot/mincross-build.d.ts.map +1 -1
- package/dist/layout/dot/mincross-cross.d.ts.map +1 -1
- package/dist/layout/dot/rank.d.ts.map +1 -1
- package/dist/layout/dot/splines.d.ts.map +1 -1
- package/dist/layout/fdp/index.d.ts.map +1 -1
- package/dist/layout/fdp/tlayout-parms.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts +11 -0
- package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts.map +1 -0
- package/dist/layout/neato/__fixtures__/hier-imds.d.ts +36 -0
- package/dist/layout/neato/__fixtures__/hier-imds.d.ts.map +1 -0
- package/dist/layout/neato/__fixtures__/hier-majorization.d.ts +32 -0
- package/dist/layout/neato/__fixtures__/hier-majorization.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
- package/dist/layout/neato/compute-hierarchy.d.ts +24 -0
- package/dist/layout/neato/compute-hierarchy.d.ts.map +1 -0
- package/dist/layout/neato/conjgrad-mkernel.d.ts +20 -0
- package/dist/layout/neato/conjgrad-mkernel.d.ts.map +1 -0
- package/dist/layout/neato/conjgrad.d.ts +19 -0
- package/dist/layout/neato/conjgrad.d.ts.map +1 -1
- package/dist/layout/neato/constrained-majorization.d.ts +26 -0
- package/dist/layout/neato/constrained-majorization.d.ts.map +1 -0
- package/dist/layout/neato/dijkstra.d.ts +33 -16
- package/dist/layout/neato/dijkstra.d.ts.map +1 -1
- package/dist/layout/neato/edge-len.d.ts.map +1 -1
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
- package/dist/layout/neato/gts-triangle.d.ts +14 -0
- package/dist/layout/neato/gts-triangle.d.ts.map +1 -0
- package/dist/layout/neato/init.d.ts +6 -22
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/kk.d.ts.map +1 -1
- package/dist/layout/neato/kkutils.d.ts +20 -0
- package/dist/layout/neato/kkutils.d.ts.map +1 -0
- package/dist/layout/neato/make-graph-data.d.ts +43 -0
- package/dist/layout/neato/make-graph-data.d.ts.map +1 -0
- package/dist/layout/neato/matrix-ops.d.ts +46 -0
- package/dist/layout/neato/matrix-ops.d.ts.map +1 -1
- package/dist/layout/neato/multispline-router.d.ts.map +1 -1
- package/dist/layout/neato/multispline.d.ts.map +1 -1
- package/dist/layout/neato/opt-arrangement.d.ts +20 -0
- package/dist/layout/neato/opt-arrangement.d.ts.map +1 -0
- package/dist/layout/neato/poly.d.ts.map +1 -1
- package/dist/layout/neato/quad-prog-solve.d.ts +44 -0
- package/dist/layout/neato/quad-prog-solve.d.ts.map +1 -0
- package/dist/layout/neato/sgd.d.ts.map +1 -1
- package/dist/layout/neato/smart-ini-x.d.ts +9 -0
- package/dist/layout/neato/smart-ini-x.d.ts.map +1 -0
- package/dist/layout/neato/splines.d.ts +1 -1
- package/dist/layout/neato/splines.d.ts.map +1 -1
- package/dist/layout/neato/start.d.ts +4 -1
- package/dist/layout/neato/start.d.ts.map +1 -1
- package/dist/layout/neato/stress.d.ts +8 -0
- package/dist/layout/neato/stress.d.ts.map +1 -1
- package/dist/model/edge.d.ts +1 -1
- package/dist/model/edge.d.ts.map +1 -1
- package/dist/model/lazy-attrs.d.ts +10 -0
- package/dist/model/lazy-attrs.d.ts.map +1 -0
- package/dist/model/node.d.ts +1 -1
- package/dist/model/node.d.ts.map +1 -1
- package/dist/ortho/index.d.ts.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/render/color-resolve.d.ts +49 -1
- package/dist/render/color-resolve.d.ts.map +1 -1
- package/dist/render/dot/edge-draw.d.ts +1 -1
- package/dist/render/dot/edge-draw.d.ts.map +1 -1
- package/dist/render/public.d.ts.map +1 -1
- package/dist/render/svg-edge-split.d.ts +2 -0
- package/dist/render/svg-edge-split.d.ts.map +1 -1
- package/dist/render/svg-multicolor.d.ts +2 -2
- package/dist/render/svg-multicolor.d.ts.map +1 -1
- package/dist/render/xdot-public.d.ts.map +1 -1
- package/dist/render.js +2799 -927
- package/dist/render.js.map +4 -4
- package/package.json +1 -1
- package/src/async/render-async.ts +6 -0
- package/src/common/agerr.ts +96 -0
- package/src/common/arrows-shapes-poly.ts +19 -5
- package/src/common/color.ts +38 -10
- package/src/common/crand.ts +8 -2
- package/src/common/emit-once.ts +27 -0
- package/src/common/graph-init.ts +2 -1
- package/src/common/htmltable-attr-warn.ts +3 -4
- package/src/common/htmltable-emit-fill.ts +104 -11
- package/src/common/htmltable-emit-rules.ts +2 -0
- package/src/common/htmltable-emit.ts +4 -3
- package/src/common/htmltable-lex-expat.ts +96 -23
- package/src/common/htmltable-pos-runs.ts +11 -6
- package/src/common/htmltable.ts +28 -117
- package/src/common/libm-trig.ts +43 -0
- package/src/common/multicolor.ts +88 -24
- package/src/common/poly-gencode.ts +30 -10
- package/src/common/poly-init.ts +20 -1
- package/src/common/postproc.ts +4 -1
- package/src/common/random.ts +11 -1
- package/src/common/render-state.ts +36 -0
- package/src/common/splines-clip.ts +15 -12
- package/src/common/splines-geom.ts +8 -4
- package/src/common/splines-routespl.ts +2 -1
- package/src/common/textmeasure-lookup.ts +9 -2
- package/src/common/textmeasure.ts +45 -2
- package/src/gvc/anchor.ts +1 -12
- package/src/gvc/context.ts +8 -0
- package/src/gvc/device-cluster.ts +93 -10
- package/src/gvc/device-labels.ts +27 -0
- package/src/gvc/device.ts +34 -8
- package/src/gvc/first-layer.ts +26 -0
- package/src/gvc/gvrender-set-style.ts +114 -0
- package/src/gvc/usershape.ts +26 -0
- package/src/index.ts +3 -0
- package/src/layout/dot/cluster.ts +2 -1
- package/src/layout/dot/compound.ts +3 -4
- package/src/layout/dot/conc.ts +6 -5
- package/src/layout/dot/decomp.ts +3 -0
- package/src/layout/dot/edge-route-clip.ts +46 -28
- package/src/layout/dot/edge-route-routing.ts +11 -3
- package/src/layout/dot/fastgr.ts +2 -2
- package/src/layout/dot/mincross-build.ts +2 -0
- package/src/layout/dot/mincross-cross.ts +2 -0
- package/src/layout/dot/rank.ts +2 -0
- package/src/layout/dot/splines.ts +2 -1
- package/src/layout/fdp/index.ts +2 -1
- package/src/layout/fdp/tlayout-parms.ts +9 -2
- package/src/layout/fdp/xlayout.ts +10 -2
- package/src/layout/neato/__fixtures__/dijkstra-f.ts +17 -0
- package/src/layout/neato/__fixtures__/hier/c1554.gv +33 -0
- package/src/layout/neato/__fixtures__/hier/c1554.plain +26 -0
- package/src/layout/neato/__fixtures__/hier/c1554.xdot +161 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.plain +11 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.plain +13 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.plain +8 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.plain +8 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.plain +13 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.plain +16 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.xdot +62 -0
- package/src/layout/neato/__fixtures__/hier-imds.ts +151 -0
- package/src/layout/neato/__fixtures__/hier-majorization.ts +145 -0
- package/src/layout/neato/cdt-surface.ts +12 -22
- package/src/layout/neato/compute-hierarchy.ts +90 -0
- package/src/layout/neato/conjgrad-mkernel.ts +141 -0
- package/src/layout/neato/conjgrad.ts +208 -0
- package/src/layout/neato/constrained-majorization.ts +479 -0
- package/src/layout/neato/dijkstra.ts +218 -92
- package/src/layout/neato/edge-len.ts +2 -1
- package/src/layout/neato/fdp-adjust.ts +3 -2
- package/src/layout/neato/gts-triangle.ts +42 -0
- package/src/layout/neato/init.ts +68 -68
- package/src/layout/neato/kk.ts +2 -1
- package/src/layout/neato/kkutils.ts +96 -0
- package/src/layout/neato/make-graph-data.ts +166 -0
- package/src/layout/neato/matrix-ops.ts +138 -0
- package/src/layout/neato/multispline-router.ts +3 -3
- package/src/layout/neato/multispline.ts +3 -2
- package/src/layout/neato/opt-arrangement.ts +86 -0
- package/src/layout/neato/poly.ts +3 -2
- package/src/layout/neato/quad-prog-solve.ts +489 -0
- package/src/layout/neato/sgd.ts +5 -2
- package/src/layout/neato/smart-ini-x.ts +503 -0
- package/src/layout/neato/splines.ts +4 -4
- package/src/layout/neato/start.ts +19 -14
- package/src/layout/neato/stress.ts +28 -0
- package/src/model/edge.ts +3 -2
- package/src/model/lazy-attrs.ts +36 -0
- package/src/model/node.ts +3 -2
- package/src/ortho/index.ts +2 -1
- package/src/parser/dot.js +4 -1
- package/src/parser/dot.pegjs +5 -1
- package/src/parser/index.ts +4 -3
- package/src/render/color-resolve.ts +193 -4
- package/src/render/dot/edge-draw.ts +6 -3
- package/src/render/dot.ts +1 -1
- package/src/render/public.ts +2 -0
- package/src/render/svg-edge-split.ts +6 -4
- package/src/render/svg-multicolor.ts +4 -3
- package/src/render/svg-parallel-edge.ts +2 -2
- package/src/render/xdot-public.ts +2 -0
|
@@ -270,12 +270,36 @@ function scanCdata(text: string, p: number, base: number, dataOf: CdataText, tok
|
|
|
270
270
|
return close < 0 ? -1 : close + CDATA_CLOSE.length;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
/**
|
|
274
|
-
|
|
273
|
+
/**
|
|
274
|
+
* A character expat's tokenizer rejects in character data (BT_NONXML in the
|
|
275
|
+
* UTF-8 table): controls other than tab, LF and CR, U+FFFE and U+FFFF, and a
|
|
276
|
+
* lone surrogate (the UTF-8 form of one is invalid). DEL is BT_OTHER, valid.
|
|
277
|
+
* @see expat lib/asciitab.h
|
|
278
|
+
* @see expat lib/xmltok_impl.c:PREFIX(contentTok) (BT_NONXML)
|
|
279
|
+
*/
|
|
280
|
+
const CONTENT_NON_XML = /[\x00-\x08\x0b\x0c\x0e-\x1f\ufffe\uffff]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Character data at `p` up to the next `<`: one `text` token, then the index it
|
|
284
|
+
* ends at. A reference cut off by the end of the data is not part of the token
|
|
285
|
+
* (`held`): expat waits for the characters that settle it. A BT_NONXML character
|
|
286
|
+
* ends the data token; the next call finds it an invalid token (`invalid`).
|
|
287
|
+
* @see expat lib/xmltok_impl.c:PREFIX(contentTok) (XML_TOK_PARTIAL, BT_NONXML)
|
|
288
|
+
*/
|
|
289
|
+
function scanText(
|
|
290
|
+
text: string, p: number, base: number, tokens: ExpatToken[],
|
|
291
|
+
): { at: number; held: boolean; invalid?: number } {
|
|
275
292
|
const e = text.indexOf('<', p);
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
293
|
+
const slice = text.slice(p, e < 0 ? text.length : e);
|
|
294
|
+
const bad = CONTENT_NON_XML.exec(slice)?.index;
|
|
295
|
+
if (bad !== undefined) {
|
|
296
|
+
if (bad > 0) tokens.push({ kind: 'text', start: base + p, end: base + p + bad });
|
|
297
|
+
return { at: p + bad, held: false, invalid: p + bad };
|
|
298
|
+
}
|
|
299
|
+
const from = e < 0 ? heldRefStart(slice) : undefined;
|
|
300
|
+
const end = from === undefined ? p + slice.length : p + from;
|
|
301
|
+
if (end > p) tokens.push({ kind: 'text', start: base + p, end: base + end });
|
|
302
|
+
return { at: end, held: from !== undefined };
|
|
279
303
|
}
|
|
280
304
|
|
|
281
305
|
/** The fed text of the label source between two offsets. */
|
|
@@ -295,7 +319,10 @@ function scanContent(text: string, base: number, inCdata: boolean, dataOf: Cdata
|
|
|
295
319
|
continue;
|
|
296
320
|
}
|
|
297
321
|
if (text[p] !== '<') {
|
|
298
|
-
|
|
322
|
+
const scanned = scanText(text, p, base, tokens);
|
|
323
|
+
if (scanned.invalid !== undefined) return { tokens, consumed: scanned.at, invalid: base + scanned.invalid, inCdata: false };
|
|
324
|
+
if (scanned.held) return { tokens, consumed: scanned.at, inCdata: false };
|
|
325
|
+
p = scanned.at;
|
|
299
326
|
continue;
|
|
300
327
|
}
|
|
301
328
|
const { scan, kind } = scanMarkup(text, p);
|
|
@@ -408,33 +435,79 @@ export class ExpatFeed {
|
|
|
408
435
|
/** Entities expat knows without a DTD; `scanEntity` leaves them as written. */
|
|
409
436
|
const XML_PREDEFINED_ENTITIES = new Set(['lt', 'gt', 'amp', 'quot', 'apos']);
|
|
410
437
|
|
|
411
|
-
|
|
438
|
+
/** An `&name;` that `scanEntity` rewrites: 2 to 8 characters (alphanumeric) that name an HTML entity. */
|
|
439
|
+
const SCANNED_ENTITY = /^&([^;&<]*);/;
|
|
412
440
|
|
|
413
|
-
|
|
441
|
+
/**
|
|
442
|
+
* The reference the content tokenizer reads at the `&` at `t`: the index after
|
|
443
|
+
* its `;`, or the index of the character that makes it an invalid token (the
|
|
444
|
+
* end of `text` when the reference runs out: `</HTML>` or another tag follows).
|
|
445
|
+
* Runs the attribute value scanner over the reference alone, closed by a quote,
|
|
446
|
+
* which is scanRef on the same bytes.
|
|
447
|
+
* @see expat lib/xmltok_impl.c:PREFIX(contentTok) (BT_AMP)
|
|
448
|
+
* @see expat lib/xmltok_impl.c:PREFIX(scanRef)
|
|
449
|
+
* @see expat lib/xmltok_impl.c:PREFIX(scanCharRef)
|
|
450
|
+
* @see expat lib/xmltok_impl.c:PREFIX(scanHexCharRef)
|
|
451
|
+
*/
|
|
452
|
+
function contentRef(text: string, t: number): { end: number } | { invalid: number } {
|
|
453
|
+
const semi = text.indexOf(';', t);
|
|
454
|
+
const ref = text.slice(t, semi < 0 ? text.length : semi + 1);
|
|
455
|
+
const scan = scanAttValue(`"${ref}"`, 0);
|
|
456
|
+
if ('close' in scan) return { end: t + scan.close - 1 };
|
|
457
|
+
return { invalid: 'invalid' in scan ? t + scan.invalid - 1 : text.length };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** A reference problem of character data: expat's error, where its event pointer is, and the `&` it began at. */
|
|
461
|
+
interface RefProblem {
|
|
462
|
+
message: string;
|
|
463
|
+
offset: number;
|
|
464
|
+
from: number;
|
|
465
|
+
}
|
|
414
466
|
|
|
415
467
|
/**
|
|
416
|
-
* The first reference of character data that expat rejects
|
|
417
|
-
*
|
|
418
|
-
* a character reference XML
|
|
419
|
-
*
|
|
468
|
+
* The first reference of character data that expat rejects: an invalid token
|
|
469
|
+
* (a bare `&`, `&#;`, `A`, `Aq`, ..), a `&name;` scanEntity leaves
|
|
470
|
+
* alone (neither an HTML entity nor predefined), or a character reference XML
|
|
471
|
+
* does not allow. An invalid token at the very end of `text` is a reference
|
|
472
|
+
* expat is still waiting on.
|
|
473
|
+
* @see lib/common/utils.c:scanEntity
|
|
474
|
+
* @see expat lib/xmlparse.c:doContent (XML_TOK_INVALID, XML_TOK_ENTITY_REF, XML_TOK_CHAR_REF)
|
|
420
475
|
*/
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
476
|
+
function firstRefProblem(text: string): RefProblem | undefined {
|
|
477
|
+
let t = text.indexOf('&');
|
|
478
|
+
while (t >= 0) {
|
|
479
|
+
if (text[t + 1] !== '#') {
|
|
480
|
+
const name = SCANNED_ENTITY.exec(text.slice(t))?.[1];
|
|
481
|
+
if (name !== undefined && name.length >= 2 && Object.hasOwn(HTML_ENTITIES, name)) {
|
|
482
|
+
t = text.indexOf('&', t + name.length + 2);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
427
485
|
}
|
|
428
|
-
const
|
|
429
|
-
if (
|
|
430
|
-
const key = text.slice(t + 1,
|
|
431
|
-
if (
|
|
432
|
-
return { message:
|
|
486
|
+
const ref = contentRef(text, t);
|
|
487
|
+
if ('invalid' in ref) return { message: XML_ERROR_INVALID_TOKEN, offset: ref.invalid, from: t };
|
|
488
|
+
const key = text.slice(t + 1, ref.end - 1);
|
|
489
|
+
if (text[t + 1] === '#') {
|
|
490
|
+
if (charRefNumber(`&${key};`) < 0) return { message: XML_ERROR_BAD_CHAR_REF, offset: t, from: t };
|
|
491
|
+
} else if (!XML_PREDEFINED_ENTITIES.has(key)) {
|
|
492
|
+
return { message: XML_ERROR_UNDEFINED_ENTITY, offset: t, from: t };
|
|
433
493
|
}
|
|
494
|
+
t = text.indexOf('&', ref.end);
|
|
434
495
|
}
|
|
435
496
|
return undefined;
|
|
436
497
|
}
|
|
437
498
|
|
|
499
|
+
/** The first reference of character data that expat rejects, and the offset of its event pointer. */
|
|
500
|
+
export function textRefError(text: string): { message: string; offset: number } | undefined {
|
|
501
|
+
const problem = firstRefProblem(text);
|
|
502
|
+
return problem === undefined ? undefined : { message: problem.message, offset: problem.offset };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Where a reference that runs to the end of `text` begins: expat holds it until more data arrives. */
|
|
506
|
+
function heldRefStart(text: string): number | undefined {
|
|
507
|
+
const problem = firstRefProblem(text);
|
|
508
|
+
return problem?.message === XML_ERROR_INVALID_TOKEN && problem.offset === text.length ? problem.from : undefined;
|
|
509
|
+
}
|
|
510
|
+
|
|
438
511
|
export interface TagChunk {
|
|
439
512
|
close: boolean;
|
|
440
513
|
selfClose: boolean;
|
|
@@ -48,7 +48,10 @@ export interface HtmlFontInfo {
|
|
|
48
48
|
fontcolor: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
/** The label font defaults the line builder needs (sizing env or placement finfo). */
|
|
52
|
+
export type LabelFont = Pick<HtmlFontInfo, 'fontname' | 'fontsize'>;
|
|
53
|
+
|
|
54
|
+
export interface LineRun {
|
|
52
55
|
items: HtmlTextItem[];
|
|
53
56
|
width: number;
|
|
54
57
|
/** Max raw font size over items (C mxfsize). */
|
|
@@ -84,7 +87,7 @@ export function itemFontFlags(item: HtmlTextItem): number {
|
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
/** Resolved face/size for an item, defaulting to the inherited finfo. */
|
|
87
|
-
function itemFace(item: HtmlTextItem, finfo:
|
|
90
|
+
function itemFace(item: HtmlTextItem, finfo: LabelFont): { fs: number; face: string } {
|
|
88
91
|
return {
|
|
89
92
|
fs: item.fontSize !== undefined ? item.fontSize : finfo.fontsize,
|
|
90
93
|
face: item.fontFace !== undefined ? item.fontFace : finfo.fontname,
|
|
@@ -95,7 +98,7 @@ function itemFace(item: HtmlTextItem, finfo: HtmlFontInfo): { fs: number; face:
|
|
|
95
98
|
interface ItemMetric { w: number; h: number; fs: number; yoffsetCenterline?: number; yoffsetLayout?: number; }
|
|
96
99
|
|
|
97
100
|
/** Measure one text item; null when the item carries no text. */
|
|
98
|
-
function measureItem(item: HtmlTextItem, finfo:
|
|
101
|
+
function measureItem(item: HtmlTextItem, finfo: LabelFont, measurer: TextMeasurer): ItemMetric | null {
|
|
99
102
|
if (item.text === undefined) return null;
|
|
100
103
|
const { fs, face } = itemFace(item, finfo);
|
|
101
104
|
const flags = itemFontFlags(item);
|
|
@@ -142,11 +145,13 @@ function accumulateItem(cur: LineRun, item: HtmlTextItem, m: ItemMetric): void {
|
|
|
142
145
|
* in the current font (consecutive BRs = a real blank line with height);
|
|
143
146
|
* - at end of text a line is added ONLY if items remain — a trailing BR does
|
|
144
147
|
* NOT create a phantom empty line (this over-sized e.g. html2's node c).
|
|
148
|
+
* The single builder: size_html_txt (sizing) and emit_htextspans (placement)
|
|
149
|
+
* both read these runs, as C reads one htextspan_t list.
|
|
145
150
|
* @see lib/common/htmlparse.y:appendFLineList / mkText
|
|
146
151
|
*/
|
|
147
152
|
export function buildLineRuns(
|
|
148
153
|
texts: HtmlText[],
|
|
149
|
-
finfo:
|
|
154
|
+
finfo: LabelFont,
|
|
150
155
|
measurer: TextMeasurer,
|
|
151
156
|
): LineRun[] {
|
|
152
157
|
const runs: LineRun[] = [];
|
|
@@ -181,7 +186,7 @@ export function buildLineRuns(
|
|
|
181
186
|
*/
|
|
182
187
|
function runSimpleKey(
|
|
183
188
|
run: LineRun,
|
|
184
|
-
finfo:
|
|
189
|
+
finfo: LabelFont,
|
|
185
190
|
): { fs: number; face: string } | null | undefined {
|
|
186
191
|
if (run.items.length > 1) return null;
|
|
187
192
|
const item = run.items[0];
|
|
@@ -195,7 +200,7 @@ function runSimpleKey(
|
|
|
195
200
|
* uniform resolved face/size across lines.
|
|
196
201
|
* @see lib/common/htmltable.c:size_html_txt (lines 946-986)
|
|
197
202
|
*/
|
|
198
|
-
export function runsAreSimple(runs: LineRun[], finfo:
|
|
203
|
+
export function runsAreSimple(runs: LineRun[], finfo: LabelFont): boolean {
|
|
199
204
|
let prevFs = -1;
|
|
200
205
|
let prevFace: string | null = null;
|
|
201
206
|
for (const run of runs) {
|
package/src/common/htmltable.ts
CHANGED
|
@@ -20,11 +20,13 @@ export type { TextMeasurer } from './textmeasure.js';
|
|
|
20
20
|
|
|
21
21
|
import type {
|
|
22
22
|
HtmlCell, HtmlCellContent, HtmlImage, HtmlLabel, HtmlTable, HtmlText,
|
|
23
|
-
|
|
23
|
+
ImageSizer,
|
|
24
24
|
} from './htmltable-types.js';
|
|
25
|
-
import { findImageSize } from '../gvc/usershape.js';
|
|
25
|
+
import { findImageSize, imageIsSupplied, imageOpenWarning } from '../gvc/usershape.js';
|
|
26
|
+
import { registerRenderState } from './render-state.js';
|
|
26
27
|
import { PointSet } from './pointset.js';
|
|
27
28
|
import type { TextMeasurer } from './textmeasure.js';
|
|
29
|
+
import { buildLineRuns, runsAreSimple } from './htmltable-pos-runs.js';
|
|
28
30
|
import { withTableFont } from './htmltable-fontinfo.js';
|
|
29
31
|
import { agerrLine, type StderrLines } from './htmltable-attr-warn.js';
|
|
30
32
|
|
|
@@ -90,108 +92,6 @@ export interface HtmlSizeEnv {
|
|
|
90
92
|
|
|
91
93
|
const defaultSizeEnv = (): HtmlSizeEnv => ({ fontsize: DEFAULT_FONTSIZE, fontname: '' });
|
|
92
94
|
|
|
93
|
-
/** One measured text line (a C "span"). @see size_html_txt */
|
|
94
|
-
interface SizedLine {
|
|
95
|
-
items: HtmlTextItem[];
|
|
96
|
-
width: number;
|
|
97
|
-
/** Max metric line height over items (C mxysize). */
|
|
98
|
-
mxysize: number;
|
|
99
|
-
/** Max raw font size over items (C mxfsize). */
|
|
100
|
-
mxfsize: number;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const itemFont = (item: HtmlTextItem, env: HtmlSizeEnv): { fs: number; face: string } => ({
|
|
104
|
-
fs: item.fontSize !== undefined ? item.fontSize : env.fontsize,
|
|
105
|
-
face: item.fontFace !== undefined ? item.fontFace : env.fontname,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const measureItem = (
|
|
109
|
-
item: HtmlTextItem,
|
|
110
|
-
measurer: TextMeasurer,
|
|
111
|
-
env: HtmlSizeEnv,
|
|
112
|
-
): { w: number; h: number } => {
|
|
113
|
-
const { fs, face } = itemFont(item, env);
|
|
114
|
-
// Variant widths per run flags. @see lib/common/htmltable.c:size_html_txt
|
|
115
|
-
const flags = (item.bold === true || item.italic === true)
|
|
116
|
-
? { bold: item.bold === true, italic: item.italic === true }
|
|
117
|
-
: undefined;
|
|
118
|
-
return measurer.measure(item.text ?? '', face, fs, flags);
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Split items into measured lines at <BR/>, mirroring C's parser exactly
|
|
123
|
-
* (htmlparse.y appendFLineList/mkText): a BR with no accumulated items yields
|
|
124
|
-
* a line with one empty-string item in the current font (consecutive BRs = a
|
|
125
|
-
* real blank line with height); at end of text a line is added ONLY if items
|
|
126
|
-
* remain — a trailing BR does NOT create a phantom empty line. Must stay in
|
|
127
|
-
* lockstep with buildLineRuns (htmltable-pos-runs.ts), the placement twin.
|
|
128
|
-
* @see lib/common/htmlparse.y:appendFLineList / mkText
|
|
129
|
-
* @see lib/common/htmltable.c:size_html_txt
|
|
130
|
-
*/
|
|
131
|
-
const splitSizedLines = (
|
|
132
|
-
texts: HtmlText[],
|
|
133
|
-
measurer: TextMeasurer,
|
|
134
|
-
env: HtmlSizeEnv,
|
|
135
|
-
): SizedLine[] => {
|
|
136
|
-
const lines: SizedLine[] = [];
|
|
137
|
-
let cur: SizedLine = { items: [], width: 0, mxysize: 0, mxfsize: 0 };
|
|
138
|
-
const accumulate = (item: HtmlTextItem): void => {
|
|
139
|
-
const sz = measureItem(item, measurer, env);
|
|
140
|
-
cur.items.push(item);
|
|
141
|
-
cur.width += sz.w;
|
|
142
|
-
cur.mxysize = Math.max(cur.mxysize, sz.h);
|
|
143
|
-
cur.mxfsize = Math.max(cur.mxfsize, itemFont(item, env).fs);
|
|
144
|
-
};
|
|
145
|
-
const flush = (br?: HtmlTextItem): void => {
|
|
146
|
-
if (cur.items.length === 0 && br !== undefined) {
|
|
147
|
-
// C: empty fitemList on a BR → one ""-item span in the current font.
|
|
148
|
-
const { br: _b, brAlign: _a, ...fontProps } = br;
|
|
149
|
-
accumulate({ text: '', ...fontProps });
|
|
150
|
-
}
|
|
151
|
-
lines.push(cur);
|
|
152
|
-
cur = { items: [], width: 0, mxysize: 0, mxfsize: 0 };
|
|
153
|
-
};
|
|
154
|
-
for (const txt of texts) {
|
|
155
|
-
for (const item of txt.items) {
|
|
156
|
-
if (item.br) { flush(item); continue; }
|
|
157
|
-
if (item.text === undefined) continue;
|
|
158
|
-
accumulate(item);
|
|
159
|
-
}
|
|
160
|
-
if (cur.items.length > 0) flush(); // C mkText: only when items remain
|
|
161
|
-
}
|
|
162
|
-
return lines;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
/** Does an item carry any font flag (C textfont_t.flags)? */
|
|
166
|
-
const itemHasFlags = (i: HtmlTextItem): boolean =>
|
|
167
|
-
i.bold === true || i.italic === true || i.underline === true ||
|
|
168
|
-
i.strikethrough === true || i.overline === true ||
|
|
169
|
-
i.subscript === true || i.superscript === true;
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* C's "simple" test: one item per line, no font flags, uniform
|
|
173
|
-
* resolved face and size across all lines.
|
|
174
|
-
* @see lib/common/htmltable.c:size_html_txt (lines 946-986)
|
|
175
|
-
*/
|
|
176
|
-
export const htmlTextIsSimple = (texts: HtmlText[], env: HtmlSizeEnv): boolean => {
|
|
177
|
-
let prevFs = -1;
|
|
178
|
-
let prevFace: string | null = null;
|
|
179
|
-
let count = 0;
|
|
180
|
-
for (const txt of texts) {
|
|
181
|
-
for (const item of txt.items) {
|
|
182
|
-
if (item.br) { count = 0; continue; }
|
|
183
|
-
if (item.text === undefined) continue;
|
|
184
|
-
if (++count > 1 || itemHasFlags(item)) return false;
|
|
185
|
-
const { fs, face } = itemFont(item, env);
|
|
186
|
-
if (prevFs < 0) prevFs = fs;
|
|
187
|
-
else if (fs !== prevFs) return false;
|
|
188
|
-
if (prevFace === null) prevFace = face;
|
|
189
|
-
else if (face !== prevFace) return false;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return true;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
95
|
/**
|
|
196
96
|
* Size a text block. C: per line lsize = mxysize when simple, else the
|
|
197
97
|
* raw max font size (mxfsize); a single-line block always uses mxysize.
|
|
@@ -202,16 +102,17 @@ const sizeTextContent = (
|
|
|
202
102
|
measurer: TextMeasurer,
|
|
203
103
|
env: HtmlSizeEnv = defaultSizeEnv(),
|
|
204
104
|
): { w: number; h: number } => {
|
|
205
|
-
|
|
206
|
-
const
|
|
105
|
+
// The one line builder, shared with placement (htmltable-pos-runs.ts).
|
|
106
|
+
const lines = buildLineRuns(texts, env, measurer);
|
|
107
|
+
const simple = runsAreSimple(lines, env);
|
|
207
108
|
let w = 0;
|
|
208
109
|
let h = 0;
|
|
209
110
|
for (const ln of lines) {
|
|
210
111
|
w = Math.max(w, ln.width);
|
|
211
|
-
const lsize = simple ? ln.
|
|
112
|
+
const lsize = simple ? ln.height : ln.fontSize;
|
|
212
113
|
h += ln.items.length > 0 ? lsize : DEFAULT_FONTSIZE;
|
|
213
114
|
}
|
|
214
|
-
if (lines.length === 1) h = lines[0]!.items.length > 0 ? lines[0]!.
|
|
115
|
+
if (lines.length === 1) h = lines[0]!.items.length > 0 ? lines[0]!.height : DEFAULT_FONTSIZE;
|
|
215
116
|
return { w, h };
|
|
216
117
|
};
|
|
217
118
|
|
|
@@ -229,25 +130,28 @@ const sizeTextContent = (
|
|
|
229
130
|
* @see lib/common/htmltable.c:size_html_img (line 1080)
|
|
230
131
|
*/
|
|
231
132
|
/** Images whose sizing failed: size_html_img's rv = 1 on every later call. */
|
|
232
|
-
const missingImages = new WeakSet<HtmlImage>();
|
|
133
|
+
const missingImages = { set: new WeakSet<HtmlImage>() };
|
|
233
134
|
|
|
234
135
|
const sizeHtmlImg = (img: HtmlImage, { imageSizer, stderr }: HtmlSizeEnv): number => {
|
|
235
136
|
// C sizes each image once (processTbl); the port's size and pos passes
|
|
236
137
|
// share layoutHtmlTable, so guard against re-sizing (and re-warning). The
|
|
237
138
|
// returned rv still follows C on every call.
|
|
238
|
-
if (img.width !== undefined && img.height !== undefined) return missingImages.has(img) ? 1 : 0;
|
|
139
|
+
if (img.width !== undefined && img.height !== undefined) return missingImages.set.has(img) ? 1 : 0;
|
|
239
140
|
const sz = imageSizer !== undefined ? imageSizer(img.src) : findImageSize(img.src);
|
|
240
141
|
if (sz !== null) {
|
|
241
142
|
img.width = sz.w; img.height = sz.h;
|
|
242
143
|
return 0;
|
|
243
144
|
}
|
|
244
|
-
// Missing-image:
|
|
145
|
+
// Missing-image: gvusershape_open's warning (unless bytes are supplied),
|
|
146
|
+
// then zero size + the Error.
|
|
147
|
+
// @see lib/gvc/gvusershape.c:gvusershape_file_access
|
|
148
|
+
if (!imageIsSupplied(img.src, imageSizer)) agerrLine(stderr, `Warning: ${imageOpenWarning(img.src)}`);
|
|
245
149
|
// C: agerrorf("No or improper image file=\"%s\"\n", img->src) — agerr
|
|
246
150
|
// prefixes "Error: " on stderr.
|
|
247
151
|
agerrLine(stderr, `Error: No or improper image file="${img.src}"`);
|
|
248
152
|
img.width = 0;
|
|
249
153
|
img.height = 0;
|
|
250
|
-
missingImages.add(img);
|
|
154
|
+
missingImages.set.add(img);
|
|
251
155
|
return 1;
|
|
252
156
|
};
|
|
253
157
|
|
|
@@ -318,7 +222,14 @@ const WARN_TABLE_UNSPECIFIED = 'Warning: fixed table size with unspecified width
|
|
|
318
222
|
* C sizes each cell/table exactly once; the port's size and pos passes both
|
|
319
223
|
* run layoutHtmlTable, so the size warnings are emitted on first sight only.
|
|
320
224
|
*/
|
|
321
|
-
const sizeWarned = new WeakSet<object>();
|
|
225
|
+
const sizeWarned = { set: new WeakSet<object>() };
|
|
226
|
+
|
|
227
|
+
// Native re-parses per run, so each render starts with both sets empty (AD-7);
|
|
228
|
+
// a WeakSet cannot be cleared, so the holder's field is swapped.
|
|
229
|
+
registerRenderState(() => {
|
|
230
|
+
missingImages.set = new WeakSet<HtmlImage>();
|
|
231
|
+
sizeWarned.set = new WeakSet<object>();
|
|
232
|
+
});
|
|
322
233
|
|
|
323
234
|
/**
|
|
324
235
|
* FIXED_FLAG branch of size_html_cell: C's rv (set at the warning sites; the
|
|
@@ -358,8 +269,8 @@ const getCellSize = (
|
|
|
358
269
|
// @see lib/common/htmltable.c:size_html_cell (FIXED_FLAG branch + box.UR MAX)
|
|
359
270
|
let szx = cw + margin;
|
|
360
271
|
let szy = ch + margin;
|
|
361
|
-
const warn = !sizeWarned.has(cell);
|
|
362
|
-
sizeWarned.add(cell);
|
|
272
|
+
const warn = !sizeWarned.set.has(cell);
|
|
273
|
+
sizeWarned.set.add(cell);
|
|
363
274
|
const fixed = fixedCellCheck(cell, { w: szx, h: szy }, warn, env.stderr);
|
|
364
275
|
rv |= fixed.rv;
|
|
365
276
|
if (fixed.zero) { szx = 0; szy = 0; }
|
|
@@ -505,8 +416,8 @@ export const layoutHtmlTable = (
|
|
|
505
416
|
* @see lib/common/htmltable.c:size_html_tbl (:1683, :1689)
|
|
506
417
|
*/
|
|
507
418
|
const warnFixedTable = (tbl: HtmlTable, dims: { wd: number; ht: number }, out: StderrLines | undefined): number => {
|
|
508
|
-
const warn = !sizeWarned.has(tbl);
|
|
509
|
-
sizeWarned.add(tbl);
|
|
419
|
+
const warn = !sizeWarned.set.has(tbl);
|
|
420
|
+
sizeWarned.set.add(tbl);
|
|
510
421
|
if (tbl.fixedsize !== true) return 0;
|
|
511
422
|
if (tbl.width && tbl.height) {
|
|
512
423
|
if (tbl.width >= dims.wd && tbl.height >= dims.ht) return 0;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `cos`/`sin` as the native build host's libm computes them, at the only
|
|
5
|
+
* inputs Graphviz feeds with constant arguments: the corner angles
|
|
6
|
+
* 2π·(i ∓ 0.5)/8 of neatosplines.c's circumscribed ellipse 8-gon.
|
|
7
|
+
*
|
|
8
|
+
* V8's Math.sin/Math.cos are not correctly rounded everywhere, and the
|
|
9
|
+
* result depends on the CPU: at 9π/8 Node 22-26 on arm64 return
|
|
10
|
+
* -0.3826834323650896 (x64 and Node 20 return the correctly rounded value)
|
|
11
|
+
* where the reference libm (macOS arm64 libSystem) returns -0.38268343236508967.
|
|
12
|
+
* The ulp moves an obstacle corner off its node's centre line, which flips
|
|
13
|
+
* knife-edge triangulations in the multispline router. The table holds the
|
|
14
|
+
* host values for those nine angles (printed with `%a` by a clang -O2
|
|
15
|
+
* harness of the same expression, which also reproduces the oracle's
|
|
16
|
+
* makeObstacle vertices); any other input falls back to Math.cos/Math.sin.
|
|
17
|
+
*
|
|
18
|
+
* @see lib/neatogen/neatosplines.c:circumscribed_polygon_corner_about_ellipse
|
|
19
|
+
* @see docs/known-divergences.md (host libm)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** angle → [cos, sin] from the host libm, angles 2π·(k + 0.5)/8, k = -1..7. */
|
|
23
|
+
const OCTAGON_TRIG: ReadonlyMap<number, readonly [number, number]> = new Map([
|
|
24
|
+
[-0.39269908169872414, [0.9238795325112867, -0.3826834323650898]],
|
|
25
|
+
[0.39269908169872414, [0.9238795325112867, 0.3826834323650898]],
|
|
26
|
+
[1.1780972450961724, [0.38268343236508984, 0.9238795325112867]],
|
|
27
|
+
[1.9634954084936207, [-0.3826834323650897, 0.9238795325112867]],
|
|
28
|
+
[2.748893571891069, [-0.9238795325112867, 0.3826834323650899]],
|
|
29
|
+
[3.5342917352885173, [-0.9238795325112868, -0.38268343236508967]],
|
|
30
|
+
[4.319689898685965, [-0.38268343236509034, -0.9238795325112865]],
|
|
31
|
+
[5.105088062083414, [0.38268343236509, -0.9238795325112866]],
|
|
32
|
+
[5.890486225480862, [0.9238795325112865, -0.3826834323650904]],
|
|
33
|
+
] as const);
|
|
34
|
+
|
|
35
|
+
/** libm `cos(x)` of the native build host (table above, else Math.cos). */
|
|
36
|
+
export function libmCos(x: number): number {
|
|
37
|
+
return OCTAGON_TRIG.get(x)?.[0] ?? Math.cos(x);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** libm `sin(x)` of the native build host (table above, else Math.sin). */
|
|
41
|
+
export function libmSin(x: number): number {
|
|
42
|
+
return OCTAGON_TRIG.get(x)?.[1] ?? Math.sin(x);
|
|
43
|
+
}
|
package/src/common/multicolor.ts
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* @see lib/common/emit.c:470 parseSegs
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { agerr, agerrorf, agwarningf } from './agerr.js';
|
|
17
|
+
import type { Edge } from '../model/edge.js';
|
|
18
|
+
import { registerRenderState } from './render-state.js';
|
|
19
|
+
|
|
16
20
|
// ---------------------------------------------------------------------------
|
|
17
21
|
// Types
|
|
18
22
|
// ---------------------------------------------------------------------------
|
|
@@ -38,6 +42,28 @@ export interface ColorSeg {
|
|
|
38
42
|
// Constants
|
|
39
43
|
// ---------------------------------------------------------------------------
|
|
40
44
|
|
|
45
|
+
/**
|
|
46
|
+
* C's `static atomic_flag warned` in parseSegs: set by the first Total-size
|
|
47
|
+
* warning or Illegal-value error in the process, silencing both afterwards.
|
|
48
|
+
* Reset per render (AD-4).
|
|
49
|
+
* @see lib/common/emit.c:470 parseSegs
|
|
50
|
+
*/
|
|
51
|
+
const parseSegsState = { warned: false };
|
|
52
|
+
|
|
53
|
+
registerRenderState(() => {
|
|
54
|
+
parseSegsState.warned = false;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** `atomic_flag_test_and_set(&warned)` negated: true when this call claimed it. */
|
|
58
|
+
function claimWarned(): boolean {
|
|
59
|
+
if (parseSegsState.warned) return false;
|
|
60
|
+
parseSegsState.warned = true;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A claim that never fires: parses without touching or consuming the flag. */
|
|
65
|
+
const claimNever = (): boolean => false;
|
|
66
|
+
|
|
41
67
|
/** @see lib/common/emit.c:451 EPS */
|
|
42
68
|
const EPS = 1e-5;
|
|
43
69
|
|
|
@@ -76,7 +102,7 @@ function appendSeg(
|
|
|
76
102
|
token: string,
|
|
77
103
|
left: number,
|
|
78
104
|
segs: ColorSeg[],
|
|
79
|
-
|
|
105
|
+
claim: () => boolean,
|
|
80
106
|
): { left: number; rval: 0 | 3 } | null {
|
|
81
107
|
const { color, v } = getSegLen(token);
|
|
82
108
|
if (v < 0) return null;
|
|
@@ -85,10 +111,7 @@ function appendSeg(
|
|
|
85
111
|
let rval: 0 | 3 = 0;
|
|
86
112
|
const del = t - left;
|
|
87
113
|
if (del > 0) {
|
|
88
|
-
if (!aeq0(del) &&
|
|
89
|
-
warnedExceeded.v = true;
|
|
90
|
-
rval = 3;
|
|
91
|
-
}
|
|
114
|
+
if (!aeq0(del) && claim()) rval = 3;
|
|
92
115
|
t = left;
|
|
93
116
|
}
|
|
94
117
|
left -= t;
|
|
@@ -128,34 +151,20 @@ function trimTrailing(segs: ColorSeg[]): void {
|
|
|
128
151
|
// ---------------------------------------------------------------------------
|
|
129
152
|
|
|
130
153
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* Fractions are optional and non-negative; their sum must not exceed 1.
|
|
134
|
-
* Segments without an explicit fraction share the remaining weight equally;
|
|
135
|
-
* if none exist the remainder is added to the last segment.
|
|
136
|
-
*
|
|
137
|
-
* Return codes (mirror C):
|
|
138
|
-
* 0 → ok; 1 → silent error; 2 → error with message; 3 → warning (sum > 1).
|
|
139
|
-
* On rv 1 or 2 the returned segs array is empty.
|
|
140
|
-
*
|
|
154
|
+
* The parse of `parseSegs` with the once-per-process flag abstracted as `claim`.
|
|
141
155
|
* @see lib/common/emit.c:470 parseSegs
|
|
142
156
|
*/
|
|
143
|
-
|
|
157
|
+
function scanSegs(
|
|
144
158
|
colorlist: string,
|
|
159
|
+
claim: () => boolean,
|
|
145
160
|
): { segs: ColorSeg[]; error: 0 | 1 | 2 | 3 } {
|
|
146
161
|
const segs: ColorSeg[] = [];
|
|
147
162
|
let left = 1;
|
|
148
163
|
let rval: 0 | 3 = 0;
|
|
149
|
-
let badFloatCount = 0;
|
|
150
|
-
const warnedExceeded = { v: false };
|
|
151
164
|
|
|
152
165
|
for (const token of colorlist.split(':')) {
|
|
153
|
-
const result = appendSeg(token, left, segs,
|
|
154
|
-
if (result === null) {
|
|
155
|
-
const errCode: 1 | 2 = badFloatCount === 0 ? 2 : 1;
|
|
156
|
-
badFloatCount++;
|
|
157
|
-
return { segs: [], error: errCode };
|
|
158
|
-
}
|
|
166
|
+
const result = appendSeg(token, left, segs, claim);
|
|
167
|
+
if (result === null) return { segs: [], error: claim() ? 2 : 1 };
|
|
159
168
|
if (result.rval === 3) rval = 3;
|
|
160
169
|
left = result.left;
|
|
161
170
|
if (aeq0(left)) { left = 0; break; }
|
|
@@ -165,3 +174,58 @@ export function parseSegs(
|
|
|
165
174
|
trimTrailing(segs);
|
|
166
175
|
return { segs, error: rval };
|
|
167
176
|
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Parse `"c1;f1:c2;f2:…"` into segments.
|
|
180
|
+
*
|
|
181
|
+
* Fractions are optional and non-negative; their sum must not exceed 1.
|
|
182
|
+
* Segments without an explicit fraction share the remaining weight equally;
|
|
183
|
+
* if none exist the remainder is added to the last segment.
|
|
184
|
+
*
|
|
185
|
+
* Return codes (mirror C):
|
|
186
|
+
* 0 → ok; 1 → silent error; 2 → error with message; 3 → warning (sum > 1).
|
|
187
|
+
* On rv 1 or 2 the returned segs array is empty.
|
|
188
|
+
*
|
|
189
|
+
* The rv 2 error and rv 3 warning are printed here, once per process (the
|
|
190
|
+
* shared `warned` flag). C's callers then append an AGPREV line for rv > 1;
|
|
191
|
+
* `context` supplies it (e.g. `in edge a -> b`): the warning continues the
|
|
192
|
+
* same line as C's AGPREV does, the error's context follows on its own line.
|
|
193
|
+
*
|
|
194
|
+
* @see lib/common/emit.c:470 parseSegs
|
|
195
|
+
*/
|
|
196
|
+
export function parseSegs(
|
|
197
|
+
colorlist: string,
|
|
198
|
+
context?: () => string,
|
|
199
|
+
): { segs: ColorSeg[]; error: 0 | 1 | 2 | 3 } {
|
|
200
|
+
const result = scanSegs(colorlist, claimWarned);
|
|
201
|
+
if (result.error === 3) {
|
|
202
|
+
const tail = context === undefined ? '' : context();
|
|
203
|
+
agwarningf(`Total size > 1 in "${colorlist}" color spec ${tail}`);
|
|
204
|
+
} else if (result.error === 2) {
|
|
205
|
+
agerrorf(`Illegal value in "${colorlist}" color attribute; float expected after ';'`);
|
|
206
|
+
if (context !== undefined) agerr('prev', context());
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* {@link parseSegs} for port-internal re-parses that have no C counterpart
|
|
213
|
+
* call (pre-passes, parallel edges): same segments and error class, but never
|
|
214
|
+
* prints and never consumes the once-per-process flag.
|
|
215
|
+
*/
|
|
216
|
+
export function parseSegsSilent(
|
|
217
|
+
colorlist: string,
|
|
218
|
+
): { segs: ColorSeg[]; error: 0 | 1 | 2 | 3 } {
|
|
219
|
+
return scanSegs(colorlist, claimNever);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The AGPREV line C's `multicolor` appends for an rv > 1 parseSegs.
|
|
224
|
+
* @see lib/common/emit.c:1985 multicolor
|
|
225
|
+
*/
|
|
226
|
+
export function edgeSegsContext(e: Edge): () => string {
|
|
227
|
+
return () => {
|
|
228
|
+
const directed = !e.tail.root.kind.endsWith('undirected');
|
|
229
|
+
return `in edge ${e.tail.name}${directed ? ' -> ' : ' -- '}${e.head.name}`;
|
|
230
|
+
};
|
|
231
|
+
}
|