postext 1.49.0 → 1.50.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/__tests__/composed/emptyTabFrame.test.d.ts +2 -0
- package/dist/__tests__/composed/emptyTabFrame.test.d.ts.map +1 -0
- package/dist/__tests__/composed/emptyTabFrame.test.js +122 -0
- package/dist/__tests__/composed/emptyTabFrame.test.js.map +1 -0
- package/dist/__tests__/composed/fenceCondition.test.d.ts +2 -0
- package/dist/__tests__/composed/fenceCondition.test.d.ts.map +1 -0
- package/dist/__tests__/composed/fenceCondition.test.js +171 -0
- package/dist/__tests__/composed/fenceCondition.test.js.map +1 -0
- package/dist/__tests__/composed/fixtures/moduleRows.d.ts +40 -0
- package/dist/__tests__/composed/fixtures/moduleRows.d.ts.map +1 -0
- package/dist/__tests__/composed/fixtures/moduleRows.js +65 -0
- package/dist/__tests__/composed/fixtures/moduleRows.js.map +1 -0
- package/dist/__tests__/composed/frameLooks.test.d.ts +2 -0
- package/dist/__tests__/composed/frameLooks.test.d.ts.map +1 -0
- package/dist/__tests__/composed/frameLooks.test.js +151 -0
- package/dist/__tests__/composed/frameLooks.test.js.map +1 -0
- package/dist/__tests__/composed/moduleRows.test.d.ts +2 -0
- package/dist/__tests__/composed/moduleRows.test.d.ts.map +1 -0
- package/dist/__tests__/composed/moduleRows.test.js +233 -0
- package/dist/__tests__/composed/moduleRows.test.js.map +1 -0
- package/dist/__tests__/composed/modules.test.js +3 -2
- package/dist/__tests__/composed/modules.test.js.map +1 -1
- package/dist/__tests__/composed/pillWord.test.d.ts +2 -0
- package/dist/__tests__/composed/pillWord.test.d.ts.map +1 -0
- package/dist/__tests__/composed/pillWord.test.js +128 -0
- package/dist/__tests__/composed/pillWord.test.js.map +1 -0
- package/dist/__tests__/exports.test.js +8 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/composed/modules.d.ts.map +1 -1
- package/dist/composed/modules.js +9 -2
- package/dist/composed/modules.js.map +1 -1
- package/dist/composed/reflowLog.js +1 -1
- package/dist/composed/reflowLog.js.map +1 -1
- package/dist/composed/slotChecks.d.ts +10 -0
- package/dist/composed/slotChecks.d.ts.map +1 -1
- package/dist/composed/slotChecks.js +39 -0
- package/dist/composed/slotChecks.js.map +1 -1
- package/dist/composed/templates.d.ts +14 -1
- package/dist/composed/templates.d.ts.map +1 -1
- package/dist/composed/templates.js +73 -5
- package/dist/composed/templates.js.map +1 -1
- package/dist/composed/types.d.ts +55 -4
- package/dist/composed/types.d.ts.map +1 -1
- package/dist/composed/validate.d.ts.map +1 -1
- package/dist/composed/validate.js +63 -6
- package/dist/composed/validate.js.map +1 -1
- package/dist/composed/wrap.d.ts.map +1 -1
- package/dist/composed/wrap.js +22 -5
- package/dist/composed/wrap.js.map +1 -1
- package/dist/configWarnings.d.ts +3 -1
- package/dist/configWarnings.d.ts.map +1 -1
- package/dist/configWarnings.js +32 -3
- package/dist/configWarnings.js.map +1 -1
- package/dist/defaults/headerFooter.d.ts +10 -0
- package/dist/defaults/headerFooter.d.ts.map +1 -1
- package/dist/defaults/headerFooter.js +21 -1
- package/dist/defaults/headerFooter.js.map +1 -1
- package/dist/design/fenceCondition.d.ts +45 -0
- package/dist/design/fenceCondition.d.ts.map +1 -0
- package/dist/design/fenceCondition.js +66 -0
- package/dist/design/fenceCondition.js.map +1 -0
- package/dist/design/layout.d.ts.map +1 -1
- package/dist/design/layout.js +33 -1
- package/dist/design/layout.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/pipeline/composedLayout.d.ts +5 -1
- package/dist/pipeline/composedLayout.d.ts.map +1 -1
- package/dist/pipeline/composedLayout.js +309 -84
- package/dist/pipeline/composedLayout.js.map +1 -1
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js +8 -1
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/contentWarnings.d.ts.map +1 -1
- package/dist/pipeline/contentWarnings.js +8 -1
- package/dist/pipeline/contentWarnings.js.map +1 -1
- package/dist/types.d.ts +68 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +21 -2
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js.map +1 -1
- package/package.json +1 -1
|
@@ -41,7 +41,7 @@ import { chainHead, FRAME_ATTR } from '../composed/wrap';
|
|
|
41
41
|
import { framePlaceable, nextFrameOf, groupPlaceable, placeBox, unionRect } from '../composed/geometry';
|
|
42
42
|
import { composedOrder, groupOfFrames, layoutGroup } from '../composed/groups';
|
|
43
43
|
import { reflowEvents } from '../composed/reflowLog';
|
|
44
|
-
import { anchorTarget, repeatInstance, repeatedFrames, resolveContinueIn } from '../composed/templates';
|
|
44
|
+
import { anchorTarget, lookOver, repeatInstance, repeatedFrames, resolveContinueIn } from '../composed/templates';
|
|
45
45
|
import { collapsedOnAddedPage, collapseFrames, continuationTemplate, continuesCollapsed, continuesOffPage, trailingFrames } from '../composed/continuation';
|
|
46
46
|
import { isModuleTemplate, resolvePageTemplate } from '../composed/templates';
|
|
47
47
|
import { frameElementId } from '../composed/ids';
|
|
@@ -224,6 +224,20 @@ export function layoutComposedPage(asked) {
|
|
|
224
224
|
};
|
|
225
225
|
/** The frame's content does not fit, or it squeezes the frames after it. */
|
|
226
226
|
const short = (c, name) => overflows(c, name) || squeezes(c, name);
|
|
227
|
+
/** A line of the frame holds nothing but the head of a word divided
|
|
228
|
+
* because it was wider than the measure (#1084): a hyphenated line with
|
|
229
|
+
* no space in it. */
|
|
230
|
+
const dividesWord = (c, name) => {
|
|
231
|
+
const ids = new Set(frameIn(c, name)?.blockIds ?? []);
|
|
232
|
+
for (const b of c.blocks) {
|
|
233
|
+
if (!ids.has(b.id))
|
|
234
|
+
continue;
|
|
235
|
+
for (const l of b.lines ?? [])
|
|
236
|
+
if (l.hyphenated && !/\s/.test(l.text.trim()))
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
};
|
|
227
241
|
/** The ladder's last step (#859): the frame is cut at its foot and the
|
|
228
242
|
* rest goes on on another page, its text and pictures back at full
|
|
229
243
|
* size (they go on anyway). */
|
|
@@ -339,10 +353,51 @@ export function layoutComposedPage(asked) {
|
|
|
339
353
|
const min = frame.minHeight && dimensionToPx(frame.minHeight, dpi, input.emPx) > dimensionToPx(h, dpi, input.emPx) ? frame.minHeight : h;
|
|
340
354
|
return { height: 'auto', minHeight: min };
|
|
341
355
|
};
|
|
356
|
+
/** The frame's text may be set smaller (`fit.shrinkText`, a
|
|
357
|
+
* `minScale` under 1) and it holds text. */
|
|
358
|
+
const textShrinks = (frame) => frame.fit.shrinkText && frame.fit.minScale < 1 && holdsText(frame);
|
|
359
|
+
/** The frame divides a word wider than its measure (#1084): a smaller
|
|
360
|
+
* scale than the one its text has, down to `minScale`, that sets the
|
|
361
|
+
* word whole and still fits wins; the largest such. When none does the
|
|
362
|
+
* word stays divided: dividing is the last resort. */
|
|
363
|
+
const wordWhole = (frame) => {
|
|
364
|
+
const name = frame.name;
|
|
365
|
+
if (runs >= COMPOSE_BUDGET || !dividesWord(cur, name))
|
|
366
|
+
return;
|
|
367
|
+
const head = headOf(name);
|
|
368
|
+
const now = fit.text.get(head) ?? 1;
|
|
369
|
+
const scales = scaleSteps(frame.fit.minScale, now);
|
|
370
|
+
if (scales.length === 0)
|
|
371
|
+
return;
|
|
372
|
+
const make = (s) => withText(fit, head, s);
|
|
373
|
+
const whole = (c) => !short(c, name) && !dividesWord(c, name);
|
|
374
|
+
if (!whole(compose(make(scales[scales.length - 1]))))
|
|
375
|
+
return;
|
|
376
|
+
let lo = 0;
|
|
377
|
+
let hi = scales.length - 1; // whole at hi
|
|
378
|
+
while (lo < hi && runs < COMPOSE_BUDGET) {
|
|
379
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
380
|
+
if (whole(compose(make(scales[mid]))))
|
|
381
|
+
hi = mid;
|
|
382
|
+
else
|
|
383
|
+
lo = mid + 1;
|
|
384
|
+
}
|
|
385
|
+
fit = make(scales[hi]);
|
|
386
|
+
cur = compose(fit);
|
|
387
|
+
};
|
|
342
388
|
for (const frame of template.frames) {
|
|
343
389
|
const name = frame.name;
|
|
344
|
-
if (
|
|
390
|
+
if (runs >= COMPOSE_BUDGET)
|
|
345
391
|
continue;
|
|
392
|
+
if (!short(cur, name)) {
|
|
393
|
+
// A frame that fits, held to its height (a fixed one, or a
|
|
394
|
+
// `maxHeight`), may still divide a word wider than its measure
|
|
395
|
+
// (#1084): a one-line pill whose word went over two lines that its
|
|
396
|
+
// height holds. Its text is set smaller first.
|
|
397
|
+
if ((fixedHeight(frame) || frame.maxHeight) && textShrinks(frame))
|
|
398
|
+
wordWhole(frame);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
346
401
|
const steps = [];
|
|
347
402
|
tried.set(name, steps);
|
|
348
403
|
// 1. Free space: a fixed height grows to its maximum ('auto' heights
|
|
@@ -375,10 +430,15 @@ export function layoutComposedPage(asked) {
|
|
|
375
430
|
};
|
|
376
431
|
// 5. The frame's text (its whole story), down to fit.minScale.
|
|
377
432
|
const shrinkText = () => {
|
|
378
|
-
if (!
|
|
433
|
+
if (!textShrinks(frame))
|
|
379
434
|
return false;
|
|
380
435
|
steps.push('shrinkText');
|
|
381
|
-
|
|
436
|
+
const scales = scaleSteps(frame.fit.minScale);
|
|
437
|
+
const make = (s) => withText(fit, headOf(name), s);
|
|
438
|
+
if (!searchScale(scales, make, name))
|
|
439
|
+
return false;
|
|
440
|
+
wordWhole(frame);
|
|
441
|
+
return true;
|
|
382
442
|
};
|
|
383
443
|
// 6. Continuation on another page (`overflow.continueIn: 'next-page'`,
|
|
384
444
|
// `'template:<id>'` or `'page:<id>'`, #859). On a page added by
|
|
@@ -460,6 +520,7 @@ export function layoutComposedPage(asked) {
|
|
|
460
520
|
templateSource: templateAttr ? { sourceStart: templateAttr.sourceStart + input.bodyOffset, sourceEnd: templateAttr.sourceEnd + input.bodyOffset } : undefined,
|
|
461
521
|
budgetLeft: () => runs < COMPOSE_BUDGET,
|
|
462
522
|
holdsText: holdsText(frame),
|
|
523
|
+
dividesWord,
|
|
463
524
|
});
|
|
464
525
|
warnings.push({
|
|
465
526
|
kind: 'frameOverflow',
|
|
@@ -770,24 +831,51 @@ function overflowProposals(c) {
|
|
|
770
831
|
const path = (key) => frame.fieldPaths[key] ?? `${frame.configPath}/${key}`;
|
|
771
832
|
const fits = (next, tpl) => c.budgetLeft() && !c.overflows(c.compose(next, tpl), name);
|
|
772
833
|
// More shrink: the largest text scale, in 2.5 % steps, at which it fits.
|
|
834
|
+
// When that scale divides a word wider than the frame's measure, the
|
|
835
|
+
// largest smaller one that fits with every word whole, as the ladder
|
|
836
|
+
// prefers (#1084, #1090); none, and the largest that fits, saying the
|
|
837
|
+
// word will be divided.
|
|
773
838
|
const head = c.headOf(name);
|
|
774
839
|
const from = frame.fit.shrinkText ? Math.min(frame.fit.minScale, vdt.scale) : 1;
|
|
775
840
|
const steps = scaleSteps(PROPOSAL_MIN_SCALE, from);
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
841
|
+
const shrunk = (i) => c.withText(fit, head, steps[i]);
|
|
842
|
+
if (c.holdsText && steps.length > 0 && fits(shrunk(steps.length - 1))) {
|
|
843
|
+
const last = steps.length - 1;
|
|
844
|
+
/** The largest step from `lo` down at which `ok` holds, given it holds
|
|
845
|
+
* at the last one: by bisection. */
|
|
846
|
+
const largest = (lo, ok) => {
|
|
847
|
+
let hi = last;
|
|
848
|
+
while (lo < hi && c.budgetLeft()) {
|
|
849
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
850
|
+
if (ok(mid))
|
|
851
|
+
hi = mid;
|
|
852
|
+
else
|
|
853
|
+
lo = mid + 1;
|
|
854
|
+
}
|
|
855
|
+
return hi;
|
|
856
|
+
};
|
|
857
|
+
let at = largest(0, (i) => fits(shrunk(i)));
|
|
858
|
+
let word;
|
|
859
|
+
if (c.budgetLeft() && c.dividesWord(c.compose(shrunk(at)), name)) {
|
|
860
|
+
const whole = (i) => {
|
|
861
|
+
if (!c.budgetLeft())
|
|
862
|
+
return false;
|
|
863
|
+
const comp = c.compose(shrunk(i));
|
|
864
|
+
return !c.overflows(comp, name) && !c.dividesWord(comp, name);
|
|
865
|
+
};
|
|
866
|
+
if (at < last && whole(last)) {
|
|
867
|
+
at = largest(at + 1, whole);
|
|
868
|
+
word = 'whole';
|
|
869
|
+
}
|
|
783
870
|
else
|
|
784
|
-
|
|
871
|
+
word = 'divided';
|
|
785
872
|
}
|
|
786
873
|
out.push({
|
|
787
874
|
kind: 'allowShrink',
|
|
788
875
|
path: path('fit/minScale'),
|
|
789
|
-
value: steps[
|
|
876
|
+
value: steps[at],
|
|
790
877
|
...(frame.fit.shrinkText ? {} : { also: [{ path: path('fit/shrinkText'), value: true }] }),
|
|
878
|
+
...(word ? { word } : {}),
|
|
791
879
|
});
|
|
792
880
|
}
|
|
793
881
|
// A frame that holds content (an empty one is short of its own box, #911:
|
|
@@ -931,9 +1019,19 @@ function composePage(pageInput, template, fit, collapsed) {
|
|
|
931
1019
|
* numbered (`VDTDesignTextBlock.pageMark`). */
|
|
932
1020
|
const toGuess = String(input.page.index + 2);
|
|
933
1021
|
const fromGuess = String(Math.max(1, input.page.index));
|
|
1022
|
+
/** The named look (#1089) the `:::frame` fence of a frame's story names
|
|
1023
|
+
* (`look="<id>"`), when `frameLooks` has it: the story's, so every
|
|
1024
|
+
* frame of a chain and the frames it goes on in on later pages wear it. */
|
|
1025
|
+
const frameLooks = pass.resolved.frameLooks;
|
|
1026
|
+
const namedLookOf = (frame) => {
|
|
1027
|
+
if (!frameLooks)
|
|
1028
|
+
return undefined;
|
|
1029
|
+
const id = input.stories.get(headOf(frame.name))?.plan.attrs.look?.trim();
|
|
1030
|
+
return id ? frameLooks.find((l) => l.id === id) : undefined;
|
|
1031
|
+
};
|
|
934
1032
|
/** The style a frame is laid out in: its box as drawn at any text scale. */
|
|
935
1033
|
const styleOf = (frame) => {
|
|
936
|
-
const style = keepChromeAt(input.styleOf(frame), textScale(frame));
|
|
1034
|
+
const style = keepChromeAt(input.styleOf(withNamedLook(frame, namedLookOf(frame))), textScale(frame));
|
|
937
1035
|
if (!fit.goOn.has(frame.name))
|
|
938
1036
|
return style;
|
|
939
1037
|
// A frame cut for another page (#859) ends with its mark: its own
|
|
@@ -1625,8 +1723,9 @@ function composePage(pageInput, template, fit, collapsed) {
|
|
|
1625
1723
|
}
|
|
1626
1724
|
}
|
|
1627
1725
|
else if (st && frame.style) {
|
|
1628
|
-
// A styled frame with nothing (left) to hold still draws its box
|
|
1629
|
-
|
|
1726
|
+
// A styled frame with nothing (left) to hold still draws its box;
|
|
1727
|
+
// an empty story's own frame (#1084) its tab and title too.
|
|
1728
|
+
result = inMode(frame, () => st.L.layoutRange(st.L.end, st.L.end, laidW, blockId, continuation, mirroredIn(frame), { boxHeight: laidH }));
|
|
1630
1729
|
}
|
|
1631
1730
|
// A frame cut below its least height overflows by the cut, holding
|
|
1632
1731
|
// content or not (#911).
|
|
@@ -1807,26 +1906,102 @@ function composePage(pageInput, template, fit, collapsed) {
|
|
|
1807
1906
|
fill(prepare(f), r);
|
|
1808
1907
|
}
|
|
1809
1908
|
};
|
|
1810
|
-
/** Lay a modules group (#1062):
|
|
1811
|
-
*
|
|
1812
|
-
*
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
1815
|
-
*
|
|
1816
|
-
*
|
|
1817
|
-
*
|
|
1818
|
-
*
|
|
1819
|
-
*
|
|
1820
|
-
*
|
|
1821
|
-
*
|
|
1822
|
-
*
|
|
1909
|
+
/** Lay a modules group (#1062): its modules in rows down the group's
|
|
1910
|
+
* box (its own, else the area). A stack gives each module a row of
|
|
1911
|
+
* its own, as wide as the group (or its template's `width`, #1088), a
|
|
1912
|
+
* `'row'` puts them all in one, start to end (each its `width`, else
|
|
1913
|
+
* an equal share of what is left), a `'grid'` `columns` to a row in
|
|
1914
|
+
* equal columns; a run of modules a group inside the stack takes
|
|
1915
|
+
* (`within`) is laid as that group says where its first module comes.
|
|
1916
|
+
* Rows run from the start side of the page's lines (the flow frame is
|
|
1917
|
+
* mirrored on a right-to-left page, so its left is the start). A
|
|
1918
|
+
* module is placed as its template draws its frames in its box
|
|
1919
|
+
* (`'container'` is the box) and its groups laid in it: first in the
|
|
1920
|
+
* room left, to find how tall it is (the frames held by the box's foot
|
|
1921
|
+
* or middle, or sized by its height, left out), then in a box that
|
|
1922
|
+
* tall, or (`rowAlign` `'stretch'`, a row of several) as tall as its
|
|
1923
|
+
* row. A row that does not fit is cut at the group's foot (its frames
|
|
1924
|
+
* clipped there), or, when the group goes on to another page
|
|
1925
|
+
* (`FitState.gridsOn`), it and every row after it go on, whole. The
|
|
1926
|
+
* modules' declared boxes follow each other at the heights their
|
|
1927
|
+
* frames declare, so the log tells a module moved by the one before
|
|
1928
|
+
* it. */
|
|
1823
1929
|
const placeModules = (group, members) => {
|
|
1824
1930
|
const byMember = new Map(members.map((f) => [f.name, f]));
|
|
1825
1931
|
const own = groupPlaceable(group);
|
|
1826
1932
|
const ownBox = own ? placeBox(own, template, pageInput, refOf, () => 0) : undefined;
|
|
1827
1933
|
const box = ownBox ?? pageInput.areas.area;
|
|
1828
1934
|
const foot = box.y + box.height;
|
|
1829
|
-
const
|
|
1935
|
+
const toPx = (d) => dimensionToPx(d, dpi, pageInput.emPx);
|
|
1936
|
+
const mods = [];
|
|
1937
|
+
for (const item of group.items ?? []) {
|
|
1938
|
+
const mod = template.modules?.find((m) => m.name === item.module);
|
|
1939
|
+
const names = item.frames.filter((n) => byMember.has(n));
|
|
1940
|
+
if (mod && names.length > 0)
|
|
1941
|
+
mods.push({ mod, names });
|
|
1942
|
+
}
|
|
1943
|
+
/** A module's width in the group by its template (`'fill'`: none). */
|
|
1944
|
+
const fixedWidth = (l) => {
|
|
1945
|
+
const w = l.mod.width;
|
|
1946
|
+
if (w === undefined || w === 'fill')
|
|
1947
|
+
return undefined;
|
|
1948
|
+
return 'percent' in w ? (box.width * w.percent) / 100 : toPx(w);
|
|
1949
|
+
};
|
|
1950
|
+
/** `list` side by side across the group, `gap` between them: each as
|
|
1951
|
+
* wide as its template says, the others sharing what is left. */
|
|
1952
|
+
const across = (list, gap) => {
|
|
1953
|
+
const fixed = list.map(fixedWidth);
|
|
1954
|
+
const taken = fixed.reduce((a, w) => a + (w ?? 0), 0) + gap * Math.max(0, list.length - 1);
|
|
1955
|
+
const shares = fixed.filter((w) => w === undefined).length;
|
|
1956
|
+
const share = shares > 0 ? Math.max(0, box.width - taken) / shares : 0;
|
|
1957
|
+
const widths = fixed.map((w) => Math.max(0, w ?? share));
|
|
1958
|
+
const xs = [];
|
|
1959
|
+
let x = box.x;
|
|
1960
|
+
for (const w of widths) {
|
|
1961
|
+
xs.push(x);
|
|
1962
|
+
x += w + gap;
|
|
1963
|
+
}
|
|
1964
|
+
return { xs, widths };
|
|
1965
|
+
};
|
|
1966
|
+
/** The rows `list` makes in a group of `g`'s kind, the first `first`
|
|
1967
|
+
* after the row before it. */
|
|
1968
|
+
const rowsOf = (list, g, first, nested) => {
|
|
1969
|
+
const gap = toPx(g.gap);
|
|
1970
|
+
const align = g.rowAlign ?? 'stretch';
|
|
1971
|
+
const tag = nested !== undefined ? { nested } : {};
|
|
1972
|
+
if (g.kind === 'row')
|
|
1973
|
+
return [{ mods: [...list], ...across(list, gap), align, gapBefore: first, ...tag }];
|
|
1974
|
+
if (g.kind === 'grid') {
|
|
1975
|
+
const columns = Math.max(1, g.columns ?? 1);
|
|
1976
|
+
const colGap = toPx(g.columnGap ?? g.gap);
|
|
1977
|
+
const rowGap = toPx(g.rowGap ?? g.gap);
|
|
1978
|
+
const colW = Math.max(1, (box.width - colGap * (columns - 1)) / columns);
|
|
1979
|
+
const out = [];
|
|
1980
|
+
for (let k = 0; k < list.length; k += columns) {
|
|
1981
|
+
const chunk = list.slice(k, k + columns);
|
|
1982
|
+
out.push({ mods: chunk, xs: chunk.map((_, c) => box.x + c * (colW + colGap)), widths: chunk.map(() => colW), align, gapBefore: k === 0 ? first : rowGap, ...tag });
|
|
1983
|
+
}
|
|
1984
|
+
return out;
|
|
1985
|
+
}
|
|
1986
|
+
// A stack: a row of its own per module, at its width from the start.
|
|
1987
|
+
return list.map((l, k) => ({ mods: [l], xs: [box.x], widths: [fixedWidth(l) ?? box.width], align: 'start', gapBefore: k === 0 ? first : gap, ...tag }));
|
|
1988
|
+
};
|
|
1989
|
+
const rows = [];
|
|
1990
|
+
if (group.kind !== 'stack')
|
|
1991
|
+
rows.push(...rowsOf(mods, group, 0));
|
|
1992
|
+
else {
|
|
1993
|
+
const stackGap = toPx(group.gap);
|
|
1994
|
+
for (let k = 0; k < mods.length;) {
|
|
1995
|
+
const nested = mods[k].mod.nested;
|
|
1996
|
+
const inner = nested !== undefined ? template.groups.find((g) => g.name === nested) : undefined;
|
|
1997
|
+
let end = k + 1;
|
|
1998
|
+
if (inner)
|
|
1999
|
+
while (end < mods.length && mods[end].mod.nested === nested)
|
|
2000
|
+
end++;
|
|
2001
|
+
rows.push(...rowsOf(mods.slice(k, end), inner ?? group, k === 0 ? 0 : stackGap, inner ? nested : undefined));
|
|
2002
|
+
k = end;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
1830
2005
|
let cursor = box.y;
|
|
1831
2006
|
let declCursor = box.y;
|
|
1832
2007
|
let out = false;
|
|
@@ -1834,40 +2009,31 @@ function composePage(pageInput, template, fit, collapsed) {
|
|
|
1834
2009
|
let lastLaid;
|
|
1835
2010
|
const boxes = [];
|
|
1836
2011
|
const declBoxes = [];
|
|
2012
|
+
const nestedBoxes = new Map();
|
|
1837
2013
|
const restore = (to, from) => {
|
|
1838
2014
|
to.clear();
|
|
1839
2015
|
for (const [k, v] of from)
|
|
1840
2016
|
to.set(k, v);
|
|
1841
2017
|
};
|
|
1842
|
-
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
spillWhole(names, laidAny, lastLaid);
|
|
1849
|
-
continue;
|
|
2018
|
+
const lay = (l, x, y, width, height, filling) => {
|
|
2019
|
+
const steps = composedOrder({ frames: l.names.map((n) => byMember.get(n)), groups: template.groups.filter((g) => l.mod.groups.includes(g.name)) });
|
|
2020
|
+
input = { ...pageInput, areas: { ...pageInput.areas, area: { x, y, width, height: Math.max(0, height) } } };
|
|
2021
|
+
try {
|
|
2022
|
+
for (const step of steps)
|
|
2023
|
+
runStep(step, filling);
|
|
1850
2024
|
}
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
}
|
|
1859
|
-
finally {
|
|
1860
|
-
input = pageInput;
|
|
1861
|
-
}
|
|
1862
|
-
};
|
|
1863
|
-
// 1. How tall the module is: its frames placed in the room left,
|
|
1864
|
-
// then everything they set put back.
|
|
2025
|
+
finally {
|
|
2026
|
+
input = pageInput;
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
/** How tall a module is at `(x, y)`, `width` wide: its frames placed
|
|
2030
|
+
* in the room left, then everything they set put back. */
|
|
2031
|
+
const measure = (l, x, y, width) => {
|
|
1865
2032
|
const kept = { rects: new Map(rects), declared: new Map(declared), groupRects: new Map(groupRects), groupDeclared: new Map(groupDeclared), pinned: pinned.length, boxShort: new Map(boxShort), fitted: new Map(fitted) };
|
|
1866
|
-
lay(foot -
|
|
1867
|
-
const placed = names.filter((n) => rects.has(n));
|
|
2033
|
+
lay(l, x, y, width, foot - y, false);
|
|
2034
|
+
const placed = l.names.filter((n) => rects.has(n));
|
|
1868
2035
|
const measured = placed.filter((n) => !heldByFoot(byMember.get(n)));
|
|
1869
2036
|
const reach = unionRect((measured.length > 0 ? measured : placed).map((n) => rects.get(n)));
|
|
1870
|
-
const height = reach ? Math.max(0, reach.y + reach.height - cursor) : 0;
|
|
1871
2037
|
restore(rects, kept.rects);
|
|
1872
2038
|
restore(declared, kept.declared);
|
|
1873
2039
|
restore(groupRects, kept.groupRects);
|
|
@@ -1875,41 +2041,81 @@ function composePage(pageInput, template, fit, collapsed) {
|
|
|
1875
2041
|
restore(boxShort, kept.boxShort);
|
|
1876
2042
|
restore(fitted, kept.fitted);
|
|
1877
2043
|
pinned.length = kept.pinned;
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
2044
|
+
return reach ? Math.max(0, reach.y + reach.height - y) : 0;
|
|
2045
|
+
};
|
|
2046
|
+
rows.forEach((row, r) => {
|
|
2047
|
+
const names = row.mods.flatMap((l) => l.names);
|
|
2048
|
+
if (out) {
|
|
1882
2049
|
spillWhole(names, laidAny, lastLaid);
|
|
1883
|
-
|
|
1884
|
-
}
|
|
1885
|
-
// 2. The module laid in a box that tall (cut at the foot when it
|
|
1886
|
-
// does not fit).
|
|
1887
|
-
clipFoot = fits ? undefined : foot;
|
|
1888
|
-
try {
|
|
1889
|
-
lay(fits ? height : foot - cursor, true);
|
|
2050
|
+
return;
|
|
1890
2051
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
2052
|
+
if (r > 0) {
|
|
2053
|
+
cursor += row.gapBefore;
|
|
2054
|
+
declCursor += row.gapBefore;
|
|
1893
2055
|
}
|
|
1894
|
-
const
|
|
1895
|
-
const
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
const d = declared.get(n);
|
|
1903
|
-
if (d && Math.abs(shift) > 0.001)
|
|
1904
|
-
declared.set(n, { ...d, y: d.y + shift });
|
|
2056
|
+
const heights = row.mods.map((l, k) => measure(l, row.xs[k], cursor, row.widths[k]));
|
|
2057
|
+
const rowH = Math.max(0, ...heights);
|
|
2058
|
+
const fits = cursor + rowH <= foot + 0.5;
|
|
2059
|
+
if (!fits && fit.gridsOn.has(group.name)) {
|
|
2060
|
+
// This row and the ones after it go on, whole.
|
|
2061
|
+
out = true;
|
|
2062
|
+
spillWhole(names, laidAny, lastLaid);
|
|
2063
|
+
return;
|
|
1905
2064
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
2065
|
+
// Each module laid in a box that tall (cut at the foot when the row
|
|
2066
|
+
// does not fit); a shorter one placed by `rowAlign`.
|
|
2067
|
+
const several = row.mods.length > 1;
|
|
2068
|
+
let rowBottom = cursor;
|
|
2069
|
+
let rowDeclBottom = declCursor;
|
|
2070
|
+
row.mods.forEach((l, k) => {
|
|
2071
|
+
const x = row.xs[k];
|
|
2072
|
+
const width = row.widths[k];
|
|
2073
|
+
const spare = rowH - heights[k];
|
|
2074
|
+
const stretched = fits && several && row.align === 'stretch';
|
|
2075
|
+
const dy = fits && several && spare > 0.01 ? (row.align === 'end' ? spare : row.align === 'center' ? spare / 2 : 0) : 0;
|
|
2076
|
+
const y = cursor + dy;
|
|
2077
|
+
clipFoot = fits ? undefined : foot;
|
|
2078
|
+
try {
|
|
2079
|
+
lay(l, x, y, width, fits ? (stretched ? rowH : heights[k]) : foot - cursor, true);
|
|
2080
|
+
}
|
|
2081
|
+
finally {
|
|
2082
|
+
clipFoot = undefined;
|
|
2083
|
+
}
|
|
2084
|
+
const u = unionRect(l.names.flatMap((n) => (rects.has(n) ? [rects.get(n)] : [])));
|
|
2085
|
+
const bottom = Math.max(stretched ? y + rowH : y, u ? u.y + u.height : y);
|
|
2086
|
+
const moduleBox = { x, y, width, height: bottom - y };
|
|
2087
|
+
moduleRects.set(l.mod.name, moduleBox);
|
|
2088
|
+
boxes.push(moduleBox);
|
|
2089
|
+
// Declared: after the rows before it as they declare themselves.
|
|
2090
|
+
const shift = declCursor - cursor;
|
|
2091
|
+
for (const n of l.names) {
|
|
2092
|
+
const d = declared.get(n);
|
|
2093
|
+
if (d && Math.abs(shift) > 0.001)
|
|
2094
|
+
declared.set(n, { ...d, y: d.y + shift });
|
|
2095
|
+
}
|
|
2096
|
+
const declY = declCursor + dy;
|
|
2097
|
+
const du = unionRect(l.names.flatMap((n) => (declared.has(n) ? [declared.get(n)] : [])));
|
|
2098
|
+
const declBottom = du ? Math.max(declY, du.y + du.height) : declY;
|
|
2099
|
+
const declBox = { x, y: declY, width, height: declBottom - declY };
|
|
2100
|
+
declBoxes.push(declBox);
|
|
2101
|
+
if (row.nested !== undefined) {
|
|
2102
|
+
const n = nestedBoxes.get(row.nested) ?? { laid: [], declared: [] };
|
|
2103
|
+
n.laid.push(moduleBox);
|
|
2104
|
+
n.declared.push(declBox);
|
|
2105
|
+
nestedBoxes.set(row.nested, n);
|
|
2106
|
+
}
|
|
2107
|
+
rowBottom = Math.max(rowBottom, bottom);
|
|
2108
|
+
rowDeclBottom = Math.max(rowDeclBottom, declBottom);
|
|
2109
|
+
});
|
|
1909
2110
|
laidAny = true;
|
|
1910
2111
|
lastLaid = frameIdOf(names[names.length - 1]);
|
|
1911
|
-
cursor =
|
|
1912
|
-
declCursor =
|
|
2112
|
+
cursor = rowBottom;
|
|
2113
|
+
declCursor = rowDeclBottom;
|
|
2114
|
+
});
|
|
2115
|
+
// A group inside the stack (#1088) is the box of its modules.
|
|
2116
|
+
for (const [name, b] of nestedBoxes) {
|
|
2117
|
+
groupRects.set(name, unionRect(b.laid));
|
|
2118
|
+
groupDeclared.set(name, unionRect(b.declared));
|
|
1913
2119
|
}
|
|
1914
2120
|
groupRects.set(group.name, ownBox ?? unionRect(boxes) ?? { ...box, height: 0 });
|
|
1915
2121
|
groupDeclared.set(group.name, ownBox ?? unionRect(declBoxes) ?? { ...box, height: 0 });
|
|
@@ -2062,6 +2268,25 @@ export function frameStyleOf(base, frame) {
|
|
|
2062
2268
|
};
|
|
2063
2269
|
}
|
|
2064
2270
|
const lookedStyles = new WeakMap();
|
|
2271
|
+
/** Frames with a named look laid over theirs (#1089), per look and frame:
|
|
2272
|
+
* the same look object each time, so `styleWithLook` keeps its styles. */
|
|
2273
|
+
const namedLookFrames = new WeakMap();
|
|
2274
|
+
/** `frame` with the named look `named` (#1089) laid over its own `look`
|
|
2275
|
+
* (`lookOver`): the order is the style, the template frame's look, then
|
|
2276
|
+
* the named look. `frame` itself when there is none. */
|
|
2277
|
+
export function withNamedLook(frame, named) {
|
|
2278
|
+
if (!named)
|
|
2279
|
+
return frame;
|
|
2280
|
+
let byFrame = namedLookFrames.get(named);
|
|
2281
|
+
if (!byFrame)
|
|
2282
|
+
namedLookFrames.set(named, (byFrame = new WeakMap()));
|
|
2283
|
+
const known = byFrame.get(frame);
|
|
2284
|
+
if (known)
|
|
2285
|
+
return known;
|
|
2286
|
+
const out = { ...frame, look: lookOver(frame.look, named) };
|
|
2287
|
+
byFrame.set(frame, out);
|
|
2288
|
+
return out;
|
|
2289
|
+
}
|
|
2065
2290
|
/** `base` with what a frame's `look` sets (#976) written over it: colours
|
|
2066
2291
|
* of the fill, border, stripe, icon, marker, title, text and label tab,
|
|
2067
2292
|
* and the tracking of the body. A fill colour turns the fill on. A part
|