docxodus 12.6.4 → 13.0.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/README.md +8 -5
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +7 -3
- package/dist/canonical.js.map +1 -1
- package/dist/conversion-options.d.ts +16 -0
- package/dist/conversion-options.d.ts.map +1 -0
- package/dist/conversion-options.js +31 -0
- package/dist/conversion-options.js.map +1 -0
- package/dist/core.d.ts +17 -9
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +23 -24
- package/dist/core.js.map +1 -1
- package/dist/docxodus.worker.js +2 -1073
- package/dist/docxodus.worker.js.map +7 -1
- package/dist/editor.bundle.js +35 -14296
- package/dist/editor.bundle.js.map +7 -0
- package/dist/editor.d.ts +33 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +6739 -4773
- package/dist/editor.js.map +7 -1
- package/dist/embed.bundle.js +159 -18703
- package/dist/embed.bundle.js.map +7 -0
- package/dist/embed.d.ts +1 -1
- package/dist/embed.iife.js +37 -18595
- package/dist/embed.iife.js.map +7 -0
- package/dist/embed.js +1 -1
- package/dist/export-assets.json +27 -27
- package/dist/export-browser.bundle.js +1388 -477
- package/dist/export-browser.d.ts +20 -0
- package/dist/export-browser.d.ts.map +1 -1
- package/dist/export-browser.js +132 -37
- package/dist/export-browser.js.map +1 -1
- package/dist/font-runtime.d.ts.map +1 -1
- package/dist/font-runtime.js +54 -0
- package/dist/font-runtime.js.map +1 -1
- package/dist/line-metrics.d.ts +60 -0
- package/dist/line-metrics.d.ts.map +1 -0
- package/dist/line-metrics.js +343 -0
- package/dist/line-metrics.js.map +1 -0
- package/dist/pagination.bundle.js +3 -3214
- package/dist/pagination.bundle.js.map +7 -0
- package/dist/pagination.d.ts +86 -9
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +358 -55
- package/dist/pagination.js.map +1 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +63 -12
- package/dist/react.js.map +1 -1
- package/dist/render-report-v2.schema.json +29 -0
- package/dist/session.bundle.js +2 -2013
- package/dist/session.bundle.js.map +7 -0
- package/dist/session.d.ts +14 -5
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +62 -29
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +137 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -1
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Memory.wasm +0 -0
- package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +11 -11
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
- package/dist/word-text.d.ts +14 -0
- package/dist/word-text.d.ts.map +1 -0
- package/dist/word-text.js +377 -0
- package/dist/word-text.js.map +1 -0
- package/dist/worker-proxy.bundle.js +2 -536
- package/dist/worker-proxy.bundle.js.map +7 -0
- package/dist/worker-proxy.d.ts +14 -1
- package/dist/worker-proxy.d.ts.map +1 -1
- package/dist/worker-proxy.js +357 -339
- package/dist/worker-proxy.js.map +1 -1
- package/package.json +13 -12
package/dist/pagination.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* and flows it across fixed-size page containers based on document dimensions.
|
|
6
6
|
*/
|
|
7
7
|
import { formatPageNumber } from "./page-number-format.js";
|
|
8
|
+
import { alignExactLineBaselines, insetClipBounds, preserveExactLineInk } from "./line-metrics.js";
|
|
8
9
|
import { DEFAULT_MARGIN, DEFAULT_PAGE_WIDTH, parseSectionDimensions, ptToPx, pxToPt, resolvePageBands, } from "./page-geometry.js";
|
|
9
10
|
/**
|
|
10
11
|
* Whether two sections describe the same physical page box. A continuous section
|
|
@@ -124,6 +125,198 @@ const MAX_FOOTNOTE_AREA_RATIO = 0.6; // 60% of content height
|
|
|
124
125
|
const FOOTNOTE_MEASUREMENT_GUARD_PT = 2;
|
|
125
126
|
/** Internal provenance retained only while a note element waits between pages. */
|
|
126
127
|
const FOOTNOTE_SOURCE_POSITION_ATTR = "data-pagination-footnote-source-position";
|
|
128
|
+
/**
|
|
129
|
+
* The top and bottom margins a block occupies in its parent's flow, in CSS px, AFTER CSS margin
|
|
130
|
+
* collapsing through its first and last in-flow children (CSS 2.1 §8.3.1). getBoundingClientRect()
|
|
131
|
+
* excludes such a collapsed-through margin, and getComputedStyle() reports only the element's own:
|
|
132
|
+
* the converter wraps a table in an unstyled `div`, so the table's own top margin collapses
|
|
133
|
+
* through the wrapper and occupies page height that neither of those reads accounted for. The
|
|
134
|
+
* paginator's budget then fell a few pixels to a line short of the laid-out page, and the export
|
|
135
|
+
* refused the clipped page (issue #848).
|
|
136
|
+
*
|
|
137
|
+
* A margin collapses with a parent's while the parent is an ordinary block box (display block or
|
|
138
|
+
* list-item, overflow visible, not floated or absolutely positioned — anything else establishes
|
|
139
|
+
* its own formatting context) with no border or padding on that side, the child is the parent's
|
|
140
|
+
* first (last) in-flow block-level child, and no line content precedes (follows) it. For the
|
|
141
|
+
* bottom edge the parent must also have an automatic height. Positive margins combine by maximum
|
|
142
|
+
* and negative ones by minimum, as CSS collapses them.
|
|
143
|
+
*/
|
|
144
|
+
export function collapsedBlockMarginsPx(view, element) {
|
|
145
|
+
return {
|
|
146
|
+
topPx: collapseMarginChain(view, element, "top"),
|
|
147
|
+
bottomPx: collapseMarginChain(view, element, "bottom"),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function collapseMarginChain(view, element, edge) {
|
|
151
|
+
const margins = [];
|
|
152
|
+
let current = element;
|
|
153
|
+
while (current) {
|
|
154
|
+
const style = view.getComputedStyle(current);
|
|
155
|
+
margins.push(parseFloat(edge === "top" ? style.marginTop : style.marginBottom) || 0);
|
|
156
|
+
current = collapsesThroughTo(view, current, style, edge);
|
|
157
|
+
}
|
|
158
|
+
const positive = Math.max(0, ...margins);
|
|
159
|
+
const negative = Math.min(0, ...margins);
|
|
160
|
+
return positive + negative;
|
|
161
|
+
}
|
|
162
|
+
/** The child whose `edge` margin collapses through `parent`'s, or null when none does. */
|
|
163
|
+
function collapsesThroughTo(view, parent, style, edge) {
|
|
164
|
+
const ordinaryBlock = (style.display === "block" || style.display === "list-item")
|
|
165
|
+
&& style.overflowX === "visible" && style.overflowY === "visible"
|
|
166
|
+
&& style.float === "none" && style.position !== "absolute" && style.position !== "fixed";
|
|
167
|
+
if (!ordinaryBlock)
|
|
168
|
+
return null;
|
|
169
|
+
// Column boxes establish a formatting context: child margins stay inside the multicol
|
|
170
|
+
// box and are already included in its measured height (CSS Multi-column Layout §2).
|
|
171
|
+
if (style.columnCount !== "auto" || style.columnWidth !== "auto")
|
|
172
|
+
return null;
|
|
173
|
+
const border = parseFloat(edge === "top" ? style.borderTopWidth : style.borderBottomWidth) || 0;
|
|
174
|
+
const padding = parseFloat(edge === "top" ? style.paddingTop : style.paddingBottom) || 0;
|
|
175
|
+
if (border !== 0 || padding !== 0)
|
|
176
|
+
return null;
|
|
177
|
+
if (edge === "bottom" && parent.style.height !== "")
|
|
178
|
+
return null;
|
|
179
|
+
const nodes = Array.from(parent.childNodes);
|
|
180
|
+
if (edge === "bottom")
|
|
181
|
+
nodes.reverse();
|
|
182
|
+
for (const node of nodes) {
|
|
183
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
184
|
+
if ((node.textContent ?? "").trim() !== "")
|
|
185
|
+
return null; // line content comes first
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (node.nodeType !== Node.ELEMENT_NODE)
|
|
189
|
+
continue;
|
|
190
|
+
const child = node;
|
|
191
|
+
const childStyle = view.getComputedStyle(child);
|
|
192
|
+
if (childStyle.display === "none")
|
|
193
|
+
continue;
|
|
194
|
+
if (childStyle.position === "absolute" || childStyle.position === "fixed" || childStyle.float !== "none")
|
|
195
|
+
continue;
|
|
196
|
+
const blockLevel = childStyle.display === "block" || childStyle.display === "list-item"
|
|
197
|
+
|| childStyle.display === "table" || childStyle.display === "flow-root"
|
|
198
|
+
|| childStyle.display === "flex" || childStyle.display === "grid";
|
|
199
|
+
return blockLevel ? child : null; // an inline-level child starts a line box
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Set by the paginator on a floating drawing it promotes out of its paragraph into the page box;
|
|
205
|
+
* the value is the `data-source-anchor-id` of the element that hosted it. It survives into the
|
|
206
|
+
* standalone export, whose offline re-verification measures through the same function.
|
|
207
|
+
*/
|
|
208
|
+
export const DRAWING_HOST_ATTRIBUTE = "data-docx-anchor-host";
|
|
209
|
+
/**
|
|
210
|
+
* Set on a source-anchored element a floating drawing was promoted out of, so
|
|
211
|
+
* {@link measureRenderedSource} looks up promoted drawings only for elements that have them.
|
|
212
|
+
*/
|
|
213
|
+
export const HOSTS_PROMOTED_DRAWING_ATTRIBUTE = "data-docx-hosts-promoted-drawing";
|
|
214
|
+
/** True when `element` shows no text (beyond white space) and no image, SVG or canvas. */
|
|
215
|
+
function rendersOnlyItsLine(element) {
|
|
216
|
+
return (element.textContent ?? '').replace(/[\s\u00a0]/g, '') === ''
|
|
217
|
+
&& element.querySelector('img, svg, canvas, [data-docx-drawing-anchor]') === null;
|
|
218
|
+
}
|
|
219
|
+
/** Self-contained closure for trusted host evaluation in the script-disabled print context. */
|
|
220
|
+
function createRenderedSourceMeasurer(clipInset, drawingHostAttribute, hostsPromotedDrawingAttribute) {
|
|
221
|
+
/**
|
|
222
|
+
* Intersect a box with the page and with every ancestor that establishes an overflow clip before
|
|
223
|
+
* the page root. getBoundingClientRect() reports layout outside those clips, which is not rendered
|
|
224
|
+
* and therefore must not satisfy PageMap completeness or inflate portable geometry.
|
|
225
|
+
*/
|
|
226
|
+
function visibleWithinPage(view, element, page, pageRect, rect) {
|
|
227
|
+
let left = Math.max(rect.left, pageRect.left);
|
|
228
|
+
let top = Math.max(rect.top, pageRect.top);
|
|
229
|
+
let right = Math.min(rect.right, pageRect.right);
|
|
230
|
+
let bottom = Math.min(rect.bottom, pageRect.bottom);
|
|
231
|
+
const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
|
|
232
|
+
for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
|
|
233
|
+
const style = view.getComputedStyle(ancestor);
|
|
234
|
+
const clipsX = clips(style.overflowX);
|
|
235
|
+
const clipsY = clips(style.overflowY);
|
|
236
|
+
if (!clipsX && !clipsY && !style.clipPath.startsWith("inset("))
|
|
237
|
+
continue;
|
|
238
|
+
const ancestorRect = ancestor.getBoundingClientRect();
|
|
239
|
+
const scale = ancestorRect.width / ancestor.offsetWidth || 1;
|
|
240
|
+
const inset = clipInset(style.clipPath, ancestorRect, scale);
|
|
241
|
+
if (inset) {
|
|
242
|
+
left = Math.max(left, inset.left);
|
|
243
|
+
top = Math.max(top, inset.top);
|
|
244
|
+
right = Math.min(right, inset.right);
|
|
245
|
+
bottom = Math.min(bottom, inset.bottom);
|
|
246
|
+
}
|
|
247
|
+
if (clipsX) {
|
|
248
|
+
left = Math.max(left, ancestorRect.left);
|
|
249
|
+
right = Math.min(right, ancestorRect.right);
|
|
250
|
+
}
|
|
251
|
+
if (clipsY) {
|
|
252
|
+
top = Math.max(top, ancestorRect.top);
|
|
253
|
+
bottom = Math.min(bottom, ancestorRect.bottom);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { left, top, right, bottom };
|
|
257
|
+
}
|
|
258
|
+
return function measureRenderedSource(view, element, page, pageRect = page.getBoundingClientRect()) {
|
|
259
|
+
const own = element.getBoundingClientRect();
|
|
260
|
+
const ownResult = () => ({ rect: own, visible: visibleWithinPage(view, element, page, pageRect, own) });
|
|
261
|
+
const ownMeasurable = own.width > 0 && own.height > 0;
|
|
262
|
+
const hostsPromotedDrawing = element.hasAttribute(hostsPromotedDrawingAttribute);
|
|
263
|
+
if (ownMeasurable && !hostsPromotedDrawing)
|
|
264
|
+
return ownResult();
|
|
265
|
+
const hostAnchorId = element.dataset.sourceAnchorId;
|
|
266
|
+
const promoted = hostAnchorId
|
|
267
|
+
? Array.from(page.querySelectorAll(`[${drawingHostAttribute}]`))
|
|
268
|
+
.filter((drawing) => drawing.getAttribute(drawingHostAttribute) === hostAnchorId)
|
|
269
|
+
: [];
|
|
270
|
+
if (ownMeasurable && promoted.length === 0)
|
|
271
|
+
return ownResult();
|
|
272
|
+
const rendered = [
|
|
273
|
+
...(ownMeasurable ? [element] : Array.from(element.querySelectorAll("*"))),
|
|
274
|
+
...promoted.flatMap((drawing) => [drawing, ...Array.from(drawing.querySelectorAll("*"))]),
|
|
275
|
+
];
|
|
276
|
+
let left = Infinity;
|
|
277
|
+
let top = Infinity;
|
|
278
|
+
let right = -Infinity;
|
|
279
|
+
let bottom = -Infinity;
|
|
280
|
+
for (const child of rendered) {
|
|
281
|
+
const style = view.getComputedStyle(child);
|
|
282
|
+
if (style.display === "none" || style.visibility === "hidden")
|
|
283
|
+
continue;
|
|
284
|
+
const childRect = child.getBoundingClientRect();
|
|
285
|
+
if (childRect.width <= 0 || childRect.height <= 0)
|
|
286
|
+
continue;
|
|
287
|
+
const visible = visibleWithinPage(view, child, page, pageRect, childRect);
|
|
288
|
+
if (visible.right <= visible.left || visible.bottom <= visible.top)
|
|
289
|
+
continue;
|
|
290
|
+
left = Math.min(left, visible.left);
|
|
291
|
+
top = Math.min(top, visible.top);
|
|
292
|
+
right = Math.max(right, visible.right);
|
|
293
|
+
bottom = Math.max(bottom, visible.bottom);
|
|
294
|
+
}
|
|
295
|
+
if (!(left < right && top < bottom))
|
|
296
|
+
return ownResult();
|
|
297
|
+
return { rect: new DOMRect(left, top, right - left, bottom - top), visible: { left, top, right, bottom } };
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Where a source-anchored element renders on `page`: `rect` is its layout box and `visible` the
|
|
302
|
+
* part of it the page shows. Normally that is the element's own border box. An element the
|
|
303
|
+
* paginator promoted floating drawings out of measures as the union of what it renders — its own
|
|
304
|
+
* box (or, when that is empty, its descendants) and the drawings promoted out of it (linked by
|
|
305
|
+
* {@link DRAWING_HOST_ATTRIBUTE}) — each clipped along its OWN ancestor chain, since a promoted
|
|
306
|
+
* drawing sits outside the text column that clips its host (issue #849). Such a paragraph keeps a
|
|
307
|
+
* line of its own when the drawing was its only content (issue #880), and its fragment still
|
|
308
|
+
* encloses the drawing. Returns the element's own degenerate box when none of that renders.
|
|
309
|
+
*
|
|
310
|
+
* Fragment-identity normalization, PageMap materialization and the standalone export's offline
|
|
311
|
+
* re-verification all measure through this one function, so they agree on which elements are
|
|
312
|
+
* measurable and where.
|
|
313
|
+
*/
|
|
314
|
+
export const measureRenderedSource = createRenderedSourceMeasurer(insetClipBounds, DRAWING_HOST_ATTRIBUTE, HOSTS_PROMOTED_DRAWING_ATTRIBUTE);
|
|
315
|
+
/** @internal Owned runtime code only; never inserts a script or enables document JavaScript. */
|
|
316
|
+
export function renderedSourceMeasurerExpression() {
|
|
317
|
+
return `(${createRenderedSourceMeasurer.toString()})(${insetClipBounds.toString()},
|
|
318
|
+
${JSON.stringify(DRAWING_HOST_ATTRIBUTE)}, ${JSON.stringify(HOSTS_PROMOTED_DRAWING_ATTRIBUTE)})`;
|
|
319
|
+
}
|
|
127
320
|
export class PaginationEngine {
|
|
128
321
|
/**
|
|
129
322
|
* Creates a new pagination engine.
|
|
@@ -177,6 +370,7 @@ export class PaginationEngine {
|
|
|
177
370
|
this.pageGap = options.pageGap ?? 20;
|
|
178
371
|
this.fragmentParagraphs = options.fragmentParagraphs ?? false;
|
|
179
372
|
this.deferFragmentIdentities = options.deferFragmentIdentities ?? false;
|
|
373
|
+
this.bodyBudgetReductionsPt = options.bodyBudgetReductionsPt ?? {};
|
|
180
374
|
this.cancellationCheckpoint = options.checkCancellation;
|
|
181
375
|
this.pageCountCheckpoint = options.checkPageCount;
|
|
182
376
|
this.layoutToken = options.layoutToken;
|
|
@@ -324,7 +518,8 @@ export class PaginationEngine {
|
|
|
324
518
|
const columnCount = parseInt(section.dataset.cols || "1", 10);
|
|
325
519
|
if (columnCount > 1) {
|
|
326
520
|
const gap = parseFloat(section.dataset.colGap || "");
|
|
327
|
-
blocks.push(...this.buildColumnBlocks(section, sectionDims, sectionIndex, columnCount, Number.isFinite(gap) ? gap : 36
|
|
521
|
+
blocks.push(...this.buildColumnBlocks(section, sectionDims, sectionIndex, columnCount, Number.isFinite(gap) ? gap : 36, section.dataset.sectionType === "continuous"
|
|
522
|
+
&& section === sectionsToProcess[sectionsToProcess.length - 1]));
|
|
328
523
|
}
|
|
329
524
|
else {
|
|
330
525
|
blocks.push(...this.measureBlocks(section, sectionDims, sectionIndex));
|
|
@@ -440,13 +635,12 @@ export class PaginationEngine {
|
|
|
440
635
|
|| !Number.isInteger(parseInt(element.dataset.fragmentIndex || "", 10))) {
|
|
441
636
|
throw new Error(`page ${page.pageNumber} contains an unqualified source anchor`);
|
|
442
637
|
}
|
|
443
|
-
const rect = element.getBoundingClientRect();
|
|
444
638
|
const style = this.view.getComputedStyle(element);
|
|
445
639
|
const deliberatelyHidden = style.display === "none" || style.visibility === "hidden";
|
|
446
640
|
if (deliberatelyHidden)
|
|
447
641
|
continue;
|
|
642
|
+
const { rect, visible: visibleRect } = measureRenderedSource(this.view, element, page.element, pageRect);
|
|
448
643
|
requiredAnchorIds.add(anchorId);
|
|
449
|
-
const visibleRect = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
450
644
|
const left = visibleRect.left;
|
|
451
645
|
const top = visibleRect.top;
|
|
452
646
|
const right = visibleRect.right;
|
|
@@ -513,8 +707,7 @@ export class PaginationEngine {
|
|
|
513
707
|
const style = this.view.getComputedStyle(element);
|
|
514
708
|
if (style.display === "none" || style.visibility === "hidden")
|
|
515
709
|
continue;
|
|
516
|
-
const rect = element.
|
|
517
|
-
const visible = this.intersectWithClippingAncestors(element, page.element, pageRect, rect);
|
|
710
|
+
const { rect, visible } = measureRenderedSource(this.view, element, page.element, pageRect);
|
|
518
711
|
if (rect.width <= 0 || rect.height <= 0
|
|
519
712
|
|| visible.right <= visible.left || visible.bottom <= visible.top)
|
|
520
713
|
continue;
|
|
@@ -526,35 +719,6 @@ export class PaginationEngine {
|
|
|
526
719
|
}
|
|
527
720
|
}
|
|
528
721
|
}
|
|
529
|
-
/**
|
|
530
|
-
* Intersect an element with every ancestor that establishes an overflow clip before the page
|
|
531
|
-
* root. getBoundingClientRect() reports layout outside those clips, which is not rendered and
|
|
532
|
-
* therefore must not satisfy PageMap completeness or inflate portable geometry.
|
|
533
|
-
*/
|
|
534
|
-
intersectWithClippingAncestors(element, page, pageRect, rect) {
|
|
535
|
-
let left = Math.max(rect.left, pageRect.left);
|
|
536
|
-
let top = Math.max(rect.top, pageRect.top);
|
|
537
|
-
let right = Math.min(rect.right, pageRect.right);
|
|
538
|
-
let bottom = Math.min(rect.bottom, pageRect.bottom);
|
|
539
|
-
const clips = (value) => value === "hidden" || value === "clip" || value === "scroll" || value === "auto";
|
|
540
|
-
for (let ancestor = element.parentElement; ancestor && ancestor !== page; ancestor = ancestor.parentElement) {
|
|
541
|
-
const style = this.view.getComputedStyle(ancestor);
|
|
542
|
-
const clipsX = clips(style.overflowX);
|
|
543
|
-
const clipsY = clips(style.overflowY);
|
|
544
|
-
if (!clipsX && !clipsY)
|
|
545
|
-
continue;
|
|
546
|
-
const ancestorRect = ancestor.getBoundingClientRect();
|
|
547
|
-
if (clipsX) {
|
|
548
|
-
left = Math.max(left, ancestorRect.left);
|
|
549
|
-
right = Math.min(right, ancestorRect.right);
|
|
550
|
-
}
|
|
551
|
-
if (clipsY) {
|
|
552
|
-
top = Math.max(top, ancestorRect.top);
|
|
553
|
-
bottom = Math.min(bottom, ancestorRect.bottom);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
return { left, top, right, bottom };
|
|
557
|
-
}
|
|
558
722
|
storyForCanonicalAnchor(anchorId) {
|
|
559
723
|
const first = anchorId.indexOf(":");
|
|
560
724
|
const second = first < 0 ? -1 : anchorId.indexOf(":", first + 1);
|
|
@@ -792,9 +956,7 @@ export class PaginationEngine {
|
|
|
792
956
|
// Measure height and margins separately for proper margin collapsing calculation
|
|
793
957
|
// getBoundingClientRect() returns content+padding+border, not margins
|
|
794
958
|
const rect = child.getBoundingClientRect();
|
|
795
|
-
const
|
|
796
|
-
const marginTopPx = parseFloat(style.marginTop) || 0;
|
|
797
|
-
const marginBottomPx = parseFloat(style.marginBottom) || 0;
|
|
959
|
+
const { topPx: marginTopPx, bottomPx: marginBottomPx } = collapsedBlockMarginsPx(this.view, child);
|
|
798
960
|
const heightPt = pxToPt(rect.height);
|
|
799
961
|
const marginTopPt = pxToPt(marginTopPx);
|
|
800
962
|
const marginBottomPt = pxToPt(marginBottomPx);
|
|
@@ -840,8 +1002,16 @@ export class PaginationEngine {
|
|
|
840
1002
|
for (const rule of sectionChildren.filter((child) => child.tagName === "HR")) {
|
|
841
1003
|
this.checkpoint();
|
|
842
1004
|
const clonedRule = rule.cloneNode(true);
|
|
843
|
-
|
|
844
|
-
|
|
1005
|
+
// The rule leaves its section when flattened, so preserve the section-scoped
|
|
1006
|
+
// print styling before measuring and painting the detached clone.
|
|
1007
|
+
const ruleStyle = this.view.getComputedStyle(rule);
|
|
1008
|
+
for (const property of ["height", "width", "max-width", "border", "background-color", "color",
|
|
1009
|
+
"margin-top", "margin-right", "margin-bottom", "margin-left"]) {
|
|
1010
|
+
clonedRule.style.setProperty(property, ruleStyle.getPropertyValue(property));
|
|
1011
|
+
}
|
|
1012
|
+
if (blocks.length === 0) {
|
|
1013
|
+
clonedRule.style.marginTop = `calc(${sectionStyle.marginTop} + ${ruleStyle.marginTop})`;
|
|
1014
|
+
}
|
|
845
1015
|
blocks.push(this.measureElement(clonedRule, dims, sectionIndex));
|
|
846
1016
|
}
|
|
847
1017
|
const listStyle = this.view.getComputedStyle(list);
|
|
@@ -886,9 +1056,11 @@ export class PaginationEngine {
|
|
|
886
1056
|
return null;
|
|
887
1057
|
clone.id = itemId;
|
|
888
1058
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1059
|
+
if (!clone.querySelector('[data-endnote-number]')) {
|
|
1060
|
+
const value = parseInt(item.getAttribute("value") || String(itemIndex + 1), 10);
|
|
1061
|
+
const marker = this.formatOrderedListMarker(Number.isFinite(value) ? value : itemIndex + 1, listStyle.listStyleType);
|
|
1062
|
+
clone.insertBefore(this.document.createTextNode(`${marker}. `), clone.firstChild);
|
|
1063
|
+
}
|
|
892
1064
|
}
|
|
893
1065
|
blocks.push(this.measureElement(clone, dims, sectionIndex));
|
|
894
1066
|
}
|
|
@@ -913,15 +1085,16 @@ export class PaginationEngine {
|
|
|
913
1085
|
/**
|
|
914
1086
|
* Flows a multi-column (`w:cols`) section's children into CSS-multicol container
|
|
915
1087
|
* blocks. Word lays such a section out as N columns inside the same body extent;
|
|
916
|
-
* a
|
|
1088
|
+
* a `column-count` container reproduces that geometry, and the paginator
|
|
917
1089
|
* then places each container as one ordinary measured block. A container grows
|
|
918
|
-
* greedily until
|
|
919
|
-
*
|
|
1090
|
+
* greedily until it would exceed the smallest page body available to the section.
|
|
1091
|
+
* Final continuous sections fill that height sequentially; other sections retain
|
|
1092
|
+
* balancing. A long columned section still splits across pages at block
|
|
920
1093
|
* boundaries. Each child lands in exactly one container, so anchors never
|
|
921
1094
|
* duplicate. An explicit page break child passes through as its own block, which
|
|
922
1095
|
* ends the current container and lets the normal flow logic turn the page.
|
|
923
1096
|
*/
|
|
924
|
-
buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt) {
|
|
1097
|
+
buildColumnBlocks(section, dims, sectionIndex, columnCount, columnGapPt, sequential = false) {
|
|
925
1098
|
const children = Array.from(section.children);
|
|
926
1099
|
const blocks = [];
|
|
927
1100
|
const maxFragmentHeight = this.smallestEffectiveContentHeight(dims, sectionIndex);
|
|
@@ -931,9 +1104,16 @@ export class PaginationEngine {
|
|
|
931
1104
|
const container = this.document.createElement("div");
|
|
932
1105
|
container.style.columnCount = String(columnCount);
|
|
933
1106
|
container.style.columnGap = `${columnGapPt}pt`;
|
|
1107
|
+
if (sequential) {
|
|
1108
|
+
container.dataset.paginationSequentialColumns = "true";
|
|
1109
|
+
container.style.columnFill = "auto";
|
|
1110
|
+
container.style.maxHeight = `${maxFragmentHeight}pt`;
|
|
1111
|
+
}
|
|
934
1112
|
for (const child of slice) {
|
|
935
1113
|
container.appendChild(child.cloneNode(true));
|
|
936
1114
|
}
|
|
1115
|
+
if (sequential)
|
|
1116
|
+
this.liftColumnSpaceBefore(container, dims);
|
|
937
1117
|
return container;
|
|
938
1118
|
};
|
|
939
1119
|
let start = 0;
|
|
@@ -948,12 +1128,20 @@ export class PaginationEngine {
|
|
|
948
1128
|
// emitted alone, preserving the established oversized-block fallback.
|
|
949
1129
|
let end = start + 1;
|
|
950
1130
|
let container = makeContainer(children.slice(start, end));
|
|
1131
|
+
if (sequential && this.columnContainerOverflows(container, dims)) {
|
|
1132
|
+
// An indivisible child that exceeds all available columns keeps the established
|
|
1133
|
+
// oversized-block path; a constrained box must not hide it in an extra column.
|
|
1134
|
+
delete container.dataset.paginationSequentialColumns;
|
|
1135
|
+
container.style.columnFill = "balance";
|
|
1136
|
+
container.style.maxHeight = "";
|
|
1137
|
+
}
|
|
951
1138
|
let measured = this.measureElement(container, dims, sectionIndex);
|
|
952
1139
|
while (end < children.length && !isBreak(children[end])) {
|
|
953
1140
|
this.checkpoint();
|
|
954
1141
|
const candidate = makeContainer(children.slice(start, end + 1));
|
|
955
1142
|
const candidateMeasured = this.measureElement(candidate, dims, sectionIndex);
|
|
956
|
-
if (candidateMeasured.heightPt > maxFragmentHeight
|
|
1143
|
+
if (candidateMeasured.heightPt > maxFragmentHeight
|
|
1144
|
+
|| (sequential && this.columnContainerOverflows(candidate, dims)))
|
|
957
1145
|
break;
|
|
958
1146
|
container = candidate;
|
|
959
1147
|
measured = candidateMeasured;
|
|
@@ -975,6 +1163,92 @@ export class PaginationEngine {
|
|
|
975
1163
|
}
|
|
976
1164
|
return blocks;
|
|
977
1165
|
}
|
|
1166
|
+
/** Keep Word's paragraph space-before in the ordinary inter-block spacing budget. */
|
|
1167
|
+
liftColumnSpaceBefore(container, dims) {
|
|
1168
|
+
container.style.marginTop = "";
|
|
1169
|
+
const first = container.firstElementChild;
|
|
1170
|
+
if (!first || !this.isWordParagraphElement(first))
|
|
1171
|
+
return;
|
|
1172
|
+
const host = this.document.createElement("div");
|
|
1173
|
+
host.style.width = `${dims.contentWidth}pt`;
|
|
1174
|
+
host.appendChild(container);
|
|
1175
|
+
this.stagingElement.appendChild(host);
|
|
1176
|
+
try {
|
|
1177
|
+
const spaceBefore = first.dataset.paginationColumnSpaceBefore
|
|
1178
|
+
?? this.view.getComputedStyle(first).marginTop;
|
|
1179
|
+
first.dataset.paginationColumnSpaceBefore = spaceBefore;
|
|
1180
|
+
first.style.marginTop = "0";
|
|
1181
|
+
container.style.marginTop = spaceBefore;
|
|
1182
|
+
}
|
|
1183
|
+
finally {
|
|
1184
|
+
container.remove();
|
|
1185
|
+
host.remove();
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
/** A height-constrained multicol box overflows horizontally when it needs another column. */
|
|
1189
|
+
columnContainerOverflows(element, dims) {
|
|
1190
|
+
const host = this.document.createElement("div");
|
|
1191
|
+
host.style.width = `${dims.contentWidth}pt`;
|
|
1192
|
+
const clone = element.cloneNode(true);
|
|
1193
|
+
host.appendChild(clone);
|
|
1194
|
+
this.stagingElement.appendChild(host);
|
|
1195
|
+
try {
|
|
1196
|
+
if (clone.scrollWidth > clone.clientWidth + 1)
|
|
1197
|
+
return true;
|
|
1198
|
+
// Keep-lines paragraphs must fit as a whole. A constrained column container
|
|
1199
|
+
// can report only its first partial box, even when scrollWidth still fits.
|
|
1200
|
+
// Move that source paragraph to the next page before print clips its text.
|
|
1201
|
+
return Array.from(clone.querySelectorAll('[data-keep-lines="true"]'))
|
|
1202
|
+
.some(paragraph => paragraph.getClientRects().length > 1);
|
|
1203
|
+
}
|
|
1204
|
+
finally {
|
|
1205
|
+
host.remove();
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Reflow a final continuous column section against the actual remaining body band.
|
|
1210
|
+
* A preceding title, running story or note reservation can make it shorter than the
|
|
1211
|
+
* fresh-page band used while collecting source blocks. Split at source-child boundaries
|
|
1212
|
+
* when the narrower band would create an extra horizontal column; the tail is reconsidered
|
|
1213
|
+
* normally on the following page. No source child is duplicated or discarded.
|
|
1214
|
+
*/
|
|
1215
|
+
reflowSequentialColumns(block, dims, availableHeightPt) {
|
|
1216
|
+
if (block.element.dataset.paginationSequentialColumns !== "true"
|
|
1217
|
+
|| availableHeightPt <= 0)
|
|
1218
|
+
return null;
|
|
1219
|
+
const maximumHeight = this.smallestEffectiveContentHeight(dims, block.sectionIndex);
|
|
1220
|
+
const height = Math.min(availableHeightPt, maximumHeight);
|
|
1221
|
+
if (Math.abs(parseFloat(block.element.style.maxHeight) - height) < 0.01)
|
|
1222
|
+
return null;
|
|
1223
|
+
const children = Array.from(block.element.children);
|
|
1224
|
+
const make = (slice, limit) => {
|
|
1225
|
+
const container = block.element.cloneNode(false);
|
|
1226
|
+
container.style.maxHeight = `${limit}pt`;
|
|
1227
|
+
for (const child of slice)
|
|
1228
|
+
container.appendChild(child.cloneNode(true));
|
|
1229
|
+
this.liftColumnSpaceBefore(container, dims);
|
|
1230
|
+
return container;
|
|
1231
|
+
};
|
|
1232
|
+
const whole = make(children, height);
|
|
1233
|
+
if (!this.columnContainerOverflows(whole, dims)) {
|
|
1234
|
+
return [this.measureElement(whole, dims, block.sectionIndex)];
|
|
1235
|
+
}
|
|
1236
|
+
let end = 0;
|
|
1237
|
+
for (let count = 1; count < children.length; count++) {
|
|
1238
|
+
this.checkpoint();
|
|
1239
|
+
const candidate = make(children.slice(0, count), height);
|
|
1240
|
+
if (this.columnContainerOverflows(candidate, dims))
|
|
1241
|
+
break;
|
|
1242
|
+
if (children[count - 1].dataset.keepWithNext !== "true")
|
|
1243
|
+
end = count;
|
|
1244
|
+
}
|
|
1245
|
+
if (end === 0)
|
|
1246
|
+
return null;
|
|
1247
|
+
return [
|
|
1248
|
+
this.measureElement(make(children.slice(0, end), height), dims, block.sectionIndex),
|
|
1249
|
+
this.measureElement(make(children.slice(end), maximumHeight), dims, block.sectionIndex),
|
|
1250
|
+
];
|
|
1251
|
+
}
|
|
978
1252
|
/**
|
|
979
1253
|
* Measures one element in the same hidden staging context used for the source blocks.
|
|
980
1254
|
* This is intentionally DOM-based: table row heights cannot be inferred from individual
|
|
@@ -990,13 +1264,13 @@ export class PaginationEngine {
|
|
|
990
1264
|
measurementHost.appendChild(measuredElement);
|
|
991
1265
|
this.stagingElement.appendChild(measurementHost);
|
|
992
1266
|
const rect = measuredElement.getBoundingClientRect();
|
|
993
|
-
const
|
|
1267
|
+
const margins = collapsedBlockMarginsPx(this.view, measuredElement);
|
|
994
1268
|
const measured = {
|
|
995
1269
|
element,
|
|
996
1270
|
sectionIndex,
|
|
997
1271
|
heightPt: pxToPt(rect.height),
|
|
998
|
-
marginTopPt: pxToPt(
|
|
999
|
-
marginBottomPt: pxToPt(
|
|
1272
|
+
marginTopPt: pxToPt(margins.topPx),
|
|
1273
|
+
marginBottomPt: pxToPt(margins.bottomPx),
|
|
1000
1274
|
keepWithNext: element.dataset.keepWithNext === "true",
|
|
1001
1275
|
keepLines: element.dataset.keepLines === "true",
|
|
1002
1276
|
pageBreakBefore: element.dataset.pageBreakBefore === "true",
|
|
@@ -2393,6 +2667,10 @@ export class PaginationEngine {
|
|
|
2393
2667
|
* Deterministic: it depends only on the section's page setup and the registry's pre-measured
|
|
2394
2668
|
* story heights, never on the page's content, which is what keeps it lazy-loading compatible.
|
|
2395
2669
|
*/
|
|
2670
|
+
/** The body height a physical page may fill: its body band less any caller-requested reduction. */
|
|
2671
|
+
bodyBudgetPt(bodyHeightPt, physicalPageNumber) {
|
|
2672
|
+
return Math.max(0, bodyHeightPt - (this.bodyBudgetReductionsPt[physicalPageNumber] ?? 0));
|
|
2673
|
+
}
|
|
2396
2674
|
getPageBands(dims, sectionIndex, pageInSection, displayedPageNumber) {
|
|
2397
2675
|
const sectionHf = this.hfRegistry.get(sectionIndex);
|
|
2398
2676
|
return resolvePageBands(dims, this.selectStoryHeight(sectionHf?.headerFirstHeight, sectionHf?.headerEvenHeight, sectionHf?.headerDefaultHeight, pageInSection, displayedPageNumber), this.selectStoryHeight(sectionHf?.footerFirstHeight, sectionHf?.footerEvenHeight, sectionHf?.footerDefaultHeight, pageInSection, displayedPageNumber));
|
|
@@ -2469,7 +2747,7 @@ export class PaginationEngine {
|
|
|
2469
2747
|
return currentPhysicalNumber + physicalPage - pageNumber;
|
|
2470
2748
|
};
|
|
2471
2749
|
// Get effective content height for first page (accounts for header/footer sizes)
|
|
2472
|
-
let
|
|
2750
|
+
let effectiveContentHeight = this.bodyBudgetPt(this.getPageBands(dimensionsFor(), pageSectionIndex, pageInSection(), displayedPageNumber()).bodyHeight, pageNumber);
|
|
2473
2751
|
let remainingHeight = effectiveContentHeight;
|
|
2474
2752
|
// Track the previous block's bottom margin for margin collapsing
|
|
2475
2753
|
let prevMarginBottomPt = 0;
|
|
@@ -2501,7 +2779,7 @@ export class PaginationEngine {
|
|
|
2501
2779
|
const adoptEmptyPageOwner = (owner) => {
|
|
2502
2780
|
pageSectionIndex = owner;
|
|
2503
2781
|
const bands = this.getPageBands(dimensionsFor(owner), owner, pageInSection(owner), displayedPageNumber(owner));
|
|
2504
|
-
effectiveContentHeight = bands.bodyHeight;
|
|
2782
|
+
effectiveContentHeight = this.bodyBudgetPt(bands.bodyHeight, pageNumber);
|
|
2505
2783
|
remainingHeight = effectiveContentHeight;
|
|
2506
2784
|
};
|
|
2507
2785
|
const admitCurrentPage = () => {
|
|
@@ -2614,7 +2892,7 @@ export class PaginationEngine {
|
|
|
2614
2892
|
}
|
|
2615
2893
|
// Get effective content height for new page position
|
|
2616
2894
|
const newBands = this.getPageBands(dimensionsFor(), pageSectionIndex, pageInSection(), displayedPageNumber());
|
|
2617
|
-
effectiveContentHeight = newBands.bodyHeight;
|
|
2895
|
+
effectiveContentHeight = this.bodyBudgetPt(newBands.bodyHeight, pageNumber);
|
|
2618
2896
|
remainingHeight = effectiveContentHeight;
|
|
2619
2897
|
prevMarginBottomPt = 0; // Reset margin tracking for new page
|
|
2620
2898
|
currentFootnoteIds = []; // Reset footnotes for new page
|
|
@@ -2770,6 +3048,12 @@ export class PaginationEngine {
|
|
|
2770
3048
|
// Account for margin collapsing: the gap between blocks is max(prevBottom, currTop), not sum
|
|
2771
3049
|
const isFirstOnPage = currentContent.length === 0;
|
|
2772
3050
|
const effectiveMarginTop = this.effectiveBlockMarginTop(block, prevMarginBottomPt, isFirstOnPage, pageInSection(block.sectionIndex));
|
|
3051
|
+
const columnFragments = this.reflowSequentialColumns(block, blockDimensions, remainingHeight - currentFootnoteHeight - additionalFootnoteHeight - effectiveMarginTop);
|
|
3052
|
+
if (columnFragments) {
|
|
3053
|
+
blocks.splice(i, 1, ...columnFragments);
|
|
3054
|
+
i--;
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
2773
3057
|
// Visible height = top margin gap + content + footnote space
|
|
2774
3058
|
// Note: bottom margin is NOT included in the fit check because the last block's
|
|
2775
3059
|
// bottom margin extends beyond the content area and is clipped by overflow:hidden.
|
|
@@ -3014,12 +3298,14 @@ export class PaginationEngine {
|
|
|
3014
3298
|
};
|
|
3015
3299
|
const widthReference = this.horizontalAnchorReference(anchor.getAttribute('data-docx-anchor-width-relative') ?? 'margin', dims, pageNumber, context);
|
|
3016
3300
|
const widthPercent = this.anchorNumber(anchor, 'width-percent');
|
|
3017
|
-
|
|
3301
|
+
// Word supports strictly positive relative dimensions (MS-ODRAWXML 2.18.3.1/2).
|
|
3302
|
+
// Zero is schema-valid but must not collapse a visible object with a stored extent.
|
|
3303
|
+
if (widthPercent !== undefined && widthPercent > 0) {
|
|
3018
3304
|
anchor.style.width = `${widthReference.size * widthPercent / 100}pt`;
|
|
3019
3305
|
}
|
|
3020
3306
|
const heightReference = this.verticalAnchorReference(anchor.getAttribute('data-docx-anchor-height-relative') ?? 'margin', dims, pageNumber, context);
|
|
3021
3307
|
const heightPercent = this.anchorNumber(anchor, 'height-percent');
|
|
3022
|
-
if (heightPercent !== undefined && anchor.dataset.docxAnchorAutofit !== 'true') {
|
|
3308
|
+
if (heightPercent !== undefined && heightPercent > 0 && anchor.dataset.docxAnchorAutofit !== 'true') {
|
|
3023
3309
|
anchor.style.height = `${heightReference.size * heightPercent / 100}pt`;
|
|
3024
3310
|
}
|
|
3025
3311
|
// Read the final border-box size after relative sizing and before reparenting. The stored
|
|
@@ -3031,7 +3317,17 @@ export class PaginationEngine {
|
|
|
3031
3317
|
const vertical = this.verticalAnchorReference(anchor.getAttribute('data-docx-anchor-v-relative') ?? 'paragraph', dims, pageNumber, context);
|
|
3032
3318
|
const left = this.resolveAnchorAxis(horizontal, width, anchor.getAttribute('data-docx-anchor-h-align'), this.anchorNumber(anchor, 'h-offset'), pageNumber);
|
|
3033
3319
|
const top = this.resolveAnchorAxis(vertical, height, anchor.getAttribute('data-docx-anchor-v-align'), this.anchorNumber(anchor, 'v-offset'), pageNumber);
|
|
3320
|
+
// Leaving the paragraph must not orphan it: a paragraph whose only content was this drawing
|
|
3321
|
+
// renders as the drawing, and measureRenderedSource finds it through this link (issue #849).
|
|
3322
|
+
const hostElement = anchor.parentElement?.closest('[data-source-anchor-id]');
|
|
3323
|
+
const hostAnchorId = hostElement?.dataset.sourceAnchorId;
|
|
3324
|
+
if (hostAnchorId)
|
|
3325
|
+
anchor.setAttribute(DRAWING_HOST_ATTRIBUTE, hostAnchorId);
|
|
3034
3326
|
pageBox.appendChild(anchor);
|
|
3327
|
+
// A host left with nothing but its paragraph-mark line (issue #880) renders as that line plus
|
|
3328
|
+
// the drawing; one with text of its own keeps measuring as its own box.
|
|
3329
|
+
if (hostElement && rendersOnlyItsLine(hostElement))
|
|
3330
|
+
hostElement.setAttribute(HOSTS_PROMOTED_DRAWING_ATTRIBUTE, 'true');
|
|
3035
3331
|
anchor.style.position = 'absolute';
|
|
3036
3332
|
anchor.style.left = `${left}pt`;
|
|
3037
3333
|
anchor.style.top = `${top}pt`;
|
|
@@ -3325,6 +3621,13 @@ export class PaginationEngine {
|
|
|
3325
3621
|
contentArea.style.height = `${Math.max(0, contentAreaHeight - notesHeightPt)}pt`;
|
|
3326
3622
|
}
|
|
3327
3623
|
}
|
|
3624
|
+
// Paint offsets must follow range fragmentation: positioned runs are deliberately excluded
|
|
3625
|
+
// from that structural algorithm, even though these offsets leave every line box unchanged.
|
|
3626
|
+
alignExactLineBaselines(contentArea);
|
|
3627
|
+
const paper = pageBox.getBoundingClientRect();
|
|
3628
|
+
const header = pageBox.querySelector(`.${this.cssPrefix}header`);
|
|
3629
|
+
const footer = pageBox.querySelector(`.${this.cssPrefix}footer`);
|
|
3630
|
+
preserveExactLineInk(contentArea, Math.max(paper.top, header?.getBoundingClientRect().bottom ?? paper.top), Math.min(paper.bottom, footer?.getBoundingClientRect().top ?? paper.bottom, notesEl?.getBoundingClientRect().top ?? paper.bottom), paper);
|
|
3328
3631
|
return {
|
|
3329
3632
|
pageNumber,
|
|
3330
3633
|
sectionIndex,
|