@quario/pdf 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +3 -2
- package/lib/box.js +111 -0
- package/lib/canvas.js +28 -15
- package/lib/fonts.js +69 -26
- package/lib/index.js +36 -15
- package/lib/layout.js +273 -132
- package/lib/style.js +10 -19
- package/lib/text.js +30 -19
- package/package.json +4 -4
package/lib/layout.js
CHANGED
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
import { display, isReportBand, text } from "quario";
|
|
25
25
|
import { balance } from "./balance.js";
|
|
26
26
|
import { adopt, frame, measuring } from "./canvas.js";
|
|
27
|
+
import { familyName } from "./fonts.js";
|
|
27
28
|
import { intrinsic } from "./image.js";
|
|
28
|
-
import {
|
|
29
|
+
import { CELL_PAD, NO_PAD, insetOf, paintBox, unbox } from "./box.js";
|
|
30
|
+
import { BAND, GUTTER, LEAD, col, merge, roled, shift, sizeOf } from "./style.js";
|
|
29
31
|
import { atoms, dress, heightOf, wrap } from "./text.js";
|
|
30
32
|
|
|
31
33
|
/** @typedef {import('./balance.js').Unit} Unit */
|
|
@@ -46,8 +48,15 @@ import { atoms, dress, heightOf, wrap } from "./text.js";
|
|
|
46
48
|
* @typedef {{ canvas: Canvas, open: Group[], gap: number, marks: any[],
|
|
47
49
|
* starts: number[], region: Region | null, pending: Pending | null,
|
|
48
50
|
* measured: WeakMap<any, Measured>,
|
|
51
|
+
* pin: number | null, skipGap: boolean, inHeader: boolean,
|
|
49
52
|
* route: (event: any, block?: Block | null) => void }} Flow
|
|
50
53
|
*/
|
|
54
|
+
/**
|
|
55
|
+
* What flow spacing reads of the page: the cursor and whether a strip is open.
|
|
56
|
+
* Page furniture has a canvas and no region, so the skip helpers take this
|
|
57
|
+
* rather than the whole flow.
|
|
58
|
+
* @typedef {{ canvas: Canvas, region: Region | null }} Cursor
|
|
59
|
+
*/
|
|
51
60
|
|
|
52
61
|
// One flow unit and its presentation; `h` is the full flow height. A text
|
|
53
62
|
// item's is its wrapped lines; an image item's is the picture, sized here so
|
|
@@ -60,7 +69,8 @@ import { atoms, dress, heightOf, wrap } from "./text.js";
|
|
|
60
69
|
* `align` is absent on a split, which has none of its own: alignment is a
|
|
61
70
|
* slot's, reached through the style layering.
|
|
62
71
|
* @typedef {{ lines: Line[], picture?: Picture, parts?: SlotPart[],
|
|
63
|
-
* bg: string | null, align?: any, h: number
|
|
72
|
+
* bg: string | null, align?: any, h: number, style?: any,
|
|
73
|
+
* inset: { t: number, r: number, b: number, l: number } }} Block
|
|
64
74
|
*/
|
|
65
75
|
/**
|
|
66
76
|
* One slot of a split, as laid out: the block it drew to and the width it was
|
|
@@ -84,15 +94,34 @@ let pictureOf = (event, avail) => {
|
|
|
84
94
|
// this is one of its slots. Null everywhere else, where nothing encloses.
|
|
85
95
|
/** @type {(canvas: Canvas, event: any, avail: number, under?: any) => Block} */
|
|
86
96
|
let blockOf = (canvas, event, avail, under = null) => {
|
|
87
|
-
let
|
|
88
|
-
let
|
|
89
|
-
|
|
97
|
+
let text = merge(unbox(under), unbox(event.style));
|
|
98
|
+
let box = event.style;
|
|
99
|
+
let bg = col((box || {}).background);
|
|
100
|
+
let inset = insetOf(box, NO_PAD);
|
|
101
|
+
let inner = Math.max(avail - inset.l - inset.r, 1);
|
|
102
|
+
if (event.type === "split") return splitBlock(canvas, event, inner, text, bg, box, inset);
|
|
90
103
|
if (event.type === "image") {
|
|
91
|
-
let picture = pictureOf(event,
|
|
92
|
-
return {
|
|
104
|
+
let picture = pictureOf(event, inner);
|
|
105
|
+
return {
|
|
106
|
+
lines: [],
|
|
107
|
+
picture,
|
|
108
|
+
bg,
|
|
109
|
+
align: text.align,
|
|
110
|
+
h: picture.h + inset.t + inset.b,
|
|
111
|
+
style: box,
|
|
112
|
+
inset,
|
|
113
|
+
};
|
|
93
114
|
}
|
|
94
|
-
let
|
|
95
|
-
|
|
115
|
+
let size = sizeOf(text, canvas.base);
|
|
116
|
+
let lines = dress(wrap(canvas, atoms(canvas, event.tokens, text), inner, size), text);
|
|
117
|
+
return {
|
|
118
|
+
lines,
|
|
119
|
+
bg,
|
|
120
|
+
align: text.align,
|
|
121
|
+
h: heightOf(lines) + inset.t + inset.b,
|
|
122
|
+
style: box,
|
|
123
|
+
inset,
|
|
124
|
+
};
|
|
96
125
|
};
|
|
97
126
|
|
|
98
127
|
// Scale a picture block down to the room it has. Best-effort, and only ever
|
|
@@ -100,9 +129,10 @@ let blockOf = (canvas, event, avail, under = null) => {
|
|
|
100
129
|
/** @type {(block: Block, room: number) => void} */
|
|
101
130
|
let shrink = (block, room) => {
|
|
102
131
|
let picture = /** @type {Picture} */ (block.picture);
|
|
103
|
-
|
|
104
|
-
picture.
|
|
105
|
-
|
|
132
|
+
let inner = Math.max(room - block.inset.t - block.inset.b, 1);
|
|
133
|
+
picture.w *= inner / picture.h;
|
|
134
|
+
picture.h = inner;
|
|
135
|
+
block.h = inner + block.inset.t + block.inset.b;
|
|
106
136
|
};
|
|
107
137
|
|
|
108
138
|
// Draw one block whole at the cursor. A picture's background fills the
|
|
@@ -111,21 +141,27 @@ let shrink = (block, room) => {
|
|
|
111
141
|
// is one box, so where it sits in the width it was given is worked out once.
|
|
112
142
|
/** @type {(canvas: Canvas, block: Block, x: number, avail: number) => void} */
|
|
113
143
|
let drawPicture = (canvas, block, x, avail) => {
|
|
144
|
+
let inset = block.inset;
|
|
114
145
|
let { bytes, format, w, h } = /** @type {Picture} */ (block.picture);
|
|
115
|
-
let
|
|
116
|
-
|
|
117
|
-
canvas
|
|
118
|
-
canvas.y
|
|
146
|
+
let outer = w + inset.l + inset.r;
|
|
147
|
+
let at = x + shift(block.align, avail - outer);
|
|
148
|
+
paintBox(canvas, at, canvas.y, outer, block.h, block.style, block.bg);
|
|
149
|
+
canvas.picture(bytes, format, at + inset.l, canvas.y - inset.t - h, w, h);
|
|
150
|
+
canvas.y -= block.h;
|
|
119
151
|
canvas.fresh = false;
|
|
120
152
|
};
|
|
121
153
|
|
|
122
154
|
/** @type {(canvas: Canvas, block: Block, x: number, avail: number) => void} */
|
|
123
155
|
let drawLines = (canvas, block, x, avail) => {
|
|
124
|
-
|
|
156
|
+
let inset = block.inset;
|
|
157
|
+
paintBox(canvas, x, canvas.y, avail, block.h, block.style, block.bg);
|
|
158
|
+
let y = canvas.y - inset.t;
|
|
159
|
+
let inner = Math.max(avail - inset.l - inset.r, 1);
|
|
125
160
|
for (let line of block.lines) {
|
|
126
|
-
canvas.drawLine(line, x,
|
|
127
|
-
|
|
161
|
+
canvas.drawLine(line, x + inset.l, y, inner, block.align);
|
|
162
|
+
y -= line.h;
|
|
128
163
|
}
|
|
164
|
+
canvas.y -= block.h;
|
|
129
165
|
canvas.fresh = false;
|
|
130
166
|
};
|
|
131
167
|
|
|
@@ -140,20 +176,20 @@ let drawLines = (canvas, block, x, avail) => {
|
|
|
140
176
|
// inheritance in the HTML target and the `under` layer in XLSX. It has no
|
|
141
177
|
// `align` of its own: alignment is a slot's, reached through that layering.
|
|
142
178
|
/**
|
|
143
|
-
* @type {(canvas: Canvas, event: any,
|
|
144
|
-
* bg: string | null) => Block}
|
|
179
|
+
* @type {(canvas: Canvas, event: any, inner: number, text: any,
|
|
180
|
+
* bg: string | null, box: any, inset: { t: number, r: number, b: number, l: number }) => Block}
|
|
145
181
|
*/
|
|
146
|
-
let splitBlock = (canvas, event,
|
|
147
|
-
let widths = slotWidths(event.slots,
|
|
182
|
+
let splitBlock = (canvas, event, inner, text, bg, box, inset) => {
|
|
183
|
+
let widths = slotWidths(event.slots, inner);
|
|
148
184
|
/** @type {SlotPart[]} */
|
|
149
185
|
let parts = [];
|
|
150
186
|
let h = 0;
|
|
151
187
|
for (let [i, slot] of event.items.entries()) {
|
|
152
|
-
let block = blockOf(canvas, slot, Math.max(widths[i], 1),
|
|
188
|
+
let block = blockOf(canvas, slot, Math.max(widths[i], 1), text);
|
|
153
189
|
if (block.h > h) h = block.h;
|
|
154
190
|
parts.push({ block, w: widths[i] });
|
|
155
191
|
}
|
|
156
|
-
return { lines: [], parts, bg, h };
|
|
192
|
+
return { lines: [], parts, bg, h: h + inset.t + inset.b, style: box, inset };
|
|
157
193
|
};
|
|
158
194
|
|
|
159
195
|
/** @type {(slots: { width?: number }[], avail: number) => number[]} */
|
|
@@ -205,17 +241,17 @@ let splitFolder = (emit) => {
|
|
|
205
241
|
|
|
206
242
|
/** @type {(canvas: Canvas, block: Block, x: number, avail: number) => void} */
|
|
207
243
|
let drawSplit = (canvas, block, x, avail) => {
|
|
208
|
-
|
|
209
|
-
let
|
|
210
|
-
|
|
244
|
+
let inset = block.inset;
|
|
245
|
+
let yTop = canvas.y;
|
|
246
|
+
paintBox(canvas, x, yTop, avail, block.h, block.style, block.bg);
|
|
247
|
+
let top = yTop - inset.t;
|
|
248
|
+
let at = x + inset.l;
|
|
211
249
|
for (let part of block.parts || []) {
|
|
212
250
|
canvas.y = top;
|
|
213
251
|
drawBlock(canvas, part.block, at, part.w);
|
|
214
252
|
at += part.w;
|
|
215
253
|
}
|
|
216
|
-
|
|
217
|
-
// beside a wrapped one does not pull the band up.
|
|
218
|
-
canvas.y = top - block.h;
|
|
254
|
+
canvas.y = yTop - block.h;
|
|
219
255
|
canvas.fresh = false;
|
|
220
256
|
};
|
|
221
257
|
|
|
@@ -270,7 +306,7 @@ let ceilOf = (state) => (state.region ? state.region.top : state.canvas.top);
|
|
|
270
306
|
// leaving an empty page behind — so a strip claims no second meaning for it.
|
|
271
307
|
// A block of no height would read fresh here and used there; none is reachable
|
|
272
308
|
// today, since every item wraps to at least one line.
|
|
273
|
-
/** @type {(state:
|
|
309
|
+
/** @type {(state: Cursor) => boolean} */
|
|
274
310
|
let freshOf = (state) => (state.region ? state.canvas.y === state.region.top : state.canvas.fresh);
|
|
275
311
|
|
|
276
312
|
/** @type {(canvas: Canvas, h: number, line: Line, floor: number) => boolean} */
|
|
@@ -330,6 +366,25 @@ let fitLines = (canvas, block, i, floor, fresh) => {
|
|
|
330
366
|
/** @type {(canvas: Canvas, block: Block, floor: number) => boolean} */
|
|
331
367
|
let whole = (canvas, block, floor) => canvas.y - block.h >= floor;
|
|
332
368
|
|
|
369
|
+
/** @type {(style: any, name: string) => number} */
|
|
370
|
+
let spaceOf = (style, name) => {
|
|
371
|
+
let n = style?.[name];
|
|
372
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
/** @type {(event: any) => boolean} */
|
|
376
|
+
let pageBand = (event) => event.role === "page-header" || event.role === "page-footer";
|
|
377
|
+
/** @type {(state: Cursor, event: any) => boolean} */
|
|
378
|
+
let keepLead = (state, event) => pageBand(event) || !freshOf(state);
|
|
379
|
+
/** @type {(state: Cursor, event: any) => number} */
|
|
380
|
+
let leadOf = (state, event) => (keepLead(state, event) ? spaceOf(event.style, "spaceBefore") : 0);
|
|
381
|
+
/** @type {(state: Cursor, event: any, name: string) => void} */
|
|
382
|
+
let skipFlow = (state, event, name) => {
|
|
383
|
+
state.canvas.y -= name === "spaceBefore" ? leadOf(state, event) : spaceOf(event.style, name);
|
|
384
|
+
};
|
|
385
|
+
/** @type {(state: Cursor, event: any) => number} */
|
|
386
|
+
let flowPad = (state, event) => leadOf(state, event) + spaceOf(event.style, "spaceAfter");
|
|
387
|
+
|
|
333
388
|
// The unsplittable blocks -- a picture and a split -- move whole to a fresh
|
|
334
389
|
// column rather than breaking, so a balanced floor never cuts one: only the
|
|
335
390
|
// page's own floor bears on it. A picture that no column can hold is scaled
|
|
@@ -392,9 +447,15 @@ let item = (state, event, measured = null) => {
|
|
|
392
447
|
let canvas = state.canvas;
|
|
393
448
|
let avail = widthOf(state);
|
|
394
449
|
let block = measured || blockOf(canvas, event, avail);
|
|
395
|
-
|
|
396
|
-
if (whole(canvas,
|
|
450
|
+
let padded = { ...block, h: block.h + flowPad(state, event) };
|
|
451
|
+
if (whole(canvas, padded, floorOf(state))) {
|
|
452
|
+
skipFlow(state, event, "spaceBefore");
|
|
453
|
+
drawBlock(canvas, block, originOf(state), avail);
|
|
454
|
+
skipFlow(state, event, "spaceAfter");
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
397
457
|
overflow(state, block);
|
|
458
|
+
skipFlow(state, event, "spaceAfter");
|
|
398
459
|
};
|
|
399
460
|
|
|
400
461
|
// What a block does when it does not fit where it stands. A picture and a
|
|
@@ -547,8 +608,9 @@ let closeRegion = (state) => {
|
|
|
547
608
|
// Two things this deliberately does not model, each stated because the cost of
|
|
548
609
|
// getting them wrong is a strip that stops early rather than a wrong document.
|
|
549
610
|
// A `total-row` is a row like any other, though it really keeps the last data
|
|
550
|
-
// row with
|
|
551
|
-
// to remove an error of one row at the end of a
|
|
611
|
+
// row with the whole emitted total block (SCHEMA.md): pairing them costs the
|
|
612
|
+
// flat per-entry map this is, to remove an error of one row at the end of a
|
|
613
|
+
// table. And a row taller than
|
|
552
614
|
// any strip is sliced rather than moved (SCHEMA.md, "How a tall row slices");
|
|
553
615
|
// called whole here, it makes every candidate height fail and the region
|
|
554
616
|
// fills, which is the honest answer for a region holding one.
|
|
@@ -700,11 +762,13 @@ let estimate = (state, event) => (WORTH[event.type] || (() => 0))(state.canvas);
|
|
|
700
762
|
// What each kind of buffered event is worth. A module constant: `estimate` runs
|
|
701
763
|
// once per buffered event, and rebuilding this table each time would allocate
|
|
702
764
|
// a closure per table row.
|
|
765
|
+
/** @type {(canvas: Canvas) => number} */
|
|
766
|
+
let rowWorth = (canvas) => rowHeight(canvas) + CELL_PAD.t + CELL_PAD.b;
|
|
703
767
|
/** @type {Record<string, (canvas: Canvas) => number>} */
|
|
704
768
|
let WORTH = {
|
|
705
|
-
row:
|
|
706
|
-
"total-row":
|
|
707
|
-
"table-start":
|
|
769
|
+
row: rowWorth,
|
|
770
|
+
"total-row": rowWorth,
|
|
771
|
+
"table-start": rowWorth,
|
|
708
772
|
"group-start": (canvas) => instanceGap(canvas),
|
|
709
773
|
};
|
|
710
774
|
|
|
@@ -759,8 +823,8 @@ let remeasure = (state, region) => {
|
|
|
759
823
|
// Reports what the correction moved the running total by.
|
|
760
824
|
/** @type {(span: any[], measured: Measured) => number} */
|
|
761
825
|
let retune = (span, measured) => {
|
|
762
|
-
let heights = [measured.head.h, ...measured.laid.map((/** @type {any} */ laid) => laid.
|
|
763
|
-
if (measured.
|
|
826
|
+
let heights = [measured.head.h, ...measured.laid.map((/** @type {any} */ laid) => laid.h)];
|
|
827
|
+
if (measured.totalRows) for (let row of measured.totalRows) heights.push(row.h);
|
|
764
828
|
let delta = 0;
|
|
765
829
|
for (let [i, h] of heights.entries()) {
|
|
766
830
|
delta += h - span[i].h;
|
|
@@ -881,7 +945,7 @@ let keepWith = (block) =>
|
|
|
881
945
|
/** @type {(state: Flow, event: any, measured?: Block | null) => void} */
|
|
882
946
|
let placeItem = (state, event, measured = null) => {
|
|
883
947
|
let block = measured || keepBlock(state, event, widthOf(state));
|
|
884
|
-
if (block && holding(state)) flush(state, keepWith(block));
|
|
948
|
+
if (block && holding(state)) flush(state, keepWith(block) + flowPad(state, event));
|
|
885
949
|
item(state, event, block);
|
|
886
950
|
};
|
|
887
951
|
|
|
@@ -910,7 +974,8 @@ let holding = (state) => state.open.some((group) => group.held.length) || state.
|
|
|
910
974
|
let openGroup = (state, event) => {
|
|
911
975
|
breakFor(state, event);
|
|
912
976
|
if (event.columns) state.pending = { count: event.columns, owner: event.depth };
|
|
913
|
-
state.gap = Math.max(state.gap, instanceGap(state.canvas));
|
|
977
|
+
state.gap = state.skipGap ? 0 : Math.max(state.gap, instanceGap(state.canvas));
|
|
978
|
+
state.skipGap = false;
|
|
914
979
|
state.open.push({ held: [], blocks: [] });
|
|
915
980
|
state.marks.push(markFor(event));
|
|
916
981
|
};
|
|
@@ -970,16 +1035,22 @@ let headOf = (canvas, span) => span - instanceGap(canvas);
|
|
|
970
1035
|
// gives flow spacing no meaning inside a table row, in either target.
|
|
971
1036
|
/** @type {(canvas: Canvas, cell: any, rowStyle: any) => any} */
|
|
972
1037
|
let cellOf = (canvas, cell, rowStyle) => {
|
|
973
|
-
let style =
|
|
1038
|
+
let style = merge(unbox(rowStyle), unbox(cell.style));
|
|
1039
|
+
let inset = insetOf(cell.style, CELL_PAD);
|
|
974
1040
|
let list = atoms(canvas, cell.tokens, style);
|
|
975
|
-
let natural = wrap(canvas, list, Infinity
|
|
1041
|
+
let natural = wrap(canvas, list, Infinity, sizeOf(style, canvas.base)).reduce(
|
|
1042
|
+
(widest, line) => Math.max(widest, line.w),
|
|
1043
|
+
0,
|
|
1044
|
+
);
|
|
976
1045
|
return {
|
|
977
1046
|
list,
|
|
978
|
-
natural: natural +
|
|
1047
|
+
natural: natural + inset.l + inset.r,
|
|
979
1048
|
align: style.align,
|
|
980
1049
|
bg: col(style.background),
|
|
981
1050
|
underline: !!style.underline,
|
|
982
1051
|
strikethrough: !!style.strikethrough,
|
|
1052
|
+
inset,
|
|
1053
|
+
style: cell.style,
|
|
983
1054
|
};
|
|
984
1055
|
};
|
|
985
1056
|
|
|
@@ -987,9 +1058,14 @@ let cellOf = (canvas, cell, rowStyle) => {
|
|
|
987
1058
|
let rowOf = (canvas, cells, widths) => {
|
|
988
1059
|
let h = rowHeight(canvas);
|
|
989
1060
|
let out = cells.map((cell, i) => {
|
|
990
|
-
let inner = Math.max(widths[i] -
|
|
991
|
-
|
|
992
|
-
let
|
|
1061
|
+
let inner = Math.max(widths[i] - cell.inset.l - cell.inset.r, 1);
|
|
1062
|
+
// A glyph or a hard break occupies; spaces alone are an empty cell.
|
|
1063
|
+
let lines = cell.list.some(
|
|
1064
|
+
(/** @type {{ hard: boolean, space: boolean }} */ atom) => atom.hard || !atom.space,
|
|
1065
|
+
)
|
|
1066
|
+
? dress(wrap(canvas, cell.list, inner, cell.list[0].size), cell)
|
|
1067
|
+
: [];
|
|
1068
|
+
let cellHeight = heightOf(lines) + cell.inset.t + cell.inset.b;
|
|
993
1069
|
if (cellHeight > h) h = cellHeight;
|
|
994
1070
|
return { ...cell, lines };
|
|
995
1071
|
});
|
|
@@ -1003,8 +1079,9 @@ let fillCell = (canvas, cell, bg, x, y, w, h) => {
|
|
|
1003
1079
|
|
|
1004
1080
|
/** @type {(canvas: Canvas, cell: any, lines: any[], x: number, y: number, w: number) => void} */
|
|
1005
1081
|
let writeCell = (canvas, cell, lines, x, y, w) => {
|
|
1082
|
+
let inset = cell.inset;
|
|
1006
1083
|
for (let line of lines) {
|
|
1007
|
-
canvas.drawLine(line, x +
|
|
1084
|
+
canvas.drawLine(line, x + inset.l, y, w - inset.l - inset.r, cell.align);
|
|
1008
1085
|
y -= line.h;
|
|
1009
1086
|
}
|
|
1010
1087
|
};
|
|
@@ -1018,9 +1095,10 @@ let writeCell = (canvas, cell, lines, x, y, w) => {
|
|
|
1018
1095
|
*/
|
|
1019
1096
|
let paintCells = (canvas, cells, lines, h, padTop, xOffsets, widths, bg) => {
|
|
1020
1097
|
if (bg) canvas.rect(bg, xOffsets[0], canvas.y - h, sum(widths), h);
|
|
1021
|
-
let y0 = canvas.y - (padTop ? PADY : 0);
|
|
1022
1098
|
for (let [i, cell] of cells.entries()) {
|
|
1023
1099
|
fillCell(canvas, cell, bg, xOffsets[i], canvas.y - h, widths[i], h);
|
|
1100
|
+
paintBox(canvas, xOffsets[i], canvas.y, widths[i], h, cell.style, null);
|
|
1101
|
+
let y0 = canvas.y - (padTop ? cell.inset.t : 0);
|
|
1024
1102
|
writeCell(canvas, cell, lines[i], xOffsets[i], y0, widths[i]);
|
|
1025
1103
|
}
|
|
1026
1104
|
canvas.y -= h;
|
|
@@ -1028,10 +1106,11 @@ let paintCells = (canvas, cells, lines, h, padTop, xOffsets, widths, bg) => {
|
|
|
1028
1106
|
};
|
|
1029
1107
|
|
|
1030
1108
|
/**
|
|
1031
|
-
* @type {(canvas: Canvas, row:
|
|
1109
|
+
* @type {(canvas: Canvas, row: TableRow, xOffsets: number[],
|
|
1032
1110
|
* widths: number[], bg: string | null) => void}
|
|
1033
1111
|
*/
|
|
1034
|
-
let drawRow = (canvas, row, xOffsets, widths, bg) =>
|
|
1112
|
+
let drawRow = (canvas, row, xOffsets, widths, bg) => {
|
|
1113
|
+
let yTop = canvas.y;
|
|
1035
1114
|
paintCells(
|
|
1036
1115
|
canvas,
|
|
1037
1116
|
row.cells,
|
|
@@ -1042,14 +1121,17 @@ let drawRow = (canvas, row, xOffsets, widths, bg) =>
|
|
|
1042
1121
|
widths,
|
|
1043
1122
|
bg,
|
|
1044
1123
|
);
|
|
1124
|
+
paintBox(canvas, xOffsets[0], yTop, sum(widths), row.h, row.style, null);
|
|
1125
|
+
};
|
|
1045
1126
|
|
|
1046
|
-
// The widest of each column's header, rows and
|
|
1047
|
-
/** @type {(cols: any[], header: any[], rows: any[],
|
|
1048
|
-
let naturalWidths = (cols, header, rows,
|
|
1127
|
+
// The widest of each column's header, rows and totals, padding included.
|
|
1128
|
+
/** @type {(cols: any[], header: any[], rows: any[], totals: { cells: any[] }[]) => number[]} */
|
|
1129
|
+
let naturalWidths = (cols, header, rows, totals) =>
|
|
1049
1130
|
cols.map((_, i) => {
|
|
1050
1131
|
let widest = header[i].natural;
|
|
1051
1132
|
for (let row of rows) widest = Math.max(widest, row.cells[i].natural);
|
|
1052
|
-
|
|
1133
|
+
for (let row of totals) widest = Math.max(widest, row.cells[i].natural);
|
|
1134
|
+
return widest;
|
|
1053
1135
|
});
|
|
1054
1136
|
|
|
1055
1137
|
/** @type {(values: number[]) => number} */
|
|
@@ -1085,14 +1167,10 @@ let columnWidths = (cols, natural, avail) => {
|
|
|
1085
1167
|
return widths;
|
|
1086
1168
|
};
|
|
1087
1169
|
|
|
1088
|
-
// The rule under a table's header row and over its total: the only rule this
|
|
1089
|
-
// target draws, always the full content width at the cursor.
|
|
1090
|
-
/** @type {(canvas: Canvas, x: number, avail: number) => void} */
|
|
1091
|
-
let underline = (canvas, x, avail) => canvas.rule(x, x + avail, canvas.y);
|
|
1092
|
-
|
|
1093
1170
|
// One table being emitted: what `carryOver`, `sliced` and `put` all need.
|
|
1094
1171
|
/**
|
|
1095
|
-
* @typedef {{
|
|
1172
|
+
* @typedef {{ cells: any[], h: number, style?: any }} TableRow
|
|
1173
|
+
* @typedef {{ state: Flow, head: TableRow,
|
|
1096
1174
|
* xOffsets: number[], widths: number[], x: number, avail: number }} Grid
|
|
1097
1175
|
*/
|
|
1098
1176
|
|
|
@@ -1104,12 +1182,14 @@ let underline = (canvas, x, avail) => canvas.rule(x, x + avail, canvas.y);
|
|
|
1104
1182
|
// The headings replay at a strip head as well as a page head, unlike a group
|
|
1105
1183
|
// header (SCHEMA.md): a column of figures under nothing is unreadable, where a
|
|
1106
1184
|
// group header is merely absent.
|
|
1185
|
+
/** @type {(row: { style?: any }) => any} */
|
|
1186
|
+
let rowFill = (row) => col((row.style || {}).background);
|
|
1187
|
+
|
|
1107
1188
|
/** @type {(grid: Grid) => void} */
|
|
1108
1189
|
let carryOver = (grid) => {
|
|
1109
1190
|
advance(grid.state);
|
|
1110
1191
|
rebase(grid);
|
|
1111
|
-
drawRow(grid.state.canvas, grid.head, grid.xOffsets, grid.widths,
|
|
1112
|
-
underline(grid.state.canvas, grid.x, grid.avail);
|
|
1192
|
+
drawRow(grid.state.canvas, grid.head, grid.xOffsets, grid.widths, rowFill(grid.head));
|
|
1113
1193
|
};
|
|
1114
1194
|
|
|
1115
1195
|
// Move the grid to the column the cursor is now in. A table's widths are its
|
|
@@ -1129,7 +1209,7 @@ let firstSlice = (canvas, row) =>
|
|
|
1129
1209
|
rowHeight(canvas),
|
|
1130
1210
|
...row.cells
|
|
1131
1211
|
.filter((/** @type {any} */ cell) => cell.lines.length)
|
|
1132
|
-
.map((/** @type {any} */ cell) =>
|
|
1212
|
+
.map((/** @type {any} */ cell) => cell.inset.t + cell.lines[0].h),
|
|
1133
1213
|
);
|
|
1134
1214
|
|
|
1135
1215
|
/** @type {(cell: any, next: number[], i: number, used: number, cap: number) => any[]} */
|
|
@@ -1149,10 +1229,17 @@ let takeLines = (cell, next, i, used, cap) => {
|
|
|
1149
1229
|
let stillMore = (row, next) =>
|
|
1150
1230
|
row.cells.some((/** @type {any} */ cell, /** @type {number} */ i) => next[i] < cell.lines.length);
|
|
1151
1231
|
|
|
1152
|
-
/** @type {(
|
|
1153
|
-
let
|
|
1154
|
-
|
|
1155
|
-
|
|
1232
|
+
/** @type {(cell: any, first: boolean, more: boolean) => number} */
|
|
1233
|
+
let vPad = (cell, first, more) => (first ? cell.inset.t : 0) + (more ? 0 : cell.inset.b);
|
|
1234
|
+
|
|
1235
|
+
/** @type {(taken: any[][], cells: any[], first: boolean, more: boolean) => number} */
|
|
1236
|
+
let sliceH = (taken, cells, first, more) => {
|
|
1237
|
+
let h = 0;
|
|
1238
|
+
for (let [i, lines] of taken.entries()) {
|
|
1239
|
+
let cellH = vPad(cells[i], first, more) + heightOf(lines);
|
|
1240
|
+
if (cellH > h) h = cellH;
|
|
1241
|
+
}
|
|
1242
|
+
return h;
|
|
1156
1243
|
};
|
|
1157
1244
|
|
|
1158
1245
|
// A row no page can hold degrades to plain flow: each page takes what fits of
|
|
@@ -1164,13 +1251,11 @@ let sliced = (grid, row, bg) => {
|
|
|
1164
1251
|
let first = true;
|
|
1165
1252
|
for (;;) {
|
|
1166
1253
|
let cap = canvas.y - canvas.bottom;
|
|
1167
|
-
let pad = first ? PADY : 0;
|
|
1168
1254
|
let taken = row.cells.map((/** @type {any} */ cell, /** @type {number} */ i) =>
|
|
1169
|
-
takeLines(cell, next, i,
|
|
1255
|
+
takeLines(cell, next, i, first ? cell.inset.t : 0, cap),
|
|
1170
1256
|
);
|
|
1171
1257
|
let more = stillMore(row, next);
|
|
1172
|
-
|
|
1173
|
-
let h = sliceH(taken, pad, more);
|
|
1258
|
+
let h = sliceH(taken, row.cells, first, more);
|
|
1174
1259
|
paintCells(canvas, row.cells, taken, h, first, grid.xOffsets, grid.widths, bg);
|
|
1175
1260
|
if (!more) return;
|
|
1176
1261
|
carryOver(grid);
|
|
@@ -1196,21 +1281,13 @@ let shouldBreak = (state, row, keep, cap) => {
|
|
|
1196
1281
|
return canvas.y - row.h - keep < floor && worthBreak(canvas, row, cap, floor) && !freshOf(state);
|
|
1197
1282
|
};
|
|
1198
1283
|
|
|
1199
|
-
/** @type {(grid: Grid, topRule: boolean, broke: boolean) => void} */
|
|
1200
|
-
let openRow = (grid, topRule, broke) => {
|
|
1201
|
-
if (broke) carryOver(grid);
|
|
1202
|
-
if (topRule && !broke) underline(grid.state.canvas, grid.x, grid.avail);
|
|
1203
|
-
};
|
|
1204
|
-
|
|
1205
1284
|
// Place one row, breaking first when that helps. `keep` is a companion row it
|
|
1206
1285
|
// must not be parted from — the total riding with the last data row.
|
|
1207
|
-
// `topRule` is the total's opening rule, which after a break would double the
|
|
1208
|
-
// replayed header's.
|
|
1209
1286
|
/**
|
|
1210
|
-
* @type {(grid: Grid, row:
|
|
1211
|
-
* keep?: number
|
|
1287
|
+
* @type {(grid: Grid, row: TableRow, bg: string | null,
|
|
1288
|
+
* keep?: number) => void}
|
|
1212
1289
|
*/
|
|
1213
|
-
let put = (grid, row, bg, keep = 0
|
|
1290
|
+
let put = (grid, row, bg, keep = 0) => {
|
|
1214
1291
|
let canvas = grid.state.canvas;
|
|
1215
1292
|
// The tallest row a fresh column holds, after the replayed group headers and
|
|
1216
1293
|
// the replayed column headings. A companion over that can never share a
|
|
@@ -1222,33 +1299,36 @@ let put = (grid, row, bg, keep = 0, topRule = false) => {
|
|
|
1222
1299
|
// and leaves a headings-only column. Clamped to `cap`: a first slice no fresh
|
|
1223
1300
|
// column could fit either is no reason to go looking for one.
|
|
1224
1301
|
let broke = shouldBreak(grid.state, row, keep, cap);
|
|
1225
|
-
|
|
1302
|
+
if (broke) carryOver(grid);
|
|
1226
1303
|
if (canvas.y - row.h < canvas.bottom) return sliced(grid, row, bg);
|
|
1227
1304
|
drawRow(canvas, row, grid.xOffsets, grid.widths, bg);
|
|
1228
1305
|
};
|
|
1229
1306
|
|
|
1230
|
-
/** @type {(canvas: Canvas, row: any) => { cells: any[],
|
|
1307
|
+
/** @type {(canvas: Canvas, row: any) => { cells: any[], style: any }} */
|
|
1231
1308
|
let bodyRow = (canvas, row) => ({
|
|
1232
1309
|
cells: row.cells.map((/** @type {any} */ cell) => cellOf(canvas, cell, row.style)),
|
|
1233
|
-
|
|
1310
|
+
style: row.style,
|
|
1234
1311
|
});
|
|
1235
1312
|
|
|
1236
1313
|
/** @type {(canvas: Canvas, buffered: any) => any} */
|
|
1237
|
-
let
|
|
1238
|
-
buffered.
|
|
1314
|
+
let totalRowsOf = (canvas, buffered) =>
|
|
1315
|
+
buffered.totals.map((/** @type {any} */ row) => ({
|
|
1316
|
+
cells: row.cells.map((/** @type {any} */ cell) => cellOf(canvas, cell, row.style)),
|
|
1317
|
+
style: row.style,
|
|
1318
|
+
}));
|
|
1239
1319
|
|
|
1240
|
-
/** @type {(laid: any[],
|
|
1241
|
-
let firstH = (laid,
|
|
1320
|
+
/** @type {(laid: any[], totalRows: any[]) => number} */
|
|
1321
|
+
let firstH = (laid, totalRows) => (laid.length ? laid[0].h : totalRows.length ? totalRows[0].h : 0);
|
|
1242
1322
|
|
|
1243
|
-
/** @type {(
|
|
1244
|
-
let
|
|
1323
|
+
/** @type {(totalRows: any[]) => number} */
|
|
1324
|
+
let totalsH = (totalRows) => totalRows.reduce((h, row) => h + row.h, 0);
|
|
1245
1325
|
|
|
1246
|
-
/** @type {(grid: Grid, laid: any[],
|
|
1247
|
-
let emitRows = (grid, laid,
|
|
1326
|
+
/** @type {(grid: Grid, laid: any[], totalRows: any[]) => void} */
|
|
1327
|
+
let emitRows = (grid, laid, totalRows) => {
|
|
1248
1328
|
let last = laid.length - 1;
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1329
|
+
let keep = totalsH(totalRows);
|
|
1330
|
+
for (let [i, row] of laid.entries()) put(grid, row, rowFill(row), i === last ? keep : 0);
|
|
1331
|
+
for (let row of totalRows) put(grid, row, rowFill(row), 0);
|
|
1252
1332
|
};
|
|
1253
1333
|
|
|
1254
1334
|
// Lay out a buffered table: measure every cell, allocate the columns, then
|
|
@@ -1259,14 +1339,19 @@ let emitRows = (grid, laid, totalRow) => {
|
|
|
1259
1339
|
// events as they arrive, and the region buffer, reading them back off what it
|
|
1260
1340
|
// held — so a new table event kind cannot reach one and miss the other.
|
|
1261
1341
|
/** @type {(events: any[]) => any} */
|
|
1262
|
-
let tableOf = (events) =>
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1342
|
+
let tableOf = (events) => {
|
|
1343
|
+
let opening = events[0];
|
|
1344
|
+
let totals = events.filter((event) => event.type === "total-row");
|
|
1345
|
+
return {
|
|
1346
|
+
columns: opening.columns,
|
|
1347
|
+
headerStyle: opening.style,
|
|
1348
|
+
rows: events.filter((event) => event.type === "row"),
|
|
1349
|
+
totals: totals.map((row) => ({ cells: row.cells, style: row.style })),
|
|
1350
|
+
};
|
|
1351
|
+
};
|
|
1267
1352
|
|
|
1268
1353
|
/**
|
|
1269
|
-
* @typedef {{ widths: number[], head: any, laid: any[],
|
|
1354
|
+
* @typedef {{ widths: number[], head: any, laid: any[], totalRows: any[] }} Measured
|
|
1270
1355
|
*/
|
|
1271
1356
|
// Measure a buffered table at a given width: the column widths every cell has
|
|
1272
1357
|
// a say in, then each row laid out against them. Everything here is
|
|
@@ -1276,18 +1361,23 @@ let tableOf = (events) => ({
|
|
|
1276
1361
|
/** @type {(canvas: Canvas, buffered: any, avail: number) => Measured} */
|
|
1277
1362
|
let measureTable = (canvas, buffered, avail) => {
|
|
1278
1363
|
let cols = /** @type {any[]} */ (buffered.columns);
|
|
1279
|
-
let header = cols.map((/** @type {any} */ col) =>
|
|
1364
|
+
let header = cols.map((/** @type {any} */ col) =>
|
|
1365
|
+
cellOf(canvas, col.header, buffered.headerStyle),
|
|
1366
|
+
);
|
|
1280
1367
|
let rows = buffered.rows.map((/** @type {any} */ row) => bodyRow(canvas, row));
|
|
1281
|
-
let
|
|
1282
|
-
let widths = columnWidths(cols, naturalWidths(cols, header, rows,
|
|
1368
|
+
let totals = totalRowsOf(canvas, buffered);
|
|
1369
|
+
let widths = columnWidths(cols, naturalWidths(cols, header, rows, totals), avail);
|
|
1283
1370
|
return {
|
|
1284
1371
|
widths,
|
|
1285
|
-
head: rowOf(canvas, header, widths),
|
|
1372
|
+
head: { ...rowOf(canvas, header, widths), style: buffered.headerStyle },
|
|
1286
1373
|
laid: rows.map((/** @type {any} */ row) => ({
|
|
1287
|
-
|
|
1288
|
-
|
|
1374
|
+
...rowOf(canvas, row.cells, widths),
|
|
1375
|
+
style: row.style,
|
|
1376
|
+
})),
|
|
1377
|
+
totalRows: totals.map((/** @type {any} */ row) => ({
|
|
1378
|
+
...rowOf(canvas, row.cells, widths),
|
|
1379
|
+
style: row.style,
|
|
1289
1380
|
})),
|
|
1290
|
-
totalRow: total && rowOf(canvas, total, widths),
|
|
1291
1381
|
};
|
|
1292
1382
|
};
|
|
1293
1383
|
|
|
@@ -1302,7 +1392,7 @@ let table = (state, buffered) => {
|
|
|
1302
1392
|
// Measured already if a region buffered this table: the balance model needed
|
|
1303
1393
|
// its real row heights, at this same width, and that is the same
|
|
1304
1394
|
// measurement — so the replay draws it without wrapping every cell again.
|
|
1305
|
-
let { widths, head, laid,
|
|
1395
|
+
let { widths, head, laid, totalRows } =
|
|
1306
1396
|
state.measured.get(buffered.opening) || measureTable(canvas, buffered, avail);
|
|
1307
1397
|
let xOffsets = widths.map((_, i) => x + sum(widths.slice(0, i)));
|
|
1308
1398
|
|
|
@@ -1310,16 +1400,15 @@ let table = (state, buffered) => {
|
|
|
1310
1400
|
let grid = { state, head, xOffsets, widths, x, avail };
|
|
1311
1401
|
|
|
1312
1402
|
// A pending group header keeps the table's header row and its first row.
|
|
1313
|
-
flush(state, grid.head.h + firstH(laid,
|
|
1403
|
+
flush(state, grid.head.h + firstH(laid, totalRows));
|
|
1314
1404
|
if (canvas.y - grid.head.h < floorOf(state)) {
|
|
1315
1405
|
advance(state);
|
|
1316
1406
|
rebase(grid);
|
|
1317
1407
|
}
|
|
1318
|
-
drawRow(canvas, grid.head, grid.xOffsets, widths,
|
|
1319
|
-
|
|
1320
|
-
//
|
|
1321
|
-
|
|
1322
|
-
emitRows(grid, laid, totalRow);
|
|
1408
|
+
drawRow(canvas, grid.head, grid.xOffsets, widths, rowFill(grid.head));
|
|
1409
|
+
// The last data row keeps the whole emitted total block with it, so a
|
|
1410
|
+
// total is never stranded alone at a page top.
|
|
1411
|
+
emitRows(grid, laid, totalRows);
|
|
1323
1412
|
};
|
|
1324
1413
|
|
|
1325
1414
|
// --- page bands -------------------------------------------------------------
|
|
@@ -1335,9 +1424,11 @@ let band = (canvas, items, yTop) => {
|
|
|
1335
1424
|
canvas.fresh = false;
|
|
1336
1425
|
// Folded straight into the draw, the way the body walk folds straight into
|
|
1337
1426
|
// `route`: the folder emits in order and nothing here needs the band whole.
|
|
1338
|
-
let fold = splitFolder((event) =>
|
|
1339
|
-
|
|
1340
|
-
|
|
1427
|
+
let fold = splitFolder((event) => {
|
|
1428
|
+
skipFlow({ canvas, region: null }, event, "spaceBefore");
|
|
1429
|
+
drawBlock(canvas, blockOf(canvas, event, avail), canvas.margin, avail);
|
|
1430
|
+
skipFlow({ canvas, region: null }, event, "spaceAfter");
|
|
1431
|
+
});
|
|
1341
1432
|
for (let event of items) fold(event);
|
|
1342
1433
|
let h = yTop - canvas.y;
|
|
1343
1434
|
canvas.y = saved;
|
|
@@ -1393,7 +1484,11 @@ let reserve = (geo, fonts, bands) => {
|
|
|
1393
1484
|
// from.
|
|
1394
1485
|
/** @type {(canvas: Canvas) => Frame} */
|
|
1395
1486
|
let pageBox = (canvas) =>
|
|
1396
|
-
frame(canvas.width, canvas.height, canvas.margin, canvas.base, canvas.
|
|
1487
|
+
Object.assign(frame(canvas.width, canvas.height, canvas.margin, canvas.base, canvas.family), {
|
|
1488
|
+
locale: canvas.locale,
|
|
1489
|
+
currency: canvas.currency,
|
|
1490
|
+
timeZone: canvas.timeZone,
|
|
1491
|
+
});
|
|
1397
1492
|
|
|
1398
1493
|
// One finished page's furniture, drawn in the strips `reserve` left for it:
|
|
1399
1494
|
// the header hanging from the top margin, the footer resting on the bottom
|
|
@@ -1458,6 +1553,9 @@ let flow = (canvas) => {
|
|
|
1458
1553
|
// the replay draws that table without wrapping every cell a second time.
|
|
1459
1554
|
// Per flow, because the keys are one render's events.
|
|
1460
1555
|
measured: new WeakMap(),
|
|
1556
|
+
pin: null,
|
|
1557
|
+
skipGap: false,
|
|
1558
|
+
inHeader: false,
|
|
1461
1559
|
// Assigned below, once `route` exists: a buffered region replays through
|
|
1462
1560
|
// it, and one entry point is what makes the replay take the path the
|
|
1463
1561
|
// content would have taken had it never been held.
|
|
@@ -1534,11 +1632,28 @@ let flow = (canvas) => {
|
|
|
1534
1632
|
/** @type {(event: any) => boolean} */
|
|
1535
1633
|
let ownFooter = (event) => event.role === "group-footer" && owner() === state.open.length - 1;
|
|
1536
1634
|
|
|
1635
|
+
/** @type {(event: any) => boolean} */
|
|
1636
|
+
let stillHeader = (event) =>
|
|
1637
|
+
event.role === "report-header" || (event.type === "split-end" && state.inHeader);
|
|
1638
|
+
/** @type {(event: any) => void} */
|
|
1639
|
+
let snapPin = (event) => {
|
|
1640
|
+
if (state.pin == null) return;
|
|
1641
|
+
if (stillHeader(event)) {
|
|
1642
|
+
state.inHeader = true;
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (state.canvas.y < state.pin) throw Error("header: content taller than height");
|
|
1646
|
+
state.canvas.y = state.pin;
|
|
1647
|
+
state.pin = null;
|
|
1648
|
+
state.inHeader = false;
|
|
1649
|
+
};
|
|
1650
|
+
|
|
1537
1651
|
// Route one event: the declaring node's own bands stay full-width, region
|
|
1538
1652
|
// content opens the strips owed to it, and anything arriving while a region
|
|
1539
1653
|
// is still deciding is buffered rather than placed.
|
|
1540
1654
|
/** @type {(event: any, block?: Block | null) => void} */
|
|
1541
1655
|
let route = (event, block = null) => {
|
|
1656
|
+
snapPin(event);
|
|
1542
1657
|
if (fullBand(event)) fullWidth(event, block);
|
|
1543
1658
|
else if (deciding()) buffer(state, event, block);
|
|
1544
1659
|
else if (opens(event)) begin(event);
|
|
@@ -1600,27 +1715,51 @@ let flow = (canvas) => {
|
|
|
1600
1715
|
buffer(state, event, block);
|
|
1601
1716
|
};
|
|
1602
1717
|
|
|
1603
|
-
// The report default onto the canvas, before anything is measured.
|
|
1604
|
-
// `
|
|
1605
|
-
// gaps scale with the document's
|
|
1606
|
-
//
|
|
1607
|
-
//
|
|
1608
|
-
// target
|
|
1609
|
-
//
|
|
1718
|
+
// The report default onto the canvas, before anything is measured. It is
|
|
1719
|
+
// narrowed to `family` and `size`, and each replaces this target's own
|
|
1720
|
+
// baseline outright: row heights and band gaps scale with the document's
|
|
1721
|
+
// type rather than staying at a size nothing is set in, and text declaring
|
|
1722
|
+
// no family is set in the document's. Lifting the pair here is the whole of
|
|
1723
|
+
// this target's reading of docs/adr/0033 -- the default reaches a node as a
|
|
1724
|
+
// fallback the canvas carries, never as a layer merged into its style, so a
|
|
1725
|
+
// document-wide fact costs no allocation however many cells a report has.
|
|
1726
|
+
//
|
|
1727
|
+
// `sizeOf` and `familyName` are this target's one reading each of what a
|
|
1728
|
+
// declared size and family amount to, so they read the default here too --
|
|
1729
|
+
// both reach this unchecked from a computed style, and two spellings of that
|
|
1730
|
+
// leniency would drift. Each falls back to what the canvas already carries,
|
|
1731
|
+
// so a default declaring one of the pair leaves the other alone, and one
|
|
1732
|
+
// whose value is unusable leaves this target's own baseline standing.
|
|
1610
1733
|
/** @type {(style: any) => void} */
|
|
1611
1734
|
let adoptDefault = (style) => {
|
|
1612
1735
|
if (!style) return;
|
|
1613
|
-
canvas.style = style;
|
|
1614
1736
|
canvas.base = sizeOf(style, canvas.base);
|
|
1737
|
+
canvas.family = familyName(style) || canvas.family;
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1740
|
+
/** @type {(event: any) => void} */
|
|
1741
|
+
let pinHeader = (event) => {
|
|
1742
|
+
if (event.headerHeight == null) return;
|
|
1743
|
+
let pin = canvas.y - event.headerHeight;
|
|
1744
|
+
if (pin < canvas.bottom) throw Error("header: height exceeds the first page's body");
|
|
1745
|
+
state.pin = pin;
|
|
1746
|
+
state.skipGap = true;
|
|
1747
|
+
state.inHeader = true;
|
|
1615
1748
|
};
|
|
1616
1749
|
|
|
1617
1750
|
return {
|
|
1618
1751
|
handlers: {
|
|
1619
1752
|
"report-start": (event) => {
|
|
1620
1753
|
opening = event;
|
|
1754
|
+
canvas.locale = event.locale;
|
|
1755
|
+
canvas.currency = event.currency;
|
|
1756
|
+
canvas.timeZone = event.timeZone;
|
|
1621
1757
|
adoptDefault(event.style);
|
|
1622
1758
|
if (event.columns) state.pending = { count: event.columns, owner: -1 };
|
|
1623
|
-
if (!event.page)
|
|
1759
|
+
if (!event.page) {
|
|
1760
|
+
pinHeader(event);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1624
1763
|
// A frame of its own, never the canvas itself: `measuring` builds
|
|
1625
1764
|
// from whatever it is handed, so handing it this canvas would spread
|
|
1626
1765
|
// the page passes onto a measurement — and reaching a document is the
|
|
@@ -1628,6 +1767,7 @@ let flow = (canvas) => {
|
|
|
1628
1767
|
// a property of the page box in any case, not of a content box
|
|
1629
1768
|
// something may already have narrowed.
|
|
1630
1769
|
adopt(canvas, reserve(pageBox(canvas), canvas.fonts, event.page));
|
|
1770
|
+
pinHeader(event);
|
|
1631
1771
|
},
|
|
1632
1772
|
// Everything else routes: `placed` above says what each event does, and
|
|
1633
1773
|
// one entry point is what lets a buffered region replay through it. The
|
|
@@ -1651,6 +1791,7 @@ let flow = (canvas) => {
|
|
|
1651
1791
|
},
|
|
1652
1792
|
},
|
|
1653
1793
|
finish: () => {
|
|
1794
|
+
snapPin({ type: "report-end" });
|
|
1654
1795
|
// A root region reaches here undrained when a report declares `columns`
|
|
1655
1796
|
// and no full-width footer followed its body.
|
|
1656
1797
|
closeRegion(state);
|