reamkit 1.23.0 → 1.24.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/esm/core/bmp.d.ts +33 -0
- package/dist/esm/core/bmp.js +276 -0
- package/dist/esm/core/document-model/types.d.ts +37 -0
- package/dist/esm/core/drawingml/chart-geometry.js +3 -2
- package/dist/esm/core/drawingml/diagram/colors.d.ts +52 -0
- package/dist/esm/core/drawingml/diagram/colors.js +125 -0
- package/dist/esm/core/drawingml/diagram/data-model.d.ts +47 -0
- package/dist/esm/core/drawingml/diagram/data-model.js +119 -0
- package/dist/esm/core/drawingml/diagram/layout-engine.d.ts +46 -0
- package/dist/esm/core/drawingml/diagram/layout-engine.js +948 -0
- package/dist/esm/core/drawingml/diagram/run.d.ts +37 -0
- package/dist/esm/core/drawingml/diagram/run.js +50 -0
- package/dist/esm/core/drawingml/diagram/to-drawing.d.ts +16 -0
- package/dist/esm/core/drawingml/diagram/to-drawing.js +86 -0
- package/dist/esm/core/drawingml/preset-geometry.js +116 -6
- package/dist/esm/core/drawingml/text-warp.d.ts +58 -0
- package/dist/esm/core/drawingml/text-warp.js +355 -0
- package/dist/esm/core/drawingml/theme-parser.js +9 -3
- package/dist/esm/core/font/measure.d.ts +12 -0
- package/dist/esm/core/font/measure.js +36 -0
- package/dist/esm/core/images.d.ts +30 -2
- package/dist/esm/core/images.js +121 -5
- package/dist/esm/core/metafile/emf.js +144 -16
- package/dist/esm/core/metafile/picture.d.ts +49 -0
- package/dist/esm/core/metafile/picture.js +70 -1
- package/dist/esm/core/metafile/wmf.js +53 -4
- package/dist/esm/core/ole/escher-blip.js +11 -1
- package/dist/esm/excel/print-model.js +2 -2
- package/dist/esm/excel/sheet-drawing.js +1 -1
- package/dist/esm/excel/sheet-to-flow.js +16 -2
- package/dist/esm/html/html-writer.js +2 -1
- package/dist/esm/layout/page-doc.d.ts +35 -0
- package/dist/esm/layout/page-doc.js +14 -1
- package/dist/esm/layout/styled-layout.js +176 -15
- package/dist/esm/pdf/shading.js +8 -6
- package/dist/esm/pdf/styled-page-emitter.js +42 -3
- package/dist/esm/pdf/vector-graphics.js +1 -1
- package/dist/esm/pdf-reader/image-decode.js +1 -1
- package/dist/esm/pptx/ppt/ppt-reader.js +95 -27
- package/dist/esm/pptx/ppt/ppt-text.d.ts +41 -0
- package/dist/esm/pptx/ppt/ppt-text.js +327 -37
- package/dist/esm/pptx/pptx-reader.js +90 -10
- package/dist/esm/pptx/slide-parser.d.ts +4 -1
- package/dist/esm/pptx/slide-parser.js +99 -13
- package/dist/esm/pptx/sp-helpers.d.ts +7 -3
- package/dist/esm/pptx/sp-helpers.js +45 -7
- package/dist/esm/pptx/table-style.d.ts +7 -0
- package/dist/esm/pptx/table-style.js +59 -7
- package/dist/esm/svg/svg-writer.js +1 -0
- package/dist/esm/word/document-parser.d.ts +4 -1
- package/dist/esm/word/document-parser.js +4 -1
- package/dist/esm/word/docx-reader.js +26 -13
- package/dist/esm/word/docx-writer.js +4 -0
- package/dist/esm/word/drawing-parser.d.ts +20 -0
- package/dist/esm/word/drawing-parser.js +37 -4
- package/dist/esm/word/table-parser.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
import { poAttr, poChildren, poIs, poTag } from "../../po-helpers.js";
|
|
2
|
+
//#region src/core/drawingml/diagram/layout-engine.ts
|
|
3
|
+
/**
|
|
4
|
+
* Lay a diagram out from its data and layout parts.
|
|
5
|
+
*
|
|
6
|
+
* @param layout The parsed `layout1.xml` tree.
|
|
7
|
+
* @param data The parsed data part.
|
|
8
|
+
* @param cx The frame's width in EMU, as the drawing states it.
|
|
9
|
+
* @param cy The frame's height in EMU.
|
|
10
|
+
* @returns The boxes to draw, or an empty list when the layout uses an
|
|
11
|
+
* algorithm this slice does not run — the caller then reports the
|
|
12
|
+
* diagram as a loss exactly as before.
|
|
13
|
+
*/
|
|
14
|
+
function layoutDiagram(layout, data, cx, cy) {
|
|
15
|
+
const root = findChild(layout, "dgm:layoutDef");
|
|
16
|
+
const top = root ? findChild(poChildren(root), "dgm:layoutNode") : void 0;
|
|
17
|
+
if (!top || !data.root) return [];
|
|
18
|
+
const parsed = parseNode(top, {
|
|
19
|
+
maxDepth: data.depth,
|
|
20
|
+
data,
|
|
21
|
+
root: data.root
|
|
22
|
+
});
|
|
23
|
+
if (parsed.alg !== "snake" && parsed.alg !== "lin" && parsed.alg !== "composite" && parsed.alg !== "cycle" && parsed.alg !== "hier") return [];
|
|
24
|
+
return layoutIn(parsed, data.root, {
|
|
25
|
+
x: 0,
|
|
26
|
+
y: 0,
|
|
27
|
+
cx,
|
|
28
|
+
cy
|
|
29
|
+
}, data, 0);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One layout node run inside the box it was given, by whichever algorithm it
|
|
33
|
+
* names.
|
|
34
|
+
*
|
|
35
|
+
* @param node The layout node.
|
|
36
|
+
* @param point The data point it is laying out.
|
|
37
|
+
* @param box The rectangle it has to fill.
|
|
38
|
+
* @param data The data part.
|
|
39
|
+
* @param index The point's place among its siblings, for the colour run.
|
|
40
|
+
*/
|
|
41
|
+
function layoutIn(node, point, box, data, index) {
|
|
42
|
+
if (node.alg === "composite") return compositeLayout(node, point, box, data, index);
|
|
43
|
+
if (node.alg === "hier") return hierarchyLayout(node, point, box, data);
|
|
44
|
+
if (node.alg === "cycle") return cycleLayout(node, point, box, data);
|
|
45
|
+
if (node.alg === "lin" || node.alg === "snake") return listLayout(node, point, box, data);
|
|
46
|
+
if (node.alg === "sp") {
|
|
47
|
+
const own = spokenFor(node, point, data).spPr;
|
|
48
|
+
return node.shapeType === void 0 ? [] : [{
|
|
49
|
+
point: {
|
|
50
|
+
id: `${point.id}/${node.name}`,
|
|
51
|
+
type: "node",
|
|
52
|
+
...own !== void 0 ? { spPr: own } : {}
|
|
53
|
+
},
|
|
54
|
+
shapeType: node.shapeType,
|
|
55
|
+
...styleOf(node),
|
|
56
|
+
index,
|
|
57
|
+
...box
|
|
58
|
+
}];
|
|
59
|
+
}
|
|
60
|
+
if (node.alg !== "tx") return [];
|
|
61
|
+
return [{
|
|
62
|
+
point: spokenFor(node, point, data),
|
|
63
|
+
shapeType: node.shapeType ?? "rect",
|
|
64
|
+
...styleOf(node),
|
|
65
|
+
index,
|
|
66
|
+
...box
|
|
67
|
+
}];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* §21.4.3.2 `hierRoot`/`hierChild` — the hierarchy family, which is the org
|
|
71
|
+
* chart. `hierRoot` puts a node's box above its subtree and `hierChild` ranges
|
|
72
|
+
* that subtree beneath it, each child being a `hierRoot` again; the two are one
|
|
73
|
+
* tree walk written as two algorithms, and the engine runs them as one.
|
|
74
|
+
*
|
|
75
|
+
* The layout program that expresses this is deeply nested — a `choose` per
|
|
76
|
+
* branch style at every level, with the same three boxes renamed each time —
|
|
77
|
+
* but it says only one thing, and it says it in the constraints at the top: a
|
|
78
|
+
* box is `w` wide and `0.5w` tall, levels are `0.21w` apart and so are
|
|
79
|
+
* siblings. So the geometry is read from there and the shape of the tree from
|
|
80
|
+
* the DATA, which is where it actually lives.
|
|
81
|
+
*
|
|
82
|
+
* @param node The `hierChild`/`hierRoot` node at the top of the hierarchy.
|
|
83
|
+
* @param point The data point it is rooted at.
|
|
84
|
+
* @param box The frame the tree has to fit.
|
|
85
|
+
* @param data The data part.
|
|
86
|
+
*/
|
|
87
|
+
function hierarchyLayout(node, point, box, data) {
|
|
88
|
+
const f = hierFactors(node.constrs);
|
|
89
|
+
const face = firstTextBox(node);
|
|
90
|
+
const build = (p) => {
|
|
91
|
+
const kids = data.children(p.id, "node").map(build);
|
|
92
|
+
const spread = kids.length === 0 ? 0 : kids.reduce((a, k) => a + k.units, 0) + f.sibSp * (kids.length - 1);
|
|
93
|
+
return {
|
|
94
|
+
point: p,
|
|
95
|
+
kids,
|
|
96
|
+
units: Math.max(1, spread),
|
|
97
|
+
spread
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const roots = data.children(point.id, "node").map(build);
|
|
101
|
+
if (roots.length === 0) return [];
|
|
102
|
+
const wide = roots.reduce((a, r) => a + r.units, 0) + f.sibSp * (roots.length - 1);
|
|
103
|
+
const rows = (t) => 1 + t.kids.reduce((a, k) => Math.max(a, rows(k)), 0);
|
|
104
|
+
const deep = roots.reduce((a, r) => Math.max(a, rows(r)), 0);
|
|
105
|
+
const tall = deep * f.aspect + (deep - 1) * f.sp;
|
|
106
|
+
if (wide <= 0 || tall <= 0) return [];
|
|
107
|
+
const w = Math.min(box.cx / wide, box.cy / tall);
|
|
108
|
+
const out = [];
|
|
109
|
+
const top = box.y + (box.cy - tall * w) / 2;
|
|
110
|
+
const place = (t, left, depth) => {
|
|
111
|
+
out.push({
|
|
112
|
+
point: t.point,
|
|
113
|
+
shapeType: face?.shapeType ?? "rect",
|
|
114
|
+
styleLbl: `node${Math.min(depth, 4)}`,
|
|
115
|
+
...face === void 0 ? {} : textPlacing(face),
|
|
116
|
+
index: depth,
|
|
117
|
+
x: left + (t.units * w - w) / 2,
|
|
118
|
+
y: top + depth * (f.aspect + f.sp) * w,
|
|
119
|
+
cx: w,
|
|
120
|
+
cy: f.aspect * w
|
|
121
|
+
});
|
|
122
|
+
let at = left + (t.units - t.spread) * w / 2;
|
|
123
|
+
for (const kid of t.kids) {
|
|
124
|
+
place(kid, at, depth + 1);
|
|
125
|
+
at += (kid.units + f.sibSp) * w;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
let at = box.x + (box.cx - wide * w) / 2;
|
|
129
|
+
for (const root of roots) {
|
|
130
|
+
place(root, at, 0);
|
|
131
|
+
at += (root.units + f.sibSp) * w;
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
function hierFactors(constrs) {
|
|
136
|
+
let aspect = .5;
|
|
137
|
+
let sp = .21;
|
|
138
|
+
let sibSp = .21;
|
|
139
|
+
for (const k of constrs) {
|
|
140
|
+
if (k.refType !== "w") continue;
|
|
141
|
+
if (k.type === "h" && k.refForName !== void 0) aspect = k.fact;
|
|
142
|
+
if (k.type === "sp") sp = k.fact;
|
|
143
|
+
if (k.type === "sibSp") sibSp = k.fact;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
aspect,
|
|
147
|
+
sp,
|
|
148
|
+
sibSp
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function firstTextBox(node) {
|
|
152
|
+
const walk = (items) => {
|
|
153
|
+
for (const item of items) {
|
|
154
|
+
if (item.kind === "node" && item.alg === "tx") return item;
|
|
155
|
+
const found = walk(item.children);
|
|
156
|
+
if (found) return found;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
return walk(node.children);
|
|
160
|
+
}
|
|
161
|
+
function textPlacing(node) {
|
|
162
|
+
const anchor = node.algParams.get("txAnchorVert");
|
|
163
|
+
const align = node.algParams.get("parTxLTRAlign");
|
|
164
|
+
return {
|
|
165
|
+
...anchor !== void 0 && anchor !== "" ? { anchor } : {},
|
|
166
|
+
...align !== void 0 && align !== "" ? { align } : {}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function styleOf(node) {
|
|
170
|
+
const sz = fontSize(node.constrs, void 0, "primFontSz");
|
|
171
|
+
const anchor = node.algParams.get("txAnchorVert");
|
|
172
|
+
const align = node.algParams.get("parTxLTRAlign");
|
|
173
|
+
return {
|
|
174
|
+
...node.styleLbl !== void 0 ? { styleLbl: node.styleLbl } : {},
|
|
175
|
+
...opt("fontSizePt", sz),
|
|
176
|
+
...bulleted(node) ? { bulleted: true } : {},
|
|
177
|
+
...node.hideGeom === true ? { hideGeom: true } : {},
|
|
178
|
+
...anchor !== void 0 && anchor !== "" ? { anchor } : {},
|
|
179
|
+
...align !== void 0 && align !== "" ? { align } : {},
|
|
180
|
+
...node.shapeRotation !== void 0 ? { rotation60k: node.shapeRotation * 6e4 } : {}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* §21.4.3.7 `dgm:presOf` — which points a box speaks for. `desOrSelf` is the
|
|
185
|
+
* point and everything under it; `des` is everything under it and NOT the point,
|
|
186
|
+
* which is how a list's second box holds the children's words and not a second
|
|
187
|
+
* copy of the label. Anything else is the point itself.
|
|
188
|
+
*/
|
|
189
|
+
function spokenFor(box, point, data) {
|
|
190
|
+
if (box.presOfPath) return walkPath(box.presOfPath, point, data);
|
|
191
|
+
if (box.presOfAxis === "desOrSelf") return withDescendants(point, data);
|
|
192
|
+
if (box.presOfAxis === "des") return gatheredText(point, data.children(point.id, "node"));
|
|
193
|
+
return point;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* §21.4.3.7 — the `st`/`cnt` slice of a `presOf` path, read from the point the
|
|
197
|
+
* box is being laid out for.
|
|
198
|
+
*
|
|
199
|
+
* The cycle-matrix layout is the reason this exists: each of its four quadrants
|
|
200
|
+
* names ONE child (`axis="ch" st="3" cnt="1"`) and each of the four callouts
|
|
201
|
+
* beside them names that child's own children (`axis="ch des" st="3 1"
|
|
202
|
+
* cnt="1 0"`). Read as a plain axis, all eight boxes spoke for the root and the
|
|
203
|
+
* whole diagram came out blank.
|
|
204
|
+
*
|
|
205
|
+
* @param path The parsed axis / start / count triple.
|
|
206
|
+
* @param point The point the box is laid out for.
|
|
207
|
+
* @param data The data part, for walking children.
|
|
208
|
+
* @returns One point carrying the text of everything the path reached.
|
|
209
|
+
*/
|
|
210
|
+
function walkPath(path, point, data) {
|
|
211
|
+
let at = [point];
|
|
212
|
+
path.axis.forEach((step, i) => {
|
|
213
|
+
const next = step === "ch" ? at.flatMap((p) => data.children(p.id, "node")) : step === "des" ? at.flatMap((p) => descendants(p, data)) : step === "self" ? at : [];
|
|
214
|
+
const from = Math.max(0, (path.st[i] ?? 1) - 1);
|
|
215
|
+
const want = path.cnt[i] ?? 0;
|
|
216
|
+
at = next.slice(from, want === 0 ? next.length : from + want);
|
|
217
|
+
});
|
|
218
|
+
if (at.length === 1) return at[0];
|
|
219
|
+
const paragraphs = at.flatMap((p) => p.text ? poChildren(p.text) : []);
|
|
220
|
+
const id = `${point.id}/${path.axis.join(".")}`;
|
|
221
|
+
if (paragraphs.length === 0) return {
|
|
222
|
+
id,
|
|
223
|
+
type: "node"
|
|
224
|
+
};
|
|
225
|
+
return {
|
|
226
|
+
id,
|
|
227
|
+
type: "node",
|
|
228
|
+
text: { "dgm:t": paragraphs }
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Every point under `point`, depth first — the `des` axis. */
|
|
232
|
+
function descendants(point, data) {
|
|
233
|
+
const out = [];
|
|
234
|
+
const walk = (id) => {
|
|
235
|
+
for (const kid of data.children(id, "node")) {
|
|
236
|
+
out.push(kid);
|
|
237
|
+
walk(kid.id);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
walk(point.id);
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* §21.4.3.7 — a `presOf`'s axis PATH, when it names more than one step or
|
|
245
|
+
* slices the one it names. A single unsliced axis stays with the plain
|
|
246
|
+
* `presOfAxis` reading, which the rest of the engine already answers.
|
|
247
|
+
*
|
|
248
|
+
* @param presOf The `dgm:presOf` element, when the box has one.
|
|
249
|
+
* @returns The path, or `undefined` when there is nothing to walk.
|
|
250
|
+
*/
|
|
251
|
+
function axisPath(presOf) {
|
|
252
|
+
if (!presOf) return void 0;
|
|
253
|
+
const axis = (poAttr(presOf, "axis") ?? "").split(/\s+/u).filter((a) => a !== "");
|
|
254
|
+
const nums = (name) => (poAttr(presOf, name) ?? "").split(/\s+/u).filter((v) => v !== "").map((v) => Number(v));
|
|
255
|
+
const st = nums("st");
|
|
256
|
+
const cnt = nums("cnt");
|
|
257
|
+
if (axis.length === 0) return void 0;
|
|
258
|
+
if (axis.length === 1 && st.length === 0 && cnt.length === 0) return void 0;
|
|
259
|
+
if (!axis.every((a) => a === "ch" || a === "des" || a === "self")) return void 0;
|
|
260
|
+
return {
|
|
261
|
+
axis,
|
|
262
|
+
st,
|
|
263
|
+
cnt
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function withDescendants(point, data) {
|
|
267
|
+
const paragraphs = point.text ? [...poChildren(point.text)] : [];
|
|
268
|
+
const walk = (id) => {
|
|
269
|
+
for (const kid of data.children(id, "node")) {
|
|
270
|
+
if (kid.text) paragraphs.push(...poChildren(kid.text));
|
|
271
|
+
walk(kid.id);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
walk(point.id);
|
|
275
|
+
if (paragraphs.length === 0) return point;
|
|
276
|
+
return {
|
|
277
|
+
...point,
|
|
278
|
+
text: { "dgm:t": paragraphs }
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* §21.4.3.2 `composite` — the children are not a list, they are placed: each
|
|
283
|
+
* states where its edges are against the parent's box or against a sibling's,
|
|
284
|
+
* and then runs its own algorithm inside what it was given. A process arrow
|
|
285
|
+
* across the frame with the steps standing along it is one node laid over
|
|
286
|
+
* another, which no list algorithm can express.
|
|
287
|
+
*/
|
|
288
|
+
/**
|
|
289
|
+
* §21.4.3.4 `<dgm:param type="ar"/>` — the width-to-height ratio the algorithm
|
|
290
|
+
* wants its cell in. The cell shrinks to it on whichever axis is over, and
|
|
291
|
+
* stays centred on what it had.
|
|
292
|
+
*
|
|
293
|
+
* The cycle-matrix layout is the reason this matters: it asks for 1.3, measures
|
|
294
|
+
* its disc against the WIDTH it gets, and on a wide slide placeholder the disc
|
|
295
|
+
* came out half again too big and its four quarters overlapped in a pinwheel.
|
|
296
|
+
*
|
|
297
|
+
* @param node The layout node, for its algorithm parameters.
|
|
298
|
+
* @param box The cell it was given.
|
|
299
|
+
* @returns The cell at the stated ratio, or unchanged when it states none.
|
|
300
|
+
*/
|
|
301
|
+
function aspectFitted(node, box) {
|
|
302
|
+
const ar = Number(node.algParams.get("ar") ?? "");
|
|
303
|
+
if (!Number.isFinite(ar) || ar <= 0 || box.cx <= 0 || box.cy <= 0) return box;
|
|
304
|
+
const cx = Math.min(box.cx, box.cy * ar);
|
|
305
|
+
const cy = Math.min(box.cy, box.cx / ar);
|
|
306
|
+
return {
|
|
307
|
+
x: box.x + (box.cx - cx) / 2,
|
|
308
|
+
y: box.y + (box.cy - cy) / 2,
|
|
309
|
+
cx,
|
|
310
|
+
cy
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function compositeLayout(node, point, box, data, index, outer = []) {
|
|
314
|
+
const kids = namedBoxes(node);
|
|
315
|
+
box = aspectFitted(node, box);
|
|
316
|
+
const run = (fontEmu) => {
|
|
317
|
+
const placed = /* @__PURE__ */ new Map();
|
|
318
|
+
return kids.map((child) => {
|
|
319
|
+
const rect = rectIn(node.constrs, child.name, box, placed, fontEmu, outer);
|
|
320
|
+
placed.set(child.name, rect);
|
|
321
|
+
return rect;
|
|
322
|
+
});
|
|
323
|
+
};
|
|
324
|
+
let rects = run(0);
|
|
325
|
+
if (kids.every((c) => fontHeight(outer, c.name) !== void 0)) {
|
|
326
|
+
const units = run(1).reduce((a, r) => Math.max(a, r.y - box.y + r.cy), 0);
|
|
327
|
+
if (units > 0) rects = run(box.cy / units);
|
|
328
|
+
}
|
|
329
|
+
const out = [];
|
|
330
|
+
kids.forEach((child, i) => {
|
|
331
|
+
const rect = rects[i];
|
|
332
|
+
if (rect) out.push(...layoutIn(child, point, rect, data, index));
|
|
333
|
+
});
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
function fontHeight(constrs, name) {
|
|
337
|
+
for (const k of constrs) if (k.type === "h" && k.forName === name && k.refType === "primFontSz" && k.op === void 0) return k.fact;
|
|
338
|
+
}
|
|
339
|
+
function places(constrs) {
|
|
340
|
+
return constrs.some((k) => k.type === "ctrX" || k.type === "ctrY" || k.type === "r" || k.type === "b" || (k.type === "l" || k.type === "t") && k.refType !== void 0 && k.refForName === void 0);
|
|
341
|
+
}
|
|
342
|
+
function rectIn(constrs, name, box, placed, fontEmu = 0, outer = []) {
|
|
343
|
+
let cx = box.cx;
|
|
344
|
+
let cy = box.cy;
|
|
345
|
+
let x;
|
|
346
|
+
let y;
|
|
347
|
+
const ref = (k) => {
|
|
348
|
+
const from = k.refForName === void 0 ? box : k.refForName === name ? {
|
|
349
|
+
cx,
|
|
350
|
+
cy
|
|
351
|
+
} : placed.get(k.refForName) ?? box;
|
|
352
|
+
return (k.refType === "w" ? from.cx : k.refType === "h" ? from.cy : k.refType === "primFontSz" ? fontEmu : 0) * k.fact;
|
|
353
|
+
};
|
|
354
|
+
const mine = [...outer, ...constrs].filter((k) => k.forName === name);
|
|
355
|
+
for (const k of mine) {
|
|
356
|
+
const v = ref(k);
|
|
357
|
+
switch (k.type) {
|
|
358
|
+
case "w":
|
|
359
|
+
cx = k.op === "lte" ? Math.min(cx, v) : v;
|
|
360
|
+
break;
|
|
361
|
+
case "h":
|
|
362
|
+
cy = k.op === "lte" ? Math.min(cy, v) : v;
|
|
363
|
+
break;
|
|
364
|
+
case "l":
|
|
365
|
+
x = box.x + v;
|
|
366
|
+
break;
|
|
367
|
+
case "r":
|
|
368
|
+
x = box.x + v - cx;
|
|
369
|
+
break;
|
|
370
|
+
case "t":
|
|
371
|
+
y = box.y + v;
|
|
372
|
+
break;
|
|
373
|
+
case "b":
|
|
374
|
+
y = box.y + v - cy;
|
|
375
|
+
break;
|
|
376
|
+
case "ctrX":
|
|
377
|
+
x = box.x + v - cx / 2;
|
|
378
|
+
break;
|
|
379
|
+
case "ctrY":
|
|
380
|
+
y = box.y + v - cy / 2;
|
|
381
|
+
break;
|
|
382
|
+
default: break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
x: x ?? box.x,
|
|
387
|
+
y: y ?? box.y,
|
|
388
|
+
cx,
|
|
389
|
+
cy
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* §21.4.3.2 `cycle` — the point's children around a circle, starting at
|
|
394
|
+
* `stAng` and spanning `spanAng` (both in degrees, clockwise from twelve
|
|
395
|
+
* o'clock). `ctrShpMap="fNode"` makes the FIRST child the hub in the middle
|
|
396
|
+
* and its own children the ring around it, which is what a radial cycle is.
|
|
397
|
+
*/
|
|
398
|
+
function cycleLayout(node, point, box, data) {
|
|
399
|
+
const boxes = namedBoxes(node);
|
|
400
|
+
const hubNode = node.algParams.get("ctrShpMap") === "fNode" ? boxes[0] : void 0;
|
|
401
|
+
const ringNode = boxes[boxes.length - 1];
|
|
402
|
+
const kids = data.children(point.id, "node");
|
|
403
|
+
const hub = hubNode ? kids[0] : void 0;
|
|
404
|
+
const ring = hub ? data.children(hub.id, "node") : kids;
|
|
405
|
+
if (ring.length === 0 || !ringNode) return [];
|
|
406
|
+
const n = ring.length;
|
|
407
|
+
const stAng = Number(node.algParams.get("stAng") ?? "0");
|
|
408
|
+
const spanAng = Number(node.algParams.get("spanAng") ?? "360");
|
|
409
|
+
const step = spanAng / (Math.abs(spanAng) >= 359.5 ? n : Math.max(1, n - 1));
|
|
410
|
+
const stated = sizeFact(node.constrs, ringNode.name, "w");
|
|
411
|
+
const gap = siblingSpace(node.constrs, ringNode.name);
|
|
412
|
+
const chord = Math.sin(Math.PI / Math.max(2, n));
|
|
413
|
+
const least = Math.min(box.cx, box.cy);
|
|
414
|
+
const size = stated !== void 0 && stated < .99 ? {
|
|
415
|
+
cx: box.cx * stated,
|
|
416
|
+
cy: box.cy * (sizeFact(node.constrs, ringNode.name, "h") ?? stated)
|
|
417
|
+
} : {
|
|
418
|
+
cx: least * chord / (1 + gap + chord),
|
|
419
|
+
cy: least * chord / (1 + gap + chord)
|
|
420
|
+
};
|
|
421
|
+
const radius = Math.min((box.cx - size.cx) / 2, (box.cy - size.cy) / 2);
|
|
422
|
+
const midX = box.x + box.cx / 2;
|
|
423
|
+
const midY = box.y + box.cy / 2;
|
|
424
|
+
const out = [];
|
|
425
|
+
if (hub && hubNode) out.push(...splitCell(node, hubNode.name, hub, data, {
|
|
426
|
+
x: midX - size.cx / 2,
|
|
427
|
+
y: midY - size.cy / 2,
|
|
428
|
+
cx: size.cx,
|
|
429
|
+
cy: size.cy
|
|
430
|
+
}, hubNode.shapeType ?? "rect", 0));
|
|
431
|
+
ring.forEach((pt, i) => {
|
|
432
|
+
const a = (stAng + i * step) * Math.PI / 180;
|
|
433
|
+
const cellX = midX + radius * Math.sin(a) - size.cx / 2;
|
|
434
|
+
const cellY = midY - radius * Math.cos(a) - size.cy / 2;
|
|
435
|
+
out.push(...splitCell(node, ringNode.name, pt, data, {
|
|
436
|
+
x: cellX,
|
|
437
|
+
y: cellY,
|
|
438
|
+
cx: size.cx,
|
|
439
|
+
cy: size.cy
|
|
440
|
+
}, ringNode.shapeType ?? "rect", i));
|
|
441
|
+
});
|
|
442
|
+
return out;
|
|
443
|
+
}
|
|
444
|
+
function siblingSpace(constrs, childName) {
|
|
445
|
+
for (const k of constrs) if (k.type === "sibSp" && (k.refForName === childName || k.refForName === void 0)) return k.fact;
|
|
446
|
+
return .5;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* §21.4.3.2 `lin` / `snake` — the point's children in a row or a column, with
|
|
450
|
+
* the gap the constraints state between them. `snake` wraps; `lin` does not.
|
|
451
|
+
*/
|
|
452
|
+
function listLayout(parsed, parent, box, data) {
|
|
453
|
+
const nodes = data.children(parent.id, "node");
|
|
454
|
+
if (nodes.length === 0) return [];
|
|
455
|
+
const childName = firstNodeName(parsed.children) ?? "node";
|
|
456
|
+
const c = resolve(parsed.constrs, childName);
|
|
457
|
+
const shapeType = childShapeType(parsed.children) ?? "rect";
|
|
458
|
+
const vertical = isVertical(parsed);
|
|
459
|
+
const body = namedBoxes(parsed);
|
|
460
|
+
const trans = transitionBox(parsed);
|
|
461
|
+
const n = nodes.length;
|
|
462
|
+
const cols = vertical ? 1 : parsed.alg === "snake" ? bestColumns(n, box.cx, box.cy, c.aspect, c.gap) : n;
|
|
463
|
+
const rows = Math.ceil(n / cols);
|
|
464
|
+
const cellW = box.cx / (cols + Math.max(0, cols - 1) * c.gap);
|
|
465
|
+
const gapW = cellW * c.gap;
|
|
466
|
+
const cellH = box.cy / (rows + Math.max(0, rows - 1) * c.gap);
|
|
467
|
+
const gapH = cellH * c.gap;
|
|
468
|
+
const out = [];
|
|
469
|
+
nodes.forEach((point, i) => {
|
|
470
|
+
const col = vertical ? Math.floor(i / rows) : i % cols;
|
|
471
|
+
const row = vertical ? i % rows : Math.floor(i / cols);
|
|
472
|
+
const inRow = vertical ? rows : Math.min(cols, n - row * cols);
|
|
473
|
+
const rowWidth = inRow * cellW + Math.max(0, inRow - 1) * gapW;
|
|
474
|
+
const left = vertical ? 0 : (box.cx - rowWidth) / 2;
|
|
475
|
+
const cell = {
|
|
476
|
+
x: box.x + left + col * (cellW + gapW),
|
|
477
|
+
y: box.y + row * (cellH + gapH),
|
|
478
|
+
cx: cellW,
|
|
479
|
+
cy: cellH
|
|
480
|
+
};
|
|
481
|
+
out.push(...body.length > 1 ? stackCell(parsed, body, point, data, cell, shapeType, i, vertical) : splitCell(parsed, childName, point, data, cell, shapeType, i));
|
|
482
|
+
const last = vertical ? row === rows - 1 : col === inRow - 1;
|
|
483
|
+
if (trans && !last) out.push(connector(trans, point, {
|
|
484
|
+
x: vertical ? cell.x : cell.x + cellW,
|
|
485
|
+
y: vertical ? cell.y + cellH : cell.y,
|
|
486
|
+
cx: vertical ? cellW : gapW,
|
|
487
|
+
cy: vertical ? gapH : cellH
|
|
488
|
+
}, vertical));
|
|
489
|
+
});
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* §21.4.3.2 — the box a list draws in the gap between two cells.
|
|
494
|
+
*
|
|
495
|
+
* `dgm:shape type="conn"` is not a preset geometry, it is "whatever joins these
|
|
496
|
+
* two", and for a list laid out across that is an arrow pointing the way the
|
|
497
|
+
* list runs. Its own constraint sizes it against the gap it was given — the
|
|
498
|
+
* accent process asks for a height of 62% of its width — and it is centred in
|
|
499
|
+
* what is left.
|
|
500
|
+
*/
|
|
501
|
+
function connector(node, point, gap, vertical) {
|
|
502
|
+
let { cx, cy } = gap;
|
|
503
|
+
for (const k of node.constrs) {
|
|
504
|
+
if (k.forName !== void 0 || k.refType === void 0) continue;
|
|
505
|
+
if (k.type === "h" && k.refType === "w") cy = Math.min(gap.cy, gap.cx * k.fact);
|
|
506
|
+
if (k.type === "w" && k.refType === "h") cx = Math.min(gap.cx, gap.cy * k.fact);
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
point: {
|
|
510
|
+
id: `${point.id}/${node.name}`,
|
|
511
|
+
type: "node"
|
|
512
|
+
},
|
|
513
|
+
shapeType: node.shapeType === "conn" || node.shapeType === void 0 ? vertical ? "downArrow" : "rightArrow" : node.shapeType,
|
|
514
|
+
...styleOf(node),
|
|
515
|
+
...node.styleLbl === void 0 ? { styleLbl: "sibTrans2D1" } : {},
|
|
516
|
+
index: 0,
|
|
517
|
+
x: gap.x + (gap.cx - cx) / 2,
|
|
518
|
+
y: gap.y + (gap.cy - cy) / 2,
|
|
519
|
+
cx,
|
|
520
|
+
cy
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function transitionBox(node) {
|
|
524
|
+
for (const item of node.children) {
|
|
525
|
+
if (item.kind !== "forEach") continue;
|
|
526
|
+
for (const inner of item.children) {
|
|
527
|
+
if (inner.kind !== "forEach" || inner.ptType !== "sibTrans") continue;
|
|
528
|
+
for (const box of inner.children) if (box.kind === "node" && box.shapeType !== void 0 && box.hideGeom !== true) return box;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* §21.4.3 — a `forEach` body of SEVERAL boxes is a stack, not a box.
|
|
534
|
+
*
|
|
535
|
+
* The vertical list family gives each point three: the row holding its label,
|
|
536
|
+
* a negative space, and the box its descendants' words go in. Laying out only
|
|
537
|
+
* the first of them drew the labels and dropped every descendant.
|
|
538
|
+
*
|
|
539
|
+
* What each box states for its height divides the cell between them. A space
|
|
540
|
+
* stated as negative is an overlap — one box pulled up over the one before it —
|
|
541
|
+
* which this engine does not run, so it takes no room and the rest share the
|
|
542
|
+
* cell; LibreOffice draws these layouts without the overlap too.
|
|
543
|
+
*/
|
|
544
|
+
function stackCell(parsed, body, point, data, cell, shapeType, index, vertical) {
|
|
545
|
+
const stated = body.map((b) => stackFact(parsed, b) ?? 0);
|
|
546
|
+
const total = stated.reduce((a, v) => a + Math.max(0, v), 0);
|
|
547
|
+
const shares = stated.map((v) => total > 0 ? Math.max(0, v) / total : 1 / body.length);
|
|
548
|
+
const out = [];
|
|
549
|
+
let at = vertical ? cell.y : cell.x;
|
|
550
|
+
body.forEach((b, k) => {
|
|
551
|
+
const span = (vertical ? cell.cy : cell.cx) * (shares[k] ?? 0);
|
|
552
|
+
if (span > 0 && !(b.alg === "sp" && b.shapeType === void 0)) {
|
|
553
|
+
const slice = {
|
|
554
|
+
x: vertical ? cell.x : at,
|
|
555
|
+
y: vertical ? at : cell.y,
|
|
556
|
+
cx: vertical ? cell.cx : span,
|
|
557
|
+
cy: vertical ? span : cell.cy
|
|
558
|
+
};
|
|
559
|
+
out.push(...splitCell(parsed, b.name, point, data, slice, b.shapeType ?? shapeType, index));
|
|
560
|
+
}
|
|
561
|
+
at += span;
|
|
562
|
+
});
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* The boxes a wrapper INSETS, each at the width it states and where it falls.
|
|
567
|
+
*
|
|
568
|
+
* Widths that add up to the whole are a division and belong to the split; ones
|
|
569
|
+
* that fall short are placements — a margin, then a label of seven tenths, then
|
|
570
|
+
* nothing. Undefined when the boxes do not all state a width, or when they fill
|
|
571
|
+
* the row between them.
|
|
572
|
+
*/
|
|
573
|
+
function insetBoxes(parent, child, width) {
|
|
574
|
+
const boxes = namedBoxes(child);
|
|
575
|
+
if (boxes.length < 2) return void 0;
|
|
576
|
+
const facts = boxes.map((b) => sizeFact(child.constrs, b.name, "w") ?? sizeFact(parent.constrs, b.name, "w"));
|
|
577
|
+
if (facts.some((v) => v === void 0)) return void 0;
|
|
578
|
+
if (facts.reduce((a, v) => a + (v ?? 0), 0) > .999) return void 0;
|
|
579
|
+
const out = [];
|
|
580
|
+
let at = 0;
|
|
581
|
+
boxes.forEach((box, i) => {
|
|
582
|
+
const cx = width * (facts[i] ?? 0);
|
|
583
|
+
out.push({
|
|
584
|
+
box,
|
|
585
|
+
x: at,
|
|
586
|
+
cx
|
|
587
|
+
});
|
|
588
|
+
at += cx;
|
|
589
|
+
});
|
|
590
|
+
return out;
|
|
591
|
+
}
|
|
592
|
+
function stackFact(parsed, box) {
|
|
593
|
+
const own = heightFact(parsed, box.name) ?? sizeFact(parsed.constrs, box.name, "h");
|
|
594
|
+
if (own !== void 0) return own;
|
|
595
|
+
for (const inner of namedBoxes(box)) {
|
|
596
|
+
const f = heightFact(parsed, inner.name) ?? sizeFact(parsed.constrs, inner.name, "h");
|
|
597
|
+
if (f !== void 0) return f;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* One data node's cell, split the way the child layoutNode divides it.
|
|
602
|
+
*
|
|
603
|
+
* A leaf child (`tx` on the node itself) takes the cell whole. A child that
|
|
604
|
+
* nests further hands its own children a share of the cell, and the two
|
|
605
|
+
* families that do differ in which way they cut it:
|
|
606
|
+
*
|
|
607
|
+
* - the `linNode` of the vertical block lists states `w` fractions for its
|
|
608
|
+
* children (0.36 for the label, 0.64 for the descendants' text) — a row;
|
|
609
|
+
* - the `composite` of the picture and colour lists gives both children the
|
|
610
|
+
* full width and states their `h` against a font size (0.8 against 1.22) —
|
|
611
|
+
* a column.
|
|
612
|
+
*
|
|
613
|
+
* Laid out as one box either way, such a cell holds one text where it should
|
|
614
|
+
* hold the node's label and its children's text in boxes of their own.
|
|
615
|
+
*/
|
|
616
|
+
function splitCell(parent, childName, point, data, cell, shapeType, index) {
|
|
617
|
+
const child = findNamed(parent.children, childName);
|
|
618
|
+
const inner = (child ? namedBoxes(child) : []).filter((b) => !(b.alg === "sp" && b.hideGeom === true));
|
|
619
|
+
const size = (box, type) => box === void 0 ? void 0 : fontSize(box.constrs, void 0, type) ?? fontSize(child?.constrs ?? [], box.name, type) ?? fontSize(parent.constrs, box.name, type);
|
|
620
|
+
const face = inner.length === 1 ? inner[0] : child;
|
|
621
|
+
const whole = () => [{
|
|
622
|
+
point: face === void 0 ? point : spokenFor(face, point, data),
|
|
623
|
+
shapeType: face?.shapeType ?? shapeType,
|
|
624
|
+
...face?.styleLbl !== void 0 ? { styleLbl: face.styleLbl } : {},
|
|
625
|
+
...opt("fontSizePt", size(face, "primFontSz")),
|
|
626
|
+
...face !== void 0 && bulleted(face) ? { bulleted: true } : {},
|
|
627
|
+
...face?.hideGeom === true ? { hideGeom: true } : {},
|
|
628
|
+
...face === void 0 ? {} : textPlacing(face),
|
|
629
|
+
index,
|
|
630
|
+
...cell
|
|
631
|
+
}];
|
|
632
|
+
if (child?.alg === "composite" && inner.length > 0 && places(child.constrs)) return compositeLayout(child, point, cell, data, index, parent.constrs);
|
|
633
|
+
const inset = child ? insetBoxes(parent, child, cell.cx) : void 0;
|
|
634
|
+
if (child && inset) {
|
|
635
|
+
const out = [];
|
|
636
|
+
for (const { box, x, cx } of inset) {
|
|
637
|
+
if (box.alg === "sp" && box.hideGeom === true) continue;
|
|
638
|
+
out.push(...splitCell(parent, box.name, point, data, {
|
|
639
|
+
...cell,
|
|
640
|
+
x: cell.x + x,
|
|
641
|
+
cx
|
|
642
|
+
}, box.shapeType ?? shapeType, index));
|
|
643
|
+
}
|
|
644
|
+
if (out.length > 0) return out;
|
|
645
|
+
}
|
|
646
|
+
if (!child || inner.length < 2) return whole();
|
|
647
|
+
const widths = inner.map((b) => sizeFact(child.constrs, b.name, "w"));
|
|
648
|
+
const across = widths.some((v) => v !== void 0 && v < .99);
|
|
649
|
+
const share = widths.reduce((a, v) => a + (v ?? 0), 0);
|
|
650
|
+
if (inner.length > 2 && !(across && widths.every((v) => v !== void 0) && Math.abs(share - 1) < .02)) return whole();
|
|
651
|
+
const kids = data.children(point.id, "node");
|
|
652
|
+
const shares = across ? fractions(widths) : fractions(inner.map((b) => sizeFact(child.constrs, b.name, "h") ?? heightFact(parent, b.name)));
|
|
653
|
+
const cap = inner.length === 2 ? heightCap(inner[0], across ? cell.cx * (shares[0] ?? 1) : cell.cx) : void 0;
|
|
654
|
+
if (!across && cap !== void 0) {
|
|
655
|
+
shares[0] = Math.min(cell.cy * (shares[0] ?? .5), cap) / cell.cy;
|
|
656
|
+
shares[1] = 1 - shares[0];
|
|
657
|
+
}
|
|
658
|
+
const out = [];
|
|
659
|
+
let at = across ? cell.x : cell.y;
|
|
660
|
+
inner.forEach((box, i) => {
|
|
661
|
+
const span = (across ? cell.cx : cell.cy) * (shares[i] ?? .5);
|
|
662
|
+
const spacer = box.alg === "sp" && box.shapeType === void 0;
|
|
663
|
+
if (span > 0 && !spacer && !(box.needsChildren && kids.length === 0)) out.push({
|
|
664
|
+
point: box.alg === "sp" ? {
|
|
665
|
+
id: `${point.id}/${box.name}`,
|
|
666
|
+
type: "node"
|
|
667
|
+
} : box.presOfAxis === void 0 ? i === 0 ? point : gatheredText(point, kids) : spokenFor(box, point, data),
|
|
668
|
+
shapeType: i === 0 ? shapeType : box.shapeType ?? "rect",
|
|
669
|
+
...box.styleLbl !== void 0 ? { styleLbl: box.styleLbl } : {},
|
|
670
|
+
...opt("fontSizePt", size(box, i === 0 ? "primFontSz" : "secFontSz")),
|
|
671
|
+
...bulleted(box) ? { bulleted: true } : {},
|
|
672
|
+
...box.hideGeom === true ? { hideGeom: true } : {},
|
|
673
|
+
...textPlacing(box),
|
|
674
|
+
index,
|
|
675
|
+
x: across ? at : cell.x,
|
|
676
|
+
y: across ? cell.y : at,
|
|
677
|
+
cx: across ? span : cell.cx,
|
|
678
|
+
cy: across ? cell.cy : span
|
|
679
|
+
});
|
|
680
|
+
at += span;
|
|
681
|
+
});
|
|
682
|
+
return out.length > 0 ? out : whole();
|
|
683
|
+
}
|
|
684
|
+
function fractions(facts) {
|
|
685
|
+
const total = facts.reduce((a, b) => a + (b ?? 0), 0);
|
|
686
|
+
if (total > 1.001) return facts.map((v) => (v ?? 0) / total);
|
|
687
|
+
const unstated = facts.filter((v) => v === void 0).length;
|
|
688
|
+
const rest = Math.max(0, 1 - total) / Math.max(1, unstated);
|
|
689
|
+
return facts.map((v) => v ?? rest);
|
|
690
|
+
}
|
|
691
|
+
function gatheredText(parent, kids) {
|
|
692
|
+
const paragraphs = kids.flatMap((k) => k.text ? poChildren(k.text) : []);
|
|
693
|
+
if (paragraphs.length === 0) return {
|
|
694
|
+
id: `${parent.id}/des`,
|
|
695
|
+
type: "node"
|
|
696
|
+
};
|
|
697
|
+
return {
|
|
698
|
+
id: `${parent.id}/des`,
|
|
699
|
+
type: "node",
|
|
700
|
+
text: { "dgm:t": paragraphs }
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
function bestColumns(n, cx, cy, aspect, gap) {
|
|
704
|
+
if (n <= 1) return 1;
|
|
705
|
+
let best = n;
|
|
706
|
+
let bestErr = Infinity;
|
|
707
|
+
for (let cols = 1; cols <= n; cols++) {
|
|
708
|
+
const rows = Math.ceil(n / cols);
|
|
709
|
+
const w = cx / (cols + Math.max(0, cols - 1) * gap);
|
|
710
|
+
const h = cy / (rows + Math.max(0, rows - 1) * gap);
|
|
711
|
+
const err = Math.abs(Math.log(h / w / (aspect || 1)));
|
|
712
|
+
if (err < bestErr - 1e-9) {
|
|
713
|
+
bestErr = err;
|
|
714
|
+
best = cols;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return best;
|
|
718
|
+
}
|
|
719
|
+
function findNamed(items, name) {
|
|
720
|
+
for (const item of items) if (item.kind === "node") {
|
|
721
|
+
if (item.name === name) return item;
|
|
722
|
+
const inner = findNamed(item.children, name);
|
|
723
|
+
if (inner) return inner;
|
|
724
|
+
} else {
|
|
725
|
+
const inner = findNamed(item.children, name);
|
|
726
|
+
if (inner) return inner;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
function namedBoxes(node) {
|
|
730
|
+
const out = [];
|
|
731
|
+
for (const item of node.children) if (item.kind === "node") out.push(item);
|
|
732
|
+
else for (const inner of item.children) if (inner.kind === "node") out.push(inner);
|
|
733
|
+
return out;
|
|
734
|
+
}
|
|
735
|
+
function sizeFact(constrs, name, type) {
|
|
736
|
+
for (const k of constrs) if (k.type === type && k.forName === name && k.refType === type && k.refForName === void 0) return k.fact;
|
|
737
|
+
}
|
|
738
|
+
function fontSize(constrs, name, type) {
|
|
739
|
+
for (const k of constrs) {
|
|
740
|
+
if (k.type !== type || k.forName !== name) continue;
|
|
741
|
+
if (k.val !== void 0) return k.val;
|
|
742
|
+
if (k.refType === "primFontSz" && k.refForName !== void 0) {
|
|
743
|
+
const from = fontSize(constrs, k.refForName, "primFontSz");
|
|
744
|
+
if (from !== void 0) return from * (k.fact || 1);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return type === "secFontSz" ? fontSize(constrs, name, "primFontSz") : void 0;
|
|
748
|
+
}
|
|
749
|
+
function bulleted(node) {
|
|
750
|
+
return Number(node.algParams.get("stBulletLvl") ?? "0") >= 1;
|
|
751
|
+
}
|
|
752
|
+
function opt(name, v) {
|
|
753
|
+
return v === void 0 ? {} : { [name]: v };
|
|
754
|
+
}
|
|
755
|
+
function heightCap(box, widthEmu) {
|
|
756
|
+
for (const k of box?.constrs ?? []) if (k.type === "h" && k.refType === "w" && k.op === "lte") return widthEmu * k.fact;
|
|
757
|
+
}
|
|
758
|
+
function heightFact(parent, name) {
|
|
759
|
+
for (const k of parent.constrs) if (k.type === "h" && k.forName === name && k.refType === "primFontSz") return k.fact;
|
|
760
|
+
}
|
|
761
|
+
function resolve(constrs, childName) {
|
|
762
|
+
let aspect = .6;
|
|
763
|
+
let gap = .1;
|
|
764
|
+
for (const k of constrs) {
|
|
765
|
+
if (k.forName === childName && k.type === "h" && k.refType === "w") aspect = k.fact;
|
|
766
|
+
if (k.type === "w" && k.refForName === childName && k.forName !== childName) gap = k.fact;
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
aspect,
|
|
770
|
+
gap
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function isVertical(node) {
|
|
774
|
+
if (node.algParams.get("flowDir") === "col") return true;
|
|
775
|
+
const dir = node.algParams.get("linDir");
|
|
776
|
+
return dir === "fromT" || dir === "fromB";
|
|
777
|
+
}
|
|
778
|
+
function firstNodeName(items) {
|
|
779
|
+
for (const item of items) {
|
|
780
|
+
if (item.kind === "node") return item.name;
|
|
781
|
+
const inner = firstNodeName(item.children);
|
|
782
|
+
if (inner !== void 0) return inner;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
function childShapeType(items) {
|
|
786
|
+
for (const item of items) {
|
|
787
|
+
if (item.kind === "node") return item.shapeType ?? childShapeType(item.children);
|
|
788
|
+
const inner = childShapeType(item.children);
|
|
789
|
+
if (inner !== void 0) return inner;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* §21.4.3.3 — a `choose` picks one branch by a function of the data, and what
|
|
794
|
+
* the branch holds belongs to whatever the `choose` was standing in. The
|
|
795
|
+
* direction variables are left at their defaults, so those take the first
|
|
796
|
+
* `if`; `maxDepth` is answered from the model, because the branch it guards is
|
|
797
|
+
* the difference between one box in a cell and two.
|
|
798
|
+
*
|
|
799
|
+
* @returns The children with every `choose` replaced by the branch taken, and
|
|
800
|
+
* the ones whose branch was guarded on a child count.
|
|
801
|
+
*/
|
|
802
|
+
function flattenChoose(kids, ctx) {
|
|
803
|
+
const flat = [];
|
|
804
|
+
const guarded = /* @__PURE__ */ new Set();
|
|
805
|
+
for (const k of kids) {
|
|
806
|
+
if (!poIs(k, "dgm:choose")) {
|
|
807
|
+
flat.push(k);
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const branches = poChildren(k);
|
|
811
|
+
const taken = branches.find((b) => poIs(b, "dgm:if") && holds(b, ctx)) ?? branches.find((b) => poIs(b, "dgm:else")) ?? branches[0];
|
|
812
|
+
const onCount = taken !== void 0 && poIs(taken, "dgm:if") && countsChildren(taken);
|
|
813
|
+
const inner = flattenChoose(poChildren(taken), ctx);
|
|
814
|
+
for (const item of inner.flat) {
|
|
815
|
+
flat.push(item);
|
|
816
|
+
if (onCount || inner.guarded.has(item)) guarded.add(item);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
flat,
|
|
821
|
+
guarded
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function parseNode(node, ctx) {
|
|
825
|
+
const { flat: flattened, guarded } = flattenChoose(poChildren(node), ctx);
|
|
826
|
+
const alg = flattened.find((k) => poIs(k, "dgm:alg"));
|
|
827
|
+
const shape = flattened.find((k) => poIs(k, "dgm:shape"));
|
|
828
|
+
const constrLst = flattened.find((k) => poIs(k, "dgm:constrLst"));
|
|
829
|
+
const params = /* @__PURE__ */ new Map();
|
|
830
|
+
for (const p of alg ? poChildren(alg) : []) {
|
|
831
|
+
const t = poAttr(p, "type");
|
|
832
|
+
const v = poAttr(p, "val");
|
|
833
|
+
if (t !== void 0 && v !== void 0) params.set(t, v);
|
|
834
|
+
}
|
|
835
|
+
const shapeType = shape ? poAttr(shape, "type") : void 0;
|
|
836
|
+
const styleLbl = poAttr(node, "styleLbl");
|
|
837
|
+
const presOf = flattened.find((k) => poIs(k, "dgm:presOf"));
|
|
838
|
+
const presOfAxis = poAttr(presOf, "axis");
|
|
839
|
+
const presOfPath = axisPath(presOf);
|
|
840
|
+
const shapeRot = shape ? Number(poAttr(shape, "rot") ?? "") : NaN;
|
|
841
|
+
return {
|
|
842
|
+
kind: "node",
|
|
843
|
+
name: poAttr(node, "name") ?? "",
|
|
844
|
+
...styleLbl !== void 0 && styleLbl !== "" ? { styleLbl } : {},
|
|
845
|
+
needsChildren: false,
|
|
846
|
+
alg: algOf(alg),
|
|
847
|
+
algParams: params,
|
|
848
|
+
...shapeType !== void 0 && shapeType !== "" ? { shapeType } : {},
|
|
849
|
+
...shape !== void 0 && poAttr(shape, "hideGeom") === "1" ? { hideGeom: true } : {},
|
|
850
|
+
...Number.isFinite(shapeRot) && shapeRot !== 0 ? { shapeRotation: shapeRot } : {},
|
|
851
|
+
...presOfAxis !== void 0 && presOfAxis !== "" ? { presOfAxis } : {},
|
|
852
|
+
...presOfPath ? { presOfPath } : {},
|
|
853
|
+
constrs: parseConstraints(constrLst),
|
|
854
|
+
children: flattened.flatMap((k) => parseItem(k, ctx, guarded.has(k)))
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
function holds(branch, ctx) {
|
|
858
|
+
const func = poAttr(branch, "func");
|
|
859
|
+
const axis = (poAttr(branch, "axis") ?? "").split(/\s+/u).filter((a) => a !== "");
|
|
860
|
+
if (func === "maxDepth") return compare(ctx.maxDepth, branch, "gte");
|
|
861
|
+
if (func === "cnt" && axis.length > 1) return compare(countAt(axis, branch, ctx), branch, "gte");
|
|
862
|
+
return true;
|
|
863
|
+
}
|
|
864
|
+
function countAt(axis, branch, ctx) {
|
|
865
|
+
const nums = (name) => (poAttr(branch, name) ?? "").split(/\s+/u).map((v) => Number(v));
|
|
866
|
+
const st = nums("st");
|
|
867
|
+
const cnt = nums("cnt");
|
|
868
|
+
const types = (poAttr(branch, "ptType") ?? "").split(/\s+/u);
|
|
869
|
+
let at = [ctx.root];
|
|
870
|
+
axis.forEach((step, i) => {
|
|
871
|
+
if (step !== "ch" && step !== "des") {
|
|
872
|
+
at = [];
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if ((types[i] === "node" || types[i] === void 0 ? "node" : void 0) === void 0) {
|
|
876
|
+
at = [];
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
const next = at.flatMap((p) => ctx.data.children(p.id, "node"));
|
|
880
|
+
const from = Math.max(0, (st[i] ?? 1) - 1);
|
|
881
|
+
const want = cnt[i];
|
|
882
|
+
const take = want === void 0 || want === 0 ? next.length : want;
|
|
883
|
+
at = next.slice(from, from + take);
|
|
884
|
+
});
|
|
885
|
+
return at.length;
|
|
886
|
+
}
|
|
887
|
+
function compare(actual, branch, fallback) {
|
|
888
|
+
const val = Number(poAttr(branch, "val") ?? "0");
|
|
889
|
+
const op = poAttr(branch, "op") ?? fallback;
|
|
890
|
+
if (op === "gte") return actual >= val;
|
|
891
|
+
if (op === "gt") return actual > val;
|
|
892
|
+
if (op === "lte") return actual <= val;
|
|
893
|
+
if (op === "lt") return actual < val;
|
|
894
|
+
if (op === "neq") return actual !== val;
|
|
895
|
+
return actual === val;
|
|
896
|
+
}
|
|
897
|
+
function countsChildren(branch) {
|
|
898
|
+
return poAttr(branch, "func") === "cnt" && poAttr(branch, "axis") === "ch" && poAttr(branch, "ptType") === "node" && (poAttr(branch, "op") ?? "gte") === "gte" && Number(poAttr(branch, "val") ?? "1") >= 1;
|
|
899
|
+
}
|
|
900
|
+
function parseItem(node, ctx, guarded = false) {
|
|
901
|
+
if (poIs(node, "dgm:layoutNode")) return [{
|
|
902
|
+
...parseNode(node, ctx),
|
|
903
|
+
needsChildren: guarded
|
|
904
|
+
}];
|
|
905
|
+
if (poIs(node, "dgm:forEach")) {
|
|
906
|
+
const inner = flattenChoose(poChildren(node), ctx);
|
|
907
|
+
return [{
|
|
908
|
+
kind: "forEach",
|
|
909
|
+
axis: poAttr(node, "axis") ?? "",
|
|
910
|
+
ptType: poAttr(node, "ptType") ?? "",
|
|
911
|
+
children: inner.flat.flatMap((k) => parseItem(k, ctx, guarded || inner.guarded.has(k)))
|
|
912
|
+
}];
|
|
913
|
+
}
|
|
914
|
+
return [];
|
|
915
|
+
}
|
|
916
|
+
function parseConstraints(list) {
|
|
917
|
+
const out = [];
|
|
918
|
+
for (const k of list ? poChildren(list) : []) {
|
|
919
|
+
if (!poIs(k, "dgm:constr")) continue;
|
|
920
|
+
const type = poAttr(k, "type");
|
|
921
|
+
if (type === void 0) continue;
|
|
922
|
+
const forName = poAttr(k, "forName");
|
|
923
|
+
const op = poAttr(k, "op");
|
|
924
|
+
const refType = poAttr(k, "refType");
|
|
925
|
+
const refForName = poAttr(k, "refForName");
|
|
926
|
+
const val = poAttr(k, "val");
|
|
927
|
+
out.push({
|
|
928
|
+
type,
|
|
929
|
+
...forName !== void 0 ? { forName } : {},
|
|
930
|
+
...op !== void 0 ? { op } : {},
|
|
931
|
+
...refType !== void 0 ? { refType } : {},
|
|
932
|
+
...refForName !== void 0 ? { refForName } : {},
|
|
933
|
+
fact: Number(poAttr(k, "fact") ?? "1"),
|
|
934
|
+
...val !== void 0 ? { val: Number(val) } : {}
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
return out;
|
|
938
|
+
}
|
|
939
|
+
function algOf(alg) {
|
|
940
|
+
const t = alg ? poAttr(alg, "type") : void 0;
|
|
941
|
+
if (t === "hierRoot" || t === "hierChild") return "hier";
|
|
942
|
+
return t === "snake" || t === "lin" || t === "sp" || t === "tx" || t === "composite" || t === "cycle" ? t : "other";
|
|
943
|
+
}
|
|
944
|
+
function findChild(nodes, tag) {
|
|
945
|
+
return nodes.find((n) => poIs(n, tag) || poTag(n) === tag);
|
|
946
|
+
}
|
|
947
|
+
//#endregion
|
|
948
|
+
export { layoutDiagram };
|