@particle-academy/last-word-ui 0.1.0 → 0.2.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/index.js CHANGED
@@ -4,54 +4,6 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
 
5
5
  // src/DocViewer.tsx
6
6
 
7
- // src/footnotes.ts
8
- function numberFootnotes(doc) {
9
- const marks = /* @__PURE__ */ new Map();
10
- const notes = [];
11
- const runs = (list) => {
12
- if (!Array.isArray(list)) return;
13
- for (const run of list) {
14
- const fn = run?.footnote;
15
- if (fn && typeof fn === "object" && Array.isArray(fn.blocks)) {
16
- const id = notes.length + 1;
17
- marks.set(run, id);
18
- notes.push({ id, footnote: fn });
19
- }
20
- }
21
- };
22
- const items = (list) => {
23
- if (!Array.isArray(list)) return;
24
- for (const item of list) {
25
- runs(item?.runs);
26
- items(item?.children);
27
- }
28
- };
29
- const blocks = (list) => {
30
- if (!Array.isArray(list)) return;
31
- for (const block of list) {
32
- switch (block?.type) {
33
- case "heading":
34
- case "paragraph":
35
- runs(block.runs);
36
- break;
37
- case "list":
38
- items(block.items);
39
- break;
40
- case "quote":
41
- blocks(block.blocks);
42
- break;
43
- case "table":
44
- for (const row of Array.isArray(block.rows) ? block.rows : []) {
45
- for (const cell of Array.isArray(row?.cells) ? row.cells : []) blocks(cell?.blocks);
46
- }
47
- break;
48
- }
49
- }
50
- };
51
- blocks(doc.blocks);
52
- return { marks, notes };
53
- }
54
-
55
7
  // src/geometry.ts
56
8
  var PAGE_SIZES_PT = {
57
9
  letter: [612, 792],
@@ -99,6 +51,329 @@ function bodyBox(g, headerHeight, footerHeight) {
99
51
  return { top, bottom, height: Math.max(1, g.height - top - bottom) };
100
52
  }
101
53
 
54
+ // src/fields.ts
55
+ var FIELD = /\{(?:(page|pages|section|sectionpages|author|title|filename)|(date|time)(?::([^{}"\\]+))?)\}/g;
56
+ var str = (v) => typeof v === "string" ? v : "";
57
+ function substituteFields(text, fields) {
58
+ return text.replace(FIELD, (_m, name, dt, format) => {
59
+ switch (name) {
60
+ case "page":
61
+ return String(fields.page);
62
+ case "pages":
63
+ return String(fields.pages);
64
+ case "section":
65
+ return String(fields.section ?? 1);
66
+ case "sectionpages":
67
+ return String(fields.sectionPages ?? fields.pages);
68
+ case "author":
69
+ return str(fields.author);
70
+ case "title":
71
+ return str(fields.title);
72
+ case "filename":
73
+ return str(fields.filename);
74
+ }
75
+ const now = fields.now instanceof Date && Number.isFinite(fields.now.getTime()) ? fields.now : /* @__PURE__ */ new Date();
76
+ if (format !== void 0) return formatDatePicture(now, format);
77
+ return dt === "time" ? now.toLocaleTimeString() : now.toLocaleDateString();
78
+ });
79
+ }
80
+ function resolveNow(now) {
81
+ if (now instanceof Date) return Number.isFinite(now.getTime()) ? new Date(now.getTime()) : /* @__PURE__ */ new Date();
82
+ if (typeof now === "string") {
83
+ const d = new Date(now);
84
+ if (Number.isFinite(d.getTime())) return d;
85
+ }
86
+ return /* @__PURE__ */ new Date();
87
+ }
88
+ var MONTHS = [
89
+ "January",
90
+ "February",
91
+ "March",
92
+ "April",
93
+ "May",
94
+ "June",
95
+ "July",
96
+ "August",
97
+ "September",
98
+ "October",
99
+ "November",
100
+ "December"
101
+ ];
102
+ var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
103
+ var PICTURE = [
104
+ "AM/PM",
105
+ "am/pm",
106
+ "dddd",
107
+ "MMMM",
108
+ "yyyy",
109
+ "ddd",
110
+ "MMM",
111
+ "dd",
112
+ "MM",
113
+ "yy",
114
+ "HH",
115
+ "hh",
116
+ "mm",
117
+ "ss",
118
+ "d",
119
+ "M",
120
+ "H",
121
+ "h",
122
+ "m",
123
+ "s"
124
+ ];
125
+ var two = (n) => String(n).padStart(2, "0");
126
+ function formatDatePicture(date, picture) {
127
+ const h12 = date.getHours() % 12 === 0 ? 12 : date.getHours() % 12;
128
+ const value = (token) => {
129
+ switch (token) {
130
+ case "d":
131
+ return String(date.getDate());
132
+ case "dd":
133
+ return two(date.getDate());
134
+ case "ddd":
135
+ return DAYS[date.getDay()].slice(0, 3);
136
+ case "dddd":
137
+ return DAYS[date.getDay()];
138
+ case "M":
139
+ return String(date.getMonth() + 1);
140
+ case "MM":
141
+ return two(date.getMonth() + 1);
142
+ case "MMM":
143
+ return MONTHS[date.getMonth()].slice(0, 3);
144
+ case "MMMM":
145
+ return MONTHS[date.getMonth()];
146
+ case "yy":
147
+ return two(date.getFullYear() % 100);
148
+ case "yyyy":
149
+ return String(date.getFullYear()).padStart(4, "0");
150
+ case "h":
151
+ return String(h12);
152
+ case "hh":
153
+ return two(h12);
154
+ case "H":
155
+ return String(date.getHours());
156
+ case "HH":
157
+ return two(date.getHours());
158
+ case "m":
159
+ return String(date.getMinutes());
160
+ case "mm":
161
+ return two(date.getMinutes());
162
+ case "s":
163
+ return String(date.getSeconds());
164
+ case "ss":
165
+ return two(date.getSeconds());
166
+ case "am/pm":
167
+ return date.getHours() < 12 ? "am" : "pm";
168
+ case "AM/PM":
169
+ return date.getHours() < 12 ? "AM" : "PM";
170
+ }
171
+ };
172
+ let out = "";
173
+ let i = 0;
174
+ while (i < picture.length) {
175
+ if (picture[i] === "'") {
176
+ const end = picture.indexOf("'", i + 1);
177
+ if (end < 0) {
178
+ out += picture.slice(i + 1);
179
+ break;
180
+ }
181
+ out += picture.slice(i + 1, end);
182
+ i = end + 1;
183
+ continue;
184
+ }
185
+ const token = PICTURE.find((t) => picture.startsWith(t, i));
186
+ if (token) {
187
+ out += value(token);
188
+ i += token.length;
189
+ continue;
190
+ }
191
+ out += picture[i];
192
+ i++;
193
+ }
194
+ return out;
195
+ }
196
+
197
+ // src/image-size.ts
198
+ var DATA_URL = /^data:image\/(png|jpe?g);base64,([A-Za-z0-9+/=\s]+)$/i;
199
+ function decodeHead(b64, bytes) {
200
+ try {
201
+ const take = Math.ceil(bytes / 3) * 4;
202
+ const bin = atob(b64.replace(/\s+/g, "").slice(0, take));
203
+ const out = new Uint8Array(bin.length);
204
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
205
+ return out;
206
+ } catch {
207
+ return null;
208
+ }
209
+ }
210
+ function pngSize(b) {
211
+ if (b.length < 24 || b[0] !== 137 || b[1] !== 80 || b[2] !== 78 || b[3] !== 71) return null;
212
+ const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
213
+ const width = dv.getUint32(16);
214
+ const height2 = dv.getUint32(20);
215
+ return width > 0 && height2 > 0 ? { width, height: height2 } : null;
216
+ }
217
+ function jpegSize(b) {
218
+ if (b.length < 4 || b[0] !== 255 || b[1] !== 216) return null;
219
+ let i = 2;
220
+ while (i + 9 < b.length) {
221
+ if (b[i] !== 255) return null;
222
+ const marker = b[i + 1];
223
+ const len = b[i + 2] << 8 | b[i + 3];
224
+ if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
225
+ const height2 = b[i + 5] << 8 | b[i + 6];
226
+ const width = b[i + 7] << 8 | b[i + 8];
227
+ return width > 0 && height2 > 0 ? { width, height: height2 } : null;
228
+ }
229
+ i += 2 + len;
230
+ }
231
+ return null;
232
+ }
233
+ function imageSizeFromDataUrl(src) {
234
+ const m = DATA_URL.exec(src);
235
+ if (!m) return null;
236
+ const isPng = m[1].toLowerCase() === "png";
237
+ const head = decodeHead(m[2], isPng ? 32 : 65536);
238
+ if (!head) return null;
239
+ return isPng ? pngSize(head) : jpegSize(head);
240
+ }
241
+ function resolveImageSize(block, maxWidthPx) {
242
+ const intrinsic = imageSizeFromDataUrl(block.src);
243
+ const given = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
244
+ let width = given(block.widthPx);
245
+ let height2 = given(block.heightPx);
246
+ const aspect = intrinsic ? intrinsic.width / intrinsic.height : 1.5;
247
+ if (width === void 0 && height2 === void 0) {
248
+ width = intrinsic?.width ?? 300;
249
+ height2 = intrinsic?.height ?? 200;
250
+ } else if (width === void 0) {
251
+ width = height2 * aspect;
252
+ } else if (height2 === void 0) {
253
+ height2 = width / aspect;
254
+ }
255
+ if (width > maxWidthPx) {
256
+ height2 = height2 * maxWidthPx / width;
257
+ width = maxWidthPx;
258
+ }
259
+ return { width: Math.max(1, Math.round(width)), height: Math.max(1, Math.round(height2)) };
260
+ }
261
+
262
+ // src/links.ts
263
+ var ALLOWED = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
264
+ function safeHref(url) {
265
+ if (typeof url !== "string" || url === "") return void 0;
266
+ if (/^#[^\s]*$/.test(url)) return url;
267
+ let parsed;
268
+ try {
269
+ parsed = new URL(url);
270
+ } catch {
271
+ return void 0;
272
+ }
273
+ return ALLOWED.has(parsed.protocol) ? url : void 0;
274
+ }
275
+
276
+ // src/style.ts
277
+ var pt = (n) => `${Math.round(n * 1e3) / 1e3}pt`;
278
+ var px = (n) => `${Math.round(n * 1e3) / 1e3}px`;
279
+ var SINGLE_LINE = 1.17;
280
+ var DEFAULT_LINE_MULTIPLE = 259 / 240;
281
+ var DEFAULT_SPACE_AFTER = 8;
282
+ var DEFAULT_FONT = "Calibri";
283
+ var DEFAULT_SIZE = 11;
284
+ var HEADING_SIZES = [20, 16, 14, 13, 12, 11];
285
+ var HEADER_FILL = "#E7E7E7";
286
+ var CODE_FILL = "#F2F2F2";
287
+ var LINK_COLOR = "#0563C1";
288
+ var QUOTE_COLOR = "#595959";
289
+ var BAND_SIZE = 9;
290
+ var BAND_COLOR = "#6B7280";
291
+ var FOOTNOTE_SIZE = 10;
292
+ var FOOTNOTE_SEPARATOR_PT = 12;
293
+ function hex(v) {
294
+ return typeof v === "string" && /^#[0-9a-fA-F]{6}$/.test(v) ? v : void 0;
295
+ }
296
+ var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
297
+ var num = (v, fallback) => finite2(v) ? v : fallback;
298
+ var FALLBACKS = {
299
+ calibri: 'Carlito, "Segoe UI", Arial, sans-serif',
300
+ cambria: "Caladea, Georgia, serif",
301
+ arial: '"Liberation Sans", Arimo, Helvetica, sans-serif',
302
+ "times new roman": '"Liberation Serif", Tinos, Times, serif',
303
+ "courier new": '"Liberation Mono", Cousine, monospace',
304
+ consolas: '"Liberation Mono", Menlo, monospace'
305
+ };
306
+ function fontStack(name) {
307
+ const clean = typeof name === "string" ? name.replace(/[^A-Za-z0-9 _-]/g, "").trim() : "";
308
+ const font = clean === "" ? DEFAULT_FONT : clean;
309
+ const fallback = FALLBACKS[font.toLowerCase()] ?? 'Carlito, "Segoe UI", Arial, sans-serif';
310
+ return `"${font}", ${fallback}`;
311
+ }
312
+ var MONO = fontStack("Consolas");
313
+ var CSS_STYLE = {
314
+ single: "solid",
315
+ double: "double",
316
+ dashed: "dashed",
317
+ dotted: "dotted"
318
+ };
319
+ function borderCss(b) {
320
+ if (!b || typeof b !== "object") return void 0;
321
+ const style = CSS_STYLE[b.style ?? "single"];
322
+ if (!style) return "none";
323
+ const width = Math.max(style === "double" ? 2.25 : 0.25, num(b.width, 0.5));
324
+ return `${pt(width)} ${style} ${hex(b.color) ?? "#000000"}`;
325
+ }
326
+ function paragraphStyle(p, size) {
327
+ const s = {
328
+ margin: 0,
329
+ marginLeft: pt(num(p.indentLeft, 0)),
330
+ marginRight: pt(num(p.indentRight, 0)),
331
+ fontSize: pt(size),
332
+ lineHeight: num(p.lineHeight, DEFAULT_LINE_MULTIPLE) * SINGLE_LINE,
333
+ color: "inherit",
334
+ fontWeight: "inherit",
335
+ whiteSpace: "pre-wrap",
336
+ overflowWrap: "break-word"
337
+ };
338
+ if (p.align === "left" || p.align === "center" || p.align === "right" || p.align === "justify") s.textAlign = p.align;
339
+ const fill = hex(p.shading);
340
+ if (fill) s.backgroundColor = fill;
341
+ Object.assign(s, boxBorders(p.borders));
342
+ return s;
343
+ }
344
+ function boxBorders(b) {
345
+ if (!b || typeof b !== "object") return {};
346
+ const s = {};
347
+ const top = borderCss(b.top);
348
+ const right = borderCss(b.right);
349
+ const bottom = borderCss(b.bottom);
350
+ const left = borderCss(b.left);
351
+ if (top) {
352
+ s.borderTop = top;
353
+ if (top !== "none") s.paddingTop = pt(1);
354
+ }
355
+ if (bottom) {
356
+ s.borderBottom = bottom;
357
+ if (bottom !== "none") s.paddingBottom = pt(1);
358
+ }
359
+ if (left) {
360
+ s.borderLeft = left;
361
+ if (left !== "none") s.paddingLeft = pt(4);
362
+ }
363
+ if (right) {
364
+ s.borderRight = right;
365
+ if (right !== "none") s.paddingRight = pt(4);
366
+ }
367
+ return s;
368
+ }
369
+ function spacingStyle(p, defaultAfter, defaultBefore = 0) {
370
+ return {
371
+ display: "flow-root",
372
+ paddingTop: pt(Math.max(0, num(p.spaceBefore, defaultBefore))),
373
+ paddingBottom: pt(Math.max(0, num(p.spaceAfter, defaultAfter)))
374
+ };
375
+ }
376
+
102
377
  // src/table-grid.ts
103
378
  var span = (v) => typeof v === "number" && Number.isInteger(v) && v > 1 ? v : 1;
104
379
  function tableGrid(rows) {
@@ -135,465 +410,6 @@ function columnPercents(colCount, widths) {
135
410
  if (total <= 0) return even;
136
411
  return w.map((x) => Math.round(x / total * 100 * 1e6) / 1e6);
137
412
  }
138
-
139
- // src/measure.ts
140
- var SPLITTABLE = /* @__PURE__ */ new Set(["paragraph", "heading", "list", "quote", "code"]);
141
- var blockId = (index) => `b${index}`;
142
- function height(el) {
143
- return el ? el.getBoundingClientRect().height : 0;
144
- }
145
- function lineBreakpoints(el) {
146
- if (typeof document === "undefined" || typeof document.createRange !== "function") return [];
147
- const top = el.getBoundingClientRect().top;
148
- const total = el.getBoundingClientRect().height;
149
- const range = document.createRange();
150
- if (typeof range.getClientRects !== "function") return [];
151
- const spans = [];
152
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
153
- for (let node = walker.nextNode(); node; node = walker.nextNode()) {
154
- if (!node.nodeValue) continue;
155
- range.selectNodeContents(node);
156
- for (const r of Array.from(range.getClientRects())) {
157
- if (r.height > 0) spans.push([r.top - top, r.bottom - top]);
158
- }
159
- }
160
- spans.sort((a, b) => a[0] - b[0]);
161
- const lines = [];
162
- for (const s of spans) {
163
- const last = lines[lines.length - 1];
164
- if (last && s[0] < last[1] - 0.5) last[1] = Math.max(last[1], s[1]);
165
- else lines.push([s[0], s[1]]);
166
- }
167
- return lines.slice(0, -1).map((l) => l[1]).filter((b) => b > 0 && b < total - 0.5);
168
- }
169
- function measure(root, blocks) {
170
- const noteHeights = /* @__PURE__ */ new Map();
171
- root.querySelectorAll("[data-lw-measure-footnote]").forEach((el) => {
172
- noteHeights.set(Number(el.dataset.lwMeasureFootnote), height(el));
173
- });
174
- const marksIn = (el) => Array.from(el.querySelectorAll("[data-lw-footnote-ref]")).map((m) => {
175
- const id = Number(m.dataset.lwFootnoteRef);
176
- return { id, el: m, height: noteHeights.get(id) ?? 0 };
177
- });
178
- const items = blocks.map((block, i) => {
179
- if (block?.type === "pageBreak") return { type: "break" };
180
- const wrap = root.querySelector(`[data-lw-measure-block="${i}"]`);
181
- if (!wrap) return { type: "block", id: blockId(i), height: 0 };
182
- if (block.type === "table") {
183
- const grid = tableGrid(Array.isArray(block.rows) ? block.rows : []);
184
- const rows = Array.from(wrap.querySelectorAll("tr[data-lw-row]")).map((tr) => {
185
- const r = Number(tr.dataset.lwRow);
186
- const notes2 = marksIn(tr).map(({ id, height: h2 }) => ({ id, height: h2 }));
187
- return {
188
- height: height(tr),
189
- ...grid.joinPrevious[r] ? { joinPrevious: true } : {},
190
- ...notes2.length ? { footnotes: notes2 } : {}
191
- };
192
- });
193
- return { type: "table", id: blockId(i), rows, headerRows: grid.headerRows };
194
- }
195
- const top = wrap.getBoundingClientRect().top;
196
- const h = height(wrap);
197
- let breakpoints = SPLITTABLE.has(block.type) ? lineBreakpoints(wrap) : [];
198
- if (block.type === "paragraph" || block.type === "heading") breakpoints = breakpoints.slice(1, -1);
199
- const notes = marksIn(wrap).map(({ id, el, height: nh }) => {
200
- const r = el.getBoundingClientRect();
201
- return { id, at: Math.min(h, Math.max(0, (r.top + r.bottom) / 2 - top)), height: nh };
202
- });
203
- const keepNext = block.type === "heading" || block.keepNext === true;
204
- return {
205
- type: "block",
206
- id: blockId(i),
207
- height: h,
208
- ...breakpoints.length ? { breakpoints } : {},
209
- ...keepNext ? { keepNext: true } : {},
210
- ...notes.length ? { footnotes: notes } : {}
211
- };
212
- });
213
- return {
214
- items,
215
- headerHeight: height(root.querySelector("[data-lw-measure-header]")),
216
- footerHeight: height(root.querySelector("[data-lw-measure-footer]")),
217
- footnoteSeparator: height(root.querySelector("[data-lw-measure-separator]"))
218
- };
219
- }
220
-
221
- // src/paginate.ts
222
- var EPS = 1e-6;
223
- function paginate(items, options) {
224
- const cap = Math.max(1, options.pageHeight);
225
- const sep = Math.max(0, options.footnoteSeparator ?? 0);
226
- const pages = [];
227
- let page;
228
- let content = 0;
229
- let noteHeights = 0;
230
- const newPage = () => {
231
- page = { index: pages.length, fragments: [], footnotes: [], used: 0 };
232
- pages.push(page);
233
- content = 0;
234
- noteHeights = 0;
235
- };
236
- const noteCost = (extraCount, extraHeight) => {
237
- const count = page.footnotes.length + extraCount;
238
- return count > 0 ? noteHeights + extraHeight + sep : 0;
239
- };
240
- const fits = (h, notes) => content + h + noteCost(notes.length, sum(notes)) <= cap + EPS;
241
- const commit = (h, notes) => {
242
- content += h;
243
- for (const n of notes) {
244
- page.footnotes.push(n.id);
245
- noteHeights += n.height;
246
- }
247
- page.used = content + noteCost(0, 0);
248
- };
249
- const empty = () => page.fragments.length === 0;
250
- newPage();
251
- items.forEach((item, index) => {
252
- if (item.type === "break") {
253
- newPage();
254
- return;
255
- }
256
- if (item.type === "table") {
257
- placeTable(item, index);
258
- return;
259
- }
260
- placeBlock(item, index);
261
- });
262
- return pages;
263
- function notesIn(item, start, end, last) {
264
- const h = Math.max(0, item.height);
265
- return (item.footnotes ?? []).filter((n) => {
266
- const at = Math.min(h, Math.max(0, n.at));
267
- return at >= start - EPS && (last ? at <= end + EPS : at < end - EPS);
268
- });
269
- }
270
- function firstPiece(item) {
271
- if (!item || item.type === "break") return 0;
272
- if (item.type === "block") {
273
- const bp = (item.breakpoints ?? []).find((b) => b > EPS && b < item.height - EPS);
274
- return bp ?? item.height;
275
- }
276
- const header = item.rows.slice(0, headerCount(item)).reduce((a, r) => a + r.height, 0);
277
- const units = tableUnits(item);
278
- return header + (units[0] ? units[0].height : 0);
279
- }
280
- function keepChainHeight(from) {
281
- let total = 0;
282
- let i = from;
283
- while (i < items.length) {
284
- const it = items[i];
285
- if (it.type === "block" && it.keepNext) {
286
- total += it.height;
287
- i++;
288
- continue;
289
- }
290
- total += firstPiece(it);
291
- break;
292
- }
293
- return total;
294
- }
295
- function placeBlock(item, index) {
296
- const height2 = Math.max(0, item.height);
297
- if (item.keepNext && !empty()) {
298
- const chain = keepChainHeight(index);
299
- const next = items[index + 1];
300
- const meaningful = next !== void 0 && next.type !== "break";
301
- if (meaningful && chain <= cap + EPS && !fits(chain, notesIn(item, 0, height2, true))) newPage();
302
- }
303
- let start = 0;
304
- for (let guard = 0; guard < 1e5; guard++) {
305
- const restNotes = notesIn(item, start, height2, true);
306
- if (fits(height2 - start, restNotes)) {
307
- page.fragments.push({ type: "block", id: item.id, item: index, start, end: height2 });
308
- commit(height2 - start, restNotes);
309
- return;
310
- }
311
- const bps = (item.breakpoints ?? []).filter((b) => b > start + EPS && b < height2 - EPS).sort((a, b) => b - a);
312
- let cut;
313
- for (const bp of bps) {
314
- if (fits(bp - start, notesIn(item, start, bp, false))) {
315
- cut = bp;
316
- break;
317
- }
318
- }
319
- if (cut !== void 0) {
320
- page.fragments.push({ type: "block", id: item.id, item: index, start, end: cut });
321
- commit(cut - start, notesIn(item, start, cut, false));
322
- newPage();
323
- start = cut;
324
- continue;
325
- }
326
- if (!empty()) {
327
- newPage();
328
- continue;
329
- }
330
- let room = cap;
331
- let end = Math.min(height2, start + room);
332
- let notes = notesIn(item, start, end, false);
333
- room = cap - noteCost(notes.length, sum(notes));
334
- if (room <= EPS) room = cap;
335
- end = Math.min(height2, start + room);
336
- notes = notesIn(item, start, end, end >= height2 - EPS);
337
- const forced = end < height2 - EPS;
338
- page.fragments.push({ type: "block", id: item.id, item: index, start, end, ...forced ? { forced: true } : {} });
339
- commit(end - start, notes);
340
- if (!forced) return;
341
- newPage();
342
- start = end;
343
- }
344
- }
345
- function headerCount(item) {
346
- return Math.max(0, Math.min(item.headerRows ?? 0, item.rows.length));
347
- }
348
- function tableUnits(item) {
349
- const units = [];
350
- for (let r = headerCount(item); r < item.rows.length; r++) {
351
- const row = item.rows[r];
352
- const last = units[units.length - 1];
353
- if (row.joinPrevious && last) {
354
- last.rows.push(r);
355
- last.height += row.height;
356
- last.notes.push(...row.footnotes ?? []);
357
- } else {
358
- units.push({ rows: [r], height: row.height, notes: [...row.footnotes ?? []] });
359
- }
360
- }
361
- return units;
362
- }
363
- function placeTable(item, index) {
364
- const h = headerCount(item);
365
- const headerIdx = Array.from({ length: h }, (_, i) => i);
366
- const headerHeight = item.rows.slice(0, h).reduce((a, r) => a + r.height, 0);
367
- const headerNotes = item.rows.slice(0, h).flatMap((r) => r.footnotes ?? []);
368
- const units = tableUnits(item);
369
- if (units.length === 0 && h === 0) return;
370
- if (units.length === 0) units.push({ rows: [], height: 0, notes: [] });
371
- let frag = null;
372
- let first = true;
373
- for (let u = 0; u < units.length; ) {
374
- const unit = units[u];
375
- if (frag === null) {
376
- const notes = first ? [...headerNotes, ...unit.notes] : unit.notes;
377
- const need = headerHeight + unit.height;
378
- if (!fits(need, notes) && !empty()) {
379
- newPage();
380
- continue;
381
- }
382
- const overflow = !fits(need, notes);
383
- frag = {
384
- type: "table",
385
- id: item.id,
386
- item: index,
387
- rows: [...headerIdx, ...unit.rows],
388
- repeatedHeader: !first && h > 0,
389
- ...overflow ? { overflow: true } : {}
390
- };
391
- page.fragments.push(frag);
392
- commit(need, notes);
393
- first = false;
394
- u++;
395
- continue;
396
- }
397
- if (fits(unit.height, unit.notes)) {
398
- frag.rows.push(...unit.rows);
399
- commit(unit.height, unit.notes);
400
- u++;
401
- continue;
402
- }
403
- newPage();
404
- frag = null;
405
- }
406
- }
407
- }
408
- function sum(notes) {
409
- return notes.reduce((a, n) => a + n.height, 0);
410
- }
411
-
412
- // src/fields.ts
413
- var FIELD = /\{page\}|\{pages\}/g;
414
- function substituteFields(text, fields) {
415
- return text.replace(FIELD, (m) => String(m === "{page}" ? fields.page : fields.pages));
416
- }
417
-
418
- // src/image-size.ts
419
- var DATA_URL = /^data:image\/(png|jpe?g);base64,([A-Za-z0-9+/=\s]+)$/i;
420
- function decodeHead(b64, bytes) {
421
- try {
422
- const take = Math.ceil(bytes / 3) * 4;
423
- const bin = atob(b64.replace(/\s+/g, "").slice(0, take));
424
- const out = new Uint8Array(bin.length);
425
- for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
426
- return out;
427
- } catch {
428
- return null;
429
- }
430
- }
431
- function pngSize(b) {
432
- if (b.length < 24 || b[0] !== 137 || b[1] !== 80 || b[2] !== 78 || b[3] !== 71) return null;
433
- const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
434
- const width = dv.getUint32(16);
435
- const height2 = dv.getUint32(20);
436
- return width > 0 && height2 > 0 ? { width, height: height2 } : null;
437
- }
438
- function jpegSize(b) {
439
- if (b.length < 4 || b[0] !== 255 || b[1] !== 216) return null;
440
- let i = 2;
441
- while (i + 9 < b.length) {
442
- if (b[i] !== 255) return null;
443
- const marker = b[i + 1];
444
- const len = b[i + 2] << 8 | b[i + 3];
445
- if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
446
- const height2 = b[i + 5] << 8 | b[i + 6];
447
- const width = b[i + 7] << 8 | b[i + 8];
448
- return width > 0 && height2 > 0 ? { width, height: height2 } : null;
449
- }
450
- i += 2 + len;
451
- }
452
- return null;
453
- }
454
- function imageSizeFromDataUrl(src) {
455
- const m = DATA_URL.exec(src);
456
- if (!m) return null;
457
- const isPng = m[1].toLowerCase() === "png";
458
- const head = decodeHead(m[2], isPng ? 32 : 65536);
459
- if (!head) return null;
460
- return isPng ? pngSize(head) : jpegSize(head);
461
- }
462
- function resolveImageSize(block, maxWidthPx) {
463
- const intrinsic = imageSizeFromDataUrl(block.src);
464
- const given = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
465
- let width = given(block.widthPx);
466
- let height2 = given(block.heightPx);
467
- const aspect = intrinsic ? intrinsic.width / intrinsic.height : 1.5;
468
- if (width === void 0 && height2 === void 0) {
469
- width = intrinsic?.width ?? 300;
470
- height2 = intrinsic?.height ?? 200;
471
- } else if (width === void 0) {
472
- width = height2 * aspect;
473
- } else if (height2 === void 0) {
474
- height2 = width / aspect;
475
- }
476
- if (width > maxWidthPx) {
477
- height2 = height2 * maxWidthPx / width;
478
- width = maxWidthPx;
479
- }
480
- return { width: Math.max(1, Math.round(width)), height: Math.max(1, Math.round(height2)) };
481
- }
482
-
483
- // src/links.ts
484
- var ALLOWED = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
485
- function safeHref(url) {
486
- if (typeof url !== "string" || url === "") return void 0;
487
- if (/^#[^\s]*$/.test(url)) return url;
488
- let parsed;
489
- try {
490
- parsed = new URL(url);
491
- } catch {
492
- return void 0;
493
- }
494
- return ALLOWED.has(parsed.protocol) ? url : void 0;
495
- }
496
-
497
- // src/style.ts
498
- var pt = (n) => `${Math.round(n * 1e3) / 1e3}pt`;
499
- var px = (n) => `${Math.round(n * 1e3) / 1e3}px`;
500
- var SINGLE_LINE = 1.17;
501
- var DEFAULT_LINE_MULTIPLE = 259 / 240;
502
- var DEFAULT_SPACE_AFTER = 8;
503
- var DEFAULT_FONT = "Calibri";
504
- var DEFAULT_SIZE = 11;
505
- var HEADING_SIZES = [20, 16, 14, 13, 12, 11];
506
- var HEADER_FILL = "#E7E7E7";
507
- var CODE_FILL = "#F2F2F2";
508
- var LINK_COLOR = "#0563C1";
509
- var QUOTE_COLOR = "#595959";
510
- var BAND_SIZE = 9;
511
- var BAND_COLOR = "#6B7280";
512
- var FOOTNOTE_SIZE = 10;
513
- var FOOTNOTE_SEPARATOR_PT = 12;
514
- function hex(v) {
515
- return typeof v === "string" && /^#[0-9a-fA-F]{6}$/.test(v) ? v : void 0;
516
- }
517
- var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
518
- var num = (v, fallback) => finite2(v) ? v : fallback;
519
- var FALLBACKS = {
520
- calibri: 'Carlito, "Segoe UI", Arial, sans-serif',
521
- cambria: "Caladea, Georgia, serif",
522
- arial: '"Liberation Sans", Arimo, Helvetica, sans-serif',
523
- "times new roman": '"Liberation Serif", Tinos, Times, serif',
524
- "courier new": '"Liberation Mono", Cousine, monospace',
525
- consolas: '"Liberation Mono", Menlo, monospace'
526
- };
527
- function fontStack(name) {
528
- const clean = typeof name === "string" ? name.replace(/[^A-Za-z0-9 _-]/g, "").trim() : "";
529
- const font = clean === "" ? DEFAULT_FONT : clean;
530
- const fallback = FALLBACKS[font.toLowerCase()] ?? 'Carlito, "Segoe UI", Arial, sans-serif';
531
- return `"${font}", ${fallback}`;
532
- }
533
- var MONO = fontStack("Consolas");
534
- var CSS_STYLE = {
535
- single: "solid",
536
- double: "double",
537
- dashed: "dashed",
538
- dotted: "dotted"
539
- };
540
- function borderCss(b) {
541
- if (!b || typeof b !== "object") return void 0;
542
- const style = CSS_STYLE[b.style ?? "single"];
543
- if (!style) return "none";
544
- const width = Math.max(style === "double" ? 2.25 : 0.25, num(b.width, 0.5));
545
- return `${pt(width)} ${style} ${hex(b.color) ?? "#000000"}`;
546
- }
547
- function paragraphStyle(p, size) {
548
- const s = {
549
- margin: 0,
550
- marginLeft: pt(num(p.indentLeft, 0)),
551
- marginRight: pt(num(p.indentRight, 0)),
552
- fontSize: pt(size),
553
- lineHeight: num(p.lineHeight, DEFAULT_LINE_MULTIPLE) * SINGLE_LINE,
554
- color: "inherit",
555
- fontWeight: "inherit",
556
- whiteSpace: "pre-wrap",
557
- overflowWrap: "break-word"
558
- };
559
- if (p.align === "left" || p.align === "center" || p.align === "right" || p.align === "justify") s.textAlign = p.align;
560
- const fill = hex(p.shading);
561
- if (fill) s.backgroundColor = fill;
562
- Object.assign(s, boxBorders(p.borders));
563
- return s;
564
- }
565
- function boxBorders(b) {
566
- if (!b || typeof b !== "object") return {};
567
- const s = {};
568
- const top = borderCss(b.top);
569
- const right = borderCss(b.right);
570
- const bottom = borderCss(b.bottom);
571
- const left = borderCss(b.left);
572
- if (top) {
573
- s.borderTop = top;
574
- if (top !== "none") s.paddingTop = pt(1);
575
- }
576
- if (bottom) {
577
- s.borderBottom = bottom;
578
- if (bottom !== "none") s.paddingBottom = pt(1);
579
- }
580
- if (left) {
581
- s.borderLeft = left;
582
- if (left !== "none") s.paddingLeft = pt(4);
583
- }
584
- if (right) {
585
- s.borderRight = right;
586
- if (right !== "none") s.paddingRight = pt(4);
587
- }
588
- return s;
589
- }
590
- function spacingStyle(p, defaultAfter, defaultBefore = 0) {
591
- return {
592
- display: "flow-root",
593
- paddingTop: pt(Math.max(0, num(p.spaceBefore, defaultBefore))),
594
- paddingBottom: pt(Math.max(0, num(p.spaceAfter, defaultAfter)))
595
- };
596
- }
597
413
  var EMPTY_MARKS = /* @__PURE__ */ new Map();
598
414
  var ZWSP = String.fromCharCode(8203);
599
415
  var RenderCtx = createContext({
@@ -864,51 +680,423 @@ function TableView({ block, rows }) {
864
680
  })
865
681
  ] }) })
866
682
  }
867
- );
868
- }
869
- function isBlocksBand(band) {
870
- return !!band && typeof band === "object" && Array.isArray(band.blocks);
683
+ );
684
+ }
685
+ function isBlocksBand(band) {
686
+ return !!band && typeof band === "object" && Array.isArray(band.blocks);
687
+ }
688
+ function hasBand(band) {
689
+ if (isBlocksBand(band)) return band.blocks.length > 0;
690
+ if (!band || typeof band !== "object") return false;
691
+ const b = band;
692
+ return [b.left, b.center, b.right].some((s) => typeof s === "string" && s !== "");
693
+ }
694
+ function BandView({ band }) {
695
+ const ctx = useContext(RenderCtx);
696
+ if (isBlocksBand(band)) {
697
+ return /* @__PURE__ */ jsx(WithContext, { value: { marks: EMPTY_MARKS }, children: band.blocks.map((b, i) => b?.type === "pageBreak" || b?.type === "chart" ? null : /* @__PURE__ */ jsx(BlockView, { block: b }, i)) });
698
+ }
699
+ const slot = (v) => typeof v === "string" ? ctx.fields ? substituteFields(v, ctx.fields) : v : "";
700
+ return /* @__PURE__ */ jsxs(
701
+ Text,
702
+ {
703
+ as: "div",
704
+ style: {
705
+ display: "grid",
706
+ gridTemplateColumns: "1fr auto 1fr",
707
+ fontSize: pt(num(band.size, BAND_SIZE)),
708
+ lineHeight: SINGLE_LINE,
709
+ color: hex(band.color) ?? BAND_COLOR,
710
+ whiteSpace: "pre"
711
+ },
712
+ children: [
713
+ /* @__PURE__ */ jsx("span", { "data-lw-slot": "left", style: { textAlign: "left" }, children: slot(band.left) }),
714
+ /* @__PURE__ */ jsx("span", { "data-lw-slot": "center", style: { textAlign: "center" }, children: slot(band.center) }),
715
+ /* @__PURE__ */ jsx("span", { "data-lw-slot": "right", style: { textAlign: "right", justifySelf: "end" }, children: slot(band.right) })
716
+ ]
717
+ }
718
+ );
719
+ }
720
+ function FootnoteView({ id, footnote }) {
721
+ return /* @__PURE__ */ jsxs("div", { "data-footnote": id, style: { display: "flex", gap: pt(3) }, children: [
722
+ /* @__PURE__ */ jsx("sup", { style: { fontSize: pt(FOOTNOTE_SIZE * 0.65), lineHeight: 1, paddingTop: pt(2) }, children: id }),
723
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx(WithContext, { value: { marks: EMPTY_MARKS, size: FOOTNOTE_SIZE, spaceAfter: 0 }, children: (Array.isArray(footnote.blocks) ? footnote.blocks : []).map((b, i) => /* @__PURE__ */ jsx(BlockView, { block: b }, i)) }) })
724
+ ] });
725
+ }
726
+ function FootnoteSeparator({ height: height2 }) {
727
+ return /* @__PURE__ */ jsx("div", { "data-lw-footnote-separator": "", style: { height: height2, display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx("div", { style: { width: pt(144), borderTop: `${pt(0.5)} solid #000000` } }) });
728
+ }
729
+
730
+ // src/bands.ts
731
+ var BAND_SLOTS = ["default", "first", "even"];
732
+ var VARIANT_KEY = {
733
+ header: { first: "firstHeader", even: "evenHeader" },
734
+ footer: { first: "firstFooter", even: "evenFooter" }
735
+ };
736
+ function pageClass(index) {
737
+ if (index === 0) return "first";
738
+ return index % 2 === 1 ? "even" : "odd";
739
+ }
740
+ var isBandObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
741
+ function slotBand(page, kind, slot) {
742
+ if (!page || typeof page !== "object") return void 0;
743
+ const key = slot === "default" ? kind : VARIANT_KEY[kind][slot];
744
+ const v = page[key];
745
+ return isBandObject(v) ? v : void 0;
746
+ }
747
+ function bandSlot(page, kind, cls) {
748
+ if (cls === "first" && slotBand(page, kind, "first") !== void 0) return "first";
749
+ if (cls === "even" && slotBand(page, kind, "even") !== void 0) return "even";
750
+ return "default";
751
+ }
752
+ function pageBand(page, kind, index) {
753
+ const band = slotBand(page, kind, bandSlot(page, kind, pageClass(index)));
754
+ return hasBand(band) ? band : null;
755
+ }
756
+ function pageBodyBoxes(g, page, heights) {
757
+ const height2 = (kind, cls) => {
758
+ const slot = bandSlot(page, kind, cls);
759
+ return hasBand(slotBand(page, kind, slot)) ? Math.max(0, heights[kind][slot] || 0) : 0;
760
+ };
761
+ const box = (cls) => bodyBox(g, height2("header", cls), height2("footer", cls));
762
+ return { first: box("first"), even: box("even"), odd: box("odd") };
763
+ }
764
+
765
+ // src/footnotes.ts
766
+ function numberFootnotes(doc) {
767
+ const marks = /* @__PURE__ */ new Map();
768
+ const notes = [];
769
+ const runs = (list) => {
770
+ if (!Array.isArray(list)) return;
771
+ for (const run of list) {
772
+ const fn = run?.footnote;
773
+ if (fn && typeof fn === "object" && Array.isArray(fn.blocks)) {
774
+ const id = notes.length + 1;
775
+ marks.set(run, id);
776
+ notes.push({ id, footnote: fn });
777
+ }
778
+ }
779
+ };
780
+ const items = (list) => {
781
+ if (!Array.isArray(list)) return;
782
+ for (const item of list) {
783
+ runs(item?.runs);
784
+ items(item?.children);
785
+ }
786
+ };
787
+ const blocks = (list) => {
788
+ if (!Array.isArray(list)) return;
789
+ for (const block of list) {
790
+ switch (block?.type) {
791
+ case "heading":
792
+ case "paragraph":
793
+ runs(block.runs);
794
+ break;
795
+ case "list":
796
+ items(block.items);
797
+ break;
798
+ case "quote":
799
+ blocks(block.blocks);
800
+ break;
801
+ case "table":
802
+ for (const row of Array.isArray(block.rows) ? block.rows : []) {
803
+ for (const cell of Array.isArray(row?.cells) ? row.cells : []) blocks(cell?.blocks);
804
+ }
805
+ break;
806
+ }
807
+ }
808
+ };
809
+ blocks(doc.blocks);
810
+ return { marks, notes };
811
+ }
812
+
813
+ // src/measure.ts
814
+ var SPLITTABLE = /* @__PURE__ */ new Set(["paragraph", "heading", "list", "quote", "code"]);
815
+ var blockId = (index) => `b${index}`;
816
+ function height(el) {
817
+ return el ? el.getBoundingClientRect().height : 0;
818
+ }
819
+ function lineBreakpoints(el) {
820
+ if (typeof document === "undefined" || typeof document.createRange !== "function") return [];
821
+ const top = el.getBoundingClientRect().top;
822
+ const total = el.getBoundingClientRect().height;
823
+ const range = document.createRange();
824
+ if (typeof range.getClientRects !== "function") return [];
825
+ const spans = [];
826
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
827
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
828
+ if (!node.nodeValue) continue;
829
+ range.selectNodeContents(node);
830
+ for (const r of Array.from(range.getClientRects())) {
831
+ if (r.height > 0) spans.push([r.top - top, r.bottom - top]);
832
+ }
833
+ }
834
+ spans.sort((a, b) => a[0] - b[0]);
835
+ const lines = [];
836
+ for (const s of spans) {
837
+ const last = lines[lines.length - 1];
838
+ if (last && s[0] < last[1] - 0.5) last[1] = Math.max(last[1], s[1]);
839
+ else lines.push([s[0], s[1]]);
840
+ }
841
+ return lines.slice(0, -1).map((l) => l[1]).filter((b) => b > 0 && b < total - 0.5);
842
+ }
843
+ function measure(root, blocks) {
844
+ const noteHeights = /* @__PURE__ */ new Map();
845
+ root.querySelectorAll("[data-lw-measure-footnote]").forEach((el) => {
846
+ noteHeights.set(Number(el.dataset.lwMeasureFootnote), height(el));
847
+ });
848
+ const marksIn = (el) => Array.from(el.querySelectorAll("[data-lw-footnote-ref]")).map((m) => {
849
+ const id = Number(m.dataset.lwFootnoteRef);
850
+ return { id, el: m, height: noteHeights.get(id) ?? 0 };
851
+ });
852
+ const items = blocks.map((block, i) => {
853
+ if (block?.type === "pageBreak") return { type: "break" };
854
+ const wrap = root.querySelector(`[data-lw-measure-block="${i}"]`);
855
+ if (!wrap) return { type: "block", id: blockId(i), height: 0 };
856
+ if (block.type === "table") {
857
+ const grid = tableGrid(Array.isArray(block.rows) ? block.rows : []);
858
+ const rows = Array.from(wrap.querySelectorAll("tr[data-lw-row]")).map((tr) => {
859
+ const r = Number(tr.dataset.lwRow);
860
+ const notes2 = marksIn(tr).map(({ id, height: h2 }) => ({ id, height: h2 }));
861
+ return {
862
+ height: height(tr),
863
+ ...grid.joinPrevious[r] ? { joinPrevious: true } : {},
864
+ ...notes2.length ? { footnotes: notes2 } : {}
865
+ };
866
+ });
867
+ return { type: "table", id: blockId(i), rows, headerRows: grid.headerRows };
868
+ }
869
+ const top = wrap.getBoundingClientRect().top;
870
+ const h = height(wrap);
871
+ let breakpoints = SPLITTABLE.has(block.type) ? lineBreakpoints(wrap) : [];
872
+ if (block.type === "paragraph" || block.type === "heading") breakpoints = breakpoints.slice(1, -1);
873
+ const notes = marksIn(wrap).map(({ id, el, height: nh }) => {
874
+ const r = el.getBoundingClientRect();
875
+ return { id, at: Math.min(h, Math.max(0, (r.top + r.bottom) / 2 - top)), height: nh };
876
+ });
877
+ const keepNext = block.type === "heading" || block.keepNext === true;
878
+ return {
879
+ type: "block",
880
+ id: blockId(i),
881
+ height: h,
882
+ ...breakpoints.length ? { breakpoints } : {},
883
+ ...keepNext ? { keepNext: true } : {},
884
+ ...notes.length ? { footnotes: notes } : {}
885
+ };
886
+ });
887
+ const bands = { header: {}, footer: {} };
888
+ for (const kind of ["header", "footer"]) {
889
+ for (const slot of BAND_SLOTS) {
890
+ bands[kind][slot] = height(root.querySelector(`[data-lw-measure-band="${kind}:${slot}"]`));
891
+ }
892
+ }
893
+ return {
894
+ items,
895
+ headerHeight: bands.header.default,
896
+ footerHeight: bands.footer.default,
897
+ bands,
898
+ footnoteSeparator: height(root.querySelector("[data-lw-measure-separator]"))
899
+ };
871
900
  }
872
- function hasBand(band) {
873
- if (isBlocksBand(band)) return band.blocks.length > 0;
874
- if (!band || typeof band !== "object") return false;
875
- const b = band;
876
- return [b.left, b.center, b.right].some((s) => typeof s === "string" && s !== "");
901
+
902
+ // src/paginate.ts
903
+ var EPS = 1e-6;
904
+ var positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
905
+ function pageCapacity(options, index) {
906
+ const base = Math.max(1, positive(options.pageHeight) ? options.pageHeight : 1);
907
+ if (index === 0) return positive(options.firstPageHeight) ? Math.max(1, options.firstPageHeight) : base;
908
+ if (index % 2 === 1) return positive(options.evenPageHeight) ? Math.max(1, options.evenPageHeight) : base;
909
+ return base;
877
910
  }
878
- function BandView({ band }) {
879
- const ctx = useContext(RenderCtx);
880
- if (isBlocksBand(band)) {
881
- return /* @__PURE__ */ jsx(WithContext, { value: { marks: EMPTY_MARKS }, children: band.blocks.map((b, i) => b?.type === "pageBreak" || b?.type === "chart" ? null : /* @__PURE__ */ jsx(BlockView, { block: b }, i)) });
911
+ function paginate(items, options) {
912
+ let cap = pageCapacity(options, 0);
913
+ const sep = Math.max(0, options.footnoteSeparator ?? 0);
914
+ const pages = [];
915
+ let page;
916
+ let content = 0;
917
+ let noteHeights = 0;
918
+ const newPage = () => {
919
+ cap = pageCapacity(options, pages.length);
920
+ page = { index: pages.length, fragments: [], footnotes: [], used: 0, capacity: cap };
921
+ pages.push(page);
922
+ content = 0;
923
+ noteHeights = 0;
924
+ };
925
+ const noteCost = (extraCount, extraHeight) => {
926
+ const count = page.footnotes.length + extraCount;
927
+ return count > 0 ? noteHeights + extraHeight + sep : 0;
928
+ };
929
+ const fits = (h, notes) => content + h + noteCost(notes.length, sum(notes)) <= cap + EPS;
930
+ const commit = (h, notes) => {
931
+ content += h;
932
+ for (const n of notes) {
933
+ page.footnotes.push(n.id);
934
+ noteHeights += n.height;
935
+ }
936
+ page.used = content + noteCost(0, 0);
937
+ };
938
+ const empty = () => page.fragments.length === 0;
939
+ newPage();
940
+ items.forEach((item, index) => {
941
+ if (item.type === "break") {
942
+ newPage();
943
+ return;
944
+ }
945
+ if (item.type === "table") {
946
+ placeTable(item, index);
947
+ return;
948
+ }
949
+ placeBlock(item, index);
950
+ });
951
+ return pages;
952
+ function notesIn(item, start, end, last) {
953
+ const h = Math.max(0, item.height);
954
+ return (item.footnotes ?? []).filter((n) => {
955
+ const at = Math.min(h, Math.max(0, n.at));
956
+ return at >= start - EPS && (last ? at <= end + EPS : at < end - EPS);
957
+ });
882
958
  }
883
- const slot = (v) => typeof v === "string" ? ctx.fields ? substituteFields(v, ctx.fields) : v : "";
884
- return /* @__PURE__ */ jsxs(
885
- Text,
886
- {
887
- as: "div",
888
- style: {
889
- display: "grid",
890
- gridTemplateColumns: "1fr auto 1fr",
891
- fontSize: pt(num(band.size, BAND_SIZE)),
892
- lineHeight: SINGLE_LINE,
893
- color: hex(band.color) ?? BAND_COLOR,
894
- whiteSpace: "pre"
895
- },
896
- children: [
897
- /* @__PURE__ */ jsx("span", { "data-lw-slot": "left", style: { textAlign: "left" }, children: slot(band.left) }),
898
- /* @__PURE__ */ jsx("span", { "data-lw-slot": "center", style: { textAlign: "center" }, children: slot(band.center) }),
899
- /* @__PURE__ */ jsx("span", { "data-lw-slot": "right", style: { textAlign: "right", justifySelf: "end" }, children: slot(band.right) })
900
- ]
959
+ function firstPiece(item) {
960
+ if (!item || item.type === "break") return 0;
961
+ if (item.type === "block") {
962
+ const bp = (item.breakpoints ?? []).find((b) => b > EPS && b < item.height - EPS);
963
+ return bp ?? item.height;
901
964
  }
902
- );
903
- }
904
- function FootnoteView({ id, footnote }) {
905
- return /* @__PURE__ */ jsxs("div", { "data-footnote": id, style: { display: "flex", gap: pt(3) }, children: [
906
- /* @__PURE__ */ jsx("sup", { style: { fontSize: pt(FOOTNOTE_SIZE * 0.65), lineHeight: 1, paddingTop: pt(2) }, children: id }),
907
- /* @__PURE__ */ jsx("div", { style: { flex: 1, minWidth: 0 }, children: /* @__PURE__ */ jsx(WithContext, { value: { marks: EMPTY_MARKS, size: FOOTNOTE_SIZE, spaceAfter: 0 }, children: (Array.isArray(footnote.blocks) ? footnote.blocks : []).map((b, i) => /* @__PURE__ */ jsx(BlockView, { block: b }, i)) }) })
908
- ] });
965
+ const header = item.rows.slice(0, headerCount(item)).reduce((a, r) => a + r.height, 0);
966
+ const units = tableUnits(item);
967
+ return header + (units[0] ? units[0].height : 0);
968
+ }
969
+ function keepChainHeight(from) {
970
+ let total = 0;
971
+ let i = from;
972
+ while (i < items.length) {
973
+ const it = items[i];
974
+ if (it.type === "block" && it.keepNext) {
975
+ total += it.height;
976
+ i++;
977
+ continue;
978
+ }
979
+ total += firstPiece(it);
980
+ break;
981
+ }
982
+ return total;
983
+ }
984
+ function placeBlock(item, index) {
985
+ const height2 = Math.max(0, item.height);
986
+ if (item.keepNext && !empty()) {
987
+ const chain = keepChainHeight(index);
988
+ const next = items[index + 1];
989
+ const meaningful = next !== void 0 && next.type !== "break";
990
+ const nextCap = pageCapacity(options, page.index + 1);
991
+ if (meaningful && chain <= nextCap + EPS && !fits(chain, notesIn(item, 0, height2, true))) newPage();
992
+ }
993
+ let start = 0;
994
+ for (let guard = 0; guard < 1e5; guard++) {
995
+ const restNotes = notesIn(item, start, height2, true);
996
+ if (fits(height2 - start, restNotes)) {
997
+ page.fragments.push({ type: "block", id: item.id, item: index, start, end: height2 });
998
+ commit(height2 - start, restNotes);
999
+ return;
1000
+ }
1001
+ const bps = (item.breakpoints ?? []).filter((b) => b > start + EPS && b < height2 - EPS).sort((a, b) => b - a);
1002
+ let cut;
1003
+ for (const bp of bps) {
1004
+ if (fits(bp - start, notesIn(item, start, bp, false))) {
1005
+ cut = bp;
1006
+ break;
1007
+ }
1008
+ }
1009
+ if (cut !== void 0) {
1010
+ page.fragments.push({ type: "block", id: item.id, item: index, start, end: cut });
1011
+ commit(cut - start, notesIn(item, start, cut, false));
1012
+ newPage();
1013
+ start = cut;
1014
+ continue;
1015
+ }
1016
+ if (!empty()) {
1017
+ newPage();
1018
+ continue;
1019
+ }
1020
+ let room = cap;
1021
+ let end = Math.min(height2, start + room);
1022
+ let notes = notesIn(item, start, end, false);
1023
+ room = cap - noteCost(notes.length, sum(notes));
1024
+ if (room <= EPS) room = cap;
1025
+ end = Math.min(height2, start + room);
1026
+ notes = notesIn(item, start, end, end >= height2 - EPS);
1027
+ const forced = end < height2 - EPS;
1028
+ page.fragments.push({ type: "block", id: item.id, item: index, start, end, ...forced ? { forced: true } : {} });
1029
+ commit(end - start, notes);
1030
+ if (!forced) return;
1031
+ newPage();
1032
+ start = end;
1033
+ }
1034
+ }
1035
+ function headerCount(item) {
1036
+ return Math.max(0, Math.min(item.headerRows ?? 0, item.rows.length));
1037
+ }
1038
+ function tableUnits(item) {
1039
+ const units = [];
1040
+ for (let r = headerCount(item); r < item.rows.length; r++) {
1041
+ const row = item.rows[r];
1042
+ const last = units[units.length - 1];
1043
+ if (row.joinPrevious && last) {
1044
+ last.rows.push(r);
1045
+ last.height += row.height;
1046
+ last.notes.push(...row.footnotes ?? []);
1047
+ } else {
1048
+ units.push({ rows: [r], height: row.height, notes: [...row.footnotes ?? []] });
1049
+ }
1050
+ }
1051
+ return units;
1052
+ }
1053
+ function placeTable(item, index) {
1054
+ const h = headerCount(item);
1055
+ const headerIdx = Array.from({ length: h }, (_, i) => i);
1056
+ const headerHeight = item.rows.slice(0, h).reduce((a, r) => a + r.height, 0);
1057
+ const headerNotes = item.rows.slice(0, h).flatMap((r) => r.footnotes ?? []);
1058
+ const units = tableUnits(item);
1059
+ if (units.length === 0 && h === 0) return;
1060
+ if (units.length === 0) units.push({ rows: [], height: 0, notes: [] });
1061
+ let frag = null;
1062
+ let first = true;
1063
+ for (let u = 0; u < units.length; ) {
1064
+ const unit = units[u];
1065
+ if (frag === null) {
1066
+ const notes = first ? [...headerNotes, ...unit.notes] : unit.notes;
1067
+ const need = headerHeight + unit.height;
1068
+ if (!fits(need, notes) && !empty()) {
1069
+ newPage();
1070
+ continue;
1071
+ }
1072
+ const overflow = !fits(need, notes);
1073
+ frag = {
1074
+ type: "table",
1075
+ id: item.id,
1076
+ item: index,
1077
+ rows: [...headerIdx, ...unit.rows],
1078
+ repeatedHeader: !first && h > 0,
1079
+ ...overflow ? { overflow: true } : {}
1080
+ };
1081
+ page.fragments.push(frag);
1082
+ commit(need, notes);
1083
+ first = false;
1084
+ u++;
1085
+ continue;
1086
+ }
1087
+ if (fits(unit.height, unit.notes)) {
1088
+ frag.rows.push(...unit.rows);
1089
+ commit(unit.height, unit.notes);
1090
+ u++;
1091
+ continue;
1092
+ }
1093
+ newPage();
1094
+ frag = null;
1095
+ }
1096
+ }
909
1097
  }
910
- function FootnoteSeparator({ height: height2 }) {
911
- return /* @__PURE__ */ jsx("div", { "data-lw-footnote-separator": "", style: { height: height2, display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx("div", { style: { width: pt(144), borderTop: `${pt(0.5)} solid #000000` } }) });
1098
+ function sum(notes) {
1099
+ return notes.reduce((a, n) => a + n.height, 0);
912
1100
  }
913
1101
 
914
1102
  // src/viewer-css.ts
@@ -925,6 +1113,8 @@ function DocViewer({
925
1113
  scale: fixedScale,
926
1114
  maxScale = 1,
927
1115
  gap = 16,
1116
+ filename,
1117
+ now,
928
1118
  onLayout,
929
1119
  injectStyles = true,
930
1120
  id,
@@ -934,8 +1124,21 @@ function DocViewer({
934
1124
  const geometry = useMemo(() => pageGeometry(doc), [doc]);
935
1125
  const numbering = useMemo(() => numberFootnotes(doc), [doc]);
936
1126
  const blocks = useMemo(() => Array.isArray(doc?.blocks) ? doc.blocks : [], [doc]);
937
- const header = hasBand(doc?.page?.header) ? doc.page.header : null;
938
- const footer = hasBand(doc?.page?.footer) ? doc.page.footer : null;
1127
+ const page = doc?.page && typeof doc.page === "object" ? doc.page : void 0;
1128
+ const nowKey = now instanceof Date ? now.getTime() : now;
1129
+ const nowDate = useMemo(() => resolveNow(nowKey === void 0 ? void 0 : typeof nowKey === "number" ? new Date(nowKey) : nowKey), [nowKey]);
1130
+ const author = doc?.meta && typeof doc.meta === "object" ? doc.meta.author : void 0;
1131
+ const title = doc?.title;
1132
+ const docFields = useMemo(
1133
+ () => ({
1134
+ section: 1,
1135
+ ...typeof author === "string" ? { author } : {},
1136
+ ...typeof title === "string" ? { title } : {},
1137
+ ...typeof filename === "string" ? { filename } : {},
1138
+ now: nowDate
1139
+ }),
1140
+ [author, title, filename, nowDate]
1141
+ );
939
1142
  const rootRef = useRef(null);
940
1143
  const measureRef = useRef(null);
941
1144
  const [layout, setLayout] = useState(null);
@@ -954,11 +1157,21 @@ function DocViewer({
954
1157
  const el = measureRef.current;
955
1158
  if (!el) return;
956
1159
  const m = measure(el, blocks);
957
- const box = bodyBox(geometry, m.headerHeight / PX_PER_PT, m.footerHeight / PX_PER_PT);
958
- const pages = paginate(m.items, { pageHeight: box.height * PX_PER_PT, footnoteSeparator: m.footnoteSeparator });
1160
+ const toPt = (h) => ({
1161
+ default: h.default / PX_PER_PT,
1162
+ first: h.first / PX_PER_PT,
1163
+ even: h.even / PX_PER_PT
1164
+ });
1165
+ const boxes = pageBodyBoxes(geometry, page, { header: toPt(m.bands.header), footer: toPt(m.bands.footer) });
1166
+ const pages = paginate(m.items, {
1167
+ pageHeight: boxes.odd.height * PX_PER_PT,
1168
+ firstPageHeight: boxes.first.height * PX_PER_PT,
1169
+ evenPageHeight: boxes.even.height * PX_PER_PT,
1170
+ footnoteSeparator: m.footnoteSeparator
1171
+ });
959
1172
  const heights = m.items.map((it) => it.type === "block" ? it.height : 0);
960
- setLayout({ pages, box, heights });
961
- }, [blocks, geometry, numbering, fontEpoch, header, footer]);
1173
+ setLayout({ pages, boxes, heights });
1174
+ }, [blocks, geometry, numbering, fontEpoch, page, docFields]);
962
1175
  useEffect(() => {
963
1176
  const fonts = typeof document !== "undefined" ? document.fonts : void 0;
964
1177
  let live = true;
@@ -1019,40 +1232,50 @@ function DocViewer({
1019
1232
  width: pt(geometry.contentWidth)
1020
1233
  },
1021
1234
  children: /* @__PURE__ */ jsxs(RenderCtx.Provider, { value: ctx, children: [
1022
- header && /* @__PURE__ */ jsx("div", { "data-lw-measure-header": "", children: /* @__PURE__ */ jsx(RenderCtx.Provider, { value: { ...ctx, fields: { page: 1, pages: 1 } }, children: /* @__PURE__ */ jsx(BandView, { band: header }) }) }),
1023
- footer && /* @__PURE__ */ jsx("div", { "data-lw-measure-footer": "", children: /* @__PURE__ */ jsx(RenderCtx.Provider, { value: { ...ctx, fields: { page: 1, pages: 1 } }, children: /* @__PURE__ */ jsx(BandView, { band: footer }) }) }),
1235
+ ["header", "footer"].flatMap(
1236
+ (kind) => BAND_SLOTS.map((slot) => {
1237
+ const band = slotBand(page, kind, slot);
1238
+ if (!hasBand(band)) return null;
1239
+ const n = slot === "even" ? 2 : 1;
1240
+ return /* @__PURE__ */ jsx("div", { "data-lw-measure-band": `${kind}:${slot}`, children: /* @__PURE__ */ jsx(RenderCtx.Provider, { value: { ...ctx, fields: { ...docFields, page: n, pages: n } }, children: /* @__PURE__ */ jsx(BandView, { band }) }) }, `${kind}:${slot}`);
1241
+ })
1242
+ ),
1024
1243
  blocks.map((b, i) => /* @__PURE__ */ jsx("div", { "data-lw-measure-block": i, style: { display: "flow-root" }, children: /* @__PURE__ */ jsx(BlockView, { block: b }) }, i)),
1025
1244
  numbering.notes.map((n) => /* @__PURE__ */ jsx("div", { "data-lw-measure-footnote": n.id, children: /* @__PURE__ */ jsx(FootnoteView, { id: n.id, footnote: n.footnote }) }, n.id)),
1026
1245
  numbering.notes.length > 0 && /* @__PURE__ */ jsx("div", { "data-lw-measure-separator": "", children: /* @__PURE__ */ jsx(FootnoteSeparator, { height: pt(FOOTNOTE_SEPARATOR_PT) }) })
1027
1246
  ] })
1028
1247
  }
1029
1248
  ),
1030
- layout && /* @__PURE__ */ jsx("div", { "data-lw-page-stack": "", style: { display: "flex", flexDirection: "column", alignItems: "center", gap }, children: layout.pages.map((page) => /* @__PURE__ */ jsx(
1249
+ layout && /* @__PURE__ */ jsx("div", { "data-lw-page-stack": "", style: { display: "flex", flexDirection: "column", alignItems: "center", gap }, children: layout.pages.map((p) => /* @__PURE__ */ jsx(
1031
1250
  PageView,
1032
1251
  {
1033
- page,
1252
+ page: p,
1034
1253
  pages: layout.pages.length,
1035
1254
  geometry,
1036
- box: layout.box,
1255
+ box: layout.boxes[pageClass(p.index)],
1037
1256
  scale,
1038
1257
  typography,
1039
1258
  ctx,
1040
- header,
1041
- footer,
1259
+ docPage: page,
1260
+ docFields,
1042
1261
  numbering,
1043
1262
  blocks,
1044
1263
  heights: layout.heights
1045
1264
  },
1046
- page.index
1265
+ p.index
1047
1266
  )) })
1048
1267
  ]
1049
1268
  }
1050
1269
  );
1051
1270
  }
1052
- function PageView({ page, pages, geometry, box, scale, typography, ctx, header, footer, numbering, blocks, heights }) {
1271
+ function PageView({ page, pages, geometry, box, scale, typography, ctx, docPage, docFields, numbering, blocks, heights }) {
1053
1272
  const n = page.index + 1;
1054
1273
  const g = geometry;
1055
- const bandCtx = { ...ctx, fields: { page: n, pages } };
1274
+ const bandCtx = { ...ctx, fields: { ...docFields, page: n, pages, sectionPages: pages } };
1275
+ const cls = pageClass(page.index);
1276
+ const header = pageBand(docPage, "header", page.index);
1277
+ const footer = pageBand(docPage, "footer", page.index);
1278
+ const variant = (kind) => bandSlot(docPage, kind, cls);
1056
1279
  const notes = page.footnotes.map((fid) => numbering.notes.find((x) => x.id === fid)).filter((x) => !!x);
1057
1280
  return /* @__PURE__ */ jsx(
1058
1281
  "div",
@@ -1080,6 +1303,7 @@ function PageView({ page, pages, geometry, box, scale, typography, ctx, header,
1080
1303
  "div",
1081
1304
  {
1082
1305
  "data-lw-band": "header",
1306
+ "data-lw-band-variant": variant("header"),
1083
1307
  style: { position: "absolute", top: pt(g.margins.header), left: pt(g.margins.left), width: pt(g.contentWidth) },
1084
1308
  children: /* @__PURE__ */ jsx(RenderCtx.Provider, { value: bandCtx, children: /* @__PURE__ */ jsx(BandView, { band: header }) })
1085
1309
  }
@@ -1110,6 +1334,7 @@ function PageView({ page, pages, geometry, box, scale, typography, ctx, header,
1110
1334
  "div",
1111
1335
  {
1112
1336
  "data-lw-band": "footer",
1337
+ "data-lw-band-variant": variant("footer"),
1113
1338
  style: { position: "absolute", bottom: pt(g.margins.footer), left: pt(g.margins.left), width: pt(g.contentWidth) },
1114
1339
  children: /* @__PURE__ */ jsx(RenderCtx.Provider, { value: bandCtx, children: /* @__PURE__ */ jsx(BandView, { band: footer }) })
1115
1340
  }
@@ -1169,6 +1394,6 @@ function docFromReader(output) {
1169
1394
  return value;
1170
1395
  }
1171
1396
 
1172
- export { DocViewer, PAGE_BAND_DISTANCE_DEFAULT, PAGE_MARGIN_DEFAULT, PAGE_SIZES_PT, PX_PER_PT, bodyBox, docFromReader, numberFootnotes, pageGeometry, pageMargins, paginate, substituteFields };
1397
+ export { DocViewer, PAGE_BAND_DISTANCE_DEFAULT, PAGE_MARGIN_DEFAULT, PAGE_SIZES_PT, PX_PER_PT, bandSlot, bodyBox, docFromReader, formatDatePicture, numberFootnotes, pageBand, pageBodyBoxes, pageCapacity, pageClass, pageGeometry, pageMargins, paginate, resolveNow, substituteFields };
1173
1398
  //# sourceMappingURL=index.js.map
1174
1399
  //# sourceMappingURL=index.js.map