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