@stll/folio-core 0.4.0 → 0.5.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/ai-edits/apply.d.ts +3 -9
- package/dist/ai-edits/apply.js +94 -14
- package/dist/ai-edits/blockRange.d.ts +33 -0
- package/dist/ai-edits/blockRange.js +58 -0
- package/dist/ai-edits/headless.d.ts +33 -4
- package/dist/ai-edits/headless.js +83 -15
- package/dist/ai-edits/index.d.ts +5 -3
- package/dist/ai-edits/index.js +3 -2
- package/dist/ai-edits/snapshot.d.ts +14 -2
- package/dist/ai-edits/snapshot.js +12 -1
- package/dist/ai-edits/types.d.ts +42 -3
- package/dist/compat/eigenpal.d.ts +3 -2
- package/dist/compat/eigenpal.js +6 -5
- package/dist/controller/layoutPipeline.d.ts +1 -0
- package/dist/controller/layoutPipeline.js +35 -137
- package/dist/controller/layoutScheduler.d.ts +2 -1
- package/dist/controller/layoutScheduler.js +6 -0
- package/dist/document-operations.d.ts +122 -0
- package/dist/document-operations.js +575 -0
- package/dist/docx/blockContentParser.js +17 -7
- package/dist/docx/encryption/agileDecryption.js +2 -1
- package/dist/docx/encryption/compoundFile.js +4 -2
- package/dist/docx/footnoteParser.d.ts +1 -17
- package/dist/docx/footnoteParser.js +1 -27
- package/dist/docx/groupDrawingParser.d.ts +8 -0
- package/dist/docx/groupDrawingParser.js +131 -0
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +8 -2
- package/dist/docx/paragraphParser.js +9 -3
- package/dist/docx/runParser.js +8 -1
- package/dist/docx/selectiveXmlPatch.js +4 -10
- package/dist/docx/serializer/numberingSerializer.js +3 -1
- package/dist/docx/settingsParser.d.ts +6 -3
- package/dist/docx/settingsParser.js +2 -0
- package/dist/docx/xmlParser.js +11 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -5
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +37 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +138 -14
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -7
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -7
- package/dist/layout-bridge/convert/toFlowBlocks.js +56 -19
- package/dist/layout-engine/index.d.ts +6 -5
- package/dist/layout-engine/index.js +89 -26
- package/dist/layout-engine/keep-together.d.ts +13 -8
- package/dist/layout-engine/keep-together.js +40 -25
- package/dist/layout-engine/layoutInstrumentation.d.ts +11 -1
- package/dist/layout-engine/layoutInstrumentation.js +10 -1
- package/dist/layout-engine/measure/measureBlocks.js +28 -18
- package/dist/layout-engine/measure/measureParagraph.js +61 -15
- package/dist/layout-engine/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +9 -3
- package/dist/layout-engine/textBoxGroup.d.ts +7 -0
- package/dist/layout-engine/textBoxGroup.js +8 -0
- package/dist/layout-engine/types.d.ts +30 -9
- package/dist/layout-engine/types.js +20 -2
- package/dist/layout-painter/renderPage.d.ts +15 -1
- package/dist/layout-painter/renderPage.js +27 -11
- package/dist/layout-painter/renderParagraph.js +18 -8
- package/dist/layout-painter/renderTable.js +10 -7
- package/dist/managers/DocumentLoaderManager.js +12 -5
- package/dist/managers/TableSelectionManager.js +1 -1
- package/dist/model.js +1 -1
- package/dist/paged-layout/pageText.d.ts +14 -0
- package/dist/paged-layout/pageText.js +21 -0
- package/dist/prosemirror/attrs/index.js +2 -0
- package/dist/prosemirror/commands/comments.js +75 -5
- package/dist/prosemirror/commands/hyperlink.js +4 -2
- package/dist/prosemirror/commands/image.js +2 -1
- package/dist/prosemirror/commands/index.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +68 -0
- package/dist/prosemirror/commands/propertyChangeScope.js +242 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +14 -3
- package/dist/prosemirror/conversion/toProseDoc.js +29 -8
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +6 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +10 -11
- package/dist/prosemirror/findReplaceSelection.js +2 -1
- package/dist/prosemirror/index.js +1 -1
- package/dist/prosemirror/insertOperations.js +1 -1
- package/dist/prosemirror/schema/nodes.d.ts +10 -5
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/utils/tabCalculator.js +4 -2
- package/dist/redline.d.ts +20 -0
- package/dist/redline.js +127 -0
- package/dist/server.d.ts +7 -3
- package/dist/server.js +6 -2
- package/dist/symbols.d.ts +35 -0
- package/dist/symbols.js +512 -0
- package/dist/utils/clipboard.js +3 -3
- package/dist/utils/fontLoader.js +3 -0
- package/dist/utils/fontResolver.js +48 -1
- package/dist/utils/zoom.d.ts +21 -0
- package/dist/utils/zoom.js +21 -0
- package/dist/version-comparison.d.ts +92 -0
- package/dist/version-comparison.js +436 -0
- package/package.json +3 -3
- package/dist/paged-layout/headerFooterMargins.d.ts +0 -75
- package/dist/paged-layout/headerFooterMargins.js +0 -122
|
@@ -22,8 +22,10 @@ const DEFAULT_FONT_FAMILY = "Calibri";
|
|
|
22
22
|
const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
|
|
23
23
|
const WIDTH_TOLERANCE = .5;
|
|
24
24
|
const JUSTIFY_SHRINK_TOLERANCE_RATIO = .016;
|
|
25
|
+
const JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO = .017;
|
|
25
26
|
const JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO = .025;
|
|
26
27
|
const JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO = .021;
|
|
28
|
+
const DEFAULT_LIST_HANGING_INDENT_PX = 24;
|
|
27
29
|
const ALL_CAPS_RATIO_THRESHOLD = .8;
|
|
28
30
|
/**
|
|
29
31
|
* Find the longest prefix of `text` that fits within `maxWidth` pixels.
|
|
@@ -270,8 +272,16 @@ function hasFollowingTabOnLine(runs, tabIndex) {
|
|
|
270
272
|
}
|
|
271
273
|
return false;
|
|
272
274
|
}
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
+
function hasPriorTabOnLine(runs, tabIndex) {
|
|
276
|
+
for (let i = tabIndex - 1; i >= 0; i--) {
|
|
277
|
+
const prior = runs[i];
|
|
278
|
+
if (!prior || isLineBreakRun(prior) || isImageRun(prior) && isBlockLayoutImageRun(prior)) break;
|
|
279
|
+
if (isTabRun(prior)) return true;
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
function canClampTabToRightEdge(alignment, currentLineWidth, hasPriorTab, followingWidth, availableWidth) {
|
|
284
|
+
if (alignment === "start" || alignment === "default") return currentLineWidth > WIDTH_TOLERANCE && (hasPriorTab || followingWidth <= availableWidth);
|
|
275
285
|
return true;
|
|
276
286
|
}
|
|
277
287
|
/**
|
|
@@ -319,10 +329,18 @@ function uppercaseLetterRatio(text) {
|
|
|
319
329
|
}
|
|
320
330
|
return letters === 0 ? 0 : uppercase / letters;
|
|
321
331
|
}
|
|
322
|
-
function justifyShrinkToleranceRatio(block) {
|
|
332
|
+
function justifyShrinkToleranceRatio(block, isFirstLine, regularSpaceCount, nonBreakingSpaceCount) {
|
|
333
|
+
if (block.attrs?.listMarker !== void 0) {
|
|
334
|
+
if ((block.attrs.indent?.hanging ?? 0) <= DEFAULT_LIST_HANGING_INDENT_PX) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
335
|
+
if (isFirstLine) return JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO;
|
|
336
|
+
const totalSpaces = regularSpaceCount + nonBreakingSpaceCount;
|
|
337
|
+
if (totalSpaces === 0) return JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO;
|
|
338
|
+
return Math.max(JUSTIFY_SHRINK_TOLERANCE_RATIO, JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO * (regularSpaceCount / totalSpaces));
|
|
339
|
+
}
|
|
323
340
|
const hasTabStops = (block.attrs?.tabs?.length ?? 0) > 0;
|
|
324
341
|
const hasTabRuns = block.runs.some(isTabRun);
|
|
325
|
-
if (
|
|
342
|
+
if (!isFirstLine && hasTabRuns && !hasTabStops) return JUSTIFY_LITERAL_TAB_CONTINUATION_SHRINK_TOLERANCE_RATIO;
|
|
343
|
+
if ((isFirstLine || hasTabStops) && (hasTabStops || hasTabRuns)) return (block.attrs?.indent?.firstLine ?? 0) === 0 ? JUSTIFY_HANGING_TAB_SHRINK_TOLERANCE_RATIO : JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
326
344
|
if (uppercaseLetterRatio(block.runs.map((run) => isTextRun(run) ? run.text ?? "" : "").join("")) > ALL_CAPS_RATIO_THRESHOLD) return JUSTIFY_SHRINK_TOLERANCE_RATIO;
|
|
327
345
|
return JUSTIFY_PROSE_SHRINK_TOLERANCE_RATIO;
|
|
328
346
|
}
|
|
@@ -412,7 +430,6 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
412
430
|
const attrs = block.attrs;
|
|
413
431
|
const spacing = attrs?.spacing;
|
|
414
432
|
const isJustifiedParagraph = attrs?.alignment === "justify";
|
|
415
|
-
const justifyToleranceRatio = justifyShrinkToleranceRatio(block);
|
|
416
433
|
const floatingZones = options?.floatingZones;
|
|
417
434
|
const paragraphYOffset = options?.paragraphYOffset ?? 0;
|
|
418
435
|
const indent = attrs?.indent;
|
|
@@ -506,6 +523,9 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
506
523
|
toRun: 0,
|
|
507
524
|
toChar: 0,
|
|
508
525
|
width: 0,
|
|
526
|
+
trailingWhitespaceWidth: 0,
|
|
527
|
+
regularSpaceCount: 0,
|
|
528
|
+
nonBreakingSpaceCount: 0,
|
|
509
529
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
510
530
|
maxFontMetrics: null,
|
|
511
531
|
maxImageHeightPx: 0,
|
|
@@ -552,7 +572,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
552
572
|
fromChar: currentLine.fromChar,
|
|
553
573
|
toRun: currentLine.toRun,
|
|
554
574
|
toChar: currentLine.toChar,
|
|
555
|
-
width: currentLine.width,
|
|
575
|
+
width: Math.max(0, currentLine.width - currentLine.trailingWhitespaceWidth),
|
|
556
576
|
...finalTypography
|
|
557
577
|
};
|
|
558
578
|
if (currentLine.leftOffset > 0) line.leftOffset = currentLine.leftOffset;
|
|
@@ -572,17 +592,21 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
572
592
|
const estimatedLineHeight = ptToPx(DEFAULT_FONT_SIZE) * DEFAULT_LINE_HEIGHT_MULTIPLIER;
|
|
573
593
|
skipObstructingFloats(estimatedLineHeight, bodyContentWidth);
|
|
574
594
|
const floatingMargins = getFloatingMargins(cumulativeHeight, estimatedLineHeight, floatingZones, paragraphYOffset);
|
|
595
|
+
const adjustedWidth = Math.max(1, getFloatingAvailableWidth(floatingMargins, bodyContentWidth));
|
|
575
596
|
currentLine = {
|
|
576
597
|
fromRun: runIndex,
|
|
577
598
|
fromChar: charIndex,
|
|
578
599
|
toRun: runIndex,
|
|
579
600
|
toChar: charIndex,
|
|
580
601
|
width: 0,
|
|
602
|
+
trailingWhitespaceWidth: 0,
|
|
603
|
+
regularSpaceCount: 0,
|
|
604
|
+
nonBreakingSpaceCount: 0,
|
|
581
605
|
maxFontSize: DEFAULT_FONT_SIZE,
|
|
582
606
|
maxFontMetrics: null,
|
|
583
607
|
maxImageHeightPx: 0,
|
|
584
608
|
maxMathHeightPx: 0,
|
|
585
|
-
availableWidth:
|
|
609
|
+
availableWidth: adjustedWidth,
|
|
586
610
|
leftOffset: floatingMargins.leftMargin,
|
|
587
611
|
rightOffset: floatingMargins.rightMargin,
|
|
588
612
|
...floatingMargins.segments?.length ? { segmentZones: floatingMargins.segments } : {}
|
|
@@ -620,6 +644,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
620
644
|
const contentX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + lineX;
|
|
621
645
|
const tabResult = calculateTabWidth(contentX, {
|
|
622
646
|
...attrs?.tabs !== void 0 ? { explicitStops: attrs.tabs } : {},
|
|
647
|
+
...attrs?.defaultTabStopTwips !== void 0 ? { defaultTabInterval: attrs.defaultTabStopTwips } : {},
|
|
623
648
|
leftIndent: pixelsToTwips(indentLeft)
|
|
624
649
|
}, {
|
|
625
650
|
followingWidth,
|
|
@@ -627,12 +652,13 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
627
652
|
});
|
|
628
653
|
let tabWidth = tabResult.width;
|
|
629
654
|
const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
|
|
630
|
-
if (!hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
655
|
+
if (!hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
|
|
631
656
|
if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
|
|
632
657
|
startNewLine(runIndex, 0);
|
|
633
658
|
updateMaxFont(style);
|
|
634
659
|
}
|
|
635
660
|
currentLine.width += tabWidth;
|
|
661
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
636
662
|
currentLine.toRun = runIndex;
|
|
637
663
|
currentLine.toChar = 1;
|
|
638
664
|
continue;
|
|
@@ -662,6 +688,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
662
688
|
const imageFootprintPx = imageHeight + (run.distTop ?? 0) + (run.distBottom ?? 0);
|
|
663
689
|
if (imageFootprintPx > currentLine.maxImageHeightPx) currentLine.maxImageHeightPx = imageFootprintPx;
|
|
664
690
|
currentLine.width += imageWidth;
|
|
691
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
665
692
|
currentLine.toRun = runIndex;
|
|
666
693
|
currentLine.toChar = 1;
|
|
667
694
|
continue;
|
|
@@ -676,6 +703,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
676
703
|
updateMaxFont(style);
|
|
677
704
|
}
|
|
678
705
|
currentLine.width += fieldWidth;
|
|
706
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
679
707
|
currentLine.toRun = runIndex;
|
|
680
708
|
currentLine.toChar = 1;
|
|
681
709
|
continue;
|
|
@@ -696,6 +724,7 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
696
724
|
}
|
|
697
725
|
if (mathFootprintPx > currentLine.maxMathHeightPx) currentLine.maxMathHeightPx = mathFootprintPx;
|
|
698
726
|
currentLine.width += mathWidth;
|
|
727
|
+
currentLine.trailingWhitespaceWidth = 0;
|
|
699
728
|
currentLine.toRun = runIndex;
|
|
700
729
|
currentLine.toChar = 1;
|
|
701
730
|
continue;
|
|
@@ -720,13 +749,17 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
720
749
|
break;
|
|
721
750
|
}
|
|
722
751
|
const word = text.slice(charIndex, nextBreak);
|
|
723
|
-
const
|
|
724
|
-
const
|
|
752
|
+
const measuredWord = trimTrailingSpacesAndTabs(word);
|
|
753
|
+
const wordWidth = measureTextWidth(measuredWord, style);
|
|
754
|
+
const fullWordWidth = measureTextWidth(word, style);
|
|
755
|
+
const regularSpaces = measuredWord.split(" ").length - 1;
|
|
756
|
+
const nonBreakingSpaces = measuredWord.split("\xA0").length - 1;
|
|
757
|
+
const widthTolerance = isJustifiedParagraph ? Math.max(WIDTH_TOLERANCE, currentLine.availableWidth * justifyShrinkToleranceRatio(block, lines.length === 0, currentLine.regularSpaceCount + regularSpaces, currentLine.nonBreakingSpaceCount + nonBreakingSpaces)) : WIDTH_TOLERANCE;
|
|
725
758
|
if (wordWidth > currentLine.availableWidth + widthTolerance) {
|
|
726
759
|
let chunkStart = 0;
|
|
727
|
-
while (chunkStart <
|
|
760
|
+
while (chunkStart < measuredWord.length) {
|
|
728
761
|
const spaceLeft = currentLine.availableWidth - currentLine.width + WIDTH_TOLERANCE;
|
|
729
|
-
let bestEnd = findMaxFittingLength(
|
|
762
|
+
let bestEnd = findMaxFittingLength(measuredWord.slice(chunkStart), style, spaceLeft);
|
|
730
763
|
if (bestEnd === 0) {
|
|
731
764
|
if (currentLine.width > 0) {
|
|
732
765
|
startNewLine(runIndex, charIndex + chunkStart);
|
|
@@ -736,16 +769,26 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
736
769
|
bestEnd = 1;
|
|
737
770
|
}
|
|
738
771
|
const chunkEnd = chunkStart + bestEnd;
|
|
739
|
-
const
|
|
772
|
+
const chunk = measuredWord.slice(chunkStart, chunkEnd);
|
|
773
|
+
const chunkWidth = measureTextWidth(chunk, style);
|
|
740
774
|
currentLine.width += chunkWidth;
|
|
775
|
+
currentLine.trailingWhitespaceWidth = chunkWidth - measureTextWidth(trimTrailingSpacesAndTabs(chunk), style);
|
|
776
|
+
currentLine.regularSpaceCount += chunk.split(" ").length - 1;
|
|
777
|
+
currentLine.nonBreakingSpaceCount += chunk.split("\xA0").length - 1;
|
|
741
778
|
currentLine.toRun = runIndex;
|
|
742
779
|
currentLine.toChar = charIndex + chunkEnd;
|
|
743
780
|
chunkStart = chunkEnd;
|
|
744
|
-
if (chunkStart <
|
|
781
|
+
if (chunkStart < measuredWord.length) {
|
|
745
782
|
startNewLine(runIndex, charIndex + chunkStart);
|
|
746
783
|
updateMaxFont(lineHeightStyle);
|
|
747
784
|
}
|
|
748
785
|
}
|
|
786
|
+
const trailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
787
|
+
currentLine.width += trailingWhitespaceWidth;
|
|
788
|
+
currentLine.trailingWhitespaceWidth = trailingWhitespaceWidth;
|
|
789
|
+
currentLine.regularSpaceCount += word.split(" ").length - 1;
|
|
790
|
+
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
791
|
+
currentLine.toChar = nextBreak;
|
|
749
792
|
charIndex = nextBreak;
|
|
750
793
|
continue;
|
|
751
794
|
}
|
|
@@ -755,7 +798,10 @@ function measureParagraph(block, maxWidth, options) {
|
|
|
755
798
|
startNewLine(runIndex, charIndex);
|
|
756
799
|
updateMaxFont(lineHeightStyle);
|
|
757
800
|
}
|
|
758
|
-
currentLine.width +=
|
|
801
|
+
currentLine.width += fullWordWidth;
|
|
802
|
+
currentLine.trailingWhitespaceWidth = fullWordWidth - wordWidth;
|
|
803
|
+
currentLine.regularSpaceCount += word.split(" ").length - 1;
|
|
804
|
+
currentLine.nonBreakingSpaceCount += word.split("\xA0").length - 1;
|
|
759
805
|
currentLine.toRun = runIndex;
|
|
760
806
|
currentLine.toChar = nextBreak;
|
|
761
807
|
charIndex = nextBreak;
|
|
@@ -27,7 +27,8 @@ type PaginatorOptions = {
|
|
|
27
27
|
w: number;
|
|
28
28
|
h: number;
|
|
29
29
|
}; /** Page margins. */
|
|
30
|
-
margins: PageMargins;
|
|
30
|
+
margins: PageMargins; /** Swap left/right margins on even physical pages. */
|
|
31
|
+
mirrorMargins?: boolean;
|
|
31
32
|
/**
|
|
32
33
|
* Margins applied only to the very first page (page 1) of the paginator.
|
|
33
34
|
* Used when a `<w:titlePg/>`-enabled section needs different margins for
|
|
@@ -46,7 +46,7 @@ function createPaginator(options) {
|
|
|
46
46
|
}
|
|
47
47
|
function currentPageUsesActiveLayout(state) {
|
|
48
48
|
if (pendingPageSize || pendingMargins) return false;
|
|
49
|
-
return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins,
|
|
49
|
+
return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number));
|
|
50
50
|
}
|
|
51
51
|
if (getContentHeight() <= 0) panic("Paginator: page size and margins yield no content area");
|
|
52
52
|
let columnWidth = calculateColumnWidth(pageSize.w, margins.left, margins.right, columns);
|
|
@@ -62,14 +62,20 @@ function createPaginator(options) {
|
|
|
62
62
|
recalculateColumnWidth();
|
|
63
63
|
}
|
|
64
64
|
function getPageMargins(pageNumber) {
|
|
65
|
-
|
|
65
|
+
const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...margins };
|
|
66
|
+
if (options.mirrorMargins === true && pageNumber % 2 === 0) return {
|
|
67
|
+
...pageMargins,
|
|
68
|
+
left: pageMargins.right,
|
|
69
|
+
right: pageMargins.left
|
|
70
|
+
};
|
|
71
|
+
return pageMargins;
|
|
66
72
|
}
|
|
67
73
|
let columnRegionTop = margins.top;
|
|
68
74
|
/**
|
|
69
75
|
* Get X position for a given column index.
|
|
70
76
|
*/
|
|
71
77
|
function getColumnX(columnIndex) {
|
|
72
|
-
return margins.left + columnIndex * (columnWidth + columns.gap);
|
|
78
|
+
return (states.at(-1)?.page.margins.left ?? getPageMargins(1).left) + columnIndex * (columnWidth + columns.gap);
|
|
73
79
|
}
|
|
74
80
|
/**
|
|
75
81
|
* Create a new page and add it to the list.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TextBoxBlock } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/layout-engine/textBoxGroup.d.ts
|
|
4
|
+
declare const setTextBoxGroupId: (textBox: TextBoxBlock, groupId: string) => void;
|
|
5
|
+
declare const getTextBoxGroupId: (textBox: TextBoxBlock) => string | undefined;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { getTextBoxGroupId, setTextBoxGroupId };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/layout-engine/textBoxGroup.ts
|
|
2
|
+
const textBoxGroupIds = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
const setTextBoxGroupId = (textBox, groupId) => {
|
|
4
|
+
textBoxGroupIds.set(textBox, groupId);
|
|
5
|
+
};
|
|
6
|
+
const getTextBoxGroupId = (textBox) => textBoxGroupIds.get(textBox);
|
|
7
|
+
//#endregion
|
|
8
|
+
export { getTextBoxGroupId, setTextBoxGroupId };
|
|
@@ -3,7 +3,7 @@ import { ImagePosition, ImageWrap, SdtProperties, SdtType, TableWidthType } from
|
|
|
3
3
|
|
|
4
4
|
//#region src/layout-engine/types.d.ts
|
|
5
5
|
declare namespace types_d_exports {
|
|
6
|
-
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun };
|
|
6
|
+
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned };
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Unique identifier for a block in the document.
|
|
@@ -304,7 +304,8 @@ type ParagraphAttrs = {
|
|
|
304
304
|
keepNext?: boolean;
|
|
305
305
|
keepLines?: boolean;
|
|
306
306
|
widowControl?: boolean;
|
|
307
|
-
pageBreakBefore?: boolean;
|
|
307
|
+
pageBreakBefore?: boolean; /** Word's cached pagination hint (`w:lastRenderedPageBreak`). */
|
|
308
|
+
renderedPageBreakBefore?: boolean;
|
|
308
309
|
styleId?: string;
|
|
309
310
|
contextualSpacing?: boolean;
|
|
310
311
|
/**
|
|
@@ -459,7 +460,14 @@ type TableBlock = {
|
|
|
459
460
|
rows: TableRow[];
|
|
460
461
|
columnWidths?: number[]; /** Table width value (twips for dxa, 50ths of percent for pct). */
|
|
461
462
|
width?: number; /** Table width type ('auto', 'pct', 'dxa', 'nil'). */
|
|
462
|
-
widthType?: TableWidthType;
|
|
463
|
+
widthType?: TableWidthType;
|
|
464
|
+
/**
|
|
465
|
+
* Table layout algorithm (`w:tblLayout`). "fixed" pins columns to their grid
|
|
466
|
+
* widths; "autofit" (the default when absent) lets Word size columns to
|
|
467
|
+
* content. Consulted alongside `widthType` to decide whether `w:noWrap` cells
|
|
468
|
+
* can be honored (see `tableColumnsArePinned`).
|
|
469
|
+
*/
|
|
470
|
+
layout?: "fixed" | "autofit"; /** Table horizontal alignment */
|
|
463
471
|
justification?: "left" | "center" | "right"; /** Table indent from left margin (in pixels, from w:tblInd) */
|
|
464
472
|
indent?: number; /** Right-to-left column order (w:bidiVisual): logical column 0 paints on the right. */
|
|
465
473
|
bidi?: boolean; /** Floating table properties (pixel values). */
|
|
@@ -903,7 +911,8 @@ type LayoutOptions = {
|
|
|
903
911
|
headerContentHeights?: HeaderFooterContentHeights; /** Footer content heights by variant. */
|
|
904
912
|
footerContentHeights?: HeaderFooterContentHeights; /** Whether section has different first page header/footer. */
|
|
905
913
|
titlePage?: boolean; /** Whether section has different even/odd headers/footers. */
|
|
906
|
-
evenAndOddHeaders?: boolean; /**
|
|
914
|
+
evenAndOddHeaders?: boolean; /** Swap left/right margins on even physical pages. */
|
|
915
|
+
mirrorMargins?: boolean; /** Per-page footnote reserved heights (pageNumber → height in pixels). */
|
|
907
916
|
footnoteReservedHeights?: Map<number, number>;
|
|
908
917
|
/**
|
|
909
918
|
* Footnote content heights keyed by internal footnote id (the OOXML
|
|
@@ -970,10 +979,8 @@ type HeaderFooterContent = {
|
|
|
970
979
|
visualTop?: number; /** Bottom-most visual extent relative to the nominal flow origin. */
|
|
971
980
|
visualBottom?: number;
|
|
972
981
|
/**
|
|
973
|
-
* Flow-only bounds
|
|
974
|
-
*
|
|
975
|
-
* the renderer paints behind body content; including them would reserve
|
|
976
|
-
* a full-page letterhead as body push-down.
|
|
982
|
+
* Flow-only bounds for consumers that need to distinguish normal
|
|
983
|
+
* header/footer flow from floating or `behindDoc` artwork.
|
|
977
984
|
*/
|
|
978
985
|
marginPushTop?: number;
|
|
979
986
|
marginPushBottom?: number;
|
|
@@ -1031,5 +1038,19 @@ declare function floatingTextBoxWrapsText(block: TextBoxFlowAttrs): boolean;
|
|
|
1031
1038
|
* Ported from eigenpal #694.
|
|
1032
1039
|
*/
|
|
1033
1040
|
declare function floatingTextBoxReservesBand(block: TextBoxFlowAttrs): boolean;
|
|
1041
|
+
/**
|
|
1042
|
+
* A table's columns are pinned when the layout engine cannot widen a column to
|
|
1043
|
+
* fit content that overflows it: either the layout is explicitly fixed
|
|
1044
|
+
* (`w:tblLayout w:type="fixed"`) or an explicit total width (`w:tblW` `dxa`/
|
|
1045
|
+
* `pct`) is fully consumed by the grid.
|
|
1046
|
+
*
|
|
1047
|
+
* In OOXML, `w:noWrap` (§17.4.30) asks Word not to soft-wrap a cell, but Word
|
|
1048
|
+
* can only honor it by widening the column. When the columns are pinned it
|
|
1049
|
+
* cannot, so overflowing content wraps despite `w:noWrap`. An auto-width table
|
|
1050
|
+
* (`w:tblW` `auto`/`nil`/absent with autofit layout) lets Word widen the column
|
|
1051
|
+
* instead, so there `w:noWrap` genuinely keeps content on one line. Measurement
|
|
1052
|
+
* and the painter both consult this so their line count and `white-space` agree.
|
|
1053
|
+
*/
|
|
1054
|
+
declare function tableColumnsArePinned(table: TableBlock): boolean;
|
|
1034
1055
|
//#endregion
|
|
1035
|
-
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, types_d_exports };
|
|
1056
|
+
export { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_d_exports };
|
|
@@ -11,7 +11,8 @@ var types_exports = /* @__PURE__ */ __exportAll({
|
|
|
11
11
|
floatingTextBoxWrapsText: () => floatingTextBoxWrapsText,
|
|
12
12
|
isFloatingImageRun: () => isFloatingImageRun,
|
|
13
13
|
isFloatingTextBoxBlock: () => isFloatingTextBoxBlock,
|
|
14
|
-
isTextWrappingFloatingImageRun: () => isTextWrappingFloatingImageRun
|
|
14
|
+
isTextWrappingFloatingImageRun: () => isTextWrappingFloatingImageRun,
|
|
15
|
+
tableColumnsArePinned: () => tableColumnsArePinned
|
|
15
16
|
});
|
|
16
17
|
/** Default internal margins for text boxes (OOXML defaults in pixels) */
|
|
17
18
|
const DEFAULT_TEXTBOX_MARGINS = {
|
|
@@ -96,5 +97,22 @@ function floatingTextBoxWrapsText(block) {
|
|
|
96
97
|
function floatingTextBoxReservesBand(block) {
|
|
97
98
|
return isFloatingTextBoxBlock(block) && block.wrapType === "topAndBottom";
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* A table's columns are pinned when the layout engine cannot widen a column to
|
|
102
|
+
* fit content that overflows it: either the layout is explicitly fixed
|
|
103
|
+
* (`w:tblLayout w:type="fixed"`) or an explicit total width (`w:tblW` `dxa`/
|
|
104
|
+
* `pct`) is fully consumed by the grid.
|
|
105
|
+
*
|
|
106
|
+
* In OOXML, `w:noWrap` (§17.4.30) asks Word not to soft-wrap a cell, but Word
|
|
107
|
+
* can only honor it by widening the column. When the columns are pinned it
|
|
108
|
+
* cannot, so overflowing content wraps despite `w:noWrap`. An auto-width table
|
|
109
|
+
* (`w:tblW` `auto`/`nil`/absent with autofit layout) lets Word widen the column
|
|
110
|
+
* instead, so there `w:noWrap` genuinely keeps content on one line. Measurement
|
|
111
|
+
* and the painter both consult this so their line count and `white-space` agree.
|
|
112
|
+
*/
|
|
113
|
+
function tableColumnsArePinned(table) {
|
|
114
|
+
if (table.layout === "fixed") return true;
|
|
115
|
+
return table.widthType === "dxa" || table.widthType === "pct";
|
|
116
|
+
}
|
|
99
117
|
//#endregion
|
|
100
|
-
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, types_exports };
|
|
118
|
+
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_exports };
|
|
@@ -42,6 +42,20 @@ type PageFloatingImage = {
|
|
|
42
42
|
*/
|
|
43
43
|
behindDoc: boolean;
|
|
44
44
|
};
|
|
45
|
+
type FloatingTableBandOptions = {
|
|
46
|
+
contentX: number;
|
|
47
|
+
tableWidth: number;
|
|
48
|
+
contentWidth: number;
|
|
49
|
+
distLeft: number;
|
|
50
|
+
distRight: number;
|
|
51
|
+
};
|
|
52
|
+
declare const floatingTableReservesBand: ({
|
|
53
|
+
contentX,
|
|
54
|
+
tableWidth,
|
|
55
|
+
contentWidth,
|
|
56
|
+
distLeft,
|
|
57
|
+
distRight
|
|
58
|
+
}: FloatingTableBandOptions) => boolean;
|
|
45
59
|
/**
|
|
46
60
|
* CSS class names for page elements
|
|
47
61
|
*/
|
|
@@ -276,4 +290,4 @@ declare function findPageShellForPmPos(container: HTMLElement, pmPos: number): {
|
|
|
276
290
|
isExact: boolean;
|
|
277
291
|
} | null;
|
|
278
292
|
//#endregion
|
|
279
|
-
export { AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
|
|
293
|
+
export { AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
|
|
@@ -19,6 +19,11 @@ import { panic } from "better-result";
|
|
|
19
19
|
* Renders a single page from Layout data to DOM elements.
|
|
20
20
|
* Each page contains positioned fragments within a content area.
|
|
21
21
|
*/
|
|
22
|
+
const floatingTableReservesBand = ({ contentX, tableWidth, contentWidth, distLeft, distRight }) => {
|
|
23
|
+
const leftClearance = Math.max(0, contentX - distLeft);
|
|
24
|
+
const rightClearance = Math.max(0, contentWidth - contentX - tableWidth - distRight);
|
|
25
|
+
return Math.max(leftClearance, rightClearance) < 24;
|
|
26
|
+
};
|
|
22
27
|
/**
|
|
23
28
|
* CSS class names for page elements
|
|
24
29
|
*/
|
|
@@ -874,7 +879,8 @@ function renderPage(page, context, options = {}) {
|
|
|
874
879
|
...img.wrapType !== void 0 ? { wrapType: img.wrapType } : {}
|
|
875
880
|
});
|
|
876
881
|
}
|
|
877
|
-
if (options.blockLookup) for (
|
|
882
|
+
if (options.blockLookup) for (let fragmentIndex = 0; fragmentIndex < page.fragments.length; fragmentIndex++) {
|
|
883
|
+
const fragment = page.fragments[fragmentIndex];
|
|
878
884
|
if (fragment.kind !== "table") continue;
|
|
879
885
|
const blockData = options.blockLookup.get(String(fragment.blockId));
|
|
880
886
|
if (blockData?.block.kind !== "table") continue;
|
|
@@ -886,9 +892,8 @@ function renderPage(page, context, options = {}) {
|
|
|
886
892
|
const distBottom = floating.bottomFromText ?? 0;
|
|
887
893
|
const distLeft = floating.leftFromText ?? 12;
|
|
888
894
|
const distRight = floating.rightFromText ?? 12;
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
side,
|
|
895
|
+
const rect = {
|
|
896
|
+
side: contentX < contentWidth / 2 ? "left" : "right",
|
|
892
897
|
x: contentX,
|
|
893
898
|
y: contentY,
|
|
894
899
|
width: fragment.width,
|
|
@@ -897,7 +902,19 @@ function renderPage(page, context, options = {}) {
|
|
|
897
902
|
distBottom,
|
|
898
903
|
distLeft,
|
|
899
904
|
distRight
|
|
905
|
+
};
|
|
906
|
+
if (floatingTableReservesBand({
|
|
907
|
+
contentX,
|
|
908
|
+
tableWidth: fragment.width,
|
|
909
|
+
contentWidth,
|
|
910
|
+
distLeft,
|
|
911
|
+
distRight
|
|
912
|
+
})) rect.wrapType = "topAndBottom";
|
|
913
|
+
if (floating.vertAnchor === "text") scopedFloatingRects.push({
|
|
914
|
+
startsAtFragmentIndex: fragmentIndex,
|
|
915
|
+
rect
|
|
900
916
|
});
|
|
917
|
+
else floatingRects.push(rect);
|
|
901
918
|
}
|
|
902
919
|
if (options.blockLookup) for (let fragmentIndex = 0; fragmentIndex < page.fragments.length; fragmentIndex++) {
|
|
903
920
|
const fragment = page.fragments[fragmentIndex];
|
|
@@ -1557,22 +1574,21 @@ function renderPages(pages, container, options = {}) {
|
|
|
1557
1574
|
const pageShells = [];
|
|
1558
1575
|
for (let i = 0; i < pages.length; i++) {
|
|
1559
1576
|
const page = pages[i];
|
|
1577
|
+
let pageEl;
|
|
1560
1578
|
if (!useVirtualization) {
|
|
1561
1579
|
const { context, pageOptions } = buildPageRenderArgs(page, totalPages, options);
|
|
1562
|
-
|
|
1563
|
-
container.insertBefore(pageEl, overlayBefore);
|
|
1564
|
-
pageShells.push(pageEl);
|
|
1580
|
+
pageEl = renderPage(page, context, pageOptions);
|
|
1565
1581
|
} else {
|
|
1566
1582
|
const doc = options.document ?? document;
|
|
1567
|
-
|
|
1583
|
+
pageEl = doc.createElement("div");
|
|
1568
1584
|
pageEl.className = options.pageClassName ?? PAGE_CLASS_NAMES.page;
|
|
1569
1585
|
pageEl.dataset["pageNumber"] = String(page.number);
|
|
1570
1586
|
pageEl.dataset["pageIndex"] = String(i);
|
|
1571
1587
|
applyPageStyles(pageEl, page.size.w, page.size.h, options);
|
|
1572
1588
|
syncPageBorderOverlay(pageEl, page, options, doc);
|
|
1573
|
-
container.insertBefore(pageEl, overlayBefore);
|
|
1574
|
-
pageShells.push(pageEl);
|
|
1575
1589
|
}
|
|
1590
|
+
container.insertBefore(pageEl, overlayBefore);
|
|
1591
|
+
pageShells.push(pageEl);
|
|
1576
1592
|
}
|
|
1577
1593
|
if (!useVirtualization) {
|
|
1578
1594
|
emitPainterPainted(container);
|
|
@@ -1820,4 +1836,4 @@ function depopulatePageShell(shell, pageDataMap) {
|
|
|
1820
1836
|
}
|
|
1821
1837
|
}
|
|
1822
1838
|
//#endregion
|
|
1823
|
-
export { PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
|
|
1839
|
+
export { PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
|
|
@@ -330,8 +330,8 @@ function renderTabRun(run, doc, width, leader) {
|
|
|
330
330
|
} else span.textContent = "\xA0";
|
|
331
331
|
return span;
|
|
332
332
|
}
|
|
333
|
-
function canClampTabToRightEdge(alignment, hasPriorRenderedContent) {
|
|
334
|
-
if (alignment === "start" || alignment === "default") return hasPriorRenderedContent;
|
|
333
|
+
function canClampTabToRightEdge(alignment, hasPriorRenderedContent, hasPriorTab, isLastLine) {
|
|
334
|
+
if (alignment === "start" || alignment === "default") return hasPriorRenderedContent && (hasPriorTab || isLastLine);
|
|
335
335
|
return true;
|
|
336
336
|
}
|
|
337
337
|
function applyTabUnderline(element, run) {
|
|
@@ -905,24 +905,32 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
905
905
|
lineEl.append(emptySpan);
|
|
906
906
|
return lineEl;
|
|
907
907
|
}
|
|
908
|
+
const hasTabRuns = runsForLine.some(isTabRun);
|
|
908
909
|
if (alignment === "justify" && options) {
|
|
909
910
|
if (!options.isLastLine || options.paragraphEndsWithLineBreak) {
|
|
910
|
-
const
|
|
911
|
+
const firstLineIndentPx = options.isFirstLine ? options.firstLineIndentPx ?? 0 : 0;
|
|
912
|
+
const firstLineHangingPx = Math.max(0, -firstLineIndentPx);
|
|
913
|
+
const justifyCapacityPx = options.availableWidth + firstLineHangingPx;
|
|
914
|
+
const overfullPx = line.width - justifyCapacityPx;
|
|
911
915
|
const shrinkableSpaces = countShrinkableSpaces(runsForLine);
|
|
912
916
|
if (overfullPx > RIGHT_EDGE_EPSILON_PX && shrinkableSpaces > 0) {
|
|
913
917
|
lineEl.style.textAlign = "left";
|
|
914
918
|
lineEl.style.textAlignLast = "auto";
|
|
915
919
|
lineEl.style.wordSpacing = `${-overfullPx / shrinkableSpaces}px`;
|
|
920
|
+
} else if (hasTabRuns && shrinkableSpaces > 0 && -overfullPx > RIGHT_EDGE_EPSILON_PX) {
|
|
921
|
+
lineEl.style.textAlign = "left";
|
|
922
|
+
lineEl.style.textAlignLast = "auto";
|
|
923
|
+
lineEl.style.wordSpacing = `${-overfullPx / shrinkableSpaces}px`;
|
|
916
924
|
} else {
|
|
917
925
|
lineEl.style.textAlign = "justify";
|
|
918
926
|
lineEl.style.textAlignLast = "justify";
|
|
919
927
|
}
|
|
920
|
-
|
|
928
|
+
const listFirstLineOffset = options.isFirstLine && block.attrs?.listMarker && !block.attrs.listMarkerHidden ? options.firstLineIndentPx ?? 0 : 0;
|
|
929
|
+
lineEl.style.width = `${options.availableWidth - listFirstLineOffset}px`;
|
|
921
930
|
}
|
|
922
931
|
}
|
|
923
932
|
lineEl.style.whiteSpace = "pre";
|
|
924
933
|
lineEl.style.overflow = "visible";
|
|
925
|
-
const hasTabRuns = runsForLine.some(isTabRun);
|
|
926
934
|
let tabContext;
|
|
927
935
|
const measureText = createTextMeasurer(doc);
|
|
928
936
|
if (hasTabRuns) {
|
|
@@ -930,6 +938,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
930
938
|
const leftIndentTwips = options?.leftIndentPx ? Math.round(options.leftIndentPx * 15) : 0;
|
|
931
939
|
tabContext = {
|
|
932
940
|
...explicitStops !== void 0 ? { explicitStops } : {},
|
|
941
|
+
...block.attrs?.defaultTabStopTwips !== void 0 ? { defaultTabInterval: block.attrs.defaultTabStopTwips } : {},
|
|
933
942
|
leftIndent: leftIndentTwips
|
|
934
943
|
};
|
|
935
944
|
}
|
|
@@ -992,7 +1001,7 @@ function renderLine(block, line, alignment, doc, options) {
|
|
|
992
1001
|
break;
|
|
993
1002
|
}
|
|
994
1003
|
let tabWidth = tabResult.width;
|
|
995
|
-
if (lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
|
|
1004
|
+
if (lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0, runsForLine.slice(0, i).some(isTabRun), options?.isLastLine === true) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
|
|
996
1005
|
const tabEl = renderTabRun(run, doc, tabWidth, tabResult.leader);
|
|
997
1006
|
lineEl.append(tabEl);
|
|
998
1007
|
currentX += tabWidth;
|
|
@@ -1240,7 +1249,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1240
1249
|
} else if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
|
|
1241
1250
|
else if (hasFirstLine && !isFlexLine) lineEl.style.textIndent = `${indent.firstLine ?? 0}px`;
|
|
1242
1251
|
} else if (indentLeft > 0) lineEl.style.paddingLeft = `${indentLeft}px`;
|
|
1243
|
-
else if (hasHanging) lineEl.style.paddingLeft = `${indent.hanging ?? 0}px`;
|
|
1252
|
+
else if (hasHanging && indentLeft === 0) lineEl.style.paddingLeft = `${indent.hanging ?? 0}px`;
|
|
1244
1253
|
if (indentRight > 0) lineEl.style.paddingRight = `${indentRight}px`;
|
|
1245
1254
|
if (isFirstLine && block.attrs?.listMarker && !block.attrs.listMarkerHidden) {
|
|
1246
1255
|
const hanging = indent?.hanging ?? 0;
|
|
@@ -1259,7 +1268,8 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
|
|
|
1259
1268
|
const markerFontFamily = block.attrs.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? block.attrs.defaultFontFamily;
|
|
1260
1269
|
const markerFontSize = block.attrs.listMarkerFontSize ?? firstTextRun?.fontSize ?? block.attrs.defaultFontSize;
|
|
1261
1270
|
const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), doc, markerFontFamily, markerFontSize, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
|
|
1262
|
-
|
|
1271
|
+
const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
|
|
1272
|
+
if (markerMarginLeft < 0 && indentLeft !== 0) marker.style.marginLeft = `${markerMarginLeft}px`;
|
|
1263
1273
|
lineEl.prepend(marker);
|
|
1264
1274
|
}
|
|
1265
1275
|
fragmentEl.append(lineEl);
|