postext 1.23.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/bundle/bitmapInfo.test.d.ts +2 -0
- package/dist/__tests__/bundle/bitmapInfo.test.d.ts.map +1 -0
- package/dist/__tests__/bundle/bitmapInfo.test.js +78 -0
- package/dist/__tests__/bundle/bitmapInfo.test.js.map +1 -0
- package/dist/__tests__/bundle/designOverflowPin.test.d.ts +2 -0
- package/dist/__tests__/bundle/designOverflowPin.test.d.ts.map +1 -0
- package/dist/__tests__/bundle/designOverflowPin.test.js +87 -0
- package/dist/__tests__/bundle/designOverflowPin.test.js.map +1 -0
- package/dist/__tests__/bundle.test.js +1 -1
- package/dist/__tests__/bundle.test.js.map +1 -1
- package/dist/__tests__/codeBlocks.test.js +1 -1
- package/dist/__tests__/codeBlocks.test.js.map +1 -1
- package/dist/__tests__/design/designTextOverflow.test.d.ts +2 -0
- package/dist/__tests__/design/designTextOverflow.test.d.ts.map +1 -0
- package/dist/__tests__/design/designTextOverflow.test.js +102 -0
- package/dist/__tests__/design/designTextOverflow.test.js.map +1 -0
- package/dist/__tests__/exports.test.js +11 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/frontmatterInvalid.test.d.ts +2 -0
- package/dist/__tests__/frontmatterInvalid.test.d.ts.map +1 -0
- package/dist/__tests__/frontmatterInvalid.test.js +101 -0
- package/dist/__tests__/frontmatterInvalid.test.js.map +1 -0
- package/dist/__tests__/hardBreaks.test.js +1 -1
- package/dist/__tests__/hardBreaks.test.js.map +1 -1
- package/dist/__tests__/headingFirstLineIndent.test.d.ts +2 -0
- package/dist/__tests__/headingFirstLineIndent.test.d.ts.map +1 -0
- package/dist/__tests__/headingFirstLineIndent.test.js +185 -0
- package/dist/__tests__/headingFirstLineIndent.test.js.map +1 -0
- package/dist/__tests__/headingInlineMarks.test.js +1 -1
- package/dist/__tests__/headingInlineMarks.test.js.map +1 -1
- package/dist/__tests__/headingLetterSpacing.test.js +1 -1
- package/dist/__tests__/headingLetterSpacing.test.js.map +1 -1
- package/dist/__tests__/mathSizeMigration.test.js +2 -2
- package/dist/__tests__/mathSizeMigration.test.js.map +1 -1
- package/dist/__tests__/pipeline/bitmapResolution.test.d.ts +2 -0
- package/dist/__tests__/pipeline/bitmapResolution.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/bitmapResolution.test.js +139 -0
- package/dist/__tests__/pipeline/bitmapResolution.test.js.map +1 -0
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js.map +1 -1
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +1 -1
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js.map +1 -1
- package/dist/__tests__/pipeline/colonListRoom.test.js +1 -1
- package/dist/__tests__/pipeline/colonListRoom.test.js.map +1 -1
- package/dist/__tests__/pipeline/compoundBreaks.test.js +1 -1
- package/dist/__tests__/pipeline/compoundBreaks.test.js.map +1 -1
- package/dist/__tests__/pipeline/dashBreaks.test.js +1 -1
- package/dist/__tests__/pipeline/dashBreaks.test.js.map +1 -1
- package/dist/__tests__/pipeline/designTextTruncated.test.d.ts +2 -0
- package/dist/__tests__/pipeline/designTextTruncated.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/designTextTruncated.test.js +116 -0
- package/dist/__tests__/pipeline/designTextTruncated.test.js.map +1 -0
- package/dist/__tests__/pipeline/floatPlacement.test.js +3 -3
- package/dist/__tests__/pipeline/floatPlacement.test.js.map +1 -1
- package/dist/__tests__/pipeline/floatShrink.test.d.ts +2 -0
- package/dist/__tests__/pipeline/floatShrink.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/floatShrink.test.js +298 -0
- package/dist/__tests__/pipeline/floatShrink.test.js.map +1 -0
- package/dist/__tests__/pipeline/headingSplitOrphans.test.js +1 -1
- package/dist/__tests__/pipeline/headingSplitOrphans.test.js.map +1 -1
- package/dist/__tests__/pipeline/inlineFigureSpace.test.js +1 -1
- package/dist/__tests__/pipeline/inlineFigureSpace.test.js.map +1 -1
- package/dist/__tests__/pipeline/paragraphContainerTail.test.js +1 -1
- package/dist/__tests__/pipeline/paragraphContainerTail.test.js.map +1 -1
- package/dist/__tests__/pipeline/raggedOptimal.test.js +1 -1
- package/dist/__tests__/pipeline/raggedOptimal.test.js.map +1 -1
- package/dist/__tests__/pipeline/textWrap.test.d.ts +2 -0
- package/dist/__tests__/pipeline/textWrap.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/textWrap.test.js +453 -0
- package/dist/__tests__/pipeline/textWrap.test.js.map +1 -0
- package/dist/__tests__/preflight.test.js +30 -1
- package/dist/__tests__/preflight.test.js.map +1 -1
- package/dist/__tests__/presets/showcaseDesignOverflow.test.d.ts +2 -0
- package/dist/__tests__/presets/showcaseDesignOverflow.test.d.ts.map +1 -0
- package/dist/__tests__/presets/showcaseDesignOverflow.test.js +58 -0
- package/dist/__tests__/presets/showcaseDesignOverflow.test.js.map +1 -0
- package/dist/__tests__/table/booktabs.test.d.ts +2 -0
- package/dist/__tests__/table/booktabs.test.d.ts.map +1 -0
- package/dist/__tests__/table/booktabs.test.js +290 -0
- package/dist/__tests__/table/booktabs.test.js.map +1 -0
- package/dist/__tests__/textWrapMeasure.test.d.ts +2 -0
- package/dist/__tests__/textWrapMeasure.test.d.ts.map +1 -0
- package/dist/__tests__/textWrapMeasure.test.js +162 -0
- package/dist/__tests__/textWrapMeasure.test.js.map +1 -0
- package/dist/__tests__/verseLines.test.js +134 -4
- package/dist/__tests__/verseLines.test.js.map +1 -1
- package/dist/bitmapResolution.d.ts +42 -0
- package/dist/bitmapResolution.d.ts.map +1 -0
- package/dist/bitmapResolution.js +82 -0
- package/dist/bitmapResolution.js.map +1 -0
- package/dist/bundle/codec.d.ts +9 -2
- package/dist/bundle/codec.d.ts.map +1 -1
- package/dist/bundle/codec.js +24 -2
- package/dist/bundle/codec.js.map +1 -1
- package/dist/bundle/configVersion.d.ts +49 -8
- package/dist/bundle/configVersion.d.ts.map +1 -1
- package/dist/bundle/configVersion.js +161 -6
- package/dist/bundle/configVersion.js.map +1 -1
- package/dist/bundle/index.d.ts +3 -3
- package/dist/bundle/index.d.ts.map +1 -1
- package/dist/bundle/index.js +2 -2
- package/dist/bundle/index.js.map +1 -1
- package/dist/bundle/manifest.d.ts +12 -2
- package/dist/bundle/manifest.d.ts.map +1 -1
- package/dist/bundle/manifest.js +132 -6
- package/dist/bundle/manifest.js.map +1 -1
- package/dist/bundle/types.d.ts +28 -2
- package/dist/bundle/types.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.js +6 -4
- package/dist/canvas-backend/blockRender.js.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.js +15 -1
- package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
- package/dist/configWarnings.js +29 -2
- package/dist/configWarnings.js.map +1 -1
- package/dist/defaults/headerFooter.d.ts +21 -6
- package/dist/defaults/headerFooter.d.ts.map +1 -1
- package/dist/defaults/headerFooter.js +37 -14
- package/dist/defaults/headerFooter.js.map +1 -1
- package/dist/defaults/headings.d.ts +5 -4
- package/dist/defaults/headings.d.ts.map +1 -1
- package/dist/defaults/headings.js +15 -4
- 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 +30 -1
- package/dist/defaults/layout.d.ts.map +1 -1
- package/dist/defaults/layout.js +105 -1
- package/dist/defaults/layout.js.map +1 -1
- package/dist/defaults/parts.js +4 -4
- package/dist/defaults/parts.js.map +1 -1
- package/dist/defaults/tableStyle.d.ts.map +1 -1
- package/dist/defaults/tableStyle.js +44 -0
- package/dist/defaults/tableStyle.js.map +1 -1
- package/dist/defaults/toc.js +2 -2
- package/dist/defaults/verse.d.ts +2 -1
- package/dist/defaults/verse.d.ts.map +1 -1
- package/dist/defaults/verse.js +6 -1
- package/dist/defaults/verse.js.map +1 -1
- package/dist/design/layout.d.ts +15 -1
- package/dist/design/layout.d.ts.map +1 -1
- package/dist/design/layout.js +28 -4
- package/dist/design/layout.js.map +1 -1
- package/dist/frontmatter.d.ts +20 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +69 -3
- package/dist/frontmatter.js.map +1 -1
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +22 -4
- package/dist/html-backend.js.map +1 -1
- package/dist/index.d.ts +13 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/measure/bidiLines.d.ts +4 -3
- package/dist/measure/bidiLines.d.ts.map +1 -1
- package/dist/measure/bidiLines.js +12 -7
- package/dist/measure/bidiLines.js.map +1 -1
- package/dist/measure/cjkCompose.d.ts.map +1 -1
- package/dist/measure/cjkCompose.js +7 -3
- package/dist/measure/cjkCompose.js.map +1 -1
- package/dist/measure/hardBreaks.d.ts.map +1 -1
- package/dist/measure/hardBreaks.js +13 -1
- package/dist/measure/hardBreaks.js.map +1 -1
- package/dist/measure/index.d.ts +3 -3
- package/dist/measure/index.d.ts.map +1 -1
- package/dist/measure/index.js +4 -4
- package/dist/measure/index.js.map +1 -1
- package/dist/measure/plain.d.ts.map +1 -1
- package/dist/measure/plain.js +11 -7
- package/dist/measure/plain.js.map +1 -1
- package/dist/measure/rich.d.ts.map +1 -1
- package/dist/measure/rich.js +9 -6
- package/dist/measure/rich.js.map +1 -1
- package/dist/measure/types.d.ts +49 -3
- package/dist/measure/types.d.ts.map +1 -1
- package/dist/measure/types.js +67 -3
- package/dist/measure/types.js.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +488 -31
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/buildBlockKind.d.ts +5 -0
- package/dist/pipeline/buildBlockKind.d.ts.map +1 -1
- package/dist/pipeline/buildBlockKind.js +14 -1
- package/dist/pipeline/buildBlockKind.js.map +1 -1
- package/dist/pipeline/buildMeasurement.js +1 -1
- package/dist/pipeline/buildMeasurement.js.map +1 -1
- package/dist/pipeline/columnBalancing.d.ts.map +1 -1
- package/dist/pipeline/columnBalancing.js +5 -0
- package/dist/pipeline/columnBalancing.js.map +1 -1
- package/dist/pipeline/contentWarnings.d.ts +16 -0
- package/dist/pipeline/contentWarnings.d.ts.map +1 -1
- package/dist/pipeline/contentWarnings.js +113 -17
- package/dist/pipeline/contentWarnings.js.map +1 -1
- package/dist/pipeline/floatPlacement.d.ts +34 -4
- package/dist/pipeline/floatPlacement.d.ts.map +1 -1
- package/dist/pipeline/floatPlacement.js +35 -5
- package/dist/pipeline/floatPlacement.js.map +1 -1
- package/dist/pipeline/floatSlots.d.ts +7 -0
- package/dist/pipeline/floatSlots.d.ts.map +1 -1
- package/dist/pipeline/floatSlots.js.map +1 -1
- package/dist/pipeline/headerFooter.d.ts +4 -1
- package/dist/pipeline/headerFooter.d.ts.map +1 -1
- package/dist/pipeline/headerFooter.js +19 -5
- package/dist/pipeline/headerFooter.js.map +1 -1
- package/dist/pipeline/measureContentBlock.d.ts +7 -0
- package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
- package/dist/pipeline/measureContentBlock.js +15 -6
- package/dist/pipeline/measureContentBlock.js.map +1 -1
- package/dist/pipeline/placement.d.ts.map +1 -1
- package/dist/pipeline/placement.js +7 -0
- package/dist/pipeline/placement.js.map +1 -1
- package/dist/pipeline/resourceLayout.d.ts +15 -0
- package/dist/pipeline/resourceLayout.d.ts.map +1 -1
- package/dist/pipeline/resourceLayout.js +95 -15
- package/dist/pipeline/resourceLayout.js.map +1 -1
- package/dist/pipeline/styles.d.ts.map +1 -1
- package/dist/pipeline/styles.js +8 -5
- package/dist/pipeline/styles.js.map +1 -1
- package/dist/pipeline/textWrap.d.ts +94 -0
- package/dist/pipeline/textWrap.d.ts.map +1 -0
- package/dist/pipeline/textWrap.js +188 -0
- package/dist/pipeline/textWrap.js.map +1 -0
- package/dist/pipeline/verseLines.d.ts +22 -2
- package/dist/pipeline/verseLines.d.ts.map +1 -1
- package/dist/pipeline/verseLines.js +52 -4
- package/dist/pipeline/verseLines.js.map +1 -1
- package/dist/preflight.d.ts +46 -1
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +42 -7
- package/dist/preflight.js.map +1 -1
- package/dist/table/booktabs.d.ts +37 -0
- package/dist/table/booktabs.d.ts.map +1 -0
- package/dist/table/booktabs.js +82 -0
- package/dist/table/booktabs.js.map +1 -0
- package/dist/types.d.ts +227 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +131 -6
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js +3 -2
- package/dist/vdt.js.map +1 -1
- package/dist/word/index.d.ts +1 -1
- package/dist/word/index.d.ts.map +1 -1
- package/dist/word/index.js +1 -1
- package/dist/word/index.js.map +1 -1
- package/dist/word/toMarkdown.d.ts +10 -0
- package/dist/word/toMarkdown.d.ts.map +1 -1
- package/dist/word/toMarkdown.js +16 -1
- package/dist/word/toMarkdown.js.map +1 -1
- package/package.json +1 -1
package/dist/pipeline/build.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withBitmapResolutions } from '../bitmapResolution';
|
|
1
2
|
import { plainTitleBreaks } from '../parse/inlineFormatting';
|
|
2
3
|
import { dimensionToPx } from '../units';
|
|
3
4
|
import { createVDTDocument, createVDTBlock, createBoundingBox, createVDTColumn, pageIsMirrored, pageIsVertical, flowRectToPage, } from '../vdt';
|
|
@@ -42,13 +43,14 @@ import { planParts, derivePartMeasureContext } from './parts';
|
|
|
42
43
|
import { layoutCallout, offsetBlock, offsetCalloutToAbsolute, pickCalloutStyle, planCallouts, resolveCalloutAttrs, } from './calloutLayout';
|
|
43
44
|
import { layoutCaptionText, layoutResourceBlock, planTableSlice } from './resourceLayout';
|
|
44
45
|
import { computeFloatPlan, floatedResourceIds, floatColumnCount, } from './floatPlacement';
|
|
45
|
-
import { enumerateCurrentPageSlots, measureFloatBand, floatGapAbove, measureSideStack, clearSideObstacles, columnHasFloatBand, fitsStrict, trueBottom, } from './floatSlots';
|
|
46
|
+
import { enumerateCurrentPageSlots, measureFloatBand, floatGapAbove, floatGapBelow, measureSideStack, clearSideObstacles, columnHasFloatBand, fitsStrict, trueBottom, } from './floatSlots';
|
|
46
47
|
import { computeHeadingContext, computeResourceNumberingState, } from './resourceNumbering';
|
|
47
48
|
import { documentLocale, effectiveResourceTypes } from '../defaults/resourceTypes';
|
|
48
49
|
import { resolveCalloutStylesConfig } from '../defaults/calloutStyles';
|
|
49
50
|
import { codeBoxStyle, codeParseOptions, wrapCodeBlocks } from './codeBlocks';
|
|
50
51
|
import { buildCodeLineNumbers, codeOverflowWarnings } from './codeLines';
|
|
51
52
|
import { pickTableStyle } from '../defaults/tableStyle';
|
|
53
|
+
import { DEFAULT_FLOAT_MIN_SCALE } from '../defaults/layout';
|
|
52
54
|
import { buildLineNumbers, lineNumberWarnings } from './lineNumbers';
|
|
53
55
|
import { buildHeadersAndFooters, defaultOpenerTitle, headingDesignBoxes, measureDefaultOpenerHeight, measureHeadingDesign } from './headerFooter';
|
|
54
56
|
import { flowColorValues } from './partPalette';
|
|
@@ -56,7 +58,7 @@ import { chapterNumberCounter, leadingBoldText } from './placeholders';
|
|
|
56
58
|
import { proposeBalanceLines, collectColumnGaps, firstDivergentColumn, gapLinesIn, boxRoomIn, boxLeverKeys, pageSegments, MAX_BALANCING_PASSES, MAX_BALANCING_PASSES_PER_DOCUMENT, balanceKey, candidateKey, flexFigureKey, flexFloatKey } from './columnBalancing';
|
|
57
59
|
import { applyBandCap, uncapBand, columnBottom, bandCapLines, bandTop, bareBandTop, resolveBandCapsGen, drainPasses, resolveTrailingCapsGen, bandCapLinesAroundZone, } from './bandCaps';
|
|
58
60
|
import { raggedLooseLines } from './raggedLines';
|
|
59
|
-
import { cjkLooseLineWarnings, collectContentWarnings, dropCapWarnings, joiningLetterSpacingWarnings, locateContentWarnings, wordOverflowWarnings } from './contentWarnings';
|
|
61
|
+
import { cjkLooseLineWarnings, collectContentWarnings, designTruncationWarnings, dropCapWarnings, floatShrinkWarnings, joiningLetterSpacingWarnings, locateContentWarnings, wordOverflowWarnings } from './contentWarnings';
|
|
60
62
|
import { dropCapSinkAfter } from './dropCap';
|
|
61
63
|
import { mostlyJoiningScript } from '../measure/joining';
|
|
62
64
|
import { annotateDocument } from '../cjkMarks';
|
|
@@ -67,6 +69,8 @@ import { overlineEmphasis } from '../emphasisOverline';
|
|
|
67
69
|
import { withBookTitleBrackets } from './annotations';
|
|
68
70
|
import { paperByBlock, stampPagePaper } from './paper';
|
|
69
71
|
import { headingLineSpanBand } from './lineSpan';
|
|
72
|
+
import { columnCursorY, insetsBefore, minTextWidthPx, openColumnWrap, resolveCalloutWrap, resolveResourceWrap, runsBesideWrap, sameInsetsFrom, settleColumnWraps, transparentToWrap, wrapGapPx, wrapInsetSteps } from './textWrap';
|
|
73
|
+
import { lineInsetsAt } from '../measure/types';
|
|
70
74
|
/** Tolerance for "does this block fit" checks against a column's free
|
|
71
75
|
* height, absorbing floating-point drift between grid multiples. */
|
|
72
76
|
const FIT_EPS = 0.01;
|
|
@@ -225,6 +229,12 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
225
229
|
* shortened it, so what does not fit it may still fit a full column. */
|
|
226
230
|
const shortSideColumn = (page, col) => col.bbox.height < page.contentArea.height - 0.5;
|
|
227
231
|
const resolved = resolveAllConfig(config);
|
|
232
|
+
// Bitmaps sized by the document's `layout.bitmapResolution` carry it as
|
|
233
|
+
// their own `resolution` from here on (#631); under the default policy
|
|
234
|
+
// the content is untouched.
|
|
235
|
+
const stampedResources = content.resources && withBitmapResolutions(content.resources, resolved.layout.bitmapResolution);
|
|
236
|
+
if (stampedResources && stampedResources !== content.resources)
|
|
237
|
+
content = { ...content, resources: stampedResources };
|
|
228
238
|
// Tracking rungs a loose paragraph may climb: none, then 5‰ steps up to
|
|
229
239
|
// the cap (`headings.balancing.maxTracking`, thousandths of an em), the
|
|
230
240
|
// cap itself always included. Only the smallest rung that gains the line
|
|
@@ -472,7 +482,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
472
482
|
// it asks for is not applied there. Decided per block, by the writing
|
|
473
483
|
// mode of the styled section the reference sits in (a horizontal
|
|
474
484
|
// appendix of a vertical book turns its figures as asked).
|
|
475
|
-
const floatPlan = computeFloatPlan(contentBlocks, resources, resourceTypes, incorporated, (blockIdx) => sectionWritingMode(sectionPlan, resolved, blockIdx) === 'vertical-rl');
|
|
485
|
+
const floatPlan = computeFloatPlan(contentBlocks, resources, resourceTypes, incorporated, (blockIdx) => sectionWritingMode(sectionPlan, resolved, blockIdx) === 'vertical-rl', resolved.layout.floatShrink, resolved.layout.wrap);
|
|
476
486
|
const floatedIds = floatedResourceIds(floatPlan, incorporated, resources, resourceTypes);
|
|
477
487
|
const floatsByFirstBlock = new Map();
|
|
478
488
|
for (const f of floatPlan) {
|
|
@@ -835,7 +845,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
835
845
|
/** Pages whose head or foot a floated box took (see the gallery rule in
|
|
836
846
|
* `placeFloatInColumns`). */
|
|
837
847
|
const calloutBandPages = new Set();
|
|
838
|
-
const enqueueCalloutFloat = (startIdx, plan, style, L, placement, span
|
|
848
|
+
const enqueueCalloutFloat = (startIdx, plan, style, L, placement, span,
|
|
849
|
+
/** Text wraps round the box (#627): a one-column float at its side. */
|
|
850
|
+
wrap) => {
|
|
839
851
|
const key = `${CALLOUT_FLOAT_PREFIX}${startIdx}`;
|
|
840
852
|
const columns = plan.attrs.columns !== undefined ? floatColumnCount(plan.attrs.columns) : style.columns;
|
|
841
853
|
// A keep-with-next replay passes the fence again: enqueue once.
|
|
@@ -851,6 +863,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
851
863
|
span: span === 'page' ? 'page' : 'column',
|
|
852
864
|
...(span === 'column' && columns > 1 ? { columns } : {}),
|
|
853
865
|
callout: { startIdx },
|
|
866
|
+
...(wrap && span === 'column' && columns === 1
|
|
867
|
+
? { widthFraction: wrap.width, align: wrap.side, wrap: wrap.side, ...(wrap.gap ? { wrapGap: wrap.gap } : {}) }
|
|
868
|
+
: {}),
|
|
854
869
|
...(col ? { refPageIndex: page.index, refY: col.bbox.y + (col.bbox.height - col.availableHeight) } : {}),
|
|
855
870
|
});
|
|
856
871
|
}
|
|
@@ -918,7 +933,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
918
933
|
};
|
|
919
934
|
const rotationKey = (rotated) => rotated ? `:${rotated.direction}${rotated.length.toFixed(2)}${rotated.upright ? 'u' : ''}` : '';
|
|
920
935
|
const asideKey = (a) => (a ? `:aside${a.dx.toFixed(1)}x${a.width.toFixed(1)}${a.alignBottom ? 'b' : 't'}${(a.offsetY ?? 0).toFixed(1)}` : '');
|
|
921
|
-
const
|
|
936
|
+
const capKey = (cap) => (cap ? `:cap${cap.maxBodyHeight.toFixed(2)}@${cap.minScale}` : '');
|
|
937
|
+
const layoutFloat = (resourceId, width, slice, rotated, aside, cap) => {
|
|
922
938
|
const resource = resourceById.get(resourceId);
|
|
923
939
|
if (!resource)
|
|
924
940
|
return null;
|
|
@@ -939,6 +955,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
939
955
|
...(rotated ? (rotated.upright ? { upright: { maxLength: rotated.length } } : { rotate: rotated.direction, rotatedLength: rotated.length }) : {}),
|
|
940
956
|
...(aside ? { captionAside: aside } : {}),
|
|
941
957
|
...(flexPx > 0 ? { bodyHeightDelta: flexPx } : {}),
|
|
958
|
+
...(cap && !rotated ? { maxBodyHeight: cap.maxBodyHeight, minBodyScale: cap.minScale } : {}),
|
|
942
959
|
});
|
|
943
960
|
};
|
|
944
961
|
/** The rotation of a pending float on a band whose columns keep `avail`
|
|
@@ -989,8 +1006,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
989
1006
|
/** Height (and caption baseline) of a float at a given width, memoised —
|
|
990
1007
|
* fit checks run for every pending float on every loop iteration. */
|
|
991
1008
|
const floatMeasureMemo = new Map();
|
|
992
|
-
const measureFloat = (resourceId, width, slice, rotated, aside) => {
|
|
993
|
-
const key = `${resourceId}:${width.toFixed(2)}${sliceKey(slice)}${rotationKey(rotated)}${asideKey(aside)}`;
|
|
1009
|
+
const measureFloat = (resourceId, width, slice, rotated, aside, cap) => {
|
|
1010
|
+
const key = `${resourceId}:${width.toFixed(2)}${sliceKey(slice)}${rotationKey(rotated)}${asideKey(aside)}${capKey(cap)}`;
|
|
994
1011
|
const memo = floatMeasureMemo.get(key);
|
|
995
1012
|
if (memo !== undefined)
|
|
996
1013
|
return memo;
|
|
@@ -1024,7 +1041,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1024
1041
|
floatMeasureMemo.set(key, mc);
|
|
1025
1042
|
return mc;
|
|
1026
1043
|
}
|
|
1027
|
-
const laid = layoutFloat(resourceId, width, slice, rotated, aside);
|
|
1044
|
+
const laid = layoutFloat(resourceId, width, slice, rotated, aside, cap);
|
|
1028
1045
|
let m = null;
|
|
1029
1046
|
if (laid && laid.block.rotation) {
|
|
1030
1047
|
// A rotated block takes its band whole: no caption baseline to align,
|
|
@@ -1045,10 +1062,12 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1045
1062
|
if (lastBaseline === undefined || ln.baseline > lastBaseline)
|
|
1046
1063
|
lastBaseline = ln.baseline;
|
|
1047
1064
|
}
|
|
1065
|
+
const picture = rb.kind === 'bitmap' || rb.kind === 'svg' || rb.kind === 'video';
|
|
1048
1066
|
m = {
|
|
1049
1067
|
height: laid.totalHeight,
|
|
1050
1068
|
...(lastBaseline !== undefined ? { lastCaptionBaseline: lastBaseline } : {}),
|
|
1051
1069
|
...(laid.asideHeight !== undefined ? { asideHeight: laid.asideHeight } : {}),
|
|
1070
|
+
...(picture ? { body: { height: rb.bodyRect.height, scale: rb.shrinkScale ?? 1 } } : {}),
|
|
1052
1071
|
};
|
|
1053
1072
|
}
|
|
1054
1073
|
floatMeasureMemo.set(key, m);
|
|
@@ -1064,7 +1083,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1064
1083
|
flushEnd = false, aside,
|
|
1065
1084
|
/** The page is a verso of mirrored margins (a floated box's `'outer'`
|
|
1066
1085
|
* corner icon hangs on the left there). */
|
|
1067
|
-
mirrored = false
|
|
1086
|
+
mirrored = false,
|
|
1087
|
+
/** A picture scaled to its slot (`placement.shrink`). */
|
|
1088
|
+
cap) => {
|
|
1068
1089
|
const strip = comicStripOf(resourceId);
|
|
1069
1090
|
if (strip) {
|
|
1070
1091
|
const block = buildComicStripBlock(strip.idx, strip.source, x, width, contentArea.height - 2 * floatGapPx, cursor.pageIndex);
|
|
@@ -1077,7 +1098,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1077
1098
|
calloutFloatResults.set(result.frame, { result, startIdx, plan: cf.plan });
|
|
1078
1099
|
return { block: result.frame, height: result.totalHeight };
|
|
1079
1100
|
}
|
|
1080
|
-
const laid = layoutFloat(resourceId, width, slice, rotated, aside);
|
|
1101
|
+
const laid = layoutFloat(resourceId, width, slice, rotated, aside, cap);
|
|
1081
1102
|
if (!laid)
|
|
1082
1103
|
return null;
|
|
1083
1104
|
const { block: rb, totalHeight } = laid;
|
|
@@ -1116,6 +1137,52 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1116
1137
|
/** Pages a `:::pagebreak{center}` opened (#424). */
|
|
1117
1138
|
const centredPages = new Set();
|
|
1118
1139
|
const reservedOf = (col) => floatReserved.get(col) ?? { top: 0, bottom: 0 };
|
|
1140
|
+
/** Text wrap (#627): floats set in a band at the head or foot of one
|
|
1141
|
+
* column that text may run beside, per column. The band stays a band
|
|
1142
|
+
* until running text comes to take it back (`openFloatWraps`). */
|
|
1143
|
+
/** The room a wrapped item takes from the lines beside it (#627): its
|
|
1144
|
+
* width and gap, in whole characters of the body under a CJK character
|
|
1145
|
+
* grid (`cjk.grid`), so the lines beside it keep to the grid. */
|
|
1146
|
+
const wrapExclusionWidth = (columnWidth, itemWidth, gap) => {
|
|
1147
|
+
const w = itemWidth + gap;
|
|
1148
|
+
const em = bodyStyle.fontSizePx;
|
|
1149
|
+
return Math.min(columnWidth, resolved.cjk.grid.enabled && em > 0 ? Math.ceil((w - 0.01) / em) * em : w);
|
|
1150
|
+
};
|
|
1151
|
+
/** The `textWrap` warnings of this pass (#627). */
|
|
1152
|
+
const textWrapNotes = [];
|
|
1153
|
+
const floatWraps = new Map();
|
|
1154
|
+
/** Give the bands of the wrapped floats of `col` back to the flow, as
|
|
1155
|
+
* exclusions the text runs beside: a float at the head of a column the
|
|
1156
|
+
* text has not entered yet, one at its foot the text has not reached. */
|
|
1157
|
+
const openFloatWraps = (col) => {
|
|
1158
|
+
const list = floatWraps.get(col);
|
|
1159
|
+
if (!list)
|
|
1160
|
+
return;
|
|
1161
|
+
floatWraps.delete(col);
|
|
1162
|
+
for (const w of list) {
|
|
1163
|
+
const r = { ...reservedOf(col) };
|
|
1164
|
+
if (w.position === 'top') {
|
|
1165
|
+
// A block already stands under it, or another band between: it
|
|
1166
|
+
// keeps its band.
|
|
1167
|
+
if (col.blocks.length > 0 || Math.abs(w.ex.y + w.need - col.bbox.y) > 0.5)
|
|
1168
|
+
continue;
|
|
1169
|
+
col.bbox.y -= w.need;
|
|
1170
|
+
col.bbox.height += w.need;
|
|
1171
|
+
col.availableHeight += w.need;
|
|
1172
|
+
r.top = Math.max(0, r.top - w.need);
|
|
1173
|
+
openColumnWrap(col, w.ex, 'top');
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
if (uncappedBottoms.has(col) || Math.abs(col.bbox.y + col.bbox.height - w.ex.y) > 0.5 || columnCursorY(col) > w.ex.y + 0.01)
|
|
1177
|
+
continue;
|
|
1178
|
+
col.bbox.height += w.need;
|
|
1179
|
+
col.availableHeight += w.need;
|
|
1180
|
+
r.bottom = Math.max(0, r.bottom - w.need);
|
|
1181
|
+
openColumnWrap(col, w.ex, 'bottom');
|
|
1182
|
+
}
|
|
1183
|
+
floatReserved.set(col, r);
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1119
1186
|
const floatHeadOf = (col) => reservedOf(col).top;
|
|
1120
1187
|
/** `BandCap.headPx` for a cap proposed on `cols`: how far the float bands
|
|
1121
1188
|
* every column holds at its head put the band's top below its bare one. */
|
|
@@ -1369,6 +1436,50 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1369
1436
|
built.block.contentIndex = anchorIdx;
|
|
1370
1437
|
(page.floats ??= []).push(built.block);
|
|
1371
1438
|
};
|
|
1439
|
+
/**
|
|
1440
|
+
* A picture float scaled to `room` px of a slot instead of moving on
|
|
1441
|
+
* (`placement.shrink`, #626): the tallest body that keeps its band
|
|
1442
|
+
* within the room. A top band rounds up to the grid, so the float may
|
|
1443
|
+
* stand as tall as the grid multiple within the room, less its gap; a
|
|
1444
|
+
* bottom band (its caption's last baseline on the grid) and a side
|
|
1445
|
+
* stack give the room back px for px. The caption's height may change
|
|
1446
|
+
* with the picture's width (`captionMeasure: 'body'`) and a safe area
|
|
1447
|
+
* crops before it scales, so the cap is refined over a few rounds. The
|
|
1448
|
+
* scale never goes below the float's `minScale`; `fits` is false when
|
|
1449
|
+
* even that does not hold the room. Null for a float that does not
|
|
1450
|
+
* scale (no `shrink`, not an upright picture, the rest of a table) or
|
|
1451
|
+
* already fits.
|
|
1452
|
+
*/
|
|
1453
|
+
const shrinkToRoom = (f, width, aside, full, room, topBand, bandOf) => {
|
|
1454
|
+
if (!f.shrink || !full.body || f.rotate || f.startRow)
|
|
1455
|
+
return null;
|
|
1456
|
+
const minScale = f.minScale ?? DEFAULT_FLOAT_MIN_SCALE;
|
|
1457
|
+
const excessOf = (m, band) => topBand
|
|
1458
|
+
? m.height + floatGapBelow(m, floatGapPx) - Math.floor((room + 0.01) / baselineGrid) * baselineGrid
|
|
1459
|
+
: band.need - room;
|
|
1460
|
+
let measure = full;
|
|
1461
|
+
let band = bandOf(full);
|
|
1462
|
+
let cap;
|
|
1463
|
+
for (let round = 0; round < 4; round++) {
|
|
1464
|
+
const excess = excessOf(measure, band);
|
|
1465
|
+
if (excess <= 0.01)
|
|
1466
|
+
break;
|
|
1467
|
+
const next = { maxBodyHeight: Math.max(0, measure.body.height - excess), minScale };
|
|
1468
|
+
if (cap && next.maxBodyHeight >= cap.maxBodyHeight - 0.01)
|
|
1469
|
+
break;
|
|
1470
|
+
cap = next;
|
|
1471
|
+
const m = measureFloat(f.resourceId, width, undefined, undefined, aside, cap);
|
|
1472
|
+
if (!m?.body)
|
|
1473
|
+
return null;
|
|
1474
|
+
measure = m;
|
|
1475
|
+
band = bandOf(m);
|
|
1476
|
+
if (m.body.scale <= minScale + 1e-6)
|
|
1477
|
+
break;
|
|
1478
|
+
}
|
|
1479
|
+
if (!cap)
|
|
1480
|
+
return null;
|
|
1481
|
+
return { measure, ...band, cap, fits: excessOf(measure, band) <= 0.01 };
|
|
1482
|
+
};
|
|
1372
1483
|
const placeFloatInColumns = (page, f, targetCols, position, pageSpan, mode, anchorToCap = false, side = false, refY) => {
|
|
1373
1484
|
const first = targetCols[0];
|
|
1374
1485
|
const probe = probeFloatBand(page, f, targetCols, position, pageSpan, anchorToCap, side, refY);
|
|
@@ -1377,6 +1488,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1377
1488
|
const { width, xLeft, rotated, aside, sideCol } = probe;
|
|
1378
1489
|
let { slice, measure, need, y } = probe;
|
|
1379
1490
|
let rest;
|
|
1491
|
+
/** A picture scaled to this slot (`placement.shrink`, #626). */
|
|
1492
|
+
let cap;
|
|
1493
|
+
const bandOf = (m) => measureFloatBand(position, m, targetCols, page.contentArea, baselineGrid, floatGapPx, (c) => (anchorToCap ? c.bbox.y + c.bbox.height : trueBottom(c, uncappedBottoms)));
|
|
1380
1494
|
/** The float was cut to this slot (a table slice). */
|
|
1381
1495
|
let cut = false;
|
|
1382
1496
|
/** A rotated block wider than the band (its upright height). */
|
|
@@ -1386,24 +1500,36 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1386
1500
|
// when the rest of the column takes it; otherwise it waits for the
|
|
1387
1501
|
// side column of the next page — where it is set anyway, overflowing,
|
|
1388
1502
|
// when even an empty column cannot hold it (a dominating figure).
|
|
1503
|
+
let cap;
|
|
1389
1504
|
if (need > first.availableHeight + 0.01) {
|
|
1390
1505
|
// A column that already holds something — a float, a box, or a
|
|
1391
1506
|
// heading design standing in it — is no empty column to overflow.
|
|
1392
1507
|
const holds = sideUsedBottom(first) > first.bbox.y + 0.5 || (sideObstacles.get(first)?.length ?? 0) > 0;
|
|
1393
|
-
if (mode === 'strict' || holds)
|
|
1394
|
-
return 'defer';
|
|
1395
1508
|
// An empty column shortened by a band above it (a page-span box,
|
|
1396
1509
|
// a top float) is no measure of the float: when a full column
|
|
1397
1510
|
// would hold it, it waits for one instead of overflowing this one.
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
//
|
|
1401
|
-
//
|
|
1402
|
-
|
|
1403
|
-
if
|
|
1404
|
-
|
|
1511
|
+
const waitsForFull = shortSideColumn(page, first) && measure.height <= page.contentArea.height + 0.01;
|
|
1512
|
+
const lastResort = mode === 'fresh' && !holds && !waitsForFull;
|
|
1513
|
+
// A picture scaled to the room the column keeps (#626): any slot
|
|
1514
|
+
// with `'slot'`, when it holds it at its smallest scale or more;
|
|
1515
|
+
// the empty column it would overflow with `'page'` too, at its
|
|
1516
|
+
// smallest scale if need be.
|
|
1517
|
+
if (f.shrink === 'slot' || (f.shrink && lastResort)) {
|
|
1518
|
+
const s = shrinkToRoom(f, width, undefined, measure, first.availableHeight, false, (m) => measureSideStack(m, first, refY, page.contentArea, baselineGrid, floatGapPx, sideObstacles.get(first)));
|
|
1519
|
+
if (s && (s.fits || lastResort))
|
|
1520
|
+
({ measure, need, y, cap } = s);
|
|
1521
|
+
}
|
|
1522
|
+
if (need > first.availableHeight + 0.01) {
|
|
1523
|
+
if (!lastResort)
|
|
1524
|
+
return 'defer';
|
|
1525
|
+
// Overflowing a column a side caption has shortened: the next pass
|
|
1526
|
+
// sets that caption under its figure and gives the column back.
|
|
1527
|
+
const cutBy = asideCutBy.get(first);
|
|
1528
|
+
if (cutBy !== undefined)
|
|
1529
|
+
captionUnderProposals.add(cutBy);
|
|
1530
|
+
}
|
|
1405
1531
|
}
|
|
1406
|
-
const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated);
|
|
1532
|
+
const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated, false, undefined, false, cap);
|
|
1407
1533
|
if (!built)
|
|
1408
1534
|
return 'skip';
|
|
1409
1535
|
first.availableHeight = Math.max(0, first.availableHeight - need);
|
|
@@ -1431,9 +1557,26 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1431
1557
|
const gallery = need <= minAvail + 0.01
|
|
1432
1558
|
&& f.refPageIndex !== undefined && f.refPageIndex < page.index
|
|
1433
1559
|
&& calloutBandPages.has(page) && !calloutFloatOf(f.resourceId);
|
|
1434
|
-
if (
|
|
1435
|
-
|
|
1436
|
-
|
|
1560
|
+
if (gallery) {
|
|
1561
|
+
galleryFill = true;
|
|
1562
|
+
}
|
|
1563
|
+
else {
|
|
1564
|
+
// A picture that scales to any slot (#626) takes the room left
|
|
1565
|
+
// beside the other band, the text room kept, when it holds it
|
|
1566
|
+
// at its smallest scale or more.
|
|
1567
|
+
const s = f.shrink === 'slot' ? shrinkToRoom(f, width, aside, measure, minAvail - minTextPx, position === 'top', bandOf) : null;
|
|
1568
|
+
if (!s?.fits)
|
|
1569
|
+
return 'defer';
|
|
1570
|
+
({ measure, need, y, cap } = s);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
// Dominating the band: a picture that scales (`'page'` or `'slot'`,
|
|
1574
|
+
// #626) takes the band's room, at its smallest scale if need be —
|
|
1575
|
+
// then it runs past the band, as any dominating float does.
|
|
1576
|
+
if (need > minAvail + 0.01 && f.shrink && !rotated) {
|
|
1577
|
+
const s = shrinkToRoom(f, width, aside, measure, minAvail, position === 'top', bandOf);
|
|
1578
|
+
if (s)
|
|
1579
|
+
({ measure, need, y, cap } = s);
|
|
1437
1580
|
}
|
|
1438
1581
|
if (need > minAvail + 0.01 || tooWide(measure)) {
|
|
1439
1582
|
// Dominating the band: a table is cut to it (a rotated table, to
|
|
@@ -1459,6 +1602,19 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1459
1602
|
// too wide.
|
|
1460
1603
|
if (f.rotate || tooWide(measure))
|
|
1461
1604
|
return 'defer';
|
|
1605
|
+
// A picture that scales to any slot (#626) takes this one when it
|
|
1606
|
+
// holds it at its smallest scale or more (the text room kept next
|
|
1607
|
+
// to another band, as `fitsStrict` keeps it); the loop below checks
|
|
1608
|
+
// the band as for any float.
|
|
1609
|
+
if (f.shrink === 'slot' && measure.body && !rotated) {
|
|
1610
|
+
const room = Math.min(...targetCols.map((c) => c.availableHeight - (columnHasFloatBand(page, c) ? minTextPx : 0)));
|
|
1611
|
+
if (need > room + 0.01) {
|
|
1612
|
+
const s = shrinkToRoom(f, width, aside, measure, room, position === 'top', bandOf);
|
|
1613
|
+
if (!s?.fits)
|
|
1614
|
+
return 'defer';
|
|
1615
|
+
({ measure, need, y, cap } = s);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1462
1618
|
for (const c of targetCols) {
|
|
1463
1619
|
if (position === 'bottom' && uncappedBottoms.has(c) && !anchorToCap) {
|
|
1464
1620
|
// Trailing cap: the band must lie entirely below the level cut.
|
|
@@ -1516,7 +1672,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1516
1672
|
// An upright figure of a vertical page stands at the head of its tier
|
|
1517
1673
|
// (`placement.align`: `left` the top, `center`, `right` the foot).
|
|
1518
1674
|
const flush = rotated?.upright ? (f.align === 'center' ? 0.5 : f.align === 'right' ? 1 : 0) : rotated ? rotatedFlushEnd(page) : false;
|
|
1519
|
-
const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated, flush, aside, mirroredOf(page));
|
|
1675
|
+
const built = buildFloatBlock(f.resourceId, xLeft, width, slice, rotated, flush, aside, mirroredOf(page), cap);
|
|
1520
1676
|
if (!built)
|
|
1521
1677
|
return 'skip';
|
|
1522
1678
|
// The caption beside the figure takes its band of the side column: a
|
|
@@ -1571,6 +1727,40 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
1571
1727
|
if (galleryFill)
|
|
1572
1728
|
for (const col of targetCols)
|
|
1573
1729
|
col.availableHeight = 0;
|
|
1730
|
+
// Text wrap (#627): a one-column float at the head or foot of its
|
|
1731
|
+
// column, which running text may take back to run beside it — unless
|
|
1732
|
+
// the text left beside it would be too narrow, or the float too short
|
|
1733
|
+
// for lines beside it.
|
|
1734
|
+
if (f.wrap && !pageSpan && !side && targetCols.length === 1 && !rotated && !aside && !cut && !galleryFill
|
|
1735
|
+
&& !(position === 'bottom' && uncappedBottoms.has(first))) {
|
|
1736
|
+
const gap = wrapGapPx(f.wrapGap, dpi, bodyStyle.fontSizePx, floatGapPx);
|
|
1737
|
+
const settings = resolved.layout.wrap;
|
|
1738
|
+
const reason = first.bbox.width - width - gap + 0.01 < minTextWidthPx(settings.minTextWidth, first.bbox.width, dpi, bodyStyle.fontSizePx)
|
|
1739
|
+
? 'tooNarrow'
|
|
1740
|
+
: Math.round(need / bodyStyle.lineHeightPx) < settings.minLinesBeside ? 'fewLines' : undefined;
|
|
1741
|
+
if (reason) {
|
|
1742
|
+
textWrapNotes.push({ kind: 'textWrap', reason, resourceId: f.resourceId, pageIndex: page.index });
|
|
1743
|
+
}
|
|
1744
|
+
else {
|
|
1745
|
+
const exWidth = wrapExclusionWidth(first.bbox.width, width, gap);
|
|
1746
|
+
const exY = position === 'top' ? first.bbox.y - need : first.bbox.y + first.bbox.height;
|
|
1747
|
+
const list = floatWraps.get(first) ?? [];
|
|
1748
|
+
list.push({
|
|
1749
|
+
position,
|
|
1750
|
+
need,
|
|
1751
|
+
ex: {
|
|
1752
|
+
x: f.wrap === 'left' ? first.bbox.x : first.bbox.x + first.bbox.width - exWidth,
|
|
1753
|
+
y: exY,
|
|
1754
|
+
width: exWidth,
|
|
1755
|
+
height: need,
|
|
1756
|
+
side: f.wrap,
|
|
1757
|
+
gap: exWidth - width,
|
|
1758
|
+
ownerId: built.block.id,
|
|
1759
|
+
},
|
|
1760
|
+
});
|
|
1761
|
+
floatWraps.set(first, list);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1574
1764
|
commitFloatBlock(page, first, built, xLeft, y, width, f.firstBlockIdx);
|
|
1575
1765
|
floatsPlaced++;
|
|
1576
1766
|
return rest ? { rest } : 'placed';
|
|
@@ -2313,7 +2503,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
2313
2503
|
blk.lines = resetLinePositions(raggedLooseLines(m.measured.lines, noteStyle.textAlign), noteStyle.lineHeightPx).map((line) => ({
|
|
2314
2504
|
...line,
|
|
2315
2505
|
bbox: createBoundingBox(line.bbox.x + x, line.bbox.y + y, line.bbox.width, line.bbox.height),
|
|
2316
|
-
...(line.measure ? { measure: { x: line.measure.x + x
|
|
2506
|
+
...(line.measure ? { measure: { ...line.measure, x: line.measure.x + x } } : {}),
|
|
2317
2507
|
baseline: line.baseline + y,
|
|
2318
2508
|
}));
|
|
2319
2509
|
const h = blk.lines.length * noteStyle.lineHeightPx;
|
|
@@ -2775,7 +2965,8 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
2775
2965
|
return;
|
|
2776
2966
|
const band = currentBand(page, cursor);
|
|
2777
2967
|
const cols = bandColumns(page, band).filter((c) => c.bbox.height > 0.5);
|
|
2778
|
-
|
|
2968
|
+
// A band where text wraps round something (#627) is not levelled.
|
|
2969
|
+
if (cols.length < 2 || cols.some((c) => c.forcedBreak || (c.exclusions?.length ?? 0) > 0))
|
|
2779
2970
|
return;
|
|
2780
2971
|
if (activeCap && activeCap.spanIndex === boundaryIndex
|
|
2781
2972
|
&& activeCap.pageIndex === page.index && activeCap.band === band) {
|
|
@@ -2845,7 +3036,15 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
2845
3036
|
* page the boundary opens: they sit the drain out and stay pending (with
|
|
2846
3037
|
* no reference stamp, which the citing line sets), so they land after
|
|
2847
3038
|
* the line that cites them, in the new segment (EF-69). */
|
|
3039
|
+
/** Text wrap (#627): an inline figure that wraps, placed as a band, whose
|
|
3040
|
+
* band the next block takes back to run beside it when it can (see
|
|
3041
|
+
* `pipeline/textWrap.ts`). */
|
|
3042
|
+
let pendingWrap = null;
|
|
2848
3043
|
const closeFlowSegment = (boundaryIndex) => {
|
|
3044
|
+
// What text wrapped round in the column the segment ends in keeps its
|
|
3045
|
+
// room (#627).
|
|
3046
|
+
pendingWrap = null;
|
|
3047
|
+
settleColumnWraps(currentColumn(doc, cursor));
|
|
2849
3048
|
settleAwaitingSideBoxes(true);
|
|
2850
3049
|
releaseStaleCitationGates();
|
|
2851
3050
|
const ownFloats = [];
|
|
@@ -4102,6 +4301,83 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
4102
4301
|
* boxes) still fall back to this inline placement for v1 — the frame's
|
|
4103
4302
|
* `callout.placement` records the request.
|
|
4104
4303
|
*/
|
|
4304
|
+
/**
|
|
4305
|
+
* A box set `here` that text wraps round (#627): laid out whole at its
|
|
4306
|
+
* share of the column, at its side, level with the next line's top, then
|
|
4307
|
+
* set as a band the next running text takes back to run beside it (see
|
|
4308
|
+
* `pendingWrap`). It never splits: too tall for the room left, it moves
|
|
4309
|
+
* on to the next column (a `textWrap` warning says so). Frame and
|
|
4310
|
+
* children go to the page's floats, where a floated box goes. Returns
|
|
4311
|
+
* false, the box set in the flow as any, when the text beside it would
|
|
4312
|
+
* be too narrow or the box too short for lines beside it.
|
|
4313
|
+
*/
|
|
4314
|
+
const placeCalloutWrapped = (startIdx, plan, style, L, frameId, wrap) => {
|
|
4315
|
+
const settings = resolved.layout.wrap;
|
|
4316
|
+
const gap = wrapGapPx(wrap.gap, dpi, bodyStyle.fontSizePx, floatGapPx);
|
|
4317
|
+
const note = (reason) => {
|
|
4318
|
+
const start = contentBlocks[startIdx];
|
|
4319
|
+
textWrapNotes.push({ kind: 'textWrap', reason, box: style.id, sourceStart: start.sourceStart + bodyOffset, sourceEnd: start.sourceEnd + bodyOffset, pageIndex: cursor.pageIndex });
|
|
4320
|
+
};
|
|
4321
|
+
let col = currentColumn(doc, cursor);
|
|
4322
|
+
if (col.bbox.width * (1 - wrap.width) - gap + 0.01 < minTextWidthPx(settings.minTextWidth, col.bbox.width, dpi, bodyStyle.fontSizePx)) {
|
|
4323
|
+
note('tooNarrow');
|
|
4324
|
+
return false;
|
|
4325
|
+
}
|
|
4326
|
+
for (let attempt = 0;; attempt++) {
|
|
4327
|
+
col = currentColumn(doc, cursor);
|
|
4328
|
+
const page = doc.pages[cursor.pageIndex];
|
|
4329
|
+
const width = col.bbox.width * wrap.width;
|
|
4330
|
+
const result = L.layoutRange(CUT_START, L.end, width, frameId, false, mirroredOf(page));
|
|
4331
|
+
if (attempt === 0 && Math.ceil((result.totalHeight + gap - 0.01) / bodyStyle.lineHeightPx) < settings.minLinesBeside) {
|
|
4332
|
+
note('fewLines');
|
|
4333
|
+
return false;
|
|
4334
|
+
}
|
|
4335
|
+
const spacing = col.blocks.length === 0 ? 0 : pendingSpacing;
|
|
4336
|
+
if (result.totalHeight > col.availableHeight - spacing + 0.01 && col.blocks.length > 0 && attempt < 4) {
|
|
4337
|
+
if (attempt === 0)
|
|
4338
|
+
note('moved');
|
|
4339
|
+
pendingSpacing = 0;
|
|
4340
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
4341
|
+
continue;
|
|
4342
|
+
}
|
|
4343
|
+
const top = columnCursorY(col) + spacing;
|
|
4344
|
+
const x = wrap.side === 'left' ? col.bbox.x : col.bbox.x + col.bbox.width - width;
|
|
4345
|
+
const frame = result.frame;
|
|
4346
|
+
stampCalloutSource(frame, startIdx, plan);
|
|
4347
|
+
frame.pageIndex = page.index;
|
|
4348
|
+
frame.columnIndex = col.index;
|
|
4349
|
+
offsetCalloutToAbsolute(result, x, top);
|
|
4350
|
+
const floats = (page.floats ??= []);
|
|
4351
|
+
doc.blocks.push(frame);
|
|
4352
|
+
floats.push(frame);
|
|
4353
|
+
for (const child of result.children) {
|
|
4354
|
+
child.pageIndex = page.index;
|
|
4355
|
+
child.columnIndex = col.index;
|
|
4356
|
+
doc.blocks.push(child);
|
|
4357
|
+
floats.push(child);
|
|
4358
|
+
}
|
|
4359
|
+
const bottomRel = Math.ceil((top - col.bbox.y + result.totalHeight + gap - 0.01) / baselineGrid) * baselineGrid;
|
|
4360
|
+
col.availableHeight = Math.max(0, Math.min(col.availableHeight, col.bbox.height - bottomRel));
|
|
4361
|
+
pendingSpacing = 0;
|
|
4362
|
+
inlineGapOwed = null;
|
|
4363
|
+
const exWidth = wrapExclusionWidth(col.bbox.width, width, gap);
|
|
4364
|
+
pendingWrap = {
|
|
4365
|
+
col,
|
|
4366
|
+
blocks: col.blocks.length,
|
|
4367
|
+
cursorY: columnCursorY(col),
|
|
4368
|
+
ex: {
|
|
4369
|
+
x: wrap.side === 'left' ? col.bbox.x : col.bbox.x + col.bbox.width - exWidth,
|
|
4370
|
+
y: top,
|
|
4371
|
+
width: exWidth,
|
|
4372
|
+
height: col.bbox.y + bottomRel - top,
|
|
4373
|
+
side: wrap.side,
|
|
4374
|
+
gap: exWidth - width,
|
|
4375
|
+
ownerId: frame.id,
|
|
4376
|
+
},
|
|
4377
|
+
};
|
|
4378
|
+
return true;
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
4105
4381
|
const placeCalloutInline = (startIdx, plan) => {
|
|
4106
4382
|
const style = pickCalloutStyle(calloutStyles, plan.attrs.type);
|
|
4107
4383
|
const firstFrameId = `block-${blockIdCounter++}`;
|
|
@@ -4130,7 +4406,17 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
4130
4406
|
const inFlow = tallerThanColumn(L.layoutRange(CUT_START, L.end, width, 'float-probe', false))
|
|
4131
4407
|
&& splitCalloutFragment(L, CUT_START, width, contentArea.height, 'float-probe', false, style.splitMinLines) !== null;
|
|
4132
4408
|
if (!inFlow) {
|
|
4133
|
-
|
|
4409
|
+
const wrap = pageIsVertical(page) ? undefined : resolveCalloutWrap(plan.attrs, resolved.layout.wrap);
|
|
4410
|
+
enqueueCalloutFloat(startIdx, plan, style, L, placement, span, wrap);
|
|
4411
|
+
calloutsOutOfFlow.add(startIdx);
|
|
4412
|
+
return undefined;
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
// A box text wraps round (#627): at its side of the column, the text
|
|
4416
|
+
// after it beside it.
|
|
4417
|
+
if (placement === 'here' && span === 'column' && !pageIsVertical(doc.pages[cursor.pageIndex])) {
|
|
4418
|
+
const wrap = resolveCalloutWrap(plan.attrs, resolved.layout.wrap);
|
|
4419
|
+
if (wrap && placeCalloutWrapped(startIdx, plan, style, L, firstFrameId, wrap)) {
|
|
4134
4420
|
calloutsOutOfFlow.add(startIdx);
|
|
4135
4421
|
return undefined;
|
|
4136
4422
|
}
|
|
@@ -4469,6 +4755,29 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
4469
4755
|
releaseStaleCitationGates(blockIdx);
|
|
4470
4756
|
tryPlacePendingFloatsOnCurrentPage(spanBoxAt(blockIdx), blockIdx);
|
|
4471
4757
|
enqueueFloatsFor(blockIdx);
|
|
4758
|
+
// Text wrap (#627): running text after a wrapped figure takes the band
|
|
4759
|
+
// the figure was set in back, and runs beside it; anything else that
|
|
4760
|
+
// takes room from the flow goes under what text wraps round first.
|
|
4761
|
+
// Markers and the `::resource` line of a floated resource leave it.
|
|
4762
|
+
if (!transparentToWrap(rawBlock) && !(rawBlock.type === 'resourceBlock' && rawBlock.resourceId !== undefined && floatedIds.has(rawBlock.resourceId))) {
|
|
4763
|
+
const wrapCol = currentColumn(doc, cursor);
|
|
4764
|
+
const beside = runsBesideWrap(rawBlock);
|
|
4765
|
+
if (pendingWrap) {
|
|
4766
|
+
const { col, blocks, cursorY, ex } = pendingWrap;
|
|
4767
|
+
pendingWrap = null;
|
|
4768
|
+
if (beside && col === wrapCol && col.blocks.length === blocks && Math.abs(columnCursorY(col) - cursorY) < 0.01) {
|
|
4769
|
+
// The cursor goes back to the figure's top; the text starts there.
|
|
4770
|
+
col.availableHeight += columnCursorY(col) - ex.y;
|
|
4771
|
+
pendingSpacing = 0;
|
|
4772
|
+
inlineGapOwed = null;
|
|
4773
|
+
openColumnWrap(col, ex, 'top');
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
if (!beside && settleColumnWraps(wrapCol)) {
|
|
4777
|
+
pendingSpacing = 0;
|
|
4778
|
+
inlineGapOwed = null;
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4472
4781
|
// --- Directives ----------------------------------------------------
|
|
4473
4782
|
// (A comic page, `:::page`, and a strip, `:::strip`, are placed below,
|
|
4474
4783
|
// once a closed part or paper run has taken its page break.)
|
|
@@ -4962,12 +5271,55 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
4962
5271
|
segments: [],
|
|
4963
5272
|
isLastLine: true,
|
|
4964
5273
|
}];
|
|
5274
|
+
// Text wrap (#627): a picture the text after it may run beside, at
|
|
5275
|
+
// its side of the column — unless the text left beside it would be
|
|
5276
|
+
// too narrow, or the picture too short to set lines beside.
|
|
5277
|
+
const wrapSettings = blockMeasureCtx.resolved.layout.wrap;
|
|
5278
|
+
const wrapSpec = kind.resource && !pageIsVertical(blockPage)
|
|
5279
|
+
? resolveResourceWrap(kind.resource, kind.resourceType, wrapSettings)
|
|
5280
|
+
: undefined;
|
|
5281
|
+
// Vertical text keeps its figures' bands whole (horizontal wrap only).
|
|
5282
|
+
if (kind.resource && pageIsVertical(blockPage) && resolveResourceWrap(kind.resource, kind.resourceType, wrapSettings)) {
|
|
5283
|
+
textWrapNotes.push({
|
|
5284
|
+
kind: 'textWrap', reason: 'verticalText', resourceId: kind.resource.id,
|
|
5285
|
+
sourceStart: rawBlock.sourceStart + bodyOffset, sourceEnd: rawBlock.sourceEnd + bodyOffset, pageIndex: cursor.pageIndex,
|
|
5286
|
+
});
|
|
5287
|
+
}
|
|
5288
|
+
let wrapGap = 0;
|
|
5289
|
+
let wraps = false;
|
|
5290
|
+
if (wrapSpec) {
|
|
5291
|
+
wrapGap = wrapGapPx(wrapSpec.gap, dpi, bodyStyle.fontSizePx, floatGapPx);
|
|
5292
|
+
const textWidth = col.bbox.width * (1 - wrapSpec.width) - wrapGap;
|
|
5293
|
+
const linesBeside = Math.ceil((groupHeight + wrapGap - 0.01) / bodyStyle.lineHeightPx);
|
|
5294
|
+
const reason = textWidth + 0.01 < minTextWidthPx(wrapSettings.minTextWidth, col.bbox.width, dpi, bodyStyle.fontSizePx)
|
|
5295
|
+
? 'tooNarrow'
|
|
5296
|
+
: linesBeside < wrapSettings.minLinesBeside ? 'fewLines' : undefined;
|
|
5297
|
+
if (reason) {
|
|
5298
|
+
textWrapNotes.push({
|
|
5299
|
+
kind: 'textWrap', reason, resourceId: kind.resource.id,
|
|
5300
|
+
sourceStart: rawBlock.sourceStart + bodyOffset, sourceEnd: rawBlock.sourceEnd + bodyOffset, pageIndex: cursor.pageIndex,
|
|
5301
|
+
});
|
|
5302
|
+
}
|
|
5303
|
+
else {
|
|
5304
|
+
wraps = true;
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
4965
5307
|
// An inline resource keeps the float gap (a line) above it, as a
|
|
4966
|
-
// float would, unless the block before asked for more.
|
|
4967
|
-
|
|
5308
|
+
// float would, unless the block before asked for more. A wrapped one
|
|
5309
|
+
// stands level with the next line's top instead.
|
|
5310
|
+
const spacingBefore = wraps ? pendingSpacing : Math.max(pendingSpacing, floatGapPx);
|
|
5311
|
+
const columnBefore = col;
|
|
4968
5312
|
enterBand(blockIdx, 0);
|
|
4969
5313
|
placeAtomicBlock(blk, groupHeight, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx, onAtomicNewPage(groupHeight));
|
|
4970
5314
|
enterBand(blockIdx, 0);
|
|
5315
|
+
// Too tall for the room left in its column, a wrapped picture moved
|
|
5316
|
+
// on with its anchor: say so.
|
|
5317
|
+
if (wraps && currentColumn(doc, cursor) !== columnBefore && columnBefore.blocks.length > 0) {
|
|
5318
|
+
textWrapNotes.push({
|
|
5319
|
+
kind: 'textWrap', reason: 'moved', resourceId: kind.resource.id,
|
|
5320
|
+
sourceStart: rawBlock.sourceStart + bodyOffset, sourceEnd: rawBlock.sourceEnd + bodyOffset, pageIndex: cursor.pageIndex,
|
|
5321
|
+
});
|
|
5322
|
+
}
|
|
4971
5323
|
// `placeBlockInColumn` (inside placeAtomicBlock) shifts `blk.lines`; the
|
|
4972
5324
|
// resource's own caption/table lines live on `resourceBlock` and must be
|
|
4973
5325
|
// offset to absolute page coordinates here using the placed bbox origin.
|
|
@@ -4978,6 +5330,34 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
4978
5330
|
addBalanceExtra(currentColumn(doc, cursor), grown);
|
|
4979
5331
|
}
|
|
4980
5332
|
doc.blocks.push(blk);
|
|
5333
|
+
// A wrapped picture (#627): the flow goes on under it and its gap, on
|
|
5334
|
+
// a grid line, unless the next block takes the band back to run
|
|
5335
|
+
// beside it (see `pendingWrap`).
|
|
5336
|
+
if (wraps && wrapSpec) {
|
|
5337
|
+
const rCol = currentColumn(doc, cursor);
|
|
5338
|
+
const top = blk.bbox.y;
|
|
5339
|
+
const bottomRel = Math.ceil((top - rCol.bbox.y + groupHeight + wrapGap - 0.01) / baselineGrid) * baselineGrid;
|
|
5340
|
+
rCol.availableHeight = Math.max(0, Math.min(rCol.availableHeight, rCol.bbox.height - bottomRel));
|
|
5341
|
+
pendingSpacing = 0;
|
|
5342
|
+
inlineGapOwed = null;
|
|
5343
|
+
const exWidth = wrapExclusionWidth(rCol.bbox.width, rCol.bbox.width * wrapSpec.width, wrapGap);
|
|
5344
|
+
pendingWrap = {
|
|
5345
|
+
col: rCol,
|
|
5346
|
+
blocks: rCol.blocks.length,
|
|
5347
|
+
cursorY: columnCursorY(rCol),
|
|
5348
|
+
ex: {
|
|
5349
|
+
x: wrapSpec.side === 'left' ? rCol.bbox.x : rCol.bbox.x + rCol.bbox.width - exWidth,
|
|
5350
|
+
y: top,
|
|
5351
|
+
width: exWidth,
|
|
5352
|
+
height: rCol.bbox.y + bottomRel - top,
|
|
5353
|
+
side: wrapSpec.side,
|
|
5354
|
+
gap: exWidth - rCol.bbox.width * wrapSpec.width,
|
|
5355
|
+
ownerId: blk.id,
|
|
5356
|
+
},
|
|
5357
|
+
};
|
|
5358
|
+
flushPendingNumberingAtBoundary();
|
|
5359
|
+
continue;
|
|
5360
|
+
}
|
|
4981
5361
|
// Snap the flow position after the resource to the baseline grid (the
|
|
4982
5362
|
// group height is arbitrary), baking in at least marginBottom — same
|
|
4983
5363
|
// convention as snapped headings — so the following text lands back on
|
|
@@ -5045,7 +5425,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
5045
5425
|
// `bulletXOffsetInColumn` is `indentPx` for unordered/task, and
|
|
5046
5426
|
// `indentPx + (maxNumberWidth - thisNumberWidth)` for ordered — giving
|
|
5047
5427
|
// the right-aligned separator.
|
|
5048
|
-
|
|
5428
|
+
// Beside a wrapped picture on the left (#627) the marker stands past
|
|
5429
|
+
// it, with its text.
|
|
5430
|
+
blk.bulletOffsetX = blk.bbox.x + bulletXOffsetInColumn + lineInsetsAt(wrapInsets, 0).start;
|
|
5049
5431
|
if (listBullet.separatorText !== undefined) {
|
|
5050
5432
|
blk.separatorText = listBullet.separatorText;
|
|
5051
5433
|
blk.separatorFontString = listBullet.separatorFontString;
|
|
@@ -5143,6 +5525,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
5143
5525
|
* default opener paints across the page. */
|
|
5144
5526
|
let brokenForWidth = measureWidth;
|
|
5145
5527
|
let laterWidths = [];
|
|
5528
|
+
/** Text wrap (#627): the lines of the block set short beside what text
|
|
5529
|
+
* wraps round, as its lines are now set (paragraph line numbers). */
|
|
5530
|
+
let wrapInsets = [];
|
|
5531
|
+
const wrapsBeside = canSplit && runsBesideWrap(rawBlock);
|
|
5146
5532
|
const rebreaksForColumn = !tryLoose && rawBlock.toc === undefined && rawBlock.index === undefined
|
|
5147
5533
|
&& (canSplit || (vdtType === 'heading' && !opensDefaultOpener(rawBlock)));
|
|
5148
5534
|
/** Measure the block again for the columns it is set in (see
|
|
@@ -5150,6 +5536,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
5150
5536
|
const measureForColumns = (extra) => measureContentBlock(rawBlock, blockIdx, brokenForWidth, blockMeasureCtx, {
|
|
5151
5537
|
styleOverride,
|
|
5152
5538
|
...(laterWidths.length > 0 ? { restColumnWidths: laterWidths } : {}),
|
|
5539
|
+
...(wrapInsets.length > 0 ? { lineInsets: wrapInsets } : {}),
|
|
5153
5540
|
...extra,
|
|
5154
5541
|
});
|
|
5155
5542
|
// "Keep with next" for colon-introduced lists: a paragraph ending in `:`
|
|
@@ -5169,6 +5556,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
5169
5556
|
while (remainingLines.length > 0) {
|
|
5170
5557
|
enterBand(blockIdx, partIndex);
|
|
5171
5558
|
const curCol = currentColumn(doc, cursor);
|
|
5559
|
+
// A wrapped float at the head or foot of this column (#627): running
|
|
5560
|
+
// text takes its band back to run beside it.
|
|
5561
|
+
if (wrapsBeside)
|
|
5562
|
+
openFloatWraps(curCol);
|
|
5172
5563
|
// The rest of the block goes into a column of another width than the
|
|
5173
5564
|
// one it was broken for (EF-157): break it again for this one. Up to
|
|
5174
5565
|
// postext 1.4 it kept the lines of the column it started in, and a
|
|
@@ -5183,6 +5574,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
5183
5574
|
capInfo = again.dropCap;
|
|
5184
5575
|
brokenForWidth = curCol.bbox.width;
|
|
5185
5576
|
laterWidths = [];
|
|
5577
|
+
wrapInsets = [];
|
|
5186
5578
|
letterSpacingPx = again.letterSpacingPx;
|
|
5187
5579
|
remainingLines = [...raggedLooseLines(again.measured.lines, style.textAlign)];
|
|
5188
5580
|
paragraphLines = remainingLines;
|
|
@@ -5201,8 +5593,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
5201
5593
|
const keepBreaks = paragraphBreaks && paragraphBreaks.at.length >= placed
|
|
5202
5594
|
? { path: paragraphBreaks.path, at: paragraphBreaks.at.slice(0, placed) }
|
|
5203
5595
|
: undefined;
|
|
5596
|
+
const keptInsets = insetsBefore(wrapInsets, placed);
|
|
5204
5597
|
const again = measureContentBlock(rawBlock, blockIdx, brokenForWidth, blockMeasureCtx, {
|
|
5205
5598
|
styleOverride, restColumnWidths: steps, ...(keepBreaks ? { keepBreaks } : {}),
|
|
5599
|
+
...(keptInsets.length > 0 ? { lineInsets: keptInsets } : {}),
|
|
5206
5600
|
});
|
|
5207
5601
|
const lines = again && (again.letterSpacingPx ?? 0) === (letterSpacingPx ?? 0)
|
|
5208
5602
|
? [...raggedLooseLines(again.measured.lines, style.textAlign)]
|
|
@@ -5210,6 +5604,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
5210
5604
|
if (lines && lines.length > placed
|
|
5211
5605
|
&& paragraphLines.slice(0, placed).every((l, i) => lines[i].text === l.text && lines[i].hyphenated === l.hyphenated)) {
|
|
5212
5606
|
laterWidths = steps;
|
|
5607
|
+
wrapInsets = keptInsets;
|
|
5213
5608
|
paragraphLines = lines;
|
|
5214
5609
|
paragraphBreaks = again.measured.breaks;
|
|
5215
5610
|
remainingLines = lines.slice(placed);
|
|
@@ -5321,6 +5716,53 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
5321
5716
|
if (partIndex === 0 && capInfo && capInfo.rise > 0.5) {
|
|
5322
5717
|
spacingBefore += Math.ceil((capInfo.rise - 0.5) / baselineGrid) * baselineGrid;
|
|
5323
5718
|
}
|
|
5719
|
+
// Text wrap (#627): the lines this block would set beside what text
|
|
5720
|
+
// wraps round in this column are broken short of it; the lines it
|
|
5721
|
+
// already placed elsewhere keep their breaks. A block that cannot
|
|
5722
|
+
// keep them (a break trace of the other measuring path) goes under
|
|
5723
|
+
// what it would have run beside instead.
|
|
5724
|
+
if (wrapsBeside) {
|
|
5725
|
+
const placed = paragraphLines.length - remainingLines.length;
|
|
5726
|
+
const y0 = columnCursorY(curCol) + spacingBefore;
|
|
5727
|
+
const wanted = wrapInsetSteps(curCol, y0, style.lineHeightPx, placed);
|
|
5728
|
+
if (!sameInsetsFrom(wrapInsets, wanted, placed)) {
|
|
5729
|
+
const steps = [...insetsBefore(wrapInsets, placed), ...wanted];
|
|
5730
|
+
const keepBreaks = placed > 0 && paragraphBreaks && paragraphBreaks.at.length >= placed
|
|
5731
|
+
? { path: paragraphBreaks.path, at: paragraphBreaks.at.slice(0, placed) }
|
|
5732
|
+
: undefined;
|
|
5733
|
+
const again = placed > 0 && !keepBreaks ? null : measureContentBlock(rawBlock, blockIdx, brokenForWidth, blockMeasureCtx, {
|
|
5734
|
+
styleOverride,
|
|
5735
|
+
...(laterWidths.length > 0 ? { restColumnWidths: laterWidths } : {}),
|
|
5736
|
+
...(steps.length > 0 ? { lineInsets: steps } : {}),
|
|
5737
|
+
...(keepBreaks ? { keepBreaks } : {}),
|
|
5738
|
+
});
|
|
5739
|
+
const lines = again && (placed === 0 || (again.letterSpacingPx ?? 0) === (letterSpacingPx ?? 0))
|
|
5740
|
+
? [...raggedLooseLines(again.measured.lines, style.textAlign)]
|
|
5741
|
+
: undefined;
|
|
5742
|
+
if (lines && lines.length > placed
|
|
5743
|
+
&& paragraphLines.slice(0, placed).every((l, i) => lines[i].text === l.text && lines[i].hyphenated === l.hyphenated)) {
|
|
5744
|
+
wrapInsets = steps;
|
|
5745
|
+
paragraphLines = lines;
|
|
5746
|
+
paragraphBreaks = again.measured.breaks;
|
|
5747
|
+
remainingLines = lines.slice(placed);
|
|
5748
|
+
if (placed === 0) {
|
|
5749
|
+
capInfo = again.dropCap;
|
|
5750
|
+
letterSpacingPx = again.letterSpacingPx;
|
|
5751
|
+
guardedEnds = [];
|
|
5752
|
+
columnEndHyphenRetried = false;
|
|
5753
|
+
}
|
|
5754
|
+
regateCitedFloats(blockIdx, lines);
|
|
5755
|
+
}
|
|
5756
|
+
else if (wanted.length > 0) {
|
|
5757
|
+
if (balancing && balancing.spaceAbove > 0) {
|
|
5758
|
+
balanceExtraInColumn.set(curCol, (balanceExtraInColumn.get(curCol) ?? 0) - balancing.spaceAbove);
|
|
5759
|
+
}
|
|
5760
|
+
settleColumnWraps(curCol);
|
|
5761
|
+
pendingSpacing = 0;
|
|
5762
|
+
continue;
|
|
5763
|
+
}
|
|
5764
|
+
}
|
|
5765
|
+
}
|
|
5324
5766
|
// A loose paragraph's extra line is balancing height too (it lands
|
|
5325
5767
|
// whole in this column — loose candidates are never split parts).
|
|
5326
5768
|
const looseTracking = looseOutcome.get(blockIdx);
|
|
@@ -6255,6 +6697,15 @@ function placeDocumentPass(content, config, cache, options, hints) {
|
|
|
6255
6697
|
joiningSpacingWarnings.set(doc, joiningLetterSpacingWarnings(doc, joiningLetterSpacing));
|
|
6256
6698
|
if (dropCapNotes.size > 0)
|
|
6257
6699
|
dropCapWarningsByDoc.set(doc, dropCapWarnings(doc, dropCapNotes));
|
|
6700
|
+
// Pictures scaled to the room of their slot (#626), at the block citing them.
|
|
6701
|
+
const shrunk = floatShrinkWarnings(doc, (idx) => {
|
|
6702
|
+
const b = contentBlocks[idx];
|
|
6703
|
+
return b ? { start: b.sourceStart + bodyOffset, end: b.sourceEnd + bodyOffset } : undefined;
|
|
6704
|
+
});
|
|
6705
|
+
if (shrunk.length > 0)
|
|
6706
|
+
floatShrinkWarningsByDoc.set(doc, shrunk);
|
|
6707
|
+
if (textWrapNotes.length > 0)
|
|
6708
|
+
textWrapWarningsByDoc.set(doc, textWrapNotes);
|
|
6258
6709
|
return { doc, forcedBreakPages, bandCapProposals, spanPlacedInBand, bandCapsApplied, looseOutcome, captionUnderProposals };
|
|
6259
6710
|
}
|
|
6260
6711
|
/** Passes a document printing its own contents gets at most, beyond the
|
|
@@ -6315,7 +6766,9 @@ export function* buildDocumentGen(content, config, cache, options) {
|
|
|
6315
6766
|
// cap; words of a joining script that run past their line, and styles
|
|
6316
6767
|
// whose letter-spacing such words do not take.
|
|
6317
6768
|
// Line numbers set over a float of the side column (#621).
|
|
6318
|
-
const loose = [...cjkLooseLineWarnings(doc), ...wordOverflowWarnings(doc), ...(joiningSpacingWarnings.get(doc) ?? []), ...(dropCapWarningsByDoc.get(doc) ?? []), ...lineNumberWarnings(doc), ...codeOverflowWarnings(doc)];
|
|
6769
|
+
const loose = [...cjkLooseLineWarnings(doc), ...wordOverflowWarnings(doc), ...(joiningSpacingWarnings.get(doc) ?? []), ...(dropCapWarningsByDoc.get(doc) ?? []), ...lineNumberWarnings(doc), ...codeOverflowWarnings(doc), ...(floatShrinkWarningsByDoc.get(doc) ?? []), ...(textWrapWarningsByDoc.get(doc) ?? [])];
|
|
6770
|
+
// Design texts cut to fit their width (#628).
|
|
6771
|
+
loose.push(...designTruncationWarnings(doc));
|
|
6319
6772
|
// Comic panels whose cell cannot hold their picture's safe area (#556).
|
|
6320
6773
|
for (const page of doc.pages) {
|
|
6321
6774
|
if (page.comic)
|
|
@@ -6360,6 +6813,10 @@ const indexWarnings = new WeakMap();
|
|
|
6360
6813
|
const joiningSpacingWarnings = new WeakMap();
|
|
6361
6814
|
/** The `dropCap` warnings of the last pass, per document (#623). */
|
|
6362
6815
|
const dropCapWarningsByDoc = new WeakMap();
|
|
6816
|
+
/** The `floatShrunk` warnings of a build (#626), found by its last pass. */
|
|
6817
|
+
const floatShrinkWarningsByDoc = new WeakMap();
|
|
6818
|
+
/** The `textWrap` warnings of a build (#627), found by its last pass. */
|
|
6819
|
+
const textWrapWarningsByDoc = new WeakMap();
|
|
6363
6820
|
/** `doc` with the page of each of its index marks (`doc.indexMarks`). */
|
|
6364
6821
|
function withIndexMarks(doc, content) {
|
|
6365
6822
|
const { content: body, contentOffset } = extractFrontmatter(content.markdown);
|