reladraw 0.1.0 → 0.3.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 +45 -35
- package/SYNTAX.md +498 -193
- package/dist/ast.d.ts +257 -38
- package/dist/ast.js +250 -18
- package/dist/constants.d.ts +60 -42
- package/dist/constants.js +78 -63
- package/dist/grammar.d.ts +24 -11
- package/dist/grammar.js +61 -15
- package/dist/icons.d.ts +55 -37
- package/dist/icons.js +83 -45
- package/dist/lexer.d.ts +1 -1
- package/dist/lexer.js +7 -6
- package/dist/measure.d.ts +3 -3
- package/dist/measure.js +3 -3
- package/dist/model.d.ts +86 -16
- package/dist/parser.js +529 -179
- package/dist/render.d.ts +1 -1
- package/dist/render.js +401 -320
- package/dist/resolve.js +1287 -249
- package/dist/text.d.ts +48 -0
- package/dist/text.js +196 -0
- package/package.json +2 -2
package/dist/ast.js
CHANGED
|
@@ -16,41 +16,182 @@ export function isDirection(word) {
|
|
|
16
16
|
return DIRECTIONS.includes(word);
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Which
|
|
19
|
+
* Which side of the target a `level with` shares. `center` is the plain form;
|
|
20
20
|
* the rest are written in front of it, as in `top level with media`.
|
|
21
21
|
*/
|
|
22
|
-
export const
|
|
23
|
-
/**
|
|
24
|
-
export const
|
|
25
|
-
|
|
22
|
+
export const SIDES = ['center', 'top', 'bottom', 'left', 'right'];
|
|
23
|
+
/** A side belongs to one axis, so an alignment never has to say which. */
|
|
24
|
+
export const SIDE_AXIS = {
|
|
25
|
+
center: 'y',
|
|
26
26
|
top: 'y',
|
|
27
27
|
bottom: 'y',
|
|
28
28
|
left: 'x',
|
|
29
29
|
right: 'x',
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* The nine points of a box anybody can name without measuring: the four
|
|
33
|
+
* corners, the four side midpoints, and the center. One closed set, accepted
|
|
34
|
+
* everywhere the language has a position — which is the rule that replaced a
|
|
35
|
+
* scatter of one-position slots, each decided on its own and each a little
|
|
36
|
+
* piece of the same expressiveness loss.
|
|
37
|
+
*
|
|
38
|
+
* Closed-and-meaningful is allowed where open-and-ordinal is not: these are
|
|
39
|
+
* words a reader decodes, and a diagram written in them still moves correctly
|
|
40
|
+
* when a box moves, which is the property the refusal of `x: 140` protects.
|
|
41
|
+
*
|
|
42
|
+
* A compound position is hyphenated and is one token, matching the diagonal
|
|
43
|
+
* directions above. A *phrase* of separate keywords stays spaced (`level
|
|
44
|
+
* with`); a compound *word* does not.
|
|
45
|
+
*
|
|
46
|
+
* Every midpoint carries `-center` rather than standing alone as `top` or
|
|
47
|
+
* `left`. Two reasons, and the second is the binding one. A side midpoint reads
|
|
48
|
+
* as "the bottom edge, centered along it", which is what the word says. And
|
|
49
|
+
* `top`, `bottom`, `left` and `right` already name a *side* in this language —
|
|
50
|
+
* an edge's `from:` and an alignment's `top level with` — so a bare `bottom`
|
|
51
|
+
* would mean a side in one place and a point in another. `from: bottom` spreads
|
|
52
|
+
* attachments along the side; `from: bottom-center` will pin one to the point.
|
|
53
|
+
*/
|
|
54
|
+
export const POSITIONS = [
|
|
55
|
+
'top-left',
|
|
56
|
+
'top-center',
|
|
57
|
+
'top-right',
|
|
58
|
+
'left-center',
|
|
59
|
+
'center',
|
|
60
|
+
'right-center',
|
|
61
|
+
'bottom-left',
|
|
62
|
+
'bottom-center',
|
|
63
|
+
'bottom-right',
|
|
64
|
+
];
|
|
65
|
+
export function isPosition(word) {
|
|
66
|
+
return POSITIONS.includes(word);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A part of a node that a placement may name: its text, one of its four sides,
|
|
70
|
+
* or one of its nine points. Written as a separate word after the node's name
|
|
71
|
+
* — `hub text`, `server right`, `board top-right`.
|
|
72
|
+
*
|
|
73
|
+
* No noun is carried. `board right side` was considered and dropped, because
|
|
74
|
+
* the language already tells a segment from a point by *spelling*: a bare
|
|
75
|
+
* `top` names a side everywhere (`from: bottom`, `top level with`) and a
|
|
76
|
+
* hyphenated `top-right` names a point, which is exactly why every midpoint
|
|
77
|
+
* carries `-center` rather than standing alone. A noun would mark with a word
|
|
78
|
+
* a distinction the hyphen already marks.
|
|
79
|
+
*
|
|
80
|
+
* The spaced form needs no reservation against a child called `text`: a
|
|
81
|
+
* sibling is always written dotted (`right of server.mirror`), so `hub text`
|
|
82
|
+
* spaced can never be `hub.text`. The dotted spelling was rejected because it
|
|
83
|
+
* interferes with the dot's one meaning.
|
|
84
|
+
*/
|
|
85
|
+
export const PART_SIDES = ['top', 'bottom', 'left', 'right'];
|
|
86
|
+
export const PARTS = ['text', ...PART_SIDES, ...POSITIONS];
|
|
87
|
+
export function isPart(word) {
|
|
88
|
+
return PARTS.includes(word);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Which way "toward the box's center" points from a part. This is the whole of
|
|
92
|
+
* what `inside` and `outside` mean, which is why neither needs a table of its
|
|
93
|
+
* own: `inside` is this direction, `outside` is its opposite, and one rule
|
|
94
|
+
* covers every part.
|
|
95
|
+
*
|
|
96
|
+
* `center` and `text` are absent on purpose. Neither is on the boundary, so
|
|
97
|
+
* there is no direction toward the interior from them, and both are refused by
|
|
98
|
+
* name where the shorthand is read.
|
|
99
|
+
*/
|
|
100
|
+
export const INWARD = {
|
|
101
|
+
top: 'below',
|
|
102
|
+
bottom: 'above',
|
|
103
|
+
left: 'right',
|
|
104
|
+
right: 'left',
|
|
105
|
+
'top-left': 'below-right',
|
|
106
|
+
'top-center': 'below',
|
|
107
|
+
'top-right': 'below-left',
|
|
108
|
+
'left-center': 'right',
|
|
109
|
+
'right-center': 'left',
|
|
110
|
+
'bottom-left': 'above-right',
|
|
111
|
+
'bottom-center': 'above',
|
|
112
|
+
'bottom-right': 'above-left',
|
|
113
|
+
};
|
|
114
|
+
/** The other way round, for `outside`. */
|
|
115
|
+
export const OPPOSITE = {
|
|
116
|
+
above: 'below',
|
|
117
|
+
below: 'above',
|
|
118
|
+
left: 'right',
|
|
119
|
+
right: 'left',
|
|
120
|
+
'above-left': 'below-right',
|
|
121
|
+
'above-right': 'below-left',
|
|
122
|
+
'below-left': 'above-right',
|
|
123
|
+
'below-right': 'above-left',
|
|
124
|
+
};
|
|
125
|
+
/** The parts `inside` and `outside` can be read from — everything on the boundary. */
|
|
126
|
+
export const BOUNDARY_PARTS = Object.keys(INWARD);
|
|
31
127
|
/**
|
|
32
128
|
* The modifiers a placement understands, in brackets after its targets. Refused
|
|
33
|
-
* by name when
|
|
129
|
+
* by name when unrecognized, for the reason `DIAGRAM_KEYS` are: a modifier that
|
|
34
130
|
* silently does nothing looks like a bug in the tool rather than a typo.
|
|
35
131
|
*/
|
|
36
132
|
export const PLACEMENT_KEYS = ['gap'];
|
|
133
|
+
/**
|
|
134
|
+
* `contents: (widths: match, align: center)` — how a container's children sit
|
|
135
|
+
* inside it. A container has a fill, a border, a title and its contents, and
|
|
136
|
+
* this is the one key that points at the last of them.
|
|
137
|
+
*
|
|
138
|
+
* Two properties, independent, which is why they are bracketed modifiers of one
|
|
139
|
+
* key rather than two keys side by side. The cross product has a live cell in
|
|
140
|
+
* every corner — *equal widths, ranged left, under a long title* is the case
|
|
141
|
+
* that proves it — so one token could never carry both. And they sit at
|
|
142
|
+
* different levels: `contents:` is a property of the node, `widths:` a property
|
|
143
|
+
* of the contents, which written as peers would read as two facts about the box
|
|
144
|
+
* when they are one about the box and one about its children.
|
|
145
|
+
*
|
|
146
|
+
* It replaced `align: widths`, which was a size operation wearing an
|
|
147
|
+
* alignment's name and had a one-element value set — a flag in a property's
|
|
148
|
+
* clothes. With it gone, `align` means one thing everywhere: how a text's lines
|
|
149
|
+
* range against each other.
|
|
150
|
+
*/
|
|
151
|
+
export const CONTENTS_KEYS = ['widths', 'align'];
|
|
152
|
+
/**
|
|
153
|
+
* What `widths:` may say. `natural` is the default and is today's behavior.
|
|
154
|
+
* `match` is what `align: widths` did — every child as wide as the widest.
|
|
155
|
+
* `fill` is the whole content band, which is `match` wherever the contents set
|
|
156
|
+
* the container's width and the better answer wherever the title wins.
|
|
157
|
+
*/
|
|
158
|
+
export const CONTENT_WIDTHS = ['natural', 'match', 'fill'];
|
|
159
|
+
/**
|
|
160
|
+
* Where the block of contents sits when it is narrower than the band. `right`
|
|
161
|
+
* is accepted although no diagram has yet wanted it: refusing it would give
|
|
162
|
+
* `align:` a different value set depending on which bracket it is in, which is
|
|
163
|
+
* the divergence this scheme exists to remove.
|
|
164
|
+
*/
|
|
165
|
+
export const CONTENT_ALIGNMENTS = ['left', 'center', 'right'];
|
|
37
166
|
/** How a placement reads back in the author's own words, for error messages. */
|
|
38
167
|
export function describePlacement(placement) {
|
|
39
168
|
const targets = listTargets(placement.targets);
|
|
169
|
+
if (placement.kind === 'on') {
|
|
170
|
+
return `on ${targets}`;
|
|
171
|
+
}
|
|
40
172
|
if (placement.kind === 'align') {
|
|
41
|
-
const
|
|
42
|
-
return `${
|
|
173
|
+
const side = placement.side === 'center' ? '' : `${placement.side} `;
|
|
174
|
+
return `${side}level with ${targets}`;
|
|
43
175
|
}
|
|
176
|
+
const gap = placement.gap === undefined ? '' : ` (gap: ${placement.gap})`;
|
|
177
|
+
// `inside` and `outside` take no `of`, and are quoted back as the author
|
|
178
|
+
// wrote them — the derived direction is what they *mean*, not what they say.
|
|
179
|
+
if (placement.written)
|
|
180
|
+
return `${placement.written} ${targets}${gap}`;
|
|
44
181
|
// "left of X" and "above X" are both good English; "above of X" is not.
|
|
45
182
|
const joiner = placement.direction === 'above' || placement.direction === 'below' ? '' : 'of ';
|
|
46
|
-
const gap = placement.gap === undefined ? '' : ` (gap: ${placement.gap})`;
|
|
47
183
|
return `${placement.direction} ${joiner}${targets}${gap}`;
|
|
48
184
|
}
|
|
49
|
-
/** "borg", "
|
|
185
|
+
/** "borg", "hub text", "borg and bare" — as the author would write them. */
|
|
50
186
|
export function listTargets(targets) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
187
|
+
const written = targets.map(nameTarget);
|
|
188
|
+
if (written.length <= 1)
|
|
189
|
+
return written[0] ?? '';
|
|
190
|
+
return `${written.slice(0, -1).join(', ')} and ${written[written.length - 1]}`;
|
|
191
|
+
}
|
|
192
|
+
/** One target in the author's words: the node's name, and its part if it named one. */
|
|
193
|
+
export function nameTarget(target) {
|
|
194
|
+
return target.part === undefined ? target.name : `${target.name} ${target.part}`;
|
|
54
195
|
}
|
|
55
196
|
/** How the axis of a passage is written, and what it means. */
|
|
56
197
|
export const PASSAGE_AXES = {
|
|
@@ -65,14 +206,105 @@ export function describeAxis(axis) {
|
|
|
65
206
|
return axis === 'y' ? 'vertically' : 'horizontally';
|
|
66
207
|
}
|
|
67
208
|
/**
|
|
68
|
-
* What a
|
|
209
|
+
* What a text's brackets may say: `"Docker" (at: bottom-center, color: muted)`.
|
|
69
210
|
*
|
|
70
|
-
* They are bracketed onto the
|
|
211
|
+
* They are bracketed onto the text rather than written among the node's
|
|
71
212
|
* attributes for the same reason a gap is bracketed onto its placement — they
|
|
72
213
|
* modify that one thing, and the brackets make the scope visible instead of
|
|
73
|
-
* positional.
|
|
74
|
-
*
|
|
214
|
+
* positional. What is left at the top level is then about the node itself:
|
|
215
|
+
* `shape`, `icon`, `fill`, `border`, `gap`, `overlap`, `style`.
|
|
216
|
+
*
|
|
217
|
+
* In a style, which has no string for a bracket to hang off, the bracket hangs
|
|
218
|
+
* off the key instead: `style synced text: (color: muted)`.
|
|
219
|
+
*
|
|
220
|
+
* `at` and `align` are independent and neither implies the other. `at` is where
|
|
221
|
+
* the block of text sits in the node — one of the nine named positions — and
|
|
222
|
+
* `align` is how its lines range against each other once it is there.
|
|
75
223
|
*/
|
|
76
|
-
export const
|
|
224
|
+
export const TEXT_KEYS = ['color', 'size', 'wrap', 'align', 'at'];
|
|
77
225
|
/** The attributes a `diagram` statement understands. */
|
|
78
226
|
export const DIAGRAM_KEYS = ['background'];
|
|
227
|
+
/**
|
|
228
|
+
* The attributes whose value is a color rather than text. A color is written
|
|
229
|
+
* as the viewer will receive it and the renderer keeps no list of color words
|
|
230
|
+
* of its own, so there is nothing to check a value *against* — but quoting is
|
|
231
|
+
* the author saying "this is text", and an unquoted value cannot hold a space,
|
|
232
|
+
* so prose has to be quoted to get in at all. Refusing a quoted color is
|
|
233
|
+
* therefore the whole of what can be checked here, and it happens to be the
|
|
234
|
+
* mistake people actually make: `subtext: "medium-fine"` reads as the text
|
|
235
|
+
* that goes underneath, and was accepted and dropped in silence.
|
|
236
|
+
*/
|
|
237
|
+
export const COLOR_KEYS = [
|
|
238
|
+
'fill',
|
|
239
|
+
'border',
|
|
240
|
+
'line',
|
|
241
|
+
'background',
|
|
242
|
+
];
|
|
243
|
+
/**
|
|
244
|
+
* A color attribute names the *part* it colors, and a part exists only on the
|
|
245
|
+
* kinds that have one. A node has a border and text; a note and a glyph body are
|
|
246
|
+
* text and nothing else; an edge is a line and its text.
|
|
247
|
+
*
|
|
248
|
+
* This table is what makes the words checkable. `border:` on a note is refused
|
|
249
|
+
* by name rather than ignored — the same rule as an unknown `diagram` key, and
|
|
250
|
+
* for the same reason: an attribute that silently does nothing looks like the
|
|
251
|
+
* tool being broken.
|
|
252
|
+
*
|
|
253
|
+
* Each entry is written the way the author would write it, since the text's is
|
|
254
|
+
* a bracket rather than a bare key, and this list is only ever quoted back.
|
|
255
|
+
*
|
|
256
|
+
* A style spanning kinds writes one key per kind — `border: #d2904e line:
|
|
257
|
+
* #d2904e` — since a style contributes a part only to the kinds that have it.
|
|
258
|
+
* That is what replaced `stroke:`, which named no part and so could never be
|
|
259
|
+
* wrong, and which is why a node's text had no word of its own until now.
|
|
260
|
+
*/
|
|
261
|
+
export const COLOR_PARTS = {
|
|
262
|
+
shape: ['fill:', 'border:', 'text: (color: …)'],
|
|
263
|
+
icon: ['text: (color: …)'],
|
|
264
|
+
none: ['text: (color: …)'],
|
|
265
|
+
edge: ['line:', 'text: (color: …)'],
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Every attribute each kind understands. An attribute a kind has no use for is
|
|
269
|
+
* refused by name rather than dropped, the same rule as an unknown `diagram`
|
|
270
|
+
* key, a `PLACEMENT_KEYS` modifier or a color part — and for the same reason,
|
|
271
|
+
* which the color parts only closed one level down: a key that silently does
|
|
272
|
+
* nothing looks like the tool being broken rather than like a typo.
|
|
273
|
+
*
|
|
274
|
+
* The color entries repeat `COLOR_PARTS` and must agree with it. They are
|
|
275
|
+
* written out rather than spliced in because this table is the answer to "what
|
|
276
|
+
* may I write here", and a reader of it should not have to assemble the list
|
|
277
|
+
* from two places.
|
|
278
|
+
*
|
|
279
|
+
* The exclusions are the whole of what this table decides, and each is a place
|
|
280
|
+
* the old silence hid something:
|
|
281
|
+
*
|
|
282
|
+
* - A node drawn as a picture, or with no body at all, takes no `fill:` or
|
|
283
|
+
* `border:`. There is no outline for either to reach.
|
|
284
|
+
* - Neither of those takes `contents:` either, which says how a node's children
|
|
285
|
+
* sit, and neither may have any.
|
|
286
|
+
* - `shape:` and `icon:` each name the body, so each appears only on the kind it
|
|
287
|
+
* makes. `shape:` is on `none` as well, because `shape: none` is how that kind
|
|
288
|
+
* is written in the first place.
|
|
289
|
+
* - An edge takes no `gap:` or `overlap:`. Those are about where a box sits, and
|
|
290
|
+
* an edge is not placed — it joins two things that are.
|
|
291
|
+
*/
|
|
292
|
+
export const ATTR_KEYS = {
|
|
293
|
+
shape: ['style', 'gap', 'overlap', 'contents', 'badge', 'shape', 'fill', 'border', 'text', 'url'],
|
|
294
|
+
icon: ['style', 'gap', 'overlap', 'icon', 'text', 'url'],
|
|
295
|
+
none: ['style', 'gap', 'overlap', 'shape', 'text', 'url'],
|
|
296
|
+
edge: ['style', 'from', 'to', 'line', 'text', 'url'],
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Every word that is an attribute *somewhere*, which is what separates a
|
|
300
|
+
* misspelling from a key written on the wrong kind of thing. The two deserve
|
|
301
|
+
* different errors: one has no remedy but the spelling, the other has a real
|
|
302
|
+
* meaning somewhere else in the file.
|
|
303
|
+
*
|
|
304
|
+
* `DIAGRAM_KEYS` is in here so that `background:` on a node is understood to be
|
|
305
|
+
* a real word in the wrong place — that mistake wants to be pointed at `fill:`,
|
|
306
|
+
* not told the word does not exist.
|
|
307
|
+
*/
|
|
308
|
+
export const ALL_ATTR_KEYS = [
|
|
309
|
+
...new Set([...Object.values(ATTR_KEYS).flat(), ...DIAGRAM_KEYS]),
|
|
310
|
+
];
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/** Spacing and text sizes shared by the resolver and the renderer, so the two cannot drift. */
|
|
2
|
-
import type { Attrs } from './ast.js';
|
|
2
|
+
import type { Attrs, Position } from './ast.js';
|
|
3
3
|
import type { Measurer } from './measure.js';
|
|
4
|
+
import { type Line } from './text.js';
|
|
4
5
|
/** Inside a box, between its border and its contents. */
|
|
5
6
|
export declare const PAD = 14;
|
|
6
7
|
/** Between stacked children of one container. */
|
|
7
8
|
export declare const CHILD_GAP = 10;
|
|
8
|
-
/** Between a container's own
|
|
9
|
+
/** Between a container's own text and its first child. */
|
|
9
10
|
export declare const HEADER_GAP = 10;
|
|
10
11
|
/**
|
|
11
12
|
* How far each deck copy is offset behind the front face. It has to clear a
|
|
12
|
-
* whole line of text plus the padding above it, or a copy's
|
|
13
|
+
* whole line of text plus the padding above it, or a copy's text is drawn and
|
|
13
14
|
* then immediately covered by the copy in front of it.
|
|
14
15
|
*/
|
|
15
16
|
export declare const DECK_STEP = 34;
|
|
@@ -28,7 +29,7 @@ export declare const GAPS: Record<string, number>;
|
|
|
28
29
|
*/
|
|
29
30
|
export declare const SEPARATION_GAP: number;
|
|
30
31
|
/**
|
|
31
|
-
* Between two
|
|
32
|
+
* Between two edges meeting the same side of the same box. An author names a
|
|
32
33
|
* side, never a point on it, so this is the tool keeping two attachments apart
|
|
33
34
|
* rather than a distance anyone asked for — small, like `SEPARATION_GAP`, and
|
|
34
35
|
* squeezed further if the side is too short to hold the whole group.
|
|
@@ -36,7 +37,7 @@ export declare const SEPARATION_GAP: number;
|
|
|
36
37
|
export declare const ATTACH_STEP = 16;
|
|
37
38
|
/** Kept clear at each end of a side, so an attachment never sits on a corner. */
|
|
38
39
|
export declare const ATTACH_MARGIN = 10;
|
|
39
|
-
/** How wide
|
|
40
|
+
/** How wide an edge's line is drawn. */
|
|
40
41
|
export declare const LINE_WIDTH = 1.6;
|
|
41
42
|
/**
|
|
42
43
|
* The arrowhead's length, in the `markerUnits="strokeWidth"` the marker is
|
|
@@ -50,44 +51,50 @@ export declare const ARROW_MARKER_WIDTH = 7;
|
|
|
50
51
|
*/
|
|
51
52
|
export declare const ARROW_LENGTH: number;
|
|
52
53
|
/**
|
|
53
|
-
* Line left showing between
|
|
54
|
+
* Line left showing between an edge's text and the box at that end of the
|
|
54
55
|
* corridor it crosses.
|
|
55
56
|
*
|
|
56
57
|
* Unlike `SEPARATION_GAP` and `ATTACH_MARGIN` this is not "small on purpose".
|
|
57
58
|
* Those two keep two things from touching, and the least distance that reads as
|
|
58
|
-
* "not touching" is the right one. This one has something to show: a
|
|
59
|
+
* "not touching" is the right one. This one has something to show: a text sits
|
|
59
60
|
* in a knockout that erases the line behind it, so whatever is left either side
|
|
60
|
-
* is the entire evidence that the
|
|
61
|
+
* is the entire evidence that the text belongs to an edge at all. At ten pixels
|
|
61
62
|
* it did not read as a line — the seed diagram in the playground drew as a word
|
|
62
63
|
* with a dash beside it — so it is the length of a run of line, not a margin.
|
|
63
64
|
*
|
|
64
65
|
* Say `gap:` if you want the corridor wider than its contents.
|
|
65
66
|
*/
|
|
66
|
-
export declare const
|
|
67
|
+
export declare const TEXT_CLEARANCE = 20;
|
|
67
68
|
/**
|
|
68
|
-
* How much room
|
|
69
|
+
* How much room an edge's text takes along one axis.
|
|
69
70
|
*
|
|
70
|
-
* The knockout rectangle drawn behind a
|
|
71
|
+
* The knockout rectangle drawn behind a text is the text plus five either side,
|
|
71
72
|
* so that rectangle, not the glyphs, is what must not overlap anything.
|
|
72
73
|
*
|
|
73
|
-
* Shared by the resolver, which widens a corridor to hold a
|
|
74
|
+
* Shared by the resolver, which widens a corridor to hold a text, and the
|
|
74
75
|
* renderer, which spaces the lanes of a channel by it, so the two cannot
|
|
75
|
-
* disagree about how much room a
|
|
76
|
-
* it and both are right: the resolver measures *along* the run, so
|
|
77
|
-
*
|
|
78
|
-
* the channel, so
|
|
76
|
+
* disagree about how much room a text needs. The two ask different questions of
|
|
77
|
+
* it and both are right: the resolver measures *along* the run, so an edge
|
|
78
|
+
* traveling horizontally needs the text's width; the renderer measures *across*
|
|
79
|
+
* the channel, so an edge traveling horizontally down one needs its height.
|
|
79
80
|
*/
|
|
80
|
-
export declare function
|
|
81
|
+
export declare function textExtent(lines: Line[], textAttrs: Attrs, axis: 'x' | 'y', measurer: Measurer, fontSize: number, line: number): number;
|
|
81
82
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
83
|
+
* The widest of these lines, which is how wide the block of them is. Shared by
|
|
84
|
+
* the resolver, which sizes a node to hold its text, and the renderer, which
|
|
85
|
+
* places that block in the room the node gave it.
|
|
86
|
+
*/
|
|
87
|
+
export declare function widestLine(lines: Line[], measurer: Measurer, fontSize: number): number;
|
|
88
|
+
/**
|
|
89
|
+
* An icon is two lines of the text tall, and that ratio is what makes it a
|
|
90
|
+
* *text-sized* ornament rather than a picture with a size of its own. It is
|
|
84
91
|
* read off the reference, where the title lines run 25 pixels baseline to
|
|
85
92
|
* baseline and the drive and machine glyphs are close to 50 tall. Deriving it
|
|
86
93
|
* from the text also means an icon on a `size: small` node shrinks with it,
|
|
87
94
|
* which is what anyone would expect and what a fixed pixel count would not do.
|
|
88
95
|
*/
|
|
89
96
|
export declare const ICON_LINES = 2;
|
|
90
|
-
/** Between the
|
|
97
|
+
/** Between the text column and the icon column beside it. */
|
|
91
98
|
export declare const ICON_GAP = 10;
|
|
92
99
|
export declare const DEFAULT_FONT_SIZE = 14;
|
|
93
100
|
/**
|
|
@@ -97,7 +104,7 @@ export declare const DEFAULT_FONT_SIZE = 14;
|
|
|
97
104
|
* it says nothing about why one piece of text is smaller than another.
|
|
98
105
|
*
|
|
99
106
|
* `small` is sampled rather than chosen. In
|
|
100
|
-
* `examples/reference/arch.png` the box and container
|
|
107
|
+
* `examples/reference/arch.png` the box and container texts run 25
|
|
101
108
|
* pixels baseline to baseline and every annotation runs 21, which is this
|
|
102
109
|
* ratio; `./dev.sh textrows` is how that was read off. `large` is the same step
|
|
103
110
|
* taken the other way, so the scale is symmetric about the document size.
|
|
@@ -108,35 +115,46 @@ export declare const TEXT_SIZES: Record<string, number>;
|
|
|
108
115
|
* the room, and the renderer, which fills it, so the two cannot disagree about
|
|
109
116
|
* how much room there is.
|
|
110
117
|
*/
|
|
111
|
-
export declare function fontSizeFor(kind: string,
|
|
118
|
+
export declare function fontSizeFor(kind: string, textAttrs: Attrs, fontSize: number, line: number): number;
|
|
112
119
|
export declare const DEFAULT_MARGIN = 40;
|
|
113
120
|
/**
|
|
114
|
-
* Where a
|
|
115
|
-
*
|
|
116
|
-
*
|
|
121
|
+
* Where a node's own text sits, and how its lines range once it is there.
|
|
122
|
+
*
|
|
123
|
+
* `at` names one of the nine positions of the box — the same closed set the
|
|
124
|
+
* overlay placement uses, and for the same reason: a box has nine points anyone
|
|
125
|
+
* can name without measuring, and a diagram written in them still moves
|
|
126
|
+
* correctly when a box moves. It replaced `top | bottom`, which was a slot: two
|
|
127
|
+
* of the nine handed out because those were the two somebody needed.
|
|
117
128
|
*
|
|
118
|
-
* The two are
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* read a position as though it were a meaning; a folded corner means "artifact
|
|
123
|
-
* rather than process" and a reader decodes it, while "lower down" means only
|
|
124
|
-
* lower down.
|
|
129
|
+
* The two halves are read independently, exactly as `overlaidAt` reads an
|
|
130
|
+
* overlay's. The vertical half says which end of the box the text's band sits
|
|
131
|
+
* at, and the contents of a container take the other end. The horizontal half
|
|
132
|
+
* says where the block of text sits across the room it is given.
|
|
125
133
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
134
|
+
* `align` is a different question and stays one: a text of more than one line
|
|
135
|
+
* has lines of unequal length wherever the block sits, and how those range
|
|
136
|
+
* against each other is not where the block is. A container's lines default to
|
|
137
|
+
* ranged left and a leaf's to centered, which is why the fallback is a
|
|
138
|
+
* parameter.
|
|
128
139
|
*/
|
|
129
|
-
export
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
at
|
|
134
|
-
|
|
140
|
+
export interface TextStyle {
|
|
141
|
+
at: Position;
|
|
142
|
+
/** Which end of the box the text's band sits at, from `at`'s vertical half. */
|
|
143
|
+
end: 'top' | 'center' | 'bottom';
|
|
144
|
+
/** Where the block sits across the room, from `at`'s horizontal half. */
|
|
145
|
+
side: 'left' | 'center' | 'right';
|
|
146
|
+
/** How the lines range against each other, in the renderer's own vocabulary. */
|
|
135
147
|
align: 'start' | 'middle' | 'end';
|
|
136
148
|
}
|
|
137
149
|
/**
|
|
138
|
-
* Read a
|
|
150
|
+
* Read a text's bracketed modifiers. Shared by the resolver, which offsets the
|
|
139
151
|
* contents away from the band, and the renderer, which draws into it, so the two
|
|
140
152
|
* cannot disagree about which end the band is at.
|
|
141
153
|
*/
|
|
142
|
-
export declare function
|
|
154
|
+
export declare function textStyleFor(attrs: Attrs, line: number, fallbackAlign?: 'start' | 'middle', fallbackAt?: Position): TextStyle;
|
|
155
|
+
/**
|
|
156
|
+
* Where a leaf's text or badge starts vertically, given which end it sits at.
|
|
157
|
+
* Shared, because the resolver works out the text's box and the renderer draws
|
|
158
|
+
* it, and the two must not be able to disagree.
|
|
159
|
+
*/
|
|
160
|
+
export declare function leafTop(end: 'top' | 'center' | 'bottom', y: number, height: number, own: number): number;
|