@formicoidea/labre-framework-uml 0.42.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/LICENSE +384 -0
- package/dist/actions.d.ts +430 -0
- package/dist/actions.js +724 -0
- package/dist/background.d.ts +153 -0
- package/dist/background.js +557 -0
- package/dist/board-hit.d.ts +137 -0
- package/dist/board-hit.js +191 -0
- package/dist/commands-manifest.d.ts +19 -0
- package/dist/commands-manifest.js +571 -0
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +983 -0
- package/dist/component.d.ts +521 -0
- package/dist/component.js +708 -0
- package/dist/consts.d.ts +365 -0
- package/dist/consts.js +463 -0
- package/dist/descriptor.d.ts +12 -0
- package/dist/descriptor.js +10 -0
- package/dist/drawio-decode.d.ts +32 -0
- package/dist/drawio-decode.js +96 -0
- package/dist/drawio-import.d.ts +133 -0
- package/dist/drawio-import.js +885 -0
- package/dist/edge-morph.d.ts +154 -0
- package/dist/edge-morph.js +153 -0
- package/dist/edge-styles.d.ts +78 -0
- package/dist/edge-styles.js +210 -0
- package/dist/effects.d.ts +9 -0
- package/dist/effects.js +6 -0
- package/dist/element-renderer.d.ts +64 -0
- package/dist/element-renderer.js +114 -0
- package/dist/element-view.d.ts +234 -0
- package/dist/element-view.js +430 -0
- package/dist/export.d.ts +61 -0
- package/dist/export.js +41 -0
- package/dist/filename.d.ts +17 -0
- package/dist/filename.js +26 -0
- package/dist/frame-tag.d.ts +70 -0
- package/dist/frame-tag.js +78 -0
- package/dist/grammar.d.ts +695 -0
- package/dist/grammar.js +1436 -0
- package/dist/import.d.ts +306 -0
- package/dist/import.js +1270 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +67 -0
- package/dist/interchange.d.ts +125 -0
- package/dist/interchange.js +372 -0
- package/dist/keywords.d.ts +204 -0
- package/dist/keywords.js +361 -0
- package/dist/kinds.d.ts +73 -0
- package/dist/kinds.js +91 -0
- package/dist/legend.d.ts +30 -0
- package/dist/legend.js +255 -0
- package/dist/model.d.ts +862 -0
- package/dist/model.js +1316 -0
- package/dist/morph.d.ts +178 -0
- package/dist/morph.js +496 -0
- package/dist/node/act-glyphs.d.ts +30 -0
- package/dist/node/act-glyphs.js +142 -0
- package/dist/node/compartment-watcher.d.ts +107 -0
- package/dist/node/compartment-watcher.js +163 -0
- package/dist/node/fit.d.ts +85 -0
- package/dist/node/fit.js +129 -0
- package/dist/node/node-renderer.d.ts +49 -0
- package/dist/node/node-renderer.js +611 -0
- package/dist/node/node-view.d.ts +51 -0
- package/dist/node/node-view.js +89 -0
- package/dist/node/paint.d.ts +84 -0
- package/dist/node/paint.js +143 -0
- package/dist/node/sd-glyphs.d.ts +36 -0
- package/dist/node/sd-glyphs.js +79 -0
- package/dist/node/stm-glyphs.d.ts +22 -0
- package/dist/node/stm-glyphs.js +111 -0
- package/dist/node/tier-metrics.d.ts +38 -0
- package/dist/node/tier-metrics.js +45 -0
- package/dist/operators.d.ts +45 -0
- package/dist/operators.js +75 -0
- package/dist/plantuml-import.d.ts +113 -0
- package/dist/plantuml-import.js +1723 -0
- package/dist/plantuml.d.ts +94 -0
- package/dist/plantuml.js +985 -0
- package/dist/presets.d.ts +149 -0
- package/dist/presets.js +302 -0
- package/dist/profiles.d.ts +2 -0
- package/dist/profiles.js +358 -0
- package/dist/reading.d.ts +160 -0
- package/dist/reading.js +648 -0
- package/dist/roles.d.ts +257 -0
- package/dist/roles.js +997 -0
- package/dist/rules.d.ts +267 -0
- package/dist/rules.js +3090 -0
- package/dist/templates.d.ts +2 -0
- package/dist/templates.js +197 -0
- package/dist/toolbar/config.d.ts +228 -0
- package/dist/toolbar/config.js +833 -0
- package/dist/toolbar/icons.d.ts +440 -0
- package/dist/toolbar/icons.js +711 -0
- package/dist/toolbar/senior-tool.d.ts +11 -0
- package/dist/toolbar/senior-tool.js +21 -0
- package/dist/toolbar/uml-menu.d.ts +17 -0
- package/dist/toolbar/uml-menu.js +20 -0
- package/dist/toolbar/uml-senior-button.d.ts +18 -0
- package/dist/toolbar/uml-senior-button.js +22 -0
- package/dist/translations.d.ts +27 -0
- package/dist/translations.js +117 -0
- package/dist/view.d.ts +33 -0
- package/dist/view.js +236 -0
- package/dist/xmi-import.d.ts +174 -0
- package/dist/xmi-import.js +2268 -0
- package/dist/xmi.d.ts +61 -0
- package/dist/xmi.js +1860 -0
- package/dist/xml-reader.d.ts +157 -0
- package/dist/xml-reader.js +430 -0
- package/dist/xml.d.ts +72 -0
- package/dist/xml.js +96 -0
- package/package.json +38 -0
package/dist/consts.js
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
// The lifeline's column height is a MODEL constant (the element's own bound
|
|
2
|
+
// override reads it), and it is also this file's footprint for the kind — so it
|
|
3
|
+
// is imported as well as re-exported below.
|
|
4
|
+
import { UML_LIFELINE_SPINE } from '@formicoidea/labre-core/model';
|
|
5
|
+
import { NOTATION_NEUTRALS } from '@formicoidea/labre-core/shared/consts';
|
|
6
|
+
/**
|
|
7
|
+
* Visual constants for the UML pack.
|
|
8
|
+
*
|
|
9
|
+
* UML is the opposite of C4 on the one question that matters here: C4's stencil
|
|
10
|
+
* IS a colour code — four blues for four levels — whereas UML 2.5.1 defines no
|
|
11
|
+
* palette at all. Every figure in the specification is black lines on white
|
|
12
|
+
* paper, and what distinguishes a class from an interface from an enumeration
|
|
13
|
+
* is the KEYWORD written above the name, not a hue. So this file states no
|
|
14
|
+
* colours of its own: it names four neutrals off the shared notation scale
|
|
15
|
+
* (R33) and leaves the meaning where the spec puts it, in the shape and the
|
|
16
|
+
* words.
|
|
17
|
+
*
|
|
18
|
+
* Every value here is a creation-time DEFAULT. A UML node is a native shape, so
|
|
19
|
+
* each of these becomes an ordinary editable property the moment the element is
|
|
20
|
+
* placed — exactly as in BPMN and C4.
|
|
21
|
+
*/
|
|
22
|
+
/* ── The four neutrals ──────────────────────────────────────────────────── */
|
|
23
|
+
/**
|
|
24
|
+
* Artefact ink: every node outline, every compartment separator, every glyph
|
|
25
|
+
* (the package tab, the note's fold, the actor's stick figure) and the text
|
|
26
|
+
* written inside an artefact.
|
|
27
|
+
*
|
|
28
|
+
* One ink for all of it, because UML draws one: the notation's contrast is
|
|
29
|
+
* between the LINES and the paper, and a second artefact grey would read as a
|
|
30
|
+
* distinction the spec never makes.
|
|
31
|
+
*/
|
|
32
|
+
export const UML_INK = NOTATION_NEUTRALS.ink;
|
|
33
|
+
/**
|
|
34
|
+
* Background structure: the diagram frame's border, its heading tag and the
|
|
35
|
+
* subject's outline and name — the furniture the diagram is drawn ON, as
|
|
36
|
+
* opposed to the diagram itself.
|
|
37
|
+
*/
|
|
38
|
+
export const UML_FRAME_INK = NOTATION_NEUTRALS.frameInk;
|
|
39
|
+
/** The paper: the diagram frame's card fill, and the fill of a boxed node. */
|
|
40
|
+
export const UML_CARD = NOTATION_NEUTRALS.cardFill;
|
|
41
|
+
/**
|
|
42
|
+
* Secondary strokes — the divider under the heading band. Lighter than
|
|
43
|
+
* {@link UML_FRAME_INK} so that the band reads as a strip of the same sheet
|
|
44
|
+
* rather than as a second frame drawn inside the first.
|
|
45
|
+
*/
|
|
46
|
+
export const UML_DIVIDER = NOTATION_NEUTRALS.divider;
|
|
47
|
+
/* ── Weights ───────────────────────────────────────────────────────────── */
|
|
48
|
+
/**
|
|
49
|
+
* Every relationship is drawn at the same weight — the notation distinguishes
|
|
50
|
+
* them by their ENDS (hollow triangle, hollow or filled diamond, open arrow)
|
|
51
|
+
* and by solid versus dashed, never by thickness. See the edge style table in
|
|
52
|
+
* `actions.ts`.
|
|
53
|
+
*/
|
|
54
|
+
export const UML_EDGE_WIDTH = 2;
|
|
55
|
+
/** Every node outline, at the same weight as every line: UML draws one pen. */
|
|
56
|
+
export const UML_NODE_STROKE_WIDTH = 2;
|
|
57
|
+
/* ── Type ──────────────────────────────────────────────────────────────── */
|
|
58
|
+
/** The same family every other framework in the library writes in. */
|
|
59
|
+
export const UML_FONT_FAMILY = 'Inter, sans-serif';
|
|
60
|
+
/**
|
|
61
|
+
* The name compartment, and the two tiers under it.
|
|
62
|
+
*
|
|
63
|
+
* A two-step ladder rather than C4's three, because a classifier has two kinds
|
|
64
|
+
* of words in it: its NAME (with its keyword line, §11.4.4) and its FEATURES —
|
|
65
|
+
* attributes and operations, which are the same sort of statement at the same
|
|
66
|
+
* size. Both are creation-time defaults an author can climb off.
|
|
67
|
+
*/
|
|
68
|
+
export const UML_NAME_FONT_SIZE = 16;
|
|
69
|
+
export const UML_BODY_FONT_SIZE = 13;
|
|
70
|
+
/* ── The diagram frame ─────────────────────────────────────────────────── */
|
|
71
|
+
/**
|
|
72
|
+
* The bite taken out of the heading tag's top-right corner, in model units.
|
|
73
|
+
*
|
|
74
|
+
* UML 2.5.1 Annex A draws the frame heading in a rectangle with one corner cut
|
|
75
|
+
* off — the pentagon that tells a reader, at a glance and before reading a
|
|
76
|
+
* word, that they are looking at a diagram frame and not at a box in the
|
|
77
|
+
* drawing. 12 units against a 44-unit band is the proportion the Annex's own
|
|
78
|
+
* figures show.
|
|
79
|
+
*/
|
|
80
|
+
export const UML_FRAME_TAG_CUT = 12;
|
|
81
|
+
/**
|
|
82
|
+
* How much shorter the tag is than the band it is drawn in — the air under it,
|
|
83
|
+
* which is what makes it read as a label PINNED to the frame rather than as a
|
|
84
|
+
* second rectangle sharing its top edge.
|
|
85
|
+
*/
|
|
86
|
+
export const UML_FRAME_TAG_FOOT = 8;
|
|
87
|
+
/** The heading's own size, and the frame's line. */
|
|
88
|
+
export const UML_FRAME_HEADING_FONT_SIZE = 20;
|
|
89
|
+
export const UML_FRAME_BORDER_WIDTH = 1.5;
|
|
90
|
+
/**
|
|
91
|
+
* The frame's other three margins — the plot's inset from its own border.
|
|
92
|
+
*
|
|
93
|
+
* Also the tag's horizontal PADDING: the heading is written at the plot's left
|
|
94
|
+
* edge, so a tag padded by the same number is a tag with its words centred in
|
|
95
|
+
* it, and the two cannot drift because there is one number.
|
|
96
|
+
*/
|
|
97
|
+
export const UML_DIAGRAM_MARGIN = 24;
|
|
98
|
+
/**
|
|
99
|
+
* The band the heading tag is written in — the frame's TOP MARGIN, owned by
|
|
100
|
+
* `@labre/affine-model` because the frame's hit test reads it too (a diagram is
|
|
101
|
+
* clickable by its heading band as well as by its border).
|
|
102
|
+
*
|
|
103
|
+
* Re-exported here so this file stays the one place a reader looks for the
|
|
104
|
+
* pack's metrics, exactly as `consts.ts` in the C4 pack re-exports
|
|
105
|
+
* `C4_BOARD_TITLE_BAND_HEIGHT`.
|
|
106
|
+
*/
|
|
107
|
+
export { UML_FRAME_BAND_HEIGHT } from '@formicoidea/labre-core/model';
|
|
108
|
+
/** The size a fresh diagram frame is created at — a Context Map sheet's own. */
|
|
109
|
+
export const UML_DIAGRAM_BOX = { w: 1400, h: 900 };
|
|
110
|
+
/**
|
|
111
|
+
* The size a fresh subject is created at (§18.1.4) — a C4 boundary's own
|
|
112
|
+
* footprint, because it is the same kind of object: a rectangle drawn round
|
|
113
|
+
* part of a diagram that is already there.
|
|
114
|
+
*/
|
|
115
|
+
export const UML_SUBJECT_BOX = { w: 520, h: 360 };
|
|
116
|
+
/**
|
|
117
|
+
* The subject's inset, equal on all four sides: unlike a diagram frame it has
|
|
118
|
+
* no furniture to make room for — §18.1.4 writes its name INSIDE the top-left
|
|
119
|
+
* corner, over the drawing, exactly as a C4 boundary writes its own.
|
|
120
|
+
*/
|
|
121
|
+
export const UML_SUBJECT_MARGIN = 12;
|
|
122
|
+
/** The subject's outline: solid and thin — §18.1.4 draws no dash and no tint. */
|
|
123
|
+
export const UML_SUBJECT_BORDER_WIDTH = 1.5;
|
|
124
|
+
/* ── Node footprints ───────────────────────────────────────────────────── */
|
|
125
|
+
/**
|
|
126
|
+
* Default node sizes (model units) per kind. TOTAL over {@link UmlNodeKind} by
|
|
127
|
+
* its type, so a kind added to the model cannot land without a footprint.
|
|
128
|
+
*
|
|
129
|
+
* The four compartmented kinds share ONE box — a class, an interface, an
|
|
130
|
+
* enumeration and an object are the same rectangle with different words in it,
|
|
131
|
+
* and a row of them lining up without anybody arranging them is what makes a
|
|
132
|
+
* class diagram readable. 200 × 120 fits a name line plus three feature lines
|
|
133
|
+
* at the sizes above with the margins `component.ts` lays out.
|
|
134
|
+
*
|
|
135
|
+
* The other four are the shapes the notation draws rather than boxes:
|
|
136
|
+
*
|
|
137
|
+
* - `package` is taller by the height of its tab (§12.2.4);
|
|
138
|
+
* - `note` is smaller and squarer — it holds a sentence, not a signature
|
|
139
|
+
* list (Annex A);
|
|
140
|
+
* - `actor` is the stick figure's own aspect ratio, tall and narrow: squeezed
|
|
141
|
+
* into a wide box the head stops being a circle, which is the one thing
|
|
142
|
+
* about a UML actor everybody recognises (§18.1.4);
|
|
143
|
+
* - `use-case` is the ellipse, wide and shallow so a verb phrase fits across
|
|
144
|
+
* its widest chord rather than in its corners, which it has none of.
|
|
145
|
+
*
|
|
146
|
+
* ## The phase-2 footprints
|
|
147
|
+
*
|
|
148
|
+
* A `component` is the class box's own 200 × 120: §11.6.4 draws it as the same
|
|
149
|
+
* rectangle with an icon in the corner, and a component diagram lines its boxes
|
|
150
|
+
* up beside a class diagram's without anybody resizing anything.
|
|
151
|
+
*
|
|
152
|
+
* The rest are their pictures' own proportions:
|
|
153
|
+
*
|
|
154
|
+
* - `port` is a SMALL SQUARE (§11.3.4) — 16 × 16, the one footprint in the pack
|
|
155
|
+
* that is not big enough to write in, which is why its label is placed beside
|
|
156
|
+
* it rather than inside it (`component.ts`);
|
|
157
|
+
* - the two interface glyphs are 60 × 40, tall enough for a ball or a socket
|
|
158
|
+
* over the stub that carries it (§10.4.4) and no wider than the glyph needs —
|
|
159
|
+
* their names are written beside them too;
|
|
160
|
+
* - `artifact` is the note's 180 × 100: a file name over a line or two of what
|
|
161
|
+
* it holds, which is the same amount of writing (§19.3.4);
|
|
162
|
+
* - the three cubes share 220 × 140 (§19.4.4). Wider and taller than a class
|
|
163
|
+
* box because the 3-D depth eats into both: the FRONT FACE — the only part a
|
|
164
|
+
* name can be written in — is what has to end up the size of a box.
|
|
165
|
+
*/
|
|
166
|
+
export const UML_NODE_BOX = {
|
|
167
|
+
class: { w: 200, h: 120 },
|
|
168
|
+
interface: { w: 200, h: 120 },
|
|
169
|
+
enumeration: { w: 200, h: 120 },
|
|
170
|
+
object: { w: 200, h: 120 },
|
|
171
|
+
package: { w: 200, h: 130 },
|
|
172
|
+
note: { w: 180, h: 100 },
|
|
173
|
+
actor: { w: 80, h: 120 },
|
|
174
|
+
'use-case': { w: 200, h: 90 },
|
|
175
|
+
component: { w: 200, h: 120 },
|
|
176
|
+
port: { w: 16, h: 16 },
|
|
177
|
+
'provided-interface': { w: 60, h: 40 },
|
|
178
|
+
'required-interface': { w: 60, h: 40 },
|
|
179
|
+
artifact: { w: 180, h: 100 },
|
|
180
|
+
node: { w: 220, h: 140 },
|
|
181
|
+
device: { w: 220, h: 140 },
|
|
182
|
+
'execution-environment': { w: 220, h: 140 },
|
|
183
|
+
// ── The activity footprints (phase 2) ──────────────────────────────────
|
|
184
|
+
// An `action` is WIDER AND SHALLOWER than a class box: §15.3.4 writes a verb
|
|
185
|
+
// phrase in it — "Vérifier le stock", one line — where a classifier holds a
|
|
186
|
+
// signature list. 160 × 60 is the box that phrase fits across rather than
|
|
187
|
+
// down, and it is the module a whole activity diagram is measured in.
|
|
188
|
+
action: { w: 160, h: 60 },
|
|
189
|
+
// The control nodes are DOTS AND BARS, not boxes: every one of them is sized
|
|
190
|
+
// to its own picture, because none of them has words inside it.
|
|
191
|
+
//
|
|
192
|
+
// - `initial` is the filled disc, 24 across — small enough to read as a
|
|
193
|
+
// marker rather than as a node, big enough to aim at;
|
|
194
|
+
// - `activity-final` and `flow-final` are 28, a touch larger because both
|
|
195
|
+
// are a circle with something INSIDE it (a second disc, a cross) and a
|
|
196
|
+
// 24-unit ring would close up;
|
|
197
|
+
// - `decision` is the diamond, square by construction so it stays a diamond;
|
|
198
|
+
// - `fork` is the bar — 120 long and 10 thick, the one footprint in the pack
|
|
199
|
+
// that is deliberately degenerate in one dimension (§15.3.4 draws a line
|
|
200
|
+
// segment, and a bar as tall as it is wide would read as a box).
|
|
201
|
+
initial: { w: 24, h: 24 },
|
|
202
|
+
'activity-final': { w: 28, h: 28 },
|
|
203
|
+
'flow-final': { w: 28, h: 28 },
|
|
204
|
+
decision: { w: 48, h: 48 },
|
|
205
|
+
fork: { w: 120, h: 10 },
|
|
206
|
+
// §15.4.4: a plain rectangle carrying a value. Narrower than an action,
|
|
207
|
+
// because it holds a NOUN — `Commande [validée]` — and taller than nothing,
|
|
208
|
+
// because the optional `[state]` goes on a second line.
|
|
209
|
+
'object-node': { w: 140, h: 60 },
|
|
210
|
+
// §16.3.4: the two pentagons. The action's own 160 × 60, so a signal sent in
|
|
211
|
+
// the middle of a flow lines up with the actions either side of it without
|
|
212
|
+
// anybody resizing anything — the point of the shape is the silhouette, not
|
|
213
|
+
// the size.
|
|
214
|
+
'send-signal': { w: 160, h: 60 },
|
|
215
|
+
'accept-event': { w: 160, h: 60 },
|
|
216
|
+
// §16.10.4: the hourglass. TALL and narrow, which is what an hourglass is —
|
|
217
|
+
// and small, because its words are written beside it rather than in it
|
|
218
|
+
// (`component.ts`), there being no inside to write in.
|
|
219
|
+
'time-event': { w: 40, h: 56 },
|
|
220
|
+
// ── The state machine footprints (phase 2) ─────────────────────────────
|
|
221
|
+
// §14.2.4: a state is the biggest box of the two behaviour families, because
|
|
222
|
+
// it is the only one with a COMPARTMENT — a name over the internal activities
|
|
223
|
+
// (`entry /`, `do /`, `exit /`), which is three lines under a heading.
|
|
224
|
+
state: { w: 180, h: 90 },
|
|
225
|
+
// The bullseye, drawn exactly like `activity-final` and therefore exactly its
|
|
226
|
+
// size: two shapes a reader is meant to recognise as the same picture must
|
|
227
|
+
// not arrive at different sizes.
|
|
228
|
+
'final-state': { w: 28, h: 28 },
|
|
229
|
+
// The pseudostates, every one of them a small mark:
|
|
230
|
+
//
|
|
231
|
+
// - `choice` is the diamond again, at 40 rather than the decision's 48 —
|
|
232
|
+
// §14.2.4 draws it smaller, and the difference is what keeps an activity's
|
|
233
|
+
// decision and a machine's choice from being mistaken for one another;
|
|
234
|
+
// - `junction` is the smallest mark in the pack at 16, a filled dot that
|
|
235
|
+
// merely joins lines;
|
|
236
|
+
// - the two histories are 28, the circle that has to hold an `H` or an `H*`;
|
|
237
|
+
// - `entry-point` and `exit-point` are 20 — a hollow circle and a crossed
|
|
238
|
+
// one, drawn ON the border of a composite state, so they are sized like
|
|
239
|
+
// the port they behave like (§11.3.4's 16 plus room for the cross);
|
|
240
|
+
// - `terminate` is the bare X, at the finals' own 28 so a machine's three
|
|
241
|
+
// end marks are one size.
|
|
242
|
+
choice: { w: 40, h: 40 },
|
|
243
|
+
junction: { w: 16, h: 16 },
|
|
244
|
+
'shallow-history': { w: 28, h: 28 },
|
|
245
|
+
'deep-history': { w: 28, h: 28 },
|
|
246
|
+
'entry-point': { w: 20, h: 20 },
|
|
247
|
+
'exit-point': { w: 20, h: 20 },
|
|
248
|
+
terminate: { w: 28, h: 28 },
|
|
249
|
+
// ── The interaction footprints (phase 3) ───────────────────────────────
|
|
250
|
+
// §17.3.4: a lifeline is a NARROW TALL COLUMN — the element IS the spine, and
|
|
251
|
+
// the head is painted over its top by the glyph layer (see
|
|
252
|
+
// {@link UML_LIFELINE_HEAD}). 16 wide so a message's native perimeter anchors
|
|
253
|
+
// land 8 units either side of the dashed line rather than 80, which is the
|
|
254
|
+
// whole reason the element is not head-shaped; 600 tall because that is a
|
|
255
|
+
// sheet's worth of conversation at the 40-unit rhythm messages are drawn on.
|
|
256
|
+
//
|
|
257
|
+
// The one footprint in the pack that is SMALLER than its own picture, and the
|
|
258
|
+
// model says so in as many words: `UmlNodeElementModel.elementBound` and
|
|
259
|
+
// `.includesPoint` both widen to the head.
|
|
260
|
+
lifeline: { w: 16, h: UML_LIFELINE_SPINE },
|
|
261
|
+
// §17.2.4: the ExecutionSpecification, a thin bar sat ON the spine. 12 wide —
|
|
262
|
+
// a touch narrower than the 16-unit column, so a bar dropped on a lifeline
|
|
263
|
+
// reads as sitting on the line rather than as replacing it. 80 tall is two
|
|
264
|
+
// messages' worth of "busy", which is what an author drags longer or shorter.
|
|
265
|
+
execution: { w: 12, h: 80 },
|
|
266
|
+
// §17.2.4: the destruction X. A SQUARE, like every other bare cross in the
|
|
267
|
+
// pack (`terminate`, `flow-final`), at the size a mark drawn over a 16-unit
|
|
268
|
+
// column has to be to be seen against it.
|
|
269
|
+
destruction: { w: 24, h: 24 },
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* How far a PORT's centre may sit from its component's outline before
|
|
273
|
+
* `uml.port-on-border` speaks — the tolerance of the `border-proximity` rule
|
|
274
|
+
* (ADR 0024).
|
|
275
|
+
*
|
|
276
|
+
* The glyph's own side, and derived from it rather than restated: §11.3.4 draws
|
|
277
|
+
* a port as a small square ON the boundary, and what makes that reading legible
|
|
278
|
+
* is the square's own footprint against the line, not the size of the component
|
|
279
|
+
* behind it. Three drawings, and the constant is chosen so each gets the answer
|
|
280
|
+
* the eye gives:
|
|
281
|
+
*
|
|
282
|
+
* - HALF IN, HALF OUT — the clause's own preferred picture. The centre is on
|
|
283
|
+
* the line, distance 0, and nothing is said.
|
|
284
|
+
* - TANGENT INSIDE, the square just clear of the edge. The centre is 8 units
|
|
285
|
+
* in, half the glyph, well under the tolerance: also silence, because that is
|
|
286
|
+
* a port an author drew on the border and a tool arguing with it is a tool
|
|
287
|
+
* switched off.
|
|
288
|
+
* - PUSHED IN BY ITS OWN SIZE, the square a full 16 units clear of the edge.
|
|
289
|
+
* The centre is 24 units in, past the tolerance, and the finding falls. That
|
|
290
|
+
* is the drawing the PO made on the recette of 2026-09-16: a port dragged
|
|
291
|
+
* into the middle of its component, which §11.3.4 gives no meaning at all.
|
|
292
|
+
*
|
|
293
|
+
* Deliberately NOT `UML_ATTACH_TOLERANCE` (`model.ts`), which is 24 and measures
|
|
294
|
+
* something else: an edge-to-edge GAP, used to decide which box a glyph belongs
|
|
295
|
+
* to when the exporters need an owner. This one measures a centre against a
|
|
296
|
+
* line, and the two numbers answering two questions is better than one number
|
|
297
|
+
* quietly meaning both.
|
|
298
|
+
*/
|
|
299
|
+
export const UML_PORT_BORDER_TOLERANCE = UML_NODE_BOX.port.w;
|
|
300
|
+
/* ── The two behaviour frames ──────────────────────────────────────────── */
|
|
301
|
+
/**
|
|
302
|
+
* The size a fresh PARTITION is created at (§15.6.4), in its default VERTICAL
|
|
303
|
+
* orientation: a column, as tall as the diagram frame it is dropped on and wide
|
|
304
|
+
* enough for an action box (160) with air either side of it.
|
|
305
|
+
*
|
|
306
|
+
* A horizontal partition is created by transposing this, which the creation
|
|
307
|
+
* site does rather than a second constant: a row is a column turned, and two
|
|
308
|
+
* pairs of numbers would be two things to keep in step.
|
|
309
|
+
*/
|
|
310
|
+
export const UML_PARTITION_BOX = { w: 360, h: 900 };
|
|
311
|
+
/**
|
|
312
|
+
* The size a fresh composite state is created at (§14.2.4) — the subject's own
|
|
313
|
+
* footprint, because it is the same kind of object: a rectangle drawn round a
|
|
314
|
+
* handful of elements that are already there.
|
|
315
|
+
*/
|
|
316
|
+
export const UML_REGION_BOX = { w: 520, h: 320 };
|
|
317
|
+
/**
|
|
318
|
+
* The partition's and the region's inset from their own border — the room the
|
|
319
|
+
* drawing gives up on the three sides that carry no band.
|
|
320
|
+
*
|
|
321
|
+
* The subject's 12, and for the same reason: both are transparent frames drawn
|
|
322
|
+
* OVER work that is already there, so every unit of margin is a unit of that
|
|
323
|
+
* work the frame's own furniture sits on top of.
|
|
324
|
+
*/
|
|
325
|
+
export const UML_PARTITION_MARGIN = 12;
|
|
326
|
+
export const UML_REGION_MARGIN = 12;
|
|
327
|
+
/** Both frames' outline: solid and thin, exactly as §15.6.4 and §14.2.4 draw them. */
|
|
328
|
+
export const UML_PARTITION_BORDER_WIDTH = 1.5;
|
|
329
|
+
export const UML_REGION_BORDER_WIDTH = 1.5;
|
|
330
|
+
/**
|
|
331
|
+
* The corner radius of a composite state, in MODEL UNITS.
|
|
332
|
+
*
|
|
333
|
+
* §14.2.4 draws a state — simple or composite — as a ROUND-CORNERED rectangle,
|
|
334
|
+
* and a composite state that came back square would read as a subject or a
|
|
335
|
+
* partition rather than as the state it is. An absolute rather than a fraction,
|
|
336
|
+
* unlike {@link UML_NODE_RADIUS}, because the framework-background declaration's
|
|
337
|
+
* `border.radius` is in model units: the primitive draws the card, and the card
|
|
338
|
+
* is measured in the units the element is.
|
|
339
|
+
*/
|
|
340
|
+
export const UML_REGION_RADIUS = 16;
|
|
341
|
+
/**
|
|
342
|
+
* The name band of a partition and of a composite state — the strip each writes
|
|
343
|
+
* its name in, owned by `@labre/affine-model` because both hit tests read it.
|
|
344
|
+
*
|
|
345
|
+
* Re-exported here so this file stays the one place a reader looks for the
|
|
346
|
+
* pack's metrics, exactly as {@link UML_FRAME_BAND_HEIGHT} is.
|
|
347
|
+
*/
|
|
348
|
+
export { UML_PARTITION_BAND, UML_REGION_BAND } from '@formicoidea/labre-core/model';
|
|
349
|
+
/* ── The interaction artefacts (phase 3) ───────────────────────────────── */
|
|
350
|
+
/**
|
|
351
|
+
* The lifeline's head box and the height of its column, owned by
|
|
352
|
+
* `@labre/affine-model` because the ELEMENT's own `elementBound` and
|
|
353
|
+
* `includesPoint` read them: §17.3.4's head is 160 wide over a 16-wide column,
|
|
354
|
+
* so the picture overflows the box the platform knows about and the model has
|
|
355
|
+
* to say so.
|
|
356
|
+
*
|
|
357
|
+
* Re-exported here so this file stays the one place a reader looks for the
|
|
358
|
+
* pack's metrics, exactly as {@link UML_FRAME_BAND_HEIGHT} is.
|
|
359
|
+
*/
|
|
360
|
+
export { UML_LIFELINE_HEAD, UML_LIFELINE_SPINE } from '@formicoidea/labre-core/model';
|
|
361
|
+
/**
|
|
362
|
+
* The dash pattern of a lifeline's SPINE, in model units (§17.3.4).
|
|
363
|
+
*
|
|
364
|
+
* 6 on, 6 off: even, because the spine is a TIMELINE rather than a relationship
|
|
365
|
+
* — nothing about it points one way — and long enough to read as a dashed line
|
|
366
|
+
* rather than as a dotted one at the zoom a whole sequence diagram is viewed
|
|
367
|
+
* at.
|
|
368
|
+
*/
|
|
369
|
+
export const UML_LIFELINE_DASH = [6, 6];
|
|
370
|
+
/** The spine's own weight: lighter than the head, which is a box and not a line. */
|
|
371
|
+
export const UML_LIFELINE_SPINE_WIDTH = 1.5;
|
|
372
|
+
/**
|
|
373
|
+
* The size a fresh combined fragment is created at (§17.6.4).
|
|
374
|
+
*
|
|
375
|
+
* Wide enough to cover three lifelines at the 200-unit spacing a sequence
|
|
376
|
+
* diagram is laid out on, and tall enough for the three or four exchanges an
|
|
377
|
+
* `alt` is actually drawn round. The same pair the model seeds its `xywh` with,
|
|
378
|
+
* and the declaration reads it from here.
|
|
379
|
+
*/
|
|
380
|
+
export const UML_FRAGMENT_BOX = { w: 600, h: 260 };
|
|
381
|
+
/**
|
|
382
|
+
* The fragment's inset from its own border on the three sides that carry no
|
|
383
|
+
* tag — the subject's and the partition's 12, and for the same reason: a
|
|
384
|
+
* transparent frame drawn over work that is already there spends every unit of
|
|
385
|
+
* margin on somebody else's drawing.
|
|
386
|
+
*/
|
|
387
|
+
export const UML_FRAGMENT_MARGIN = 12;
|
|
388
|
+
/** Its outline: solid and thin, exactly as §17.6.4 draws it. */
|
|
389
|
+
export const UML_FRAGMENT_BORDER_WIDTH = 1.5;
|
|
390
|
+
/**
|
|
391
|
+
* The dashed line between two operands (§17.6.4).
|
|
392
|
+
*
|
|
393
|
+
* A LONGER dash than the lifeline's, on purpose: the two are drawn on the same
|
|
394
|
+
* sheet, crossing each other, and a reader has to tell "this participant exists
|
|
395
|
+
* but is idle" from "the alternative below is a different case".
|
|
396
|
+
*/
|
|
397
|
+
export const UML_FRAGMENT_OPERAND_DASH = [10, 6];
|
|
398
|
+
/**
|
|
399
|
+
* The fragment's operator band — the strip its pentagon is drawn in, owned by
|
|
400
|
+
* `@labre/affine-model` because the fragment's hit test reads it.
|
|
401
|
+
*
|
|
402
|
+
* Re-exported for the reason every other band is.
|
|
403
|
+
*/
|
|
404
|
+
export { UML_FRAGMENT_BAND } from '@formicoidea/labre-core/model';
|
|
405
|
+
/**
|
|
406
|
+
* The guard written in an operand's own corner, and its inset from the
|
|
407
|
+
* operand's top-left.
|
|
408
|
+
*
|
|
409
|
+
* Smaller than a name: §17.6.4 writes `[x > 0]` as a condition, not as a
|
|
410
|
+
* heading, and a guard set at the frame heading's 20px would compete with the
|
|
411
|
+
* operator word two lines above it.
|
|
412
|
+
*/
|
|
413
|
+
export const UML_FRAGMENT_GUARD_FONT_SIZE = UML_BODY_FONT_SIZE;
|
|
414
|
+
export const UML_FRAGMENT_GUARD_INSET = 8;
|
|
415
|
+
/**
|
|
416
|
+
* How close to an operand separator the pointer counts as being ON it, in model
|
|
417
|
+
* units either side.
|
|
418
|
+
*
|
|
419
|
+
* The BPMN pool's own grab half-width, and deliberately: the two gestures are
|
|
420
|
+
* the same gesture on the same kind of line, and a user who has learnt one
|
|
421
|
+
* should not find the other fussier. 12 is wide enough to aim at with a mouse
|
|
422
|
+
* and narrow enough that it does not swallow the messages drawn either side of
|
|
423
|
+
* it.
|
|
424
|
+
*/
|
|
425
|
+
export const UML_OPERAND_GRAB = 12;
|
|
426
|
+
/**
|
|
427
|
+
* The GUARD's rename target: the corner box a double-click on an operand's
|
|
428
|
+
* `[condition]` has to land in.
|
|
429
|
+
*
|
|
430
|
+
* Deliberately a CORNER and not the whole band (see `board-hit.ts`): an
|
|
431
|
+
* operand's guard is written over the messages it governs, so a zone covering
|
|
432
|
+
* the operand would take every double-click meant for them. Wide enough for a
|
|
433
|
+
* realistic condition at the guard's own size, and one line tall plus the inset
|
|
434
|
+
* it is anchored by.
|
|
435
|
+
*/
|
|
436
|
+
export const UML_OPERAND_GUARD_WIDTH = 180;
|
|
437
|
+
export const UML_OPERAND_GUARD_HEIGHT = UML_FRAGMENT_GUARD_FONT_SIZE + UML_FRAGMENT_GUARD_INSET * 2;
|
|
438
|
+
/**
|
|
439
|
+
* The smallest an operand may be dragged, in model units at the fragment's own
|
|
440
|
+
* reference height — converted to a weight against that fragment's total, so a
|
|
441
|
+
* fragment stretched taller keeps the same VISIBLE floor.
|
|
442
|
+
*
|
|
443
|
+
* 48 is the guard line plus room for one message under it: an operand smaller
|
|
444
|
+
* than that holds nothing, and the separator that made it is no longer a
|
|
445
|
+
* division of anything.
|
|
446
|
+
*/
|
|
447
|
+
export const UML_OPERAND_MIN_HEIGHT = 48;
|
|
448
|
+
/* ── Rounded artefacts ─────────────────────────────────────────────────── */
|
|
449
|
+
/**
|
|
450
|
+
* The corner radius of an `action` and of a `state`, as the native shape
|
|
451
|
+
* renderer reads it: a value below 1 is a FRACTION of the box's shorter side.
|
|
452
|
+
*
|
|
453
|
+
* The two round-cornered rectangles of the notation (§15.3.4, §14.2.4), and the
|
|
454
|
+
* only two places this pack departs from `radius: 0`. That zero is not a house
|
|
455
|
+
* style to be defended here — it is what §11.4.4 draws for a classifier — and
|
|
456
|
+
* these two are what §15.3.4 and §14.2.4 draw for their own boxes. A square
|
|
457
|
+
* action is an action that looks like a class.
|
|
458
|
+
*
|
|
459
|
+
* 0.12 of the shorter side: 7 units on a 160 × 60 action, 11 on a 180 × 90
|
|
460
|
+
* state. Enough to be seen at a glance across a sheet, little enough that the
|
|
461
|
+
* box is still a box.
|
|
462
|
+
*/
|
|
463
|
+
export const UML_NODE_RADIUS = 0.12;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { UmlRenderViewExtension, UmlViewExtension } from './view.js';
|
|
2
|
+
/** Host wiring for the uml framework. */
|
|
3
|
+
export declare const umlFramework: {
|
|
4
|
+
readonly flag: "uml";
|
|
5
|
+
readonly telemetryKey: "uml";
|
|
6
|
+
readonly extensions: readonly [{
|
|
7
|
+
readonly viewExtension: typeof UmlRenderViewExtension;
|
|
8
|
+
}, {
|
|
9
|
+
readonly flag: "uml";
|
|
10
|
+
readonly viewExtension: typeof UmlViewExtension;
|
|
11
|
+
}];
|
|
12
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { UmlRenderViewExtension, UmlViewExtension } from './view.js';
|
|
2
|
+
/** Host wiring for the uml framework. */
|
|
3
|
+
export const umlFramework = {
|
|
4
|
+
flag: 'uml',
|
|
5
|
+
telemetryKey: 'uml',
|
|
6
|
+
extensions: [
|
|
7
|
+
{ viewExtension: UmlRenderViewExtension },
|
|
8
|
+
{ flag: 'uml', viewExtension: UmlViewExtension },
|
|
9
|
+
],
|
|
10
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sentence a host sees when the runtime cannot inflate.
|
|
3
|
+
*
|
|
4
|
+
* Exported so the command's spec can assert the wording rather than a substring
|
|
5
|
+
* of it, and so a host that wants to offer "open it in draw.io and save it
|
|
6
|
+
* uncompressed" has something to match on.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DRAWIO_NO_DECOMPRESSION = "This runtime cannot read a compressed .drawio file (DecompressionStream is unavailable). Re-save it from draw.io with File \u203A Properties \u203A Compressed turned off.";
|
|
9
|
+
export declare const DRAWIO_EMPTY_DIAGRAM = "This .drawio file holds no <diagram> to read.";
|
|
10
|
+
export declare const DRAWIO_BAD_PAYLOAD = "This .drawio file\u2019s <diagram> is neither XML nor a payload this reader could inflate.";
|
|
11
|
+
/**
|
|
12
|
+
* An `.drawio` file's text as the `<mxGraphModel>` XML inside it.
|
|
13
|
+
*
|
|
14
|
+
* Identity when the text already holds an `<mxGraphModel>` — that covers a
|
|
15
|
+
* `.drawio.xml`, an uncompressed `.drawio`, and the decoded corpus a spec
|
|
16
|
+
* feeds in. Otherwise: take the first `<diagram>`'s text, base64-decode it,
|
|
17
|
+
* inflate it as a raw deflate stream, and undo the `encodeURIComponent`
|
|
18
|
+
* draw.io applied before compressing.
|
|
19
|
+
*
|
|
20
|
+
* Only the FIRST page is read, and it is a limitation rather than an oversight:
|
|
21
|
+
* a multi-page `.drawio` is several diagrams, this import draws one sheet, and
|
|
22
|
+
* a reader that silently concatenated two pages would produce a drawing neither
|
|
23
|
+
* page ever was. The remaining pages are what a second import of the same file
|
|
24
|
+
* is for.
|
|
25
|
+
*
|
|
26
|
+
* Throws, with one of the three sentences above, when it cannot. That is the
|
|
27
|
+
* contract the import pipeline already has for a file it cannot read
|
|
28
|
+
* (`importInterchangeFile` shows the thrown sentence as the failure
|
|
29
|
+
* notification), and it is the right one here: an empty board with a `warning`
|
|
30
|
+
* would claim the file was read and found empty.
|
|
31
|
+
*/
|
|
32
|
+
export declare function decodeDrawio(text: string): Promise<string>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isCompressedDrawio } from './drawio-import.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one impure step of the draw.io import: inflating an `<mxfile>`'s payload
|
|
4
|
+
* (`docs/adr/0019`, the compressed-payload split).
|
|
5
|
+
*
|
|
6
|
+
* ## Why this is a module of its own, and not part of the reader
|
|
7
|
+
*
|
|
8
|
+
* `docs/adr/0012` P3 makes every interchange reader a PURE function of text: no
|
|
9
|
+
* `std`, no DOM, no clock, no platform. That is what lets labre-mcp call the
|
|
10
|
+
* very function the editor command calls, and what lets a unit suite prove a
|
|
11
|
+
* format with six string literals. Inflating a raw-deflate stream breaks all
|
|
12
|
+
* three halves of it — it needs `DecompressionStream`, it is asynchronous, and
|
|
13
|
+
* what it hands back is bytes — so it does not belong in `drawio-import.ts` and
|
|
14
|
+
* it is not in the capability's `run` either.
|
|
15
|
+
*
|
|
16
|
+
* The split that came out of that:
|
|
17
|
+
*
|
|
18
|
+
* - `importDrawio` (pure, synchronous) reads DECODED `<mxGraphModel>` XML;
|
|
19
|
+
* - `UML_DRAWIO_IMPORT.run` does the same, and answers a payload that is still
|
|
20
|
+
* compressed with one `warning` note rather than pretending it read nothing;
|
|
21
|
+
* - this function, called by the COMMAND before either of them, is where the
|
|
22
|
+
* platform API lives.
|
|
23
|
+
*
|
|
24
|
+
* A caller that has plain XML pays nothing: an `<mxGraphModel>` anywhere in the
|
|
25
|
+
* text is returned unchanged, which is also what draw.io itself writes with
|
|
26
|
+
* File › Properties › Compressed turned off.
|
|
27
|
+
*
|
|
28
|
+
* ## No dependency, and no vendored inflater
|
|
29
|
+
*
|
|
30
|
+
* `DecompressionStream('deflate-raw')` is the platform's own zlib, shipped in
|
|
31
|
+
* every browser this library supports and in Node 18 and up. Adding `pako` or
|
|
32
|
+
* `fflate` to read one file format would put forty kilobytes into every bundle
|
|
33
|
+
* that imports this package for the sake of a function the runtime already has.
|
|
34
|
+
* Where the API is genuinely absent the failure is NAMED — see below — rather
|
|
35
|
+
* than left as a `TypeError` about an undefined constructor.
|
|
36
|
+
*/
|
|
37
|
+
/** `<diagram …>PAYLOAD</diagram>` — the first page of an `mxfile`. */
|
|
38
|
+
const DIAGRAM = /<diagram\b[^>]*>([\s\S]*?)<\/diagram>/i;
|
|
39
|
+
/**
|
|
40
|
+
* The sentence a host sees when the runtime cannot inflate.
|
|
41
|
+
*
|
|
42
|
+
* Exported so the command's spec can assert the wording rather than a substring
|
|
43
|
+
* of it, and so a host that wants to offer "open it in draw.io and save it
|
|
44
|
+
* uncompressed" has something to match on.
|
|
45
|
+
*/
|
|
46
|
+
export const DRAWIO_NO_DECOMPRESSION = 'This runtime cannot read a compressed .drawio file (DecompressionStream is unavailable). Re-save it from draw.io with File › Properties › Compressed turned off.';
|
|
47
|
+
export const DRAWIO_EMPTY_DIAGRAM = 'This .drawio file holds no <diagram> to read.';
|
|
48
|
+
export const DRAWIO_BAD_PAYLOAD = 'This .drawio file’s <diagram> is neither XML nor a payload this reader could inflate.';
|
|
49
|
+
/**
|
|
50
|
+
* An `.drawio` file's text as the `<mxGraphModel>` XML inside it.
|
|
51
|
+
*
|
|
52
|
+
* Identity when the text already holds an `<mxGraphModel>` — that covers a
|
|
53
|
+
* `.drawio.xml`, an uncompressed `.drawio`, and the decoded corpus a spec
|
|
54
|
+
* feeds in. Otherwise: take the first `<diagram>`'s text, base64-decode it,
|
|
55
|
+
* inflate it as a raw deflate stream, and undo the `encodeURIComponent`
|
|
56
|
+
* draw.io applied before compressing.
|
|
57
|
+
*
|
|
58
|
+
* Only the FIRST page is read, and it is a limitation rather than an oversight:
|
|
59
|
+
* a multi-page `.drawio` is several diagrams, this import draws one sheet, and
|
|
60
|
+
* a reader that silently concatenated two pages would produce a drawing neither
|
|
61
|
+
* page ever was. The remaining pages are what a second import of the same file
|
|
62
|
+
* is for.
|
|
63
|
+
*
|
|
64
|
+
* Throws, with one of the three sentences above, when it cannot. That is the
|
|
65
|
+
* contract the import pipeline already has for a file it cannot read
|
|
66
|
+
* (`importInterchangeFile` shows the thrown sentence as the failure
|
|
67
|
+
* notification), and it is the right one here: an empty board with a `warning`
|
|
68
|
+
* would claim the file was read and found empty.
|
|
69
|
+
*/
|
|
70
|
+
export async function decodeDrawio(text) {
|
|
71
|
+
if (!isCompressedDrawio(text))
|
|
72
|
+
return text;
|
|
73
|
+
const payload = DIAGRAM.exec(text)?.[1]?.trim();
|
|
74
|
+
if (!payload)
|
|
75
|
+
throw new Error(DRAWIO_EMPTY_DIAGRAM);
|
|
76
|
+
const Decompression = globalThis.DecompressionStream;
|
|
77
|
+
if (!Decompression)
|
|
78
|
+
throw new Error(DRAWIO_NO_DECOMPRESSION);
|
|
79
|
+
let inflated;
|
|
80
|
+
try {
|
|
81
|
+
const binary = atob(payload);
|
|
82
|
+
const bytes = Uint8Array.from(binary, character => character.charCodeAt(0));
|
|
83
|
+
const stream = new Blob([bytes])
|
|
84
|
+
.stream()
|
|
85
|
+
.pipeThrough(new Decompression('deflate-raw'));
|
|
86
|
+
// draw.io deflates the URI-ENCODED xml, so this is percent-escaped text and
|
|
87
|
+
// not the document yet.
|
|
88
|
+
inflated = decodeURIComponent(await new Response(stream).text());
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
throw new Error(DRAWIO_BAD_PAYLOAD);
|
|
92
|
+
}
|
|
93
|
+
if (!inflated.includes('<mxGraphModel'))
|
|
94
|
+
throw new Error(DRAWIO_BAD_PAYLOAD);
|
|
95
|
+
return inflated;
|
|
96
|
+
}
|