@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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Cursor state of the HTML-like label parser and the consume step that prints
|
|
4
|
+
* a token's lexer warnings. C lexes a token when the parser reaches it, so the
|
|
5
|
+
* warnings of a token the parser never gets to print nothing; T9's lexer
|
|
6
|
+
* warnings ride on the same `warnings` array.
|
|
7
|
+
*
|
|
8
|
+
* @see lib/common/htmllex.c:doAttrs
|
|
9
|
+
* @see lib/common/htmllex.c:clearHTMLlexer
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { HtmlParseError, LexerOutput, Token } from './htmltable-types.js';
|
|
13
|
+
import type { StderrLines } from './htmltable-attr-warn.js';
|
|
14
|
+
|
|
15
|
+
export interface ParseState {
|
|
16
|
+
tokens: Token[];
|
|
17
|
+
pos: number;
|
|
18
|
+
/** Lexical error found after the last token: C reports it when the parser asks for more. */
|
|
19
|
+
pending?: HtmlParseError;
|
|
20
|
+
/** Where lexer warnings go as tokens are reached (C's stderr, line by line); none: silent. */
|
|
21
|
+
stderr?: StderrLines;
|
|
22
|
+
/** Tokens whose warnings are already out (a token is lexed once). */
|
|
23
|
+
emitted?: WeakSet<Token>;
|
|
24
|
+
/** `ctx->warn`: a reached token's warning made the label's rv 1. */
|
|
25
|
+
warn?: boolean;
|
|
26
|
+
/** What the lexer wrote after its last token, printed when the parser asks for more. */
|
|
27
|
+
eof?: LexerOutput;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const peek = (s: ParseState): Token | undefined => s.tokens[s.pos];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Print what the C lexer wrote while lexing `tok`, once, and fold its `warn`
|
|
34
|
+
* into the label's rv.
|
|
35
|
+
*/
|
|
36
|
+
export const emitWarnings = (s: ParseState, tok: Token): void => {
|
|
37
|
+
if (s.stderr === undefined || s.emitted?.has(tok) === true) return;
|
|
38
|
+
s.emitted?.add(tok);
|
|
39
|
+
emitOutput(s, tok);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const emitOutput = (s: ParseState, out: LexerOutput): void => {
|
|
43
|
+
for (const text of out.warnings ?? []) s.stderr?.write(text);
|
|
44
|
+
if (out.warn === true) s.warn = true;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The parser ran off the last token: C lexes the rest now, so what it wrote
|
|
49
|
+
* after that token (warnings of units that produced none) prints once.
|
|
50
|
+
*/
|
|
51
|
+
export const emitEof = (s: ParseState): void => {
|
|
52
|
+
if (s.stderr === undefined || s.eof === undefined) return;
|
|
53
|
+
const out = s.eof;
|
|
54
|
+
delete s.eof;
|
|
55
|
+
emitOutput(s, out);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Take the token at the cursor; its warnings print now. */
|
|
59
|
+
export const consume = (s: ParseState): Token => {
|
|
60
|
+
const t = s.tokens[s.pos];
|
|
61
|
+
s.pos++;
|
|
62
|
+
if (t !== undefined) emitWarnings(s, t);
|
|
63
|
+
return t;
|
|
64
|
+
};
|
|
@@ -20,7 +20,14 @@ import type {
|
|
|
20
20
|
OpenToken,
|
|
21
21
|
} from './htmltable-types.js';
|
|
22
22
|
import { HtmlParseError } from './htmltable-types.js';
|
|
23
|
+
import { StderrLines } from './htmltable-attr-warn.js';
|
|
24
|
+
import type { ParseState } from './htmltable-parse-state.js';
|
|
25
|
+
import { consume, emitEof, emitWarnings, peek } from './htmltable-parse-state.js';
|
|
23
26
|
import { htmlErrorAtToken, tokenizeDeferred } from './htmltable-lex.js';
|
|
27
|
+
import type { FontState } from './htmltable-parse-font.js';
|
|
28
|
+
import {
|
|
29
|
+
fontItemProps, inlineFontState, isInlineTag, mergeFontState, tableFont, wrapperFont,
|
|
30
|
+
} from './htmltable-parse-font.js';
|
|
24
31
|
import {
|
|
25
32
|
buildCell,
|
|
26
33
|
buildTable,
|
|
@@ -30,59 +37,6 @@ import {
|
|
|
30
37
|
parseRows,
|
|
31
38
|
} from './htmltable-parse-attrs.js';
|
|
32
39
|
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Font-stack — mirrors pushFont / popFont in htmlparse.y
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
interface FontState {
|
|
38
|
-
bold?: boolean;
|
|
39
|
-
italic?: boolean;
|
|
40
|
-
underline?: boolean;
|
|
41
|
-
overline?: boolean;
|
|
42
|
-
strikethrough?: boolean;
|
|
43
|
-
subscript?: boolean;
|
|
44
|
-
superscript?: boolean;
|
|
45
|
-
fontColor?: string;
|
|
46
|
-
fontFace?: string;
|
|
47
|
-
fontSize?: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const definedOf = (s: FontState): Partial<FontState> =>
|
|
51
|
-
Object.fromEntries(
|
|
52
|
-
Object.entries(s).filter(([, v]) => v !== undefined),
|
|
53
|
-
) as Partial<FontState>;
|
|
54
|
-
|
|
55
|
-
const mergeFontState = (parent: FontState, child: FontState): FontState =>
|
|
56
|
-
({ ...parent, ...definedOf(child) }) as FontState;
|
|
57
|
-
|
|
58
|
-
const BOOL_FONT_KEYS = [
|
|
59
|
-
'bold', 'italic', 'underline', 'overline',
|
|
60
|
-
'strikethrough', 'subscript', 'superscript',
|
|
61
|
-
] as const;
|
|
62
|
-
type BoolFontKey = typeof BOOL_FONT_KEYS[number];
|
|
63
|
-
|
|
64
|
-
const fontItemProps = (f: FontState): Partial<HtmlTextItem> => {
|
|
65
|
-
const out: Partial<HtmlTextItem> = {};
|
|
66
|
-
for (const k of BOOL_FONT_KEYS) {
|
|
67
|
-
if (f[k]) out[k as BoolFontKey] = true;
|
|
68
|
-
}
|
|
69
|
-
if (f.fontColor) out.fontColor = f.fontColor;
|
|
70
|
-
if (f.fontFace) out.fontFace = f.fontFace;
|
|
71
|
-
if (f.fontSize !== undefined) out.fontSize = f.fontSize;
|
|
72
|
-
return out;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
// Parser state
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
|
|
79
|
-
interface ParseState {
|
|
80
|
-
tokens: Token[];
|
|
81
|
-
pos: number;
|
|
82
|
-
/** Lexical error found after the last token: C reports it when the parser asks for more. */
|
|
83
|
-
pending?: HtmlParseError;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
40
|
/** bison's default message for an unexpected token. @see lib/common/htmlparse.y */
|
|
87
41
|
const MSG_SYNTAX_ERROR = 'syntax error';
|
|
88
42
|
/** @see lib/common/htmlparse.y:288 (table rule) */
|
|
@@ -103,36 +57,17 @@ const nonSpace = (text: string): boolean => /[^ \t\n\r]/.test(text);
|
|
|
103
57
|
* A token without a recorded chunk yields a plain, silent HtmlParseError.
|
|
104
58
|
* @see lib/common/htmllex.c:htmlerror
|
|
105
59
|
*/
|
|
106
|
-
const parseError = (s: ParseState, tok: Token | undefined, message: string): HtmlParseError =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const consume = (s: ParseState): Token => {
|
|
112
|
-
const t = s.tokens[s.pos];
|
|
113
|
-
s.pos++;
|
|
114
|
-
return t;
|
|
60
|
+
const parseError = (s: ParseState, tok: Token | undefined, message: string): HtmlParseError => {
|
|
61
|
+
// The lexer already ran for the token the error is reported at.
|
|
62
|
+
if (tok !== undefined) emitWarnings(s, tok);
|
|
63
|
+
else emitEof(s);
|
|
64
|
+
return (tok === undefined ? s.pending : htmlErrorAtToken(message, tok)) ?? new HtmlParseError(message);
|
|
115
65
|
};
|
|
116
66
|
|
|
117
67
|
// ---------------------------------------------------------------------------
|
|
118
68
|
// Inline-tag helpers — mirrors htmlparse.y fonttext / inline rules
|
|
119
69
|
// ---------------------------------------------------------------------------
|
|
120
70
|
|
|
121
|
-
const isInlineTag = (tag: string): boolean =>
|
|
122
|
-
['B', 'I', 'U', 'O', 'S', 'SUB', 'SUP'].includes(tag);
|
|
123
|
-
|
|
124
|
-
const inlineFontState = (tag: string, _attrs: Record<string, string>): FontState => {
|
|
125
|
-
const fs: FontState = {};
|
|
126
|
-
if (tag === 'B') fs.bold = true;
|
|
127
|
-
if (tag === 'I') fs.italic = true;
|
|
128
|
-
if (tag === 'U') fs.underline = true;
|
|
129
|
-
if (tag === 'O') fs.overline = true;
|
|
130
|
-
if (tag === 'S') fs.strikethrough = true;
|
|
131
|
-
if (tag === 'SUB') fs.subscript = true;
|
|
132
|
-
if (tag === 'SUP') fs.superscript = true;
|
|
133
|
-
return fs;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
71
|
/**
|
|
137
72
|
* `br: T_br T_end_br`, likewise image, HR and VR: the open tag must be
|
|
138
73
|
* followed at once by its close (`<BR/>` yields both); anything else is a
|
|
@@ -219,6 +154,70 @@ export const parseText = (s: ParseState, fontStack: FontState[]): HtmlText => {
|
|
|
219
154
|
return { kind: 'text', items };
|
|
220
155
|
};
|
|
221
156
|
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// fonttable — a <TABLE> wrapped in one font-like tag
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
/** Tags of `fonttable : font table n_font | italic ... | bold ...`. @see lib/common/htmlparse.y:307 */
|
|
162
|
+
const FONTTABLE_TAGS: readonly string[] = ['FONT', 'B', 'I', 'U', 'O'];
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The wrapper tag when the tokens at the cursor open a `fonttable`: one of
|
|
166
|
+
* FONTTABLE_TAGS, then only strings (the table rule's opt_space), then TABLE.
|
|
167
|
+
*/
|
|
168
|
+
const fontTableWrapper = (s: ParseState): OpenToken | undefined => {
|
|
169
|
+
const open = peek(s);
|
|
170
|
+
if (open?.type !== 'open' || !FONTTABLE_TAGS.includes(open.tag)) return undefined;
|
|
171
|
+
let i = s.pos + 1;
|
|
172
|
+
while (s.tokens[i]?.type === 'text') i++;
|
|
173
|
+
const next = s.tokens[i];
|
|
174
|
+
return next?.type === 'open' && next.tag === 'TABLE' ? open : undefined;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/** Consume strings; true when any is non-space (the table rule's nonSpace check). */
|
|
178
|
+
const skipStrings = (s: ParseState): boolean => {
|
|
179
|
+
let found = false;
|
|
180
|
+
for (let t = peek(s); t?.type === 'text'; t = peek(s)) {
|
|
181
|
+
consume(s);
|
|
182
|
+
if (nonSpace(t.value)) found = true;
|
|
183
|
+
}
|
|
184
|
+
return found;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* `font table n_font` (and the I/U/O/B forms): the wrapper's font sits on the
|
|
189
|
+
* fontstack while the table parses, then pops at the closing tag.
|
|
190
|
+
* @see lib/common/htmlparse.y:fonttable
|
|
191
|
+
*/
|
|
192
|
+
const parseFontTable = (s: ParseState, open: OpenToken, fontStack: FontState[]): HtmlTable => {
|
|
193
|
+
consume(s);
|
|
194
|
+
fontStack.push(wrapperFont(open, fontStack));
|
|
195
|
+
const textBefore = skipStrings(s);
|
|
196
|
+
const tblTok = peek(s);
|
|
197
|
+
if (textBefore && tblTok !== undefined) throw parseError(s, tblTok, MSG_TEXT_BEFORE_TABLE);
|
|
198
|
+
const table = parseTable(s, fontStack);
|
|
199
|
+
const textAfter = skipStrings(s);
|
|
200
|
+
const close = peek(s);
|
|
201
|
+
if (close !== undefined && textAfter) throw parseError(s, close, MSG_TEXT_AFTER_TABLE);
|
|
202
|
+
if (close?.type !== 'close' || close.tag !== open.tag) throw parseError(s, close, MSG_SYNTAX_ERROR);
|
|
203
|
+
consume(s);
|
|
204
|
+
fontStack.pop();
|
|
205
|
+
return table;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A <TABLE> that follows a consumed tag can only be a syntax error: the table
|
|
210
|
+
* rule's opt_space admits strings alone.
|
|
211
|
+
* @see lib/common/htmlparse.y:287
|
|
212
|
+
*/
|
|
213
|
+
const rejectTableAfterTag = (s: ParseState, start: number): void => {
|
|
214
|
+
const nxt = peek(s);
|
|
215
|
+
if (nxt?.type !== 'open' || nxt.tag !== 'TABLE') return;
|
|
216
|
+
if (s.tokens.slice(start, s.pos).some((t) => t.type !== 'text')) {
|
|
217
|
+
throw parseError(s, nxt, MSG_SYNTAX_ERROR);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
222
221
|
// ---------------------------------------------------------------------------
|
|
223
222
|
// Cell content helpers — mirrors htmlparse.y cell rule
|
|
224
223
|
// ---------------------------------------------------------------------------
|
|
@@ -238,7 +237,7 @@ const parseImgContent = (
|
|
|
238
237
|
|
|
239
238
|
/** What a cell holds so far: C's cell has exactly one child. */
|
|
240
239
|
interface CellScan {
|
|
241
|
-
kind: 'none' | 'text' | 'table' | 'image';
|
|
240
|
+
kind: 'none' | 'text' | 'table' | 'fonttable' | 'image';
|
|
242
241
|
/** Non-space text before the nested table (checked when the table opens). */
|
|
243
242
|
textNonSpace: boolean;
|
|
244
243
|
/** Non-space text after the nested table (checked when the run ends). */
|
|
@@ -261,6 +260,7 @@ const scanCellText = (s: ParseState, t: Token, fontStack: FontState[], st: CellS
|
|
|
261
260
|
const start = s.pos;
|
|
262
261
|
const txt = parseText(s, fontStack);
|
|
263
262
|
if (s.pos === start) throw parseError(s, t, MSG_SYNTAX_ERROR);
|
|
263
|
+
rejectTableAfterTag(s, start);
|
|
264
264
|
if (txt.items.some((i) => i.text !== undefined && nonSpace(i.text))) st.textNonSpace = true;
|
|
265
265
|
st.kind = 'text';
|
|
266
266
|
return txt.items.length > 0 ? [txt] : [];
|
|
@@ -287,7 +287,13 @@ const scanCellImage = (
|
|
|
287
287
|
const scanCellItem = (
|
|
288
288
|
s: ParseState, t: Token, cellAttrs: Record<string, string>, fontStack: FontState[], st: CellScan,
|
|
289
289
|
): HtmlCellContent[] => {
|
|
290
|
+
if (st.kind === 'fonttable') throw parseError(s, t, MSG_SYNTAX_ERROR);
|
|
290
291
|
if (st.kind === 'table') return scanAfterTable(s, t, st);
|
|
292
|
+
const wrapper = st.kind === 'none' ? fontTableWrapper(s) : undefined;
|
|
293
|
+
if (wrapper !== undefined) {
|
|
294
|
+
st.kind = 'fonttable';
|
|
295
|
+
return [parseFontTable(s, wrapper, fontStack)];
|
|
296
|
+
}
|
|
291
297
|
if (t.type === 'open' && t.tag === 'TABLE') return scanCellTable(s, t, fontStack, st);
|
|
292
298
|
if (t.type === 'open' && t.tag === 'IMG') return scanCellImage(s, t, cellAttrs, st);
|
|
293
299
|
if (st.kind === 'image') throw parseError(s, t, MSG_SYNTAX_ERROR);
|
|
@@ -438,7 +444,10 @@ export const parseTable = (s: ParseState, fontStack: FontState[]): HtmlTable =>
|
|
|
438
444
|
for (let t = peek(s); t !== undefined; t = peek(s)) {
|
|
439
445
|
if (scanTableToken(s, t, fontStack, ts)) break;
|
|
440
446
|
}
|
|
441
|
-
|
|
447
|
+
const table = buildTable(ts.rows, open.attrs);
|
|
448
|
+
const font = tableFont(fontStack);
|
|
449
|
+
if (font !== undefined) table.font = font;
|
|
450
|
+
return table;
|
|
442
451
|
};
|
|
443
452
|
|
|
444
453
|
/** Parse one row, applying table-level rule defaults. @see lib/common/htmlparse.y addRow/setCell */
|
|
@@ -497,6 +506,16 @@ const finishTableLabel = (s: ParseState): void => {
|
|
|
497
506
|
consume(s);
|
|
498
507
|
};
|
|
499
508
|
|
|
509
|
+
/** `html : T_html fonttable T_end_html` with a font-like wrapper. */
|
|
510
|
+
const parseFontTableLabel = (s: ParseState, open: OpenToken, fontStack: FontState[]): HtmlLabel => {
|
|
511
|
+
const table = parseFontTable(s, open, fontStack);
|
|
512
|
+
const nxt = peek(s);
|
|
513
|
+
if (nxt === undefined) return { kind: 'table', table };
|
|
514
|
+
if (nxt.type !== 'close' || nxt.tag !== 'HTML') throw parseError(s, nxt, MSG_SYNTAX_ERROR);
|
|
515
|
+
consume(s);
|
|
516
|
+
return { kind: 'table', table };
|
|
517
|
+
};
|
|
518
|
+
|
|
500
519
|
const parseTableLabel = (s: ParseState, fontStack: FontState[]): HtmlLabel => {
|
|
501
520
|
skipLeadingWs(s);
|
|
502
521
|
const table = parseTable(s, fontStack);
|
|
@@ -504,25 +523,22 @@ const parseTableLabel = (s: ParseState, fontStack: FontState[]): HtmlLabel => {
|
|
|
504
523
|
return { kind: 'table', table };
|
|
505
524
|
};
|
|
506
525
|
|
|
507
|
-
/** `html : T_html fonttext T_end_html
|
|
526
|
+
/** `html : T_html fonttext T_end_html` (a textitem is required); a `<TABLE>` after text is the table rule's error. */
|
|
508
527
|
const parseTextLabel = (s: ParseState, fontStack: FontState[]): HtmlLabel => {
|
|
509
528
|
const texts: HtmlText[] = [];
|
|
529
|
+
const start = s.pos;
|
|
510
530
|
const txt = parseText(s, fontStack);
|
|
511
531
|
if (txt.items.length > 0) texts.push(txt);
|
|
512
532
|
const nxt = peek(s);
|
|
513
533
|
if (nxt === undefined) return { kind: 'text', texts };
|
|
514
534
|
if (nxt.type === 'open' && nxt.tag === 'TABLE') {
|
|
515
535
|
// C's `table` rule aborts the whole label when non-space text precedes a
|
|
516
|
-
// top-level <TABLE
|
|
517
|
-
// (`fonttable`), which this parser does not model: rejected without a
|
|
518
|
-
// message rather than with a wrong one.
|
|
536
|
+
// top-level <TABLE>; after any tag it is a plain syntax error.
|
|
519
537
|
// @see lib/common/htmlparse.y:288 (table rule nonSpace check)
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
throw new HtmlParseError('TABLE');
|
|
538
|
+
rejectTableAfterTag(s, start);
|
|
539
|
+
throw parseError(s, nxt, MSG_TEXT_BEFORE_TABLE);
|
|
524
540
|
}
|
|
525
|
-
if (nxt.type !== 'close' || nxt.tag !== 'HTML') throw parseError(s, nxt, MSG_SYNTAX_ERROR);
|
|
541
|
+
if (nxt.type !== 'close' || nxt.tag !== 'HTML' || s.pos === start) throw parseError(s, nxt, MSG_SYNTAX_ERROR);
|
|
526
542
|
consume(s);
|
|
527
543
|
return { kind: 'text', texts };
|
|
528
544
|
};
|
|
@@ -532,28 +548,52 @@ const parseTokens = (s: ParseState): HtmlLabel => {
|
|
|
532
548
|
const fontStack: FontState[] = [{}];
|
|
533
549
|
const htmlOpen = consume(s);
|
|
534
550
|
if (htmlOpen.type !== 'open' || htmlOpen.tag !== 'HTML') throw new HtmlParseError('HTML');
|
|
551
|
+
const wrapper = fontTableWrapper(s);
|
|
552
|
+
if (wrapper !== undefined) return parseFontTableLabel(s, wrapper, fontStack);
|
|
535
553
|
const firstReal = findFirstRealToken(s.tokens, s.pos);
|
|
536
554
|
if (firstReal?.type === 'open' && firstReal.tag === 'TABLE') return parseTableLabel(s, fontStack);
|
|
537
555
|
return parseTextLabel(s, fontStack);
|
|
538
556
|
};
|
|
539
557
|
|
|
558
|
+
/** A parsed label and `ctx->warn`, which `make_html_label` ORs into its rv. */
|
|
559
|
+
export interface ParsedHtmlLabel {
|
|
560
|
+
label: HtmlLabel;
|
|
561
|
+
warn: boolean;
|
|
562
|
+
}
|
|
563
|
+
|
|
540
564
|
/**
|
|
541
|
-
* Parse an HTML-like label string
|
|
542
|
-
*
|
|
565
|
+
* Parse an HTML-like label string, printing the lexer's warnings (one
|
|
566
|
+
* `writeLine` per stderr line, `console.warn` by default) as the parser reaches
|
|
567
|
+
* their tokens. Input is the content between the outer `<` `>` delimiters
|
|
568
|
+
* (already stripped).
|
|
543
569
|
*
|
|
544
570
|
* @see lib/common/htmlparse.y:parseHTML
|
|
545
571
|
*/
|
|
546
|
-
export const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
572
|
+
export const parseHtmlLabelRv = (
|
|
573
|
+
src: string,
|
|
574
|
+
writeLine: (line: string) => void = (line) => console.warn(line),
|
|
575
|
+
): ParsedHtmlLabel => {
|
|
576
|
+
const { tokens, error, eof } = tokenizeDeferred(`<HTML>${src}</HTML>`, { start: 6, end: 6 + src.length });
|
|
577
|
+
const s: ParseState = {
|
|
578
|
+
tokens, pos: 0, pending: error, stderr: new StderrLines(writeLine), emitted: new WeakSet(), warn: false, eof,
|
|
579
|
+
};
|
|
550
580
|
try {
|
|
551
|
-
label = parseTokens(s);
|
|
581
|
+
const label = parseTokens(s);
|
|
582
|
+
if (error !== undefined) throw error;
|
|
583
|
+
return { label, warn: s.warn === true };
|
|
552
584
|
} catch (e) {
|
|
553
585
|
// Running off the truncated stream means C's lexer would have hit the error.
|
|
554
|
-
if (error !== undefined && s.pos >= tokens.length) throw error;
|
|
586
|
+
if (error !== undefined && s.pos >= tokens.length) { emitEof(s); throw error; }
|
|
555
587
|
throw e;
|
|
588
|
+
} finally {
|
|
589
|
+
s.stderr?.flush();
|
|
556
590
|
}
|
|
557
|
-
if (error !== undefined) throw error;
|
|
558
|
-
return label;
|
|
559
591
|
};
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* {@link parseHtmlLabelRv} without printing: callers that only inspect the
|
|
595
|
+
* label (the async font/image collection pass) must not repeat the warnings
|
|
596
|
+
* the sync pass prints.
|
|
597
|
+
*/
|
|
598
|
+
export const parseHtmlLabel = (src: string): HtmlLabel =>
|
|
599
|
+
parseHtmlLabelRv(src, () => undefined).label;
|
|
@@ -20,7 +20,7 @@ import type { TextlabelT } from './types.js';
|
|
|
20
20
|
import type { TextMeasurer } from './textmeasure.js';
|
|
21
21
|
import type { HtmlCell, HtmlImage, HtmlLabel, HtmlTable, HtmlText, ImageSizer } from './htmltable-types.js';
|
|
22
22
|
import {
|
|
23
|
-
layoutHtmlTable, cellPad, cellBorder, sizeHtmlLabel,
|
|
23
|
+
layoutHtmlTable, cellPad, cellBorder, sizeHtmlLabel, parseHtmlLabelRv, inheritTablePenColor, withTableFont,
|
|
24
24
|
} from './htmltable.js';
|
|
25
25
|
import { makeLabel } from './make-label.js';
|
|
26
26
|
import { HtmlLexError } from './htmltable-lex.js';
|
|
@@ -292,8 +292,8 @@ function alignImageBox(cbox: Box, cell: HtmlCell, iw: number, ih: number): void
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
/** Position a nested <TABLE> child inside the cell's inset content box
|
|
296
|
-
* @see
|
|
295
|
+
/** Position a nested <TABLE> child inside the cell's inset content box; its font push
|
|
296
|
+
* plays emit_html_tbl's role. @see htmltable.c:pos_html_cell (HTML_TBL branch), emit_html_tbl */
|
|
297
297
|
function placeCellNested(ctx: CellPlaceCtx): PlacedHtml | undefined {
|
|
298
298
|
const { cell, tbl, box, finfo, measurer, sidesMask } = ctx;
|
|
299
299
|
const nested = cell.content.find((c): c is HtmlTable => c.kind === 'table');
|
|
@@ -303,7 +303,7 @@ function placeCellNested(ctx: CellPlaceCtx): PlacedHtml | undefined {
|
|
|
303
303
|
ll: { x: box.ll.x + inset, y: box.ll.y + inset },
|
|
304
304
|
ur: { x: box.ur.x - inset, y: box.ur.y - inset },
|
|
305
305
|
};
|
|
306
|
-
return posHtmlTable(nested, finfo, measurer, cbox, sidesMask);
|
|
306
|
+
return withTableFont(finfo, nested.font, 'emit', () => posHtmlTable(nested, finfo, measurer, cbox, sidesMask));
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
/** Position one cell's text inside its box, copying decoration metadata. @see pos_html_cell */
|
|
@@ -516,7 +516,8 @@ export function posHtmlLabel(
|
|
|
516
516
|
finfo: HtmlFontInfo,
|
|
517
517
|
measurer: TextMeasurer,
|
|
518
518
|
): PlacedHtml {
|
|
519
|
-
|
|
519
|
+
const env = { ...finfo }; // copied: the emit-side savef leak must not outlive the label
|
|
520
|
+
if (label.kind === 'table') return withTableFont(env, label.table.font, 'emit', () => posHtmlTable(label.table, env, measurer));
|
|
520
521
|
return posTextLabel(label, finfo, measurer);
|
|
521
522
|
}
|
|
522
523
|
|
|
@@ -542,9 +543,9 @@ export function makeHtmlLabelRv(
|
|
|
542
543
|
measurer: TextMeasurer,
|
|
543
544
|
): HtmlLabelResult {
|
|
544
545
|
const { fontname, fontsize, fontcolor, imageSizer, pencolor } = finfo;
|
|
545
|
-
let lbl: HtmlLabel;
|
|
546
|
+
let lbl: HtmlLabel, lexWarn: boolean; // lexWarn: htmllex.c's ctx->warn, rv bit 1
|
|
546
547
|
try {
|
|
547
|
-
lbl =
|
|
548
|
+
({ label: lbl, warn: lexWarn } = parseHtmlLabelRv(content));
|
|
548
549
|
} catch (e) {
|
|
549
550
|
// C aborts the HTML parse on a syntax error (YYABORT): parseHTML returns
|
|
550
551
|
// rv 3 (ctx->error) with no label, and make_html_label returns it with
|
|
@@ -557,7 +558,7 @@ export function makeHtmlLabelRv(
|
|
|
557
558
|
// which then flows table->cell->nested-table.
|
|
558
559
|
// @see lib/common/htmltable.c:1911 (getPenColor), :1406/:1556 (inheritance)
|
|
559
560
|
if (lbl.kind === 'table') inheritTablePenColor(lbl.table, pencolor);
|
|
560
|
-
const rv = sizeHtmlLabel(lbl, measurer, {
|
|
561
|
+
const rv = Number(lexWarn) | sizeHtmlLabel(lbl, measurer, {
|
|
561
562
|
fontsize, fontname, imageSizer,
|
|
562
563
|
});
|
|
563
564
|
const placed = posHtmlLabel(lbl, { fontname, fontsize, fontcolor }, measurer);
|
|
@@ -176,10 +176,24 @@ export interface HtmlRow {
|
|
|
176
176
|
ruled?: boolean;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
/**
|
|
180
|
+
* The font a `fonttable` wrapper (or an enclosing one) gives a table: the
|
|
181
|
+
* name, color and size `pushFontInfo` swaps into the environment.
|
|
182
|
+
* @see lib/common/htmltable.h:htmltbl_t.font
|
|
183
|
+
* @see lib/common/htmltable.c:pushFontInfo
|
|
184
|
+
*/
|
|
185
|
+
export interface HtmlTableFont {
|
|
186
|
+
fontColor?: string;
|
|
187
|
+
fontFace?: string;
|
|
188
|
+
fontSize?: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
179
191
|
/** @see lib/common/htmltable.h:htmltbl_t */
|
|
180
192
|
export interface HtmlTable {
|
|
181
193
|
kind: 'table';
|
|
182
194
|
rows: HtmlRow[];
|
|
195
|
+
/** Set only inside a `fonttable` wrapper. @see lib/common/htmlparse.y:294 */
|
|
196
|
+
font?: HtmlTableFont;
|
|
183
197
|
border?: number;
|
|
184
198
|
cellborder?: number;
|
|
185
199
|
cellspacing?: number;
|
|
@@ -238,21 +252,39 @@ export type HtmlLabel =
|
|
|
238
252
|
// the lex file's function boundaries when it encounters `|` union syntax.
|
|
239
253
|
// ---------------------------------------------------------------------------
|
|
240
254
|
|
|
255
|
+
/**
|
|
256
|
+
* What the C lexer wrote to stderr while producing a token, and the `ctx->warn`
|
|
257
|
+
* it set. Any token can carry it: a unit that yields no token (a comment) or
|
|
258
|
+
* an event lost to a later one in the same `XML_Parse` call still wrote.
|
|
259
|
+
* @see lib/common/htmllex.c:htmllex
|
|
260
|
+
*/
|
|
261
|
+
export interface LexerOutput {
|
|
262
|
+
/**
|
|
263
|
+
* Exact text per write, including any trailing newline (a message without
|
|
264
|
+
* one runs into the next write). The parser prints it when it consumes the
|
|
265
|
+
* token.
|
|
266
|
+
* @see lib/common/htmllex.c:doAttrs
|
|
267
|
+
*/
|
|
268
|
+
warnings?: string[];
|
|
269
|
+
/** Some handler set `ctx->warn`: the label's `rv` becomes 1. */
|
|
270
|
+
warn?: true;
|
|
271
|
+
}
|
|
272
|
+
|
|
241
273
|
/** @see lib/common/htmllex.c:startElement */
|
|
242
|
-
export interface OpenToken {
|
|
274
|
+
export interface OpenToken extends LexerOutput {
|
|
243
275
|
type: 'open';
|
|
244
276
|
tag: string;
|
|
245
277
|
attrs: Record<string, string>;
|
|
246
278
|
}
|
|
247
279
|
|
|
248
280
|
/** @see lib/common/htmllex.c:endElement */
|
|
249
|
-
export interface CloseToken {
|
|
281
|
+
export interface CloseToken extends LexerOutput {
|
|
250
282
|
type: 'close';
|
|
251
283
|
tag: string;
|
|
252
284
|
}
|
|
253
285
|
|
|
254
286
|
/** @see lib/common/htmllex.c:characterData */
|
|
255
|
-
export interface TextToken {
|
|
287
|
+
export interface TextToken extends LexerOutput {
|
|
256
288
|
type: 'text';
|
|
257
289
|
value: string;
|
|
258
290
|
}
|
package/src/common/htmltable.ts
CHANGED
|
@@ -12,10 +12,10 @@ export { HtmlParseError } from './htmltable-types.js';
|
|
|
12
12
|
export type {
|
|
13
13
|
HtmlAlign, HtmlVAlign,
|
|
14
14
|
HtmlHR, HtmlImage, HtmlTextItem, HtmlText,
|
|
15
|
-
HtmlCellContent, HtmlCell, HtmlRow, HtmlTable, HtmlLabel,
|
|
15
|
+
HtmlCellContent, HtmlCell, HtmlRow, HtmlTable, HtmlTableFont, HtmlLabel,
|
|
16
16
|
ImageSizer,
|
|
17
17
|
} from './htmltable-types.js';
|
|
18
|
-
export { parseHtmlLabel } from './htmltable-parse.js';
|
|
18
|
+
export { parseHtmlLabel, parseHtmlLabelRv } from './htmltable-parse.js';
|
|
19
19
|
export type { TextMeasurer } from './textmeasure.js';
|
|
20
20
|
|
|
21
21
|
import type {
|
|
@@ -26,6 +26,9 @@ import type { ImageSizer } from './htmltable-types.js';
|
|
|
26
26
|
import { findImageSize } from '../gvc/usershape.js';
|
|
27
27
|
import { PointSet } from './pointset.js';
|
|
28
28
|
import type { TextMeasurer } from './textmeasure.js';
|
|
29
|
+
import { withTableFont } from './htmltable-fontinfo.js';
|
|
30
|
+
|
|
31
|
+
export { withTableFont } from './htmltable-fontinfo.js';
|
|
29
32
|
|
|
30
33
|
const DEFAULT_BORDER = 1;
|
|
31
34
|
const DEFAULT_CELLPADDING = 2;
|
|
@@ -84,7 +87,7 @@ export interface HtmlSizeEnv {
|
|
|
84
87
|
imageSizer?: ImageSizer;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
const
|
|
90
|
+
const defaultSizeEnv = (): HtmlSizeEnv => ({ fontsize: DEFAULT_FONTSIZE, fontname: '' });
|
|
88
91
|
|
|
89
92
|
/** One measured text line (a C "span"). @see size_html_txt */
|
|
90
93
|
interface SizedLine {
|
|
@@ -196,7 +199,7 @@ export const htmlTextIsSimple = (texts: HtmlText[], env: HtmlSizeEnv): boolean =
|
|
|
196
199
|
const sizeTextContent = (
|
|
197
200
|
texts: HtmlText[],
|
|
198
201
|
measurer: TextMeasurer,
|
|
199
|
-
env: HtmlSizeEnv =
|
|
202
|
+
env: HtmlSizeEnv = defaultSizeEnv(),
|
|
200
203
|
): { w: number; h: number } => {
|
|
201
204
|
const lines = splitSizedLines(texts, measurer, env);
|
|
202
205
|
const simple = htmlTextIsSimple(texts, env);
|
|
@@ -481,7 +484,7 @@ export interface HtmlTableLayout {
|
|
|
481
484
|
export const layoutHtmlTable = (
|
|
482
485
|
tbl: HtmlTable,
|
|
483
486
|
measurer: TextMeasurer,
|
|
484
|
-
env: HtmlSizeEnv =
|
|
487
|
+
env: HtmlSizeEnv = defaultSizeEnv(),
|
|
485
488
|
): HtmlTableLayout => {
|
|
486
489
|
const spacing = getSpacing(tbl);
|
|
487
490
|
const border = getBorder(tbl);
|
|
@@ -515,7 +518,7 @@ const warnFixedTable = (tbl: HtmlTable, dims: { wd: number; ht: number }): numbe
|
|
|
515
518
|
};
|
|
516
519
|
|
|
517
520
|
/** @see lib/common/htmltable.c:size_html_tbl */
|
|
518
|
-
const
|
|
521
|
+
const sizeTableBody = (
|
|
519
522
|
tbl: HtmlTable,
|
|
520
523
|
measurer: TextMeasurer,
|
|
521
524
|
env: HtmlSizeEnv,
|
|
@@ -546,6 +549,10 @@ const sizeTableInner = (
|
|
|
546
549
|
return rv | fixed;
|
|
547
550
|
};
|
|
548
551
|
|
|
552
|
+
/** @see lib/common/htmltable.c:size_html_tbl (pushFontInfo ... popFontInfo around the body) */
|
|
553
|
+
const sizeTableInner = (tbl: HtmlTable, measurer: TextMeasurer, env: HtmlSizeEnv): number =>
|
|
554
|
+
withTableFont(env, tbl.font, 'size', () => sizeTableBody(tbl, measurer, env));
|
|
555
|
+
|
|
549
556
|
/**
|
|
550
557
|
* Compute and write `dimen` onto all cells and the label.
|
|
551
558
|
*
|
|
@@ -559,7 +566,7 @@ const sizeTableInner = (
|
|
|
559
566
|
export const sizeHtmlLabel = (
|
|
560
567
|
label: HtmlLabel,
|
|
561
568
|
measurer: TextMeasurer,
|
|
562
|
-
env: HtmlSizeEnv =
|
|
569
|
+
env: HtmlSizeEnv = defaultSizeEnv(),
|
|
563
570
|
): number => {
|
|
564
571
|
if (label.kind === 'table') {
|
|
565
572
|
const rv = sizeTableInner(label.table, measurer, env);
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { Point } from '../model/geom.js';
|
|
12
|
+
import { bezier } from './bezier.js';
|
|
12
13
|
import { fma } from './fma.js';
|
|
13
14
|
|
|
14
15
|
type BezLike = { list: Point[]; sflag: number; eflag: number; sp: Point; ep: Point; size?: number };
|
|
@@ -30,27 +31,6 @@ export function dist2(p: Point, q: Point): number {
|
|
|
30
31
|
/** DIST: sqrt of the fused DIST2 (`fmadd` then `fsqrt`). @see lib/common/geom.h:DIST */
|
|
31
32
|
function distPt(p: Point, q: Point): number { return Math.sqrt(dist2(p, q)); }
|
|
32
33
|
|
|
33
|
-
/**
|
|
34
|
-
* Point at t on a cubic: C's de Casteljau triangle. Each
|
|
35
|
-
* `(1.0 - t) * a + t * b` compiles to `fmul v, b, t; fmla v, a, 1-t`, i.e.
|
|
36
|
-
* fma(a, 1 - t, t * b), both in Bezier itself and where dotneato_closest
|
|
37
|
-
* inlines it (libgvc 16.1 disassembly). The Left/Right halves are not needed
|
|
38
|
-
* here, so only the apex is kept.
|
|
39
|
-
* @see lib/common/utils.c:Bezier
|
|
40
|
-
*/
|
|
41
|
-
function bezier(v: Point[], t: number): Point {
|
|
42
|
-
const u = 1.0 - t;
|
|
43
|
-
const x = [v[0].x, v[1].x, v[2].x, v[3].x];
|
|
44
|
-
const y = [v[0].y, v[1].y, v[2].y, v[3].y];
|
|
45
|
-
for (let i = 1; i <= 3; i++) {
|
|
46
|
-
for (let j = 0; j <= 3 - i; j++) {
|
|
47
|
-
x[j] = fma(x[j], u, t * x[j + 1]);
|
|
48
|
-
y[j] = fma(y[j], u, t * y[j + 1]);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return { x: x[0], y: y[0] };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
34
|
/** @see lib/common/utils.c:dotneato_closest */
|
|
55
35
|
export function dotneatoClosest(spl: SplLike, pt: Point): Point {
|
|
56
36
|
let besti = 0; let bestj = -1; let bestd2 = Number.MAX_VALUE;
|
|
@@ -76,7 +56,7 @@ export function dotneatoClosest(spl: SplLike, pt: Point): Point {
|
|
|
76
56
|
let pt2: Point;
|
|
77
57
|
for (;;) {
|
|
78
58
|
const t = (lo + hi) / 2.0;
|
|
79
|
-
pt2 = bezier(c, t);
|
|
59
|
+
pt2 = bezier(c, t, null, null);
|
|
80
60
|
if (Math.abs(dlo2 - dhi2) < 1.0 || Math.abs(hi - lo) < 0.00001) break;
|
|
81
61
|
if (dlo2 < dhi2) { hi = t; dhi2 = dist2(pt2, pt); }
|
|
82
62
|
else { lo = t; dlo2 = dist2(pt2, pt); }
|