@stll/folio-core 0.47.7 → 0.48.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/controller/fontReadiness.js +3 -3
- package/dist/controller/hyphenationReadiness.d.ts +19 -0
- package/dist/controller/hyphenationReadiness.js +47 -0
- package/dist/controller/layoutPipeline.d.ts +3 -0
- package/dist/controller/layoutPipeline.js +59 -17
- package/dist/display-list/build/buildDisplayList.js +39 -12
- package/dist/display-list/build/pageFurniture.d.ts +7 -1
- package/dist/display-list/build/pageFurniture.js +21 -8
- package/dist/display-list/build/paragraphPrimitives.js +5 -4
- package/dist/display-list/build/tablePrimitives.js +10 -6
- package/dist/docx/attributeRemainder.js +10 -2
- package/dist/docx/blockContentParser.d.ts +2 -0
- package/dist/docx/blockContentParser.js +77 -62
- package/dist/docx/commentParser.d.ts +2 -1
- package/dist/docx/commentParser.js +57 -45
- package/dist/docx/containerChildren.d.ts +21 -8
- package/dist/docx/containerChildren.js +39 -9
- package/dist/docx/documentParser.d.ts +5 -1
- package/dist/docx/documentParser.js +8 -5
- package/dist/docx/documentSectionFacts.d.ts +22 -0
- package/dist/docx/documentSectionFacts.js +43 -0
- package/dist/docx/fieldParser.d.ts +8 -1
- package/dist/docx/fieldParser.js +47 -3
- package/dist/docx/fontTableParser.js +34 -27
- package/dist/docx/footnoteParser.d.ts +3 -2
- package/dist/docx/footnoteParser.js +15 -12
- package/dist/docx/groupDrawingParser.d.ts +2 -1
- package/dist/docx/groupDrawingParser.js +3 -6
- package/dist/docx/headerFooterParser.d.ts +7 -2
- package/dist/docx/headerFooterParser.js +15 -5
- package/dist/docx/hyperlinkParser.d.ts +48 -36
- package/dist/docx/hyperlinkParser.js +97 -82
- package/dist/docx/numberingParser.js +108 -84
- package/dist/docx/paragraphParser.d.ts +5 -1
- package/dist/docx/paragraphParser.js +381 -362
- package/dist/docx/paragraphProperties.js +123 -109
- package/dist/docx/paragraphPropertySource.d.ts +6 -1
- package/dist/docx/paragraphPropertySource.js +9 -1
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +17 -6
- package/dist/docx/parser.d.ts +7 -1
- package/dist/docx/parser.js +33 -16
- package/dist/docx/previewBudget.d.ts +55 -33
- package/dist/docx/previewBudget.js +59 -65
- package/dist/docx/rezip.js +41 -58
- package/dist/docx/runParser.d.ts +4 -1
- package/dist/docx/runParser.js +172 -184
- package/dist/docx/sdtProperties.js +87 -84
- package/dist/docx/sectionParser.js +169 -160
- package/dist/docx/serializer/paragraphSerializer.js +42 -38
- package/dist/docx/serializer/settingsSerializer.js +19 -1
- package/dist/docx/settingsParser.js +8 -0
- package/dist/docx/streamingXmlParser.d.ts +6 -2
- package/dist/docx/streamingXmlParser.js +12 -7
- package/dist/docx/styleParser.js +34 -8
- package/dist/docx/tableParser.d.ts +21 -12
- package/dist/docx/tableParser.js +518 -441
- package/dist/docx/textBoxParser.d.ts +8 -3
- package/dist/docx/textBoxParser.js +3 -3
- package/dist/docx/verbatimCapture.d.ts +10 -8
- package/dist/docx/verbatimCapture.js +45 -2
- package/dist/docx/vmlImageParser.d.ts +15 -13
- package/dist/docx/vmlImageParser.js +75 -60
- package/dist/docx/vmlPreview.d.ts +3 -3
- package/dist/docx/vmlPreview.js +3 -4
- package/dist/docx/xmlNamespaceContext.d.ts +9 -0
- package/dist/docx/xmlNamespaceContext.js +54 -0
- package/dist/docx/xmlParser.d.ts +6 -3
- package/dist/docx/xmlParser.js +11 -41
- package/dist/docx/xmlResourceLimits.d.ts +2 -1
- package/dist/docx/xmlResourceLimits.js +4 -1
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/headless-layout.d.ts +11 -2
- package/dist/headless-layout.js +153 -107
- package/dist/internal/paragraphFormattingSerialization.js +3 -3
- package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
- package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
- package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
- package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
- package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +258 -35
- package/dist/layout-bridge/engine/hitTest.js +5 -3
- package/dist/layout-bridge/engine/selectionRects.js +5 -3
- package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
- package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +70 -11
- package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
- package/dist/layout-engine/justificationCompatibility.js +9 -5
- package/dist/layout-engine/keep-together.d.ts +11 -5
- package/dist/layout-engine/keep-together.js +39 -16
- package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
- package/dist/layout-engine/layoutInstrumentation.js +8 -1
- package/dist/layout-engine/measure/advanceComposition.js +4 -7
- package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
- package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
- package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
- package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
- package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
- package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
- package/dist/layout-engine/measure/lineBreaks.js +10 -3
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
- package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
- package/dist/layout-engine/measure/measureBlocks.js +42 -8
- package/dist/layout-engine/measure/measureContainer.js +5 -5
- package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
- package/dist/layout-engine/measure/measureHelpers.js +21 -5
- package/dist/layout-engine/measure/measureParagraph.js +109 -30
- package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
- package/dist/layout-engine/measure/tabCalculator.js +2 -1
- package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
- package/dist/layout-engine/measure/tableCellFloating.js +51 -19
- package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
- package/dist/layout-engine/measure/tableCellGrid.js +45 -5
- package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
- package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
- package/dist/layout-engine/noteAreaFlow.js +83 -0
- package/dist/layout-engine/paginator.d.ts +10 -1
- package/dist/layout-engine/paginator.js +13 -1
- package/dist/layout-engine/tableRowBreak.js +3 -1
- package/dist/layout-engine/types.d.ts +89 -3
- package/dist/layout-engine/types.js +96 -1
- package/dist/layout-painter/index.d.ts +3 -0
- package/dist/layout-painter/renderPage.js +32 -83
- package/dist/layout-painter/renderParagraph.d.ts +6 -1
- package/dist/layout-painter/renderParagraph.js +34 -22
- package/dist/layout-painter/renderTable.js +26 -13
- package/dist/prosemirror/attrs/index.d.ts +9 -9
- package/dist/prosemirror/attrs/index.js +43 -11
- package/dist/prosemirror/conversion/fromProseDoc.js +8 -1
- package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
- package/dist/prosemirror/conversion/markInterner.js +90 -0
- package/dist/prosemirror/conversion/toProseDoc.js +77 -41
- package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
- package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +2 -1
- package/dist/prosemirror/listMarker.d.ts +1 -0
- package/dist/prosemirror/listMarker.js +2 -0
- package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
- package/dist/prosemirror/listRenderingAttrs.js +3 -0
- package/dist/prosemirror/schema/nodes.d.ts +13 -1
- package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
- package/dist/prosemirror/textBoxHostParagraph.js +21 -0
- package/dist/prosemirror/validation.js +91 -80
- package/dist/utils/createDocument.js +10 -1
- package/dist/utils/scriptSegments.d.ts +33 -4
- package/dist/utils/scriptSegments.js +39 -6
- package/dist/utils/textFormattingMerge.d.ts +17 -3
- package/dist/utils/textFormattingMerge.js +18 -12
- package/dist/utils/trailingText.d.ts +22 -0
- package/dist/utils/trailingText.js +32 -0
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { 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, ListMarkerFormatting, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, tableColumnsArePinned } from "./types.js";
|
|
1
|
+
import { 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, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned } from "./types.js";
|
|
2
2
|
import { PageState, Paginator, PaginatorOptions, createPaginator } from "./paginator.js";
|
|
3
3
|
import { KeepNextChain, calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
4
4
|
import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
|
|
@@ -42,4 +42,4 @@ declare function layoutDocument(blocks: FlowBlock[], measures: Measure[], option
|
|
|
42
42
|
*/
|
|
43
43
|
declare function getHeaderRowsHeight(measure: TableMeasure, headerRowCount: number): number;
|
|
44
44
|
//#endregion
|
|
45
|
-
export { BlockId, BorderStyle, type BreakDecision, 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, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, tableColumnsArePinned };
|
|
45
|
+
export { BlockId, BorderStyle, type BreakDecision, 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, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, MathRun, Measure, MeasuredLine, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, NoteAreaLayout, NoteSeparatorKind, NoteSeparatorRule, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, stripFlowBlockPmAnchors, tableColumnsArePinned };
|
|
@@ -2,9 +2,10 @@ import { emuToPixels } from "../utils/units.js";
|
|
|
2
2
|
import { reflowFootnoteColumns } from "./footnoteColumnReflow.js";
|
|
3
3
|
import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
|
|
4
4
|
import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
5
|
-
import { measuredLineAdvance } from "./lineFlow.js";
|
|
5
|
+
import { measuredLineAdvance, measuredLineRangeHeight } from "./lineFlow.js";
|
|
6
6
|
import { resolveFloatingTablePageX } from "./measure/floatingTablePosition.js";
|
|
7
7
|
import { resolveTableInlineOffset } from "./measure/tableInlinePlacement.js";
|
|
8
|
+
import { annotateNoteSeparators, continuesNoteArea, createNoteAreaContinuation } from "./noteAreaFlow.js";
|
|
8
9
|
import { SECTION_START_PLACEMENT, createPaginator } from "./paginator.js";
|
|
9
10
|
import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
|
|
10
11
|
import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
|
|
@@ -14,7 +15,7 @@ import { applyPendingToActive, createInitialSectionState, getEffectiveColumns, g
|
|
|
14
15
|
import { applySectionVerticalAlignment } from "./sectionVerticalAlignment.js";
|
|
15
16
|
import { buildTableRowBreakInfo, getRowContinuationSkip, snapRowBreak } from "./tableRowBreak.js";
|
|
16
17
|
import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "./textBoxFlow.js";
|
|
17
|
-
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, tableColumnsArePinned } from "./types.js";
|
|
18
|
+
import { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, resolveTableCellPadding, resolveTableWidthPx, stripFlowBlockPmAnchors, tableColumnsArePinned } from "./types.js";
|
|
18
19
|
import { panic } from "better-result";
|
|
19
20
|
//#region src/layout-engine/index.ts
|
|
20
21
|
/**
|
|
@@ -147,14 +148,14 @@ function applyContextualSpacing(blocks) {
|
|
|
147
148
|
*/
|
|
148
149
|
function layoutDocument(blocks, measures, options) {
|
|
149
150
|
const initialLayout = layoutDocumentPass(blocks, measures, options);
|
|
150
|
-
return applySectionVerticalAlignment(options.footnoteHeightById ? reflowFootnoteColumns({
|
|
151
|
+
return annotateNoteSeparators(applySectionVerticalAlignment(options.footnoteHeightById ? reflowFootnoteColumns({
|
|
151
152
|
initialLayout,
|
|
152
153
|
...options.footnoteReservedHeights ? { initialReserveFloors: options.footnoteReservedHeights } : {},
|
|
153
154
|
runLayout: (reserveFloors) => layoutDocumentPass(blocks, measures, {
|
|
154
155
|
...options,
|
|
155
156
|
footnoteReservedHeights: reserveFloors
|
|
156
157
|
})
|
|
157
|
-
}) : initialLayout, options.sectionVerticalAlignments);
|
|
158
|
+
}) : initialLayout, options.sectionVerticalAlignments), blocks, measures, options.noteAreas);
|
|
158
159
|
}
|
|
159
160
|
function layoutDocumentPass(blocks, measures, options) {
|
|
160
161
|
if (blocks.length !== measures.length) panic(`layoutDocument: expected one measure per block (blocks=${blocks.length}, measures=${measures.length})`);
|
|
@@ -208,9 +209,11 @@ function layoutDocumentPass(blocks, measures, options) {
|
|
|
208
209
|
let activeSectionPageHeight = initialConfig.pageSize.h;
|
|
209
210
|
let activeSectionMarginBottom = initialConfig.margins.bottom;
|
|
210
211
|
let renderedBreakState = INITIAL_RENDERED_BREAK_STATE;
|
|
212
|
+
const noteAreaContinuation = createNoteAreaContinuation(options.noteAreas, paginator);
|
|
211
213
|
for (let i = 0; i < blocks.length; i++) {
|
|
212
214
|
const block = blocks[i];
|
|
213
215
|
const measure = measures[i];
|
|
216
|
+
if (noteAreaContinuation !== void 0) paginator.setPageTopContinuation(continuesNoteArea(options.noteAreas, block) ? noteAreaContinuation : void 0);
|
|
214
217
|
const firstLine = measure.kind === "paragraph" ? measure.lines.at(0) : void 0;
|
|
215
218
|
const firstLineAdvance = firstLine ? measuredLineAdvance(firstLine) : 0;
|
|
216
219
|
const renderedBreakNeedsSnap = measure.kind === "paragraph" && (!paginator.fits(measure.totalHeight) || firstLineAdvance > 0 && paginator.getAvailableHeight() <= firstLineAdvance * RENDERED_BREAK_REFLOW_TOLERANCE_LINES);
|
|
@@ -225,13 +228,13 @@ function layoutDocumentPass(blocks, measures, options) {
|
|
|
225
228
|
renderedBreakNeedsSnap
|
|
226
229
|
});
|
|
227
230
|
if (block.kind !== "pageBreak") {
|
|
228
|
-
if (breakDecision.pageAdvance === PAGE_ADVANCE.PHYSICAL) paginator.forcePageBreak();
|
|
231
|
+
if (breakDecision.pageAdvance === PAGE_ADVANCE.PHYSICAL) paginator.forcePageBreak({ coalesceBlankPage: hasPageBreakBefore(block) });
|
|
229
232
|
else if (breakDecision.pageAdvance === PAGE_ADVANCE.COALESCED) paginator.coalescePageBreak();
|
|
230
233
|
}
|
|
231
234
|
renderedBreakState = breakDecision.state;
|
|
232
235
|
const chain = keepNextChains.get(i);
|
|
233
236
|
if (chain && !midChainIndices.has(i)) {
|
|
234
|
-
const chainHeight = calculateChainHeight(chain, blocks, measures);
|
|
237
|
+
const chainHeight = calculateChainHeight(chain, blocks, measures, paginator.getCurrentState().trailingSpacing);
|
|
235
238
|
const pageBeforeChainLayout = paginator.getCurrentState().page.number;
|
|
236
239
|
paginator.ensureFits(chainHeight);
|
|
237
240
|
if (paginator.getCurrentState().page.number > pageBeforeChainLayout) renderedBreakState = recordReflowBoundary(renderedBreakState, true);
|
|
@@ -306,6 +309,7 @@ function layoutDocumentPass(blocks, measures, options) {
|
|
|
306
309
|
previousPage: paginator.states[pageBeforeBlockLayout - 1]?.page
|
|
307
310
|
});
|
|
308
311
|
}
|
|
312
|
+
paginator.setPageTopContinuation(void 0);
|
|
309
313
|
if (paginator.states.length === 0) paginator.getCurrentState();
|
|
310
314
|
return {
|
|
311
315
|
pageSize,
|
|
@@ -376,6 +380,16 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
|
|
|
376
380
|
}
|
|
377
381
|
const spaceBefore = suppressSpaceBefore ? 0 : getParagraphSpacingBefore(block);
|
|
378
382
|
const spaceAfter = getParagraphSpacingAfter(block);
|
|
383
|
+
let keepLinesDemand;
|
|
384
|
+
if (lines.length > 1 && hasKeepLines(block)) {
|
|
385
|
+
let footnoteHeight = 0;
|
|
386
|
+
for (const line of lines) footnoteHeight += getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById).height;
|
|
387
|
+
keepLinesDemand = {
|
|
388
|
+
linesHeight: measuredLineRangeHeight(lines, 0, lines.length),
|
|
389
|
+
footnoteHeight
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
let keepLinesMoveAttempted = false;
|
|
379
393
|
let currentLineIndex = 0;
|
|
380
394
|
while (currentLineIndex < lines.length) {
|
|
381
395
|
const state = paginator.getCurrentState();
|
|
@@ -400,6 +414,19 @@ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeig
|
|
|
400
414
|
continue;
|
|
401
415
|
}
|
|
402
416
|
}
|
|
417
|
+
if (keepLinesDemand !== void 0 && !keepLinesMoveAttempted && currentLineIndex === 0 && state.cursorY !== state.topMargin) {
|
|
418
|
+
const wholeHeight = keepLinesDemand.linesHeight + projectedFootnoteReserveGrowth(state, keepLinesDemand.footnoteHeight);
|
|
419
|
+
const collapsedLead = collapseParagraphSpacing({
|
|
420
|
+
before: spaceBefore,
|
|
421
|
+
after: state.trailingSpacing
|
|
422
|
+
});
|
|
423
|
+
const columnCapacity = state.contentBottom - state.topMargin;
|
|
424
|
+
if (collapsedLead + wholeHeight > availableHeight && spaceBefore + wholeHeight <= columnCapacity) {
|
|
425
|
+
keepLinesMoveAttempted = true;
|
|
426
|
+
paginator.ensureFits(collapsedLead + wholeHeight);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
403
430
|
let linesHeight = 0;
|
|
404
431
|
let linesFnHeight = 0;
|
|
405
432
|
const linesFnIds = [];
|
|
@@ -561,13 +588,19 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
|
|
|
561
588
|
return previous?.kind === "table" && previous.blockId === block.id && previous.toRow === rowIndex && previous.y + previous.height === state.cursorY;
|
|
562
589
|
};
|
|
563
590
|
const shouldRepeatHeaderRows = (rowIndex, consumed, state = paginator.getCurrentState()) => headerRowCount > 0 && rowIndex >= headerRowCount && !(consumed === 0 && hasAdjacentPriorTableRows(rowIndex, state));
|
|
591
|
+
const rowOpensFlowRegion = (rowIndex, state) => {
|
|
592
|
+
if (state.cursorY === state.topMargin) return true;
|
|
593
|
+
const previous = state.page.fragments.at(-1);
|
|
594
|
+
return rowIndex > 0 && rowIndex <= headerRowCount && previous?.kind === "table" && previous.blockId === block.id && previous.fromRow === 0 && previous.toRow === rowIndex && previous.y === state.topMargin && previous.y + previous.height === state.cursorY;
|
|
595
|
+
};
|
|
564
596
|
const canSplitRow = (rowIndex, state = paginator.getCurrentState()) => {
|
|
565
597
|
const row = rows[rowIndex];
|
|
566
598
|
const sourceRow = block.rows[rowIndex];
|
|
567
|
-
if (!row || !sourceRow || sourceRow.
|
|
599
|
+
if (!row || !sourceRow || sourceRow.isHeader || verticallyMergedRows.has(rowIndex)) return false;
|
|
568
600
|
if ((breakInfo.breakOffsets[rowIndex]?.length ?? 0) <= 1) return false;
|
|
569
601
|
const freshHeaderOverhead = headerRowCount > 0 && rowIndex >= headerRowCount ? headerRowsHeight : 0;
|
|
570
602
|
const oversized = row.height + freshHeaderOverhead > getCurrentRowCapacity(state);
|
|
603
|
+
if (sourceRow.cantSplit) return oversized && sourceRow.heightRule !== "exact" && rowOpensFlowRegion(rowIndex, state);
|
|
571
604
|
if (!oversized && (state.footnoteHeight > 0 || (rowFootnoteIds[rowIndex]?.length ?? 0) > 0)) return false;
|
|
572
605
|
const currentHeaderOverhead = shouldRepeatHeaderRows(rowIndex, 0, state) ? headerRowsHeight : 0;
|
|
573
606
|
const currentAvailableHeight = paginator.getAvailableHeight() - currentHeaderOverhead - state.trailingSpacing;
|
|
@@ -955,18 +988,44 @@ function layoutTextBox(block, measure, { paginator, sectionMarginTop, sectionPag
|
|
|
955
988
|
paginator.addUnflowedFragment(fragment);
|
|
956
989
|
return;
|
|
957
990
|
}
|
|
958
|
-
const
|
|
991
|
+
const effect = block.effectExtent;
|
|
992
|
+
const occupiedHeight = measure.height + (effect?.top ?? 0) + (effect?.bottom ?? 0);
|
|
993
|
+
const occupiedWidth = measure.width + (effect?.left ?? 0) + (effect?.right ?? 0);
|
|
994
|
+
const host = block.hostParagraph;
|
|
959
995
|
const fragment = {
|
|
960
996
|
kind: "textBox",
|
|
961
997
|
blockId: block.id,
|
|
962
|
-
x:
|
|
998
|
+
x: 0,
|
|
963
999
|
y: 0,
|
|
964
1000
|
width: measure.width,
|
|
965
1001
|
height: measure.height,
|
|
966
1002
|
...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
|
|
967
1003
|
...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
|
|
968
1004
|
};
|
|
969
|
-
|
|
1005
|
+
const result = paginator.addFragment(fragment, occupiedHeight, host?.spacing?.before ?? 0, host?.spacing?.after ?? 0);
|
|
1006
|
+
fragment.x = result.x + hostParagraphLineOffset(host, paginator.columnWidth, occupiedWidth) + (effect?.left ?? 0);
|
|
1007
|
+
fragment.y = result.y + (effect?.top ?? 0);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Where a line holding only an object of `width` starts within a column:
|
|
1011
|
+
* after the host paragraph's start and first-line indents, then aligned in
|
|
1012
|
+
* what remains before its end indent.
|
|
1013
|
+
*/
|
|
1014
|
+
function hostParagraphLineOffset(host, columnWidth, width) {
|
|
1015
|
+
if (!host) return 0;
|
|
1016
|
+
const indent = host.indent;
|
|
1017
|
+
const start = (indent?.left ?? 0) + (indent?.firstLine ?? 0) - (indent?.hanging ?? 0);
|
|
1018
|
+
const slack = Math.max(0, columnWidth - start - (indent?.right ?? 0) - width);
|
|
1019
|
+
switch (host.alignment) {
|
|
1020
|
+
case "center": return start + slack / 2;
|
|
1021
|
+
case "right": return start + slack;
|
|
1022
|
+
case "left":
|
|
1023
|
+
case "justify":
|
|
1024
|
+
case void 0: return start;
|
|
1025
|
+
default:
|
|
1026
|
+
host.alignment;
|
|
1027
|
+
return start;
|
|
1028
|
+
}
|
|
970
1029
|
}
|
|
971
1030
|
/**
|
|
972
1031
|
* A topAndBottom text box whose vertical anchor pins it to the page frame
|
|
@@ -1072,4 +1131,4 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
|
|
|
1072
1131
|
paginator.updateColumns(nextSectionConfig.columns ?? DEFAULT_COLUMNS);
|
|
1073
1132
|
}
|
|
1074
1133
|
//#endregion
|
|
1075
|
-
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, tableColumnsArePinned };
|
|
1134
|
+
export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, NOTE_SEPARATOR_RULE_THICKNESS, NOTE_SEPARATOR_WIDTH_FRACTION, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isListNumPr, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, resolveTableCellPadding, resolveTableWidthPx, scheduleSectionBreak, stripFlowBlockPmAnchors, tableColumnsArePinned };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ParagraphAttrs } from "./types.js";
|
|
2
2
|
//#region src/layout-engine/justificationCompatibility.d.ts
|
|
3
|
-
declare
|
|
3
|
+
declare const resolveJustificationCompatibility: (compatibilityMode: number | undefined) => NonNullable<ParagraphAttrs["justificationCompatibility"]> | undefined;
|
|
4
4
|
//#endregion
|
|
5
5
|
export { resolveJustificationCompatibility };
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
//#region src/layout-engine/justificationCompatibility.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
/**
|
|
3
|
+
* First `compatibilityMode` that fits justified lines with the current rules.
|
|
4
|
+
*
|
|
5
|
+
* Earlier modes keep strict justified fitting. A document that declares no
|
|
6
|
+
* `compatSetting` at all is read with the oldest semantics, so it takes the
|
|
7
|
+
* legacy rule too.
|
|
8
|
+
*/
|
|
9
|
+
const FIRST_MODERN_JUSTIFICATION_COMPATIBILITY_MODE = 15;
|
|
10
|
+
const resolveJustificationCompatibility = (compatibilityMode) => compatibilityMode !== void 0 && compatibilityMode >= FIRST_MODERN_JUSTIFICATION_COMPATIBILITY_MODE ? void 0 : { type: "legacy" };
|
|
7
11
|
//#endregion
|
|
8
12
|
export { resolveJustificationCompatibility };
|
|
@@ -30,12 +30,18 @@ declare function computeKeepNextChains(blocks: FlowBlock[]): Map<number, KeepNex
|
|
|
30
30
|
* Calculate the height needed to keep consecutive paragraph boundaries from
|
|
31
31
|
* breaking across pages.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* Each successor (later member or the anchor) must bring its minimum
|
|
34
|
+
* unbreakable opening onto the page: see {@link minimumOpeningLineCount}. When
|
|
35
|
+
* that opening is the whole paragraph, the successor is indivisible and the
|
|
36
|
+
* reservation continues through it; otherwise the chain stops after the
|
|
37
|
+
* opening, because a later split cannot separate it from its predecessor.
|
|
38
|
+
*
|
|
39
|
+
* A paragraph measure's `totalHeight` already includes its own spacing before
|
|
40
|
+
* and after. The chain accounts for spacing separately (collapsing each gap to
|
|
41
|
+
* the larger side, as the paginator does), so members contribute only their
|
|
42
|
+
* line advances; otherwise every gap would be reserved twice.
|
|
37
43
|
*/
|
|
38
|
-
declare function calculateChainHeight(chain: KeepNextChain, blocks: FlowBlock[], measures: Measure[]): number;
|
|
44
|
+
declare function calculateChainHeight(chain: KeepNextChain, blocks: FlowBlock[], measures: Measure[], incomingSpacing?: number): number;
|
|
39
45
|
/**
|
|
40
46
|
* Get the set of indices that are mid-chain (not chain starters).
|
|
41
47
|
* These should skip the keepNext check since their chain starter already decided.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { measuredLineRangeHeight } from "./lineFlow.js";
|
|
1
2
|
import { collapseParagraphSpacing, isAuthoredEmptyParagraph, isEmptyParagraph } from "./paragraphSpacing.js";
|
|
2
3
|
//#region src/layout-engine/keep-together.ts
|
|
3
4
|
/**
|
|
@@ -6,6 +7,8 @@ import { collapseParagraphSpacing, isAuthoredEmptyParagraph, isEmptyParagraph }
|
|
|
6
7
|
* DOCX paragraphs can have keepNext (keep with next paragraph) and keepLines
|
|
7
8
|
* (keep all lines together) properties that affect pagination.
|
|
8
9
|
*/
|
|
10
|
+
/** Lines §17.3.1.44 widow control keeps on each side of a paragraph split. */
|
|
11
|
+
const MIN_WIDOW_CONTROL_SPLIT_LINES = 2;
|
|
9
12
|
/**
|
|
10
13
|
* Whether an empty paragraph is only a structural separator through which a
|
|
11
14
|
* keep-with-next chain may pass. An explicitly styled or directly formatted
|
|
@@ -74,23 +77,47 @@ function computeKeepNextChains(blocks) {
|
|
|
74
77
|
return chains;
|
|
75
78
|
}
|
|
76
79
|
/**
|
|
80
|
+
* Lines of a paragraph that must share a page with the paragraph before it for
|
|
81
|
+
* a `w:keepNext` link (§17.3.1.15) to hold.
|
|
82
|
+
*
|
|
83
|
+
* - `w:keepLines` (§17.3.1.14): the paragraph never splits, so all of it.
|
|
84
|
+
* - `w:widowControl` (§17.3.1.44): a split leaves at least two lines on each
|
|
85
|
+
* side, so a paragraph shorter than four lines cannot split at all and a
|
|
86
|
+
* longer one needs its first two lines.
|
|
87
|
+
* - Otherwise a single line.
|
|
88
|
+
*/
|
|
89
|
+
function minimumOpeningLineCount(block, measure) {
|
|
90
|
+
const lineCount = measure.lines.length;
|
|
91
|
+
if (block.attrs?.keepLines === true) return lineCount;
|
|
92
|
+
if (block.attrs?.widowControl !== false) return lineCount < MIN_WIDOW_CONTROL_SPLIT_LINES * 2 ? lineCount : MIN_WIDOW_CONTROL_SPLIT_LINES;
|
|
93
|
+
return Math.min(1, lineCount);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
77
96
|
* Calculate the height needed to keep consecutive paragraph boundaries from
|
|
78
97
|
* breaking across pages.
|
|
79
98
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
99
|
+
* Each successor (later member or the anchor) must bring its minimum
|
|
100
|
+
* unbreakable opening onto the page: see {@link minimumOpeningLineCount}. When
|
|
101
|
+
* that opening is the whole paragraph, the successor is indivisible and the
|
|
102
|
+
* reservation continues through it; otherwise the chain stops after the
|
|
103
|
+
* opening, because a later split cannot separate it from its predecessor.
|
|
104
|
+
*
|
|
105
|
+
* A paragraph measure's `totalHeight` already includes its own spacing before
|
|
106
|
+
* and after. The chain accounts for spacing separately (collapsing each gap to
|
|
107
|
+
* the larger side, as the paginator does), so members contribute only their
|
|
108
|
+
* line advances; otherwise every gap would be reserved twice.
|
|
84
109
|
*/
|
|
85
|
-
function calculateChainHeight(chain, blocks, measures) {
|
|
110
|
+
function calculateChainHeight(chain, blocks, measures, incomingSpacing = 0) {
|
|
86
111
|
const firstMemberIndex = chain.memberIndices.at(0);
|
|
87
112
|
if (firstMemberIndex === void 0) return 0;
|
|
88
113
|
const firstBlock = blocks[firstMemberIndex];
|
|
89
114
|
const firstMeasure = measures[firstMemberIndex];
|
|
90
115
|
if (firstBlock?.kind !== "paragraph" || firstMeasure?.kind !== "paragraph") return 0;
|
|
91
|
-
let totalHeight = (
|
|
116
|
+
let totalHeight = collapseParagraphSpacing({
|
|
117
|
+
before: firstBlock.attrs?.spacing?.before ?? 0,
|
|
118
|
+
after: incomingSpacing
|
|
119
|
+
}) + measuredLineRangeHeight(firstMeasure.lines, 0, firstMeasure.lines.length);
|
|
92
120
|
let trailingSpacing = firstBlock.attrs?.spacing?.after ?? 0;
|
|
93
|
-
const startsWithTrailingTableSeparator = blocks[firstMemberIndex - 1]?.kind === "table" && isEmptyParagraph(firstBlock) && !firstBlock.attrs?.keepNext;
|
|
94
121
|
const successorIndices = [...chain.memberIndices.slice(1)];
|
|
95
122
|
if (chain.anchorIndex !== -1) successorIndices.push(chain.anchorIndex);
|
|
96
123
|
for (let index = 0; index < successorIndices.length; index++) {
|
|
@@ -103,16 +130,12 @@ function calculateChainHeight(chain, blocks, measures) {
|
|
|
103
130
|
before: successorBlock.attrs?.spacing?.before ?? 0,
|
|
104
131
|
after: trailingSpacing
|
|
105
132
|
});
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
133
|
+
const lineCount = successorMeasure.lines.length;
|
|
134
|
+
if (lineCount === 0) return totalHeight;
|
|
108
135
|
const isAnchor = index === successorIndices.length - 1 && chain.anchorIndex !== -1;
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return totalHeight + firstLine.lineHeight + (secondLine?.lineHeight ?? 0);
|
|
113
|
-
}
|
|
114
|
-
if (isAnchor || isSplittable) return totalHeight + firstLine.lineHeight;
|
|
115
|
-
totalHeight += successorMeasure.totalHeight;
|
|
136
|
+
const openingLines = minimumOpeningLineCount(successorBlock, successorMeasure);
|
|
137
|
+
totalHeight += measuredLineRangeHeight(successorMeasure.lines, 0, openingLines);
|
|
138
|
+
if (isAnchor || openingLines < lineCount) return totalHeight;
|
|
116
139
|
trailingSpacing = successorBlock.attrs?.spacing?.after ?? 0;
|
|
117
140
|
}
|
|
118
141
|
return totalHeight;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FlowBlock } from "./types.js";
|
|
2
2
|
//#region src/layout-engine/layoutInstrumentation.d.ts
|
|
3
|
-
type LayoutRunReason = "font-ready" | "initial" | "layout-input" | "manual" | "transaction";
|
|
3
|
+
type LayoutRunReason = "font-ready" | "hyphenation-ready" | "initial" | "layout-input" | "manual" | "transaction";
|
|
4
4
|
type LayoutPhase = "flow-blocks" | "header-footer" | "initial-fonts" | "layout-document" | "measure-blocks" | "render-pages";
|
|
5
5
|
type HiddenEditorStateReason = "external-document" | "mount";
|
|
6
6
|
type HiddenEditorPhase = "editor-state" | "editor-view" | "to-prose-doc" | "update-state";
|
|
@@ -18,6 +18,10 @@ type LayoutInstrumentation = {
|
|
|
18
18
|
onHiddenEditorStateCreate?: (event: {
|
|
19
19
|
reason: HiddenEditorStateReason;
|
|
20
20
|
}) => void;
|
|
21
|
+
onHyphenationDictionaryError?: (event: {
|
|
22
|
+
dictionary: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}) => void;
|
|
21
25
|
onLayoutComplete?: (event: {
|
|
22
26
|
reason: LayoutRunReason;
|
|
23
27
|
}) => void;
|
|
@@ -51,9 +55,11 @@ declare function recordMeasureBlockError(blockIndex: number, block: FlowBlock, e
|
|
|
51
55
|
declare function recordLayoutComplete(reason?: LayoutRunReason): void;
|
|
52
56
|
declare function recordLayoutStart(reason: LayoutRunReason): void;
|
|
53
57
|
declare function recordLayoutError(reason: LayoutRunReason, error: unknown): void;
|
|
58
|
+
/** A dictionary failed to load; layout continues without hyphenating that language. */
|
|
59
|
+
declare function recordHyphenationDictionaryError(dictionary: string, error: Error): void;
|
|
54
60
|
declare function recordLayoutPhase(reason: LayoutRunReason, phase: LayoutPhase, durationMs: number): void;
|
|
55
61
|
declare function recordHiddenEditorStateCreate(reason: HiddenEditorStateReason): void;
|
|
56
62
|
declare function recordHiddenEditorPhase(reason: HiddenEditorStateReason, phase: HiddenEditorPhase, durationMs: number): void;
|
|
57
63
|
declare function recordDocumentLoadPhase(phase: DocumentLoadPhase, durationMs: number): void;
|
|
58
64
|
//#endregion
|
|
59
|
-
export { DocumentLoadPhase, HiddenEditorPhase, HiddenEditorStateReason, LayoutInstrumentation, LayoutPhase, LayoutRunReason, recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
|
|
65
|
+
export { DocumentLoadPhase, HiddenEditorPhase, HiddenEditorStateReason, LayoutInstrumentation, LayoutPhase, LayoutRunReason, recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordHyphenationDictionaryError, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
|
|
@@ -25,6 +25,13 @@ function recordLayoutError(reason, error) {
|
|
|
25
25
|
reason
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
/** A dictionary failed to load; layout continues without hyphenating that language. */
|
|
29
|
+
function recordHyphenationDictionaryError(dictionary, error) {
|
|
30
|
+
globalThis.__folioLayoutInstrumentation?.onHyphenationDictionaryError?.({
|
|
31
|
+
dictionary,
|
|
32
|
+
message: error.message
|
|
33
|
+
});
|
|
34
|
+
}
|
|
28
35
|
function recordLayoutPhase(reason, phase, durationMs) {
|
|
29
36
|
globalThis.__folioLayoutInstrumentation?.onLayoutPhase?.({
|
|
30
37
|
durationMs,
|
|
@@ -49,4 +56,4 @@ function recordDocumentLoadPhase(phase, durationMs) {
|
|
|
49
56
|
});
|
|
50
57
|
}
|
|
51
58
|
//#endregion
|
|
52
|
-
export { recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
|
|
59
|
+
export { recordDocumentLoadPhase, recordHiddenEditorPhase, recordHiddenEditorStateCreate, recordHyphenationDictionaryError, recordLayoutComplete, recordLayoutError, recordLayoutPhase, recordLayoutStart, recordMeasureBlock, recordMeasureBlockError };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
|
|
2
|
-
import { SCRIPT_CLASS, hasComplexScript, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
|
|
2
|
+
import { SCRIPT_CLASS, hasComplexScript, hasEastAsiaSlotText, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
|
|
3
3
|
import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./complexScriptFormatting.js";
|
|
4
4
|
//#region src/layout-engine/measure/advanceComposition.ts
|
|
5
5
|
/**
|
|
@@ -48,10 +48,7 @@ const glyphAdvanceStyle = (style) => {
|
|
|
48
48
|
if (style.kerning !== void 0) result.kerning = style.kerning;
|
|
49
49
|
return result;
|
|
50
50
|
};
|
|
51
|
-
const needsPerScriptFonts = (style, text) => style.eastAsiaFontFamily !== void 0 &&
|
|
52
|
-
const isEastAsia = (char) => {
|
|
53
|
-
return scriptClassOf(char.codePointAt(0)) === SCRIPT_CLASS.eastAsia;
|
|
54
|
-
};
|
|
51
|
+
const needsPerScriptFonts = (style, text) => style.eastAsiaFontFamily !== void 0 && hasEastAsiaSlotText(text, style.eastAsiaHint) || hasComplexScriptFormatting(style) && hasComplexScript(text);
|
|
55
52
|
/**
|
|
56
53
|
* Width of a string: the advance width used for line breaking, never the
|
|
57
54
|
* painted (ink) extent, which includes overhang a break decision must ignore.
|
|
@@ -62,7 +59,7 @@ const composeTextWidth = ({ text, style: source, advanceOf }) => {
|
|
|
62
59
|
const transformed = applyMeasurementTextTransform(text, style);
|
|
63
60
|
const letterSpacing = style.letterSpacing ?? 0;
|
|
64
61
|
const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
|
|
65
|
-
const glyphWidth = !source.forceComplexScript && !letterSpacing && needsPerScriptFonts(style, transformed) ? segmentByScript(transformed).reduce((sum, segment) => sum + advanceOf(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script))), 0) : advanceOf(transformed, style);
|
|
62
|
+
const glyphWidth = !source.forceComplexScript && !letterSpacing && needsPerScriptFonts(style, transformed) ? segmentByScript(transformed, style.eastAsiaHint).reduce((sum, segment) => sum + advanceOf(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script))), 0) : advanceOf(transformed, style);
|
|
66
63
|
const codePoints = countCodePoints(transformed);
|
|
67
64
|
return (letterSpacing && codePoints > 1 ? glyphWidth + letterSpacing * (codePoints - 1) : glyphWidth) * horizontalScale;
|
|
68
65
|
};
|
|
@@ -97,7 +94,7 @@ const composeRunMeasurement = ({ text, style: source, metrics, advanceOf, advanc
|
|
|
97
94
|
let codePointIndex = 0;
|
|
98
95
|
for (const char of text) {
|
|
99
96
|
const cp = char.codePointAt(0);
|
|
100
|
-
const charStyle = perScript ? scriptStyle(style, scriptClassOf(cp)) : style;
|
|
97
|
+
const charStyle = perScript ? scriptStyle(style, scriptClassOf(cp, style.eastAsiaHint)) : style;
|
|
101
98
|
let charWidth = shaped.get(codePointIndex) ?? advanceOf(applyMeasurementTextTransform(char, style), charStyle);
|
|
102
99
|
if (letterSpacing && offset + char.length < text.length) charWidth += letterSpacing;
|
|
103
100
|
charWidth *= scale;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
//#region src/layout-engine/measure/hyphenationDictionaries.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* On-demand hyphenation dictionaries for automatic hyphenation.
|
|
4
|
+
*
|
|
5
|
+
* Each `hyphen` pattern set is tens to hundreds of kilobytes of source whose
|
|
6
|
+
* evaluation builds a pattern trie, and most documents never enable
|
|
7
|
+
* `w:autoHyphenation`. A dictionary is therefore imported only once measurement
|
|
8
|
+
* asks to hyphenate a word in its language. Measurement is synchronous, so the
|
|
9
|
+
* first request starts the import and hyphenates nothing. A layout run wrapped
|
|
10
|
+
* in {@link collectRequestedHyphenationDictionaries} learns which dictionaries
|
|
11
|
+
* it lacked, so its own editor can re-run layout on load or report the failure
|
|
12
|
+
* (`controller/hyphenationReadiness`). Hosts that must lay out correctly on the
|
|
13
|
+
* first pass (headless layout, tests) await `preloadHyphenationDictionaries`
|
|
14
|
+
* (`./hyphenationPreload`) before measuring.
|
|
15
|
+
*
|
|
16
|
+
* This module sits on every editor's import graph, so it speaks in its own
|
|
17
|
+
* settled-state union rather than `Result`, keeping the `Result` API out of
|
|
18
|
+
* adapters' type checks; the preload boundary converts.
|
|
19
|
+
*/
|
|
20
|
+
type HyphenateWord = (text: string) => string;
|
|
21
|
+
declare const HYPHENATION_DICTIONARY_IDS: readonly ["cs", "en-gb", "en-us", "sk"];
|
|
22
|
+
type HyphenationDictionaryId = (typeof HYPHENATION_DICTIONARY_IDS)[number];
|
|
23
|
+
type HyphenationDictionaryLoaders = Readonly<Record<HyphenationDictionaryId, () => Promise<HyphenateWord>>>;
|
|
24
|
+
declare const HyphenationDictionaryError_base: import("better-result").TaggedErrorClass<"HyphenationDictionaryError">;
|
|
25
|
+
declare class HyphenationDictionaryError extends HyphenationDictionaryError_base<{
|
|
26
|
+
message: string;
|
|
27
|
+
dictionary: HyphenationDictionaryId;
|
|
28
|
+
cause: unknown;
|
|
29
|
+
}> {}
|
|
30
|
+
type SettledHyphenationDictionary = Readonly<{
|
|
31
|
+
status: "loaded";
|
|
32
|
+
hyphenate: HyphenateWord;
|
|
33
|
+
}> | Readonly<{
|
|
34
|
+
status: "failed";
|
|
35
|
+
error: HyphenationDictionaryError;
|
|
36
|
+
}>;
|
|
37
|
+
type HyphenationDictionaryState = Readonly<{
|
|
38
|
+
status: "unloaded";
|
|
39
|
+
}> | Readonly<{
|
|
40
|
+
status: "loading";
|
|
41
|
+
promise: Promise<SettledHyphenationDictionary>;
|
|
42
|
+
}> | SettledHyphenationDictionary;
|
|
43
|
+
/** Compare before and after a measurement to tell whether it lacked a dictionary. */
|
|
44
|
+
declare const getHyphenationDictionaryMisses: () => number;
|
|
45
|
+
/** The dictionary that hyphenates text in `locale`, if one is bundled. */
|
|
46
|
+
declare const hyphenationDictionaryFor: (locale: string | undefined) => HyphenationDictionaryId | undefined;
|
|
47
|
+
/** Changes whenever a dictionary finishes loading, so measurement caches cannot go stale. */
|
|
48
|
+
declare const getHyphenationDictionaryGeneration: () => number;
|
|
49
|
+
/** Load `dictionary` once; later calls share the load or its settled outcome. */
|
|
50
|
+
declare const requestHyphenationDictionary: (dictionary: HyphenationDictionaryId) => Promise<SettledHyphenationDictionary>;
|
|
51
|
+
/**
|
|
52
|
+
* The loaded hyphenator for `dictionary`, or `undefined` while it is not
|
|
53
|
+
* available. An unloaded dictionary starts loading, and a missing one is
|
|
54
|
+
* recorded against the enclosing {@link collectRequestedHyphenationDictionaries}
|
|
55
|
+
* run; the caller hyphenates nothing now.
|
|
56
|
+
*/
|
|
57
|
+
declare const hyphenatorOrRequest: (dictionary: HyphenationDictionaryId) => HyphenateWord | undefined;
|
|
58
|
+
type CollectedHyphenationDictionaries<T> = {
|
|
59
|
+
result: T;
|
|
60
|
+
/** Dictionaries the run needed and did not have: loading or failed. */
|
|
61
|
+
missing: ReadonlySet<HyphenationDictionaryId>;
|
|
62
|
+
};
|
|
63
|
+
/** Run a synchronous layout pass and report the dictionaries it lacked. */
|
|
64
|
+
declare const collectRequestedHyphenationDictionaries: <T>(run: () => T) => CollectedHyphenationDictionaries<T>;
|
|
65
|
+
/**
|
|
66
|
+
* Test seam: forget every dictionary so a test observes the unloaded path, and
|
|
67
|
+
* optionally load through `loaders` (for example, ones that fail) until the
|
|
68
|
+
* next reset.
|
|
69
|
+
*/
|
|
70
|
+
declare const resetHyphenationDictionaries: (loaders?: HyphenationDictionaryLoaders) => void;
|
|
71
|
+
/** Test seam: which dictionaries have been requested or loaded. */
|
|
72
|
+
declare const hyphenationDictionaryStatus: (dictionary: HyphenationDictionaryId) => HyphenationDictionaryState["status"];
|
|
73
|
+
//#endregion
|
|
74
|
+
export { CollectedHyphenationDictionaries, HyphenateWord, HyphenationDictionaryError, HyphenationDictionaryId, HyphenationDictionaryLoaders, SettledHyphenationDictionary, collectRequestedHyphenationDictionaries, getHyphenationDictionaryGeneration, getHyphenationDictionaryMisses, hyphenationDictionaryFor, hyphenationDictionaryStatus, hyphenatorOrRequest, requestHyphenationDictionary, resetHyphenationDictionaries };
|