@knowvah/dot-engine 2.6.4 → 2.6.5
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/dist/common/arrows-shapes-poly.d.ts.map +1 -1
- package/dist/common/bezier.d.ts +12 -0
- package/dist/common/bezier.d.ts.map +1 -0
- package/dist/common/htmltable-attr-warn.d.ts +41 -0
- package/dist/common/htmltable-attr-warn.d.ts.map +1 -0
- package/dist/common/htmltable-fontinfo.d.ts +42 -0
- package/dist/common/htmltable-fontinfo.d.ts.map +1 -0
- package/dist/common/htmltable-lex-expat.d.ts +63 -0
- package/dist/common/htmltable-lex-expat.d.ts.map +1 -0
- package/dist/common/htmltable-lex-units.d.ts +32 -0
- package/dist/common/htmltable-lex-units.d.ts.map +1 -0
- package/dist/common/htmltable-lex.d.ts +10 -2
- package/dist/common/htmltable-lex.d.ts.map +1 -1
- package/dist/common/htmltable-parse-attrs.d.ts +7 -5
- package/dist/common/htmltable-parse-attrs.d.ts.map +1 -1
- package/dist/common/htmltable-parse-font.d.ts +33 -0
- package/dist/common/htmltable-parse-font.d.ts.map +1 -0
- package/dist/common/htmltable-parse-state.d.ts +39 -0
- package/dist/common/htmltable-parse-state.d.ts.map +1 -0
- package/dist/common/htmltable-parse.d.ts +18 -23
- package/dist/common/htmltable-parse.d.ts.map +1 -1
- package/dist/common/htmltable-pos.d.ts.map +1 -1
- package/dist/common/htmltable-types.d.ts +33 -3
- package/dist/common/htmltable-types.d.ts.map +1 -1
- package/dist/common/htmltable.d.ts +3 -2
- package/dist/common/htmltable.d.ts.map +1 -1
- package/dist/common/spline-midpoint.d.ts.map +1 -1
- package/dist/common/splines-geom.d.ts +5 -6
- package/dist/common/splines-geom.d.ts.map +1 -1
- package/dist/common/splines-routespl.d.ts.map +1 -1
- package/dist/common/taper.d.ts.map +1 -1
- package/dist/gvc/anchor.d.ts.map +1 -1
- package/dist/index.js +1362 -647
- package/dist/index.js.map +4 -4
- package/dist/layout/dot/compound-clip.d.ts +5 -5
- package/dist/layout/dot/compound-clip.d.ts.map +1 -1
- package/dist/layout/dot/compound-geom.d.ts +2 -7
- package/dist/layout/dot/compound-geom.d.ts.map +1 -1
- package/dist/layout/dot/compound.d.ts +1 -1
- package/dist/layout/dot/compound.d.ts.map +1 -1
- package/dist/layout/dot/edge-route-clip.d.ts +1 -1
- package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
- package/dist/layout/dot/ortho-adapter.d.ts +13 -4
- package/dist/layout/dot/ortho-adapter.d.ts.map +1 -1
- package/dist/layout/dot/splines-label.d.ts.map +1 -1
- package/dist/layout/neato/bfs.d.ts +10 -3
- package/dist/layout/neato/bfs.d.ts.map +1 -1
- package/dist/layout/neato/init.d.ts +6 -11
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/splines.d.ts.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/scan-token.d.ts +40 -0
- package/dist/parser/scan-token.d.ts.map +1 -0
- package/dist/render/svg-edge-split.d.ts.map +1 -1
- package/dist/render.js +1384 -669
- package/dist/render.js.map +4 -4
- package/package.json +1 -1
- package/src/common/arrows-shapes-poly.ts +9 -10
- package/src/common/bezier.ts +33 -0
- package/src/common/htmltable-attr-warn.ts +270 -0
- package/src/common/htmltable-fontinfo.ts +83 -0
- package/src/common/htmltable-lex-expat.ts +357 -0
- package/src/common/htmltable-lex-units.ts +75 -0
- package/src/common/htmltable-lex.ts +221 -160
- package/src/common/htmltable-parse-attrs.ts +10 -14
- package/src/common/htmltable-parse-font.ts +92 -0
- package/src/common/htmltable-parse-state.ts +64 -0
- package/src/common/htmltable-parse.ts +138 -98
- package/src/common/htmltable-pos.ts +9 -8
- package/src/common/htmltable-types.ts +35 -3
- package/src/common/htmltable.ts +14 -7
- package/src/common/spline-midpoint.ts +2 -22
- package/src/common/splines-geom.ts +5 -25
- package/src/common/splines-routespl.ts +21 -13
- package/src/common/taper.ts +2 -14
- package/src/gvc/anchor.ts +5 -14
- package/src/layout/dot/compound-clip.ts +9 -19
- package/src/layout/dot/compound-geom.ts +6 -23
- package/src/layout/dot/compound.ts +1 -1
- package/src/layout/dot/edge-route-clip.ts +6 -29
- package/src/layout/dot/ortho-adapter.ts +5 -5
- package/src/layout/dot/splines-label.ts +3 -29
- package/src/layout/neato/bfs.ts +13 -4
- package/src/layout/neato/init.ts +15 -15
- package/src/layout/neato/splines.ts +4 -8
- package/src/parser/index.ts +13 -1
- package/src/parser/scan-token.ts +289 -0
- package/src/render/svg-edge-split.ts +5 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowvah/dot-engine",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.5",
|
|
4
4
|
"description": "Faithful TypeScript port of Graphviz (dot, neato, fdp, sfdp, circo, twopi, osage, patchwork) — no C: no native binary, no WASM. Oracle-verified against the C implementation, runs in the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphviz",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Point } from '../model/geom.js';
|
|
12
12
|
import { ccwrotatepf, cwrotatepf } from '../model/geom.js';
|
|
13
|
+
import { bezier } from './bezier.js';
|
|
13
14
|
import type { GenResult } from './arrows-shapes-util.js';
|
|
14
15
|
import {
|
|
15
16
|
vsub, vadd, miterShape, negUnit, backwardDelta, axialProjection,
|
|
@@ -194,17 +195,15 @@ export function arrowLengthTee(lenfact: number, arrowsize: number, penwidth: num
|
|
|
194
195
|
// @see lib/common/arrows.c:arrow_type_curve (:1031)
|
|
195
196
|
// ===========================================================================
|
|
196
197
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Split a cubic at t=0.5 (C's Bezier(AF, 0.5, NULL, AF) / (AF, 0.5, AF, NULL));
|
|
200
|
+
* return the left or right half.
|
|
201
|
+
* @see lib/common/arrows.c:arrow_type_curve
|
|
202
|
+
*/
|
|
200
203
|
function cubicHalf(c: Point[], rightHalf: boolean): Point[] {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const D = lerp(A, B);
|
|
205
|
-
const E = lerp(B, C);
|
|
206
|
-
const M = lerp(D, E);
|
|
207
|
-
return rightHalf ? [M, E, C, c[3]] : [c[0], A, D, M];
|
|
204
|
+
const half: Point[] = [];
|
|
205
|
+
bezier(c, 0.5, rightHalf ? null : half, rightHalf ? half : null);
|
|
206
|
+
return half;
|
|
208
207
|
}
|
|
209
208
|
|
|
210
209
|
/** Build the four Bézier control points for the curve arms. */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
import type { Point } from '../model/geom.js';
|
|
4
|
+
import { fma } from './fma.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* De Casteljau evaluation of a cubic at t, optionally returning the two
|
|
8
|
+
* sub-curves. The oracle compiles each `(1.0 - t) * a + t * b` to
|
|
9
|
+
* `fmul v, b, t; fmla v, a, 1-t`, i.e. fma(a, 1 - t, t * b): `t * b` rounds
|
|
10
|
+
* first and `(1 - t) * a` is the fused product (libgvc 16.1 `_Bezier`
|
|
11
|
+
* disassembly; every inlined copy has the same shape). `left`/`right`, when
|
|
12
|
+
* given, are filled (indices 0..3) like C's `Left`/`Right`.
|
|
13
|
+
* @see lib/common/utils.c:Bezier
|
|
14
|
+
*/
|
|
15
|
+
export function bezier(v: Point[], t: number, left: Point[] | null, right: Point[] | null): Point {
|
|
16
|
+
const degree = 3;
|
|
17
|
+
const u = 1.0 - t;
|
|
18
|
+
const tri: Point[][] = [[v[0], v[1], v[2], v[3]]];
|
|
19
|
+
for (let i = 1; i <= degree; i++) {
|
|
20
|
+
const prev = tri[i - 1];
|
|
21
|
+
const row: Point[] = [];
|
|
22
|
+
for (let j = 0; j <= degree - i; j++) {
|
|
23
|
+
row.push({
|
|
24
|
+
x: fma(prev[j].x, u, t * prev[j + 1].x),
|
|
25
|
+
y: fma(prev[j].y, u, t * prev[j + 1].y),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
tri.push(row);
|
|
29
|
+
}
|
|
30
|
+
if (left !== null) for (let j = 0; j <= degree; j++) left[j] = tri[j][0];
|
|
31
|
+
if (right !== null) for (let j = 0; j <= degree; j++) right[j] = tri[degree - j][j];
|
|
32
|
+
return tri[degree][0];
|
|
33
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* The attribute tables and value handlers of htmllex.c's `doAttrs`: which
|
|
4
|
+
* attributes each element accepts, which values a handler takes or ignores,
|
|
5
|
+
* and the warnings C prints for the rest.
|
|
6
|
+
*
|
|
7
|
+
* `checkAttrs` is pure. It returns the attributes C would have applied plus
|
|
8
|
+
* the stderr text C would have written; the parser prints that text when it
|
|
9
|
+
* consumes the token (see {@link StderrLines}).
|
|
10
|
+
*
|
|
11
|
+
* @see lib/common/htmllex.c:doAttrs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** An attribute as written: name in source case, value verbatim. */
|
|
15
|
+
export type AttrEntry = readonly [name: string, value: string];
|
|
16
|
+
|
|
17
|
+
export interface CheckedAttrs {
|
|
18
|
+
/** Accepted attributes, names lower-cased, in document order (later wins). */
|
|
19
|
+
attrs: Record<string, string>;
|
|
20
|
+
/** Exact C stderr text per write, trailing newline kept where C has one. */
|
|
21
|
+
warnings: string[];
|
|
22
|
+
/** `ctx->warn`: the label's `rv` becomes 1. */
|
|
23
|
+
warn: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** What one handler did: `keep` is the value applied, undefined when ignored. */
|
|
27
|
+
interface Outcome {
|
|
28
|
+
keep: string | undefined;
|
|
29
|
+
text: string[];
|
|
30
|
+
warn: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type Handler = (v: string) => Outcome;
|
|
34
|
+
|
|
35
|
+
const accepted = (v: string): Outcome => ({ keep: v, text: [], warn: false });
|
|
36
|
+
const ignored = (text: string): Outcome => ({ keep: undefined, text: [text], warn: true });
|
|
37
|
+
const warning = (msg: string): string => `Warning: ${msg}`;
|
|
38
|
+
|
|
39
|
+
/** ASCII-only lower-casing: `strcasecmp` does not fold other letters. */
|
|
40
|
+
const lower = (s: string): string => s.replace(/[A-Z]/g, (c) => c.toLowerCase());
|
|
41
|
+
|
|
42
|
+
const accept: Handler = accepted;
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Numeric handlers
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
/** `strtol(v, &ep, 10)`: leading blanks, a sign, digits; `ep == v` otherwise. */
|
|
49
|
+
const STRTOL = /^[ \t\n\v\f\r]*([+-]?\d+)/;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Scan v for an integral value in [min, max]; the message carries no newline.
|
|
53
|
+
* @see lib/common/htmllex.c:doInt
|
|
54
|
+
*/
|
|
55
|
+
function doInt(v: string, s: string, min: number, max: number): Outcome {
|
|
56
|
+
const m = STRTOL.exec(v);
|
|
57
|
+
if (m === null) return ignored(warning(`Improper ${s} value ${v} - ignored`));
|
|
58
|
+
const b = Number(m[1]);
|
|
59
|
+
if (b > max) return ignored(warning(`${s} value ${v} > ${max} - too large - ignored`));
|
|
60
|
+
if (b < min) return ignored(warning(`${s} value ${v} < ${min} - too small - ignored`));
|
|
61
|
+
return accepted(v);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const intHandler = (s: string, min: number, max: number): Handler => (v) => doInt(v, s, min, max);
|
|
65
|
+
|
|
66
|
+
const UCHAR_MAX = 255;
|
|
67
|
+
const SCHAR_MIN = -128;
|
|
68
|
+
const SCHAR_MAX = 127;
|
|
69
|
+
const INT8_MAX = 127;
|
|
70
|
+
const USHRT_MAX = 65535;
|
|
71
|
+
const GRADIENT_MAX = 360;
|
|
72
|
+
|
|
73
|
+
/** ROWSPAN and COLSPAN: an integer in [0, 65535] that is not 0. */
|
|
74
|
+
const spanHandler = (s: string): Handler => (v) => {
|
|
75
|
+
const r = doInt(v, s, 0, USHRT_MAX);
|
|
76
|
+
if (r.keep === undefined || Number(STRTOL.exec(v)?.[1]) !== 0) return r;
|
|
77
|
+
return ignored(warning(`${s} value cannot be 0 - ignored\n`));
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Enumerated handlers
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Accept the listed words (any case); anything else is ignored with
|
|
86
|
+
* `Illegal value %s for <what> - ignored\n`.
|
|
87
|
+
*/
|
|
88
|
+
const wordsHandler = (words: readonly string[], what: string): Handler => (v) =>
|
|
89
|
+
words.includes(lower(v)) ? accepted(v) : ignored(warning(`Illegal value ${v} for ${what} - ignored\n`));
|
|
90
|
+
|
|
91
|
+
/** COLUMNS and ROWS look at the first character only. */
|
|
92
|
+
const starHandler = (what: string): Handler => (v) =>
|
|
93
|
+
v.startsWith('*') ? accepted(v) : ignored(warning(`Unknown value ${v} for ${what} - ignored\n`));
|
|
94
|
+
|
|
95
|
+
const valign = wordsHandler(['bottom', 'top', 'middle'], 'VALIGN');
|
|
96
|
+
const fixedsize = wordsHandler(['true', 'false'], 'FIXEDSIZE');
|
|
97
|
+
const tableAlign = wordsHandler(['left', 'right', 'center'], 'ALIGN');
|
|
98
|
+
const brAlign = tableAlign;
|
|
99
|
+
const cellAlign = wordsHandler(['left', 'right', 'text', 'center'], 'ALIGN in TD');
|
|
100
|
+
const balign = wordsHandler(['left', 'right', 'center'], 'BALIGN in TD');
|
|
101
|
+
|
|
102
|
+
const SIDES_LETTERS = 'ltrb';
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Every byte of v that is not l, t, r or b (either case) gets a warning,
|
|
106
|
+
* `%c` and the signed `%d` of the C `char`; the handler still returns 0.
|
|
107
|
+
* @see lib/common/htmllex.c:sidesfn
|
|
108
|
+
*/
|
|
109
|
+
const sides: Handler = (v) => {
|
|
110
|
+
const text: string[] = [];
|
|
111
|
+
for (const b of new TextEncoder().encode(v)) {
|
|
112
|
+
if (SIDES_LETTERS.includes(lower(String.fromCharCode(b)))) continue;
|
|
113
|
+
const code = b > SCHAR_MAX ? b - 256 : b;
|
|
114
|
+
text.push(warning(`Unrecognized character '${String.fromCharCode(b)}' (${code}) in sides attribute\n`));
|
|
115
|
+
}
|
|
116
|
+
return { keep: v, text, warn: false };
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const STYLE_WORDS: readonly string[] = ['rounded', 'radial', 'solid', 'invisible', 'invis', 'dotted', 'dashed'];
|
|
120
|
+
const STYLE_DELIM = ' ,';
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* `tok(v, " ,")`: the first token may be empty; after a token the string ends
|
|
124
|
+
* the scan, otherwise the separators are skipped and the next token (possibly
|
|
125
|
+
* empty, after a trailing separator) follows.
|
|
126
|
+
* @see lib/util/tokenize.h:tok_next
|
|
127
|
+
*/
|
|
128
|
+
function styleTokens(v: string): string[] {
|
|
129
|
+
const out: string[] = [];
|
|
130
|
+
const span = (from: number, member: boolean): number => {
|
|
131
|
+
let i = from;
|
|
132
|
+
while (i < v.length && STYLE_DELIM.includes(v[i]!) === member) i++;
|
|
133
|
+
return i;
|
|
134
|
+
};
|
|
135
|
+
let end = span(0, false);
|
|
136
|
+
out.push(v.slice(0, end));
|
|
137
|
+
while (end < v.length) {
|
|
138
|
+
const start = span(end, true);
|
|
139
|
+
end = span(start, false);
|
|
140
|
+
out.push(v.slice(start, end));
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Each illegal token warns and sets rv; the legal ones apply.
|
|
147
|
+
* @see lib/common/htmllex.c:stylefn
|
|
148
|
+
*/
|
|
149
|
+
const style: Handler = (v) => {
|
|
150
|
+
const legal: string[] = [];
|
|
151
|
+
const text: string[] = [];
|
|
152
|
+
for (const tk of styleTokens(v)) {
|
|
153
|
+
if (STYLE_WORDS.includes(lower(tk))) legal.push(tk);
|
|
154
|
+
else text.push(warning(`Illegal value ${tk} for STYLE - ignored\n`));
|
|
155
|
+
}
|
|
156
|
+
return { keep: legal.length > 0 ? legal.join(' ') : undefined, text, warn: text.length > 0 };
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Tables (alphabetical, as in C)
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
const border = intHandler('BORDER', 0, UCHAR_MAX);
|
|
164
|
+
const cellpadding = intHandler('CELLPADDING', 0, UCHAR_MAX);
|
|
165
|
+
const cellspacing = intHandler('CELLSPACING', SCHAR_MIN, SCHAR_MAX);
|
|
166
|
+
const gradientangle = intHandler('GRADIENTANGLE', 0, GRADIENT_MAX);
|
|
167
|
+
const height = intHandler('HEIGHT', 0, USHRT_MAX);
|
|
168
|
+
const width = intHandler('WIDTH', 0, USHRT_MAX);
|
|
169
|
+
|
|
170
|
+
type ItemTable = ReadonlyMap<string, Handler>;
|
|
171
|
+
|
|
172
|
+
/** @see lib/common/htmllex.c:tbl_items */
|
|
173
|
+
const TBL_ITEMS: ItemTable = new Map<string, Handler>([
|
|
174
|
+
['align', tableAlign], ['bgcolor', accept], ['border', border],
|
|
175
|
+
['cellborder', intHandler('CELLBORDER', 0, INT8_MAX)], ['cellpadding', cellpadding],
|
|
176
|
+
['cellspacing', cellspacing], ['color', accept], ['columns', starHandler('COLUMNS')],
|
|
177
|
+
['fixedsize', fixedsize], ['gradientangle', gradientangle], ['height', height],
|
|
178
|
+
['href', accept], ['id', accept], ['port', accept], ['rows', starHandler('ROWS')],
|
|
179
|
+
['sides', sides], ['style', style], ['target', accept], ['title', accept],
|
|
180
|
+
['tooltip', accept], ['valign', valign], ['width', width],
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
/** @see lib/common/htmllex.c:cell_items */
|
|
184
|
+
const CELL_ITEMS: ItemTable = new Map<string, Handler>([
|
|
185
|
+
['align', cellAlign], ['balign', balign], ['bgcolor', accept], ['border', border],
|
|
186
|
+
['cellpadding', cellpadding], ['cellspacing', cellspacing], ['color', accept],
|
|
187
|
+
['colspan', spanHandler('COLSPAN')], ['fixedsize', fixedsize],
|
|
188
|
+
['gradientangle', gradientangle], ['height', height], ['href', accept], ['id', accept],
|
|
189
|
+
['port', accept], ['rowspan', spanHandler('ROWSPAN')], ['sides', sides], ['style', style],
|
|
190
|
+
['target', accept], ['title', accept], ['tooltip', accept], ['valign', valign],
|
|
191
|
+
['width', width],
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
/** @see lib/common/htmllex.c:font_items */
|
|
195
|
+
const FONT_ITEMS: ItemTable = new Map<string, Handler>([
|
|
196
|
+
['color', accept], ['face', accept], ['point-size', intHandler('POINT-SIZE', 0, UCHAR_MAX)],
|
|
197
|
+
]);
|
|
198
|
+
|
|
199
|
+
/** @see lib/common/htmllex.c:img_items */
|
|
200
|
+
const IMG_ITEMS: ItemTable = new Map<string, Handler>([['scale', accept], ['src', accept]]);
|
|
201
|
+
|
|
202
|
+
/** @see lib/common/htmllex.c:br_items */
|
|
203
|
+
const BR_ITEMS: ItemTable = new Map<string, Handler>([['align', brAlign]]);
|
|
204
|
+
|
|
205
|
+
/** Element tag to its item table and the name `doAttrs` prints for it. */
|
|
206
|
+
const ELEMENTS: ReadonlyMap<string, { items: ItemTable; name: string }> = new Map([
|
|
207
|
+
['TABLE', { items: TBL_ITEMS, name: '<TABLE>' }],
|
|
208
|
+
['TD', { items: CELL_ITEMS, name: '<TD>' }],
|
|
209
|
+
['FONT', { items: FONT_ITEMS, name: '<FONT>' }],
|
|
210
|
+
['IMG', { items: IMG_ITEMS, name: '<IMG>' }],
|
|
211
|
+
['BR', { items: BR_ITEMS, name: '<BR>' }],
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* `doAttrs` for one element: run each attribute through its handler in
|
|
216
|
+
* document order. Elements without a table (TR, B, I, ..., HR, VR, HTML) take
|
|
217
|
+
* their attributes untouched, as C never looks at them.
|
|
218
|
+
* @see lib/common/htmllex.c:doAttrs
|
|
219
|
+
*/
|
|
220
|
+
export function checkAttrs(tag: string, pairs: readonly AttrEntry[]): CheckedAttrs {
|
|
221
|
+
const el = ELEMENTS.get(tag);
|
|
222
|
+
const attrs: Record<string, string> = {};
|
|
223
|
+
const warnings: string[] = [];
|
|
224
|
+
let warn = false;
|
|
225
|
+
for (const [name, value] of pairs) {
|
|
226
|
+
const key = lower(name);
|
|
227
|
+
if (el === undefined) {
|
|
228
|
+
attrs[key] = value;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const handler = el.items.get(key);
|
|
232
|
+
if (handler === undefined) {
|
|
233
|
+
warnings.push(warning(`Illegal attribute ${name} in ${el.name} - ignored\n`));
|
|
234
|
+
warn = true;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const r = handler(value);
|
|
238
|
+
warnings.push(...r.text);
|
|
239
|
+
warn ||= r.warn;
|
|
240
|
+
if (r.keep !== undefined) attrs[key] = r.keep;
|
|
241
|
+
}
|
|
242
|
+
return { attrs, warnings, warn };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// stderr emulation
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* C's stderr as lines: text is appended, each completed line goes to
|
|
251
|
+
* `writeLine`, and a message without a trailing newline stays pending so the
|
|
252
|
+
* next write continues it. `flush` emits a pending partial line.
|
|
253
|
+
*/
|
|
254
|
+
export class StderrLines {
|
|
255
|
+
private pending = '';
|
|
256
|
+
|
|
257
|
+
constructor(private readonly writeLine: (line: string) => void) {}
|
|
258
|
+
|
|
259
|
+
write(text: string): void {
|
|
260
|
+
const parts = (this.pending + text).split('\n');
|
|
261
|
+
this.pending = parts.pop() ?? '';
|
|
262
|
+
for (const line of parts) this.writeLine(line);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
flush(): void {
|
|
266
|
+
if (this.pending === '') return;
|
|
267
|
+
this.writeLine(this.pending);
|
|
268
|
+
this.pending = '';
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* The HTML label environment's font swap: C's `pushFontInfo` / `popFontInfo`
|
|
4
|
+
* and the function-static `savef` slots they share across table recursion.
|
|
5
|
+
*
|
|
6
|
+
* @see lib/common/htmltable.c:pushFontInfo
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { HtmlTableFont } from './htmltable-types.js';
|
|
10
|
+
|
|
11
|
+
/** Font fields `pushFontInfo` swaps; `fontcolor` exists only in the position env. */
|
|
12
|
+
export interface FontEnv {
|
|
13
|
+
fontsize: number;
|
|
14
|
+
fontname: string;
|
|
15
|
+
fontcolor?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* C's `static textfont_t savef`: `pushFontInfo` writes it, `popFontInfo` reads
|
|
20
|
+
* it back. One instance serves every (nested) table of a pass, so an inner
|
|
21
|
+
* table's push overwrites the outer's save. Zero-initialised like C
|
|
22
|
+
* (size 0.0, name and color null).
|
|
23
|
+
*/
|
|
24
|
+
export interface SavedFont {
|
|
25
|
+
fontname?: string;
|
|
26
|
+
fontcolor?: string;
|
|
27
|
+
fontsize: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @see lib/common/htmltable.c:size_html_tbl (function-static savef) */
|
|
31
|
+
const SIZE_SAVEF: SavedFont = { fontsize: 0 };
|
|
32
|
+
/** Emit-side slot; the port's position pass plays emit_html_tbl's role. @see lib/common/htmltable.c:emit_html_tbl */
|
|
33
|
+
const EMIT_SAVEF: SavedFont = { fontsize: 0 };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Replace the environment's font attributes with `fp`'s, saving the old ones
|
|
37
|
+
* in `savp`. Only attributes set in the environment are dealt with.
|
|
38
|
+
* @see lib/common/htmltable.c:pushFontInfo
|
|
39
|
+
*/
|
|
40
|
+
export const pushFontInfo = (env: FontEnv, fp: HtmlTableFont, savp: SavedFont): void => {
|
|
41
|
+
if (fp.fontFace !== undefined) {
|
|
42
|
+
savp.fontname = env.fontname;
|
|
43
|
+
env.fontname = fp.fontFace;
|
|
44
|
+
} else savp.fontname = undefined;
|
|
45
|
+
if (env.fontcolor !== undefined) {
|
|
46
|
+
if (fp.fontColor !== undefined) {
|
|
47
|
+
savp.fontcolor = env.fontcolor;
|
|
48
|
+
env.fontcolor = fp.fontColor;
|
|
49
|
+
} else savp.fontcolor = undefined;
|
|
50
|
+
}
|
|
51
|
+
if (env.fontsize >= 0) {
|
|
52
|
+
if (fp.fontSize !== undefined && fp.fontSize >= 0) {
|
|
53
|
+
savp.fontsize = env.fontsize;
|
|
54
|
+
env.fontsize = fp.fontSize;
|
|
55
|
+
} else savp.fontsize = -1;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Restore saved font attributes, copying only set values.
|
|
61
|
+
* @see lib/common/htmltable.c:popFontInfo
|
|
62
|
+
*/
|
|
63
|
+
export const popFontInfo = (env: FontEnv, savp: SavedFont): void => {
|
|
64
|
+
if (savp.fontname !== undefined) env.fontname = savp.fontname;
|
|
65
|
+
if (savp.fontcolor !== undefined) env.fontcolor = savp.fontcolor;
|
|
66
|
+
if (savp.fontsize >= 0) env.fontsize = savp.fontsize;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run `fn` with the table's font pushed into `env` (`if (tbl->font)` push ...
|
|
71
|
+
* pop), using the size or emit-side saved-font slot.
|
|
72
|
+
* @see lib/common/htmltable.c:size_html_tbl, emit_html_tbl
|
|
73
|
+
*/
|
|
74
|
+
export const withTableFont = <R>(
|
|
75
|
+
env: FontEnv, font: HtmlTableFont | undefined, slot: 'size' | 'emit', fn: () => R,
|
|
76
|
+
): R => {
|
|
77
|
+
if (font === undefined) return fn();
|
|
78
|
+
const savp = slot === 'size' ? SIZE_SAVEF : EMIT_SAVEF;
|
|
79
|
+
pushFontInfo(env, font, savp);
|
|
80
|
+
const r = fn();
|
|
81
|
+
popFontInfo(env, savp);
|
|
82
|
+
return r;
|
|
83
|
+
};
|