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/dist/ast.js CHANGED
@@ -16,41 +16,182 @@ export function isDirection(word) {
16
16
  return DIRECTIONS.includes(word);
17
17
  }
18
18
  /**
19
- * Which edge of the target a `level with` shares. `centre` is the plain form;
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 EDGES = ['centre', 'top', 'bottom', 'left', 'right'];
23
- /** An edge belongs to one axis, so an alignment never has to say which. */
24
- export const EDGE_AXIS = {
25
- centre: 'y',
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 unrecognised, for the reason `DIAGRAM_KEYS` are: a modifier that
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 edge = placement.edge === 'centre' ? '' : `${placement.edge} `;
42
- return `${edge}level with ${targets}`;
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", "borg and bare", "borg, bare and media" — as the author would write them. */
185
+ /** "borg", "hub text", "borg and bare" — as the author would write them. */
50
186
  export function listTargets(targets) {
51
- if (targets.length <= 1)
52
- return targets[0] ?? '';
53
- return `${targets.slice(0, -1).join(', ')} and ${targets[targets.length - 1]}`;
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 label's brackets may say: `"Docker" (at: bottom, align: centre)`.
209
+ * What a text's brackets may say: `"Docker" (at: bottom-center, color: muted)`.
69
210
  *
70
- * They are bracketed onto the label rather than written among the node's
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. `at` and `align` are independent: neither implies the other, and a
74
- * label at the bottom is an ordinary label that happens to be at the bottom.
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 LABEL_KEYS = ['at', 'align'];
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
+ ];
@@ -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 label and its first child. */
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 label is drawn and
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 links meeting the same side of the same box. An author names a
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 a link's line is drawn. */
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 a link's label and the box at that end of the
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 label sits
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 label belongs to a link at all. At ten pixels
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 LABEL_CLEARANCE = 20;
67
+ export declare const TEXT_CLEARANCE = 20;
67
68
  /**
68
- * How much room a link's label takes along one axis.
69
+ * How much room an edge's text takes along one axis.
69
70
  *
70
- * The knockout rectangle drawn behind a label is the text plus five either side,
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 label, and the
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 label needs. The two ask different questions of
76
- * it and both are right: the resolver measures *along* the run, so a link
77
- * travelling horizontally needs the label's width; the renderer measures *across*
78
- * the channel, so a link travelling horizontally down one needs its height.
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 labelExtent(label: string, appearance: Attrs, axis: 'x' | 'y', measurer: Measurer, fontSize: number, line: number): number;
81
+ export declare function textExtent(lines: Line[], textAttrs: Attrs, axis: 'x' | 'y', measurer: Measurer, fontSize: number, line: number): number;
81
82
  /**
82
- * An icon is two lines of the label tall, and that ratio is what makes it a
83
- * *label-sized* ornament rather than a picture with a size of its own. It is
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 label column and the icon column beside it. */
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 labels run 25
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, appearance: Attrs, fontSize: number, line: number): number;
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 container's own label sits. Every container reserves a band for its
115
- * label and its icon; `at` says which end of the box that band is, and the
116
- * contents take what is left. `align` says how the text sits across it.
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 independent and neither implies the other. A label at the bottom
119
- * is an ordinary label that happens to be at the bottom — there is no kind of
120
- * label being named here and no second thing quietly coming along with the
121
- * first. An earlier version bundled them as `label: heading | caption`, which
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
- * A leaf has no band — its label is centred in the box — so neither says
127
- * anything about one.
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 declare const LABEL_ENDS: readonly ["top", "bottom"];
130
- export type LabelEnd = (typeof LABEL_ENDS)[number];
131
- export interface LabelStyle {
132
- /** Which end of the box the band sits at. */
133
- at: LabelEnd;
134
- /** How the text sits in the band, in the renderer's own vocabulary. */
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 label's bracketed modifiers. Shared by the resolver, which offsets the
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 labelStyleFor(label: Attrs, line: number): LabelStyle;
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;