postext 1.4.1 → 1.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/README.md +76 -202
- package/dist/__tests__/breakOpportunities.test.d.ts +2 -0
- package/dist/__tests__/breakOpportunities.test.d.ts.map +1 -0
- package/dist/__tests__/breakOpportunities.test.js +204 -0
- package/dist/__tests__/breakOpportunities.test.js.map +1 -0
- package/dist/__tests__/bundle.test.js +399 -1
- package/dist/__tests__/bundle.test.js.map +1 -1
- package/dist/__tests__/bundleLocaleAndBytes.test.d.ts +2 -0
- package/dist/__tests__/bundleLocaleAndBytes.test.d.ts.map +1 -0
- package/dist/__tests__/bundleLocaleAndBytes.test.js +177 -0
- package/dist/__tests__/bundleLocaleAndBytes.test.js.map +1 -0
- package/dist/__tests__/colorAlpha.test.d.ts +2 -0
- package/dist/__tests__/colorAlpha.test.d.ts.map +1 -0
- package/dist/__tests__/colorAlpha.test.js +87 -0
- package/dist/__tests__/colorAlpha.test.js.map +1 -0
- package/dist/__tests__/columnBalancing.test.js +88 -0
- package/dist/__tests__/columnBalancing.test.js.map +1 -1
- package/dist/__tests__/columnClip.test.js +20 -0
- package/dist/__tests__/columnClip.test.js.map +1 -1
- package/dist/__tests__/columnRuleBands.test.d.ts +2 -0
- package/dist/__tests__/columnRuleBands.test.d.ts.map +1 -0
- package/dist/__tests__/columnRuleBands.test.js +146 -0
- package/dist/__tests__/columnRuleBands.test.js.map +1 -0
- package/dist/__tests__/contentWarnings.test.d.ts +2 -0
- package/dist/__tests__/contentWarnings.test.d.ts.map +1 -0
- package/dist/__tests__/contentWarnings.test.js +219 -0
- package/dist/__tests__/contentWarnings.test.js.map +1 -0
- package/dist/__tests__/continuation.test.js +2 -1
- package/dist/__tests__/continuation.test.js.map +1 -1
- package/dist/__tests__/createLayout.test.js +18 -1
- package/dist/__tests__/createLayout.test.js.map +1 -1
- package/dist/__tests__/cropMarks.test.d.ts +2 -0
- package/dist/__tests__/cropMarks.test.d.ts.map +1 -0
- package/dist/__tests__/cropMarks.test.js +138 -0
- package/dist/__tests__/cropMarks.test.js.map +1 -0
- package/dist/__tests__/defaults/calloutStyles.test.js +30 -0
- package/dist/__tests__/defaults/calloutStyles.test.js.map +1 -1
- package/dist/__tests__/defaults/headingsBreakBefore.test.d.ts +2 -0
- package/dist/__tests__/defaults/headingsBreakBefore.test.d.ts.map +1 -0
- package/dist/__tests__/defaults/headingsBreakBefore.test.js +78 -0
- package/dist/__tests__/defaults/headingsBreakBefore.test.js.map +1 -0
- package/dist/__tests__/defaults/paragraphStyles.test.js +22 -0
- package/dist/__tests__/defaults/paragraphStyles.test.js.map +1 -1
- package/dist/__tests__/design/bleedClip.test.d.ts +2 -0
- package/dist/__tests__/design/bleedClip.test.d.ts.map +1 -0
- package/dist/__tests__/design/bleedClip.test.js +91 -0
- package/dist/__tests__/design/bleedClip.test.js.map +1 -0
- package/dist/__tests__/design/boxBorderInside.test.d.ts +2 -0
- package/dist/__tests__/design/boxBorderInside.test.d.ts.map +1 -0
- package/dist/__tests__/design/boxBorderInside.test.js +102 -0
- package/dist/__tests__/design/boxBorderInside.test.js.map +1 -0
- package/dist/__tests__/design/designLineHeight.test.d.ts +2 -0
- package/dist/__tests__/design/designLineHeight.test.d.ts.map +1 -0
- package/dist/__tests__/design/designLineHeight.test.js +108 -0
- package/dist/__tests__/design/designLineHeight.test.js.map +1 -0
- package/dist/__tests__/design/designText.test.d.ts +2 -0
- package/dist/__tests__/design/designText.test.d.ts.map +1 -0
- package/dist/__tests__/design/designText.test.js +331 -0
- package/dist/__tests__/design/designText.test.js.map +1 -0
- package/dist/__tests__/design/designTextJustify.test.d.ts +2 -0
- package/dist/__tests__/design/designTextJustify.test.d.ts.map +1 -0
- package/dist/__tests__/design/designTextJustify.test.js +119 -0
- package/dist/__tests__/design/designTextJustify.test.js.map +1 -0
- package/dist/__tests__/design/designTextNegativeTracking.test.d.ts +2 -0
- package/dist/__tests__/design/designTextNegativeTracking.test.d.ts.map +1 -0
- package/dist/__tests__/design/designTextNegativeTracking.test.js +95 -0
- package/dist/__tests__/design/designTextNegativeTracking.test.js.map +1 -0
- package/dist/__tests__/design/designTextRender.test.d.ts +2 -0
- package/dist/__tests__/design/designTextRender.test.d.ts.map +1 -0
- package/dist/__tests__/design/designTextRender.test.js +106 -0
- package/dist/__tests__/design/designTextRender.test.js.map +1 -0
- package/dist/__tests__/design/dropCap.test.d.ts +2 -0
- package/dist/__tests__/design/dropCap.test.d.ts.map +1 -0
- package/dist/__tests__/design/dropCap.test.js +205 -0
- package/dist/__tests__/design/dropCap.test.js.map +1 -0
- package/dist/__tests__/design/ellipsis.test.d.ts +2 -0
- package/dist/__tests__/design/ellipsis.test.d.ts.map +1 -0
- package/dist/__tests__/design/ellipsis.test.js +113 -0
- package/dist/__tests__/design/ellipsis.test.js.map +1 -0
- package/dist/__tests__/design/headingDesignClip.test.d.ts +2 -0
- package/dist/__tests__/design/headingDesignClip.test.d.ts.map +1 -0
- package/dist/__tests__/design/headingDesignClip.test.js +60 -0
- package/dist/__tests__/design/headingDesignClip.test.js.map +1 -0
- package/dist/__tests__/design/headingDesignSideChannel.test.d.ts +2 -0
- package/dist/__tests__/design/headingDesignSideChannel.test.d.ts.map +1 -0
- package/dist/__tests__/design/headingDesignSideChannel.test.js +187 -0
- package/dist/__tests__/design/headingDesignSideChannel.test.js.map +1 -0
- package/dist/__tests__/design/inColumnTitleBreak.test.d.ts +2 -0
- package/dist/__tests__/design/inColumnTitleBreak.test.d.ts.map +1 -0
- package/dist/__tests__/design/inColumnTitleBreak.test.js +60 -0
- package/dist/__tests__/design/inColumnTitleBreak.test.js.map +1 -0
- package/dist/__tests__/design/openerBottomAnchor.test.d.ts +2 -0
- package/dist/__tests__/design/openerBottomAnchor.test.d.ts.map +1 -0
- package/dist/__tests__/design/openerBottomAnchor.test.js +106 -0
- package/dist/__tests__/design/openerBottomAnchor.test.js.map +1 -0
- package/dist/__tests__/design/openerChapterNumber.test.d.ts +2 -0
- package/dist/__tests__/design/openerChapterNumber.test.d.ts.map +1 -0
- package/dist/__tests__/design/openerChapterNumber.test.js +163 -0
- package/dist/__tests__/design/openerChapterNumber.test.js.map +1 -0
- package/dist/__tests__/design/openerImageReserve.test.d.ts +2 -0
- package/dist/__tests__/design/openerImageReserve.test.d.ts.map +1 -0
- package/dist/__tests__/design/openerImageReserve.test.js +83 -0
- package/dist/__tests__/design/openerImageReserve.test.js.map +1 -0
- package/dist/__tests__/design/openerReserve.test.d.ts +2 -0
- package/dist/__tests__/design/openerReserve.test.d.ts.map +1 -0
- package/dist/__tests__/design/openerReserve.test.js +147 -0
- package/dist/__tests__/design/openerReserve.test.js.map +1 -0
- package/dist/__tests__/design/ruleDefaults.test.d.ts +2 -0
- package/dist/__tests__/design/ruleDefaults.test.d.ts.map +1 -0
- package/dist/__tests__/design/ruleDefaults.test.js +59 -0
- package/dist/__tests__/design/ruleDefaults.test.js.map +1 -0
- package/dist/__tests__/design/runningHeads.test.js +4 -2
- package/dist/__tests__/design/runningHeads.test.js.map +1 -1
- package/dist/__tests__/design/titleTextWrapped.test.d.ts +2 -0
- package/dist/__tests__/design/titleTextWrapped.test.d.ts.map +1 -0
- package/dist/__tests__/design/titleTextWrapped.test.js +258 -0
- package/dist/__tests__/design/titleTextWrapped.test.js.map +1 -0
- package/dist/__tests__/design/trackingAlignment.test.d.ts +2 -0
- package/dist/__tests__/design/trackingAlignment.test.d.ts.map +1 -0
- package/dist/__tests__/design/trackingAlignment.test.js +101 -0
- package/dist/__tests__/design/trackingAlignment.test.js.map +1 -0
- package/dist/__tests__/design/verticalAlignOverflow.test.d.ts +2 -0
- package/dist/__tests__/design/verticalAlignOverflow.test.d.ts.map +1 -0
- package/dist/__tests__/design/verticalAlignOverflow.test.js +68 -0
- package/dist/__tests__/design/verticalAlignOverflow.test.js.map +1 -0
- package/dist/__tests__/exports.test.js +24 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/fontStack.test.d.ts +2 -0
- package/dist/__tests__/fontStack.test.d.ts.map +1 -0
- package/dist/__tests__/fontStack.test.js +100 -0
- package/dist/__tests__/fontStack.test.js.map +1 -0
- package/dist/__tests__/frontmatterValues.test.d.ts +2 -0
- package/dist/__tests__/frontmatterValues.test.d.ts.map +1 -0
- package/dist/__tests__/frontmatterValues.test.js +119 -0
- package/dist/__tests__/frontmatterValues.test.js.map +1 -0
- package/dist/__tests__/gradedRunt.test.d.ts +2 -0
- package/dist/__tests__/gradedRunt.test.d.ts.map +1 -0
- package/dist/__tests__/gradedRunt.test.js +66 -0
- package/dist/__tests__/gradedRunt.test.js.map +1 -0
- package/dist/__tests__/headingAlign.test.d.ts +2 -0
- package/dist/__tests__/headingAlign.test.d.ts.map +1 -0
- package/dist/__tests__/headingAlign.test.js +92 -0
- package/dist/__tests__/headingAlign.test.js.map +1 -0
- package/dist/__tests__/headingInlineMarks.test.d.ts +2 -0
- package/dist/__tests__/headingInlineMarks.test.d.ts.map +1 -0
- package/dist/__tests__/headingInlineMarks.test.js +126 -0
- package/dist/__tests__/headingInlineMarks.test.js.map +1 -0
- package/dist/__tests__/headingLetterSpacing.test.d.ts +2 -0
- package/dist/__tests__/headingLetterSpacing.test.d.ts.map +1 -0
- package/dist/__tests__/headingLetterSpacing.test.js +135 -0
- package/dist/__tests__/headingLetterSpacing.test.js.map +1 -0
- package/dist/__tests__/headingNumberPlaceholders.test.d.ts +2 -0
- package/dist/__tests__/headingNumberPlaceholders.test.d.ts.map +1 -0
- package/dist/__tests__/headingNumberPlaceholders.test.js +104 -0
- package/dist/__tests__/headingNumberPlaceholders.test.js.map +1 -0
- package/dist/__tests__/headingNumberingStyles.test.d.ts +2 -0
- package/dist/__tests__/headingNumberingStyles.test.d.ts.map +1 -0
- package/dist/__tests__/headingNumberingStyles.test.js +151 -0
- package/dist/__tests__/headingNumberingStyles.test.js.map +1 -0
- package/dist/__tests__/headingSourceTitle.test.d.ts +2 -0
- package/dist/__tests__/headingSourceTitle.test.d.ts.map +1 -0
- package/dist/__tests__/headingSourceTitle.test.js +35 -0
- package/dist/__tests__/headingSourceTitle.test.js.map +1 -0
- package/dist/__tests__/headingStyleBreakAfterPagebreak.test.d.ts +2 -0
- package/dist/__tests__/headingStyleBreakAfterPagebreak.test.d.ts.map +1 -0
- package/dist/__tests__/headingStyleBreakAfterPagebreak.test.js +52 -0
- package/dist/__tests__/headingStyleBreakAfterPagebreak.test.js.map +1 -0
- package/dist/__tests__/hiddenHeadings.test.d.ts +2 -0
- package/dist/__tests__/hiddenHeadings.test.d.ts.map +1 -0
- package/dist/__tests__/hiddenHeadings.test.js +137 -0
- package/dist/__tests__/hiddenHeadings.test.js.map +1 -0
- package/dist/__tests__/htmlHostReset.test.d.ts +2 -0
- package/dist/__tests__/htmlHostReset.test.d.ts.map +1 -0
- package/dist/__tests__/htmlHostReset.test.js +63 -0
- package/dist/__tests__/htmlHostReset.test.js.map +1 -0
- package/dist/__tests__/hyphenationLocale.test.d.ts +2 -0
- package/dist/__tests__/hyphenationLocale.test.d.ts.map +1 -0
- package/dist/__tests__/hyphenationLocale.test.js +143 -0
- package/dist/__tests__/hyphenationLocale.test.js.map +1 -0
- package/dist/__tests__/justifyTracking.test.d.ts +2 -0
- package/dist/__tests__/justifyTracking.test.d.ts.map +1 -0
- package/dist/__tests__/justifyTracking.test.js +217 -0
- package/dist/__tests__/justifyTracking.test.js.map +1 -0
- package/dist/__tests__/layoutWorkerClient.test.d.ts +2 -0
- package/dist/__tests__/layoutWorkerClient.test.d.ts.map +1 -0
- package/dist/__tests__/layoutWorkerClient.test.js +140 -0
- package/dist/__tests__/layoutWorkerClient.test.js.map +1 -0
- package/dist/__tests__/links.test.d.ts +2 -0
- package/dist/__tests__/links.test.d.ts.map +1 -0
- package/dist/__tests__/links.test.js +227 -0
- package/dist/__tests__/links.test.js.map +1 -0
- package/dist/__tests__/localisedStrings.test.d.ts +2 -0
- package/dist/__tests__/localisedStrings.test.d.ts.map +1 -0
- package/dist/__tests__/localisedStrings.test.js +118 -0
- package/dist/__tests__/localisedStrings.test.js.map +1 -0
- package/dist/__tests__/looseLines.test.d.ts +2 -0
- package/dist/__tests__/looseLines.test.d.ts.map +1 -0
- package/dist/__tests__/looseLines.test.js +72 -0
- package/dist/__tests__/looseLines.test.js.map +1 -0
- package/dist/__tests__/mainEntryReact.test.d.ts +2 -0
- package/dist/__tests__/mainEntryReact.test.d.ts.map +1 -0
- package/dist/__tests__/mainEntryReact.test.js +13 -0
- package/dist/__tests__/mainEntryReact.test.js.map +1 -0
- package/dist/__tests__/mathBundle.test.d.ts +2 -0
- package/dist/__tests__/mathBundle.test.d.ts.map +1 -0
- package/dist/__tests__/mathBundle.test.js +80 -0
- package/dist/__tests__/mathBundle.test.js.map +1 -0
- package/dist/__tests__/mathEngine.test.d.ts +2 -0
- package/dist/__tests__/mathEngine.test.d.ts.map +1 -0
- package/dist/__tests__/mathEngine.test.js +69 -0
- package/dist/__tests__/mathEngine.test.js.map +1 -0
- package/dist/__tests__/mathEngineInit.test.d.ts +2 -0
- package/dist/__tests__/mathEngineInit.test.d.ts.map +1 -0
- package/dist/__tests__/mathEngineInit.test.js +33 -0
- package/dist/__tests__/mathEngineInit.test.js.map +1 -0
- package/dist/__tests__/mathSize.test.d.ts +2 -0
- package/dist/__tests__/mathSize.test.d.ts.map +1 -0
- package/dist/__tests__/mathSize.test.js +168 -0
- package/dist/__tests__/mathSize.test.js.map +1 -0
- package/dist/__tests__/mathSizeMigration.test.d.ts +2 -0
- package/dist/__tests__/mathSizeMigration.test.d.ts.map +1 -0
- package/dist/__tests__/mathSizeMigration.test.js +188 -0
- package/dist/__tests__/mathSizeMigration.test.js.map +1 -0
- package/dist/__tests__/measureCache.test.js +148 -4
- package/dist/__tests__/measureCache.test.js.map +1 -1
- package/dist/__tests__/measureCacheKeepBreaks.test.d.ts +2 -0
- package/dist/__tests__/measureCacheKeepBreaks.test.d.ts.map +1 -0
- package/dist/__tests__/measureCacheKeepBreaks.test.js +59 -0
- package/dist/__tests__/measureCacheKeepBreaks.test.js.map +1 -0
- package/dist/__tests__/noBreakSpaces.test.d.ts +2 -0
- package/dist/__tests__/noBreakSpaces.test.d.ts.map +1 -0
- package/dist/__tests__/noBreakSpaces.test.js +208 -0
- package/dist/__tests__/noBreakSpaces.test.js.map +1 -0
- package/dist/__tests__/numberFormats.test.d.ts +2 -0
- package/dist/__tests__/numberFormats.test.d.ts.map +1 -0
- package/dist/__tests__/numberFormats.test.js +142 -0
- package/dist/__tests__/numberFormats.test.js.map +1 -0
- package/dist/__tests__/paletteEverywhere.test.d.ts +2 -0
- package/dist/__tests__/paletteEverywhere.test.d.ts.map +1 -0
- package/dist/__tests__/paletteEverywhere.test.js +228 -0
- package/dist/__tests__/paletteEverywhere.test.js.map +1 -0
- package/dist/__tests__/paragraphStyleIndent.test.d.ts +2 -0
- package/dist/__tests__/paragraphStyleIndent.test.d.ts.map +1 -0
- package/dist/__tests__/paragraphStyleIndent.test.js +91 -0
- package/dist/__tests__/paragraphStyleIndent.test.js.map +1 -0
- package/dist/__tests__/parse/attrQuoting.test.d.ts +2 -0
- package/dist/__tests__/parse/attrQuoting.test.d.ts.map +1 -0
- package/dist/__tests__/parse/attrQuoting.test.js +87 -0
- package/dist/__tests__/parse/attrQuoting.test.js.map +1 -0
- package/dist/__tests__/parse/codeSpans.test.d.ts +2 -0
- package/dist/__tests__/parse/codeSpans.test.d.ts.map +1 -0
- package/dist/__tests__/parse/codeSpans.test.js +31 -0
- package/dist/__tests__/parse/codeSpans.test.js.map +1 -0
- package/dist/__tests__/parse/intrawordUnderscore.test.d.ts +2 -0
- package/dist/__tests__/parse/intrawordUnderscore.test.d.ts.map +1 -0
- package/dist/__tests__/parse/intrawordUnderscore.test.js +69 -0
- package/dist/__tests__/parse/intrawordUnderscore.test.js.map +1 -0
- package/dist/__tests__/parse/scripts.test.js +4 -1
- package/dist/__tests__/parse/scripts.test.js.map +1 -1
- package/dist/__tests__/parts.test.js +103 -0
- package/dist/__tests__/parts.test.js.map +1 -1
- package/dist/__tests__/pipeline/balancingRecord.test.d.ts +2 -0
- package/dist/__tests__/pipeline/balancingRecord.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/balancingRecord.test.js +176 -0
- package/dist/__tests__/pipeline/balancingRecord.test.js.map +1 -0
- package/dist/__tests__/pipeline/blockquoteStyle.test.d.ts +2 -0
- package/dist/__tests__/pipeline/blockquoteStyle.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/blockquoteStyle.test.js +99 -0
- package/dist/__tests__/pipeline/blockquoteStyle.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutContinuation.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutContinuation.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutContinuation.test.js +133 -0
- package/dist/__tests__/pipeline/calloutContinuation.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutContinuationInset.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutContinuationInset.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutContinuationInset.test.js +116 -0
- package/dist/__tests__/pipeline/calloutContinuationInset.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutCornerWideIcon.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutCornerWideIcon.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutCornerWideIcon.test.js +69 -0
- package/dist/__tests__/pipeline/calloutCornerWideIcon.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutFloatMargins.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutFloatMargins.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutFloatMargins.test.js +104 -0
- package/dist/__tests__/pipeline/calloutFloatMargins.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +148 -0
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutListColor.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutListColor.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutListColor.test.js +86 -0
- package/dist/__tests__/pipeline/calloutListColor.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutRaggedLines.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutRaggedLines.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutRaggedLines.test.js +65 -0
- package/dist/__tests__/pipeline/calloutRaggedLines.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutRuntTracking.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutRuntTracking.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutRuntTracking.test.js +84 -0
- package/dist/__tests__/pipeline/calloutRuntTracking.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +187 -0
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutStripeRadius.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutStripeRadius.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutStripeRadius.test.js +127 -0
- package/dist/__tests__/pipeline/calloutStripeRadius.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutTitleLineHeight.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutTitleLineHeight.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutTitleLineHeight.test.js +81 -0
- package/dist/__tests__/pipeline/calloutTitleLineHeight.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutTypography.test.d.ts +2 -0
- package/dist/__tests__/pipeline/calloutTypography.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/calloutTypography.test.js +97 -0
- package/dist/__tests__/pipeline/calloutTypography.test.js.map +1 -0
- package/dist/__tests__/pipeline/capUnderHeadFloats.test.d.ts +2 -0
- package/dist/__tests__/pipeline/capUnderHeadFloats.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/capUnderHeadFloats.test.js +68 -0
- package/dist/__tests__/pipeline/capUnderHeadFloats.test.js.map +1 -0
- package/dist/__tests__/pipeline/captionAlign.test.d.ts +2 -0
- package/dist/__tests__/pipeline/captionAlign.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/captionAlign.test.js +99 -0
- package/dist/__tests__/pipeline/captionAlign.test.js.map +1 -0
- package/dist/__tests__/pipeline/chipPalette.test.d.ts +2 -0
- package/dist/__tests__/pipeline/chipPalette.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/chipPalette.test.js +80 -0
- package/dist/__tests__/pipeline/chipPalette.test.js.map +1 -0
- package/dist/__tests__/pipeline/chipVerticalPadding.test.d.ts +2 -0
- package/dist/__tests__/pipeline/chipVerticalPadding.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/chipVerticalPadding.test.js +66 -0
- package/dist/__tests__/pipeline/chipVerticalPadding.test.js.map +1 -0
- package/dist/__tests__/pipeline/chips.test.js +32 -0
- package/dist/__tests__/pipeline/chips.test.js.map +1 -1
- package/dist/__tests__/pipeline/closingBoxLever.test.d.ts +2 -0
- package/dist/__tests__/pipeline/closingBoxLever.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/closingBoxLever.test.js +68 -0
- package/dist/__tests__/pipeline/closingBoxLever.test.js.map +1 -0
- package/dist/__tests__/pipeline/closingBoxOrder.test.d.ts +2 -0
- package/dist/__tests__/pipeline/closingBoxOrder.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/closingBoxOrder.test.js +87 -0
- package/dist/__tests__/pipeline/closingBoxOrder.test.js.map +1 -0
- package/dist/__tests__/pipeline/closingPageAfterFloat.test.d.ts +2 -0
- package/dist/__tests__/pipeline/closingPageAfterFloat.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/closingPageAfterFloat.test.js +159 -0
- package/dist/__tests__/pipeline/closingPageAfterFloat.test.js.map +1 -0
- package/dist/__tests__/pipeline/closingPageAfterFloatPage.test.d.ts +2 -0
- package/dist/__tests__/pipeline/closingPageAfterFloatPage.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/closingPageAfterFloatPage.test.js +90 -0
- package/dist/__tests__/pipeline/closingPageAfterFloatPage.test.js.map +1 -0
- package/dist/__tests__/pipeline/colonBalancing.test.js +19 -15
- package/dist/__tests__/pipeline/colonBalancing.test.js.map +1 -1
- package/dist/__tests__/pipeline/colonListRoom.test.d.ts +2 -0
- package/dist/__tests__/pipeline/colonListRoom.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/colonListRoom.test.js +127 -0
- package/dist/__tests__/pipeline/colonListRoom.test.js.map +1 -0
- package/dist/__tests__/pipeline/columnWidthRebreak.test.d.ts +2 -0
- package/dist/__tests__/pipeline/columnWidthRebreak.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/columnWidthRebreak.test.js +124 -0
- package/dist/__tests__/pipeline/columnWidthRebreak.test.js.map +1 -0
- package/dist/__tests__/pipeline/compoundBreaks.test.d.ts +2 -0
- package/dist/__tests__/pipeline/compoundBreaks.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/compoundBreaks.test.js +368 -0
- package/dist/__tests__/pipeline/compoundBreaks.test.js.map +1 -0
- package/dist/__tests__/pipeline/continuationUnderFloat.test.d.ts +2 -0
- package/dist/__tests__/pipeline/continuationUnderFloat.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/continuationUnderFloat.test.js +64 -0
- package/dist/__tests__/pipeline/continuationUnderFloat.test.js.map +1 -0
- package/dist/__tests__/pipeline/crossChapterRef.test.d.ts +2 -0
- package/dist/__tests__/pipeline/crossChapterRef.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/crossChapterRef.test.js +124 -0
- package/dist/__tests__/pipeline/crossChapterRef.test.js.map +1 -0
- package/dist/__tests__/pipeline/dashBreaks.test.d.ts +2 -0
- package/dist/__tests__/pipeline/dashBreaks.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/dashBreaks.test.js +358 -0
- package/dist/__tests__/pipeline/dashBreaks.test.js.map +1 -0
- package/dist/__tests__/pipeline/defaultOpenerPaint.test.d.ts +2 -0
- package/dist/__tests__/pipeline/defaultOpenerPaint.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/defaultOpenerPaint.test.js +128 -0
- package/dist/__tests__/pipeline/defaultOpenerPaint.test.js.map +1 -0
- package/dist/__tests__/pipeline/displayMathFlow.test.d.ts +2 -0
- package/dist/__tests__/pipeline/displayMathFlow.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/displayMathFlow.test.js +156 -0
- package/dist/__tests__/pipeline/displayMathFlow.test.js.map +1 -0
- package/dist/__tests__/pipeline/emptyResourceTemplate.test.d.ts +2 -0
- package/dist/__tests__/pipeline/emptyResourceTemplate.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/emptyResourceTemplate.test.js +66 -0
- package/dist/__tests__/pipeline/emptyResourceTemplate.test.js.map +1 -0
- package/dist/__tests__/pipeline/figureBodyAlign.test.d.ts +2 -0
- package/dist/__tests__/pipeline/figureBodyAlign.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/figureBodyAlign.test.js +92 -0
- package/dist/__tests__/pipeline/figureBodyAlign.test.js.map +1 -0
- package/dist/__tests__/pipeline/floatAfterCitingLine.test.d.ts +2 -0
- package/dist/__tests__/pipeline/floatAfterCitingLine.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/floatAfterCitingLine.test.js +217 -0
- package/dist/__tests__/pipeline/floatAfterCitingLine.test.js.map +1 -0
- package/dist/__tests__/pipeline/floatFirstSlot.test.js +15 -0
- package/dist/__tests__/pipeline/floatFirstSlot.test.js.map +1 -1
- package/dist/__tests__/pipeline/floatOverInlineResource.test.d.ts +2 -0
- package/dist/__tests__/pipeline/floatOverInlineResource.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/floatOverInlineResource.test.js +75 -0
- package/dist/__tests__/pipeline/floatOverInlineResource.test.js.map +1 -0
- package/dist/__tests__/pipeline/floatPlacementRules.test.d.ts +2 -0
- package/dist/__tests__/pipeline/floatPlacementRules.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/floatPlacementRules.test.js +177 -0
- package/dist/__tests__/pipeline/floatPlacementRules.test.js.map +1 -0
- package/dist/__tests__/pipeline/floatSlots.test.js +35 -1
- package/dist/__tests__/pipeline/floatSlots.test.js.map +1 -1
- package/dist/__tests__/pipeline/hardHyphenBreaks.test.d.ts +2 -0
- package/dist/__tests__/pipeline/hardHyphenBreaks.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/hardHyphenBreaks.test.js +165 -0
- package/dist/__tests__/pipeline/hardHyphenBreaks.test.js.map +1 -0
- package/dist/__tests__/pipeline/headingDesignCuts.test.d.ts +2 -0
- package/dist/__tests__/pipeline/headingDesignCuts.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/headingDesignCuts.test.js +96 -0
- package/dist/__tests__/pipeline/headingDesignCuts.test.js.map +1 -0
- package/dist/__tests__/pipeline/headingSnapPerLevel.test.d.ts +2 -0
- package/dist/__tests__/pipeline/headingSnapPerLevel.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/headingSnapPerLevel.test.js +97 -0
- package/dist/__tests__/pipeline/headingSnapPerLevel.test.js.map +1 -0
- package/dist/__tests__/pipeline/headingSplitOrphans.test.d.ts +2 -0
- package/dist/__tests__/pipeline/headingSplitOrphans.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/headingSplitOrphans.test.js +134 -0
- package/dist/__tests__/pipeline/headingSplitOrphans.test.js.map +1 -0
- package/dist/__tests__/pipeline/hyphenAcrossColumns.test.d.ts +2 -0
- package/dist/__tests__/pipeline/hyphenAcrossColumns.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/hyphenAcrossColumns.test.js +232 -0
- package/dist/__tests__/pipeline/hyphenAcrossColumns.test.js.map +1 -0
- package/dist/__tests__/pipeline/indentAfterOutOfFlow.test.d.ts +2 -0
- package/dist/__tests__/pipeline/indentAfterOutOfFlow.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/indentAfterOutOfFlow.test.js +93 -0
- package/dist/__tests__/pipeline/indentAfterOutOfFlow.test.js.map +1 -0
- package/dist/__tests__/pipeline/inlineFigureSpace.test.d.ts +2 -0
- package/dist/__tests__/pipeline/inlineFigureSpace.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/inlineFigureSpace.test.js +195 -0
- package/dist/__tests__/pipeline/inlineFigureSpace.test.js.map +1 -0
- package/dist/__tests__/pipeline/lineSourceEdges.test.d.ts +2 -0
- package/dist/__tests__/pipeline/lineSourceEdges.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/lineSourceEdges.test.js +143 -0
- package/dist/__tests__/pipeline/lineSourceEdges.test.js.map +1 -0
- package/dist/__tests__/pipeline/lineSourceRanges.test.d.ts +2 -0
- package/dist/__tests__/pipeline/lineSourceRanges.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/lineSourceRanges.test.js +96 -0
- package/dist/__tests__/pipeline/lineSourceRanges.test.js.map +1 -0
- package/dist/__tests__/pipeline/listTopSnap.test.d.ts +2 -0
- package/dist/__tests__/pipeline/listTopSnap.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/listTopSnap.test.js +90 -0
- package/dist/__tests__/pipeline/listTopSnap.test.js.map +1 -0
- package/dist/__tests__/pipeline/looseParagraphLimit.test.d.ts +2 -0
- package/dist/__tests__/pipeline/looseParagraphLimit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/looseParagraphLimit.test.js +63 -0
- package/dist/__tests__/pipeline/looseParagraphLimit.test.js.map +1 -0
- package/dist/__tests__/pipeline/nestedListSpacing.test.d.ts +2 -0
- package/dist/__tests__/pipeline/nestedListSpacing.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/nestedListSpacing.test.js +62 -0
- package/dist/__tests__/pipeline/nestedListSpacing.test.js.map +1 -0
- package/dist/__tests__/pipeline/openerColumnStarts.test.d.ts +2 -0
- package/dist/__tests__/pipeline/openerColumnStarts.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/openerColumnStarts.test.js +189 -0
- package/dist/__tests__/pipeline/openerColumnStarts.test.js.map +1 -0
- package/dist/__tests__/pipeline/openerFillsPage.test.d.ts +2 -0
- package/dist/__tests__/pipeline/openerFillsPage.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/openerFillsPage.test.js +133 -0
- package/dist/__tests__/pipeline/openerFillsPage.test.js.map +1 -0
- package/dist/__tests__/pipeline/openerTallerThanColumn.test.d.ts +2 -0
- package/dist/__tests__/pipeline/openerTallerThanColumn.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/openerTallerThanColumn.test.js +139 -0
- package/dist/__tests__/pipeline/openerTallerThanColumn.test.js.map +1 -0
- package/dist/__tests__/pipeline/orderedNumberWidth.test.d.ts +2 -0
- package/dist/__tests__/pipeline/orderedNumberWidth.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/orderedNumberWidth.test.js +69 -0
- package/dist/__tests__/pipeline/orderedNumberWidth.test.js.map +1 -0
- package/dist/__tests__/pipeline/pageSpanHeadingWidth.test.d.ts +2 -0
- package/dist/__tests__/pipeline/pageSpanHeadingWidth.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/pageSpanHeadingWidth.test.js +74 -0
- package/dist/__tests__/pipeline/pageSpanHeadingWidth.test.js.map +1 -0
- package/dist/__tests__/pipeline/paragraphContainerTail.test.d.ts +2 -0
- package/dist/__tests__/pipeline/paragraphContainerTail.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/paragraphContainerTail.test.js +218 -0
- package/dist/__tests__/pipeline/paragraphContainerTail.test.js.map +1 -0
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.d.ts +2 -0
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.js +121 -0
- package/dist/__tests__/pipeline/paragraphStyleFaces.test.js.map +1 -0
- package/dist/__tests__/pipeline/paragraphStyles.test.js +85 -0
- package/dist/__tests__/pipeline/paragraphStyles.test.js.map +1 -1
- package/dist/__tests__/pipeline/raggedOptimal.test.d.ts +2 -0
- package/dist/__tests__/pipeline/raggedOptimal.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/raggedOptimal.test.js +231 -0
- package/dist/__tests__/pipeline/raggedOptimal.test.js.map +1 -0
- package/dist/__tests__/pipeline/resolvedPaletteCopy.test.d.ts +2 -0
- package/dist/__tests__/pipeline/resolvedPaletteCopy.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/resolvedPaletteCopy.test.js +68 -0
- package/dist/__tests__/pipeline/resolvedPaletteCopy.test.js.map +1 -0
- package/dist/__tests__/pipeline/resourceNumberingRules.test.d.ts +2 -0
- package/dist/__tests__/pipeline/resourceNumberingRules.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/resourceNumberingRules.test.js +103 -0
- package/dist/__tests__/pipeline/resourceNumberingRules.test.js.map +1 -0
- package/dist/__tests__/pipeline/sectionPageOwnership.test.d.ts +2 -0
- package/dist/__tests__/pipeline/sectionPageOwnership.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/sectionPageOwnership.test.js +89 -0
- package/dist/__tests__/pipeline/sectionPageOwnership.test.js.map +1 -0
- package/dist/__tests__/pipeline/sectionPaletteSharedBase.test.d.ts +2 -0
- package/dist/__tests__/pipeline/sectionPaletteSharedBase.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/sectionPaletteSharedBase.test.js +278 -0
- package/dist/__tests__/pipeline/sectionPaletteSharedBase.test.js.map +1 -0
- package/dist/__tests__/pipeline/sectionPaletteTables.test.d.ts +2 -0
- package/dist/__tests__/pipeline/sectionPaletteTables.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/sectionPaletteTables.test.js +104 -0
- package/dist/__tests__/pipeline/sectionPaletteTables.test.js.map +1 -0
- package/dist/__tests__/pipeline/sideBoxAtColumnEnd.test.d.ts +2 -0
- package/dist/__tests__/pipeline/sideBoxAtColumnEnd.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/sideBoxAtColumnEnd.test.js +176 -0
- package/dist/__tests__/pipeline/sideBoxAtColumnEnd.test.js.map +1 -0
- package/dist/__tests__/pipeline/sideColumnPercent.test.d.ts +2 -0
- package/dist/__tests__/pipeline/sideColumnPercent.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/sideColumnPercent.test.js +114 -0
- package/dist/__tests__/pipeline/sideColumnPercent.test.js.map +1 -0
- package/dist/__tests__/pipeline/snippetDollarEscape.test.d.ts +2 -0
- package/dist/__tests__/pipeline/snippetDollarEscape.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/snippetDollarEscape.test.js +112 -0
- package/dist/__tests__/pipeline/snippetDollarEscape.test.js.map +1 -0
- package/dist/__tests__/pipeline/snippetLineBreaks.test.d.ts +2 -0
- package/dist/__tests__/pipeline/snippetLineBreaks.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/snippetLineBreaks.test.js +178 -0
- package/dist/__tests__/pipeline/snippetLineBreaks.test.js.map +1 -0
- package/dist/__tests__/pipeline/spanCutUnderColumnFloat.test.d.ts +2 -0
- package/dist/__tests__/pipeline/spanCutUnderColumnFloat.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/spanCutUnderColumnFloat.test.js +90 -0
- package/dist/__tests__/pipeline/spanCutUnderColumnFloat.test.js.map +1 -0
- package/dist/__tests__/pipeline/splitBoxCappedBand.test.d.ts +2 -0
- package/dist/__tests__/pipeline/splitBoxCappedBand.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/splitBoxCappedBand.test.js +64 -0
- package/dist/__tests__/pipeline/splitBoxCappedBand.test.js.map +1 -0
- package/dist/__tests__/pipeline/tableCellNoBreakSpaces.test.d.ts +2 -0
- package/dist/__tests__/pipeline/tableCellNoBreakSpaces.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/tableCellNoBreakSpaces.test.js +97 -0
- package/dist/__tests__/pipeline/tableCellNoBreakSpaces.test.js.map +1 -0
- package/dist/__tests__/pipeline/tableHeaderTracking.test.d.ts +2 -0
- package/dist/__tests__/pipeline/tableHeaderTracking.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/tableHeaderTracking.test.js +139 -0
- package/dist/__tests__/pipeline/tableHeaderTracking.test.js.map +1 -0
- package/dist/__tests__/pipeline/tableSplitTextRoom.test.d.ts +2 -0
- package/dist/__tests__/pipeline/tableSplitTextRoom.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/tableSplitTextRoom.test.js +90 -0
- package/dist/__tests__/pipeline/tableSplitTextRoom.test.js.map +1 -0
- package/dist/__tests__/pipeline/tableStyles.test.js +94 -1
- package/dist/__tests__/pipeline/tableStyles.test.js.map +1 -1
- package/dist/__tests__/pipeline/tallParagraphShortColumn.test.d.ts +2 -0
- package/dist/__tests__/pipeline/tallParagraphShortColumn.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/tallParagraphShortColumn.test.js +79 -0
- package/dist/__tests__/pipeline/tallParagraphShortColumn.test.js.map +1 -0
- package/dist/__tests__/pipeline/textFaces.test.d.ts +2 -0
- package/dist/__tests__/pipeline/textFaces.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/textFaces.test.js +258 -0
- package/dist/__tests__/pipeline/textFaces.test.js.map +1 -0
- package/dist/__tests__/pipeline/trailingBand.test.js +29 -0
- package/dist/__tests__/pipeline/trailingBand.test.js.map +1 -1
- package/dist/__tests__/pipeline/trailingCapHeading.test.d.ts +2 -0
- package/dist/__tests__/pipeline/trailingCapHeading.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/trailingCapHeading.test.js +69 -0
- package/dist/__tests__/pipeline/trailingCapHeading.test.js.map +1 -0
- package/dist/__tests__/pipeline/trailingCapOverflow.test.d.ts +2 -0
- package/dist/__tests__/pipeline/trailingCapOverflow.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/trailingCapOverflow.test.js +116 -0
- package/dist/__tests__/pipeline/trailingCapOverflow.test.js.map +1 -0
- package/dist/__tests__/placeholders.test.js +100 -2
- package/dist/__tests__/placeholders.test.js.map +1 -1
- package/dist/__tests__/punctuationGlue.test.d.ts +2 -0
- package/dist/__tests__/punctuationGlue.test.d.ts.map +1 -0
- package/dist/__tests__/punctuationGlue.test.js +165 -0
- package/dist/__tests__/punctuationGlue.test.js.map +1 -0
- package/dist/__tests__/raggedHyphenation.test.d.ts +2 -0
- package/dist/__tests__/raggedHyphenation.test.d.ts.map +1 -0
- package/dist/__tests__/raggedHyphenation.test.js +184 -0
- package/dist/__tests__/raggedHyphenation.test.js.map +1 -0
- package/dist/__tests__/renderWarnings.test.d.ts +2 -0
- package/dist/__tests__/renderWarnings.test.d.ts.map +1 -0
- package/dist/__tests__/renderWarnings.test.js +72 -0
- package/dist/__tests__/renderWarnings.test.js.map +1 -0
- package/dist/__tests__/resolvedLocale.test.d.ts +2 -0
- package/dist/__tests__/resolvedLocale.test.d.ts.map +1 -0
- package/dist/__tests__/resolvedLocale.test.js +51 -0
- package/dist/__tests__/resolvedLocale.test.js.map +1 -0
- package/dist/__tests__/runningChapter.test.d.ts +2 -0
- package/dist/__tests__/runningChapter.test.d.ts.map +1 -0
- package/dist/__tests__/runningChapter.test.js +161 -0
- package/dist/__tests__/runningChapter.test.js.map +1 -0
- package/dist/__tests__/runtFixStretchLimit.test.d.ts +2 -0
- package/dist/__tests__/runtFixStretchLimit.test.d.ts.map +1 -0
- package/dist/__tests__/runtFixStretchLimit.test.js +145 -0
- package/dist/__tests__/runtFixStretchLimit.test.js.map +1 -0
- package/dist/__tests__/scriptStacking.test.d.ts +2 -0
- package/dist/__tests__/scriptStacking.test.d.ts.map +1 -0
- package/dist/__tests__/scriptStacking.test.js +331 -0
- package/dist/__tests__/scriptStacking.test.js.map +1 -0
- package/dist/__tests__/sectionPaletteFlow.test.d.ts +2 -0
- package/dist/__tests__/sectionPaletteFlow.test.d.ts.map +1 -0
- package/dist/__tests__/sectionPaletteFlow.test.js +51 -0
- package/dist/__tests__/sectionPaletteFlow.test.js.map +1 -0
- package/dist/__tests__/singleInk.test.js +64 -2
- package/dist/__tests__/singleInk.test.js.map +1 -1
- package/dist/__tests__/singleInkRender.test.d.ts +2 -0
- package/dist/__tests__/singleInkRender.test.d.ts.map +1 -0
- package/dist/__tests__/singleInkRender.test.js +396 -0
- package/dist/__tests__/singleInkRender.test.js.map +1 -0
- package/dist/__tests__/spaceDirective.test.js +75 -0
- package/dist/__tests__/spaceDirective.test.js.map +1 -1
- package/dist/__tests__/table/model.test.js +124 -1
- package/dist/__tests__/table/model.test.js.map +1 -1
- package/dist/__tests__/tableFillSeams.test.d.ts +2 -0
- package/dist/__tests__/tableFillSeams.test.d.ts.map +1 -0
- package/dist/__tests__/tableFillSeams.test.js +180 -0
- package/dist/__tests__/tableFillSeams.test.js.map +1 -0
- package/dist/__tests__/toc.test.js +23 -2
- package/dist/__tests__/toc.test.js.map +1 -1
- package/dist/__tests__/tocLeaderKerning.test.d.ts +2 -0
- package/dist/__tests__/tocLeaderKerning.test.d.ts.map +1 -0
- package/dist/__tests__/tocLeaderKerning.test.js +83 -0
- package/dist/__tests__/tocLeaderKerning.test.js.map +1 -0
- package/dist/__tests__/tocNumberBaseline.test.d.ts +2 -0
- package/dist/__tests__/tocNumberBaseline.test.d.ts.map +1 -0
- package/dist/__tests__/tocNumberBaseline.test.js +90 -0
- package/dist/__tests__/tocNumberBaseline.test.js.map +1 -0
- package/dist/__tests__/tocPartsHeight.test.d.ts +2 -0
- package/dist/__tests__/tocPartsHeight.test.d.ts.map +1 -0
- package/dist/__tests__/tocPartsHeight.test.js +41 -0
- package/dist/__tests__/tocPartsHeight.test.js.map +1 -0
- package/dist/__tests__/trackedLineAlign.test.d.ts +2 -0
- package/dist/__tests__/trackedLineAlign.test.d.ts.map +1 -0
- package/dist/__tests__/trackedLineAlign.test.js +96 -0
- package/dist/__tests__/trackedLineAlign.test.js.map +1 -0
- package/dist/bundle/adapters.d.ts +10 -3
- package/dist/bundle/adapters.d.ts.map +1 -1
- package/dist/bundle/adapters.js +13 -5
- package/dist/bundle/adapters.js.map +1 -1
- package/dist/bundle/api.d.ts +32 -0
- package/dist/bundle/api.d.ts.map +1 -1
- package/dist/bundle/api.js +12 -2
- package/dist/bundle/api.js.map +1 -1
- package/dist/bundle/book.d.ts +6 -1
- package/dist/bundle/book.d.ts.map +1 -1
- package/dist/bundle/book.js +89 -17
- package/dist/bundle/book.js.map +1 -1
- package/dist/bundle/codec.d.ts +87 -3
- package/dist/bundle/codec.d.ts.map +1 -1
- package/dist/bundle/codec.js +193 -13
- package/dist/bundle/codec.js.map +1 -1
- package/dist/bundle/configVersion.d.ts +295 -0
- package/dist/bundle/configVersion.d.ts.map +1 -0
- package/dist/bundle/configVersion.js +788 -0
- package/dist/bundle/configVersion.js.map +1 -0
- package/dist/bundle/index.d.ts +6 -4
- package/dist/bundle/index.d.ts.map +1 -1
- package/dist/bundle/index.js +2 -1
- package/dist/bundle/index.js.map +1 -1
- package/dist/bundle/manifest.d.ts +19 -5
- package/dist/bundle/manifest.d.ts.map +1 -1
- package/dist/bundle/manifest.js +53 -21
- package/dist/bundle/manifest.js.map +1 -1
- package/dist/bundle/types.d.ts +19 -0
- package/dist/bundle/types.d.ts.map +1 -1
- package/dist/bundle/zip.d.ts +14 -1
- package/dist/bundle/zip.d.ts.map +1 -1
- package/dist/bundle/zip.js +13 -2
- package/dist/bundle/zip.js.map +1 -1
- package/dist/canvas-backend/blockRender.d.ts +3 -1
- package/dist/canvas-backend/blockRender.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.js +27 -17
- package/dist/canvas-backend/blockRender.js.map +1 -1
- package/dist/canvas-backend/decorations.d.ts.map +1 -1
- package/dist/canvas-backend/decorations.js +6 -36
- package/dist/canvas-backend/decorations.js.map +1 -1
- package/dist/canvas-backend/headerFooter.d.ts +3 -1
- package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
- package/dist/canvas-backend/headerFooter.js +85 -22
- package/dist/canvas-backend/headerFooter.js.map +1 -1
- package/dist/canvas-backend/index.d.ts +17 -3
- package/dist/canvas-backend/index.d.ts.map +1 -1
- package/dist/canvas-backend/index.js +75 -14
- package/dist/canvas-backend/index.js.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts +45 -5
- package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.js +133 -22
- package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
- package/dist/columnClip.d.ts +13 -2
- package/dist/columnClip.d.ts.map +1 -1
- package/dist/columnClip.js +37 -4
- package/dist/columnClip.js.map +1 -1
- package/dist/columnRule.d.ts +7 -2
- package/dist/columnRule.d.ts.map +1 -1
- package/dist/columnRule.js +27 -2
- package/dist/columnRule.js.map +1 -1
- package/dist/configWarnings.d.ts +26 -0
- package/dist/configWarnings.d.ts.map +1 -0
- package/dist/configWarnings.js +223 -0
- package/dist/configWarnings.js.map +1 -0
- package/dist/createLayout.d.ts +16 -1
- package/dist/createLayout.d.ts.map +1 -1
- package/dist/createLayout.js +25 -22
- package/dist/createLayout.js.map +1 -1
- package/dist/cropMarks.d.ts +28 -0
- package/dist/cropMarks.d.ts.map +1 -0
- package/dist/cropMarks.js +40 -0
- package/dist/cropMarks.js.map +1 -0
- package/dist/defaults/bodyText.d.ts +12 -2
- package/dist/defaults/bodyText.d.ts.map +1 -1
- package/dist/defaults/bodyText.js +132 -7
- package/dist/defaults/bodyText.js.map +1 -1
- package/dist/defaults/calloutStyles.d.ts +13 -2
- package/dist/defaults/calloutStyles.d.ts.map +1 -1
- package/dist/defaults/calloutStyles.js +64 -4
- package/dist/defaults/calloutStyles.js.map +1 -1
- package/dist/defaults/chipStyles.d.ts +4 -3
- package/dist/defaults/chipStyles.d.ts.map +1 -1
- package/dist/defaults/chipStyles.js +11 -3
- package/dist/defaults/chipStyles.js.map +1 -1
- package/dist/defaults/headerFooter.d.ts +11 -1
- package/dist/defaults/headerFooter.d.ts.map +1 -1
- package/dist/defaults/headerFooter.js +52 -4
- package/dist/defaults/headerFooter.js.map +1 -1
- package/dist/defaults/headingStyles.d.ts +7 -3
- package/dist/defaults/headingStyles.d.ts.map +1 -1
- package/dist/defaults/headingStyles.js +34 -6
- package/dist/defaults/headingStyles.js.map +1 -1
- package/dist/defaults/headings.d.ts +10 -2
- package/dist/defaults/headings.d.ts.map +1 -1
- package/dist/defaults/headings.js +79 -16
- package/dist/defaults/headings.js.map +1 -1
- package/dist/defaults/index.d.ts +1 -1
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +1 -1
- package/dist/defaults/index.js.map +1 -1
- package/dist/defaults/layout.d.ts.map +1 -1
- package/dist/defaults/layout.js +27 -0
- package/dist/defaults/layout.js.map +1 -1
- package/dist/defaults/lists-shared.d.ts +2 -0
- package/dist/defaults/lists-shared.d.ts.map +1 -1
- package/dist/defaults/lists-shared.js +2 -0
- package/dist/defaults/lists-shared.js.map +1 -1
- package/dist/defaults/math.d.ts.map +1 -1
- package/dist/defaults/math.js +12 -0
- package/dist/defaults/math.js.map +1 -1
- package/dist/defaults/orderedLists.d.ts +7 -1
- package/dist/defaults/orderedLists.d.ts.map +1 -1
- package/dist/defaults/orderedLists.js +30 -4
- package/dist/defaults/orderedLists.js.map +1 -1
- package/dist/defaults/page.d.ts.map +1 -1
- package/dist/defaults/page.js +7 -2
- package/dist/defaults/page.js.map +1 -1
- package/dist/defaults/paragraphStyles.d.ts +2 -1
- package/dist/defaults/paragraphStyles.d.ts.map +1 -1
- package/dist/defaults/paragraphStyles.js +26 -1
- package/dist/defaults/paragraphStyles.js.map +1 -1
- package/dist/defaults/resourceTypes.d.ts +5 -1
- package/dist/defaults/resourceTypes.d.ts.map +1 -1
- package/dist/defaults/resourceTypes.js +37 -6
- package/dist/defaults/resourceTypes.js.map +1 -1
- package/dist/defaults/shared.d.ts +10 -0
- package/dist/defaults/shared.d.ts.map +1 -1
- package/dist/defaults/shared.js +79 -0
- package/dist/defaults/shared.js.map +1 -1
- package/dist/defaults/tableStyle.d.ts +1 -1
- package/dist/defaults/tableStyle.d.ts.map +1 -1
- package/dist/defaults/tableStyle.js +40 -6
- package/dist/defaults/tableStyle.js.map +1 -1
- package/dist/defaults/toc.d.ts +2 -1
- package/dist/defaults/toc.d.ts.map +1 -1
- package/dist/defaults/toc.js +2 -1
- package/dist/defaults/toc.js.map +1 -1
- package/dist/defaults/unorderedLists.d.ts +1 -0
- package/dist/defaults/unorderedLists.d.ts.map +1 -1
- package/dist/defaults/unorderedLists.js +7 -1
- package/dist/defaults/unorderedLists.js.map +1 -1
- package/dist/design/layout.d.ts +41 -3
- package/dist/design/layout.d.ts.map +1 -1
- package/dist/design/layout.js +322 -90
- package/dist/design/layout.js.map +1 -1
- package/dist/design/placeholders.d.ts +39 -4
- package/dist/design/placeholders.d.ts.map +1 -1
- package/dist/design/placeholders.js +61 -11
- package/dist/design/placeholders.js.map +1 -1
- package/dist/design/richText.d.ts +110 -0
- package/dist/design/richText.d.ts.map +1 -0
- package/dist/design/richText.js +458 -0
- package/dist/design/richText.js.map +1 -0
- package/dist/frontmatter.d.ts +14 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +61 -0
- package/dist/frontmatter.js.map +1 -1
- package/dist/html-backend.d.ts +60 -2
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +317 -62
- package/dist/html-backend.js.map +1 -1
- package/dist/hyphenate.d.ts +30 -4
- package/dist/hyphenate.d.ts.map +1 -1
- package/dist/hyphenate.js +71 -5
- package/dist/hyphenate.js.map +1 -1
- package/dist/index.d.ts +28 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -11
- package/dist/index.js.map +1 -1
- package/dist/knuthPlass/breakpoints.d.ts +21 -0
- package/dist/knuthPlass/breakpoints.d.ts.map +1 -1
- package/dist/knuthPlass/breakpoints.js +205 -14
- package/dist/knuthPlass/breakpoints.js.map +1 -1
- package/dist/knuthPlass/constants.d.ts +15 -0
- package/dist/knuthPlass/constants.d.ts.map +1 -1
- package/dist/knuthPlass/constants.js +15 -0
- package/dist/knuthPlass/constants.js.map +1 -1
- package/dist/knuthPlass/pretextAdapter.d.ts +23 -2
- package/dist/knuthPlass/pretextAdapter.d.ts.map +1 -1
- package/dist/knuthPlass/pretextAdapter.js +95 -9
- package/dist/knuthPlass/pretextAdapter.js.map +1 -1
- package/dist/knuthPlass/richAdapter.d.ts +26 -2
- package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
- package/dist/knuthPlass/richAdapter.js +79 -18
- package/dist/knuthPlass/richAdapter.js.map +1 -1
- package/dist/knuthPlass/tracking.d.ts +24 -0
- package/dist/knuthPlass/tracking.d.ts.map +1 -0
- package/dist/knuthPlass/tracking.js +77 -0
- package/dist/knuthPlass/tracking.js.map +1 -0
- package/dist/knuthPlass/types.d.ts +66 -4
- package/dist/knuthPlass/types.d.ts.map +1 -1
- package/dist/lineInk.d.ts +12 -0
- package/dist/lineInk.d.ts.map +1 -0
- package/dist/lineInk.js +20 -0
- package/dist/lineInk.js.map +1 -0
- package/dist/locale.d.ts +26 -0
- package/dist/locale.d.ts.map +1 -0
- package/dist/locale.js +63 -0
- package/dist/locale.js.map +1 -0
- package/dist/looseLines.d.ts +44 -0
- package/dist/looseLines.d.ts.map +1 -0
- package/dist/looseLines.js +46 -0
- package/dist/looseLines.js.map +1 -0
- package/dist/math/THIRD_PARTY_LICENSES.txt +221 -0
- package/dist/math/engine.d.ts +24 -0
- package/dist/math/engine.d.ts.map +1 -1
- package/dist/math/engine.js +252 -83
- package/dist/math/engine.js.map +1 -1
- package/dist/math/index.d.ts +1 -1
- package/dist/math/index.d.ts.map +1 -1
- package/dist/math/index.js +1 -1
- package/dist/math/index.js.map +1 -1
- package/dist/math/mathjax.d.ts +20 -0
- package/dist/math/mathjax.d.ts.map +1 -0
- package/dist/math/mathjax.js +29 -0
- package/dist/measure/breakRules.d.ts +50 -0
- package/dist/measure/breakRules.d.ts.map +1 -0
- package/dist/measure/breakRules.js +116 -0
- package/dist/measure/breakRules.js.map +1 -0
- package/dist/measure/cache.d.ts.map +1 -1
- package/dist/measure/cache.js +26 -5
- package/dist/measure/cache.js.map +1 -1
- package/dist/measure/cjk.d.ts +19 -0
- package/dist/measure/cjk.d.ts.map +1 -0
- package/dist/measure/cjk.js +70 -0
- package/dist/measure/cjk.js.map +1 -0
- package/dist/measure/font.d.ts +21 -5
- package/dist/measure/font.d.ts.map +1 -1
- package/dist/measure/font.js +61 -4
- package/dist/measure/font.js.map +1 -1
- package/dist/measure/index.d.ts +16 -2
- package/dist/measure/index.d.ts.map +1 -1
- package/dist/measure/index.js +61 -1
- package/dist/measure/index.js.map +1 -1
- package/dist/measure/links.d.ts +15 -0
- package/dist/measure/links.d.ts.map +1 -0
- package/dist/measure/links.js +115 -0
- package/dist/measure/links.js.map +1 -0
- package/dist/measure/plain.d.ts +10 -2
- package/dist/measure/plain.d.ts.map +1 -1
- package/dist/measure/plain.js +175 -25
- package/dist/measure/plain.js.map +1 -1
- package/dist/measure/rich.d.ts +86 -6
- package/dist/measure/rich.d.ts.map +1 -1
- package/dist/measure/rich.js +808 -131
- package/dist/measure/rich.js.map +1 -1
- package/dist/measure/spaces.d.ts +34 -0
- package/dist/measure/spaces.d.ts.map +1 -0
- package/dist/measure/spaces.js +42 -0
- package/dist/measure/spaces.js.map +1 -0
- package/dist/measure/types.d.ts +99 -0
- package/dist/measure/types.d.ts.map +1 -1
- package/dist/measure/types.js +19 -0
- package/dist/measure/types.js.map +1 -1
- package/dist/numberWords.d.ts +22 -0
- package/dist/numberWords.d.ts.map +1 -0
- package/dist/numberWords.js +155 -0
- package/dist/numberWords.js.map +1 -0
- package/dist/numbering.d.ts +52 -3
- package/dist/numbering.d.ts.map +1 -1
- package/dist/numbering.js +116 -16
- package/dist/numbering.js.map +1 -1
- package/dist/parse/blockParser.d.ts.map +1 -1
- package/dist/parse/blockParser.js +54 -7
- package/dist/parse/blockParser.js.map +1 -1
- package/dist/parse/index.d.ts +1 -1
- package/dist/parse/index.d.ts.map +1 -1
- package/dist/parse/index.js.map +1 -1
- package/dist/parse/injectSpans.d.ts +3 -1
- package/dist/parse/injectSpans.d.ts.map +1 -1
- package/dist/parse/injectSpans.js +8 -4
- package/dist/parse/injectSpans.js.map +1 -1
- package/dist/parse/inlineFormatting.d.ts +50 -2
- package/dist/parse/inlineFormatting.d.ts.map +1 -1
- package/dist/parse/inlineFormatting.js +441 -26
- package/dist/parse/inlineFormatting.js.map +1 -1
- package/dist/parse/inlineSnippet.d.ts +12 -3
- package/dist/parse/inlineSnippet.d.ts.map +1 -1
- package/dist/parse/inlineSnippet.js +10 -3
- package/dist/parse/inlineSnippet.js.map +1 -1
- package/dist/parse/links.d.ts +9 -0
- package/dist/parse/links.d.ts.map +1 -0
- package/dist/parse/links.js +23 -0
- package/dist/parse/links.js.map +1 -0
- package/dist/parse/sourceMapping.d.ts.map +1 -1
- package/dist/parse/sourceMapping.js +68 -7
- package/dist/parse/sourceMapping.js.map +1 -1
- package/dist/parse/types.d.ts +32 -0
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/pipeline/bandCaps.d.ts +26 -5
- package/dist/pipeline/bandCaps.d.ts.map +1 -1
- package/dist/pipeline/bandCaps.js +47 -20
- package/dist/pipeline/bandCaps.js.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +1417 -172
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/buildBlockKind.d.ts +4 -0
- package/dist/pipeline/buildBlockKind.d.ts.map +1 -1
- package/dist/pipeline/buildBlockKind.js +38 -7
- package/dist/pipeline/buildBlockKind.js.map +1 -1
- package/dist/pipeline/buildHelpers.d.ts +5 -2
- package/dist/pipeline/buildHelpers.d.ts.map +1 -1
- package/dist/pipeline/buildHelpers.js +101 -25
- package/dist/pipeline/buildHelpers.js.map +1 -1
- package/dist/pipeline/buildMeasurement.d.ts.map +1 -1
- package/dist/pipeline/buildMeasurement.js +6 -2
- package/dist/pipeline/buildMeasurement.js.map +1 -1
- package/dist/pipeline/calloutLayout.d.ts +34 -7
- package/dist/pipeline/calloutLayout.d.ts.map +1 -1
- package/dist/pipeline/calloutLayout.js +331 -44
- package/dist/pipeline/calloutLayout.js.map +1 -1
- package/dist/pipeline/chips.d.ts.map +1 -1
- package/dist/pipeline/chips.js +3 -0
- package/dist/pipeline/chips.js.map +1 -1
- package/dist/pipeline/columnBalancing.d.ts +26 -6
- package/dist/pipeline/columnBalancing.d.ts.map +1 -1
- package/dist/pipeline/columnBalancing.js +82 -21
- package/dist/pipeline/columnBalancing.js.map +1 -1
- package/dist/pipeline/config.d.ts +18 -0
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js +44 -7
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/contentWarnings.d.ts +50 -0
- package/dist/pipeline/contentWarnings.d.ts.map +1 -0
- package/dist/pipeline/contentWarnings.js +362 -0
- package/dist/pipeline/contentWarnings.js.map +1 -0
- package/dist/pipeline/continuation.d.ts.map +1 -1
- package/dist/pipeline/continuation.js +25 -5
- package/dist/pipeline/continuation.js.map +1 -1
- package/dist/pipeline/floatPlacement.d.ts +16 -7
- package/dist/pipeline/floatPlacement.d.ts.map +1 -1
- package/dist/pipeline/floatPlacement.js +22 -5
- package/dist/pipeline/floatPlacement.js.map +1 -1
- package/dist/pipeline/floatSlots.d.ts +30 -4
- package/dist/pipeline/floatSlots.d.ts.map +1 -1
- package/dist/pipeline/floatSlots.js +42 -7
- package/dist/pipeline/floatSlots.js.map +1 -1
- package/dist/pipeline/headerFooter.d.ts +125 -11
- package/dist/pipeline/headerFooter.d.ts.map +1 -1
- package/dist/pipeline/headerFooter.js +444 -69
- package/dist/pipeline/headerFooter.js.map +1 -1
- package/dist/pipeline/headingDesignCuts.d.ts +40 -0
- package/dist/pipeline/headingDesignCuts.d.ts.map +1 -0
- package/dist/pipeline/headingDesignCuts.js +72 -0
- package/dist/pipeline/headingDesignCuts.js.map +1 -0
- package/dist/pipeline/headingStyles.d.ts +12 -0
- package/dist/pipeline/headingStyles.d.ts.map +1 -1
- package/dist/pipeline/headingStyles.js +45 -1
- package/dist/pipeline/headingStyles.js.map +1 -1
- package/dist/pipeline/lists.d.ts +33 -1
- package/dist/pipeline/lists.d.ts.map +1 -1
- package/dist/pipeline/lists.js +53 -2
- package/dist/pipeline/lists.js.map +1 -1
- package/dist/pipeline/measureContentBlock.d.ts +25 -1
- package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
- package/dist/pipeline/measureContentBlock.js +137 -17
- package/dist/pipeline/measureContentBlock.js.map +1 -1
- package/dist/pipeline/outline.d.ts +10 -5
- package/dist/pipeline/outline.d.ts.map +1 -1
- package/dist/pipeline/outline.js +38 -14
- package/dist/pipeline/outline.js.map +1 -1
- package/dist/pipeline/paragraphContainers.d.ts +13 -4
- package/dist/pipeline/paragraphContainers.d.ts.map +1 -1
- package/dist/pipeline/paragraphContainers.js +4 -3
- package/dist/pipeline/paragraphContainers.js.map +1 -1
- package/dist/pipeline/partPalette.d.ts +49 -3
- package/dist/pipeline/partPalette.d.ts.map +1 -1
- package/dist/pipeline/partPalette.js +414 -46
- package/dist/pipeline/partPalette.js.map +1 -1
- package/dist/pipeline/parts.d.ts +5 -1
- package/dist/pipeline/parts.d.ts.map +1 -1
- package/dist/pipeline/parts.js +16 -0
- package/dist/pipeline/parts.js.map +1 -1
- package/dist/pipeline/placeholders.d.ts +152 -3
- package/dist/pipeline/placeholders.d.ts.map +1 -1
- package/dist/pipeline/placeholders.js +259 -33
- package/dist/pipeline/placeholders.js.map +1 -1
- package/dist/pipeline/placement.d.ts +6 -1
- package/dist/pipeline/placement.d.ts.map +1 -1
- package/dist/pipeline/placement.js +13 -2
- package/dist/pipeline/placement.js.map +1 -1
- package/dist/pipeline/raggedLines.d.ts +5 -0
- package/dist/pipeline/raggedLines.d.ts.map +1 -1
- package/dist/pipeline/raggedLines.js +24 -3
- package/dist/pipeline/raggedLines.js.map +1 -1
- package/dist/pipeline/resourceLayout.d.ts +6 -4
- package/dist/pipeline/resourceLayout.d.ts.map +1 -1
- package/dist/pipeline/resourceLayout.js +137 -36
- package/dist/pipeline/resourceLayout.js.map +1 -1
- package/dist/pipeline/resourceNumbering.d.ts.map +1 -1
- package/dist/pipeline/resourceNumbering.js +5 -15
- package/dist/pipeline/resourceNumbering.js.map +1 -1
- package/dist/pipeline/styles.d.ts +38 -5
- package/dist/pipeline/styles.d.ts.map +1 -1
- package/dist/pipeline/styles.js +99 -46
- package/dist/pipeline/styles.js.map +1 -1
- package/dist/pipeline/toc.d.ts.map +1 -1
- package/dist/pipeline/toc.js +41 -5
- package/dist/pipeline/toc.js.map +1 -1
- package/dist/react/createLayout.d.ts +20 -0
- package/dist/react/createLayout.d.ts.map +1 -0
- package/dist/react/createLayout.js +50 -0
- package/dist/react/createLayout.js.map +1 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +4 -0
- package/dist/react/index.js.map +1 -0
- package/dist/svg/singleInk.d.ts +46 -1
- package/dist/svg/singleInk.d.ts.map +1 -1
- package/dist/svg/singleInk.js +180 -10
- package/dist/svg/singleInk.js.map +1 -1
- package/dist/table/model.d.ts +36 -1
- package/dist/table/model.d.ts.map +1 -1
- package/dist/table/model.js +163 -20
- package/dist/table/model.js.map +1 -1
- package/dist/types.d.ts +979 -68
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +439 -13
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js +91 -0
- package/dist/vdt.js.map +1 -1
- package/dist/worker/client.d.ts +15 -1
- package/dist/worker/client.d.ts.map +1 -1
- package/dist/worker/client.js +52 -3
- package/dist/worker/client.js.map +1 -1
- package/dist/worker/entryUrl.d.ts +15 -0
- package/dist/worker/entryUrl.d.ts.map +1 -0
- package/dist/worker/entryUrl.js +33 -0
- package/dist/worker/entryUrl.js.map +1 -0
- package/dist/worker/fonts.d.ts +18 -0
- package/dist/worker/fonts.d.ts.map +1 -0
- package/dist/worker/fonts.js +53 -0
- package/dist/worker/fonts.js.map +1 -0
- package/dist/worker/layout.worker.js +41 -6
- package/dist/worker/layout.worker.js.map +1 -1
- package/dist/worker/protocol.d.ts +4 -0
- package/dist/worker/protocol.d.ts.map +1 -1
- package/package.json +12 -5
package/dist/pipeline/build.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { flattenTitleBreaks } from '../parse/inlineFormatting';
|
|
2
2
|
import { dimensionToPx } from '../units';
|
|
3
3
|
import { createVDTDocument, createVDTBlock, createBoundingBox, } from '../vdt';
|
|
4
4
|
import { anchorBox } from '../design/layout';
|
|
5
5
|
import { parseMarkdownMemo, spaceDirectiveLines } from '../parse';
|
|
6
|
-
import { buildPageLabels,
|
|
7
|
-
import { extractFrontmatter } from '../frontmatter';
|
|
6
|
+
import { buildPageLabels, computeHeadingNumbering, parseNumberFormat, } from '../numbering';
|
|
7
|
+
import { extractFrontmatter, normalizeMetadata } from '../frontmatter';
|
|
8
|
+
import { collectConfigWarnings } from '../configWarnings';
|
|
8
9
|
import { initHyphenator } from '../measure';
|
|
9
|
-
import { resolveAllConfig, computeBaselineGrid } from './config';
|
|
10
|
-
import { createHeadingLevelResolver, deriveSectionGeometryConfig, deriveSectionMeasureContext, headingIsNumbered, headingStyleOf, planHeadingSections, } from './headingStyles';
|
|
11
|
-
import { computeOutline, hasTocDirective, outlineFromDoc, sameOutline } from './outline';
|
|
10
|
+
import { resolveAllConfig, computeBaselineGrid, resolvedLocale } from './config';
|
|
11
|
+
import { createHeadingLevelResolver, deriveSectionGeometryConfig, deriveSectionMeasureContext, headingIsHidden, headingIsNumbered, headingMarksFor, headingStyleOf, planHeadingSections, } from './headingStyles';
|
|
12
|
+
import { computeOutline, hasTocDirective, headingNumberingOptions, headingTemplatesOf, outlineFromDoc, sameOutline } from './outline';
|
|
12
13
|
import { expandTocDirectives } from './toc';
|
|
13
14
|
import { resolveBodyStyle, resolveBlockquoteStyle } from './styles';
|
|
14
|
-
import { computeLevelIndentsPx, computeOrderedLevelIndentsPx, computeOrderedListRunMetrics, } from './lists';
|
|
15
|
-
import { resetLinePositions, createPageWithColumns, currentColumn, advanceToNextColumn, advanceToNextPageBoundary, enforcePageParity, placeBlockInColumn, placeAtomicBlock, createPartPage, pageHasContent, sideColumnOf, sideColumns, sideUsedBottom, pageIsOccupied, bandColumns, currentBand, isBandLevel, bandUsedBottom, closeBandAndInsertSpan, } from './placement';
|
|
15
|
+
import { computeLevelIndentsPx, computeOrderedLevelIndentsPx, computeOrderedListRunMetrics, listBulletPosition, listItemGapPx, listItemSpacingPx, } from './lists';
|
|
16
|
+
import { resetLinePositions, createPageWithColumns, currentColumn, advanceToNextColumn, advanceToNextPageBoundary, enforcePageParity, placeBlockInColumn, placeAtomicBlock, createPartPage, pageHasContent, sideColumnOf, sideColumns, sideUsedBottom, pageIsOccupied, bandColumns, currentBand, isBandLevel, bandUsedBottom, closeBandAndInsertSpan, pageLayoutOf, } from './placement';
|
|
16
17
|
import { chooseParagraphSplit } from './orphanWidow';
|
|
17
18
|
import { applyStyleAttrs, computePageMetrics, isMarkerBlock, nextNonMarkerBlock, spaceLinesAfter, prevNonMarkerBlock, rollbackTrailingBlocks, } from './buildHelpers';
|
|
18
19
|
import { measureContentBlock } from './measureContentBlock';
|
|
@@ -21,17 +22,26 @@ import { planParts, derivePartMeasureContext } from './parts';
|
|
|
21
22
|
import { layoutCallout, offsetCalloutToAbsolute, pickCalloutStyle, planCallouts, resolveCalloutAttrs, } from './calloutLayout';
|
|
22
23
|
import { layoutResourceBlock, planTableSlice } from './resourceLayout';
|
|
23
24
|
import { computeFloatPlan, floatedResourceIds, } from './floatPlacement';
|
|
24
|
-
import { enumerateCurrentPageSlots, measureFloatBand, measureSideStack, columnHasFloatBand, fitsStrict, trueBottom, } from './floatSlots';
|
|
25
|
+
import { enumerateCurrentPageSlots, measureFloatBand, floatGapAbove, measureSideStack, clearSideObstacles, columnHasFloatBand, fitsStrict, trueBottom, } from './floatSlots';
|
|
25
26
|
import { computeHeadingContext, computeResourceNumbering, } from './resourceNumbering';
|
|
26
|
-
import { defaultResourceTypes } from '../defaults/resourceTypes';
|
|
27
|
+
import { defaultResourceTypes, documentLocale } from '../defaults/resourceTypes';
|
|
27
28
|
import { pickTableStyle } from '../defaults/tableStyle';
|
|
28
|
-
import { buildHeadersAndFooters,
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
29
|
+
import { buildHeadersAndFooters, defaultOpenerTitle, headingDesignBoxes, headingTitleText, measureDefaultOpenerHeight, measureHeadingDesign } from './headerFooter';
|
|
30
|
+
import { flowColorValues } from './partPalette';
|
|
31
|
+
import { chapterNumberCounter, leadingBoldText } from './placeholders';
|
|
32
|
+
import { proposeBalanceLines, collectColumnGaps, firstDivergentColumn, gapLinesIn, boxRoomIn, boxLeverKeys, pageSegments, MAX_BALANCING_PASSES, MAX_BALANCING_PASSES_PER_DOCUMENT, balanceKey } from './columnBalancing';
|
|
33
|
+
import { applyBandCap, uncapBand, columnBottom, bandCapLines, bandTop, bareBandTop, resolveBandCapsGen, drainPasses, resolveTrailingCapsGen, bandCapLinesAroundZone, } from './bandCaps';
|
|
31
34
|
import { raggedLooseLines } from './raggedLines';
|
|
35
|
+
import { collectContentWarnings, locateContentWarnings } from './contentWarnings';
|
|
32
36
|
/** Tolerance for "does this block fit" checks against a column's free
|
|
33
37
|
* height, absorbing floating-point drift between grid multiples. */
|
|
34
38
|
const FIT_EPS = 0.01;
|
|
39
|
+
/** Room (px) under closing boxes a balancing pass must close to be kept on
|
|
40
|
+
* that ground alone (see `boxRoomIn`). */
|
|
41
|
+
const BOX_ROOM_EPS_PX = 0.5;
|
|
42
|
+
/** Overflow past a capped column's foot (px) that counts as content the
|
|
43
|
+
* cut did not hold — a clipped line, not floating-point drift. */
|
|
44
|
+
const CAPPED_OVERFLOW_PX = 0.5;
|
|
35
45
|
/** Smallest share of its width an inline figure is set at to stay in the
|
|
36
46
|
* room left in its column (`layout.fitFiguresToPage`). */
|
|
37
47
|
const MIN_INLINE_FIGURE_SCALE = 0.5;
|
|
@@ -41,14 +51,33 @@ export class BuildCancelledError extends Error {
|
|
|
41
51
|
this.name = 'BuildCancelledError';
|
|
42
52
|
}
|
|
43
53
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
/** `{chapterNumber}` at each content block: the value of the last level-1
|
|
55
|
+
* heading at or before it ('' before the first), by the counter the
|
|
56
|
+
* running heads use over the placed blocks (`computeChapterNumbers`) —
|
|
57
|
+
* fed the same fields the placed heading blocks will carry. */
|
|
58
|
+
function chapterNumbersOfContent(blocks, resolved, prefixes, values, ordinalOffset) {
|
|
59
|
+
const numberless = new Set(resolved.headingStyles.filter((s) => s.numberingTemplate === '').map((s) => s.id));
|
|
60
|
+
const next = chapterNumberCounter(ordinalOffset, (b) => b.styleId !== undefined && numberless.has(b.styleId));
|
|
61
|
+
let current = '';
|
|
62
|
+
return blocks.map((b, i) => {
|
|
63
|
+
if (b.type === 'heading' && (b.level ?? 1) === 1) {
|
|
64
|
+
const style = headingStyleOf(b, resolved);
|
|
65
|
+
const value = next({
|
|
66
|
+
contentIndex: i,
|
|
67
|
+
unnumbered: style ? !style.numbered : false,
|
|
68
|
+
headingNumber: values[i],
|
|
69
|
+
numberPrefix: prefixes[i],
|
|
70
|
+
styleId: style?.id,
|
|
71
|
+
});
|
|
72
|
+
// A plate (`runningChapter: false`) reads its own value; the blocks
|
|
73
|
+
// after it stay in the chapter it interrupts.
|
|
74
|
+
if (style && !style.runningChapter)
|
|
75
|
+
return value;
|
|
76
|
+
current = value;
|
|
77
|
+
}
|
|
78
|
+
return current;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
52
81
|
/**
|
|
53
82
|
* Column balancing: measure a paragraph asked to run `extraLines` long.
|
|
54
83
|
* Walks the tracking ladder — no tracking first, then a little positive
|
|
@@ -57,21 +86,38 @@ const ALLOWED_PAGE_FORMATS = new Set([
|
|
|
57
86
|
* gains the line on its own, without needing the looseness target, counts
|
|
58
87
|
* too). A solution whose extra line is a runt is refused: filling a
|
|
59
88
|
* column's foot is no reason to leave a syllable alone at the end of a
|
|
60
|
-
* paragraph.
|
|
61
|
-
*
|
|
89
|
+
* paragraph. So is one with a justified line past `bodyText.maxWordSpacing`
|
|
90
|
+
* (EF-129): the breaker keeps a looser line only when no break set stays
|
|
91
|
+
* within the limit, and filling a column is no reason to print one. Falls
|
|
92
|
+
* back to the plain measurement when no rung works, recording the outcome
|
|
93
|
+
* either way.
|
|
94
|
+
*
|
|
95
|
+
* The target counts from the paragraph as placed, which the runt fix may
|
|
96
|
+
* have set a line shorter than the breaker's own optimum; the breaker's
|
|
97
|
+
* looseness counts from that optimum, so such a paragraph asks it for one
|
|
98
|
+
* line less (EF-129: asked for one more, it could only match the target by
|
|
99
|
+
* falling back to its optimum, unchecked).
|
|
62
100
|
*/
|
|
63
101
|
function measureLooseParagraph(rawBlock, blockIdx, columnWidth, ctx, styleOverride, extraLines, trackingLadder, looseOutcome) {
|
|
64
102
|
const base = measureContentBlock(rawBlock, blockIdx, columnWidth, ctx, { styleOverride });
|
|
65
103
|
if (!base)
|
|
66
104
|
return null;
|
|
67
105
|
const target = base.measured.lines.length + extraLines;
|
|
106
|
+
// The breaker's optimum, before any runt fix (a looseness of 0 skips it).
|
|
107
|
+
const optimum = measureContentBlock(rawBlock, blockIdx, columnWidth, ctx, { styleOverride, looseness: 0 });
|
|
108
|
+
const looseness = optimum ? target - optimum.measured.lines.length : extraLines;
|
|
109
|
+
const maxWordSpacing = ctx.resolved.bodyText.maxWordSpacing + 1e-9;
|
|
110
|
+
const withinLimit = (lines) => lines.every((l) => l.isLastLine || l.justifiedSpaceRatio === undefined || l.justifiedSpaceRatio <= maxWordSpacing);
|
|
68
111
|
for (const tracking of trackingLadder) {
|
|
69
112
|
const loose = measureContentBlock(rawBlock, blockIdx, columnWidth, ctx, {
|
|
70
113
|
styleOverride,
|
|
71
|
-
looseness
|
|
114
|
+
looseness,
|
|
72
115
|
trackingEm: tracking > 0 ? tracking / 1000 : undefined,
|
|
73
116
|
});
|
|
74
|
-
if (loose
|
|
117
|
+
if (loose
|
|
118
|
+
&& loose.measured.lines.length === target
|
|
119
|
+
&& !loose.measured.lastLineRunt
|
|
120
|
+
&& withinLimit(loose.measured.lines)) {
|
|
75
121
|
looseOutcome.set(blockIdx, tracking);
|
|
76
122
|
return loose;
|
|
77
123
|
}
|
|
@@ -116,13 +162,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
116
162
|
// Level configs with heading-style overrides merged in (`{style="…"}`).
|
|
117
163
|
const headingLevels = createHeadingLevelResolver(resolved);
|
|
118
164
|
const dpi = resolved.page.dpi;
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
165
|
+
// The document's hyphenation dictionary, set whatever the alignment: every
|
|
166
|
+
// hyphenating text shares it — body text, paragraph styles, callout bodies,
|
|
167
|
+
// design text — and so do the words wider than their measure, divided at a
|
|
168
|
+
// syllable even in ragged text. Left unset, a build would hyphenate with
|
|
169
|
+
// whichever dictionary the previous one (or the default, en-us) chose.
|
|
170
|
+
initHyphenator(resolved.bodyText.hyphenation.locale);
|
|
126
171
|
// Compute baseline grid
|
|
127
172
|
const baselineGrid = computeBaselineGrid(resolved);
|
|
128
173
|
// Create document
|
|
@@ -139,6 +184,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
139
184
|
// overrides apply from the first page until this document opens its own.
|
|
140
185
|
if (continuation?.part)
|
|
141
186
|
doc.partStart = continuation.part;
|
|
187
|
+
// A part that closed the preceding content: its break is still owed
|
|
188
|
+
// (`pendingPartBreak` below) and a first page left blank is its verso.
|
|
189
|
+
const afterPartPage = continuation?.afterPartPage === true && resolved.parts.page;
|
|
190
|
+
if (afterPartPage)
|
|
191
|
+
doc.afterPartPage = true;
|
|
192
|
+
// The book's page count, when the host knows it (`{bookTotalPages}`).
|
|
193
|
+
const bookPageCount = Math.floor(continuation?.bookPageCount ?? 0);
|
|
194
|
+
if (bookPageCount > 0)
|
|
195
|
+
doc.bookPageCount = bookPageCount;
|
|
142
196
|
const pageMetrics = computePageMetrics(resolved);
|
|
143
197
|
const { pageWidthPx, pageHeightPx, trimOffset } = pageMetrics;
|
|
144
198
|
// The geometry pages are opened with: the document's, or — inside a
|
|
@@ -153,10 +207,14 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
153
207
|
doc.pages.push(firstPage);
|
|
154
208
|
// Extract frontmatter, then parse the remaining markdown body
|
|
155
209
|
const { metadata: frontmatterMeta, content: markdownBody, contentOffset: bodyOffset, fieldSources } = extractFrontmatter(content.markdown);
|
|
156
|
-
|
|
210
|
+
// Typed YAML values (`title: 1984`, `publishDate: 2026-09-24`) print as
|
|
211
|
+
// text; dates in the document language (a blank `locale` is unset).
|
|
212
|
+
doc.metadata = normalizeMetadata({ ...(content.metadata ?? {}), ...frontmatterMeta }, resolvedLocale(resolved));
|
|
157
213
|
if (fieldSources)
|
|
158
214
|
doc.metadataSources = fieldSources;
|
|
159
|
-
|
|
215
|
+
// A configuration that leaves headings' inline marks off sets their
|
|
216
|
+
// spans plain here, for the outline and the layout alike (EF-122).
|
|
217
|
+
const parsedBlocks = headingMarksFor(parseMarkdownMemo(markdownBody), resolved);
|
|
160
218
|
const headingStart = continuation?.headings;
|
|
161
219
|
// `:::toc` expands into the entries of the book's outline — the one the
|
|
162
220
|
// host supplied, else this document's own (page labels unknown on the
|
|
@@ -165,18 +223,17 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
165
223
|
?? (hasTocDirective(parsedBlocks) ? computeOutline(parsedBlocks, resolved, headingStart) : undefined);
|
|
166
224
|
const contentBlocks = expandTocDirectives(parsedBlocks, outline, resolved);
|
|
167
225
|
const isNumbered = (b) => headingIsNumbered(b, resolved);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const headingPrefixes = computeHeadingNumbers(contentBlocks, headingTemplates, headingStart ? [headingStart.h1, headingStart.h2, headingStart.h3, headingStart.h4, headingStart.h5, headingStart.h6] : undefined, isNumbered);
|
|
226
|
+
const { prefixes: headingPrefixes, values: headingNumbers } = computeHeadingNumbering(contentBlocks, headingTemplatesOf(resolved), headingStart ? [headingStart.h1, headingStart.h2, headingStart.h3, headingStart.h4, headingStart.h5, headingStart.h6] : undefined, isNumbered, headingNumberingOptions(resolved));
|
|
227
|
+
// `{chapterNumber}` at each content block — the current chapter's, as the
|
|
228
|
+
// running heads print it on the block's page (`computeChapterNumbers`
|
|
229
|
+
// runs the same counter over the placed blocks) — so an opener design is
|
|
230
|
+
// measured with the number it is painted with.
|
|
231
|
+
const chapterNumberByBlock = chapterNumbersOfContent(contentBlocks, resolved, headingPrefixes, headingNumbers, doc.chapterOrdinalOffset ?? 0);
|
|
175
232
|
// Resource numbering — computed up front (before the placement loop) so that
|
|
176
233
|
// captions and inline `:ref`s can resolve their rendered number strings
|
|
177
234
|
// before measurement. Numbering follows order of first reference in the
|
|
178
235
|
// document.
|
|
179
|
-
const resourceTypes = config?.resourceTypes ?? defaultResourceTypes();
|
|
236
|
+
const resourceTypes = config?.resourceTypes ?? defaultResourceTypes(documentLocale(config));
|
|
180
237
|
const resources = content.resources ?? [];
|
|
181
238
|
const headingContext = computeHeadingContext(contentBlocks, headingStart, isNumbered);
|
|
182
239
|
const resourceNumbering = computeResourceNumbering(contentBlocks, resourceTypes, resources, headingContext, continuation ? { counters: continuation.resourceCounters, numbered: continuation.resourceNumbers } : undefined);
|
|
@@ -212,9 +269,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
212
269
|
// resources detach from the running text and take the first free slot
|
|
213
270
|
// after that reference (`floatSlots.ts`); the text flows past the
|
|
214
271
|
// reference uninterrupted. `position: 'here'` resources keep inline
|
|
215
|
-
// `::resource` placement and are not floated.
|
|
216
|
-
|
|
217
|
-
|
|
272
|
+
// `::resource` placement and are not floated. A resource an earlier
|
|
273
|
+
// chapter already numbered (`continuation.resourceNumbers`) was placed
|
|
274
|
+
// there: here it is only referred to, never floated again.
|
|
275
|
+
const incorporated = new Set(Object.keys(continuation?.resourceNumbers ?? {}));
|
|
276
|
+
const floatPlan = computeFloatPlan(contentBlocks, resources, resourceTypes, incorporated);
|
|
277
|
+
const floatedIds = floatedResourceIds(floatPlan, incorporated, resources, resourceTypes);
|
|
218
278
|
const floatsByFirstBlock = new Map();
|
|
219
279
|
for (const f of floatPlan) {
|
|
220
280
|
const list = floatsByFirstBlock.get(f.firstBlockIdx);
|
|
@@ -230,6 +290,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
230
290
|
* page: they take the side column of the next page the flow opens, in
|
|
231
291
|
* order (see `placeCalloutSide`). */
|
|
232
292
|
const pendingSideBoxes = [];
|
|
293
|
+
/** `span: 'side'` boxes of a style with `sideAtColumnEnd: 'after'`
|
|
294
|
+
* (EF-161), and the side boxes fenced behind them, waiting for the text
|
|
295
|
+
* after their fence to land (see `settleAwaitingSideBoxes`). Each keeps
|
|
296
|
+
* where its fence was: its page, band, text column and the y the text
|
|
297
|
+
* would have gone on at. */
|
|
298
|
+
const awaitingSideBoxes = [];
|
|
233
299
|
/** Whether the band the cursor is in lays out as a multi-column band:
|
|
234
300
|
* two or more text columns, or one beside a float-only side column — a
|
|
235
301
|
* page-span block then cuts the band across every column. */
|
|
@@ -258,6 +324,133 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
258
324
|
pendingFloats.push(stamped);
|
|
259
325
|
}
|
|
260
326
|
};
|
|
327
|
+
/** Floats first cited in a text block that is still being placed, keyed
|
|
328
|
+
* by resource id: the block and the index, among its measured lines, of
|
|
329
|
+
* the line that holds the citation. A float is queued when its citing
|
|
330
|
+
* block starts, but until that line is placed it takes no slot — not the
|
|
331
|
+
* head of a page the block's earlier lines open, nor that of the page
|
|
332
|
+
* the whole block moves on to — so it never lands above the line that
|
|
333
|
+
* cites it (EF-69). Side floats sit beside their text and are not held. */
|
|
334
|
+
const citationGates = new Map();
|
|
335
|
+
/** The text block whose lines are being placed, and how many are. */
|
|
336
|
+
const citedLines = { blockIdx: -1, placed: 0 };
|
|
337
|
+
const awaitsCitation = (f) => citationGates.has(f.resourceId);
|
|
338
|
+
/** Drop the gates of blocks before `blockIdx` (a block always places all
|
|
339
|
+
* its lines before the flow moves past it; this only guards against a
|
|
340
|
+
* gate outliving its block), or every gate at a chapter barrier. */
|
|
341
|
+
const releaseStaleCitationGates = (blockIdx = Infinity) => {
|
|
342
|
+
for (const [id, gate] of citationGates)
|
|
343
|
+
if (gate.blockIdx < blockIdx)
|
|
344
|
+
citationGates.delete(id);
|
|
345
|
+
};
|
|
346
|
+
/** Gate the pending floats first cited in `lines` of text block
|
|
347
|
+
* `blockIdx`, before its first line is placed. A keep-with-next rewind
|
|
348
|
+
* that places the headings before it again leaves its gates standing:
|
|
349
|
+
* they belong to a later block. */
|
|
350
|
+
const gateCitedFloats = (blockIdx, lines) => {
|
|
351
|
+
citedLines.blockIdx = blockIdx;
|
|
352
|
+
citedLines.placed = 0;
|
|
353
|
+
const fl = floatsByFirstBlock.get(blockIdx);
|
|
354
|
+
if (!fl)
|
|
355
|
+
return;
|
|
356
|
+
for (const f of fl) {
|
|
357
|
+
if (f.span === 'side' || !pendingFloats.some((p) => p.resourceId === f.resourceId))
|
|
358
|
+
continue;
|
|
359
|
+
const line = lines.findIndex((l) => l.segments?.some((s) => s.refResourceId === f.resourceId));
|
|
360
|
+
if (line >= 0)
|
|
361
|
+
citationGates.set(f.resourceId, { blockIdx, line });
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
/** A paragraph re-broken after some of its lines were placed (the lines
|
|
365
|
+
* placed are unchanged): the floats it still holds back wait for the
|
|
366
|
+
* line of the new setting that cites them. */
|
|
367
|
+
const regateCitedFloats = (blockIdx, lines) => {
|
|
368
|
+
for (const [id, gate] of citationGates) {
|
|
369
|
+
if (gate.blockIdx !== blockIdx || gate.line === Infinity)
|
|
370
|
+
continue;
|
|
371
|
+
const line = lines.findIndex((l) => l.segments?.some((s) => s.refResourceId === id));
|
|
372
|
+
if (line >= 0)
|
|
373
|
+
citationGates.set(id, { blockIdx, line });
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
/** Gate the pending floats first cited by the children of a box (content
|
|
377
|
+
* indices `from`…`to`) until the fragment that holds the citing child
|
|
378
|
+
* is committed: a box split across pages must not let a figure its
|
|
379
|
+
* second part cites head the page that part opens (above it). */
|
|
380
|
+
const gateBoxCitations = (from, to) => {
|
|
381
|
+
for (let i = from; i <= to; i++) {
|
|
382
|
+
for (const f of floatsByFirstBlock.get(i) ?? []) {
|
|
383
|
+
if (f.span === 'side' || !pendingFloats.some((p) => p.resourceId === f.resourceId))
|
|
384
|
+
continue;
|
|
385
|
+
citationGates.set(f.resourceId, { blockIdx: i, line: Infinity });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
/** A box fragment was committed: every child before content index
|
|
390
|
+
* `placedBefore` is set whole, so the floats they cite may take slots
|
|
391
|
+
* from here on. */
|
|
392
|
+
const releaseBoxCitations = (placedBefore) => {
|
|
393
|
+
for (const [id, gate] of citationGates) {
|
|
394
|
+
if (gate.line !== Infinity || gate.blockIdx >= placedBefore)
|
|
395
|
+
continue;
|
|
396
|
+
citationGates.delete(id);
|
|
397
|
+
const at = pendingFloats.findIndex((p) => p.resourceId === id);
|
|
398
|
+
if (at >= 0)
|
|
399
|
+
pendingFloats[at] = { ...pendingFloats[at], refPageIndex: cursor.pageIndex };
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
/** The block a free heading at `blockIdx` keeps with: the first one after
|
|
403
|
+
* it that is not a heading, a directive or a container marker (a box's
|
|
404
|
+
* opening fence counts: the box is placed there). `undefined` without
|
|
405
|
+
* keep-with-next, or when nothing follows. */
|
|
406
|
+
const keepWithNextTarget = (blockIdx) => {
|
|
407
|
+
if (!resolved.headings.keepWithNext)
|
|
408
|
+
return undefined;
|
|
409
|
+
for (let j = blockIdx + 1; j < contentBlocks.length; j++) {
|
|
410
|
+
const b = contentBlocks[j];
|
|
411
|
+
if (b.type === 'containerStart' && b.containerName === 'callout')
|
|
412
|
+
return j;
|
|
413
|
+
if (b.type === 'heading' || b.type === 'directive' || isMarkerBlock(b))
|
|
414
|
+
continue;
|
|
415
|
+
return j;
|
|
416
|
+
}
|
|
417
|
+
return undefined;
|
|
418
|
+
};
|
|
419
|
+
/** `placed`, the part of the block just set in its column, holds its
|
|
420
|
+
* next lines: release the floats they cite, stamped with the page and
|
|
421
|
+
* height of the citing line. A free heading's floats wait instead for
|
|
422
|
+
* the block it keeps with: until that block is placed, a keep-with-next
|
|
423
|
+
* rollback can still carry the heading to the next column or page, and
|
|
424
|
+
* a float placed meanwhile would sit above the line that cites it. The
|
|
425
|
+
* gate moves to that block's first line (`releaseStaleCitationGates`
|
|
426
|
+
* keeps it; its first `citedLinesPlaced` releases it, or the flow moving
|
|
427
|
+
* past it does), and a rollback that replays the heading gates it on the
|
|
428
|
+
* heading again (`gateCitedFloats`). */
|
|
429
|
+
const citedLinesPlaced = (blockIdx, placed) => {
|
|
430
|
+
// Side boxes waiting for the text after their fence stand beside it
|
|
431
|
+
// before the floats this text cites take the side column.
|
|
432
|
+
settleAwaitingSideBoxes(false);
|
|
433
|
+
if (citedLines.blockIdx !== blockIdx)
|
|
434
|
+
return;
|
|
435
|
+
const from = citedLines.placed;
|
|
436
|
+
citedLines.placed += placed.lines.length;
|
|
437
|
+
const keeper = placed.type === 'heading' && placed.containerId === undefined
|
|
438
|
+
? keepWithNextTarget(blockIdx)
|
|
439
|
+
: undefined;
|
|
440
|
+
for (const [id, gate] of citationGates) {
|
|
441
|
+
if (gate.blockIdx !== blockIdx || gate.line >= citedLines.placed)
|
|
442
|
+
continue;
|
|
443
|
+
if (keeper !== undefined) {
|
|
444
|
+
citationGates.set(id, { blockIdx: keeper, line: 0 });
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
citationGates.delete(id);
|
|
448
|
+
const at = pendingFloats.findIndex((p) => p.resourceId === id);
|
|
449
|
+
const line = placed.lines[Math.max(0, gate.line - from)];
|
|
450
|
+
if (at >= 0 && line)
|
|
451
|
+
pendingFloats[at] = { ...pendingFloats[at], refPageIndex: cursor.pageIndex, refY: line.bbox.y };
|
|
452
|
+
}
|
|
453
|
+
};
|
|
261
454
|
/** Floated boxes (`placement: 'auto' | 'top' | 'bottom'`, `span` column / page):
|
|
262
455
|
* a `:::callout` that leaves the flow like a resource and takes the
|
|
263
456
|
* first free band after the point it occurs at — the foot of the
|
|
@@ -404,9 +597,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
404
597
|
const cf = calloutFloatOf(resourceId);
|
|
405
598
|
if (cf) {
|
|
406
599
|
// A floated box: its frame at the band's width, title and icon
|
|
407
|
-
// included; no caption baseline to align.
|
|
600
|
+
// included; no caption baseline to align. Its margins, when wider
|
|
601
|
+
// than the float gap, are the space the band keeps next to it
|
|
602
|
+
// (EF-144), as a box set in the side column keeps its `marginBottom`.
|
|
408
603
|
const r = cf.L.layoutRange(CUT_START, cf.L.end, width, 'float-probe', false);
|
|
409
|
-
const mc = {
|
|
604
|
+
const mc = {
|
|
605
|
+
height: r.totalHeight,
|
|
606
|
+
...(r.marginBottomPx > floatGapPx ? { gapBelow: r.marginBottomPx } : {}),
|
|
607
|
+
...(r.marginTopPx > floatGapPx ? { gapAbove: r.marginTopPx } : {}),
|
|
608
|
+
};
|
|
410
609
|
floatMeasureMemo.set(key, mc);
|
|
411
610
|
return mc;
|
|
412
611
|
}
|
|
@@ -488,6 +687,13 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
488
687
|
* sends single-column floats to the least reserved column). */
|
|
489
688
|
const floatReserved = new Map();
|
|
490
689
|
const reservedOf = (col) => floatReserved.get(col) ?? { top: 0, bottom: 0 };
|
|
690
|
+
const floatHeadOf = (col) => reservedOf(col).top;
|
|
691
|
+
/** `BandCap.headPx` for a cap proposed on `cols`: how far the float bands
|
|
692
|
+
* every column holds at its head put the band's top below its bare one. */
|
|
693
|
+
const capHeadPx = (cols) => {
|
|
694
|
+
const head = bandTop(cols) - bareBandTop(cols, floatHeadOf);
|
|
695
|
+
return head > 0.5 ? { headPx: head } : {};
|
|
696
|
+
};
|
|
491
697
|
/** Content index of the block that first referenced the latest float
|
|
492
698
|
* reserved at the head of each column. */
|
|
493
699
|
const topFloatRefOf = new Map();
|
|
@@ -593,6 +799,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
593
799
|
const rest = slice.continues ? { ...f, startRow: end } : undefined;
|
|
594
800
|
return rest ? { slice, rest } : { slice };
|
|
595
801
|
};
|
|
802
|
+
/** The heading-design elements standing in each float-only side column
|
|
803
|
+
* (EF-78), with the heading block whose design paints them: the side
|
|
804
|
+
* stack sets its figures, tables and boxes clear of them
|
|
805
|
+
* (`clearSideObstacles`), above them when they fit there. Recorded by
|
|
806
|
+
* `markSideObstacles`, dropped when their heading is rolled back. */
|
|
807
|
+
const sideObstacles = new WeakMap();
|
|
596
808
|
/**
|
|
597
809
|
* Reserve a float band on `targetCols` (one column, or every text column
|
|
598
810
|
* of the band for a page-span float) and position the float there.
|
|
@@ -634,7 +846,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
634
846
|
return null;
|
|
635
847
|
// A side float stacks in the side column beside the citing text.
|
|
636
848
|
if (side) {
|
|
637
|
-
const { need, y } = measureSideStack(measure, first, refY, page.contentArea, baselineGrid, floatGapPx);
|
|
849
|
+
const { need, y } = measureSideStack(measure, first, refY, page.contentArea, baselineGrid, floatGapPx, sideObstacles.get(first));
|
|
638
850
|
return { need, y, measure, slice, width, xLeft, rotated };
|
|
639
851
|
}
|
|
640
852
|
// A bottom band normally anchors to the column's true foot (under a
|
|
@@ -659,10 +871,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
659
871
|
}
|
|
660
872
|
// A bottom float's caption cuts the side column's foot; when the side
|
|
661
873
|
// stack already reaches into that band (a box set beside the text
|
|
662
|
-
// above),
|
|
874
|
+
// above), or a heading design stands there, the caption goes under the
|
|
875
|
+
// figure instead of behind the box.
|
|
663
876
|
if (aside && sideCol && position === 'bottom' && measure.asideHeight !== undefined) {
|
|
664
877
|
const captionTop = y + measure.height - measure.asideHeight - floatGapPx;
|
|
665
|
-
|
|
878
|
+
const standsInBand = (sideObstacles.get(sideCol) ?? []).some((o) => o.bottom > captionTop + 0.5);
|
|
879
|
+
if (sideUsedBottom(sideCol) > captionTop + 0.5 || standsInBand) {
|
|
666
880
|
const plain = measureFloat(f.resourceId, width, slice, rotated);
|
|
667
881
|
if (plain) {
|
|
668
882
|
({ need, y } = measureFloatBand(position, plain, targetCols, page.contentArea, baselineGrid, floatGapPx, bottomOf));
|
|
@@ -681,13 +895,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
681
895
|
return Math.max(0, col.availableHeight - probe.need);
|
|
682
896
|
if (uncappedBottoms.has(col) && !anchorToCap)
|
|
683
897
|
return col.availableHeight;
|
|
684
|
-
const newHeight = Math.max(0, probe.y - floatGapPx - col.bbox.y);
|
|
898
|
+
const newHeight = Math.max(0, probe.y - floatGapAbove(probe.measure, floatGapPx) - col.bbox.y);
|
|
685
899
|
return Math.max(0, col.availableHeight - (col.bbox.height - newHeight));
|
|
686
900
|
};
|
|
687
901
|
/** Set a built float at its band position and hand it to the page: a
|
|
688
|
-
* resource block into `page.floats
|
|
689
|
-
*
|
|
690
|
-
|
|
902
|
+
* resource block into `page.floats`, with the content index of the block
|
|
903
|
+
* that first cites or embeds it (`anchorIdx`, where a tagged PDF reads
|
|
904
|
+
* it); a floated box's frame and children into `page.floats` and
|
|
905
|
+
* `doc.blocks`, the way a fixed box goes. */
|
|
906
|
+
const commitFloatBlock = (page, col, built, x, y, width, anchorIdx) => {
|
|
691
907
|
const cf = calloutFloatResults.get(built.block);
|
|
692
908
|
if (cf) {
|
|
693
909
|
calloutFloatResults.delete(built.block);
|
|
@@ -713,6 +929,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
713
929
|
built.block.bbox = createBoundingBox(x, y, width, built.height);
|
|
714
930
|
built.block.pageIndex = page.index;
|
|
715
931
|
built.block.columnIndex = col.index;
|
|
932
|
+
built.block.contentIndex = anchorIdx;
|
|
716
933
|
(page.floats ??= []).push(built.block);
|
|
717
934
|
};
|
|
718
935
|
const placeFloatInColumns = (page, f, targetCols, position, pageSpan, mode, anchorToCap = false, side = false, refY) => {
|
|
@@ -733,7 +950,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
733
950
|
// side column of the next page — where it is set anyway, overflowing,
|
|
734
951
|
// when even an empty column cannot hold it (a dominating figure).
|
|
735
952
|
if (need > first.availableHeight + 0.01) {
|
|
736
|
-
|
|
953
|
+
// A column that already holds something — a float, a box, or a
|
|
954
|
+
// heading design standing in it — is no empty column to overflow.
|
|
955
|
+
const holds = sideUsedBottom(first) > first.bbox.y + 0.5 || (sideObstacles.get(first)?.length ?? 0) > 0;
|
|
956
|
+
if (mode === 'strict' || holds)
|
|
737
957
|
return 'defer';
|
|
738
958
|
// An empty column shortened by a band above it (a page-span box,
|
|
739
959
|
// a top float) is no measure of the float: when a full column
|
|
@@ -750,7 +970,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
750
970
|
if (!built)
|
|
751
971
|
return 'skip';
|
|
752
972
|
first.availableHeight = Math.max(0, first.availableHeight - need);
|
|
753
|
-
commitFloatBlock(page, first, built, xLeft, y, width);
|
|
973
|
+
commitFloatBlock(page, first, built, xLeft, y, width, f.firstBlockIdx);
|
|
754
974
|
floatsPlaced++;
|
|
755
975
|
return 'placed';
|
|
756
976
|
}
|
|
@@ -805,7 +1025,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
805
1025
|
for (const c of targetCols) {
|
|
806
1026
|
if (position === 'bottom' && uncappedBottoms.has(c) && !anchorToCap) {
|
|
807
1027
|
// Trailing cap: the band must lie entirely below the level cut.
|
|
808
|
-
if (y - floatGapPx < c.bbox.y + c.bbox.height - 0.01)
|
|
1028
|
+
if (y - floatGapAbove(measure, floatGapPx) < c.bbox.y + c.bbox.height - 0.01)
|
|
809
1029
|
return 'defer';
|
|
810
1030
|
continue;
|
|
811
1031
|
}
|
|
@@ -843,6 +1063,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
843
1063
|
if (minAvail - need < minTextPx)
|
|
844
1064
|
need = Math.max(need, minAvail);
|
|
845
1065
|
}
|
|
1066
|
+
// A page opened for a block that cannot split (an inline figure or
|
|
1067
|
+
// table, see `onAtomicNewPage`): a float takes its head or foot only
|
|
1068
|
+
// when the block still fits the column beside it; otherwise it waits
|
|
1069
|
+
// for the next page, as it did before the block took the flush.
|
|
1070
|
+
if (mode === 'fresh' && freshPageHold && targetCols.includes(freshPageHold.col)) {
|
|
1071
|
+
const after = galleryFill ? 0 : availableAfterBand(freshPageHold.col, position, { need, y, measure }, false);
|
|
1072
|
+
if (after < freshPageHold.px - FIT_EPS)
|
|
1073
|
+
return 'defer';
|
|
1074
|
+
}
|
|
846
1075
|
// The rest goes on after this slice in reading order, never before.
|
|
847
1076
|
if (rest) {
|
|
848
1077
|
rest = { ...rest, notBefore: { pageIndex: page.index, columnIndex: targetCols[targetCols.length - 1].index } };
|
|
@@ -884,7 +1113,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
884
1113
|
uncappedBottoms.set(col, capped - need);
|
|
885
1114
|
}
|
|
886
1115
|
else {
|
|
887
|
-
const newHeight = Math.max(0, y - floatGapPx - col.bbox.y);
|
|
1116
|
+
const newHeight = Math.max(0, y - floatGapAbove(measure, floatGapPx) - col.bbox.y);
|
|
888
1117
|
const reserved = col.bbox.height - newHeight;
|
|
889
1118
|
col.bbox.height = newHeight;
|
|
890
1119
|
col.availableHeight = Math.max(0, col.availableHeight - reserved);
|
|
@@ -902,7 +1131,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
902
1131
|
if (galleryFill)
|
|
903
1132
|
for (const col of targetCols)
|
|
904
1133
|
col.availableHeight = 0;
|
|
905
|
-
commitFloatBlock(page, first, built, xLeft, y, width);
|
|
1134
|
+
commitFloatBlock(page, first, built, xLeft, y, width, f.firstBlockIdx);
|
|
906
1135
|
floatsPlaced++;
|
|
907
1136
|
return rest ? { rest } : 'placed';
|
|
908
1137
|
};
|
|
@@ -925,8 +1154,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
925
1154
|
* tables are numbered in first-reference order, and the reader must
|
|
926
1155
|
* meet them in that order too — table 3 never lands after table 4, even
|
|
927
1156
|
* when 4 would fit a slot 3 does not. Sequences do not hold each other
|
|
928
|
-
* up: a waiting table lets a later figure through.
|
|
1157
|
+
* up: a waiting table lets a later figure through. A float whose citing
|
|
1158
|
+
* line is not placed yet waits too (see `citationGates`), and holds back
|
|
1159
|
+
* its sequence. */
|
|
929
1160
|
const heldBack = (i) => {
|
|
1161
|
+
if (awaitsCitation(pendingFloats[i]))
|
|
1162
|
+
return true;
|
|
930
1163
|
const typeId = resourceById.get(pendingFloats[i].resourceId)?.typeId;
|
|
931
1164
|
for (let j = 0; j < i; j++) {
|
|
932
1165
|
if (resourceById.get(pendingFloats[j].resourceId)?.typeId === typeId)
|
|
@@ -1157,6 +1390,24 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1157
1390
|
/** Reserve floats on each freshly opened content page. Passed only to the
|
|
1158
1391
|
* content-flow column advances — parity / force-blank pages never get it. */
|
|
1159
1392
|
const onNewPage = (page) => flushFloatsIntoPage(page);
|
|
1393
|
+
/** The block a page is being opened for, when it cannot split: the
|
|
1394
|
+
* column it goes into and the height it needs there (see
|
|
1395
|
+
* `placeFloatInColumns`). Set only while that page's floats are flushed. */
|
|
1396
|
+
let freshPageHold = null;
|
|
1397
|
+
/** `onNewPage` for a page an inline figure or table opens (EF-160): the
|
|
1398
|
+
* floats pending take its bands as on any page the flow opens — up to
|
|
1399
|
+
* postext 1.4 they skipped it and waited for the page after — but never
|
|
1400
|
+
* the room the block needs under or over them. */
|
|
1401
|
+
const onAtomicNewPage = (heightPx) => (page) => {
|
|
1402
|
+
const col = page.columns.find((c) => c.kind !== 'span' && c.kind !== 'side');
|
|
1403
|
+
freshPageHold = col ? { col, px: heightPx } : null;
|
|
1404
|
+
try {
|
|
1405
|
+
flushFloatsIntoPage(page);
|
|
1406
|
+
}
|
|
1407
|
+
finally {
|
|
1408
|
+
freshPageHold = null;
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1160
1411
|
/** Whether content block `idx` opens a callout that will span the page
|
|
1161
1412
|
* in the current (multi-column) band — the floats placed right before
|
|
1162
1413
|
* it prefer the head of an empty column. */
|
|
@@ -1195,6 +1446,9 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1195
1446
|
break; // safety: no progress
|
|
1196
1447
|
}
|
|
1197
1448
|
};
|
|
1449
|
+
/** Boxes that left the flow this pass (side column, float band, fixed),
|
|
1450
|
+
* by the content index of their opening marker (see `leftFlow`). */
|
|
1451
|
+
const calloutsOutOfFlow = new Set();
|
|
1198
1452
|
// Everything per-block measurement needs that is constant for this pass.
|
|
1199
1453
|
const measureCtx = {
|
|
1200
1454
|
resolved,
|
|
@@ -1202,6 +1456,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1202
1456
|
bodyStyle,
|
|
1203
1457
|
blockquoteStyle,
|
|
1204
1458
|
headingPrefixes,
|
|
1459
|
+
headingNumbers,
|
|
1205
1460
|
listLevelIndentsPx,
|
|
1206
1461
|
orderedLevelIndentsPx,
|
|
1207
1462
|
orderedMetrics,
|
|
@@ -1211,10 +1466,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1211
1466
|
contentBlocks,
|
|
1212
1467
|
cache,
|
|
1213
1468
|
bodyOffset,
|
|
1469
|
+
source: markdownBody,
|
|
1214
1470
|
resources,
|
|
1215
1471
|
resourceTypes,
|
|
1216
1472
|
resourceNumbering,
|
|
1217
1473
|
floatedIds,
|
|
1474
|
+
leftFlow: calloutsOutOfFlow,
|
|
1218
1475
|
};
|
|
1219
1476
|
// Blocks inside a `:::part` measure with the part body typography.
|
|
1220
1477
|
const partMeasureCtx = partPlan.byStart.size > 0
|
|
@@ -1262,6 +1519,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1262
1519
|
blk.headingStyleId = style.id;
|
|
1263
1520
|
if (!style.numbered)
|
|
1264
1521
|
blk.unnumbered = true;
|
|
1522
|
+
if (!style.runningChapter && (raw.level ?? 1) === 1)
|
|
1523
|
+
blk.notRunningChapter = true;
|
|
1524
|
+
}
|
|
1525
|
+
// A letter-case transform changes what the lines print, not the
|
|
1526
|
+
// title: keep it as written for the bookmarks (EF-81). A title citing
|
|
1527
|
+
// a resource keeps the printed text, whose `:ref` label the source
|
|
1528
|
+
// does not hold.
|
|
1529
|
+
if (headingLevels.forBlock(raw)?.textTransform === 'uppercase' && !raw.spans.some((s) => s.ref)) {
|
|
1530
|
+
blk.sourceTitle = flattenTitleBreaks(raw.text);
|
|
1265
1531
|
}
|
|
1266
1532
|
}
|
|
1267
1533
|
if (raw.toc?.kind === 'entry') {
|
|
@@ -1279,6 +1545,21 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1279
1545
|
};
|
|
1280
1546
|
let blockIdCounter = 0;
|
|
1281
1547
|
let pendingSpacing = 0;
|
|
1548
|
+
/** The float gap an inline resource still owes the block right after it
|
|
1549
|
+
* (EF-93), or the space a `:::paragraphs` container still owes the block
|
|
1550
|
+
* after its closing marker (EF-159; `container`, carried through the
|
|
1551
|
+
* closing markers that follow, an enclosing container's included),
|
|
1552
|
+
* measured from the grid line its foot snapped to: `pending` is what it
|
|
1553
|
+
* left in `pendingSpacing`, in whole grid lines so text lands back on the
|
|
1554
|
+
* grid; `exact` is the gap itself, which a block that sets itself off
|
|
1555
|
+
* the grid and snaps the flow after it (a heading, a display formula, a
|
|
1556
|
+
* box) collapses its own space above with instead. */
|
|
1557
|
+
let inlineGapOwed = null;
|
|
1558
|
+
/** The pending spacing a heading, a display formula or a box at `idx`
|
|
1559
|
+
* collapses its top margin with (see `inlineGapOwed`). */
|
|
1560
|
+
const pendingBeforeSelfSnapping = (idx) => inlineGapOwed && inlineGapOwed.afterIdx === idx - 1 && pendingSpacing === inlineGapOwed.pending
|
|
1561
|
+
? inlineGapOwed.exact
|
|
1562
|
+
: pendingSpacing;
|
|
1282
1563
|
// Pages whose break into the next page is explicit rather than natural
|
|
1283
1564
|
// overflow. Column balancing leaves their last column short (a chapter's
|
|
1284
1565
|
// closing page legitimately ends early).
|
|
@@ -1301,6 +1582,9 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1301
1582
|
// after landing (the cap then trims whatever the block left).
|
|
1302
1583
|
let registeredBand = null;
|
|
1303
1584
|
let bandStart = null;
|
|
1585
|
+
/** Bands registered so far in this pass per opening block and part
|
|
1586
|
+
* (`contentIndex:part`): the `visit` of the next one (see `enterBand`). */
|
|
1587
|
+
const bandVisits = new Map();
|
|
1304
1588
|
let activeCap = null;
|
|
1305
1589
|
/** True bottoms of capped columns (restored when the span block cuts). */
|
|
1306
1590
|
const uncappedBottoms = new Map();
|
|
@@ -1317,6 +1601,157 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1317
1601
|
if (px > 0)
|
|
1318
1602
|
balanceExtraInColumn.set(col, (balanceExtraInColumn.get(col) ?? 0) + px);
|
|
1319
1603
|
};
|
|
1604
|
+
/** Whether a heading is painted by the default opener: a `span: 'page'`
|
|
1605
|
+
* level (or style) without design elements of its own, that prints
|
|
1606
|
+
* (EF-100). */
|
|
1607
|
+
const opensDefaultOpener = (raw) => {
|
|
1608
|
+
const lvl = headingLevels.forBlock(raw);
|
|
1609
|
+
return lvl?.span === 'page'
|
|
1610
|
+
&& !(lvl.advancedDesign.enabled && lvl.advancedDesign.slot.elements.length > 0)
|
|
1611
|
+
&& !headingIsHidden(raw, lvl);
|
|
1612
|
+
};
|
|
1613
|
+
/** Page-spanning heading placed in `headingCol`, reserving `h` px from the
|
|
1614
|
+
* top of the page: the same vertical band is reserved in every other
|
|
1615
|
+
* column of the page, so body text under the opener band starts below it
|
|
1616
|
+
* in ALL columns, not just the one it was placed in. A column still
|
|
1617
|
+
* untouched moves its head below the band (as a top float does), so a
|
|
1618
|
+
* float offered that column's head lands under the opener instead of
|
|
1619
|
+
* over it. A text column's head goes to the first grid line at or under
|
|
1620
|
+
* the band's foot, so what it snaps lands on the page's grid, as in the
|
|
1621
|
+
* opener's own column (EF-139); its first block is then placed as
|
|
1622
|
+
* `openerHeadSpacing` says. An `h` at least as tall as the columns
|
|
1623
|
+
* spends them whole. `pending` is the space the opener leaves under it:
|
|
1624
|
+
* its `marginBottom` when it did not snap, 0 when the snap took it in;
|
|
1625
|
+
* `gridText` whether the text under it lands on the page's grid (its
|
|
1626
|
+
* level snaps, or would but for the heading after it). */
|
|
1627
|
+
const reserveOpenerBand = (headingCol, h, opener, pending = 0, gridText = true) => {
|
|
1628
|
+
const page = doc.pages[cursor.pageIndex];
|
|
1629
|
+
for (const otherCol of page.columns) {
|
|
1630
|
+
if (otherCol === headingCol)
|
|
1631
|
+
continue;
|
|
1632
|
+
if (otherCol.blocks.length === 0 && otherCol.availableHeight >= otherCol.bbox.height - 0.01) {
|
|
1633
|
+
const foot = otherCol.bbox.y + h;
|
|
1634
|
+
const text = otherCol.kind !== 'side' && otherCol.kind !== 'span';
|
|
1635
|
+
const reach = (text ? gridUpOnPage(page, foot) : foot) - otherCol.bbox.y;
|
|
1636
|
+
const shift = Math.min(reach, otherCol.bbox.height);
|
|
1637
|
+
otherCol.bbox.y += shift;
|
|
1638
|
+
otherCol.bbox.height -= shift;
|
|
1639
|
+
otherCol.availableHeight = Math.max(0, otherCol.availableHeight - reach);
|
|
1640
|
+
if (opener && text) {
|
|
1641
|
+
const textTop = gridText ? gridUpOnPage(page, foot + pending) : foot + pending;
|
|
1642
|
+
openerBandHeads.set(otherCol, { openerCol: headingCol, opener, foot, textTop, floatTop: reservedOf(otherCol).top });
|
|
1643
|
+
}
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
otherCol.availableHeight = Math.max(0, otherCol.availableHeight - h);
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
/** The page grid line at or below `y` (absolute px). */
|
|
1650
|
+
const gridUpOnPage = (page, y) => page.contentArea.y + Math.ceil((y - page.contentArea.y - 0.01) / baselineGrid) * baselineGrid;
|
|
1651
|
+
/** Columns headed by a page-span opener's band (see `reserveOpenerBand`):
|
|
1652
|
+
* the opener, its column, the band's foot, where text right under the
|
|
1653
|
+
* opener starts and the float band the column held at its head then. */
|
|
1654
|
+
const openerBandHeads = new Map();
|
|
1655
|
+
/**
|
|
1656
|
+
* The room above the first block of a column headed by an opener band,
|
|
1657
|
+
* relative to the column's head, or undefined for any other column (or
|
|
1658
|
+
* one the flow reached after a float took its head). Every column under
|
|
1659
|
+
* the band starts as the opener's own column does (EF-139):
|
|
1660
|
+
* - text starts where text right under the opener starts: the band's
|
|
1661
|
+
* foot plus the space the opener leaves, taken to the next grid line
|
|
1662
|
+
* when the opener's level snaps (its text lands on the page's grid, as
|
|
1663
|
+
* it does when the opener did not snap only because a heading follows);
|
|
1664
|
+
* - a heading (or another block that keeps its top margin, `level`) is
|
|
1665
|
+
* set level with the first visible block under the opener when that
|
|
1666
|
+
* is a heading or a display formula, margins included as there (a
|
|
1667
|
+
* balancing lever above it excepted), else level with the text there.
|
|
1668
|
+
* A hidden heading (`hidden: true`) under the opener does not count:
|
|
1669
|
+
* it prints nothing to line up with. Its own snap then puts the text
|
|
1670
|
+
* after it on the page's grid.
|
|
1671
|
+
* The block may sit up to a line above the column's head, which is on
|
|
1672
|
+
* the grid; the renderers' column clip takes it in (`columnClipRect`).
|
|
1673
|
+
* Up to postext 1.4 the first block of such a column started at the
|
|
1674
|
+
* band's foot, off the grid when the band ended between two lines, and a
|
|
1675
|
+
* heading there dropped the top margin the first column's kept.
|
|
1676
|
+
*/
|
|
1677
|
+
const openerHeadSpacing = (col, level) => {
|
|
1678
|
+
const head = openerBandHeads.get(col);
|
|
1679
|
+
if (!head || col.blocks.length > 0 || reservedOf(col).top !== head.floatTop)
|
|
1680
|
+
return undefined;
|
|
1681
|
+
let target = head.textTop;
|
|
1682
|
+
if (level) {
|
|
1683
|
+
const at = head.openerCol.blocks.indexOf(head.opener);
|
|
1684
|
+
const next = at >= 0 ? head.openerCol.blocks.slice(at + 1).find((b) => !b.hidden) : undefined;
|
|
1685
|
+
if (next && (next.type === 'heading' || next.type === 'mathDisplay')) {
|
|
1686
|
+
target = Math.max(head.foot, next.bbox.y - (next.balancing?.spaceAbove ?? 0));
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return target - col.bbox.y;
|
|
1690
|
+
};
|
|
1691
|
+
/** The side column each in-column heading marked obstacles in (see
|
|
1692
|
+
* `markSideObstacles`), to drop them when the heading is rolled back. */
|
|
1693
|
+
const sideObstacleColumn = new WeakMap();
|
|
1694
|
+
/**
|
|
1695
|
+
* Keep the float-only side column's stack off the heading design placed
|
|
1696
|
+
* as `blk` (EF-78). A design element may be anchored outside the heading's
|
|
1697
|
+
* column — a chapter numeral set in the outer margin column of a textbook
|
|
1698
|
+
* opener, a section number hung in the margin — where the side stack
|
|
1699
|
+
* (`span: 'side'` figures, tables and boxes) would paint over it. Each
|
|
1700
|
+
* such element is recorded as an obstacle of the side column: what the
|
|
1701
|
+
* stack sets later goes above it when it fits there, else one float gap
|
|
1702
|
+
* under it (`clearSideObstacles`). A numeral at the head of the channel
|
|
1703
|
+
* thus holds the whole stack under it, while a number hung beside a
|
|
1704
|
+
* heading further down the page leaves the head of the channel to the
|
|
1705
|
+
* figures the page cites. What the stack already holds is not moved.
|
|
1706
|
+
* A page-spanning opener needs none of this: its band is reserved in
|
|
1707
|
+
* every column, the side column included (`reserveOpenerBand`).
|
|
1708
|
+
*/
|
|
1709
|
+
const markSideObstacles = (blk, design) => {
|
|
1710
|
+
if (design.lvl.span === 'page')
|
|
1711
|
+
return;
|
|
1712
|
+
const page = doc.pages[cursor.pageIndex];
|
|
1713
|
+
const side = sideColumnOf(page, currentBand(page, cursor));
|
|
1714
|
+
if (!side || side.bbox.height <= 0.5)
|
|
1715
|
+
return;
|
|
1716
|
+
const boxes = headingDesignBoxes(design.lvl, design.info, blk.bbox, resolved.page.dpi, doc.metadata, cursor.pageIndex, designFrames, resourceById);
|
|
1717
|
+
const top = side.bbox.y;
|
|
1718
|
+
const foot = side.bbox.y + side.bbox.height;
|
|
1719
|
+
const left = side.bbox.x;
|
|
1720
|
+
const right = side.bbox.x + side.bbox.width;
|
|
1721
|
+
const marks = [];
|
|
1722
|
+
for (const b of boxes) {
|
|
1723
|
+
if (b.width <= 0 || b.height <= 0)
|
|
1724
|
+
continue;
|
|
1725
|
+
if (b.x + b.width <= left + 0.5 || b.x >= right - 0.5)
|
|
1726
|
+
continue;
|
|
1727
|
+
if (b.y + b.height <= top + 0.5 || b.y >= foot - 0.5)
|
|
1728
|
+
continue;
|
|
1729
|
+
marks.push({ owner: blk, top: b.y, bottom: b.y + b.height });
|
|
1730
|
+
}
|
|
1731
|
+
if (marks.length === 0)
|
|
1732
|
+
return;
|
|
1733
|
+
const list = sideObstacles.get(side);
|
|
1734
|
+
if (list)
|
|
1735
|
+
list.push(...marks);
|
|
1736
|
+
else
|
|
1737
|
+
sideObstacles.set(side, marks);
|
|
1738
|
+
sideObstacleColumn.set(blk, side);
|
|
1739
|
+
};
|
|
1740
|
+
/** `rollbackTrailingBlocks` that also drops a rolled-back heading's
|
|
1741
|
+
* side-column obstacles: the heading is set again further on. */
|
|
1742
|
+
const rollbackHeadings = (col) => {
|
|
1743
|
+
const popped = rollbackTrailingBlocks(col, doc.blocks, isFreeHeading);
|
|
1744
|
+
for (const p of popped) {
|
|
1745
|
+
const side = sideObstacleColumn.get(p);
|
|
1746
|
+
if (!side)
|
|
1747
|
+
continue;
|
|
1748
|
+
sideObstacleColumn.delete(p);
|
|
1749
|
+
const list = sideObstacles.get(side);
|
|
1750
|
+
if (list)
|
|
1751
|
+
sideObstacles.set(side, list.filter((o) => o.owner !== p));
|
|
1752
|
+
}
|
|
1753
|
+
return popped;
|
|
1754
|
+
};
|
|
1320
1755
|
const bandCapProposals = new Map();
|
|
1321
1756
|
const spanPlacedInBand = new Set();
|
|
1322
1757
|
const bandCapsApplied = new Set();
|
|
@@ -1328,12 +1763,21 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1328
1763
|
if (registeredBand && registeredBand.pageIndex === page.index && registeredBand.band === band)
|
|
1329
1764
|
return;
|
|
1330
1765
|
registeredBand = { pageIndex: page.index, band };
|
|
1331
|
-
|
|
1766
|
+
// The same block (or part) can be offered to several bands before one
|
|
1767
|
+
// takes it: a page a float fills leaves it no room, and it moves on to
|
|
1768
|
+
// the next page. Each of those bands is told apart by how many came
|
|
1769
|
+
// before it with the same opening block, so a cap lands on the band it
|
|
1770
|
+
// was measured in and not on the first one the block passed through,
|
|
1771
|
+
// which took nothing (EF-167, EF-189).
|
|
1772
|
+
const key = `${contentIndex}:${part}`;
|
|
1773
|
+
const visit = bandVisits.get(key) ?? 0;
|
|
1774
|
+
bandVisits.set(key, visit + 1);
|
|
1775
|
+
bandStart = { contentIndex, part, visit };
|
|
1332
1776
|
activeCap = null;
|
|
1333
1777
|
if (!bandCaps)
|
|
1334
1778
|
return;
|
|
1335
1779
|
for (const [spanIndex, cap] of bandCaps) {
|
|
1336
|
-
if (cap.startContentIndex !== contentIndex || cap.startPart !== part)
|
|
1780
|
+
if (cap.startContentIndex !== contentIndex || cap.startPart !== part || (cap.startVisit ?? 0) !== visit)
|
|
1337
1781
|
continue;
|
|
1338
1782
|
// A cap whose last band took nothing (its opening block is taller
|
|
1339
1783
|
// than the cut — a heading's leading over a one-line cap) would cut
|
|
@@ -1344,7 +1788,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1344
1788
|
if (prev && prev.every((c) => c.blocks.length === 0))
|
|
1345
1789
|
continue;
|
|
1346
1790
|
const cols = bandColumns(page, band);
|
|
1347
|
-
|
|
1791
|
+
// The cut is measured from under the float bands the proposing pass
|
|
1792
|
+
// had at the band's head, which may not have landed yet.
|
|
1793
|
+
const top = Math.max(bandTop(cols), bareBandTop(cols, floatHeadOf) + (cap.headPx ?? 0));
|
|
1794
|
+
applyBandCap(cols, cap.lines * baselineGrid, uncappedBottoms, cap.zone, cap.kind === 'trailing', top);
|
|
1348
1795
|
cappedBandColumns.set(spanIndex, cols);
|
|
1349
1796
|
activeCap = { spanIndex, pageIndex: page.index, band };
|
|
1350
1797
|
bandCapsApplied.add(spanIndex);
|
|
@@ -1422,29 +1869,55 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1422
1869
|
kind: 'trailing',
|
|
1423
1870
|
startContentIndex: bandStart.contentIndex,
|
|
1424
1871
|
startPart: bandStart.part,
|
|
1872
|
+
...(bandStart.visit > 0 ? { startVisit: bandStart.visit } : {}),
|
|
1425
1873
|
lines: aroundZone ?? Math.max(bandCapLines(cols, baselineGrid, footBands), ...floatHeads),
|
|
1426
1874
|
retries: 0,
|
|
1427
1875
|
...(aroundZone !== null ? { zone } : {}),
|
|
1876
|
+
...capHeadPx(cols),
|
|
1428
1877
|
});
|
|
1429
1878
|
};
|
|
1430
1879
|
/** Close the flow at a chapter-level boundary (block `boundaryIndex`):
|
|
1431
1880
|
* floats take the page's free slots, the closing band is levelled, the
|
|
1432
1881
|
* page is marked as an explicit break, and every float still pending is
|
|
1433
1882
|
* drained onto pages opened BEFORE the boundary. The caller then opens
|
|
1434
|
-
* the boundary's own page.
|
|
1883
|
+
* the boundary's own page.
|
|
1884
|
+
*
|
|
1885
|
+
* The floats the boundary block itself cites first — a `breakBefore`
|
|
1886
|
+
* heading that names a figure, the first paragraph after a part — were
|
|
1887
|
+
* queued at the top of its iteration, but their citation is set on the
|
|
1888
|
+
* page the boundary opens: they sit the drain out and stay pending (with
|
|
1889
|
+
* no reference stamp, which the citing line sets), so they land after
|
|
1890
|
+
* the line that cites them, in the new segment (EF-69). */
|
|
1435
1891
|
const closeFlowSegment = (boundaryIndex) => {
|
|
1892
|
+
settleAwaitingSideBoxes(true);
|
|
1893
|
+
releaseStaleCitationGates();
|
|
1894
|
+
const ownFloats = [];
|
|
1895
|
+
for (let i = pendingFloats.length - 1; i >= 0; i--) {
|
|
1896
|
+
const f = pendingFloats[i];
|
|
1897
|
+
if (f.firstBlockIdx !== boundaryIndex || f.callout)
|
|
1898
|
+
continue;
|
|
1899
|
+
const unstamped = { ...f };
|
|
1900
|
+
delete unstamped.refPageIndex;
|
|
1901
|
+
delete unstamped.refY;
|
|
1902
|
+
ownFloats.unshift(unstamped);
|
|
1903
|
+
pendingFloats.splice(i, 1);
|
|
1904
|
+
}
|
|
1436
1905
|
tryPlacePendingFloatsOnCurrentPage();
|
|
1437
1906
|
proposeTrailingCap(boundaryIndex);
|
|
1438
1907
|
markForcedBreak();
|
|
1439
1908
|
hugClosingText();
|
|
1440
1909
|
drainPendingFloats();
|
|
1910
|
+
pendingFloats.push(...ownFloats);
|
|
1441
1911
|
};
|
|
1442
1912
|
/** On the closing page of a chapter nothing follows the page-span floats
|
|
1443
1913
|
* set at the foot of its last band: they move up to sit right under the
|
|
1444
1914
|
* band's text (one float gap below its grid-rounded bottom, stacked in
|
|
1445
1915
|
* their order) instead of leaving a gap between the text and a table at
|
|
1446
|
-
* the page foot.
|
|
1916
|
+
* the page foot. `layout.hugClosingFloats: false` leaves them where their
|
|
1917
|
+
* placement put them (EF-94). */
|
|
1447
1918
|
const hugClosingText = () => {
|
|
1919
|
+
if (!resolved.layout.hugClosingFloats)
|
|
1920
|
+
return;
|
|
1448
1921
|
const page = doc.pages[cursor.pageIndex];
|
|
1449
1922
|
const floats = page.floats ?? [];
|
|
1450
1923
|
// A side column holds its own band under a page-span float: leave it.
|
|
@@ -1472,8 +1945,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1472
1945
|
}
|
|
1473
1946
|
};
|
|
1474
1947
|
/** Parity of the page break a closed `:::part` still owes (applied before
|
|
1475
|
-
* the next placed block)
|
|
1476
|
-
|
|
1948
|
+
* the next placed block) — from the start when the part closed the
|
|
1949
|
+
* preceding content (`continuation.afterPartPage`). */
|
|
1950
|
+
let pendingPartBreak = afterPartPage && resolved.parts.breakAfter.enabled
|
|
1951
|
+
? resolved.parts.breakAfter.parity
|
|
1952
|
+
: null;
|
|
1477
1953
|
/** Content index of the closing fence of a part set without a page
|
|
1478
1954
|
* (`parts.page: false`): blocks up to it are skipped. */
|
|
1479
1955
|
let skipPartUntil = null;
|
|
@@ -1575,6 +2051,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1575
2051
|
doc.blocks.push(child);
|
|
1576
2052
|
}
|
|
1577
2053
|
};
|
|
2054
|
+
/** The widths a child's lines are counted at in a fragment `width` wide
|
|
2055
|
+
* that opens `from` (see `CalloutCut.widths`). */
|
|
2056
|
+
const cutWidths = (from, width) => {
|
|
2057
|
+
const before = from.line > 0 ? from.widths : undefined;
|
|
2058
|
+
if (!before || before.length === 0)
|
|
2059
|
+
return [{ fromLine: 0, width }];
|
|
2060
|
+
const last = before[before.length - 1];
|
|
2061
|
+
return Math.abs(last.width - width) <= 0.5 ? before : [...before, { fromLine: from.line, width }];
|
|
2062
|
+
};
|
|
1578
2063
|
const CUT_START = { child: 0, line: 0 };
|
|
1579
2064
|
const makeCalloutLayouter = (startIdx, plan, style) => {
|
|
1580
2065
|
const children = contentBlocks.slice(startIdx + 1, plan.endIdx);
|
|
@@ -1621,6 +2106,8 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1621
2106
|
style,
|
|
1622
2107
|
attrs: plan.attrs,
|
|
1623
2108
|
continuation,
|
|
2109
|
+
// Any range short of the box's end is a fragment that goes on.
|
|
2110
|
+
...(to.child < children.length || to.line > 0 ? { continues: true } : {}),
|
|
1624
2111
|
children: children.slice(from.child, toChild),
|
|
1625
2112
|
childStartIdx: startIdx + 1 + from.child,
|
|
1626
2113
|
width,
|
|
@@ -1629,8 +2116,9 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1629
2116
|
containerId: plan.containerId,
|
|
1630
2117
|
frameId,
|
|
1631
2118
|
nextChildId: () => `${frameId}-c${n++}`,
|
|
1632
|
-
|
|
2119
|
+
paragraphContainers,
|
|
1633
2120
|
...(from.line > 0 ? { lineFrom: from.line } : {}),
|
|
2121
|
+
...(from.line > 0 && from.widths ? { lineWidths: from.widths } : {}),
|
|
1634
2122
|
...(to.line > 0 ? { lineTo: to.line } : {}),
|
|
1635
2123
|
...(open.length > 0 ? { openNested: open } : {}),
|
|
1636
2124
|
mirrored,
|
|
@@ -1641,6 +2129,18 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1641
2129
|
/** Whether a page is a verso of mirrored margins (an `'outer'` corner
|
|
1642
2130
|
* icon hangs on the left there). */
|
|
1643
2131
|
const mirroredOf = (page) => resolved.page.margins.mirror === true && (page.index + pageIndexOffset + 1) % 2 === 0;
|
|
2132
|
+
/** Where a block's paint ends: its last line's foot (a list tail's box
|
|
2133
|
+
* bakes its bottom margin in and may reach past a cut with every line
|
|
2134
|
+
* inside it), else its box's. */
|
|
2135
|
+
const paintedBottom = (b) => {
|
|
2136
|
+
const last = b.lines[b.lines.length - 1];
|
|
2137
|
+
return last ? last.bbox.y + last.bbox.height : b.bbox.y + b.bbox.height;
|
|
2138
|
+
};
|
|
2139
|
+
/** Whether a band cut by a cap holds a block painted past the cut: one
|
|
2140
|
+
* that could not split across it (a paragraph tail the orphan and widow
|
|
2141
|
+
* minimums keep whole, a keep-together box) was force-placed into an
|
|
2142
|
+
* empty capped column and ran past its foot. */
|
|
2143
|
+
const capClipsLines = (cols) => cols.some((c) => c.bandCapped && c.blocks.some((b) => !b.hidden && paintedBottom(b) > c.bbox.y + c.bbox.height + CAPPED_OVERFLOW_PX));
|
|
1644
2144
|
/** Used bottom of a band, the float-only side column's stack included:
|
|
1645
2145
|
* a span block cuts under the side boxes already set there. */
|
|
1646
2146
|
const bandUsedBottomWithSide = (page, cols) => {
|
|
@@ -1654,8 +2154,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1654
2154
|
* children leaves on each side at least that many text lines or at least
|
|
1655
2155
|
* one indivisible block (a figure, table, display formula or nested
|
|
1656
2156
|
* box); a cut inside a text child counts every line on its side, a block
|
|
1657
|
-
* as one line,
|
|
1658
|
-
*
|
|
2157
|
+
* as one line, and leaves at least `layout.boxChildSplitMinLines` lines
|
|
2158
|
+
* of that child on each side (two by default, `splitMinLines` when that
|
|
2159
|
+
* is lower; EF-115). A nested box that may split
|
|
2160
|
+
* itself (`keepTogether: false`, or taller than a full column) offers the cuts
|
|
1659
2161
|
* inside it too, by its own `splitMinLines` over its own children; the
|
|
1660
2162
|
* fragments on both sides redraw its frame. The full layout's geometry
|
|
1661
2163
|
* ranks the candidates (box bottom = content bottom at the cut + the
|
|
@@ -1677,7 +2179,8 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1677
2179
|
};
|
|
1678
2180
|
if (full.units.length === 0)
|
|
1679
2181
|
return null;
|
|
1680
|
-
|
|
2182
|
+
// A head that goes on also carries the style's continuation marker.
|
|
2183
|
+
const tail = tailOf(full) + full.continuesMarkerPx;
|
|
1681
2184
|
/** Candidate cuts with the head's content bottom. */
|
|
1682
2185
|
const candidates = [];
|
|
1683
2186
|
/** Collect the cuts among `units` (one box's items; `below` = the tails
|
|
@@ -1707,12 +2210,21 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1707
2210
|
if (!indivisible(c) && c.lines.length > 1) {
|
|
1708
2211
|
// The first laid-out child of a continuation opens after
|
|
1709
2212
|
// `from.line` lines: cuts inside it are counted from the
|
|
1710
|
-
// child's own head.
|
|
2213
|
+
// child's own head. Each side of the box still counts all its
|
|
2214
|
+
// lines against `min`, and a cut inside a child leaves at least
|
|
2215
|
+
// `layout.boxChildSplitMinLines` lines of it on each side (2 by
|
|
2216
|
+
// default, so no lone line: EF-115), or `min` when that is
|
|
2217
|
+
// lower. The count is of this fragment's part of the child,
|
|
2218
|
+
// since a part left behind already passed the test.
|
|
2219
|
+
const childMin = Math.min(min, resolved.layout.boxChildSplitMinLines);
|
|
1711
2220
|
const lineBase = k === from.child ? from.line : 0;
|
|
1712
|
-
for (let l =
|
|
2221
|
+
for (let l = childMin; l <= c.lines.length - childMin; l++) {
|
|
1713
2222
|
const line = c.lines[l - 1];
|
|
1714
2223
|
if (bothHold(linesBefore + l, blocksBefore, false)) {
|
|
1715
|
-
candidates.push({
|
|
2224
|
+
candidates.push({
|
|
2225
|
+
cut: { child: k, line: lineBase + l, widths: k === from.child ? cutWidths(from, width) : [{ fromLine: 0, width }] },
|
|
2226
|
+
bottom: line.bbox.y + line.bbox.height + below,
|
|
2227
|
+
});
|
|
1716
2228
|
}
|
|
1717
2229
|
}
|
|
1718
2230
|
}
|
|
@@ -1866,13 +2378,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1866
2378
|
* the band can be cut for the box (level, or capped for it) and the
|
|
1867
2379
|
* figure fits between the cut and the band bottom; otherwise the figure
|
|
1868
2380
|
* stays pending for the ordinary slots. Returns whether any was set.
|
|
2381
|
+
* A floated box (`placement` on a `:::callout`) is no figure: it keeps
|
|
2382
|
+
* to the head or foot of a page, the bands it was floated to.
|
|
1869
2383
|
*/
|
|
1870
2384
|
const placeSpanFloatsAtCut = (page, capActiveHere) => {
|
|
1871
2385
|
let placedAny = false;
|
|
1872
2386
|
for (let i = 0; i < pendingFloats.length;) {
|
|
1873
2387
|
const f = pendingFloats[i];
|
|
1874
2388
|
const cols = bandColumns(page, currentBand(page, cursor));
|
|
1875
|
-
if (f.span !== 'page' || cols.length < 2 || heldBack(i) || !((capActiveHere && !placedAny) || levelForBox(cols))) {
|
|
2389
|
+
if (f.span !== 'page' || f.callout || cols.length < 2 || heldBack(i) || !((capActiveHere && !placedAny) || levelForBox(cols))) {
|
|
1876
2390
|
i++;
|
|
1877
2391
|
continue;
|
|
1878
2392
|
}
|
|
@@ -1897,8 +2411,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1897
2411
|
continue;
|
|
1898
2412
|
}
|
|
1899
2413
|
if (capActiveHere && !placedAny) {
|
|
2414
|
+
// A band whose content ran past the cut did not deliver its cap
|
|
2415
|
+
// (EF-116): the driver takes the cut lower instead.
|
|
2416
|
+
const overran = capClipsLines(cols);
|
|
1900
2417
|
uncapBand(cols, uncappedBottoms);
|
|
1901
|
-
|
|
2418
|
+
if (!overran)
|
|
2419
|
+
spanPlacedInBand.add(startIdx);
|
|
1902
2420
|
}
|
|
1903
2421
|
closeBandAndInsertSpan(page, cols, cutY, built.block, need, cursor, spacing, built.height);
|
|
1904
2422
|
// The float was built at (x, 0): move its inner geometry down to
|
|
@@ -1980,15 +2498,19 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1980
2498
|
const lines = bandCapLines(cols, baselineGrid);
|
|
1981
2499
|
const capBottom = bandTop(cols) + lines * baselineGrid;
|
|
1982
2500
|
const bandBottom = Math.min(...cols.map((c) => columnBottom(c, uncappedBottoms)));
|
|
1983
|
-
|
|
2501
|
+
// A floated box is never set at the cut (see
|
|
2502
|
+
// `placeSpanFloatsAtCut`): it only gets the trailing cap.
|
|
2503
|
+
const m = first.callout ? null : measureFloat(first.resourceId, page.contentArea.width, sliceOf(first));
|
|
1984
2504
|
const figureFits = m !== null && capBottom + needFor(floatGapPx, m.height, floatGapPx) <= bandBottom + 0.01;
|
|
1985
2505
|
if (figureFits) {
|
|
1986
2506
|
bandCapProposals.set(startIdx, {
|
|
1987
2507
|
kind: 'span',
|
|
1988
2508
|
startContentIndex: bandStart.contentIndex,
|
|
1989
2509
|
startPart: bandStart.part,
|
|
2510
|
+
...(bandStart.visit > 0 ? { startVisit: bandStart.visit } : {}),
|
|
1990
2511
|
lines,
|
|
1991
2512
|
retries: 0,
|
|
2513
|
+
...capHeadPx(cols),
|
|
1992
2514
|
});
|
|
1993
2515
|
}
|
|
1994
2516
|
else {
|
|
@@ -2056,8 +2578,15 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2056
2578
|
}
|
|
2057
2579
|
if (action) {
|
|
2058
2580
|
if (capActive) {
|
|
2581
|
+
// The box cuts where the band's content ends. When that content
|
|
2582
|
+
// ran past the cap's cut (a paragraph tail that could not split
|
|
2583
|
+
// over it, force-placed into an empty capped column), the cap is
|
|
2584
|
+
// not delivered: the driver takes the cut a line lower rather than
|
|
2585
|
+
// keep a cut as low as that one column (EF-116).
|
|
2586
|
+
const overran = capClipsLines(action.fit.cols);
|
|
2059
2587
|
uncapBand(action.fit.cols, uncappedBottoms);
|
|
2060
|
-
|
|
2588
|
+
if (!overran)
|
|
2589
|
+
spanPlacedInBand.add(startIdx);
|
|
2061
2590
|
}
|
|
2062
2591
|
const placed = action.kind === 'whole' ? action.result : action.fragment.result;
|
|
2063
2592
|
const to = action.kind === 'whole' ? L.end : action.fragment.to;
|
|
@@ -2066,10 +2595,14 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2066
2595
|
const spanCol = closeBandAndInsertSpan(page, action.fit.cols, action.fit.cutY, placed.frame, action.need, cursor, action.fit.spacing, placed.totalHeight);
|
|
2067
2596
|
commitCallout(placed, startIdx, plan, spanCol, part > 0 || action.kind === 'split' ? fragmentRange(L, from, to) : undefined);
|
|
2068
2597
|
// Floats first-referenced inside the box enqueue once its head is
|
|
2069
|
-
// committed, in reading order (same as the inline path)
|
|
2070
|
-
|
|
2598
|
+
// committed, in reading order (same as the inline path); those its
|
|
2599
|
+
// later parts cite wait for them.
|
|
2600
|
+
if (part === 0) {
|
|
2071
2601
|
for (let i = startIdx + 1; i <= plan.endIdx; i++)
|
|
2072
2602
|
enqueueFloatsFor(i);
|
|
2603
|
+
gateBoxCitations(startIdx + 1, plan.endIdx);
|
|
2604
|
+
}
|
|
2605
|
+
releaseBoxCitations(action.kind === 'whole' ? plan.endIdx + 1 : L.childBase + to.child);
|
|
2073
2606
|
// The new band starts on the grid right below the span column; nothing
|
|
2074
2607
|
// to snap — `need` already bakes in `marginBottom`.
|
|
2075
2608
|
pendingSpacing = 0;
|
|
@@ -2119,8 +2652,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2119
2652
|
kind: 'span',
|
|
2120
2653
|
startContentIndex: bandStart.contentIndex,
|
|
2121
2654
|
startPart: bandStart.part,
|
|
2655
|
+
...(bandStart.visit > 0 ? { startVisit: bandStart.visit } : {}),
|
|
2122
2656
|
lines,
|
|
2123
2657
|
retries: 0,
|
|
2658
|
+
...capHeadPx(cols),
|
|
2124
2659
|
});
|
|
2125
2660
|
proposedSpan = true;
|
|
2126
2661
|
}
|
|
@@ -2353,8 +2888,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2353
2888
|
const used = sideUsedBottom(side);
|
|
2354
2889
|
const raw = Math.max(used, refY ?? used);
|
|
2355
2890
|
const gridUpSide = (v) => page.contentArea.y + Math.ceil((v - page.contentArea.y - 0.01) / baselineGrid) * baselineGrid;
|
|
2356
|
-
let y = gridUpSide(raw);
|
|
2357
2891
|
const below = Math.max(result.marginBottomPx, floatGapPx);
|
|
2892
|
+
// Clear of the heading-design elements standing in the column (EF-78).
|
|
2893
|
+
const obstacles = sideObstacles.get(side);
|
|
2894
|
+
let y = clearSideObstacles(gridUpSide(raw), result.totalHeight, obstacles, below, gridUpSide);
|
|
2358
2895
|
let need = y - used + result.totalHeight + below;
|
|
2359
2896
|
// The gap under the box is owed only to what follows: a box whose foot
|
|
2360
2897
|
// lands on the column's foot needs none.
|
|
@@ -2362,16 +2899,17 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2362
2899
|
if (!fits() && refY !== undefined && raw > used + 0.5) {
|
|
2363
2900
|
// Beside its text the box runs off the column: it slides up — as far
|
|
2364
2901
|
// as the stack above allows — to the lowest position that fits, its
|
|
2365
|
-
// foot on the column's foot (the bottom-aligned marginal box)
|
|
2902
|
+
// foot on the column's foot (the bottom-aligned marginal box), unless
|
|
2903
|
+
// a heading design stands there.
|
|
2366
2904
|
const foot = side.bbox.y + side.bbox.height;
|
|
2367
2905
|
const fit = page.contentArea.y + Math.floor((foot - result.totalHeight - page.contentArea.y + 0.01) / baselineGrid) * baselineGrid;
|
|
2368
|
-
if (fit >= used - 0.01) {
|
|
2906
|
+
if (fit >= used - 0.01 && clearSideObstacles(fit, result.totalHeight, obstacles, below, gridUpSide) === fit) {
|
|
2369
2907
|
y = fit;
|
|
2370
2908
|
need = y - used + result.totalHeight + below;
|
|
2371
2909
|
}
|
|
2372
2910
|
}
|
|
2373
2911
|
if (!fits()) {
|
|
2374
|
-
if (mode === 'strict' || used > side.bbox.y + 0.5)
|
|
2912
|
+
if (mode === 'strict' || used > side.bbox.y + 0.5 || (obstacles?.length ?? 0) > 0)
|
|
2375
2913
|
return false;
|
|
2376
2914
|
// A box that a full side column would hold waits for one rather than
|
|
2377
2915
|
// overflowing an empty column a band above has shortened.
|
|
@@ -2409,11 +2947,93 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2409
2947
|
return false;
|
|
2410
2948
|
const curCol = currentColumn(doc, cursor);
|
|
2411
2949
|
const refY = curCol.bbox.y + (curCol.bbox.height - curCol.availableHeight) + (curCol.blocks.length > 0 ? pendingSpacing : 0);
|
|
2950
|
+
// `sideAtColumnEnd: 'after'` (EF-161): the box waits for the text after
|
|
2951
|
+
// its fence, and stands beside it when the break rules send that text
|
|
2952
|
+
// on to the next page (a column with no room left, a paragraph the
|
|
2953
|
+
// widow and orphan rules move whole, a heading kept with its text). A
|
|
2954
|
+
// side box fenced while one waits waits too, so the side column keeps
|
|
2955
|
+
// their order. A box nothing follows in its chapter stays with the
|
|
2956
|
+
// text before it, like any box with `'before'` (postext 1.4's rule).
|
|
2957
|
+
// A box fenced again after a keep-with-next rewind is already waiting.
|
|
2958
|
+
if (awaitingSideBoxes.some((w) => w.box.startIdx === startIdx))
|
|
2959
|
+
return true;
|
|
2960
|
+
if (awaitingSideBoxes.length > 0 || (style.sideAtColumnEnd === 'after' && !nextIsBarrier(plan.endIdx + 1))) {
|
|
2961
|
+
awaitingSideBoxes.push({ box: { startIdx, plan, style }, pageIndex: page.index, band: currentBand(page, cursor), column: curCol, refY });
|
|
2962
|
+
return true;
|
|
2963
|
+
}
|
|
2412
2964
|
if (!trySideBox(page, side, { startIdx, plan, style }, refY, 'strict')) {
|
|
2413
2965
|
pendingSideBoxes.push({ startIdx, plan, style });
|
|
2414
2966
|
}
|
|
2415
2967
|
return true;
|
|
2416
2968
|
};
|
|
2969
|
+
/** The first block after content index `endIdx` that the flow holds in a
|
|
2970
|
+
* column: the first part of the block that follows a side box's fence. */
|
|
2971
|
+
const firstFlowBlockAfter = (endIdx) => {
|
|
2972
|
+
let first;
|
|
2973
|
+
for (const b of doc.blocks) {
|
|
2974
|
+
const ci = b.contentIndex;
|
|
2975
|
+
if (ci === undefined || ci <= endIdx || (first !== undefined && first.contentIndex <= ci))
|
|
2976
|
+
continue;
|
|
2977
|
+
const col = doc.pages[b.pageIndex]?.columns[b.columnIndex];
|
|
2978
|
+
if (!col || col.kind === 'side' || !col.blocks.includes(b))
|
|
2979
|
+
continue;
|
|
2980
|
+
first = b;
|
|
2981
|
+
}
|
|
2982
|
+
return first;
|
|
2983
|
+
};
|
|
2984
|
+
/**
|
|
2985
|
+
* Set the side boxes waiting for the text after their fence
|
|
2986
|
+
* (`awaitingSideBoxes`), in order. Called once the flow holds a block
|
|
2987
|
+
* after the last of them that it will not take back — a heading kept
|
|
2988
|
+
* with its text can still move on with it — and with `force` where the
|
|
2989
|
+
* flow segment ends. A box with `sideAtColumnEnd: 'after'` whose text
|
|
2990
|
+
* went on in another column or on a later page stands level with that
|
|
2991
|
+
* text's first block, in the side column there; every other box stands
|
|
2992
|
+
* at its fence, as it would have been set there. One the side column
|
|
2993
|
+
* cannot hold takes the side column of a later page.
|
|
2994
|
+
*/
|
|
2995
|
+
const settleAwaitingSideBoxes = (force) => {
|
|
2996
|
+
if (awaitingSideBoxes.length === 0)
|
|
2997
|
+
return;
|
|
2998
|
+
const lastEnd = awaitingSideBoxes[awaitingSideBoxes.length - 1].box.plan.endIdx;
|
|
2999
|
+
if (!force) {
|
|
3000
|
+
const held = doc.blocks.some((b) => {
|
|
3001
|
+
if ((b.contentIndex ?? -1) <= lastEnd || isFreeHeading(b))
|
|
3002
|
+
return false;
|
|
3003
|
+
const col = doc.pages[b.pageIndex]?.columns[b.columnIndex];
|
|
3004
|
+
return col !== undefined && col.kind !== 'side' && col.blocks.includes(b);
|
|
3005
|
+
});
|
|
3006
|
+
if (!held)
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
for (const w of awaitingSideBoxes.splice(0)) {
|
|
3010
|
+
let { pageIndex, band, refY } = w;
|
|
3011
|
+
if (w.box.style.sideAtColumnEnd === 'after') {
|
|
3012
|
+
const anchor = firstFlowBlockAfter(w.box.plan.endIdx);
|
|
3013
|
+
const anchorPage = anchor ? doc.pages[anchor.pageIndex] : undefined;
|
|
3014
|
+
const col = anchorPage?.columns[anchor.columnIndex];
|
|
3015
|
+
if (anchorPage && col && col !== w.column && col.kind !== 'span' && sideColumnOf(anchorPage, col.band ?? 0)) {
|
|
3016
|
+
pageIndex = anchorPage.index;
|
|
3017
|
+
band = col.band ?? 0;
|
|
3018
|
+
refY = anchor.bbox.y;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
const page = doc.pages[pageIndex];
|
|
3022
|
+
const side = sideColumnOf(page, band);
|
|
3023
|
+
if (side && trySideBox(page, side, w.box, refY, 'strict'))
|
|
3024
|
+
continue;
|
|
3025
|
+
// The pages the flow has opened since: their side columns, from the
|
|
3026
|
+
// top, as `flushSideBoxesIntoPage` would have set it there.
|
|
3027
|
+
let placed = false;
|
|
3028
|
+
for (let p = pageIndex + 1; p <= cursor.pageIndex && !placed; p++) {
|
|
3029
|
+
const later = doc.pages[p];
|
|
3030
|
+
const laterSide = sideColumnOf(later, 0);
|
|
3031
|
+
placed = laterSide !== undefined && trySideBox(later, laterSide, w.box, undefined, 'fresh');
|
|
3032
|
+
}
|
|
3033
|
+
if (!placed)
|
|
3034
|
+
pendingSideBoxes.push(w.box);
|
|
3035
|
+
}
|
|
3036
|
+
};
|
|
2417
3037
|
/** Set the waiting side boxes in the side column of a freshly opened
|
|
2418
3038
|
* page, in order; one that does not fit an empty column is set anyway. */
|
|
2419
3039
|
const flushSideBoxesIntoPage = (page) => {
|
|
@@ -2458,6 +3078,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2458
3078
|
// Fixed boxes leave the flow entirely.
|
|
2459
3079
|
if (placement === 'fixed') {
|
|
2460
3080
|
placeCalloutFixed(startIdx, plan, style, layoutAt);
|
|
3081
|
+
calloutsOutOfFlow.add(startIdx);
|
|
2461
3082
|
return undefined;
|
|
2462
3083
|
}
|
|
2463
3084
|
// Floated boxes leave it too: they take the first free band after
|
|
@@ -2475,6 +3096,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2475
3096
|
&& splitCalloutFragment(L, CUT_START, width, contentArea.height, 'float-probe', false, style.splitMinLines) !== null;
|
|
2476
3097
|
if (!inFlow) {
|
|
2477
3098
|
enqueueCalloutFloat(startIdx, plan, style, L, placement, span);
|
|
3099
|
+
calloutsOutOfFlow.add(startIdx);
|
|
2478
3100
|
return undefined;
|
|
2479
3101
|
}
|
|
2480
3102
|
}
|
|
@@ -2491,6 +3113,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2491
3113
|
// Side boxes stack in the float-only side column beside the text
|
|
2492
3114
|
// (whatever their placement: a side box never floats to a band).
|
|
2493
3115
|
if (span === 'side' && placeCalloutSide(startIdx, plan, style)) {
|
|
3116
|
+
calloutsOutOfFlow.add(startIdx);
|
|
2494
3117
|
return undefined;
|
|
2495
3118
|
}
|
|
2496
3119
|
}
|
|
@@ -2500,6 +3123,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2500
3123
|
/** Times the box left an EMPTY short column (see below) — bounded. */
|
|
2501
3124
|
let shortColumnMoves = 0;
|
|
2502
3125
|
for (;;) {
|
|
3126
|
+
// A box (or the rest of a split one) that opens a band applies the
|
|
3127
|
+
// band's cap before it measures its room, as a paragraph does: sized
|
|
3128
|
+
// against the uncut column, a continuation ran past the cut of a
|
|
3129
|
+
// band levelled for a page-span box after it (EF-126).
|
|
3130
|
+
enterBand(startIdx, part);
|
|
2503
3131
|
let curCol = currentColumn(doc, cursor);
|
|
2504
3132
|
const continuation = part > 0;
|
|
2505
3133
|
const result = L.layoutRange(from, L.end, curCol.bbox.width, frameId, continuation, mirroredOf(doc.pages[cursor.pageIndex]));
|
|
@@ -2511,7 +3139,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2511
3139
|
const balanceBefore = curCol.blocks.length > 0 || reservedOf(curCol).top > 0
|
|
2512
3140
|
? (balanceExtraPx?.get(balanceKey(startIdx, part)) ?? 0)
|
|
2513
3141
|
: 0;
|
|
2514
|
-
const spacing = (curCol.blocks.length === 0 ? 0 : Math.max(
|
|
3142
|
+
const spacing = (curCol.blocks.length === 0 ? 0 : Math.max(pendingBeforeSelfSnapping(startIdx), result.marginTopPx)) + balanceBefore;
|
|
2515
3143
|
const roomPx = curCol.availableHeight - spacing;
|
|
2516
3144
|
let fragment = null;
|
|
2517
3145
|
if (result.totalHeight > roomPx + 0.01) {
|
|
@@ -2559,7 +3187,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2559
3187
|
}
|
|
2560
3188
|
pendingSpacing = 0;
|
|
2561
3189
|
if (part === 0 && resolved.headings.keepWithNext && run > 0 && run < curCol.blocks.length) {
|
|
2562
|
-
const rolledBack =
|
|
3190
|
+
const rolledBack = rollbackHeadings(curCol);
|
|
2563
3191
|
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2564
3192
|
return (rolledBack[0].contentIndex ?? startIdx - rolledBack.length) - 1;
|
|
2565
3193
|
}
|
|
@@ -2582,27 +3210,33 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2582
3210
|
}
|
|
2583
3211
|
// Floats first-referenced inside the box still enqueue in reading order
|
|
2584
3212
|
// (only once the box is committed, so a keep-with-next replay does not
|
|
2585
|
-
// enqueue them twice)
|
|
2586
|
-
|
|
3213
|
+
// enqueue them twice); those a later part of a split box cites wait
|
|
3214
|
+
// for that part.
|
|
3215
|
+
if (part === 0) {
|
|
2587
3216
|
for (let i = startIdx + 1; i <= plan.endIdx; i++)
|
|
2588
3217
|
enqueueFloatsFor(i);
|
|
3218
|
+
gateBoxCitations(startIdx + 1, plan.endIdx);
|
|
3219
|
+
}
|
|
2589
3220
|
const placed = fragment ? fragment.result : result;
|
|
2590
3221
|
const to = fragment ? fragment.to : L.end;
|
|
2591
3222
|
if (part > 0 || fragment)
|
|
2592
3223
|
markFragment(placed, part, fragment !== null);
|
|
2593
|
-
const spacingBefore = (curCol.blocks.length === 0 ? 0 : Math.max(
|
|
2594
|
-
if (balanceBefore > 0)
|
|
3224
|
+
const spacingBefore = (curCol.blocks.length === 0 ? 0 : Math.max(pendingBeforeSelfSnapping(startIdx), placed.marginTopPx)) + balanceBefore;
|
|
3225
|
+
if (balanceBefore > 0) {
|
|
2595
3226
|
addBalanceExtra(curCol, balanceBefore);
|
|
3227
|
+
placed.frame.balancing = { levers: ['trailingCallout'], spaceAbove: balanceBefore };
|
|
3228
|
+
}
|
|
2596
3229
|
// Spacing collapses at a column top, so the lever's push under a
|
|
2597
3230
|
// float band is consumed here: the box then opens that far down.
|
|
2598
3231
|
if (balanceBefore > 0 && curCol.blocks.length === 0) {
|
|
2599
3232
|
curCol.availableHeight = Math.max(0, curCol.availableHeight - balanceBefore);
|
|
2600
3233
|
}
|
|
2601
|
-
enterBand(startIdx,
|
|
3234
|
+
enterBand(startIdx, part);
|
|
2602
3235
|
placeAtomicBlock(placed.frame, placed.totalHeight, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
2603
|
-
enterBand(startIdx,
|
|
3236
|
+
enterBand(startIdx, part);
|
|
2604
3237
|
curCol = currentColumn(doc, cursor);
|
|
2605
3238
|
commitCallout(placed, startIdx, plan, curCol, part > 0 || fragment ? fragmentRange(L, from, to) : undefined);
|
|
3239
|
+
releaseBoxCitations(fragment ? L.childBase + to.child : plan.endIdx + 1);
|
|
2606
3240
|
// Snap the flow after the box to the baseline grid, baking in at least
|
|
2607
3241
|
// `marginBottom` (grid wins, margin is a minimum — the resource rule).
|
|
2608
3242
|
// An off-grid style (`snapToGrid: false`) keeps its exact margin
|
|
@@ -2625,6 +3259,40 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2625
3259
|
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2626
3260
|
}
|
|
2627
3261
|
};
|
|
3262
|
+
/**
|
|
3263
|
+
* `bodyText.keepColonWithList` with `colonListRoom: 'item'` (EF-110):
|
|
3264
|
+
* whether the first item of the list after the lead-in paragraph
|
|
3265
|
+
* `leadIdx` can start in `roomPx` of column under it — it fits whole, or
|
|
3266
|
+
* the orphan and widow rules for lists would leave some of its lines
|
|
3267
|
+
* there, as they do when it is set.
|
|
3268
|
+
*/
|
|
3269
|
+
const firstListItemStarts = (leadIdx, roomPx, width, ctx) => {
|
|
3270
|
+
let idx = leadIdx + 1;
|
|
3271
|
+
while (idx < contentBlocks.length && isMarkerBlock(contentBlocks[idx]))
|
|
3272
|
+
idx++;
|
|
3273
|
+
const raw = contentBlocks[idx];
|
|
3274
|
+
if (!raw || raw.type !== 'listItem')
|
|
3275
|
+
return true;
|
|
3276
|
+
const item = measureContentBlock(raw, idx, width, ctx);
|
|
3277
|
+
if (!item)
|
|
3278
|
+
return true;
|
|
3279
|
+
const lines = item.measured.lines.length;
|
|
3280
|
+
const fit = Math.floor((roomPx + FIT_EPS) / item.kind.style.lineHeightPx);
|
|
3281
|
+
if (lines <= fit)
|
|
3282
|
+
return true;
|
|
3283
|
+
if (fit < 1)
|
|
3284
|
+
return false;
|
|
3285
|
+
const body = resolved.bodyText;
|
|
3286
|
+
return chooseParagraphSplit(lines, fit, {
|
|
3287
|
+
avoidOrphans: body.avoidOrphans && body.avoidOrphansInLists,
|
|
3288
|
+
orphanMinLines: body.orphanMinLines,
|
|
3289
|
+
orphanPenalty: body.orphanPenalty,
|
|
3290
|
+
avoidWidows: body.avoidWidows && body.avoidWidowsInLists,
|
|
3291
|
+
widowMinLines: body.widowMinLines,
|
|
3292
|
+
widowPenalty: body.widowPenalty,
|
|
3293
|
+
slackWeight: body.slackWeight,
|
|
3294
|
+
}).splitAt > 0;
|
|
3295
|
+
};
|
|
2628
3296
|
for (let blockIdx = 0; blockIdx < contentBlocks.length; blockIdx++) {
|
|
2629
3297
|
if (options?.shouldCancel?.())
|
|
2630
3298
|
throw new BuildCancelledError();
|
|
@@ -2639,7 +3307,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2639
3307
|
// free slot of the current page now — after their reference in reading
|
|
2640
3308
|
// order. Then enqueue the floats first-referenced in this block, so the
|
|
2641
3309
|
// next page opened while placing it (or any later block) reserves their
|
|
2642
|
-
// band and the next iteration offers them the slots that follow.
|
|
3310
|
+
// band and the next iteration offers them the slots that follow. A text
|
|
3311
|
+
// block holds its floats back until the line citing them is placed
|
|
3312
|
+
// (`gateCitedFloats`).
|
|
3313
|
+
settleAwaitingSideBoxes(false);
|
|
3314
|
+
releaseStaleCitationGates(blockIdx);
|
|
2643
3315
|
tryPlacePendingFloatsOnCurrentPage(spanBoxAt(blockIdx), blockIdx);
|
|
2644
3316
|
enqueueFloatsFor(blockIdx);
|
|
2645
3317
|
// --- Directives ----------------------------------------------------
|
|
@@ -2693,8 +3365,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2693
3365
|
}
|
|
2694
3366
|
else if (name === 'numbering') {
|
|
2695
3367
|
const change = {};
|
|
2696
|
-
|
|
2697
|
-
|
|
3368
|
+
// Any spelling of a format (`roman-lower`, `arabic`, `i`…); an
|
|
3369
|
+
// unknown one keeps the current format.
|
|
3370
|
+
const fmt = parseNumberFormat(attrs.format);
|
|
3371
|
+
if (fmt)
|
|
2698
3372
|
change.format = fmt;
|
|
2699
3373
|
if (attrs.startAt !== undefined) {
|
|
2700
3374
|
const n = Number(attrs.startAt);
|
|
@@ -2711,9 +3385,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2711
3385
|
// --- Container markers ---------------------------------------------
|
|
2712
3386
|
// `:::paragraphs` applies its style's top margin on entry through the
|
|
2713
3387
|
// pending-spacing mechanism (collapses like any margin, vanishes at a
|
|
2714
|
-
// column top). Its bottom margin is normally
|
|
2715
|
-
// paragraph
|
|
2716
|
-
//
|
|
3388
|
+
// column top). Its bottom margin is normally set by the last
|
|
3389
|
+
// paragraph (baked into its grid snap, or carried past it with
|
|
3390
|
+
// `paragraphContainerSpacing: 'collapse'`); the pending-spacing
|
|
3391
|
+
// fallback covers containers that end with a non-paragraph block. Replaying a marker after a keep-with-next rewind is
|
|
2717
3392
|
// harmless: the container plan is index-based, and `max` is idempotent.
|
|
2718
3393
|
// `:::part`: the opener lives on a dedicated single-column page. On
|
|
2719
3394
|
// entry, break to a fresh page of the configured parity and convert it
|
|
@@ -2813,6 +3488,13 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2813
3488
|
const pc = rawBlock.containerId !== undefined
|
|
2814
3489
|
? paragraphContainers.byId.get(rawBlock.containerId)
|
|
2815
3490
|
: undefined;
|
|
3491
|
+
// What a container tail still owes passes through the closing
|
|
3492
|
+
// markers right after it (see `inlineGapOwed`).
|
|
3493
|
+
const owed = rawBlock.type === 'containerEnd' && inlineGapOwed?.container
|
|
3494
|
+
&& inlineGapOwed.afterIdx === blockIdx - 1 && pendingSpacing === inlineGapOwed.pending
|
|
3495
|
+
? inlineGapOwed
|
|
3496
|
+
: null;
|
|
3497
|
+
let owedExact = owed?.exact ?? 0;
|
|
2816
3498
|
if (pc) {
|
|
2817
3499
|
// Margins collapse with the pending spacing; a negative one pulls
|
|
2818
3500
|
// the flow up past it instead (the container starts inside the
|
|
@@ -2823,9 +3505,13 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2823
3505
|
pendingSpacing = collapse(pc.marginTopPx);
|
|
2824
3506
|
}
|
|
2825
3507
|
else if (contentBlocks[blockIdx - 1]?.type !== 'paragraph') {
|
|
2826
|
-
|
|
3508
|
+
const margin = pc.marginBottomPx;
|
|
3509
|
+
owedExact = margin < 0 ? owedExact + margin : Math.max(owedExact, margin);
|
|
3510
|
+
pendingSpacing = collapse(margin);
|
|
2827
3511
|
}
|
|
2828
3512
|
}
|
|
3513
|
+
if (owed)
|
|
3514
|
+
inlineGapOwed = { afterIdx: blockIdx, pending: pendingSpacing, exact: owedExact, container: true };
|
|
2829
3515
|
continue;
|
|
2830
3516
|
}
|
|
2831
3517
|
// --- Heading `breakBefore` ----------------------------------------
|
|
@@ -2874,8 +3560,24 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2874
3560
|
// to place inline (empty text, unknown resource id, floated resource).
|
|
2875
3561
|
const col = currentColumn(doc, cursor);
|
|
2876
3562
|
const blockMeasureCtx = partPlan.byBlock[blockIdx] ? partMeasureCtx : sectionMeasureCtx(sectionPlan.byBlock[blockIdx]);
|
|
3563
|
+
/** The space under a container that closes on a paragraph merges with
|
|
3564
|
+
* the next block's own (`bodyText.paragraphContainerSpacing:
|
|
3565
|
+
* 'collapse'`, EF-159 / EF-181): it is at least the paragraph spacing
|
|
3566
|
+
* of the text around the container, and a snapped tail carries it as
|
|
3567
|
+
* pending spacing instead of baking it into the snap. A negative
|
|
3568
|
+
* `marginBottom` pulls the flow up as in 1.4. A container that closes
|
|
3569
|
+
* on anything else (a list) is set as in 1.4 under either rule: the
|
|
3570
|
+
* block keeps its own space in its snap, and the container's
|
|
3571
|
+
* `marginBottom` follows as pending spacing from the closing marker. */
|
|
3572
|
+
const containerTail = isContainerTail && rawBlock.type === 'paragraph' ? paragraphContainer : undefined;
|
|
3573
|
+
const collapseTail = containerTail !== undefined
|
|
3574
|
+
&& resolved.bodyText.paragraphContainerSpacing === 'collapse'
|
|
3575
|
+
&& containerTail.tailStyle.marginBottomPx >= 0;
|
|
3576
|
+
const tailStyle = collapseTail && blockMeasureCtx.bodyStyle.marginBottomPx > containerTail.tailStyle.marginBottomPx
|
|
3577
|
+
? { ...containerTail.tailStyle, marginBottomPx: blockMeasureCtx.bodyStyle.marginBottomPx }
|
|
3578
|
+
: containerTail?.tailStyle;
|
|
2877
3579
|
const styleOverride = paragraphContainer
|
|
2878
|
-
? (isContainerTail ? paragraphContainer.tailStyle : paragraphContainer.style)
|
|
3580
|
+
? (isContainerTail ? tailStyle ?? paragraphContainer.tailStyle : paragraphContainer.style)
|
|
2879
3581
|
: undefined;
|
|
2880
3582
|
// Column balancing "run a paragraph long": the loose path is taken only
|
|
2881
3583
|
// for the paragraphs the driver asked for, so the common case keeps its
|
|
@@ -2890,9 +3592,16 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2890
3592
|
&& knownOutcome === undefined
|
|
2891
3593
|
&& (looseGained.get(budget.group) ?? 0) >= budget.need;
|
|
2892
3594
|
const tryLoose = looseLines !== undefined && !budgetMet && knownOutcome !== null;
|
|
3595
|
+
// A page-span heading without a design of its own is painted by the
|
|
3596
|
+
// default opener across the content area: measured at that width, its
|
|
3597
|
+
// band holds the title as it is painted (EF-100). A designed opener
|
|
3598
|
+
// keeps the column-width measure: its design sets the band.
|
|
3599
|
+
const measureWidth = rawBlock.type === 'heading' && opensDefaultOpener(rawBlock)
|
|
3600
|
+
? doc.pages[cursor.pageIndex].contentArea.width
|
|
3601
|
+
: col.bbox.width;
|
|
2893
3602
|
let measuredBlock = tryLoose
|
|
2894
3603
|
? measureLooseParagraph(rawBlock, blockIdx, col.bbox.width, blockMeasureCtx, styleOverride, looseLines, trackingLadder, looseOutcome)
|
|
2895
|
-
: measureContentBlock(rawBlock, blockIdx,
|
|
3604
|
+
: measureContentBlock(rawBlock, blockIdx, measureWidth, blockMeasureCtx, { styleOverride });
|
|
2896
3605
|
// Screen pages (`layout.fitFiguresToPage`): an inline figure a little
|
|
2897
3606
|
// too tall for the room left in its column — under an opener band, say
|
|
2898
3607
|
// — is set smaller to stay with its text rather than leave the rest of
|
|
@@ -2930,8 +3639,16 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2930
3639
|
}
|
|
2931
3640
|
if (!measuredBlock)
|
|
2932
3641
|
continue;
|
|
2933
|
-
const { kind, contentBlock, measured, prefixLen, absoluteSourceMap, mathDisplayRender
|
|
2934
|
-
|
|
3642
|
+
const { kind, contentBlock, measured, prefixLen, absoluteSourceMap, mathDisplayRender } = measuredBlock;
|
|
3643
|
+
/** Tracking the block's lines are set with; a paragraph moved whole into
|
|
3644
|
+
* a column of another width is measured again, and may change it. */
|
|
3645
|
+
let { letterSpacingPx } = measuredBlock;
|
|
3646
|
+
const { vdtType, headingLevel, numberPrefix, headingNumber, listBullet, listDepth, listKind, bulletXOffsetInColumn, strikethroughText } = kind;
|
|
3647
|
+
// A structural heading (`hidden`) is placed like any heading — its
|
|
3648
|
+
// break, keep-with-next and grid snap apply — but with no line height
|
|
3649
|
+
// and no margins: it takes no room, and renderers skip it.
|
|
3650
|
+
const hiddenHeading = vdtType === 'heading' && headingIsHidden(rawBlock, headingLevels.forBlock(rawBlock));
|
|
3651
|
+
const style = hiddenHeading ? { ...kind.style, lineHeightPx: 0, marginTopPx: 0, marginBottomPx: 0 } : kind.style;
|
|
2935
3652
|
// --- Resource blocks (image / svg / table + caption) -----------------
|
|
2936
3653
|
// Placed atomically (kept-together) — no mid-content split for v1.
|
|
2937
3654
|
if (vdtType === 'resource') {
|
|
@@ -2959,7 +3676,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2959
3676
|
// float would, unless the block before asked for more.
|
|
2960
3677
|
const spacingBefore = Math.max(pendingSpacing, floatGapPx);
|
|
2961
3678
|
enterBand(blockIdx, 0);
|
|
2962
|
-
placeAtomicBlock(blk, groupHeight, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
3679
|
+
placeAtomicBlock(blk, groupHeight, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx, onAtomicNewPage(groupHeight));
|
|
2963
3680
|
enterBand(blockIdx, 0);
|
|
2964
3681
|
// `placeBlockInColumn` (inside placeAtomicBlock) shifts `blk.lines`; the
|
|
2965
3682
|
// resource's own caption/table lines live on `resourceBlock` and must be
|
|
@@ -2970,14 +3687,25 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2970
3687
|
// group height is arbitrary), baking in at least marginBottom — same
|
|
2971
3688
|
// convention as snapped headings — so the following text lands back on
|
|
2972
3689
|
// the global grid instead of inheriting the resource's offset.
|
|
3690
|
+
// The white under it then makes up the float gap, as above it (EF-93):
|
|
3691
|
+
// what the snap left short of that gap, in whole grid lines, is carried
|
|
3692
|
+
// as pending spacing, so it collapses with the next block's own space
|
|
3693
|
+
// above — a heading's, a list's or a box's top margin, the next inline
|
|
3694
|
+
// resource's float gap — and vanishes at a column foot, where the
|
|
3695
|
+
// column is full either way. `layout.inlineResourceGap: 'above'` keeps
|
|
3696
|
+
// 1.4's rule, the snap alone.
|
|
2973
3697
|
{
|
|
2974
3698
|
const rCol = currentColumn(doc, cursor);
|
|
2975
3699
|
const usedHeight = rCol.bbox.height - rCol.availableHeight;
|
|
2976
3700
|
const naturalBottom = usedHeight + style.marginBottomPx;
|
|
2977
3701
|
const snappedBottom = Math.ceil((naturalBottom - 0.01) / baselineGrid) * baselineGrid;
|
|
2978
3702
|
rCol.availableHeight = Math.max(0, rCol.bbox.height - snappedBottom);
|
|
3703
|
+
const owed = resolved.layout.inlineResourceGap === 'above'
|
|
3704
|
+
? 0
|
|
3705
|
+
: floatGapPx - (snappedBottom - usedHeight);
|
|
3706
|
+
pendingSpacing = owed > 0.01 ? Math.ceil((owed - 0.01) / baselineGrid) * baselineGrid : 0;
|
|
3707
|
+
inlineGapOwed = pendingSpacing > 0 ? { afterIdx: blockIdx, pending: pendingSpacing, exact: owed } : null;
|
|
2979
3708
|
}
|
|
2980
|
-
pendingSpacing = 0;
|
|
2981
3709
|
flushPendingNumberingAtBoundary();
|
|
2982
3710
|
continue;
|
|
2983
3711
|
}
|
|
@@ -3007,15 +3735,25 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3007
3735
|
blk.strikethroughText = true;
|
|
3008
3736
|
// Bullet Y = x-height midpoint of the item's first text line.
|
|
3009
3737
|
// Pairs with `textBaseline='middle'` at render so the bullet stays
|
|
3010
|
-
// visually centered on the text regardless of its own font size.
|
|
3738
|
+
// visually centered on the text regardless of its own font size. A
|
|
3739
|
+
// contents number sits on the line's baseline instead.
|
|
3011
3740
|
const firstLine = blk.lines[0];
|
|
3012
3741
|
if (firstLine) {
|
|
3013
|
-
|
|
3742
|
+
const at = listBulletPosition(listBullet, firstLine.baseline);
|
|
3743
|
+
blk.bulletY = at.bulletY;
|
|
3744
|
+
if (at.bulletBaselineY !== undefined)
|
|
3745
|
+
blk.bulletBaselineY = at.bulletBaselineY;
|
|
3014
3746
|
}
|
|
3015
3747
|
};
|
|
3016
3748
|
// Neighbour lookaheads see through container markers.
|
|
3017
3749
|
const nextBlock = nextNonMarkerBlock(contentBlocks, blockIdx) ?? null;
|
|
3018
3750
|
const nextIsListItem = nextBlock?.type === 'listItem';
|
|
3751
|
+
/** The space before the next item of the list (EF-165): the item
|
|
3752
|
+
* spacing of the shallower list, so the item after a nested list
|
|
3753
|
+
* takes its own list's spacing, not the nested one's. */
|
|
3754
|
+
const itemGapPx = (bullet) => (nextBlock?.type === 'listItem'
|
|
3755
|
+
? listItemGapPx({ spacingPx: bullet.itemSpacingPx, depth: listDepth ?? 1 }, { spacingPx: listItemSpacingPx(nextBlock, blockMeasureCtx.resolved), depth: nextBlock.depth ?? 1 })
|
|
3756
|
+
: bullet.itemSpacingPx);
|
|
3019
3757
|
// For headings, only snap to baseline grid if the next block is NOT a heading.
|
|
3020
3758
|
// Consecutive headings flow without grid snapping; the last heading in the
|
|
3021
3759
|
// group snaps so that the following body text realigns with the grid.
|
|
@@ -3026,10 +3764,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3026
3764
|
// are off-grid by design.
|
|
3027
3765
|
const nextIsHeading = nextBlock?.type === 'heading';
|
|
3028
3766
|
// The contents (`:::toc`) keep their own rhythm: an entry set as a list
|
|
3029
|
-
// item is not a list tail to realign the text after it.
|
|
3030
|
-
|
|
3767
|
+
// item is not a list tail to realign the text after it. A heading snaps
|
|
3768
|
+
// per its level (or style), which inherits `headings.snapToGrid`.
|
|
3769
|
+
const shouldSnapToGrid = rawBlock.toc === undefined && ((vdtType === 'heading' && !nextIsHeading
|
|
3770
|
+
&& (headingLevels.forBlock(rawBlock)?.snapToGrid ?? resolved.headings.snapToGrid)) ||
|
|
3031
3771
|
(vdtType === 'listItem' && !nextIsListItem) ||
|
|
3032
|
-
(vdtType === 'paragraph' && isContainerTail) ||
|
|
3772
|
+
(vdtType === 'paragraph' && isContainerTail && paragraphContainer?.snapToGrid !== false) ||
|
|
3033
3773
|
vdtType === 'mathDisplay');
|
|
3034
3774
|
// Place block, splitting across columns/pages if needed.
|
|
3035
3775
|
// List items may split too — orphan/widow protection per-list is gated by
|
|
@@ -3039,10 +3779,42 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3039
3779
|
// joints, a last word that cannot come up) is set ragged, not stretched.
|
|
3040
3780
|
let remainingLines = [...raggedLooseLines(measured.lines, style.textAlign)];
|
|
3041
3781
|
let partIndex = 0;
|
|
3782
|
+
// The floats this block cites wait for the line that cites them.
|
|
3783
|
+
gateCitedFloats(blockIdx, remainingLines);
|
|
3042
3784
|
/** Times this block left an EMPTY short column (see `shortColumn`) —
|
|
3043
3785
|
* bounded so a page whose columns are all short (footnotes, design
|
|
3044
3786
|
* bands) cannot make it wander forever. */
|
|
3045
3787
|
let shortColumnMoves = 0;
|
|
3788
|
+
/** Whether the paragraph was already re-broken to keep a hyphen off
|
|
3789
|
+
* the last line of a column (`bodyText.hyphenateAcrossColumns`). */
|
|
3790
|
+
let columnEndHyphenRetried = false;
|
|
3791
|
+
/** `hyphenateAcrossColumns: false`: the paragraph's lines as set now
|
|
3792
|
+
* (a re-break replaces them) and where the breaker broke them, the
|
|
3793
|
+
* lines (1-based) its last re-break kept a hyphen off, and the part
|
|
3794
|
+
* last re-broken from. */
|
|
3795
|
+
let paragraphLines = remainingLines;
|
|
3796
|
+
let paragraphBreaks = measured.breaks;
|
|
3797
|
+
let guardedEnds = [];
|
|
3798
|
+
let columnEndHyphenPart = -1;
|
|
3799
|
+
/** The column width the block was broken for, and the columns of other
|
|
3800
|
+
* widths its later lines were broken for since (EF-157). A text block
|
|
3801
|
+
* that goes on into a column of another width — a one-and-a-half
|
|
3802
|
+
* layout with text in both columns, a styled section with its own
|
|
3803
|
+
* margins — is broken again there: its placed lines stay, the rest is
|
|
3804
|
+
* broken for the new column. Not the loose paragraphs balancing runs
|
|
3805
|
+
* long (placed whole where they were measured), nor a heading the
|
|
3806
|
+
* default opener paints across the page. */
|
|
3807
|
+
let brokenForWidth = measureWidth;
|
|
3808
|
+
let laterWidths = [];
|
|
3809
|
+
const rebreaksForColumn = !tryLoose && rawBlock.toc === undefined
|
|
3810
|
+
&& (canSplit || (vdtType === 'heading' && !opensDefaultOpener(rawBlock)));
|
|
3811
|
+
/** Measure the block again for the columns it is set in (see
|
|
3812
|
+
* `laterWidths`), with `extra` options. */
|
|
3813
|
+
const measureForColumns = (extra) => measureContentBlock(rawBlock, blockIdx, brokenForWidth, blockMeasureCtx, {
|
|
3814
|
+
styleOverride,
|
|
3815
|
+
...(laterWidths.length > 0 ? { restColumnWidths: laterWidths } : {}),
|
|
3816
|
+
...extra,
|
|
3817
|
+
});
|
|
3046
3818
|
// "Keep with next" for colon-introduced lists: a paragraph ending in `:`
|
|
3047
3819
|
// followed directly by a list acts as a lead-in title — the colon-bearing
|
|
3048
3820
|
// line must share a column with the first list item. Only checked for the
|
|
@@ -3055,14 +3827,70 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3055
3827
|
while (remainingLines.length > 0) {
|
|
3056
3828
|
enterBand(blockIdx, partIndex);
|
|
3057
3829
|
const curCol = currentColumn(doc, cursor);
|
|
3830
|
+
// The rest of the block goes into a column of another width than the
|
|
3831
|
+
// one it was broken for (EF-157): break it again for this one. Up to
|
|
3832
|
+
// postext 1.4 it kept the lines of the column it started in, and a
|
|
3833
|
+
// line set for the wide column ran past the narrow one (clipped).
|
|
3834
|
+
const restWidth = laterWidths.length > 0 ? laterWidths[laterWidths.length - 1].columnWidth : brokenForWidth;
|
|
3835
|
+
if (rebreaksForColumn && Math.abs(curCol.bbox.width - restWidth) > 0.5) {
|
|
3836
|
+
const placed = paragraphLines.length - remainingLines.length;
|
|
3837
|
+
if (placed === 0) {
|
|
3838
|
+
// Nothing placed yet: the whole block, for this column.
|
|
3839
|
+
const again = measureContentBlock(rawBlock, blockIdx, curCol.bbox.width, blockMeasureCtx, { styleOverride });
|
|
3840
|
+
if (again && again.measured.lines.length > 0) {
|
|
3841
|
+
brokenForWidth = curCol.bbox.width;
|
|
3842
|
+
laterWidths = [];
|
|
3843
|
+
letterSpacingPx = again.letterSpacingPx;
|
|
3844
|
+
remainingLines = [...raggedLooseLines(again.measured.lines, style.textAlign)];
|
|
3845
|
+
paragraphLines = remainingLines;
|
|
3846
|
+
paragraphBreaks = again.measured.breaks;
|
|
3847
|
+
guardedEnds = [];
|
|
3848
|
+
columnEndHyphenRetried = false;
|
|
3849
|
+
regateCitedFloats(blockIdx, remainingLines);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
else {
|
|
3853
|
+
// The placed lines keep their breaks; the rest is broken for
|
|
3854
|
+
// this column. Taken only when the placed lines come out as they
|
|
3855
|
+
// were (a break trace from the other measuring path cannot keep
|
|
3856
|
+
// them) and with the same tracking.
|
|
3857
|
+
const steps = [...laterWidths, { fromLine: placed, columnWidth: curCol.bbox.width }];
|
|
3858
|
+
const keepBreaks = paragraphBreaks && paragraphBreaks.at.length >= placed
|
|
3859
|
+
? { path: paragraphBreaks.path, at: paragraphBreaks.at.slice(0, placed) }
|
|
3860
|
+
: undefined;
|
|
3861
|
+
const again = measureContentBlock(rawBlock, blockIdx, brokenForWidth, blockMeasureCtx, {
|
|
3862
|
+
styleOverride, restColumnWidths: steps, ...(keepBreaks ? { keepBreaks } : {}),
|
|
3863
|
+
});
|
|
3864
|
+
const lines = again && (again.letterSpacingPx ?? 0) === (letterSpacingPx ?? 0)
|
|
3865
|
+
? [...raggedLooseLines(again.measured.lines, style.textAlign)]
|
|
3866
|
+
: undefined;
|
|
3867
|
+
if (lines && lines.length > placed
|
|
3868
|
+
&& paragraphLines.slice(0, placed).every((l, i) => lines[i].text === l.text && lines[i].hyphenated === l.hyphenated)) {
|
|
3869
|
+
laterWidths = steps;
|
|
3870
|
+
paragraphLines = lines;
|
|
3871
|
+
paragraphBreaks = again.measured.breaks;
|
|
3872
|
+
remainingLines = lines.slice(placed);
|
|
3873
|
+
regateCitedFloats(blockIdx, lines);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3058
3877
|
const isFirstInColumn = curCol.blocks.length === 0;
|
|
3878
|
+
/** Under a page-span opener: where this column's first block goes. A
|
|
3879
|
+
* heading, a display formula or a contents row keeps level with the
|
|
3880
|
+
* first block under the opener; text starts on the grid. */
|
|
3881
|
+
const openerHead = isFirstInColumn
|
|
3882
|
+
? openerHeadSpacing(curCol, vdtType === 'heading' || vdtType === 'mathDisplay' || rawBlock.toc !== undefined)
|
|
3883
|
+
: undefined;
|
|
3059
3884
|
// Compute spacing before this block — margin collapsing between
|
|
3060
3885
|
// consecutive headings: only the larger of marginBottom / marginTop applies
|
|
3061
3886
|
let spacingBefore = 0;
|
|
3887
|
+
/** Column balancing: the levers applied to this placement, stamped
|
|
3888
|
+
* on the block it places (`VDTBlock.balancing`). */
|
|
3889
|
+
let balancing;
|
|
3062
3890
|
if (!isFirstInColumn) {
|
|
3063
3891
|
spacingBefore = pendingSpacing;
|
|
3064
3892
|
if (vdtType === 'heading' || vdtType === 'mathDisplay') {
|
|
3065
|
-
spacingBefore = Math.max(spacingBefore, style.marginTopPx);
|
|
3893
|
+
spacingBefore = Math.max(partIndex === 0 ? pendingBeforeSelfSnapping(blockIdx) : spacingBefore, style.marginTopPx);
|
|
3066
3894
|
// Column balancing: extra whole grid lines above this heading so
|
|
3067
3895
|
// the column it sits in ends flush with the page bottom. Applied
|
|
3068
3896
|
// after margin collapsing — the heading's effective top margin
|
|
@@ -3074,6 +3902,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3074
3902
|
if (extraPx) {
|
|
3075
3903
|
spacingBefore += extraPx;
|
|
3076
3904
|
addBalanceExtra(curCol, extraPx);
|
|
3905
|
+
balancing = { levers: ['heading'], spaceAbove: extraPx };
|
|
3077
3906
|
}
|
|
3078
3907
|
}
|
|
3079
3908
|
}
|
|
@@ -3081,6 +3910,17 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3081
3910
|
const prevWasList = prevNonMarkerBlock(contentBlocks, blockIdx)?.type === 'listItem';
|
|
3082
3911
|
if (!prevWasList) {
|
|
3083
3912
|
spacingBefore = Math.max(spacingBefore, style.marginTopPx);
|
|
3913
|
+
// `snapTopToGrid` (EF-103): the space above rounds up so the
|
|
3914
|
+
// first item lands on the grid, the margin a minimum. Read from
|
|
3915
|
+
// the block's own context, since a part or a styled section may
|
|
3916
|
+
// override its lists.
|
|
3917
|
+
const listsCfg = blockMeasureCtx.resolved;
|
|
3918
|
+
const lists = listKind === 'ordered' ? listsCfg.orderedLists : listsCfg.unorderedLists;
|
|
3919
|
+
if (partIndex === 0 && lists.snapTopToGrid) {
|
|
3920
|
+
const usedHeight = curCol.bbox.height - curCol.availableHeight;
|
|
3921
|
+
const top = Math.ceil((usedHeight + spacingBefore - 0.01) / baselineGrid) * baselineGrid;
|
|
3922
|
+
spacingBefore = top - usedHeight;
|
|
3923
|
+
}
|
|
3084
3924
|
}
|
|
3085
3925
|
}
|
|
3086
3926
|
else if (rawBlock.toc) {
|
|
@@ -3097,25 +3937,43 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3097
3937
|
if (extraPx) {
|
|
3098
3938
|
spacingBefore += extraPx;
|
|
3099
3939
|
addBalanceExtra(curCol, extraPx);
|
|
3940
|
+
// The block the lever pushes down follows a list's end, or a
|
|
3941
|
+
// display formula / box (the two candidates the gap collector
|
|
3942
|
+
// offers a non-heading block that does not open its column).
|
|
3943
|
+
const prev = curCol.blocks[curCol.blocks.length - 1];
|
|
3944
|
+
const afterList = prev?.type === 'listItem' && prev.containerId === undefined;
|
|
3945
|
+
balancing = { levers: [afterList ? 'listEnd' : 'afterDisplay'], spaceAbove: extraPx };
|
|
3100
3946
|
}
|
|
3101
3947
|
}
|
|
3102
3948
|
}
|
|
3949
|
+
else if (openerHead !== undefined) {
|
|
3950
|
+
// The head of a column under a page-span opener (EF-139).
|
|
3951
|
+
spacingBefore = openerHead;
|
|
3952
|
+
}
|
|
3103
3953
|
else if (reservedOf(curCol).top > 0) {
|
|
3104
3954
|
// Column balancing: extra grid lines between the float band at the
|
|
3105
3955
|
// head of this column and its first block (the after-float lever).
|
|
3106
3956
|
// A paragraph resuming from the column before is levered on its own
|
|
3107
3957
|
// fragment, so the room lands under the figure and not back at the
|
|
3108
|
-
// paragraph's start.
|
|
3958
|
+
// paragraph's start. A heading there is a heading lever.
|
|
3109
3959
|
const extraPx = balanceExtraPx?.get(balanceKey(blockIdx, partIndex));
|
|
3110
3960
|
if (extraPx) {
|
|
3111
3961
|
spacingBefore += extraPx;
|
|
3112
3962
|
addBalanceExtra(curCol, extraPx);
|
|
3963
|
+
balancing = { levers: [vdtType === 'heading' ? 'heading' : 'afterFloat'], spaceAbove: extraPx };
|
|
3113
3964
|
}
|
|
3114
3965
|
}
|
|
3115
3966
|
// A loose paragraph's extra line is balancing height too (it lands
|
|
3116
3967
|
// whole in this column — loose candidates are never split parts).
|
|
3117
|
-
|
|
3968
|
+
const looseTracking = looseOutcome.get(blockIdx);
|
|
3969
|
+
if (partIndex === 0 && tryLoose && looseLines !== undefined && typeof looseTracking === 'number') {
|
|
3118
3970
|
addBalanceExtra(curCol, looseLines * style.lineHeightPx);
|
|
3971
|
+
balancing = {
|
|
3972
|
+
levers: [...(balancing?.levers ?? []), 'looseParagraph'],
|
|
3973
|
+
spaceAbove: balancing?.spaceAbove ?? 0,
|
|
3974
|
+
extraLines: looseLines,
|
|
3975
|
+
tracking: looseTracking,
|
|
3976
|
+
};
|
|
3119
3977
|
}
|
|
3120
3978
|
const effectiveAvailable = curCol.availableHeight - spacingBefore;
|
|
3121
3979
|
// A hair of tolerance: room of exactly N lines (grid arithmetic in
|
|
@@ -3132,24 +3990,44 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3132
3990
|
// actual content bottom so the reserved block height (and the
|
|
3133
3991
|
// subsequent marginBottom + grid snap) starts from there.
|
|
3134
3992
|
let effectiveRemainHeight = totalRemainHeight;
|
|
3135
|
-
|
|
3993
|
+
/** The heading's design and the values it is laid out with, kept for
|
|
3994
|
+
* the side-column obstacles once the block is placed (EF-78). */
|
|
3995
|
+
let headingDesign;
|
|
3996
|
+
if (vdtType === 'heading' && headingLevel !== undefined && partIndex === 0 && !hiddenHeading) {
|
|
3136
3997
|
const lvl = headingLevels.forBlock(rawBlock);
|
|
3137
3998
|
if (lvl) {
|
|
3138
|
-
const full = remainingLines
|
|
3139
|
-
.map((ln) => (ln.segments ?? []).map((s) => s.text).join(''))
|
|
3140
|
-
.join(' ');
|
|
3141
3999
|
const pref = numberPrefix ?? '';
|
|
3142
|
-
|
|
4000
|
+
// The lines joined back into the title (EF-162), as the design is
|
|
4001
|
+
// painted (`buildHeadersAndFooters`).
|
|
4002
|
+
const title = headingTitleText(remainingLines, pref, rawBlock.titleBreaks, rawBlock.text.length);
|
|
3143
4003
|
// Span-page openers lay out across the full content area (both
|
|
3144
4004
|
// columns); in-column headings use just the column width.
|
|
3145
4005
|
const pageArea = doc.pages[cursor.pageIndex].contentArea;
|
|
3146
4006
|
const measureWidth = lvl.span === 'page' ? pageArea.width : curCol.bbox.width;
|
|
3147
|
-
const
|
|
4007
|
+
const info = { titleText: title, formattedNumber: pref, numericValue: headingNumber, locale: resolvedLocale(resolved), chapterNumber: chapterNumberByBlock[blockIdx] ?? '', attrs: rawBlock.attrs };
|
|
4008
|
+
const design = measureHeadingDesign(lvl, info, measureWidth, resolved.page.dpi, doc.metadata, cursor.pageIndex, designFrames, {
|
|
3148
4009
|
x: lvl.span === 'page' ? pageArea.x : curCol.bbox.x,
|
|
3149
4010
|
y: curCol.bbox.y + (curCol.bbox.height - curCol.availableHeight) + spacingBefore,
|
|
3150
|
-
});
|
|
3151
|
-
if (
|
|
3152
|
-
effectiveRemainHeight =
|
|
4011
|
+
}, resourceById);
|
|
4012
|
+
if (design.height > effectiveRemainHeight)
|
|
4013
|
+
effectiveRemainHeight = design.height;
|
|
4014
|
+
// A text anchored to the foot or the middle of the band keeps the
|
|
4015
|
+
// box the design is painted in at least as tall as it needs, so it
|
|
4016
|
+
// never rises above the heading's top (EF-176). The box of a
|
|
4017
|
+
// heading that snaps holds its margin too.
|
|
4018
|
+
const textFloor = design.textFloor - (shouldSnapToGrid ? style.marginBottomPx : 0);
|
|
4019
|
+
if (textFloor > effectiveRemainHeight)
|
|
4020
|
+
effectiveRemainHeight = textFloor;
|
|
4021
|
+
// The default opener sets the title with the design painter, which
|
|
4022
|
+
// can take more lines than the heading's own measure (a justified
|
|
4023
|
+
// line whose spaces shrink, a forced break, a run it prints plain):
|
|
4024
|
+
// the band holds every line it paints (EF-100).
|
|
4025
|
+
if (opensDefaultOpener(rawBlock)) {
|
|
4026
|
+
const painted = measureDefaultOpenerHeight(lvl, resolved.headings.textAlign, defaultOpenerTitle(remainingLines, pref, rawBlock.titleBreaks, rawBlock.text.length), pref, pageArea.width, resolved.page.dpi, doc.metadata, cursor.pageIndex);
|
|
4027
|
+
if (painted > effectiveRemainHeight)
|
|
4028
|
+
effectiveRemainHeight = painted;
|
|
4029
|
+
}
|
|
4030
|
+
headingDesign = { lvl, info };
|
|
3153
4031
|
}
|
|
3154
4032
|
}
|
|
3155
4033
|
// Keep-with-list: if this colon-paragraph would fit but would leave no
|
|
@@ -3171,9 +4049,21 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3171
4049
|
? resolved.orderedLists.marginTop
|
|
3172
4050
|
: resolved.unorderedLists.marginTop;
|
|
3173
4051
|
const nextListMarginTopPx = dimensionToPx(nextListMarginDim, dpi, bodyStyle.fontSizePx);
|
|
3174
|
-
|
|
4052
|
+
let effectiveGap = Math.max(style.marginBottomPx, nextListMarginTopPx);
|
|
4053
|
+
// A list that snaps its top (EF-103) opens on the next grid line.
|
|
4054
|
+
const nextLists = blockMeasureCtx.resolved;
|
|
4055
|
+
if ((nextListKind === 'ordered' ? nextLists.orderedLists : nextLists.unorderedLists).snapTopToGrid) {
|
|
4056
|
+
effectiveGap = Math.ceil((paragraphBottom + effectiveGap - 0.01) / baselineGrid) * baselineGrid - paragraphBottom;
|
|
4057
|
+
}
|
|
3175
4058
|
const minSpaceForList = effectiveGap + bodyStyle.lineHeightPx;
|
|
3176
|
-
|
|
4059
|
+
// A line of room is not always enough (EF-110): with
|
|
4060
|
+
// `colonListRoom: 'item'` the first item has to be able to start
|
|
4061
|
+
// there, by the rules that split it — a two-line item the orphan
|
|
4062
|
+
// and widow rules keep whole needs both. `'line'` keeps the 1.4
|
|
4063
|
+
// check, one line.
|
|
4064
|
+
if (availableAfter < minSpaceForList
|
|
4065
|
+
|| (resolved.bodyText.colonListRoom === 'item'
|
|
4066
|
+
&& !firstListItemStarts(blockIdx, availableAfter - effectiveGap, curCol.bbox.width, blockMeasureCtx))) {
|
|
3177
4067
|
const effectiveWidowMin = resolved.bodyText.avoidWidows
|
|
3178
4068
|
? Math.max(1, resolved.bodyText.widowMinLines)
|
|
3179
4069
|
: 1;
|
|
@@ -3198,7 +4088,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3198
4088
|
blk.sourceEnd = splitLines[splitLines.length - 1].sourceEnd;
|
|
3199
4089
|
blk.sourceMap = absoluteSourceMap;
|
|
3200
4090
|
blk.plainPrefixLen = prefixLen;
|
|
4091
|
+
if (balancing)
|
|
4092
|
+
blk.balancing = balancing;
|
|
3201
4093
|
placeBlockInColumn(blk, splitAt * style.lineHeightPx, curCol, cursor);
|
|
4094
|
+
citedLinesPlaced(blockIdx, blk);
|
|
3202
4095
|
doc.blocks.push(blk);
|
|
3203
4096
|
remainingLines = remainingLines.slice(splitAt);
|
|
3204
4097
|
partIndex++;
|
|
@@ -3224,13 +4117,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3224
4117
|
}
|
|
3225
4118
|
}
|
|
3226
4119
|
if (headingRunCount > 0 && headingRunCount < curCol.blocks.length) {
|
|
3227
|
-
const popped =
|
|
3228
|
-
for (const p of popped) {
|
|
3229
|
-
const idx = doc.blocks.indexOf(p);
|
|
3230
|
-
if (idx !== -1)
|
|
3231
|
-
doc.blocks.splice(idx, 1);
|
|
3232
|
-
curCol.availableHeight += p.bbox.height;
|
|
3233
|
-
}
|
|
4120
|
+
const popped = rollbackHeadings(curCol);
|
|
3234
4121
|
// Rewind so the for-loop's blockIdx++ lands on the first
|
|
3235
4122
|
// rolled-back heading (marker blocks in between are replayed).
|
|
3236
4123
|
blockIdx = (popped[0].contentIndex ?? blockIdx - headingRunCount) - 1;
|
|
@@ -3246,6 +4133,76 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3246
4133
|
// headingRunCount === curCol.blocks.length: fall through to place.
|
|
3247
4134
|
}
|
|
3248
4135
|
}
|
|
4136
|
+
// Keep a display formula with its lead-in (`math.keepWithLeadIn`,
|
|
4137
|
+
// TeX's predisplay penalty, EF-84): when this paragraph — or the part
|
|
4138
|
+
// of it left to set — fits the column but the formula after it does
|
|
4139
|
+
// not fit under its last line, that line goes on with the formula.
|
|
4140
|
+
// The lines left here keep the widow minimum; with fewer, a paragraph
|
|
4141
|
+
// that opens here moves on whole, and one already running on from
|
|
4142
|
+
// the column before stays (it cannot do better anywhere).
|
|
4143
|
+
if (resolved.math.keepWithLeadIn
|
|
4144
|
+
&& vdtType === 'paragraph'
|
|
4145
|
+
&& contentBlocks[blockIdx + 1]?.type === 'mathDisplay'
|
|
4146
|
+
&& totalRemainHeight <= effectiveAvailable + FIT_EPS) {
|
|
4147
|
+
const formula = measureContentBlock(contentBlocks[blockIdx + 1], blockIdx + 1, curCol.bbox.width, blockMeasureCtx);
|
|
4148
|
+
const formulaHeight = formula ? (formula.measured.lines[0]?.bbox.height ?? formula.measured.totalHeight) : 0;
|
|
4149
|
+
const gap = Math.max(style.marginBottomPx, formula?.kind.style.marginTopPx ?? 0);
|
|
4150
|
+
const usedHeight = (curCol.bbox.height - curCol.availableHeight) + spacingBefore;
|
|
4151
|
+
// As the plain pass saw it: room balancing added above is not room
|
|
4152
|
+
// the formula lost.
|
|
4153
|
+
const availableAfter = curCol.bbox.height - (usedHeight + totalRemainHeight) + (balanceExtraInColumn.get(curCol) ?? 0);
|
|
4154
|
+
if (formula && formulaHeight > 0 && availableAfter + FIT_EPS < gap + formulaHeight) {
|
|
4155
|
+
const minKeep = resolved.bodyText.avoidWidows ? Math.max(1, resolved.bodyText.widowMinLines) : 1;
|
|
4156
|
+
const splitAt = remainingLines.length - 1;
|
|
4157
|
+
if (splitAt >= minKeep) {
|
|
4158
|
+
if (spacingBefore !== 0)
|
|
4159
|
+
curCol.availableHeight -= spacingBefore;
|
|
4160
|
+
const partId = partIndex === 0 ? id : `${id}-cont-${partIndex}`;
|
|
4161
|
+
const splitLines = remainingLines.slice(0, splitAt);
|
|
4162
|
+
const blk = createVDTBlock(partId, vdtType, style.fontString, style.color, style.textAlign);
|
|
4163
|
+
applyStyleAttrs(blk, style);
|
|
4164
|
+
if (letterSpacingPx !== undefined)
|
|
4165
|
+
blk.letterSpacing = letterSpacingPx;
|
|
4166
|
+
blk.contentIndex = blockIdx;
|
|
4167
|
+
stampBlockExtras(blk, rawBlock);
|
|
4168
|
+
blk.lines = resetLinePositions(splitLines, style.lineHeightPx);
|
|
4169
|
+
blk.dirty = false;
|
|
4170
|
+
blk.snappedToGrid = false;
|
|
4171
|
+
blk.sourceStart = splitLines[0].sourceStart;
|
|
4172
|
+
blk.sourceEnd = splitLines[splitLines.length - 1].sourceEnd;
|
|
4173
|
+
blk.sourceMap = absoluteSourceMap;
|
|
4174
|
+
blk.plainPrefixLen = prefixLen;
|
|
4175
|
+
if (balancing)
|
|
4176
|
+
blk.balancing = balancing;
|
|
4177
|
+
placeBlockInColumn(blk, splitAt * style.lineHeightPx, curCol, cursor);
|
|
4178
|
+
citedLinesPlaced(blockIdx, blk);
|
|
4179
|
+
doc.blocks.push(blk);
|
|
4180
|
+
remainingLines = remainingLines.slice(splitAt);
|
|
4181
|
+
partIndex++;
|
|
4182
|
+
pendingSpacing = 0;
|
|
4183
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4186
|
+
if (partIndex === 0 && curCol.blocks.length > 0) {
|
|
4187
|
+
// Moving on whole must not strand a heading closing the column.
|
|
4188
|
+
const headingRun = trailingHeadingRun(curCol);
|
|
4189
|
+
if (resolved.headings.keepWithNext && headingRun > 0 && headingRun < curCol.blocks.length) {
|
|
4190
|
+
const rolledBack = rollbackHeadings(curCol);
|
|
4191
|
+
if (rolledBack.length > 0) {
|
|
4192
|
+
blockIdx = (rolledBack[0].contentIndex ?? blockIdx - rolledBack.length) - 1;
|
|
4193
|
+
pendingSpacing = 0;
|
|
4194
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
4195
|
+
break;
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
if (headingRun < curCol.blocks.length) {
|
|
4199
|
+
pendingSpacing = 0;
|
|
4200
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
4201
|
+
continue;
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
3249
4206
|
// A block opening a column normally stays (no column can take it any
|
|
3250
4207
|
// better), except in a column at least a line shorter than the content
|
|
3251
4208
|
// area — the band under a page-span box, or a column cut by a float:
|
|
@@ -3294,7 +4251,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3294
4251
|
shortColumnMoves++;
|
|
3295
4252
|
// Roll back any immediately-preceding heading blocks in this
|
|
3296
4253
|
// column so they travel with this one.
|
|
3297
|
-
const rolledBack =
|
|
4254
|
+
const rolledBack = rollbackHeadings(curCol);
|
|
3298
4255
|
if (rolledBack.length > 0) {
|
|
3299
4256
|
// Rewind so the for-loop's blockIdx++ lands on the first
|
|
3300
4257
|
// rolled-back heading (marker blocks in between are replayed).
|
|
@@ -3323,7 +4280,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3323
4280
|
blk.headingLevel = headingLevel;
|
|
3324
4281
|
if (numberPrefix)
|
|
3325
4282
|
blk.numberPrefix = numberPrefix;
|
|
4283
|
+
if (headingNumber !== undefined)
|
|
4284
|
+
blk.headingNumber = headingNumber;
|
|
3326
4285
|
}
|
|
4286
|
+
if (hiddenHeading)
|
|
4287
|
+
blk.hidden = true;
|
|
3327
4288
|
if (partIndex === 0 && vdtType === 'heading' && rawBlock.attrs)
|
|
3328
4289
|
blk.attrs = rawBlock.attrs;
|
|
3329
4290
|
if (partIndex === 0 && vdtType === 'heading' && rawBlock.attrSources) {
|
|
@@ -3334,10 +4295,6 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3334
4295
|
blk.titleBreaks = rawBlock.titleBreaks;
|
|
3335
4296
|
blk.titleLength = rawBlock.text.length;
|
|
3336
4297
|
}
|
|
3337
|
-
if (partIndex === 0 && vdtType === 'heading' && rawBlock.titleBreaks) {
|
|
3338
|
-
blk.titleBreaks = rawBlock.titleBreaks;
|
|
3339
|
-
blk.titleLength = rawBlock.text.length;
|
|
3340
|
-
}
|
|
3341
4298
|
if (vdtType === 'mathDisplay' && mathDisplayRender) {
|
|
3342
4299
|
blk.mathRender = mathDisplayRender;
|
|
3343
4300
|
blk.tex = rawBlock.tex;
|
|
@@ -3360,15 +4317,21 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3360
4317
|
}
|
|
3361
4318
|
blk.sourceMap = absoluteSourceMap;
|
|
3362
4319
|
blk.plainPrefixLen = prefixLen;
|
|
4320
|
+
if (balancing)
|
|
4321
|
+
blk.balancing = balancing;
|
|
3363
4322
|
let h = effectiveRemainHeight;
|
|
4323
|
+
/** What a collapsing container tail still owes the next block,
|
|
4324
|
+
* from the grid line its text snapped to (see `collapseTail`). */
|
|
4325
|
+
let tailOwed = 0;
|
|
3364
4326
|
if (shouldSnapToGrid && partIndex === 0) {
|
|
3365
4327
|
// Snap using the absolute position in the column so that the block
|
|
3366
4328
|
// bottom lands on a baseline grid line. This accounts for off-grid
|
|
3367
4329
|
// starts (e.g. after consecutive unsnapped headings) and bakes in
|
|
3368
4330
|
// the minimum marginBottom — the grid always wins, but the margin
|
|
3369
|
-
// below is guaranteed to be at least marginBottomPx.
|
|
4331
|
+
// below is guaranteed to be at least marginBottomPx. A collapsing
|
|
4332
|
+
// container tail snaps under its text alone and carries the rest.
|
|
3370
4333
|
const usedHeight = curCol.bbox.height - curCol.availableHeight;
|
|
3371
|
-
const naturalBottom = usedHeight + effectiveRemainHeight + style.marginBottomPx;
|
|
4334
|
+
const naturalBottom = usedHeight + effectiveRemainHeight + (collapseTail ? 0 : style.marginBottomPx);
|
|
3372
4335
|
// Tolerance guards against FP drift: if naturalBottom is already on
|
|
3373
4336
|
// the grid (e.g. marginBottom is an exact multiple of baselineGrid),
|
|
3374
4337
|
// don't round up to the next line.
|
|
@@ -3376,39 +4339,44 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3376
4339
|
// A negative margin below a container tail may snap the flow back
|
|
3377
4340
|
// above the text's own bottom; never below the block's top.
|
|
3378
4341
|
h = Math.max(0, snappedBottom - usedHeight);
|
|
4342
|
+
if (collapseTail)
|
|
4343
|
+
tailOwed = style.marginBottomPx - (snappedBottom - (usedHeight + effectiveRemainHeight));
|
|
4344
|
+
}
|
|
4345
|
+
// A design that ends within a line of the column's foot fits, but
|
|
4346
|
+
// its margin and grid snap would carry the block past the foot: it
|
|
4347
|
+
// takes the rest of the column instead, as a design reaching past
|
|
4348
|
+
// the foot does (the text after it opens the next column or page).
|
|
4349
|
+
if (vdtType === 'heading' && effectiveRemainHeight > totalRemainHeight && h > curCol.availableHeight) {
|
|
4350
|
+
h = Math.max(effectiveRemainHeight, curCol.availableHeight);
|
|
3379
4351
|
}
|
|
3380
4352
|
placeBlockInColumn(blk, h, curCol, cursor);
|
|
4353
|
+
citedLinesPlaced(blockIdx, blk);
|
|
3381
4354
|
finalizeListItem(blk, partIndex === 0);
|
|
3382
4355
|
doc.blocks.push(blk);
|
|
3383
|
-
// Page-spanning heading: reserve
|
|
3384
|
-
// other column on this page so body text under the opener band
|
|
3385
|
-
// starts below it in ALL columns, not just the one it was placed in.
|
|
3386
|
-
// A column still untouched moves its head below the band (as a top
|
|
3387
|
-
// float does), so a float offered that column's head lands under
|
|
3388
|
-
// the opener instead of over it; the text starts where it did.
|
|
4356
|
+
// Page-spanning heading: reserve its band in every other column.
|
|
3389
4357
|
if (vdtType === 'heading' && headingLevel !== undefined) {
|
|
3390
4358
|
const lvl = headingLevels.forBlock(rawBlock);
|
|
3391
4359
|
if (lvl?.span === 'page') {
|
|
3392
|
-
|
|
3393
|
-
for (const otherCol of page.columns) {
|
|
3394
|
-
if (otherCol === curCol)
|
|
3395
|
-
continue;
|
|
3396
|
-
if (otherCol.blocks.length === 0 && otherCol.availableHeight >= otherCol.bbox.height - 0.01) {
|
|
3397
|
-
const shift = Math.min(h, otherCol.bbox.height);
|
|
3398
|
-
otherCol.bbox.y += shift;
|
|
3399
|
-
otherCol.bbox.height -= shift;
|
|
3400
|
-
}
|
|
3401
|
-
otherCol.availableHeight = Math.max(0, otherCol.availableHeight - h);
|
|
3402
|
-
}
|
|
4360
|
+
reserveOpenerBand(curCol, h, blk, shouldSnapToGrid && partIndex === 0 ? 0 : style.marginBottomPx, lvl.snapToGrid ?? resolved.headings.snapToGrid);
|
|
3403
4361
|
}
|
|
3404
4362
|
}
|
|
4363
|
+
// Its design's elements in the side column hold the side stack off.
|
|
4364
|
+
if (headingDesign)
|
|
4365
|
+
markSideObstacles(blk, headingDesign);
|
|
3405
4366
|
// For snapped headings/list-tails the margin is baked into the snap;
|
|
3406
4367
|
// for unsnapped ones (consecutive) track it for collapsing
|
|
3407
4368
|
if (vdtType === 'listItem' && nextIsListItem) {
|
|
3408
|
-
pendingSpacing = listBullet
|
|
4369
|
+
pendingSpacing = itemGapPx(listBullet);
|
|
3409
4370
|
}
|
|
3410
4371
|
else {
|
|
3411
4372
|
pendingSpacing = (shouldSnapToGrid && partIndex === 0) ? 0 : style.marginBottomPx;
|
|
4373
|
+
// A collapsing container tail: the space the snap did not cover,
|
|
4374
|
+
// in whole grid lines, merges with the next block's own space
|
|
4375
|
+
// above (a heading's margin collapses with the exact rest).
|
|
4376
|
+
if (collapseTail && shouldSnapToGrid && partIndex === 0 && tailOwed > 0.01) {
|
|
4377
|
+
pendingSpacing = Math.ceil((tailOwed - 0.01) / baselineGrid) * baselineGrid;
|
|
4378
|
+
inlineGapOwed = { afterIdx: blockIdx, pending: pendingSpacing, exact: tailOwed, container: true };
|
|
4379
|
+
}
|
|
3412
4380
|
}
|
|
3413
4381
|
break;
|
|
3414
4382
|
}
|
|
@@ -3429,17 +4397,136 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3429
4397
|
slackWeight: resolved.bodyText.slackWeight,
|
|
3430
4398
|
});
|
|
3431
4399
|
// The block sits right under a heading: pushing it whole would
|
|
3432
|
-
// strand the heading. Keep
|
|
3433
|
-
// least the widow minimum —
|
|
3434
|
-
//
|
|
4400
|
+
// strand the heading. Keep the most lines that fit under it — at
|
|
4401
|
+
// least the widow minimum — that also leave the orphan minimum for
|
|
4402
|
+
// the next column (`headings.keepWithNextSplit: 'rules'`, EF-185;
|
|
4403
|
+
// `'fill'`, postext 1.4's rule, keeps as many as fit, however few
|
|
4404
|
+
// go on). With no such split the heading moves along with the block
|
|
3435
4405
|
// (the no-fit branch below rolls it back).
|
|
3436
4406
|
const headingRun = partIndex === 0 ? trailingHeadingRun(curCol) : 0;
|
|
3437
4407
|
if (choice.splitAt === 0 && headingRun > 0 && headingRun < curCol.blocks.length) {
|
|
3438
4408
|
const minKeep = effectiveAvoidWidows ? Math.max(1, resolved.bodyText.widowMinLines) : 1;
|
|
3439
|
-
const
|
|
3440
|
-
|
|
4409
|
+
const minTail = effectiveAvoidOrphans && resolved.headings.keepWithNextSplit === 'rules'
|
|
4410
|
+
? Math.max(1, resolved.bodyText.orphanMinLines)
|
|
4411
|
+
: 1;
|
|
4412
|
+
let keep = Math.min(linesPerAvailable, remainingLines.length);
|
|
4413
|
+
while (keep >= minKeep && keep < remainingLines.length && remainingLines.length - keep < minTail)
|
|
4414
|
+
keep--;
|
|
4415
|
+
if (keep >= minKeep)
|
|
4416
|
+
choice = { splitAt: keep, demerit: choice.demerit };
|
|
4417
|
+
}
|
|
4418
|
+
// An empty column the block cannot leave (a full-height one, or a
|
|
4419
|
+
// short one once the moves below are spent) holds as many lines as
|
|
4420
|
+
// it can: a paragraph never runs past its column's foot, whatever
|
|
4421
|
+
// the orphan and widow rules would rather do (EF-97). Not a column
|
|
4422
|
+
// a band cap cut short: there the block runs past the cut, and the
|
|
4423
|
+
// driver takes the cut lower, or drops it, so the rules still hold
|
|
4424
|
+
// (under a figure heading such a column it moves on instead, see
|
|
4425
|
+
// `capShortUnderFloat` below).
|
|
4426
|
+
if (choice.splitAt === 0 && curCol.blocks.length === 0 && !shortColumn && !uncappedBottoms.has(curCol)) {
|
|
4427
|
+
const maxFit = Math.min(linesPerAvailable, remainingLines.length - 1);
|
|
4428
|
+
if (maxFit >= 1)
|
|
3441
4429
|
choice = { splitAt: maxFit, demerit: choice.demerit };
|
|
3442
4430
|
}
|
|
4431
|
+
// `bodyText.hyphenateAcrossColumns: false` (EF-86): the lines that
|
|
4432
|
+
// close a column must not end on a hyphen. Those are the line that
|
|
4433
|
+
// closes this column and the lines later full columns would end on.
|
|
4434
|
+
// When one of them ends on a hyphen, the paragraph is broken again,
|
|
4435
|
+
// once, with a hyphen at each of them priced out of the breaker; the
|
|
4436
|
+
// placement then starts over with its lines. Only a paragraph set
|
|
4437
|
+
// from its first line here — its earlier lines are not placed yet —
|
|
4438
|
+
// and never one the balancing is running long.
|
|
4439
|
+
if (choice.splitAt > 0
|
|
4440
|
+
&& !resolved.bodyText.hyphenateAcrossColumns
|
|
4441
|
+
&& !columnEndHyphenRetried
|
|
4442
|
+
&& partIndex === 0
|
|
4443
|
+
&& looseLines === undefined) {
|
|
4444
|
+
const fullColumn = Math.max(1, Math.floor((contentArea.height + 0.01) / style.lineHeightPx));
|
|
4445
|
+
const ends = [];
|
|
4446
|
+
for (let n = choice.splitAt; n < remainingLines.length; n += fullColumn)
|
|
4447
|
+
ends.push(n);
|
|
4448
|
+
/** How a setting ends its guarded lines: whether this column's
|
|
4449
|
+
* last line is hyphenated, and how many guarded lines are. */
|
|
4450
|
+
const columnEndHyphens = (lines) => ({
|
|
4451
|
+
first: lines[choice.splitAt - 1]?.hyphenated === true,
|
|
4452
|
+
count: ends.filter((n) => lines[n - 1]?.hyphenated).length,
|
|
4453
|
+
});
|
|
4454
|
+
const natural = columnEndHyphens(remainingLines);
|
|
4455
|
+
columnEndHyphenRetried = natural.count > 0;
|
|
4456
|
+
const again = natural.count > 0
|
|
4457
|
+
? measureForColumns({ avoidHyphenAtLines: ends })
|
|
4458
|
+
: undefined;
|
|
4459
|
+
// The re-break is kept when it hyphenates fewer guarded lines. The
|
|
4460
|
+
// line closing this column counts first: it is the one certain
|
|
4461
|
+
// break, while the later ends assume full columns follow.
|
|
4462
|
+
const retried = again && again.measured.lines.length > 0 ? columnEndHyphens(again.measured.lines) : undefined;
|
|
4463
|
+
if (again
|
|
4464
|
+
&& retried
|
|
4465
|
+
&& (again.letterSpacingPx ?? 0) === (letterSpacingPx ?? 0)
|
|
4466
|
+
&& (natural.first !== retried.first ? natural.first : retried.count < natural.count)) {
|
|
4467
|
+
remainingLines = [...raggedLooseLines(again.measured.lines, style.textAlign)];
|
|
4468
|
+
paragraphLines = remainingLines;
|
|
4469
|
+
paragraphBreaks = again.measured.breaks;
|
|
4470
|
+
guardedEnds = ends;
|
|
4471
|
+
columnEndHyphenPart = 0;
|
|
4472
|
+
gateCitedFloats(blockIdx, remainingLines);
|
|
4473
|
+
// The pass over this column starts again: the balancing room it
|
|
4474
|
+
// recorded above is recorded again then.
|
|
4475
|
+
if (balancing && balancing.spaceAbove > 0) {
|
|
4476
|
+
balanceExtraInColumn.set(curCol, (balanceExtraInColumn.get(curCol) ?? 0) - balancing.spaceAbove);
|
|
4477
|
+
}
|
|
4478
|
+
continue;
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
// …and again from a later part: the first re-break assumed full
|
|
4482
|
+
// columns after this one, and a column that ends elsewhere (a widow
|
|
4483
|
+
// kept, a float, a shorter page) can still end on a hyphen. The
|
|
4484
|
+
// paragraph is broken again with that end guarded too, the lines
|
|
4485
|
+
// already placed kept at their breaks (only the rest is broken
|
|
4486
|
+
// again), and the new setting is taken when it puts no hyphen at
|
|
4487
|
+
// this column's end (or fewer at the ends still to come). The
|
|
4488
|
+
// placed lines are checked again all the same: a break trace from
|
|
4489
|
+
// the other measuring path cannot keep them.
|
|
4490
|
+
if (choice.splitAt > 0
|
|
4491
|
+
&& !resolved.bodyText.hyphenateAcrossColumns
|
|
4492
|
+
&& partIndex > 0
|
|
4493
|
+
&& columnEndHyphenPart !== partIndex
|
|
4494
|
+
&& looseLines === undefined
|
|
4495
|
+
&& remainingLines[choice.splitAt - 1]?.hyphenated === true) {
|
|
4496
|
+
columnEndHyphenPart = partIndex;
|
|
4497
|
+
const placed = paragraphLines.length - remainingLines.length;
|
|
4498
|
+
const fullColumn = Math.max(1, Math.floor((contentArea.height + 0.01) / style.lineHeightPx));
|
|
4499
|
+
const later = [];
|
|
4500
|
+
for (let n = choice.splitAt; n < remainingLines.length; n += fullColumn)
|
|
4501
|
+
later.push(placed + n);
|
|
4502
|
+
// The ends already placed keep their guard; the later ones the
|
|
4503
|
+
// last re-break assumed give way to where the columns now end.
|
|
4504
|
+
const ends = [...guardedEnds.filter((n) => n <= placed), ...later];
|
|
4505
|
+
const hyphenatedEnds = (lines) => later.filter((n) => lines[n - 1]?.hyphenated).length;
|
|
4506
|
+
const keepBreaks = paragraphBreaks && paragraphBreaks.at.length >= placed
|
|
4507
|
+
? { path: paragraphBreaks.path, at: paragraphBreaks.at.slice(0, placed) }
|
|
4508
|
+
: undefined;
|
|
4509
|
+
const again = measureForColumns({ avoidHyphenAtLines: ends, ...(keepBreaks ? { keepBreaks } : {}) });
|
|
4510
|
+
const lines = again && (again.letterSpacingPx ?? 0) === (letterSpacingPx ?? 0)
|
|
4511
|
+
? [...raggedLooseLines(again.measured.lines, style.textAlign)]
|
|
4512
|
+
: undefined;
|
|
4513
|
+
const keepsPlaced = lines !== undefined && lines.length > placed
|
|
4514
|
+
&& paragraphLines.slice(0, placed).every((l, i) => lines[i].text === l.text && lines[i].hyphenated === l.hyphenated);
|
|
4515
|
+
if (lines
|
|
4516
|
+
&& keepsPlaced
|
|
4517
|
+
&& (lines[placed + choice.splitAt - 1]?.hyphenated !== true
|
|
4518
|
+
|| hyphenatedEnds(lines) < hyphenatedEnds(paragraphLines))) {
|
|
4519
|
+
paragraphLines = lines;
|
|
4520
|
+
paragraphBreaks = again.measured.breaks;
|
|
4521
|
+
guardedEnds = ends;
|
|
4522
|
+
remainingLines = lines.slice(placed);
|
|
4523
|
+
regateCitedFloats(blockIdx, lines);
|
|
4524
|
+
if (balancing && balancing.spaceAbove > 0) {
|
|
4525
|
+
balanceExtraInColumn.set(curCol, (balanceExtraInColumn.get(curCol) ?? 0) - balancing.spaceAbove);
|
|
4526
|
+
}
|
|
4527
|
+
continue;
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
3443
4530
|
if (choice.splitAt > 0) {
|
|
3444
4531
|
// Consume spacing (negative: a container margin pulling the block up)
|
|
3445
4532
|
if (spacingBefore !== 0) {
|
|
@@ -3457,6 +4544,8 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3457
4544
|
blk.headingLevel = headingLevel;
|
|
3458
4545
|
if (numberPrefix)
|
|
3459
4546
|
blk.numberPrefix = numberPrefix;
|
|
4547
|
+
if (headingNumber !== undefined)
|
|
4548
|
+
blk.headingNumber = headingNumber;
|
|
3460
4549
|
}
|
|
3461
4550
|
blk.lines = resetLinePositions(splitLines, style.lineHeightPx);
|
|
3462
4551
|
blk.dirty = false;
|
|
@@ -3467,8 +4556,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3467
4556
|
}
|
|
3468
4557
|
blk.sourceMap = absoluteSourceMap;
|
|
3469
4558
|
blk.plainPrefixLen = prefixLen;
|
|
4559
|
+
if (balancing)
|
|
4560
|
+
blk.balancing = balancing;
|
|
3470
4561
|
const splitHeight = choice.splitAt * style.lineHeightPx;
|
|
3471
4562
|
placeBlockInColumn(blk, splitHeight, curCol, cursor);
|
|
4563
|
+
citedLinesPlaced(blockIdx, blk);
|
|
3472
4564
|
finalizeListItem(blk, partIndex === 0);
|
|
3473
4565
|
doc.blocks.push(blk);
|
|
3474
4566
|
remainingLines = remainingLines.slice(choice.splitAt);
|
|
@@ -3479,9 +4571,25 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3479
4571
|
}
|
|
3480
4572
|
// choice.splitAt === 0: fall through to push whole paragraph to next column
|
|
3481
4573
|
}
|
|
4574
|
+
// An empty column a band cap cut, under a figure at its head: a block
|
|
4575
|
+
// that cannot start there — none of its lines stays, by the rules
|
|
4576
|
+
// that split it — goes on to the next column of the band, as it does
|
|
4577
|
+
// from such a column uncapped. Forced into it, it ran past the cut,
|
|
4578
|
+
// and the cut was taken where it ended, however low (EF-116: the
|
|
4579
|
+
// columns beside it then ended far above the box under them).
|
|
4580
|
+
const capShortUnderFloat = curCol.blocks.length === 0
|
|
4581
|
+
&& uncappedBottoms.has(curCol)
|
|
4582
|
+
&& reservedOf(curCol).top > 0
|
|
4583
|
+
&& (() => {
|
|
4584
|
+
const cols = bandColumns(doc.pages[cursor.pageIndex], currentBand(doc.pages[cursor.pageIndex], cursor));
|
|
4585
|
+
const at = cols.indexOf(curCol);
|
|
4586
|
+
return at >= 0 && at < cols.length - 1;
|
|
4587
|
+
})();
|
|
3482
4588
|
// Cannot split — advance to next column if current has content (or
|
|
3483
|
-
// the column is a short band that cannot hold the block at all).
|
|
3484
|
-
|
|
4589
|
+
// the column is a short band that cannot hold the block at all). A
|
|
4590
|
+
// block that can split moves on from a short band however tall it is:
|
|
4591
|
+
// the next, taller column breaks it by the rules (EF-97).
|
|
4592
|
+
if (curCol.blocks.length > 0 || capShortUnderFloat || (shortColumn && (effectiveRemainHeight <= contentArea.height || canSplit))) {
|
|
3485
4593
|
if (curCol.blocks.length === 0)
|
|
3486
4594
|
shortColumnMoves++;
|
|
3487
4595
|
// Heading keep-with-next (no-fit variant): when a block can't fit
|
|
@@ -3504,7 +4612,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3504
4612
|
&& headingRun > 0 && !runFillsColumn;
|
|
3505
4613
|
const pullsRun = vdtType === 'heading' && (!runFillsColumn || shortColumn);
|
|
3506
4614
|
if (resolved.headings.keepWithNext && (pullsRun || strands)) {
|
|
3507
|
-
const rolledBack =
|
|
4615
|
+
const rolledBack = rollbackHeadings(curCol);
|
|
3508
4616
|
if (rolledBack.length > 0) {
|
|
3509
4617
|
blockIdx = (rolledBack[0].contentIndex ?? blockIdx - rolledBack.length) - 1;
|
|
3510
4618
|
pendingSpacing = 0;
|
|
@@ -3532,8 +4640,18 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3532
4640
|
blk.letterSpacing = letterSpacingPx;
|
|
3533
4641
|
blk.contentIndex = blockIdx;
|
|
3534
4642
|
stampBlockExtras(blk, rawBlock);
|
|
3535
|
-
|
|
4643
|
+
// Stamp the prefix as the other placement branches do: a full-page
|
|
4644
|
+
// opener lands here, and paint and running heads read its
|
|
4645
|
+
// `{chapterNumber}` from `numberPrefix`, as the measure did (EF-59).
|
|
4646
|
+
if (partIndex === 0) {
|
|
3536
4647
|
blk.headingLevel = headingLevel;
|
|
4648
|
+
if (numberPrefix)
|
|
4649
|
+
blk.numberPrefix = numberPrefix;
|
|
4650
|
+
if (headingNumber !== undefined)
|
|
4651
|
+
blk.headingNumber = headingNumber;
|
|
4652
|
+
}
|
|
4653
|
+
if (hiddenHeading)
|
|
4654
|
+
blk.hidden = true;
|
|
3537
4655
|
if (partIndex === 0 && vdtType === 'heading' && rawBlock.attrs)
|
|
3538
4656
|
blk.attrs = rawBlock.attrs;
|
|
3539
4657
|
if (partIndex === 0 && vdtType === 'heading' && rawBlock.attrSources) {
|
|
@@ -3553,11 +4671,33 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3553
4671
|
}
|
|
3554
4672
|
blk.sourceMap = absoluteSourceMap;
|
|
3555
4673
|
blk.plainPrefixLen = prefixLen;
|
|
3556
|
-
|
|
4674
|
+
if (balancing)
|
|
4675
|
+
blk.balancing = balancing;
|
|
4676
|
+
// A heading whose design (or `minHeight`) reaches past the column's
|
|
4677
|
+
// foot — a full-page cover, say — claims the rest of its column, and
|
|
4678
|
+
// of the page when it spans it: the next block opens the next column
|
|
4679
|
+
// or page instead of running on under (or beside) the design. Its
|
|
4680
|
+
// block runs down to the column's foot (EF-91), so the design is laid
|
|
4681
|
+
// out against the band it claims — elements anchored to the band's
|
|
4682
|
+
// middle or foot, or filling it, keep to that band, as they do when
|
|
4683
|
+
// the reservation fits — rather than against the height of its text.
|
|
4684
|
+
const claimsColumn = vdtType === 'heading' && effectiveRemainHeight > totalRemainHeight;
|
|
4685
|
+
const placedHeight = claimsColumn
|
|
4686
|
+
? Math.max(totalRemainHeight, Math.min(effectiveRemainHeight, curCol.availableHeight))
|
|
4687
|
+
: totalRemainHeight;
|
|
4688
|
+
placeBlockInColumn(blk, placedHeight, curCol, cursor);
|
|
4689
|
+
citedLinesPlaced(blockIdx, blk);
|
|
3557
4690
|
finalizeListItem(blk, partIndex === 0);
|
|
3558
4691
|
doc.blocks.push(blk);
|
|
4692
|
+
if (claimsColumn) {
|
|
4693
|
+
curCol.availableHeight = 0;
|
|
4694
|
+
if (headingLevels.forBlock(rawBlock)?.span === 'page')
|
|
4695
|
+
reserveOpenerBand(curCol, effectiveRemainHeight, blk, style.marginBottomPx);
|
|
4696
|
+
}
|
|
4697
|
+
if (headingDesign)
|
|
4698
|
+
markSideObstacles(blk, headingDesign);
|
|
3559
4699
|
if (vdtType === 'listItem') {
|
|
3560
|
-
pendingSpacing = nextIsListItem ? listBullet
|
|
4700
|
+
pendingSpacing = nextIsListItem ? itemGapPx(listBullet) : style.marginBottomPx;
|
|
3561
4701
|
}
|
|
3562
4702
|
else {
|
|
3563
4703
|
pendingSpacing = style.marginBottomPx;
|
|
@@ -3569,6 +4709,52 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3569
4709
|
// End of the document: level the closing band and place any floats still
|
|
3570
4710
|
// pending (referenced on the last page) on pages appended after it.
|
|
3571
4711
|
closeFlowSegment(contentBlocks.length);
|
|
4712
|
+
// A band that is still cut at the end of the pass (a trailing cap: the
|
|
4713
|
+
// closing band of a chapter or of the document) must hold what it took,
|
|
4714
|
+
// under the same rules as an uncut one. Two ways a cut too low breaks
|
|
4715
|
+
// them, and in both the cap is not delivered, so the driver grows it a
|
|
4716
|
+
// line (or drops it and keeps the uncut layout):
|
|
4717
|
+
// - a block that cannot split across the cut — a paragraph tail the
|
|
4718
|
+
// orphan and widow minimums keep whole, a keep-together box — is
|
|
4719
|
+
// force-placed into an empty capped column and runs past its foot; the
|
|
4720
|
+
// renderers clip the column to its box, so its last lines would never
|
|
4721
|
+
// be painted (EF-55, EF-72). What counts is what is painted: a list
|
|
4722
|
+
// tail's box bakes its bottom margin in and may reach past the cut
|
|
4723
|
+
// with every line inside it;
|
|
4724
|
+
// - a keep-with-next heading opening an empty capped column stays there
|
|
4725
|
+
// (a column-opening heading is never pushed on) while its text goes to
|
|
4726
|
+
// the next column of the band: the heading closes its column (EF-61).
|
|
4727
|
+
const capStrandsHeading = (cols) => {
|
|
4728
|
+
if (!resolved.headings.keepWithNext)
|
|
4729
|
+
return false;
|
|
4730
|
+
return cols.some((c, i) => {
|
|
4731
|
+
const last = c.blocks[c.blocks.length - 1];
|
|
4732
|
+
return c.bandCapped && last !== undefined && isFreeHeading(last) && !last.hidden
|
|
4733
|
+
&& cols.slice(i + 1).some((next) => next.blocks.length > 0);
|
|
4734
|
+
});
|
|
4735
|
+
};
|
|
4736
|
+
for (const [capIndex, cols] of cappedBandColumns) {
|
|
4737
|
+
if (!spanPlacedInBand.has(capIndex))
|
|
4738
|
+
continue;
|
|
4739
|
+
if (capClipsLines(cols) || capStrandsHeading(cols))
|
|
4740
|
+
spanPlacedInBand.delete(capIndex);
|
|
4741
|
+
}
|
|
4742
|
+
// A styled section's pages draw the column rule of its own layout where
|
|
4743
|
+
// that differs from the document's (EF-112); the renderers take the
|
|
4744
|
+
// document's on every other page.
|
|
4745
|
+
const docRule = resolved.layout.columnRule;
|
|
4746
|
+
const docRuleWidthPx = dimensionToPx(docRule.lineWidth, dpi);
|
|
4747
|
+
for (const page of doc.pages) {
|
|
4748
|
+
const layout = pageLayoutOf(page);
|
|
4749
|
+
if (!layout || layout === resolved.layout || page.partInfo)
|
|
4750
|
+
continue;
|
|
4751
|
+
const rule = layout.columnRule;
|
|
4752
|
+
const lineWidthPx = dimensionToPx(rule.lineWidth, dpi);
|
|
4753
|
+
const same = rule.enabled === docRule.enabled
|
|
4754
|
+
&& (!rule.enabled || (rule.color.hex === docRule.color.hex && lineWidthPx === docRuleWidthPx));
|
|
4755
|
+
if (!same)
|
|
4756
|
+
page.columnRule = { enabled: rule.enabled, color: rule.color.hex, lineWidthPx };
|
|
4757
|
+
}
|
|
3572
4758
|
// Stamp page-number info onto every page (including blank parity pages).
|
|
3573
4759
|
const labels = buildPageLabels(doc.pages.length, pageNumberSegments);
|
|
3574
4760
|
for (let i = 0; i < doc.pages.length; i++) {
|
|
@@ -3585,7 +4771,22 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
3585
4771
|
.map((s) => s.startPageIndex);
|
|
3586
4772
|
if (restarts.length > 0)
|
|
3587
4773
|
doc.pageNumberRestarts = restarts;
|
|
3588
|
-
buildHeadersAndFooters(doc, resourceById
|
|
4774
|
+
buildHeadersAndFooters(doc, resourceById, {
|
|
4775
|
+
// Where two palette entries share a base value, each kind of flow
|
|
4776
|
+
// colour follows its own settings under a part or section palette.
|
|
4777
|
+
// (The default resource types set no caption style of their own.)
|
|
4778
|
+
flowColorValues: (overrides) => flowColorValues(config, overrides, config?.resourceTypes),
|
|
4779
|
+
// `{firstMark.<style>}` / `{lastMark.<style>}`: a paragraph of a
|
|
4780
|
+
// `:::paragraphs` container marks the page with its leading bold run.
|
|
4781
|
+
paragraphMark: (idx) => {
|
|
4782
|
+
const styleId = paragraphContainers.byBlock[idx]?.styleId;
|
|
4783
|
+
const block = contentBlocks[idx];
|
|
4784
|
+
if (styleId === undefined || block?.type !== 'paragraph')
|
|
4785
|
+
return undefined;
|
|
4786
|
+
const text = leadingBoldText(block.spans);
|
|
4787
|
+
return text.length > 0 ? { styleId, text } : undefined;
|
|
4788
|
+
},
|
|
4789
|
+
});
|
|
3589
4790
|
doc.converged = true;
|
|
3590
4791
|
doc.iterationCount = 1;
|
|
3591
4792
|
return { doc, forcedBreakPages, bandCapProposals, spanPlacedInBand, bandCapsApplied, looseOutcome, captionUnderProposals };
|
|
@@ -3636,6 +4837,24 @@ export async function buildDocumentAsync(content, config, cache, options) {
|
|
|
3636
4837
|
}
|
|
3637
4838
|
/** The build as a generator: one `yield` after every placement pass. */
|
|
3638
4839
|
export function* buildDocumentGen(content, config, cache, options) {
|
|
4840
|
+
const doc = yield* buildDocumentRounds(content, config, cache, options);
|
|
4841
|
+
// References and markup the source names wrongly: read from the source
|
|
4842
|
+
// once, located on the pages of the finished layout. Kept apart from
|
|
4843
|
+
// `doc.warnings`, whose entries keep their postext 1.4 shape.
|
|
4844
|
+
const found = collectContentWarnings(content.markdown, config, content.resources ?? []);
|
|
4845
|
+
if (found.length > 0)
|
|
4846
|
+
doc.contentWarnings = locateContentWarnings(doc, found);
|
|
4847
|
+
// Config values the build replaced (an unknown number format, a font
|
|
4848
|
+
// stack, a side column no column width can take): walked once per build,
|
|
4849
|
+
// not per pass — they belong to no page.
|
|
4850
|
+
const configWarnings = collectConfigWarnings(config);
|
|
4851
|
+
if (configWarnings.length > 0)
|
|
4852
|
+
doc.configWarnings = configWarnings;
|
|
4853
|
+
return doc;
|
|
4854
|
+
}
|
|
4855
|
+
/** {@link buildDocumentGen} before the document-wide warnings: the
|
|
4856
|
+
* contents rounds around the balanced build. */
|
|
4857
|
+
function* buildDocumentRounds(content, config, cache, options) {
|
|
3639
4858
|
// A document printing its own table of contents (`:::toc` with no
|
|
3640
4859
|
// host-supplied outline) is laid out with the page labels of the previous
|
|
3641
4860
|
// build until they no longer change: the contents' own length moves what
|
|
@@ -3741,7 +4960,7 @@ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
|
|
|
3741
4960
|
range,
|
|
3742
4961
|
bestScore: score,
|
|
3743
4962
|
attempts: old?.attempts ?? 0,
|
|
3744
|
-
done: score === 0,
|
|
4963
|
+
done: score === 0 && boxRoomIn(gaps, range) <= BOX_ROOM_EPS_PX,
|
|
3745
4964
|
stable: score === 0,
|
|
3746
4965
|
failedLoose: old?.failedLoose ?? new Set(),
|
|
3747
4966
|
failedLines: old?.failedLines ?? new Set(),
|
|
@@ -3817,7 +5036,7 @@ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
|
|
|
3817
5036
|
const warnings = [
|
|
3818
5037
|
...(best.doc.warnings ?? []).filter((w) => !taken(w.pageIndex)),
|
|
3819
5038
|
...(next.doc.warnings ?? []).filter((w) => taken(w.pageIndex)),
|
|
3820
|
-
].sort((a, b) => a.pageIndex - b.pageIndex);
|
|
5039
|
+
].sort((a, b) => (a.pageIndex ?? -1) - (b.pageIndex ?? -1));
|
|
3821
5040
|
const doc = { ...best.doc, pages, blocks, ...(warnings.length > 0 ? { warnings } : { warnings: undefined }) };
|
|
3822
5041
|
const pageBest = pageOfContent(best.doc);
|
|
3823
5042
|
const pageNext = pageOfContent(next.doc);
|
|
@@ -3917,6 +5136,7 @@ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
|
|
|
3917
5136
|
optimalLineBreaking: best.doc.config.bodyText.optimalLineBreaking,
|
|
3918
5137
|
failedLoose,
|
|
3919
5138
|
failedLines,
|
|
5139
|
+
closingBox: balancing.closingBox,
|
|
3920
5140
|
});
|
|
3921
5141
|
// The levers newly proposed, by segment; those of a segment that is
|
|
3922
5142
|
// done (plateaued, out of attempts) are withdrawn from the pass.
|
|
@@ -3965,6 +5185,7 @@ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
|
|
|
3965
5185
|
const nextGaps = collectColumnGaps(next.doc, next.forcedBreakPages);
|
|
3966
5186
|
const capPage = pageOfContent(best.doc);
|
|
3967
5187
|
const accepted = [];
|
|
5188
|
+
const boxKeys = boxLeverKeys(gaps);
|
|
3968
5189
|
for (const si of trying) {
|
|
3969
5190
|
const s = segments[si];
|
|
3970
5191
|
// An earlier segment's cascade shifted this one's pages: the pass
|
|
@@ -3992,6 +5213,13 @@ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
|
|
|
3992
5213
|
for (const k of looseFailed)
|
|
3993
5214
|
s.failedLoose.add(k);
|
|
3994
5215
|
const looseWon = keysLoose.filter((k) => typeof next.looseOutcome.get(k) === 'number');
|
|
5216
|
+
// A box closing its column that moves down onto the column's last
|
|
5217
|
+
// grid slot (or level with the column beside it) closes less than a
|
|
5218
|
+
// line, which the score does not count. On its own — no other new
|
|
5219
|
+
// lever in the segment — such a pass is kept when it holds the
|
|
5220
|
+
// score, moves nothing across a column and closes room under a box
|
|
5221
|
+
// (EF-70).
|
|
5222
|
+
const boxesOnly = keysLines.length > 0 && keysLoose.length === 0 && keysLines.every((k) => boxKeys.has(k));
|
|
3995
5223
|
if (score < s.bestScore) {
|
|
3996
5224
|
for (const k of keysLines)
|
|
3997
5225
|
applied.lines.set(k, proposal.lines.get(k));
|
|
@@ -4004,14 +5232,31 @@ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
|
|
|
4004
5232
|
}
|
|
4005
5233
|
accepted.push(s.range);
|
|
4006
5234
|
}
|
|
5235
|
+
else if (boxesOnly && score === s.bestScore
|
|
5236
|
+
&& firstDivergentColumn(best.doc, next.doc, s.range) === null
|
|
5237
|
+
&& boxRoomIn(nextGaps, s.range) < boxRoomIn(gaps, s.range) - BOX_ROOM_EPS_PX) {
|
|
5238
|
+
for (const k of keysLines)
|
|
5239
|
+
applied.lines.set(k, proposal.lines.get(k));
|
|
5240
|
+
accepted.push(s.range);
|
|
5241
|
+
}
|
|
4007
5242
|
else if (!containCascade(next, s, keysLines, keysLoose, gaps)) {
|
|
4008
5243
|
// Plateau or regression without a cascade to contain: retry when
|
|
4009
5244
|
// a loose candidate was just blacklisted (the proposer falls
|
|
4010
5245
|
// through to the next one), or when the new loose paragraphs
|
|
4011
5246
|
// gained their lines yet the segment did not improve (the gain
|
|
4012
5247
|
// landed elsewhere — drop them too). A pure spacing plateau means
|
|
4013
|
-
// the segment is done: it keeps the best layout found so far
|
|
4014
|
-
|
|
5248
|
+
// the segment is done: it keeps the best layout found so far —
|
|
5249
|
+
// unless a closing box rode along with the levers that bought
|
|
5250
|
+
// nothing: those are dropped and the box is tried on its own.
|
|
5251
|
+
const boxes = keysLines.filter((k) => boxKeys.has(k));
|
|
5252
|
+
if (boxes.length > 0 && !boxesOnly) {
|
|
5253
|
+
for (const k of keysLines)
|
|
5254
|
+
if (!boxKeys.has(k))
|
|
5255
|
+
s.failedLines.add(k);
|
|
5256
|
+
for (const k of keysLoose)
|
|
5257
|
+
s.failedLoose.add(k);
|
|
5258
|
+
}
|
|
5259
|
+
else if (looseFailed.length > 0 || looseWon.length > 0) {
|
|
4015
5260
|
for (const k of looseWon)
|
|
4016
5261
|
s.failedLoose.add(k);
|
|
4017
5262
|
}
|