postext 1.50.0 → 1.51.1

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.
Files changed (87) hide show
  1. package/dist/__tests__/composed/fixtures/moduleNesting.d.ts +25 -0
  2. package/dist/__tests__/composed/fixtures/moduleNesting.d.ts.map +1 -0
  3. package/dist/__tests__/composed/fixtures/moduleNesting.js +36 -0
  4. package/dist/__tests__/composed/fixtures/moduleNesting.js.map +1 -0
  5. package/dist/__tests__/composed/moduleNesting.test.d.ts +2 -0
  6. package/dist/__tests__/composed/moduleNesting.test.d.ts.map +1 -0
  7. package/dist/__tests__/composed/moduleNesting.test.js +177 -0
  8. package/dist/__tests__/composed/moduleNesting.test.js.map +1 -0
  9. package/dist/__tests__/composed/moduleRows.test.js +9 -3
  10. package/dist/__tests__/composed/moduleRows.test.js.map +1 -1
  11. package/dist/__tests__/defaults/stripRoundTrip.harness.d.ts +65 -0
  12. package/dist/__tests__/defaults/stripRoundTrip.harness.d.ts.map +1 -0
  13. package/dist/__tests__/defaults/stripRoundTrip.harness.js +493 -0
  14. package/dist/__tests__/defaults/stripRoundTrip.harness.js.map +1 -0
  15. package/dist/__tests__/defaults/stripRoundTrip.part1.test.d.ts +2 -0
  16. package/dist/__tests__/defaults/stripRoundTrip.part1.test.d.ts.map +1 -0
  17. package/dist/__tests__/defaults/stripRoundTrip.part1.test.js +4 -0
  18. package/dist/__tests__/defaults/stripRoundTrip.part1.test.js.map +1 -0
  19. package/dist/__tests__/defaults/stripRoundTrip.part2.test.d.ts +2 -0
  20. package/dist/__tests__/defaults/stripRoundTrip.part2.test.d.ts.map +1 -0
  21. package/dist/__tests__/defaults/stripRoundTrip.part2.test.js +4 -0
  22. package/dist/__tests__/defaults/stripRoundTrip.part2.test.js.map +1 -0
  23. package/dist/__tests__/defaults/stripRoundTrip.part3.test.d.ts +2 -0
  24. package/dist/__tests__/defaults/stripRoundTrip.part3.test.d.ts.map +1 -0
  25. package/dist/__tests__/defaults/stripRoundTrip.part3.test.js +4 -0
  26. package/dist/__tests__/defaults/stripRoundTrip.part3.test.js.map +1 -0
  27. package/dist/__tests__/defaults/stripRoundTrip.part4.test.d.ts +2 -0
  28. package/dist/__tests__/defaults/stripRoundTrip.part4.test.d.ts.map +1 -0
  29. package/dist/__tests__/defaults/stripRoundTrip.part4.test.js +4 -0
  30. package/dist/__tests__/defaults/stripRoundTrip.part4.test.js.map +1 -0
  31. package/dist/__tests__/defaults/stripRoundTrip.part5.test.d.ts +2 -0
  32. package/dist/__tests__/defaults/stripRoundTrip.part5.test.d.ts.map +1 -0
  33. package/dist/__tests__/defaults/stripRoundTrip.part5.test.js +4 -0
  34. package/dist/__tests__/defaults/stripRoundTrip.part5.test.js.map +1 -0
  35. package/dist/__tests__/defaults/stripRoundTrip.part6.test.d.ts +2 -0
  36. package/dist/__tests__/defaults/stripRoundTrip.part6.test.d.ts.map +1 -0
  37. package/dist/__tests__/defaults/stripRoundTrip.part6.test.js +4 -0
  38. package/dist/__tests__/defaults/stripRoundTrip.part6.test.js.map +1 -0
  39. package/dist/__tests__/defaults/stripRoundTrip.test.js +10 -444
  40. package/dist/__tests__/defaults/stripRoundTrip.test.js.map +1 -1
  41. package/dist/__tests__/exports.test.js +1 -0
  42. package/dist/__tests__/exports.test.js.map +1 -1
  43. package/dist/comics/lettering/__tests__/strictPlacement.fixtures.d.ts +12 -0
  44. package/dist/comics/lettering/__tests__/strictPlacement.fixtures.d.ts.map +1 -0
  45. package/dist/comics/lettering/__tests__/strictPlacement.fixtures.js +22 -0
  46. package/dist/comics/lettering/__tests__/strictPlacement.fixtures.js.map +1 -0
  47. package/dist/comics/lettering/__tests__/strictPlacement.ja.test.d.ts +2 -0
  48. package/dist/comics/lettering/__tests__/strictPlacement.ja.test.d.ts.map +1 -0
  49. package/dist/comics/lettering/__tests__/strictPlacement.ja.test.js +13 -0
  50. package/dist/comics/lettering/__tests__/strictPlacement.ja.test.js.map +1 -0
  51. package/dist/comics/lettering/__tests__/strictPlacement.off.test.d.ts +2 -0
  52. package/dist/comics/lettering/__tests__/strictPlacement.off.test.d.ts.map +1 -0
  53. package/dist/comics/lettering/__tests__/strictPlacement.off.test.js +17 -0
  54. package/dist/comics/lettering/__tests__/strictPlacement.off.test.js.map +1 -0
  55. package/dist/comics/lettering/__tests__/strictPlacement.test.js +2 -49
  56. package/dist/comics/lettering/__tests__/strictPlacement.test.js.map +1 -1
  57. package/dist/comics/lettering/__tests__/strictPlacement.zh.test.d.ts +2 -0
  58. package/dist/comics/lettering/__tests__/strictPlacement.zh.test.d.ts.map +1 -0
  59. package/dist/comics/lettering/__tests__/strictPlacement.zh.test.js +23 -0
  60. package/dist/comics/lettering/__tests__/strictPlacement.zh.test.js.map +1 -0
  61. package/dist/composed/groups.d.ts.map +1 -1
  62. package/dist/composed/groups.js +9 -1
  63. package/dist/composed/groups.js.map +1 -1
  64. package/dist/composed/modules.d.ts +9 -0
  65. package/dist/composed/modules.d.ts.map +1 -1
  66. package/dist/composed/modules.js +38 -4
  67. package/dist/composed/modules.js.map +1 -1
  68. package/dist/composed/templates.d.ts +3 -0
  69. package/dist/composed/templates.d.ts.map +1 -1
  70. package/dist/composed/templates.js +15 -7
  71. package/dist/composed/templates.js.map +1 -1
  72. package/dist/composed/types.d.ts +26 -5
  73. package/dist/composed/types.d.ts.map +1 -1
  74. package/dist/composed/validate.d.ts.map +1 -1
  75. package/dist/composed/validate.js +20 -4
  76. package/dist/composed/validate.js.map +1 -1
  77. package/dist/index.d.ts +1 -1
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -1
  80. package/dist/index.js.map +1 -1
  81. package/dist/pipeline/composedLayout.d.ts.map +1 -1
  82. package/dist/pipeline/composedLayout.js +193 -85
  83. package/dist/pipeline/composedLayout.js.map +1 -1
  84. package/dist/vdt.d.ts +4 -0
  85. package/dist/vdt.d.ts.map +1 -1
  86. package/dist/vdt.js.map +1 -1
  87. package/package.json +1 -1
@@ -46,7 +46,7 @@ import { collapsedOnAddedPage, collapseFrames, continuationTemplate, continuesCo
46
46
  import { isModuleTemplate, resolvePageTemplate } from '../composed/templates';
47
47
  import { frameElementId } from '../composed/ids';
48
48
  import { expandRepeats, instanceOf, repeatItemsOf } from '../composed/repeat';
49
- import { moduleOfFrame } from '../composed/modules';
49
+ import { moduleOfFrame, nestedChain } from '../composed/modules';
50
50
  import { frameLinePx, frameRuleBlocks } from '../composed/rules';
51
51
  import { overlapChecks } from '../composed/overlap';
52
52
  import { offsetCalloutToAbsolute } from './calloutLayout';
@@ -1029,6 +1029,12 @@ function composePage(pageInput, template, fit, collapsed) {
1029
1029
  const id = input.stories.get(headOf(frame.name))?.plan.attrs.look?.trim();
1030
1030
  return id ? frameLooks.find((l) => l.id === id) : undefined;
1031
1031
  };
1032
+ /** The look a frame's story names that `frameLooks` does not have
1033
+ * (#1095): the canvas marks the frame. */
1034
+ const unknownLookOf = (frame) => {
1035
+ const id = input.stories.get(headOf(frame.name))?.plan.attrs.look?.trim();
1036
+ return id && !frameLooks?.some((l) => l.id === id) ? id : undefined;
1037
+ };
1032
1038
  /** The style a frame is laid out in: its box as drawn at any text scale. */
1033
1039
  const styleOf = (frame) => {
1034
1040
  const style = keepChromeAt(input.styleOf(withNamedLook(frame, namedLookOf(frame))), textScale(frame));
@@ -1163,6 +1169,9 @@ function composePage(pageInput, template, fit, collapsed) {
1163
1169
  const moduleRects = new Map();
1164
1170
  /** The foot a module cut at its group's foot is clipped to (#1062). */
1165
1171
  let clipFoot;
1172
+ /** A module a stretching row lays taller than it is (#1095): its
1173
+ * frames, its own foot (`at`) and the row's. */
1174
+ let stretchTo;
1166
1175
  const pushed = seed
1167
1176
  ? [...seed].map(([name, box]) => ({ name, box, ...(seedShapes?.get(name) ? { contour: seedShapes.get(name) } : {}) }))
1168
1177
  : [];
@@ -1760,6 +1769,7 @@ function composePage(pageInput, template, fit, collapsed) {
1760
1769
  ...(unmirroredAt(frame) !== undefined ? { unmirrorX: unmirroredAt(frame) } : {}),
1761
1770
  layer: frame.layer,
1762
1771
  ...(frame.style ? { styleId: frame.style } : {}),
1772
+ ...(holds && unknownLookOf(frame) !== undefined ? { lookUnknown: unknownLookOf(frame) } : {}),
1763
1773
  scale: s,
1764
1774
  ...(pic !== undefined && holds ? { imageScale: pic } : {}),
1765
1775
  overflowPx,
@@ -1852,6 +1862,27 @@ function composePage(pageInput, template, fit, collapsed) {
1852
1862
  rects.set(name, out);
1853
1863
  return out;
1854
1864
  };
1865
+ /** A module's `'auto'` box that ends at the module's foot, in a row
1866
+ * that stretches the module (#1095): to the row's foot, its declared
1867
+ * box by as much (the template asks for it). A frame as wide as its
1868
+ * content (a pill, a tab) keeps its height. */
1869
+ const stretchAuto = (name, r) => {
1870
+ const s = stretchTo;
1871
+ if (!s || !s.names.has(name))
1872
+ return r;
1873
+ const f = byName.get(name);
1874
+ if (!f || f.height !== 'auto' || f.width === 'auto' || f.pin || heldByFoot(f))
1875
+ return r;
1876
+ const bottom = r.y + r.height;
1877
+ if (Math.abs(bottom - s.at) > 0.5 || s.foot - bottom <= 0.01)
1878
+ return r;
1879
+ const out = { ...r, height: s.foot - r.y };
1880
+ rects.set(name, out);
1881
+ const d = declared.get(name);
1882
+ if (d)
1883
+ declared.set(name, { ...d, height: d.height + (s.foot - bottom) });
1884
+ return out;
1885
+ };
1855
1886
  /** The frames of `names` whose content goes on whole on another page
1856
1887
  * (#1061, #1062): none of it is laid here. */
1857
1888
  const spillWhole = (names, advanced, lastLaid) => {
@@ -1868,7 +1899,7 @@ function composePage(pageInput, template, fit, collapsed) {
1868
1899
  const runStep = (step, filling) => {
1869
1900
  if (step.kind === 'frame') {
1870
1901
  const p = prepare(step.frame);
1871
- const r = clipAtFoot(step.frame.name, placeFrame(p));
1902
+ const r = clipAtFoot(step.frame.name, stretchAuto(step.frame.name, placeFrame(p)));
1872
1903
  if (filling)
1873
1904
  fill(p, r);
1874
1905
  return;
@@ -1889,7 +1920,7 @@ function composePage(pageInput, template, fit, collapsed) {
1889
1920
  let lastLaid;
1890
1921
  for (const f of step.frames) {
1891
1922
  if (!grid.spilled.has(f.name)) {
1892
- fill(prepare(f), clipAtFoot(f.name, grid.rects.get(f.name)));
1923
+ fill(prepare(f), clipAtFoot(f.name, stretchAuto(f.name, grid.rects.get(f.name))));
1893
1924
  lastLaid = frameIdOf(f.name);
1894
1925
  continue;
1895
1926
  }
@@ -1901,7 +1932,7 @@ function composePage(pageInput, template, fit, collapsed) {
1901
1932
  // A frame of a chain inside the group is reached once the frame
1902
1933
  // before it was filled: each is prepared again as it is filled.
1903
1934
  for (const f of step.frames) {
1904
- const r = clipAtFoot(f.name, solved.get(f.name));
1935
+ const r = clipAtFoot(f.name, stretchAuto(f.name, solved.get(f.name)));
1905
1936
  if (filling)
1906
1937
  fill(prepare(f), r);
1907
1938
  }
@@ -1911,16 +1942,19 @@ function composePage(pageInput, template, fit, collapsed) {
1911
1942
  * its own, as wide as the group (or its template's `width`, #1088), a
1912
1943
  * `'row'` puts them all in one, start to end (each its `width`, else
1913
1944
  * 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
1945
+ * equal columns; a run of modules a group inside it takes (`within`)
1946
+ * is laid as that group says where its first module comes: its rows
1947
+ * among a stack's, one cell of a row or a grid (#1095: to any depth).
1948
+ * A row's room past its modules' widths goes by `grow`, what it is
1949
+ * short by comes back by `shrink` (#1095). Rows run from the start side of the page's lines (the flow frame is
1917
1950
  * mirrored on a right-to-left page, so its left is the start). A
1918
1951
  * module is placed as its template draws its frames in its box
1919
1952
  * (`'container'` is the box) and its groups laid in it: first in the
1920
1953
  * room left, to find how tall it is (the frames held by the box's foot
1921
1954
  * or middle, or sized by its height, left out), then in a box that
1922
1955
  * 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
1956
+ * row, its `'auto'` boxes that end at its foot reaching the row's
1957
+ * (#1095). A row that does not fit is cut at the group's foot (its frames
1924
1958
  * clipped there), or, when the group goes on to another page
1925
1959
  * (`FitState.gridsOn`), it and every row after it go on, whole. The
1926
1960
  * modules' declared boxes follow each other at the heights their
@@ -1933,75 +1967,101 @@ function composePage(pageInput, template, fit, collapsed) {
1933
1967
  const box = ownBox ?? pageInput.areas.area;
1934
1968
  const foot = box.y + box.height;
1935
1969
  const toPx = (d) => dimensionToPx(d, dpi, pageInput.emPx);
1970
+ const groupNamed = new Map(template.groups.map((g) => [g.name, g]));
1936
1971
  const mods = [];
1937
1972
  for (const item of group.items ?? []) {
1938
1973
  const mod = template.modules?.find((m) => m.name === item.module);
1939
1974
  const names = item.frames.filter((n) => byMember.has(n));
1940
1975
  if (mod && names.length > 0)
1941
- mods.push({ mod, names });
1976
+ mods.push({ mod, names, chain: nestedChain(template.groups, mod.nested) });
1942
1977
  }
1943
- /** A module's width in the group by its template (`'fill'`: none). */
1944
- const fixedWidth = (l) => {
1978
+ /** A module's width by its template in a group `width` wide
1979
+ * (`'fill'`: none). */
1980
+ const fixedWidth = (l, width) => {
1945
1981
  const w = l.mod.width;
1946
1982
  if (w === undefined || w === 'fill')
1947
1983
  return undefined;
1948
- return 'percent' in w ? (box.width * w.percent) / 100 : toPx(w);
1984
+ return 'percent' in w ? (width * w.percent) / 100 : toPx(w);
1949
1985
  };
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));
1986
+ /** The cells of `list` in `g`: each module `g` lays itself, and each
1987
+ * run of the modules of a group inside `g`. */
1988
+ const cellsOf = (list, g) => {
1989
+ const out = [];
1990
+ for (const l of list) {
1991
+ const name = l.chain.find((n) => groupNamed.get(n)?.within === g.name);
1992
+ const child = name !== undefined ? groupNamed.get(name) : undefined;
1993
+ const last = out[out.length - 1];
1994
+ if (child && last?.kind === 'group' && last.group === child)
1995
+ last.list.push(l);
1996
+ else
1997
+ out.push(child ? { kind: 'group', group: child, list: [l] } : { kind: 'module', l });
1998
+ }
1999
+ return out;
2000
+ };
2001
+ const namesOf = (c) => (c.kind === 'module' ? c.l.names : c.list.flatMap((l) => l.names));
2002
+ /** `cells` side by side across `width` from `x`, `gap` between them:
2003
+ * each as wide as its template says, then the room left shared by
2004
+ * `grow` (a `'fill'` module, and a group's cell, 1 at least), or,
2005
+ * when the widths run past the end, given back by `shrink` in
2006
+ * proportion to each width (#1095). */
2007
+ const across = (cells, gap, x, width) => {
2008
+ const fixed = cells.map((c) => (c.kind === 'module' ? fixedWidth(c.l, width) : undefined));
2009
+ const taken = fixed.reduce((a, w) => a + (w ?? 0), 0) + gap * Math.max(0, cells.length - 1);
2010
+ const spare = width - taken;
2011
+ let widths;
2012
+ if (spare >= 0) {
2013
+ const grows = cells.map((c, k) => (c.kind !== 'module' ? 1 : fixed[k] === undefined ? Math.max(1, c.l.mod.grow ?? 1) : c.l.mod.grow ?? 0));
2014
+ const total = grows.reduce((a, g) => a + g, 0);
2015
+ widths = fixed.map((w, k) => Math.max(0, total > 0 ? (w ?? 0) + (spare * grows[k]) / total : w ?? 0));
2016
+ }
2017
+ else {
2018
+ const weights = cells.map((c, k) => (c.kind === 'module' && fixed[k] !== undefined ? (c.l.mod.shrink ?? 0) * fixed[k] : 0));
2019
+ const total = weights.reduce((a, g) => a + g, 0);
2020
+ widths = fixed.map((w, k) => Math.max(0, total > 0 ? (w ?? 0) + (spare * weights[k]) / total : w ?? 0));
2021
+ }
1958
2022
  const xs = [];
1959
- let x = box.x;
2023
+ let at = x;
1960
2024
  for (const w of widths) {
1961
- xs.push(x);
1962
- x += w + gap;
2025
+ xs.push(at);
2026
+ at += w + gap;
1963
2027
  }
1964
2028
  return { xs, widths };
1965
2029
  };
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) => {
2030
+ /** The rows `list` makes in a group of `g`'s kind across `width`
2031
+ * from `x`, the first `first` after the row before it. A group
2032
+ * inside a stack adds its rows to the stack's; inside a row or a
2033
+ * grid, a run of its modules is one cell. */
2034
+ const rowsOf = (list, g, x, width, first) => {
1969
2035
  const gap = toPx(g.gap);
1970
2036
  const align = g.rowAlign ?? 'stretch';
1971
- const tag = nested !== undefined ? { nested } : {};
2037
+ const cells = cellsOf(list, g);
1972
2038
  if (g.kind === 'row')
1973
- return [{ mods: [...list], ...across(list, gap), align, gapBefore: first, ...tag }];
2039
+ return [{ cells, ...across(cells, gap, x, width), align, gapBefore: first }];
1974
2040
  if (g.kind === 'grid') {
1975
2041
  const columns = Math.max(1, g.columns ?? 1);
1976
2042
  const colGap = toPx(g.columnGap ?? g.gap);
1977
2043
  const rowGap = toPx(g.rowGap ?? g.gap);
1978
- const colW = Math.max(1, (box.width - colGap * (columns - 1)) / columns);
2044
+ const colW = Math.max(1, (width - colGap * (columns - 1)) / columns);
1979
2045
  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 });
2046
+ for (let k = 0; k < cells.length; k += columns) {
2047
+ const chunk = cells.slice(k, k + columns);
2048
+ out.push({ cells: chunk, xs: chunk.map((_, c) => x + c * (colW + colGap)), widths: chunk.map(() => colW), align, gapBefore: k === 0 ? first : rowGap });
1983
2049
  }
1984
2050
  return out;
1985
2051
  }
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 }));
2052
+ // A stack: a row of its own per module, at its width from the
2053
+ // start; a group's rows in turn.
2054
+ const out = [];
2055
+ cells.forEach((c, k) => {
2056
+ const before = k === 0 ? first : gap;
2057
+ if (c.kind === 'module')
2058
+ out.push({ cells: [c], xs: [x], widths: [fixedWidth(c.l, width) ?? width], align: 'start', gapBefore: before });
2059
+ else
2060
+ out.push(...rowsOf(c.list, c.group, x, width, before));
2061
+ });
2062
+ return out;
1988
2063
  };
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
- }
2064
+ const rows = rowsOf(mods, group, box.x, box.width, 0);
2005
2065
  let cursor = box.y;
2006
2066
  let declCursor = box.y;
2007
2067
  let out = false;
@@ -2043,43 +2103,53 @@ function composePage(pageInput, template, fit, collapsed) {
2043
2103
  pinned.length = kept.pinned;
2044
2104
  return reach ? Math.max(0, reach.y + reach.height - y) : 0;
2045
2105
  };
2046
- rows.forEach((row, r) => {
2047
- const names = row.mods.flatMap((l) => l.names);
2048
- if (out) {
2049
- spillWhole(names, laidAny, lastLaid);
2050
- return;
2051
- }
2052
- if (r > 0) {
2053
- cursor += row.gapBefore;
2054
- declCursor += row.gapBefore;
2055
- }
2056
- const heights = row.mods.map((l, k) => measure(l, row.xs[k], cursor, row.widths[k]));
2106
+ /** How tall a cell is at `(x, y)`, `width` wide: a module's height,
2107
+ * or a group's rows one after another (#1095). */
2108
+ const measureCell = (c, x, y, width) => {
2109
+ if (c.kind === 'module')
2110
+ return measure(c.l, x, y, width);
2111
+ let at = y;
2112
+ rowsOf(c.list, c.group, x, width, 0).forEach((row, r) => {
2113
+ if (r > 0)
2114
+ at += row.gapBefore;
2115
+ at += Math.max(0, ...row.cells.map((d, k) => measureCell(d, row.xs[k], at, row.widths[k])));
2116
+ });
2117
+ return at - y;
2118
+ };
2119
+ /** Lay `row` at `rowTop` (its declared top `declTop`), its cells
2120
+ * `heights` tall: each module in a box that tall (cut at the foot
2121
+ * when the row does not fit), a shorter one placed by `rowAlign`; a
2122
+ * group's cell its rows in turn. The row's foot, laid and declared. */
2123
+ const layRow = (row, rowTop, declTop, heights, fits) => {
2057
2124
  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;
2064
- }
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) => {
2125
+ const several = row.cells.length > 1;
2126
+ let rowBottom = rowTop;
2127
+ let rowDeclBottom = declTop;
2128
+ row.cells.forEach((c, k) => {
2071
2129
  const x = row.xs[k];
2072
2130
  const width = row.widths[k];
2073
2131
  const spare = rowH - heights[k];
2074
- const stretched = fits && several && row.align === 'stretch';
2132
+ const stretched = fits && several && row.align === 'stretch' && c.kind === 'module';
2075
2133
  const dy = fits && several && spare > 0.01 ? (row.align === 'end' ? spare : row.align === 'center' ? spare / 2 : 0) : 0;
2076
- const y = cursor + dy;
2134
+ const y = rowTop + dy;
2135
+ if (c.kind === 'group') {
2136
+ // A run of a group inside this one (#1095): its rows, from the
2137
+ // cell's top.
2138
+ const laid = layRows(rowsOf(c.list, c.group, x, width, 0), y, declTop + dy, fits);
2139
+ rowBottom = Math.max(rowBottom, laid.bottom);
2140
+ rowDeclBottom = Math.max(rowDeclBottom, laid.declBottom);
2141
+ return;
2142
+ }
2143
+ const l = c.l;
2077
2144
  clipFoot = fits ? undefined : foot;
2145
+ // Its 'auto' boxes that end at its foot reach the row's (#1095).
2146
+ stretchTo = stretched && spare > 0.01 ? { at: y + heights[k], foot: y + rowH, names: new Set(l.names) } : undefined;
2078
2147
  try {
2079
- lay(l, x, y, width, fits ? (stretched ? rowH : heights[k]) : foot - cursor, true);
2148
+ lay(l, x, y, width, fits ? (stretched ? rowH : heights[k]) : foot - rowTop, true);
2080
2149
  }
2081
2150
  finally {
2082
2151
  clipFoot = undefined;
2152
+ stretchTo = undefined;
2083
2153
  }
2084
2154
  const u = unionRect(l.names.flatMap((n) => (rects.has(n) ? [rects.get(n)] : [])));
2085
2155
  const bottom = Math.max(stretched ? y + rowH : y, u ? u.y + u.height : y);
@@ -2087,32 +2157,70 @@ function composePage(pageInput, template, fit, collapsed) {
2087
2157
  moduleRects.set(l.mod.name, moduleBox);
2088
2158
  boxes.push(moduleBox);
2089
2159
  // Declared: after the rows before it as they declare themselves.
2090
- const shift = declCursor - cursor;
2160
+ const shift = declTop - rowTop;
2091
2161
  for (const n of l.names) {
2092
2162
  const d = declared.get(n);
2093
2163
  if (d && Math.abs(shift) > 0.001)
2094
2164
  declared.set(n, { ...d, y: d.y + shift });
2095
2165
  }
2096
- const declY = declCursor + dy;
2166
+ const declY = declTop + dy;
2097
2167
  const du = unionRect(l.names.flatMap((n) => (declared.has(n) ? [declared.get(n)] : [])));
2098
2168
  const declBottom = du ? Math.max(declY, du.y + du.height) : declY;
2099
2169
  const declBox = { x, y: declY, width, height: declBottom - declY };
2100
2170
  declBoxes.push(declBox);
2101
- if (row.nested !== undefined) {
2102
- const n = nestedBoxes.get(row.nested) ?? { laid: [], declared: [] };
2171
+ for (const name of l.chain) {
2172
+ const n = nestedBoxes.get(name) ?? { laid: [], declared: [] };
2103
2173
  n.laid.push(moduleBox);
2104
2174
  n.declared.push(declBox);
2105
- nestedBoxes.set(row.nested, n);
2175
+ nestedBoxes.set(name, n);
2106
2176
  }
2107
2177
  rowBottom = Math.max(rowBottom, bottom);
2108
2178
  rowDeclBottom = Math.max(rowDeclBottom, declBottom);
2109
2179
  });
2180
+ return { bottom: rowBottom, declBottom: rowDeclBottom };
2181
+ };
2182
+ /** The rows of a group's cell, one after another from `top`. */
2183
+ const layRows = (list, top, declTop, fits) => {
2184
+ let at = top;
2185
+ let declAt = declTop;
2186
+ list.forEach((row, r) => {
2187
+ if (r > 0) {
2188
+ at += row.gapBefore;
2189
+ declAt += row.gapBefore;
2190
+ }
2191
+ const heights = row.cells.map((c, k) => measureCell(c, row.xs[k], at, row.widths[k]));
2192
+ const laid = layRow(row, at, declAt, heights, fits);
2193
+ at = laid.bottom;
2194
+ declAt = laid.declBottom;
2195
+ });
2196
+ return { bottom: at, declBottom: declAt };
2197
+ };
2198
+ rows.forEach((row, r) => {
2199
+ const names = row.cells.flatMap(namesOf);
2200
+ if (out) {
2201
+ spillWhole(names, laidAny, lastLaid);
2202
+ return;
2203
+ }
2204
+ if (r > 0) {
2205
+ cursor += row.gapBefore;
2206
+ declCursor += row.gapBefore;
2207
+ }
2208
+ const heights = row.cells.map((c, k) => measureCell(c, row.xs[k], cursor, row.widths[k]));
2209
+ const rowH = Math.max(0, ...heights);
2210
+ const fits = cursor + rowH <= foot + 0.5;
2211
+ if (!fits && fit.gridsOn.has(group.name)) {
2212
+ // This row and the ones after it go on, whole.
2213
+ out = true;
2214
+ spillWhole(names, laidAny, lastLaid);
2215
+ return;
2216
+ }
2217
+ const laid = layRow(row, cursor, declCursor, heights, fits);
2110
2218
  laidAny = true;
2111
2219
  lastLaid = frameIdOf(names[names.length - 1]);
2112
- cursor = rowBottom;
2113
- declCursor = rowDeclBottom;
2220
+ cursor = laid.bottom;
2221
+ declCursor = laid.declBottom;
2114
2222
  });
2115
- // A group inside the stack (#1088) is the box of its modules.
2223
+ // A group inside another (#1088, #1095) is the box of its modules.
2116
2224
  for (const [name, b] of nestedBoxes) {
2117
2225
  groupRects.set(name, unionRect(b.laid));
2118
2226
  groupDeclared.set(name, unionRect(b.declared));