postext 1.40.0 → 1.41.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 (67) hide show
  1. package/dist/__tests__/composed/crossChapterJumps.test.d.ts +2 -0
  2. package/dist/__tests__/composed/crossChapterJumps.test.d.ts.map +1 -0
  3. package/dist/__tests__/composed/crossChapterJumps.test.js +299 -0
  4. package/dist/__tests__/composed/crossChapterJumps.test.js.map +1 -0
  5. package/dist/__tests__/exports.test.js +3 -0
  6. package/dist/__tests__/exports.test.js.map +1 -1
  7. package/dist/bundle/book.d.ts +5 -0
  8. package/dist/bundle/book.d.ts.map +1 -1
  9. package/dist/bundle/book.js +53 -7
  10. package/dist/bundle/book.js.map +1 -1
  11. package/dist/bundle/layoutCache.d.ts +5 -0
  12. package/dist/bundle/layoutCache.d.ts.map +1 -1
  13. package/dist/bundle/layoutCache.js.map +1 -1
  14. package/dist/composed/carry.d.ts +53 -0
  15. package/dist/composed/carry.d.ts.map +1 -0
  16. package/dist/composed/carry.js +114 -0
  17. package/dist/composed/carry.js.map +1 -0
  18. package/dist/composed/carryTypes.d.ts +76 -0
  19. package/dist/composed/carryTypes.d.ts.map +1 -0
  20. package/dist/composed/carryTypes.js +2 -0
  21. package/dist/composed/carryTypes.js.map +1 -0
  22. package/dist/composed/continuation.d.ts +8 -0
  23. package/dist/composed/continuation.d.ts.map +1 -1
  24. package/dist/composed/continuation.js.map +1 -1
  25. package/dist/composed/sourceMap.d.ts +16 -0
  26. package/dist/composed/sourceMap.d.ts.map +1 -1
  27. package/dist/composed/sourceMap.js +36 -8
  28. package/dist/composed/sourceMap.js.map +1 -1
  29. package/dist/index.d.ts +2 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/pipeline/build.d.ts +12 -0
  34. package/dist/pipeline/build.d.ts.map +1 -1
  35. package/dist/pipeline/build.js +237 -15
  36. package/dist/pipeline/build.js.map +1 -1
  37. package/dist/pipeline/calloutLayout.d.ts +2 -17
  38. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  39. package/dist/pipeline/calloutLayout.js.map +1 -1
  40. package/dist/pipeline/composedLayout.d.ts +12 -0
  41. package/dist/pipeline/composedLayout.d.ts.map +1 -1
  42. package/dist/pipeline/composedLayout.js +76 -1
  43. package/dist/pipeline/composedLayout.js.map +1 -1
  44. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  45. package/dist/pipeline/contentWarnings.js +5 -3
  46. package/dist/pipeline/contentWarnings.js.map +1 -1
  47. package/dist/pipeline/continuation.d.ts +11 -0
  48. package/dist/pipeline/continuation.d.ts.map +1 -1
  49. package/dist/pipeline/continuation.js +36 -0
  50. package/dist/pipeline/continuation.js.map +1 -1
  51. package/dist/pipeline/cutTypes.d.ts +42 -0
  52. package/dist/pipeline/cutTypes.d.ts.map +1 -0
  53. package/dist/pipeline/cutTypes.js +6 -0
  54. package/dist/pipeline/cutTypes.js.map +1 -0
  55. package/dist/pipeline/frameLayout.d.ts +3 -21
  56. package/dist/pipeline/frameLayout.d.ts.map +1 -1
  57. package/dist/pipeline/frameLayout.js.map +1 -1
  58. package/dist/pipeline/index.d.ts +2 -1
  59. package/dist/pipeline/index.d.ts.map +1 -1
  60. package/dist/pipeline/index.js +2 -1
  61. package/dist/pipeline/index.js.map +1 -1
  62. package/dist/types.d.ts +16 -0
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/vdt.d.ts +36 -0
  65. package/dist/vdt.d.ts.map +1 -1
  66. package/dist/vdt.js.map +1 -1
  67. package/package.json +1 -1
@@ -44,7 +44,8 @@ import { layoutComposedSpread } from '../composed/spread';
44
44
  import { assignJumpReceivers, assignReceivers, continuationTarget, MAX_CONTINUATION_PAGES } from '../composed/continuation';
45
45
  import { frameElementId } from '../composed/ids';
46
46
  import { nextFrameOf } from '../composed/geometry';
47
- import { composedPageAt, frameStyleOf, layoutComposedPage, planComposedPages } from './composedLayout';
47
+ import { carriedPass, composedPageAt, frameStyleOf, layoutComposedPage, planComposedPages } from './composedLayout';
48
+ import { importJumps } from '../composed/carry';
48
49
  import { configKeyOf, layoutComposedPageMemo } from '../composed/pageMemo';
49
50
  import { anchorOutline, computeOutline, hasIndexDirective, hasTocDirective, headingNumberingOptions, headingTemplatesOf, outlineFromDoc, sameOutline } from './outline';
50
51
  import { expandTocDirectives } from './toc';
@@ -485,7 +486,9 @@ function placeDocumentPass(content, config, cache, options, hints) {
485
486
  const anchorRefs = hasAnchorRefs(parsedBlocks, resourceIds);
486
487
  const outline = content.outline
487
488
  ?? (hasTocDirective(parsedBlocks) || hasIndexDirective(parsedBlocks) || anchorRefs ? computeOutline(parsedBlocks, resolved, headingStart, continuation?.statementCounters) : undefined);
488
- const anchorRefContext = anchorRefs
489
+ // A story carried in from another chapter (#943) may refer to anchors too.
490
+ const carriedRefs = continuation?.jumps?.some((j) => j.outline) === true;
491
+ const anchorRefContext = anchorRefs || carriedRefs
489
492
  ? { targets: anchorTargetsOf(outline), strings: resolved.crossRefs }
490
493
  : undefined;
491
494
  const indexExpanded = expandIndexDirectives(expandTocDirectives(parsedBlocks, outline, resolved), outline, resolved);
@@ -518,6 +521,15 @@ function placeDocumentPass(content, config, cache, options, hints) {
518
521
  // callout machinery places and splits.
519
522
  const codeBoxes = wrapCodeBlocks(spanEmbeds.blocks, resolved);
520
523
  const contentBlocks = codeBoxes.blocks;
524
+ // Stories of earlier chapters that jump into a page of this one (#943):
525
+ // appended past the chapter's blocks by the composed pages' layout, with
526
+ // the notes they cite numbered as their chapter numbered them.
527
+ const carried = continuation?.jumps && continuation.jumps.length > 0 ? importJumps(continuation.jumps, contentBlocks.length, bodyOffset) : undefined;
528
+ for (const note of carried?.footnotes ?? []) {
529
+ footnoteNumbering.numbers.set(note.id, note.number);
530
+ if (note.def)
531
+ footnoteDefs.set(note.id, note.def);
532
+ }
521
533
  if (indexExpanded.warnings.length > 0)
522
534
  indexWarnings.set(doc, indexExpanded.warnings);
523
535
  const isNumbered = (b) => headingIsNumbered(b, resolved);
@@ -5997,6 +6009,93 @@ function placeDocumentPass(content, config, cache, options, hints) {
5997
6009
  }
5998
6010
  /** Stories waiting for the later page fence they jump to, by its id. */
5999
6011
  const jumps = new Map();
6012
+ /** The closing marker of the fence each composed page was set from. */
6013
+ const fenceEnd = new Map();
6014
+ /** The carried stories are laid through a pass that answers for them;
6015
+ * they wait for their fence with the others. */
6016
+ const composedPass = carried ? carriedPass(framePass, carried) : framePass;
6017
+ /** Stories of this chapter that jump into a page of a later one (#943). */
6018
+ const pendingJumps = [];
6019
+ /** The rest of the story `c` cut, as the chapter it goes to needs it
6020
+ * (#943): its box and children as this chapter has them ready to lay,
6021
+ * the cut, and the numbers the lookups by position would give. */
6022
+ const exportJump = (c, page) => {
6023
+ const startIdx = c.story.startIdx;
6024
+ const again = carried?.stories.get(startIdx);
6025
+ const all = composedPass.contentBlocks;
6026
+ const blocks = all.slice(startIdx, c.story.plan.endIdx + 1);
6027
+ const numbers = again ? [...(again.jump.numbers ?? [])] : [];
6028
+ if (!again) {
6029
+ for (let i = startIdx; i <= c.story.plan.endIdx; i++) {
6030
+ if (headingPrefixes[i] === undefined && headingNumbers[i] === undefined)
6031
+ continue;
6032
+ numbers.push({
6033
+ at: i - startIdx,
6034
+ ...(headingPrefixes[i] !== undefined ? { prefix: headingPrefixes[i] } : {}),
6035
+ ...(headingNumbers[i] !== undefined ? { value: headingNumbers[i] } : {}),
6036
+ ...(chapterNumberByBlock[i] ? { chapterNumber: chapterNumberByBlock[i] } : {}),
6037
+ });
6038
+ }
6039
+ }
6040
+ const notes = [];
6041
+ const seen = new Set();
6042
+ for (const b of blocks) {
6043
+ for (const span of b.spans) {
6044
+ const id = span.footnote?.id;
6045
+ if (id === undefined || seen.has(id))
6046
+ continue;
6047
+ seen.add(id);
6048
+ const number = footnoteNumbering.numbers.get(id);
6049
+ if (number === undefined)
6050
+ continue;
6051
+ const def = footnoteDefs.get(id);
6052
+ notes.push({ id, number, ...(def ? { def } : {}) });
6053
+ }
6054
+ }
6055
+ const rule = c.rule;
6056
+ const frame = continueInPage(rule)?.frame;
6057
+ return {
6058
+ ...(again ? { chapter: again.chapter } : {}),
6059
+ target: page,
6060
+ ...(frame !== undefined ? { targetFrame: frame } : {}),
6061
+ rule,
6062
+ frame: c.frame,
6063
+ fromFrameId: c.fromFrameId,
6064
+ head: c.head,
6065
+ template: c.template,
6066
+ collapsed: c.collapsed,
6067
+ slot: c.slot,
6068
+ pieces: c.pieces,
6069
+ headBlockId: c.headBlockId,
6070
+ ...(c.trailing ? { trailing: c.trailing } : {}),
6071
+ ...(c.fromMark !== undefined ? { fromMark: c.fromMark } : {}),
6072
+ cut: c.cut,
6073
+ blocks: blocks,
6074
+ bodyOffset,
6075
+ ...(numbers.length > 0 ? { numbers } : {}),
6076
+ orderedLevelIndentsPx: again?.jump.orderedLevelIndentsPx ?? orderedLevelIndentsPx,
6077
+ ...(notes.length > 0 ? { footnotes: notes } : {}),
6078
+ ...(hasAnchorRefs(blocks, new Set((content.resources ?? []).map((r) => r.id))) ? { outline: true } : {}),
6079
+ };
6080
+ };
6081
+ for (const [id, list] of carried?.byTarget ?? []) {
6082
+ if (fenceById.has(id)) {
6083
+ jumps.set(id, [...(jumps.get(id) ?? []), ...list]);
6084
+ continue;
6085
+ }
6086
+ // The host named this chapter for a page it does not have.
6087
+ for (const c of list) {
6088
+ composedNotes.push({
6089
+ kind: 'continueTargetMissing',
6090
+ template: c.template,
6091
+ frame: c.frame,
6092
+ fenceIndex: -1,
6093
+ target: id,
6094
+ fromChapter: c.foreign?.chapter ?? -1,
6095
+ configPath: '',
6096
+ });
6097
+ }
6098
+ }
6000
6099
  /** Set the composed page `planned` on a fresh page (#855), or a spread
6001
6100
  * template on the two facing pages from the next verso (#861). */
6002
6101
  const placeComposedPage = (planned, fence) => {
@@ -6010,7 +6109,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
6010
6109
  * other templates of the same kind (single pages for this side, or
6011
6110
  * spreads). */
6012
6111
  const common = (page, current) => ({
6013
- pass: framePass,
6112
+ pass: composedPass,
6014
6113
  fence,
6015
6114
  startIdx: planned.startIdx,
6016
6115
  endIdx: planned.endIdx,
@@ -6143,6 +6242,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
6143
6242
  const commit = (list) => {
6144
6243
  for (const { page, composed, blocks } of list) {
6145
6244
  page.composed = composed;
6245
+ fenceEnd.set(composed, planned.endIdx);
6146
6246
  const floats = (page.floats ??= []);
6147
6247
  for (const b of blocks) {
6148
6248
  floats.push(b);
@@ -6164,12 +6264,7 @@ function placeDocumentPass(content, config, cache, options, hints) {
6164
6264
  indexFrames(placed);
6165
6265
  /** The sending frame of `c` points to the frame `to` that took it, and
6166
6266
  * its page's log says where it went. */
6167
- const link = (c, to) => {
6168
- const src = frameAt.get(c.fromFrameId);
6169
- const dst = frameAt.get(to);
6170
- if (!src || !dst)
6171
- return;
6172
- src.frame.continuesTo = to;
6267
+ const logContinued = (src, c, event) => {
6173
6268
  const log = src.page.composed.log;
6174
6269
  let at = log.length;
6175
6270
  for (let i = log.length - 1; i >= 0; i--) {
@@ -6178,7 +6273,19 @@ function placeDocumentPass(content, config, cache, options, hints) {
6178
6273
  break;
6179
6274
  }
6180
6275
  }
6181
- log.splice(at, 0, { frame: c.frame, kind: 'continued', toPage: dst.page.index, toFrame: to });
6276
+ log.splice(at, 0, { frame: c.frame, kind: 'continued', ...event });
6277
+ };
6278
+ const link = (c, to) => {
6279
+ // The sending frame is on a page of another chapter: the host links
6280
+ // the two (`resolveJumpMarks`).
6281
+ if (c.foreign)
6282
+ return;
6283
+ const src = frameAt.get(c.fromFrameId);
6284
+ const dst = frameAt.get(to);
6285
+ if (!src || !dst)
6286
+ return;
6287
+ src.frame.continuesTo = to;
6288
+ logContinued(src, c, { toPage: dst.page.index, toFrame: to });
6182
6289
  };
6183
6290
  for (const [name, c] of jumpReceivers)
6184
6291
  link(c, frameElementId(fence.fenceIndex, name));
@@ -6201,6 +6308,16 @@ function placeDocumentPass(content, config, cache, options, hints) {
6201
6308
  continue;
6202
6309
  }
6203
6310
  const from = frameAt.get(c.fromFrameId);
6311
+ // A page of a later chapter (#943): the host hands the rest over.
6312
+ const later = !to && content.laterPageIds && Object.prototype.hasOwnProperty.call(content.laterPageIds, target.page) ? content.laterPageIds[target.page] : undefined;
6313
+ if (later !== undefined) {
6314
+ pendingJumps.push(exportJump(c, target.page));
6315
+ if (from) {
6316
+ from.frame.continuesToChapter = later;
6317
+ logContinued(from, c, { toChapter: later });
6318
+ }
6319
+ continue;
6320
+ }
6204
6321
  composedNotes.push({
6205
6322
  kind: 'continueTargetMissing',
6206
6323
  template: c.template,
@@ -8699,6 +8816,10 @@ function placeDocumentPass(content, config, cache, options, hints) {
8699
8816
  // Composed pages (#855): frames whose content does not fit.
8700
8817
  if (composedNotes.length > 0)
8701
8818
  composedWarningsByDoc.set(doc, composedNotes);
8819
+ if (pendingJumps.length > 0)
8820
+ doc.pendingJumps = pendingJumps;
8821
+ if (carried)
8822
+ stampCarriedSource(doc, carried, fenceEnd);
8702
8823
  // The composition rules (#749): short lists cut, floats on opener pages.
8703
8824
  const ruleNotes = [...shortListWarnings(), ...openerFloatNotes, ...tocGroupWarnings()];
8704
8825
  if (ruleNotes.length > 0)
@@ -8867,6 +8988,39 @@ function afterTextWarnings(doc) {
8867
8988
  }
8868
8989
  return out;
8869
8990
  }
8991
+ /**
8992
+ * Mark the blocks laid from stories that came in from other chapters
8993
+ * (#943): `VDTBlock.sourceChapter` says whose file their source offsets
8994
+ * are offsets in.
8995
+ */
8996
+ function stampCarriedSource(doc, carried, fenceEnd) {
8997
+ const stories = [...carried.stories.values()];
8998
+ const noteChapter = new Map(carried.footnotes.map((n) => [n.id, n.chapter]));
8999
+ for (const block of doc.blocks) {
9000
+ const note = block.footnoteNote !== undefined ? noteChapter.get(block.footnoteNote) : undefined;
9001
+ if (note !== undefined)
9002
+ block.sourceChapter = note;
9003
+ const at = block.contentIndex;
9004
+ if (at === undefined)
9005
+ continue;
9006
+ const story = stories.find((st) => at >= st.startIdx && at <= st.endIdx);
9007
+ if (story)
9008
+ block.sourceChapter = story.chapter;
9009
+ }
9010
+ // The frames that set a carried story's text: the box (a block of the
9011
+ // frame) says whose it is.
9012
+ for (const page of doc.pages) {
9013
+ for (const frame of page.composed?.frames ?? []) {
9014
+ const box = frame.blockIds.length > 0 ? page.floats?.find((b) => b.id === frame.blockIds[0]) : undefined;
9015
+ if (box?.sourceChapter === undefined)
9016
+ continue;
9017
+ frame.sourceChapter = box.sourceChapter;
9018
+ const end = fenceEnd.get(page.composed);
9019
+ if (end !== undefined)
9020
+ frame.readIndex = end + 0.5;
9021
+ }
9022
+ }
9023
+ }
8870
9024
  /**
8871
9025
  * Write the page numbers into the continuation marks of composed pages'
8872
9026
  * frames (#917, `VDTDesignTextBlock.pageMark`): `{page}` becomes the label
@@ -8880,6 +9034,22 @@ function fillPageMarks(pages) {
8880
9034
  for (const f of p.composed?.frames ?? [])
8881
9035
  if (!f.facingBlocks)
8882
9036
  frames.set(f.id, p);
9037
+ forEachPageMark(pages, (f, d) => {
9038
+ // A page of another chapter is `resolveJumpMarks`'s (its frame ids are
9039
+ // another chapter's).
9040
+ if ((d.pageMark.page === 'to' ? f.continuesToChapter : f.continuedFromChapter) !== undefined)
9041
+ return undefined;
9042
+ const other = d.pageMark.page === 'to' ? f.continuesTo : f.continuedFrom;
9043
+ const at = other !== undefined ? frames.get(other) : undefined;
9044
+ return at ? pageLabelOf(at) : undefined;
9045
+ });
9046
+ }
9047
+ /** The label a page prints. */
9048
+ const pageLabelOf = (page) => page.pageLabel ?? String(page.pageNumberValue ?? page.index + 1);
9049
+ /** Each continuation mark with a page number of `pages`' composed frames,
9050
+ * with `label` answering for the page it names: the mark's text is laid
9051
+ * out again with the number when it answers. */
9052
+ function forEachPageMark(pages, label) {
8883
9053
  for (const p of pages) {
8884
9054
  for (const f of p.composed?.frames ?? []) {
8885
9055
  for (const id of f.blockIds) {
@@ -8887,12 +9057,10 @@ function fillPageMarks(pages) {
8887
9057
  for (const d of box?.designOverlay?.blocks ?? []) {
8888
9058
  if (d.kind !== 'text' || !d.pageMark)
8889
9059
  continue;
8890
- const other = d.pageMark.page === 'to' ? f.continuesTo : f.continuedFrom;
8891
- const at = other !== undefined ? frames.get(other) : undefined;
8892
- if (!at)
9060
+ const shown = label(f, d);
9061
+ if (shown === undefined)
8893
9062
  continue;
8894
- const label = at.pageLabel ?? String(at.pageNumberValue ?? at.index + 1);
8895
- const text = d.pageMark.text.split('{page}').join(label);
9063
+ const text = d.pageMark.text.split('{page}').join(shown);
8896
9064
  const lh = d.lines.length > 0 ? d.bbox.height / d.lines.length : d.bbox.height;
8897
9065
  const first = d.lines[0];
8898
9066
  const slack0 = first ? d.bbox.width - first.width : 0;
@@ -8910,6 +9078,60 @@ function fillPageMarks(pages) {
8910
9078
  }
8911
9079
  }
8912
9080
  }
9081
+ /**
9082
+ * Link the stories that jump from one chapter to a page of another (#943)
9083
+ * and write the page numbers into their continuation marks. `docs` is the
9084
+ * book laid out, one document per chapter, in the order of the book (a
9085
+ * story's `chapter` is its position in it): the sending frame gets the
9086
+ * frame that took the rest (`continuesTo`, its `continued` event the page
9087
+ * and frame there), and every mark naming a page of another chapter
9088
+ * prints that page's label as its chapter numbered it. Marks within a
9089
+ * chapter were filled by its own build. Safe to run again on the same
9090
+ * documents.
9091
+ */
9092
+ export function resolveJumpMarks(docs) {
9093
+ const byId = docs.map((doc) => {
9094
+ const out = new Map();
9095
+ for (const p of doc.pages)
9096
+ for (const f of p.composed?.frames ?? [])
9097
+ if (!f.facingBlocks)
9098
+ out.set(f.id, p);
9099
+ return out;
9100
+ });
9101
+ const frameOf = (chapter, id) => {
9102
+ const page = byId[chapter]?.get(id);
9103
+ const frame = page?.composed?.frames.find((f) => f.id === id);
9104
+ return page && frame ? { page, frame } : undefined;
9105
+ };
9106
+ // The receiving frames name where their story came from; give the
9107
+ // sending frame the way on.
9108
+ docs.forEach((doc) => {
9109
+ for (const p of doc.pages) {
9110
+ for (const f of p.composed?.frames ?? []) {
9111
+ if (f.continuedFromChapter === undefined || f.continuedFrom === undefined)
9112
+ continue;
9113
+ const from = frameOf(f.continuedFromChapter, f.continuedFrom);
9114
+ if (!from)
9115
+ continue;
9116
+ from.frame.continuesTo = f.id;
9117
+ const event = from.page.composed.log.find((e) => e.kind === 'continued' && e.frame === from.frame.name && e.toChapter !== undefined);
9118
+ if (event) {
9119
+ event.toPage = p.index;
9120
+ event.toFrame = f.id;
9121
+ }
9122
+ }
9123
+ }
9124
+ });
9125
+ docs.forEach((doc) => {
9126
+ forEachPageMark(doc.pages, (f, d) => {
9127
+ const [chapter, id] = d.pageMark.page === 'to' ? [f.continuesToChapter, f.continuesTo] : [f.continuedFromChapter, f.continuedFrom];
9128
+ if (chapter === undefined || id === undefined)
9129
+ return undefined;
9130
+ const at = byId[chapter]?.get(id);
9131
+ return at ? pageLabelOf(at) : undefined;
9132
+ });
9133
+ });
9134
+ }
8913
9135
  /** `doc` with the page of each of its index marks (`doc.indexMarks`). */
8914
9136
  function withIndexMarks(doc, content) {
8915
9137
  const { content: body, contentOffset } = extractFrontmatter(content.markdown);