postext 1.9.2 → 1.11.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.
Files changed (41) hide show
  1. package/dist/__tests__/pipeline/calloutOverflow.test.js +25 -0
  2. package/dist/__tests__/pipeline/calloutOverflow.test.js.map +1 -1
  3. package/dist/__tests__/pipeline/footnotesCjk.test.d.ts +2 -0
  4. package/dist/__tests__/pipeline/footnotesCjk.test.d.ts.map +1 -0
  5. package/dist/__tests__/pipeline/footnotesCjk.test.js +206 -0
  6. package/dist/__tests__/pipeline/footnotesCjk.test.js.map +1 -0
  7. package/dist/defaults/footnotes.d.ts +2 -1
  8. package/dist/defaults/footnotes.d.ts.map +1 -1
  9. package/dist/defaults/footnotes.js +22 -2
  10. package/dist/defaults/footnotes.js.map +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/knuthPlass/richAdapter.d.ts +2 -0
  15. package/dist/knuthPlass/richAdapter.d.ts.map +1 -1
  16. package/dist/knuthPlass/richAdapter.js +1 -0
  17. package/dist/knuthPlass/richAdapter.js.map +1 -1
  18. package/dist/measure/cache.js +1 -1
  19. package/dist/measure/cache.js.map +1 -1
  20. package/dist/measure/rich.d.ts +6 -0
  21. package/dist/measure/rich.d.ts.map +1 -1
  22. package/dist/measure/rich.js +12 -1
  23. package/dist/measure/rich.js.map +1 -1
  24. package/dist/parse/types.d.ts +4 -1
  25. package/dist/parse/types.d.ts.map +1 -1
  26. package/dist/pipeline/build.d.ts +3 -0
  27. package/dist/pipeline/build.d.ts.map +1 -1
  28. package/dist/pipeline/build.js +84 -9
  29. package/dist/pipeline/build.js.map +1 -1
  30. package/dist/pipeline/config.js +1 -1
  31. package/dist/pipeline/config.js.map +1 -1
  32. package/dist/pipeline/footnotes.d.ts +34 -7
  33. package/dist/pipeline/footnotes.d.ts.map +1 -1
  34. package/dist/pipeline/footnotes.js +62 -14
  35. package/dist/pipeline/footnotes.js.map +1 -1
  36. package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
  37. package/dist/pipeline/measureContentBlock.js +20 -4
  38. package/dist/pipeline/measureContentBlock.js.map +1 -1
  39. package/dist/types.d.ts +30 -4
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +1 -1
@@ -23,7 +23,7 @@ import { chooseParagraphSplit } from './orphanWidow';
23
23
  import { applyStyleAttrs, computePageMetrics, pageMirrored, sheetRectToFlow, isMarkerBlock, nextNonMarkerBlock, spaceLinesAfter, prevNonMarkerBlock, rollbackTrailingBlocks, } from './buildHelpers';
24
24
  import { measureContentBlock } from './measureContentBlock';
25
25
  import { planParagraphContainers } from './paragraphContainers';
26
- import { appendChapterEndNotes, footnoteIdsOfLines, footnoteParagraphStyle, noteContentBlock, numberFootnotes, splitFootnoteDefinitions, withFootnoteStyle, } from './footnotes';
26
+ import { appendChapterEndNotes, footnoteIdsOfLines, footnoteParagraphStyle, noteContentBlock, numberFootnotes, numberFootnotesByPlacement, sameFootnoteNumbers, splitFootnoteDefinitions, withFootnoteStyle, } from './footnotes';
27
27
  import { planParts, derivePartMeasureContext } from './parts';
28
28
  import { layoutCallout, offsetCalloutToAbsolute, pickCalloutStyle, planCallouts, resolveCalloutAttrs, } from './calloutLayout';
29
29
  import { layoutResourceBlock, planTableSlice } from './resourceLayout';
@@ -273,10 +273,19 @@ function placeDocumentPass(content, config, cache, options, hints) {
273
273
  // come back as paragraphs after each chapter's last block.
274
274
  const footnoteSplit = splitFootnoteDefinitions(headingMarksFor(parseMarkdownMemo(markdownBody), resolved));
275
275
  const footnoteDefs = footnoteSplit.defs;
276
- const footnoteNumbering = numberFootnotes(footnoteSplit.blocks, resolved.footnotes.numbering, resolved.footnotes.numbering === 'document' ? Math.max(0, Math.floor(continuation?.footnoteNumber ?? 0)) : 0);
276
+ // Numbered by page or column (`numbering: 'page'`), a note takes the
277
+ // number the previous build gave it where it landed; the first build
278
+ // numbers by chapter (see `buildDocumentNumbered`).
279
+ const footnoteNumbering = numberFootnotes(footnoteSplit.blocks, resolved.footnotes.numbering, resolved.footnotes.numbering === 'document' ? Math.max(0, Math.floor(continuation?.footnoteNumber ?? 0)) : 0, resolved.footnotes.numberFormat);
280
+ if (hints.footnoteNumbers) {
281
+ for (const [id, number] of hints.footnoteNumbers) {
282
+ if (footnoteNumbering.numbers.has(id))
283
+ footnoteNumbering.numbers.set(id, number);
284
+ }
285
+ }
277
286
  const chapterEndNotes = resolved.footnotes.placement === 'chapterEnd' && footnoteNumbering.numbers.size > 0;
278
287
  const parsedBlocks = chapterEndNotes
279
- ? appendChapterEndNotes(footnoteSplit.blocks, footnoteNumbering, footnoteDefs)
288
+ ? appendChapterEndNotes(footnoteSplit.blocks, footnoteNumbering, footnoteDefs, resolved.footnotes.markerPosition)
280
289
  : footnoteSplit.blocks;
281
290
  const headingStart = continuation?.headings;
282
291
  // `:::toc` expands into the entries of the book's outline — the one the
@@ -1591,7 +1600,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
1591
1600
  let m = noteMeasures.get(key);
1592
1601
  if (m === undefined) {
1593
1602
  const number = footnoteNumbering.numbers.get(id) ?? '?';
1594
- m = measureContentBlock(noteContentBlock(footnoteDefs.get(id), id, number), 0, width, measureCtx, { styleOverride: noteStyle });
1603
+ m = measureContentBlock(noteContentBlock(footnoteDefs.get(id), id, number, resolved.footnotes.markerPosition), 0, width, measureCtx, { styleOverride: noteStyle });
1595
1604
  noteMeasures.set(key, m);
1596
1605
  }
1597
1606
  return m;
@@ -2819,6 +2828,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
2819
2828
  /** The box already moved to a fresh page (or sits on an empty one):
2820
2829
  * whatever does not fit there is force-placed and overflows. */
2821
2830
  let forceHere = false;
2831
+ /** Times the box left a fresh page whose room the floats flushed onto
2832
+ * it had taken — bounded. */
2833
+ let floatPageMoves = 0;
2822
2834
  for (;;) {
2823
2835
  let page = doc.pages[cursor.pageIndex];
2824
2836
  const continuation = part > 0;
@@ -2942,6 +2954,22 @@ function placeDocumentPass(content, config, cache, options, hints) {
2942
2954
  }
2943
2955
  }
2944
2956
  }
2957
+ // A fresh page whose room went to the floats flushed onto it (a
2958
+ // figure page, a page-span figure standing upright across a vertical
2959
+ // page) holds no text to cut: a box a page would hold moves on to the
2960
+ // next page rather than overflow this one.
2961
+ if (!action && forceHere && fit && floatPageMoves < 2
2962
+ && fit.spacing + result.totalHeight > fit.roomPx + 0.5
2963
+ && result.totalHeight <= contentArea.height + 0.01
2964
+ && !pageHasContent(page) && (page.floats?.length ?? 0) > 0) {
2965
+ floatPageMoves++;
2966
+ pendingSpacing = 0;
2967
+ const startPageIndex = cursor.pageIndex;
2968
+ do {
2969
+ advanceToNextColumn(doc, cursor, geomResolved, contentArea, pageWidthPx, pageHeightPx, onNewPage);
2970
+ } while (cursor.pageIndex === startPageIndex);
2971
+ continue;
2972
+ }
2945
2973
  if (!action && forceHere && fit) {
2946
2974
  action = { kind: 'whole', fit, need: fit.need, result };
2947
2975
  // A box that fills its band to the last grid line is not an overflow.
@@ -5309,21 +5337,68 @@ function* buildDocumentRounds(content, config, cache, options) {
5309
5337
  const parsed = parseMarkdownMemo(extractFrontmatter(content.markdown).content);
5310
5338
  if (hasTocDirective(parsed) || hasIndexDirective(parsed)) {
5311
5339
  let outline = computeOutline(parsed, resolveAllConfig(config), content.continuation?.headings);
5312
- let doc = withIndexMarks(yield* buildDocumentBalanced({ ...content, outline }, config, cache, options, 0), content);
5340
+ let doc = withIndexMarks(yield* buildDocumentNumbered({ ...content, outline }, config, cache, options, 0), content);
5313
5341
  for (let round = 0; round < MAX_TOC_ROUNDS; round++) {
5314
5342
  const after = outlineFromDoc(doc, outline);
5315
5343
  if (sameOutline(after, outline))
5316
5344
  break;
5317
5345
  outline = after;
5318
- doc = withIndexMarks(yield* buildDocumentBalanced({ ...content, outline }, config, cache, options, round + 1), content);
5346
+ doc = withIndexMarks(yield* buildDocumentNumbered({ ...content, outline }, config, cache, options, round + 1), content);
5319
5347
  }
5320
5348
  return doc;
5321
5349
  }
5322
5350
  }
5323
- return withIndexMarks(yield* buildDocumentBalanced(content, config, cache, options), content);
5351
+ return withIndexMarks(yield* buildDocumentNumbered(content, config, cache, options), content);
5352
+ }
5353
+ /** The number each note's marker prints in `doc` (its first marker). */
5354
+ function printedFootnoteNumbers(doc) {
5355
+ const out = new Map();
5356
+ for (const block of doc.blocks) {
5357
+ if (block.footnoteNote !== undefined)
5358
+ continue;
5359
+ for (const line of block.lines) {
5360
+ for (const seg of line.segments ?? []) {
5361
+ if (seg.footnoteId !== undefined && !out.has(seg.footnoteId))
5362
+ out.set(seg.footnoteId, seg.text);
5363
+ }
5364
+ }
5365
+ }
5366
+ return out;
5367
+ }
5368
+ /** Builds a document numbering its notes by page (or column) gets at most,
5369
+ * beyond the first, for the numbers to settle. */
5370
+ const MAX_FOOTNOTE_ROUNDS = 3;
5371
+ /**
5372
+ * {@link buildDocumentBalanced}, with `footnotes.numbering: 'page'` /
5373
+ * `'column'`: a note's number depends on the page the layout sets it on,
5374
+ * so the document is numbered by chapter, laid out, numbered again where
5375
+ * its notes landed, and laid out with those numbers until they no longer
5376
+ * change (as the contents' page labels are). A circled marker is the same
5377
+ * width whatever its number, so the second build reflows nothing and the
5378
+ * numbers hold; a decimal one may move a line (9 → 10), hence the bound.
5379
+ */
5380
+ function* buildDocumentNumbered(content, config, cache, options, tocRound = 0) {
5381
+ let doc = yield* buildDocumentBalanced(content, config, cache, options, tocRound);
5382
+ const f = doc.config.footnotes;
5383
+ if (f.placement !== 'column' || (f.numbering !== 'page' && f.numbering !== 'column'))
5384
+ return doc;
5385
+ for (let round = 0; round < MAX_FOOTNOTE_ROUNDS; round++) {
5386
+ const used = printedFootnoteNumbers(doc);
5387
+ if (used.size === 0)
5388
+ break;
5389
+ const placed = numberFootnotesByPlacement(doc.pages, f.numbering, f.numberFormat).numbers;
5390
+ // A note the layout set nowhere keeps the number it had.
5391
+ const next = new Map(used);
5392
+ for (const [id, number] of placed)
5393
+ next.set(id, number);
5394
+ if (sameFootnoteNumbers(next, used))
5395
+ break;
5396
+ doc = yield* buildDocumentBalanced(content, config, cache, options, tocRound, next);
5397
+ }
5398
+ return doc;
5324
5399
  }
5325
5400
  const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
5326
- function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
5401
+ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0, footnoteNumbers) {
5327
5402
  // Each pass reports its own progress, numbered in build order.
5328
5403
  let passIndex = 0;
5329
5404
  const onProgress = options?.onProgress;
@@ -5337,7 +5412,7 @@ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
5337
5412
  const runPass = (hints) => {
5338
5413
  passIndex++;
5339
5414
  const started = onPass ? now() : 0;
5340
- const result = buildDocumentPass(content, config, cache, passOptions, { ...hints, captionUnder });
5415
+ const result = buildDocumentPass(content, config, cache, passOptions, { ...hints, captionUnder, ...(footnoteNumbers ? { footnoteNumbers } : {}) });
5341
5416
  for (const id of result.captionUnderProposals)
5342
5417
  captionUnder.add(id);
5343
5418
  onPass?.({ pass: passIndex, tocRound, ms: now() - started, pages: result.doc.pages.length });