postext 0.3.36 → 0.3.38
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__/continuation.test.js +44 -0
- package/dist/__tests__/continuation.test.js.map +1 -1
- package/dist/__tests__/exports.test.js +13 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/headingStyles.test.d.ts +2 -0
- package/dist/__tests__/headingStyles.test.d.ts.map +1 -0
- package/dist/__tests__/headingStyles.test.js +134 -0
- package/dist/__tests__/headingStyles.test.js.map +1 -0
- package/dist/__tests__/parse/containers.test.js +1 -1
- package/dist/__tests__/parse/containers.test.js.map +1 -1
- package/dist/__tests__/toc.test.d.ts +2 -0
- package/dist/__tests__/toc.test.d.ts.map +1 -0
- package/dist/__tests__/toc.test.js +210 -0
- package/dist/__tests__/toc.test.js.map +1 -0
- package/dist/canvas-backend/blockRender.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.js +21 -11
- package/dist/canvas-backend/blockRender.js.map +1 -1
- package/dist/canvas-backend/index.d.ts.map +1 -1
- package/dist/canvas-backend/index.js +9 -0
- package/dist/canvas-backend/index.js.map +1 -1
- package/dist/defaults/headerFooter.d.ts.map +1 -1
- package/dist/defaults/headerFooter.js +17 -0
- package/dist/defaults/headerFooter.js.map +1 -1
- package/dist/defaults/headingStyles.d.ts +10 -0
- package/dist/defaults/headingStyles.d.ts.map +1 -0
- package/dist/defaults/headingStyles.js +85 -0
- package/dist/defaults/headingStyles.js.map +1 -0
- package/dist/defaults/headings.d.ts +6 -1
- package/dist/defaults/headings.d.ts.map +1 -1
- package/dist/defaults/headings.js +32 -0
- package/dist/defaults/headings.js.map +1 -1
- package/dist/defaults/index.d.ts +2 -0
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +18 -0
- package/dist/defaults/index.js.map +1 -1
- package/dist/defaults/paragraphStyles.d.ts.map +1 -1
- package/dist/defaults/paragraphStyles.js +4 -1
- package/dist/defaults/paragraphStyles.js.map +1 -1
- package/dist/defaults/toc.d.ts +26 -0
- package/dist/defaults/toc.d.ts.map +1 -0
- package/dist/defaults/toc.js +167 -0
- package/dist/defaults/toc.js.map +1 -0
- package/dist/design/layout.d.ts +15 -3
- package/dist/design/layout.d.ts.map +1 -1
- package/dist/design/layout.js +66 -1
- package/dist/design/layout.js.map +1 -1
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +16 -5
- package/dist/html-backend.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/numbering.d.ts +5 -1
- package/dist/numbering.d.ts.map +1 -1
- package/dist/numbering.js +7 -1
- package/dist/numbering.js.map +1 -1
- package/dist/parse/blockParser.d.ts.map +1 -1
- package/dist/parse/blockParser.js +1 -1
- 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/types.d.ts +26 -1
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +196 -57
- 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 +2 -2
- package/dist/pipeline/buildBlockKind.js.map +1 -1
- package/dist/pipeline/columnBalancing.d.ts.map +1 -1
- package/dist/pipeline/columnBalancing.js +3 -1
- package/dist/pipeline/columnBalancing.js.map +1 -1
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js +3 -1
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/continuation.d.ts +10 -1
- package/dist/pipeline/continuation.d.ts.map +1 -1
- package/dist/pipeline/continuation.js +16 -1
- package/dist/pipeline/continuation.js.map +1 -1
- package/dist/pipeline/headerFooter.d.ts +5 -2
- package/dist/pipeline/headerFooter.d.ts.map +1 -1
- package/dist/pipeline/headerFooter.js +90 -15
- package/dist/pipeline/headerFooter.js.map +1 -1
- package/dist/pipeline/headingStyles.d.ts +58 -0
- package/dist/pipeline/headingStyles.d.ts.map +1 -0
- package/dist/pipeline/headingStyles.js +157 -0
- package/dist/pipeline/headingStyles.js.map +1 -0
- package/dist/pipeline/index.d.ts +2 -1
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +2 -1
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
- package/dist/pipeline/measureContentBlock.js +4 -0
- package/dist/pipeline/measureContentBlock.js.map +1 -1
- package/dist/pipeline/outline.d.ts +37 -0
- package/dist/pipeline/outline.d.ts.map +1 -0
- package/dist/pipeline/outline.js +148 -0
- package/dist/pipeline/outline.js.map +1 -0
- package/dist/pipeline/pageRoles.d.ts +2 -1
- package/dist/pipeline/pageRoles.d.ts.map +1 -1
- package/dist/pipeline/pageRoles.js +5 -4
- package/dist/pipeline/pageRoles.js.map +1 -1
- package/dist/pipeline/placeholders.d.ts.map +1 -1
- package/dist/pipeline/placeholders.js +6 -0
- package/dist/pipeline/placeholders.js.map +1 -1
- package/dist/pipeline/resourceNumbering.d.ts +3 -1
- package/dist/pipeline/resourceNumbering.d.ts.map +1 -1
- package/dist/pipeline/resourceNumbering.js +4 -2
- package/dist/pipeline/resourceNumbering.js.map +1 -1
- package/dist/pipeline/styles.d.ts +5 -2
- package/dist/pipeline/styles.d.ts.map +1 -1
- package/dist/pipeline/styles.js +7 -3
- package/dist/pipeline/styles.js.map +1 -1
- package/dist/pipeline/toc.d.ts +22 -0
- package/dist/pipeline/toc.d.ts.map +1 -0
- package/dist/pipeline/toc.js +251 -0
- package/dist/pipeline/toc.js.map +1 -0
- package/dist/types.d.ts +277 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +42 -2
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js.map +1 -1
- package/package.json +1 -1
package/dist/pipeline/build.js
CHANGED
|
@@ -6,7 +6,10 @@ import { parseMarkdownMemo } from '../parse';
|
|
|
6
6
|
import { buildPageLabels, computeHeadingNumbers, } from '../numbering';
|
|
7
7
|
import { extractFrontmatter } from '../frontmatter';
|
|
8
8
|
import { initHyphenator } from '../measure';
|
|
9
|
-
import { resolveAllConfig, computeBaselineGrid
|
|
9
|
+
import { resolveAllConfig, computeBaselineGrid } from './config';
|
|
10
|
+
import { createHeadingLevelResolver, deriveSectionGeometryConfig, deriveSectionMeasureContext, headingIsNumbered, headingStyleOf, planHeadingSections, } from './headingStyles';
|
|
11
|
+
import { computeOutline, hasTocDirective, outlineFromDoc, sameOutline } from './outline';
|
|
12
|
+
import { expandTocDirectives } from './toc';
|
|
10
13
|
import { resolveBodyStyle, resolveBlockquoteStyle } from './styles';
|
|
11
14
|
import { computeLevelIndentsPx, computeOrderedLevelIndentsPx, computeOrderedListRunMetrics, } from './lists';
|
|
12
15
|
import { resetLinePositions, createPageWithColumns, currentColumn, advanceToNextColumn, advanceToNextPageBoundary, enforcePageParity, placeBlockInColumn, placeAtomicBlock, createPartPage, pageHasContent, pageIsOccupied, bandColumns, currentBand, isBandLevel, bandUsedBottom, closeBandAndInsertSpan, } from './placement';
|
|
@@ -97,7 +100,8 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
97
100
|
const looseOutcome = new Map();
|
|
98
101
|
// Lines gained so far per column budget group (see `LooseBudget`).
|
|
99
102
|
const looseGained = new Map();
|
|
100
|
-
|
|
103
|
+
// Level configs with heading-style overrides merged in (`{style="…"}`).
|
|
104
|
+
const headingLevels = createHeadingLevelResolver(resolved);
|
|
101
105
|
const dpi = resolved.page.dpi;
|
|
102
106
|
// Initialize hyphenator if needed (body text, or any justified paragraph
|
|
103
107
|
// style that hyphenates — they share the document locale).
|
|
@@ -123,7 +127,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
123
127
|
if (continuation?.part)
|
|
124
128
|
doc.partStart = continuation.part;
|
|
125
129
|
const pageMetrics = computePageMetrics(resolved);
|
|
126
|
-
const { pageWidthPx, pageHeightPx, trimOffset
|
|
130
|
+
const { pageWidthPx, pageHeightPx, trimOffset } = pageMetrics;
|
|
131
|
+
// The geometry pages are opened with: the document's, or — inside a
|
|
132
|
+
// styled section with its own margins / layout — the section's.
|
|
133
|
+
let contentArea = pageMetrics.contentArea;
|
|
134
|
+
let geomResolved = resolved;
|
|
127
135
|
// Page/bleed frames for design elements anchored to `'page'` / `'bleed'`.
|
|
128
136
|
const designFrames = { page: pageMetrics.trimBox, bleed: pageMetrics.bleedBox };
|
|
129
137
|
doc.trimOffset = trimOffset;
|
|
@@ -133,22 +141,29 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
133
141
|
// Extract frontmatter, then parse the remaining markdown body
|
|
134
142
|
const { metadata: frontmatterMeta, content: markdownBody, contentOffset: bodyOffset } = extractFrontmatter(content.markdown);
|
|
135
143
|
doc.metadata = { ...(content.metadata ?? {}), ...frontmatterMeta };
|
|
136
|
-
const
|
|
144
|
+
const parsedBlocks = parseMarkdownMemo(markdownBody);
|
|
145
|
+
const headingStart = continuation?.headings;
|
|
146
|
+
// `:::toc` expands into the entries of the book's outline — the one the
|
|
147
|
+
// host supplied, else this document's own (page labels unknown on the
|
|
148
|
+
// first pass; `buildDocument` lays the document out again with them).
|
|
149
|
+
const outline = content.outline
|
|
150
|
+
?? (hasTocDirective(parsedBlocks) ? computeOutline(parsedBlocks, resolved, headingStart) : undefined);
|
|
151
|
+
const contentBlocks = expandTocDirectives(parsedBlocks, outline, resolved);
|
|
152
|
+
const isNumbered = (b) => headingIsNumbered(b, resolved);
|
|
137
153
|
const headingTemplates = {};
|
|
138
154
|
for (const lvl of resolved.headings.levels) {
|
|
139
155
|
if (lvl.numberingTemplate && lvl.numberingTemplate.length > 0) {
|
|
140
156
|
headingTemplates[lvl.level] = lvl.numberingTemplate;
|
|
141
157
|
}
|
|
142
158
|
}
|
|
143
|
-
const
|
|
144
|
-
const headingPrefixes = computeHeadingNumbers(contentBlocks, headingTemplates, headingStart ? [headingStart.h1, headingStart.h2, headingStart.h3, headingStart.h4, headingStart.h5, headingStart.h6] : undefined);
|
|
159
|
+
const headingPrefixes = computeHeadingNumbers(contentBlocks, headingTemplates, headingStart ? [headingStart.h1, headingStart.h2, headingStart.h3, headingStart.h4, headingStart.h5, headingStart.h6] : undefined, isNumbered);
|
|
145
160
|
// Resource numbering — computed up front (before the placement loop) so that
|
|
146
161
|
// captions and inline `:ref`s can resolve their rendered number strings
|
|
147
162
|
// before measurement. Numbering follows order of first reference in the
|
|
148
163
|
// document.
|
|
149
164
|
const resourceTypes = config?.resourceTypes ?? defaultResourceTypes();
|
|
150
165
|
const resources = content.resources ?? [];
|
|
151
|
-
const headingContext = computeHeadingContext(contentBlocks, headingStart);
|
|
166
|
+
const headingContext = computeHeadingContext(contentBlocks, headingStart, isNumbered);
|
|
152
167
|
const resourceNumbering = computeResourceNumbering(contentBlocks, resourceTypes, resources, headingContext, continuation ? { counters: continuation.resourceCounters, numbered: continuation.resourceNumbers } : undefined);
|
|
153
168
|
// Lookups threaded into block-kind resolution + measurement.
|
|
154
169
|
const resourceById = new Map();
|
|
@@ -173,6 +188,9 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
173
188
|
const calloutPlan = planCallouts(contentBlocks);
|
|
174
189
|
// `:::part` ranges: start/end marker indices and the enclosed blocks.
|
|
175
190
|
const partPlan = planParts(contentBlocks);
|
|
191
|
+
// Styled sections (`{style="…"}` headings): geometry, running heads and
|
|
192
|
+
// body typography per content-block index.
|
|
193
|
+
const sectionPlan = planHeadingSections(contentBlocks, resolved);
|
|
176
194
|
// --- Float planning (issue #49 — resources float to page bands) ----------
|
|
177
195
|
// A resource is incorporated by its first reference (an inline `:ref` or a
|
|
178
196
|
// `::resource` directive, whichever comes first in reading order). Floated
|
|
@@ -837,7 +855,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
837
855
|
const before = floatsPlaced;
|
|
838
856
|
const startPageIndex = cursor.pageIndex;
|
|
839
857
|
do {
|
|
840
|
-
advanceToNextColumn(doc, cursor,
|
|
858
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
841
859
|
} while (cursor.pageIndex === startPageIndex);
|
|
842
860
|
if (floatsPlaced === before)
|
|
843
861
|
break; // safety: no progress
|
|
@@ -846,6 +864,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
846
864
|
// Everything per-block measurement needs that is constant for this pass.
|
|
847
865
|
const measureCtx = {
|
|
848
866
|
resolved,
|
|
867
|
+
headingLevels,
|
|
849
868
|
bodyStyle,
|
|
850
869
|
blockquoteStyle,
|
|
851
870
|
headingPrefixes,
|
|
@@ -869,6 +888,61 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
869
888
|
: measureCtx;
|
|
870
889
|
// Placement cursor
|
|
871
890
|
const cursor = { pageIndex: 0, columnIndex: 0 };
|
|
891
|
+
// Blocks inside a styled section with a body style measure with it.
|
|
892
|
+
const sectionMeasureCtxs = new Map();
|
|
893
|
+
const sectionMeasureCtx = (style) => {
|
|
894
|
+
if (!style?.bodyStyle)
|
|
895
|
+
return measureCtx;
|
|
896
|
+
let ctx = sectionMeasureCtxs.get(style);
|
|
897
|
+
if (!ctx) {
|
|
898
|
+
ctx = deriveSectionMeasureContext(measureCtx, style);
|
|
899
|
+
sectionMeasureCtxs.set(style, ctx);
|
|
900
|
+
}
|
|
901
|
+
return ctx;
|
|
902
|
+
};
|
|
903
|
+
/** The styled section whose geometry the pages opened from now on take. */
|
|
904
|
+
let currentSection;
|
|
905
|
+
const enterSection = (style) => {
|
|
906
|
+
currentSection = style;
|
|
907
|
+
geomResolved = style ? deriveSectionGeometryConfig(resolved, style) : resolved;
|
|
908
|
+
contentArea = geomResolved === resolved ? pageMetrics.contentArea : computePageMetrics(geomResolved).contentArea;
|
|
909
|
+
// A page still empty takes the geometry right away — the document (or
|
|
910
|
+
// a chapter laid out on its own) opening with a styled heading.
|
|
911
|
+
const page = doc.pages[cursor.pageIndex];
|
|
912
|
+
if (page && !page.partInfo && cursor.columnIndex === 0
|
|
913
|
+
&& !pageHasContent(page) && !(page.floats && page.floats.length > 0)) {
|
|
914
|
+
const fresh = createPageWithColumns(page.index, geomResolved, contentArea, pageWidthPx, pageHeightPx, pageIndexOffset);
|
|
915
|
+
if (page.blankForParity)
|
|
916
|
+
fresh.blankForParity = true;
|
|
917
|
+
if (page.blankForForce)
|
|
918
|
+
fresh.blankForForce = true;
|
|
919
|
+
doc.pages[cursor.pageIndex] = fresh;
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
/** Heading-style and contents-row stamps a placed block carries for the
|
|
923
|
+
* running heads (`computeSectionStyles`) and the contents' part rows. */
|
|
924
|
+
const stampBlockExtras = (blk, raw) => {
|
|
925
|
+
if (raw.type === 'heading') {
|
|
926
|
+
const style = headingStyleOf(raw, resolved);
|
|
927
|
+
if (style) {
|
|
928
|
+
blk.headingStyleId = style.id;
|
|
929
|
+
if (!style.numbered)
|
|
930
|
+
blk.unnumbered = true;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (raw.toc?.kind === 'entry') {
|
|
934
|
+
blk.tocEntry = raw.toc.pageIndex !== undefined ? { pageIndex: raw.toc.pageIndex } : {};
|
|
935
|
+
}
|
|
936
|
+
if (raw.toc?.kind === 'part') {
|
|
937
|
+
blk.tocPart = {
|
|
938
|
+
number: raw.toc.number,
|
|
939
|
+
title: raw.toc.title ?? '',
|
|
940
|
+
pageLabel: raw.toc.pageLabel ?? '',
|
|
941
|
+
...(raw.toc.palette ? { palette: raw.toc.palette } : {}),
|
|
942
|
+
...(raw.toc.pageIndex !== undefined ? { pageIndex: raw.toc.pageIndex } : {}),
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
};
|
|
872
946
|
let blockIdCounter = 0;
|
|
873
947
|
let pendingSpacing = 0;
|
|
874
948
|
// Pages whose break into the next page is explicit rather than natural
|
|
@@ -1029,11 +1103,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1029
1103
|
if (curPage.partInfo && !pageHasContent(curPage)) {
|
|
1030
1104
|
const startPageIndex = cursor.pageIndex;
|
|
1031
1105
|
do {
|
|
1032
|
-
advanceToNextColumn(doc, cursor,
|
|
1106
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
1033
1107
|
} while (cursor.pageIndex === startPageIndex);
|
|
1034
1108
|
return;
|
|
1035
1109
|
}
|
|
1036
|
-
advanceToNextPageBoundary(doc, cursor,
|
|
1110
|
+
advanceToNextPageBoundary(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
1037
1111
|
};
|
|
1038
1112
|
// Page-numbering segments. The implicit first segment comes from
|
|
1039
1113
|
// `cfg.page.pageNumbering`; `:::numbering` directives append more,
|
|
@@ -1047,20 +1121,34 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1047
1121
|
},
|
|
1048
1122
|
];
|
|
1049
1123
|
let pendingNumberingChange = null;
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1124
|
+
/** The earliest page a pending change may number: the page the directive
|
|
1125
|
+
* was met on while still empty, else the page after it. */
|
|
1126
|
+
let pendingNumberingPage = 0;
|
|
1127
|
+
/** A page holding something the reader sees: column content, a float, a
|
|
1128
|
+
* part opener. A parity blank is not one. */
|
|
1129
|
+
const pageTakesNumbering = (page) => pageHasContent(page) || (page.floats?.length ?? 0) > 0 || page.partInfo !== undefined;
|
|
1130
|
+
/** Commits a pending `:::numbering` change to the first page from the
|
|
1131
|
+
* directive on that receives content: the page the directive opens (the
|
|
1132
|
+
* head of a chapter, right after a page break) or, when that page was
|
|
1133
|
+
* already written on, the next one — never a blank page padding parity
|
|
1134
|
+
* in between. Called after every block iteration and page advance. */
|
|
1053
1135
|
const flushPendingNumberingAtBoundary = () => {
|
|
1054
|
-
if (
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1136
|
+
if (!pendingNumberingChange)
|
|
1137
|
+
return;
|
|
1138
|
+
if (cursor.pageIndex < pendingNumberingPage)
|
|
1139
|
+
return;
|
|
1140
|
+
const page = doc.pages[cursor.pageIndex];
|
|
1141
|
+
if (!pageTakesNumbering(page))
|
|
1142
|
+
return;
|
|
1143
|
+
// A change already recorded for this page is replaced.
|
|
1144
|
+
const last = pageNumberSegments[pageNumberSegments.length - 1];
|
|
1145
|
+
if (last.startPageIndex === cursor.pageIndex && pageNumberSegments.length > 1)
|
|
1146
|
+
pageNumberSegments.pop();
|
|
1147
|
+
pageNumberSegments.push({
|
|
1148
|
+
startPageIndex: cursor.pageIndex,
|
|
1149
|
+
...pendingNumberingChange,
|
|
1150
|
+
});
|
|
1151
|
+
pendingNumberingChange = null;
|
|
1064
1152
|
};
|
|
1065
1153
|
/** Heading blocks that are not part of a callout — the only ones the
|
|
1066
1154
|
* keep-with-next rollbacks may pull along (a callout is one unbreakable
|
|
@@ -1508,7 +1596,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1508
1596
|
frameId = `block-${blockIdCounter++}`;
|
|
1509
1597
|
const startPageIndex = cursor.pageIndex;
|
|
1510
1598
|
do {
|
|
1511
|
-
advanceToNextColumn(doc, cursor,
|
|
1599
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
1512
1600
|
} while (cursor.pageIndex === startPageIndex);
|
|
1513
1601
|
forceHere = true;
|
|
1514
1602
|
continue;
|
|
@@ -1580,7 +1668,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1580
1668
|
pendingSpacing = 0;
|
|
1581
1669
|
const startPageIndex = cursor.pageIndex;
|
|
1582
1670
|
do {
|
|
1583
|
-
advanceToNextColumn(doc, cursor,
|
|
1671
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
1584
1672
|
} while (cursor.pageIndex === startPageIndex);
|
|
1585
1673
|
page = doc.pages[cursor.pageIndex];
|
|
1586
1674
|
}
|
|
@@ -1610,7 +1698,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1610
1698
|
continue;
|
|
1611
1699
|
}
|
|
1612
1700
|
if (b.type === 'heading' && b.level) {
|
|
1613
|
-
const level =
|
|
1701
|
+
const level = headingLevels.forBlock(b);
|
|
1614
1702
|
return level?.breakBefore?.enabled === true || level?.span === 'page';
|
|
1615
1703
|
}
|
|
1616
1704
|
return false;
|
|
@@ -1716,7 +1804,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1716
1804
|
pendingSpacing = 0;
|
|
1717
1805
|
const startPageIndex = cursor.pageIndex;
|
|
1718
1806
|
do {
|
|
1719
|
-
advanceToNextColumn(doc, cursor,
|
|
1807
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
1720
1808
|
} while (cursor.pageIndex === startPageIndex);
|
|
1721
1809
|
page = doc.pages[cursor.pageIndex];
|
|
1722
1810
|
fit = attempt(page, false) ?? attempt(page, true);
|
|
@@ -1854,10 +1942,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1854
1942
|
pendingSpacing = 0;
|
|
1855
1943
|
if (part === 0 && resolved.headings.keepWithNext && run > 0 && run < curCol.blocks.length) {
|
|
1856
1944
|
const rolledBack = rollbackTrailingBlocks(curCol, doc.blocks, isFreeHeading);
|
|
1857
|
-
advanceToNextColumn(doc, cursor,
|
|
1945
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
1858
1946
|
return (rolledBack[0].contentIndex ?? startIdx - rolledBack.length) - 1;
|
|
1859
1947
|
}
|
|
1860
|
-
advanceToNextColumn(doc, cursor,
|
|
1948
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
1861
1949
|
// Try the next column afresh: it may be short too (a float band
|
|
1862
1950
|
// reserved on the page it opened), or of another width (oneAndHalf).
|
|
1863
1951
|
continue;
|
|
@@ -1893,7 +1981,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1893
1981
|
curCol.availableHeight = Math.max(0, curCol.availableHeight - balanceBefore);
|
|
1894
1982
|
}
|
|
1895
1983
|
enterBand(startIdx, 0);
|
|
1896
|
-
placeAtomicBlock(placed.frame, placed.totalHeight, spacingBefore, cursor, doc,
|
|
1984
|
+
placeAtomicBlock(placed.frame, placed.totalHeight, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
1897
1985
|
enterBand(startIdx, 0);
|
|
1898
1986
|
curCol = currentColumn(doc, cursor);
|
|
1899
1987
|
commitCallout(placed, startIdx, plan, curCol, part > 0 || fragment ? fragmentRange(L, from, to) : undefined);
|
|
@@ -1912,7 +2000,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1912
2000
|
from = to;
|
|
1913
2001
|
part++;
|
|
1914
2002
|
frameId = `block-${blockIdCounter++}`;
|
|
1915
|
-
advanceToNextColumn(doc, cursor,
|
|
2003
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
1916
2004
|
}
|
|
1917
2005
|
};
|
|
1918
2006
|
for (let blockIdx = 0; blockIdx < contentBlocks.length; blockIdx++) {
|
|
@@ -1920,6 +2008,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1920
2008
|
throw new BuildCancelledError();
|
|
1921
2009
|
options?.onProgress?.({ pass: 1, blocks: blockIdx, totalBlocks: contentBlocks.length, pages: doc.pages.length });
|
|
1922
2010
|
const rawBlock = contentBlocks[blockIdx];
|
|
2011
|
+
// The styled section this block sits in: its geometry applies to the
|
|
2012
|
+
// pages opened from here (a heading with `breakBefore` opens one).
|
|
2013
|
+
const blockSection = sectionPlan.byBlock[blockIdx];
|
|
2014
|
+
if (blockSection !== currentSection)
|
|
2015
|
+
enterSection(blockSection);
|
|
1923
2016
|
// Floats whose reference landed in an earlier iteration take the first
|
|
1924
2017
|
// free slot of the current page now — after their reference in reading
|
|
1925
2018
|
// order. Then enqueue the floats first-referenced in this block, so the
|
|
@@ -1934,13 +2027,13 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1934
2027
|
if (name === 'pagebreak') {
|
|
1935
2028
|
pendingSpacing = 0;
|
|
1936
2029
|
markForcedBreak();
|
|
1937
|
-
advanceToNextPageBoundary(doc, cursor,
|
|
2030
|
+
advanceToNextPageBoundary(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
1938
2031
|
const parity = attrs.parity;
|
|
1939
2032
|
if (parity === 'odd'
|
|
1940
2033
|
|| parity === 'even'
|
|
1941
2034
|
|| parity === 'always-odd'
|
|
1942
2035
|
|| parity === 'always-even') {
|
|
1943
|
-
enforcePageParity(doc, cursor,
|
|
2036
|
+
enforcePageParity(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, parity);
|
|
1944
2037
|
}
|
|
1945
2038
|
// Pending floats land on the page that follows the break — after
|
|
1946
2039
|
// parity padding, so a blank parity page never carries a float.
|
|
@@ -1959,7 +2052,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1959
2052
|
const page = doc.pages[cursor.pageIndex];
|
|
1960
2053
|
if (cursor.columnIndex === page.columns.length - 1)
|
|
1961
2054
|
markForcedBreak();
|
|
1962
|
-
advanceToNextColumn(doc, cursor,
|
|
2055
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
1963
2056
|
flushPendingNumberingAtBoundary();
|
|
1964
2057
|
}
|
|
1965
2058
|
}
|
|
@@ -1973,8 +2066,10 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1973
2066
|
if (Number.isInteger(n) && n >= 1)
|
|
1974
2067
|
change.startAt = n;
|
|
1975
2068
|
}
|
|
1976
|
-
if (Object.keys(change).length > 0)
|
|
2069
|
+
if (Object.keys(change).length > 0) {
|
|
1977
2070
|
pendingNumberingChange = change;
|
|
2071
|
+
pendingNumberingPage = pageTakesNumbering(doc.pages[cursor.pageIndex]) ? cursor.pageIndex + 1 : cursor.pageIndex;
|
|
2072
|
+
}
|
|
1978
2073
|
}
|
|
1979
2074
|
continue;
|
|
1980
2075
|
}
|
|
@@ -1997,7 +2092,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
1997
2092
|
pendingSpacing = 0;
|
|
1998
2093
|
closeFlowSegment(blockIdx);
|
|
1999
2094
|
leaveCurrentPage();
|
|
2000
|
-
enforcePageParity(doc, cursor,
|
|
2095
|
+
enforcePageParity(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, resolved.parts.breakBefore.parity);
|
|
2001
2096
|
cursor.columnIndex = 0;
|
|
2002
2097
|
// Map the opener's title back to the `title="…"` attribute of the
|
|
2003
2098
|
// fence so the editor can place the cursor from a click on the band.
|
|
@@ -2034,7 +2129,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2034
2129
|
pendingSpacing = 0;
|
|
2035
2130
|
closeFlowSegment(blockIdx);
|
|
2036
2131
|
leaveCurrentPage();
|
|
2037
|
-
enforcePageParity(doc, cursor,
|
|
2132
|
+
enforcePageParity(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, parity);
|
|
2038
2133
|
flushPendingNumberingAtBoundary();
|
|
2039
2134
|
}
|
|
2040
2135
|
if (rawBlock.type === 'containerStart' && rawBlock.containerName === 'callout') {
|
|
@@ -2082,14 +2177,14 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2082
2177
|
}
|
|
2083
2178
|
// --- Heading `breakBefore` ----------------------------------------
|
|
2084
2179
|
if (rawBlock.type === 'heading' && rawBlock.level) {
|
|
2085
|
-
const level =
|
|
2180
|
+
const level = headingLevels.forBlock(rawBlock);
|
|
2086
2181
|
const bb = level?.breakBefore;
|
|
2087
2182
|
if (bb && bb.enabled) {
|
|
2088
2183
|
pendingSpacing = 0;
|
|
2089
2184
|
closeFlowSegment(blockIdx);
|
|
2090
|
-
advanceToNextPageBoundary(doc, cursor,
|
|
2185
|
+
advanceToNextPageBoundary(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
2091
2186
|
if (bb.parity !== 'any') {
|
|
2092
|
-
enforcePageParity(doc, cursor,
|
|
2187
|
+
enforcePageParity(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, bb.parity);
|
|
2093
2188
|
}
|
|
2094
2189
|
flushPendingNumberingAtBoundary();
|
|
2095
2190
|
}
|
|
@@ -2100,7 +2195,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2100
2195
|
if (level?.span === 'page') {
|
|
2101
2196
|
pendingSpacing = 0;
|
|
2102
2197
|
closeFlowSegment(blockIdx);
|
|
2103
|
-
advanceToNextPageBoundary(doc, cursor,
|
|
2198
|
+
advanceToNextPageBoundary(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
2104
2199
|
cursor.columnIndex = 0;
|
|
2105
2200
|
flushPendingNumberingAtBoundary();
|
|
2106
2201
|
}
|
|
@@ -2117,7 +2212,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2117
2212
|
// Measure against the current column width. `null` means there is nothing
|
|
2118
2213
|
// to place inline (empty text, unknown resource id, floated resource).
|
|
2119
2214
|
const col = currentColumn(doc, cursor);
|
|
2120
|
-
const blockMeasureCtx = partPlan.byBlock[blockIdx] ? partMeasureCtx :
|
|
2215
|
+
const blockMeasureCtx = partPlan.byBlock[blockIdx] ? partMeasureCtx : sectionMeasureCtx(sectionPlan.byBlock[blockIdx]);
|
|
2121
2216
|
const styleOverride = paragraphContainer
|
|
2122
2217
|
? (isContainerTail ? paragraphContainer.tailStyle : paragraphContainer.style)
|
|
2123
2218
|
: undefined;
|
|
@@ -2151,6 +2246,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2151
2246
|
const groupHeight = measured.totalHeight;
|
|
2152
2247
|
const blk = createVDTBlock(id, 'resource', style.fontString, style.color, style.textAlign);
|
|
2153
2248
|
blk.contentIndex = blockIdx;
|
|
2249
|
+
stampBlockExtras(blk, rawBlock);
|
|
2154
2250
|
blk.resourceBlock = resourceBlock;
|
|
2155
2251
|
blk.dirty = false;
|
|
2156
2252
|
blk.snappedToGrid = false;
|
|
@@ -2168,7 +2264,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2168
2264
|
}];
|
|
2169
2265
|
const spacingBefore = pendingSpacing;
|
|
2170
2266
|
enterBand(blockIdx, 0);
|
|
2171
|
-
placeAtomicBlock(blk, groupHeight, spacingBefore, cursor, doc,
|
|
2267
|
+
placeAtomicBlock(blk, groupHeight, spacingBefore, cursor, doc, geomResolved, contentArea, pageWidthPx, pageHeightPx);
|
|
2172
2268
|
enterBand(blockIdx, 0);
|
|
2173
2269
|
// `placeBlockInColumn` (inside placeAtomicBlock) shifts `blk.lines`; the
|
|
2174
2270
|
// resource's own caption/table lines live on `resourceBlock` and must be
|
|
@@ -2234,10 +2330,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2234
2330
|
// paragraph of a `:::paragraphs` container, whose leading and spacing
|
|
2235
2331
|
// are off-grid by design.
|
|
2236
2332
|
const nextIsHeading = nextBlock?.type === 'heading';
|
|
2237
|
-
|
|
2333
|
+
// The contents (`:::toc`) keep their own rhythm: an entry set as a list
|
|
2334
|
+
// item is not a list tail to realign the text after it.
|
|
2335
|
+
const shouldSnapToGrid = rawBlock.toc === undefined && ((vdtType === 'heading' && !nextIsHeading) ||
|
|
2238
2336
|
(vdtType === 'listItem' && !nextIsListItem) ||
|
|
2239
2337
|
(vdtType === 'paragraph' && isContainerTail) ||
|
|
2240
|
-
vdtType === 'mathDisplay';
|
|
2338
|
+
vdtType === 'mathDisplay');
|
|
2241
2339
|
// Place block, splitting across columns/pages if needed.
|
|
2242
2340
|
// List items may split too — orphan/widow protection per-list is gated by
|
|
2243
2341
|
// `avoidOrphansInLists` / `avoidWidowsInLists`; bullet stays on first part.
|
|
@@ -2290,6 +2388,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2290
2388
|
spacingBefore = Math.max(spacingBefore, style.marginTopPx);
|
|
2291
2389
|
}
|
|
2292
2390
|
}
|
|
2391
|
+
else if (rawBlock.toc) {
|
|
2392
|
+
// A part row or an unnumbered entry of the contents: its top
|
|
2393
|
+
// margin (`toc.parts.marginTop`, the level's `marginTop`) applies
|
|
2394
|
+
// like a heading's.
|
|
2395
|
+
spacingBefore = Math.max(spacingBefore, style.marginTopPx);
|
|
2396
|
+
}
|
|
2293
2397
|
// Column balancing: extra grid lines above a non-heading balance
|
|
2294
2398
|
// target — the first block after a list end. Heading targets are
|
|
2295
2399
|
// handled inside the heading branch above (after margin collapsing).
|
|
@@ -2328,7 +2432,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2328
2432
|
// subsequent marginBottom + grid snap) starts from there.
|
|
2329
2433
|
let effectiveRemainHeight = totalRemainHeight;
|
|
2330
2434
|
if (vdtType === 'heading' && headingLevel !== undefined && partIndex === 0) {
|
|
2331
|
-
const lvl =
|
|
2435
|
+
const lvl = headingLevels.forBlock(rawBlock);
|
|
2332
2436
|
if (lvl) {
|
|
2333
2437
|
const full = remainingLines
|
|
2334
2438
|
.map((ln) => (ln.segments ?? []).map((s) => s.text).join(''))
|
|
@@ -2382,6 +2486,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2382
2486
|
if (letterSpacingPx !== undefined)
|
|
2383
2487
|
blk.letterSpacing = letterSpacingPx;
|
|
2384
2488
|
blk.contentIndex = blockIdx;
|
|
2489
|
+
stampBlockExtras(blk, rawBlock);
|
|
2385
2490
|
blk.headingLevel = headingLevel;
|
|
2386
2491
|
if (numberPrefix)
|
|
2387
2492
|
blk.numberPrefix = numberPrefix;
|
|
@@ -2397,7 +2502,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2397
2502
|
remainingLines = remainingLines.slice(splitAt);
|
|
2398
2503
|
partIndex++;
|
|
2399
2504
|
pendingSpacing = 0;
|
|
2400
|
-
advanceToNextColumn(doc, cursor,
|
|
2505
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2401
2506
|
continue;
|
|
2402
2507
|
}
|
|
2403
2508
|
// Can't cleanly split the colon line off — would create a widow.
|
|
@@ -2429,12 +2534,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2429
2534
|
// rolled-back heading (marker blocks in between are replayed).
|
|
2430
2535
|
blockIdx = (popped[0].contentIndex ?? blockIdx - headingRunCount) - 1;
|
|
2431
2536
|
pendingSpacing = 0;
|
|
2432
|
-
advanceToNextColumn(doc, cursor,
|
|
2537
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2433
2538
|
break;
|
|
2434
2539
|
}
|
|
2435
2540
|
if (headingRunCount === 0) {
|
|
2436
2541
|
pendingSpacing = 0;
|
|
2437
|
-
advanceToNextColumn(doc, cursor,
|
|
2542
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2438
2543
|
continue;
|
|
2439
2544
|
}
|
|
2440
2545
|
// headingRunCount === curCol.blocks.length: fall through to place.
|
|
@@ -2490,11 +2595,11 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2490
2595
|
// rolled-back heading (marker blocks in between are replayed).
|
|
2491
2596
|
blockIdx = (rolledBack[0].contentIndex ?? blockIdx - rolledBack.length) - 1;
|
|
2492
2597
|
pendingSpacing = 0;
|
|
2493
|
-
advanceToNextColumn(doc, cursor,
|
|
2598
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2494
2599
|
break;
|
|
2495
2600
|
}
|
|
2496
2601
|
pendingSpacing = 0;
|
|
2497
|
-
advanceToNextColumn(doc, cursor,
|
|
2602
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2498
2603
|
continue;
|
|
2499
2604
|
}
|
|
2500
2605
|
}
|
|
@@ -2508,6 +2613,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2508
2613
|
if (letterSpacingPx !== undefined)
|
|
2509
2614
|
blk.letterSpacing = letterSpacingPx;
|
|
2510
2615
|
blk.contentIndex = blockIdx;
|
|
2616
|
+
stampBlockExtras(blk, rawBlock);
|
|
2511
2617
|
if (partIndex === 0) {
|
|
2512
2618
|
blk.headingLevel = headingLevel;
|
|
2513
2619
|
if (numberPrefix)
|
|
@@ -2573,7 +2679,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2573
2679
|
// other column on this page so body text under the opener band
|
|
2574
2680
|
// starts below it in ALL columns, not just the one it was placed in.
|
|
2575
2681
|
if (vdtType === 'heading' && headingLevel !== undefined) {
|
|
2576
|
-
const lvl =
|
|
2682
|
+
const lvl = headingLevels.forBlock(rawBlock);
|
|
2577
2683
|
if (lvl?.span === 'page') {
|
|
2578
2684
|
const page = doc.pages[cursor.pageIndex];
|
|
2579
2685
|
for (const otherCol of page.columns) {
|
|
@@ -2633,6 +2739,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2633
2739
|
if (letterSpacingPx !== undefined)
|
|
2634
2740
|
blk.letterSpacing = letterSpacingPx;
|
|
2635
2741
|
blk.contentIndex = blockIdx;
|
|
2742
|
+
stampBlockExtras(blk, rawBlock);
|
|
2636
2743
|
if (partIndex === 0) {
|
|
2637
2744
|
blk.headingLevel = headingLevel;
|
|
2638
2745
|
if (numberPrefix)
|
|
@@ -2654,7 +2761,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2654
2761
|
remainingLines = remainingLines.slice(choice.splitAt);
|
|
2655
2762
|
partIndex++;
|
|
2656
2763
|
pendingSpacing = 0;
|
|
2657
|
-
advanceToNextColumn(doc, cursor,
|
|
2764
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2658
2765
|
continue;
|
|
2659
2766
|
}
|
|
2660
2767
|
// choice.splitAt === 0: fall through to push whole paragraph to next column
|
|
@@ -2679,12 +2786,12 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2679
2786
|
if (rolledBack.length > 0) {
|
|
2680
2787
|
blockIdx = (rolledBack[0].contentIndex ?? blockIdx - rolledBack.length) - 1;
|
|
2681
2788
|
pendingSpacing = 0;
|
|
2682
|
-
advanceToNextColumn(doc, cursor,
|
|
2789
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2683
2790
|
break;
|
|
2684
2791
|
}
|
|
2685
2792
|
}
|
|
2686
2793
|
pendingSpacing = 0;
|
|
2687
|
-
advanceToNextColumn(doc, cursor,
|
|
2794
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2688
2795
|
continue;
|
|
2689
2796
|
}
|
|
2690
2797
|
// Empty column with less than a line of room (a band cap cutting right
|
|
@@ -2692,7 +2799,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2692
2799
|
// go here — move on. The next column, or a fresh page, has room.
|
|
2693
2800
|
if (curCol.availableHeight < style.lineHeightPx - 0.01 && totalRemainHeight > curCol.availableHeight + 0.01) {
|
|
2694
2801
|
pendingSpacing = 0;
|
|
2695
|
-
advanceToNextColumn(doc, cursor,
|
|
2802
|
+
advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
|
|
2696
2803
|
continue;
|
|
2697
2804
|
}
|
|
2698
2805
|
// Empty column but block still doesn't fit (block taller than page) — place anyway
|
|
@@ -2702,6 +2809,7 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2702
2809
|
if (letterSpacingPx !== undefined)
|
|
2703
2810
|
blk.letterSpacing = letterSpacingPx;
|
|
2704
2811
|
blk.contentIndex = blockIdx;
|
|
2812
|
+
stampBlockExtras(blk, rawBlock);
|
|
2705
2813
|
if (partIndex === 0)
|
|
2706
2814
|
blk.headingLevel = headingLevel;
|
|
2707
2815
|
if (partIndex === 0 && vdtType === 'heading' && rawBlock.attrs)
|
|
@@ -2750,12 +2858,43 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
|
|
|
2750
2858
|
page.pageLabel = info.label;
|
|
2751
2859
|
page.pageNumberFormat = info.format;
|
|
2752
2860
|
}
|
|
2753
|
-
|
|
2861
|
+
const restarts = pageNumberSegments
|
|
2862
|
+
.filter((s, i) => i > 0 && s.startAt !== undefined && s.startPageIndex < doc.pages.length)
|
|
2863
|
+
.map((s) => s.startPageIndex);
|
|
2864
|
+
if (restarts.length > 0)
|
|
2865
|
+
doc.pageNumberRestarts = restarts;
|
|
2866
|
+
buildHeadersAndFooters(doc, resourceById);
|
|
2754
2867
|
doc.converged = true;
|
|
2755
2868
|
doc.iterationCount = 1;
|
|
2756
2869
|
return { doc, forcedBreakPages, bandCapProposals, spanPlacedInBand, bandCapsApplied, looseOutcome };
|
|
2757
2870
|
}
|
|
2871
|
+
/** Passes a document printing its own contents gets at most, beyond the
|
|
2872
|
+
* first, for the page labels it prints to settle. */
|
|
2873
|
+
const MAX_TOC_ROUNDS = 3;
|
|
2758
2874
|
export function buildDocument(content, config, cache, options) {
|
|
2875
|
+
// A document printing its own table of contents (`:::toc` with no
|
|
2876
|
+
// host-supplied outline) is laid out with the page labels of the previous
|
|
2877
|
+
// build until they no longer change: the contents' own length moves what
|
|
2878
|
+
// follows, and a numbering restart after the front matter usually settles
|
|
2879
|
+
// it in one extra round.
|
|
2880
|
+
if (content.outline === undefined) {
|
|
2881
|
+
const parsed = parseMarkdownMemo(extractFrontmatter(content.markdown).content);
|
|
2882
|
+
if (hasTocDirective(parsed)) {
|
|
2883
|
+
let outline = computeOutline(parsed, resolveAllConfig(config), content.continuation?.headings);
|
|
2884
|
+
let doc = buildDocumentBalanced({ ...content, outline }, config, cache, options);
|
|
2885
|
+
for (let round = 0; round < MAX_TOC_ROUNDS; round++) {
|
|
2886
|
+
const after = outlineFromDoc(doc, outline);
|
|
2887
|
+
if (sameOutline(after, outline))
|
|
2888
|
+
break;
|
|
2889
|
+
outline = after;
|
|
2890
|
+
doc = buildDocumentBalanced({ ...content, outline }, config, cache, options);
|
|
2891
|
+
}
|
|
2892
|
+
return doc;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
return buildDocumentBalanced(content, config, cache, options);
|
|
2896
|
+
}
|
|
2897
|
+
function buildDocumentBalanced(content, config, cache, options) {
|
|
2759
2898
|
// Each pass reports its own progress, numbered in build order.
|
|
2760
2899
|
let passIndex = 0;
|
|
2761
2900
|
const onProgress = options?.onProgress;
|