@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.
Files changed (88) hide show
  1. package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
  2. package/dist/common/bezier.d.ts +12 -0
  3. package/dist/common/bezier.d.ts.map +1 -0
  4. package/dist/common/htmltable-attr-warn.d.ts +41 -0
  5. package/dist/common/htmltable-attr-warn.d.ts.map +1 -0
  6. package/dist/common/htmltable-fontinfo.d.ts +42 -0
  7. package/dist/common/htmltable-fontinfo.d.ts.map +1 -0
  8. package/dist/common/htmltable-lex-expat.d.ts +63 -0
  9. package/dist/common/htmltable-lex-expat.d.ts.map +1 -0
  10. package/dist/common/htmltable-lex-units.d.ts +32 -0
  11. package/dist/common/htmltable-lex-units.d.ts.map +1 -0
  12. package/dist/common/htmltable-lex.d.ts +10 -2
  13. package/dist/common/htmltable-lex.d.ts.map +1 -1
  14. package/dist/common/htmltable-parse-attrs.d.ts +7 -5
  15. package/dist/common/htmltable-parse-attrs.d.ts.map +1 -1
  16. package/dist/common/htmltable-parse-font.d.ts +33 -0
  17. package/dist/common/htmltable-parse-font.d.ts.map +1 -0
  18. package/dist/common/htmltable-parse-state.d.ts +39 -0
  19. package/dist/common/htmltable-parse-state.d.ts.map +1 -0
  20. package/dist/common/htmltable-parse.d.ts +18 -23
  21. package/dist/common/htmltable-parse.d.ts.map +1 -1
  22. package/dist/common/htmltable-pos.d.ts.map +1 -1
  23. package/dist/common/htmltable-types.d.ts +33 -3
  24. package/dist/common/htmltable-types.d.ts.map +1 -1
  25. package/dist/common/htmltable.d.ts +3 -2
  26. package/dist/common/htmltable.d.ts.map +1 -1
  27. package/dist/common/spline-midpoint.d.ts.map +1 -1
  28. package/dist/common/splines-geom.d.ts +5 -6
  29. package/dist/common/splines-geom.d.ts.map +1 -1
  30. package/dist/common/splines-routespl.d.ts.map +1 -1
  31. package/dist/common/taper.d.ts.map +1 -1
  32. package/dist/gvc/anchor.d.ts.map +1 -1
  33. package/dist/index.js +1362 -647
  34. package/dist/index.js.map +4 -4
  35. package/dist/layout/dot/compound-clip.d.ts +5 -5
  36. package/dist/layout/dot/compound-clip.d.ts.map +1 -1
  37. package/dist/layout/dot/compound-geom.d.ts +2 -7
  38. package/dist/layout/dot/compound-geom.d.ts.map +1 -1
  39. package/dist/layout/dot/compound.d.ts +1 -1
  40. package/dist/layout/dot/compound.d.ts.map +1 -1
  41. package/dist/layout/dot/edge-route-clip.d.ts +1 -1
  42. package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
  43. package/dist/layout/dot/ortho-adapter.d.ts +13 -4
  44. package/dist/layout/dot/ortho-adapter.d.ts.map +1 -1
  45. package/dist/layout/dot/splines-label.d.ts.map +1 -1
  46. package/dist/layout/neato/bfs.d.ts +10 -3
  47. package/dist/layout/neato/bfs.d.ts.map +1 -1
  48. package/dist/layout/neato/init.d.ts +6 -11
  49. package/dist/layout/neato/init.d.ts.map +1 -1
  50. package/dist/layout/neato/splines.d.ts.map +1 -1
  51. package/dist/parser/index.d.ts.map +1 -1
  52. package/dist/parser/scan-token.d.ts +40 -0
  53. package/dist/parser/scan-token.d.ts.map +1 -0
  54. package/dist/render/svg-edge-split.d.ts.map +1 -1
  55. package/dist/render.js +1384 -669
  56. package/dist/render.js.map +4 -4
  57. package/package.json +1 -1
  58. package/src/common/arrows-shapes-poly.ts +9 -10
  59. package/src/common/bezier.ts +33 -0
  60. package/src/common/htmltable-attr-warn.ts +270 -0
  61. package/src/common/htmltable-fontinfo.ts +83 -0
  62. package/src/common/htmltable-lex-expat.ts +357 -0
  63. package/src/common/htmltable-lex-units.ts +75 -0
  64. package/src/common/htmltable-lex.ts +221 -160
  65. package/src/common/htmltable-parse-attrs.ts +10 -14
  66. package/src/common/htmltable-parse-font.ts +92 -0
  67. package/src/common/htmltable-parse-state.ts +64 -0
  68. package/src/common/htmltable-parse.ts +138 -98
  69. package/src/common/htmltable-pos.ts +9 -8
  70. package/src/common/htmltable-types.ts +35 -3
  71. package/src/common/htmltable.ts +14 -7
  72. package/src/common/spline-midpoint.ts +2 -22
  73. package/src/common/splines-geom.ts +5 -25
  74. package/src/common/splines-routespl.ts +21 -13
  75. package/src/common/taper.ts +2 -14
  76. package/src/gvc/anchor.ts +5 -14
  77. package/src/layout/dot/compound-clip.ts +9 -19
  78. package/src/layout/dot/compound-geom.ts +6 -23
  79. package/src/layout/dot/compound.ts +1 -1
  80. package/src/layout/dot/edge-route-clip.ts +6 -29
  81. package/src/layout/dot/ortho-adapter.ts +5 -5
  82. package/src/layout/dot/splines-label.ts +3 -29
  83. package/src/layout/neato/bfs.ts +13 -4
  84. package/src/layout/neato/init.ts +15 -15
  85. package/src/layout/neato/splines.ts +4 -8
  86. package/src/parser/index.ts +13 -1
  87. package/src/parser/scan-token.ts +289 -0
  88. 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.4",
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
- const lerp = (a: Point, b: Point): Point => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
198
-
199
- /** Split a cubic at t=0.5 (de Casteljau); return the left or right half. */
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 A = lerp(c[0], c[1]);
202
- const B = lerp(c[1], c[2]);
203
- const C = lerp(c[2], c[3]);
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
+ };