postext 0.3.39 → 0.3.41

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 (66) hide show
  1. package/dist/__bench__/chapter.bench.test.d.ts +2 -0
  2. package/dist/__bench__/chapter.bench.test.d.ts.map +1 -0
  3. package/dist/__bench__/chapter.bench.test.js +80 -0
  4. package/dist/__bench__/chapter.bench.test.js.map +1 -0
  5. package/dist/__tests__/buildAsync.test.d.ts +2 -0
  6. package/dist/__tests__/buildAsync.test.d.ts.map +1 -0
  7. package/dist/__tests__/buildAsync.test.js +50 -0
  8. package/dist/__tests__/buildAsync.test.js.map +1 -0
  9. package/dist/__tests__/hyphenateMemo.test.d.ts +2 -0
  10. package/dist/__tests__/hyphenateMemo.test.d.ts.map +1 -0
  11. package/dist/__tests__/hyphenateMemo.test.js +21 -0
  12. package/dist/__tests__/hyphenateMemo.test.js.map +1 -0
  13. package/dist/__tests__/knuthPlassMerge.test.d.ts +2 -0
  14. package/dist/__tests__/knuthPlassMerge.test.d.ts.map +1 -0
  15. package/dist/__tests__/knuthPlassMerge.test.js +55 -0
  16. package/dist/__tests__/knuthPlassMerge.test.js.map +1 -0
  17. package/dist/__tests__/memos.test.d.ts +2 -0
  18. package/dist/__tests__/memos.test.d.ts.map +1 -0
  19. package/dist/__tests__/memos.test.js +37 -0
  20. package/dist/__tests__/memos.test.js.map +1 -0
  21. package/dist/canvas-backend/index.d.ts +5 -0
  22. package/dist/canvas-backend/index.d.ts.map +1 -1
  23. package/dist/canvas-backend/index.js +7 -4
  24. package/dist/canvas-backend/index.js.map +1 -1
  25. package/dist/hyphenate.d.ts.map +1 -1
  26. package/dist/hyphenate.js +50 -1
  27. package/dist/hyphenate.js.map +1 -1
  28. package/dist/knuthPlass/breakpoints.d.ts.map +1 -1
  29. package/dist/knuthPlass/breakpoints.js +7 -1
  30. package/dist/knuthPlass/breakpoints.js.map +1 -1
  31. package/dist/knuthPlass/types.d.ts +8 -0
  32. package/dist/knuthPlass/types.d.ts.map +1 -1
  33. package/dist/measure/plain.d.ts.map +1 -1
  34. package/dist/measure/plain.js +1 -0
  35. package/dist/measure/plain.js.map +1 -1
  36. package/dist/measure/rich.d.ts.map +1 -1
  37. package/dist/measure/rich.js +1 -0
  38. package/dist/measure/rich.js.map +1 -1
  39. package/dist/parse/blockParser.d.ts +5 -3
  40. package/dist/parse/blockParser.d.ts.map +1 -1
  41. package/dist/parse/blockParser.js +32 -21
  42. package/dist/parse/blockParser.js.map +1 -1
  43. package/dist/pipeline/bandCaps.d.ts +16 -0
  44. package/dist/pipeline/bandCaps.d.ts.map +1 -1
  45. package/dist/pipeline/bandCaps.js +22 -0
  46. package/dist/pipeline/bandCaps.js.map +1 -1
  47. package/dist/pipeline/build.d.ts +33 -0
  48. package/dist/pipeline/build.d.ts.map +1 -1
  49. package/dist/pipeline/build.js +63 -12
  50. package/dist/pipeline/build.js.map +1 -1
  51. package/dist/pipeline/config.d.ts.map +1 -1
  52. package/dist/pipeline/config.js +18 -0
  53. package/dist/pipeline/config.js.map +1 -1
  54. package/dist/pipeline/index.d.ts +2 -2
  55. package/dist/pipeline/index.d.ts.map +1 -1
  56. package/dist/pipeline/index.js +1 -1
  57. package/dist/pipeline/index.js.map +1 -1
  58. package/dist/worker/client.d.ts +18 -2
  59. package/dist/worker/client.d.ts.map +1 -1
  60. package/dist/worker/client.js +28 -3
  61. package/dist/worker/client.js.map +1 -1
  62. package/dist/worker/layout.worker.js +135 -56
  63. package/dist/worker/layout.worker.js.map +1 -1
  64. package/dist/worker/protocol.d.ts +27 -2
  65. package/dist/worker/protocol.d.ts.map +1 -1
  66. package/package.json +3 -2
@@ -26,7 +26,7 @@ import { computeHeadingContext, computeResourceNumbering, } from './resourceNumb
26
26
  import { defaultResourceTypes } from '../defaults/resourceTypes';
27
27
  import { buildHeadersAndFooters, measureHeadingAdvancedDesignHeight } from './headerFooter';
28
28
  import { proposeBalanceLines, collectColumnGaps, firstDivergentColumn, gapLinesIn, pageSegments, MAX_BALANCING_PASSES, MAX_BALANCING_PASSES_PER_DOCUMENT, balanceKey } from './columnBalancing';
29
- import { applyBandCap, uncapBand, columnBottom, bandCapLines, bandTop, resolveBandCaps, resolveTrailingCaps, bandCapLinesAroundZone, } from './bandCaps';
29
+ import { applyBandCap, uncapBand, columnBottom, bandCapLines, bandTop, resolveBandCapsGen, drainPasses, resolveTrailingCapsGen, bandCapLinesAroundZone, } from './bandCaps';
30
30
  import { raggedLooseLines } from './raggedLines';
31
31
  /** Tolerance for "does this block fit" checks against a column's free
32
32
  * height, absorbing floating-point drift between grid multiples. */
@@ -2923,6 +2923,48 @@ export function buildDocumentPass(content, config, cache, options, hints = {}) {
2923
2923
  * first, for the page labels it prints to settle. */
2924
2924
  const MAX_TOC_ROUNDS = 3;
2925
2925
  export function buildDocument(content, config, cache, options) {
2926
+ return drainPasses(buildDocumentGen(content, config, cache, options));
2927
+ }
2928
+ /** Let the event loop turn: `scheduler.yield()` where it exists, else a
2929
+ * message-channel hop (a `setTimeout(0)` is clamped to 4 ms once nested). */
2930
+ export function yieldToEventLoop() {
2931
+ const scheduler = globalThis.scheduler;
2932
+ if (scheduler?.yield)
2933
+ return scheduler.yield();
2934
+ if (typeof MessageChannel !== 'undefined') {
2935
+ return new Promise((resolve) => {
2936
+ const channel = new MessageChannel();
2937
+ channel.port1.onmessage = () => {
2938
+ channel.port1.close();
2939
+ resolve();
2940
+ };
2941
+ channel.port2.postMessage(null);
2942
+ });
2943
+ }
2944
+ return new Promise((resolve) => setTimeout(resolve, 0));
2945
+ }
2946
+ /**
2947
+ * {@link buildDocument}, yielding to the event loop between placement
2948
+ * passes. A build is several passes (band caps, column balancing, contents
2949
+ * rounds), each a synchronous re-placement of the document; in a worker
2950
+ * this lets a `cancel` posted mid-build be observed at the next pass
2951
+ * boundary — `shouldCancel` is polled there as well as inside a pass —
2952
+ * instead of after the whole build.
2953
+ */
2954
+ export async function buildDocumentAsync(content, config, cache, options) {
2955
+ const gen = buildDocumentGen(content, config, cache, options);
2956
+ const pause = options?.yieldBetweenPasses ?? yieldToEventLoop;
2957
+ for (;;) {
2958
+ const step = gen.next();
2959
+ if (step.done)
2960
+ return step.value;
2961
+ await pause();
2962
+ if (options?.shouldCancel?.())
2963
+ throw new BuildCancelledError();
2964
+ }
2965
+ }
2966
+ /** The build as a generator: one `yield` after every placement pass. */
2967
+ export function* buildDocumentGen(content, config, cache, options) {
2926
2968
  // A document printing its own table of contents (`:::toc` with no
2927
2969
  // host-supplied outline) is laid out with the page labels of the previous
2928
2970
  // build until they no longer change: the contents' own length moves what
@@ -2932,36 +2974,44 @@ export function buildDocument(content, config, cache, options) {
2932
2974
  const parsed = parseMarkdownMemo(extractFrontmatter(content.markdown).content);
2933
2975
  if (hasTocDirective(parsed)) {
2934
2976
  let outline = computeOutline(parsed, resolveAllConfig(config), content.continuation?.headings);
2935
- let doc = buildDocumentBalanced({ ...content, outline }, config, cache, options);
2977
+ let doc = yield* buildDocumentBalanced({ ...content, outline }, config, cache, options, 0);
2936
2978
  for (let round = 0; round < MAX_TOC_ROUNDS; round++) {
2937
2979
  const after = outlineFromDoc(doc, outline);
2938
2980
  if (sameOutline(after, outline))
2939
2981
  break;
2940
2982
  outline = after;
2941
- doc = buildDocumentBalanced({ ...content, outline }, config, cache, options);
2983
+ doc = yield* buildDocumentBalanced({ ...content, outline }, config, cache, options, round + 1);
2942
2984
  }
2943
2985
  return doc;
2944
2986
  }
2945
2987
  }
2946
- return buildDocumentBalanced(content, config, cache, options);
2988
+ return yield* buildDocumentBalanced(content, config, cache, options);
2947
2989
  }
2948
- function buildDocumentBalanced(content, config, cache, options) {
2990
+ const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
2991
+ function* buildDocumentBalanced(content, config, cache, options, tocRound = 0) {
2949
2992
  // Each pass reports its own progress, numbered in build order.
2950
2993
  let passIndex = 0;
2951
2994
  const onProgress = options?.onProgress;
2995
+ const onPass = options?.onPass;
2952
2996
  const passOptions = onProgress
2953
2997
  ? { ...options, onProgress: (p) => onProgress({ ...p, pass: passIndex }) }
2954
2998
  : options;
2955
2999
  const runPass = (hints) => {
2956
3000
  passIndex++;
2957
- return buildDocumentPass(content, config, cache, passOptions, hints);
3001
+ const started = onPass ? now() : 0;
3002
+ const result = buildDocumentPass(content, config, cache, passOptions, hints);
3003
+ onPass?.({ pass: passIndex, tocRound, ms: now() - started, pages: result.doc.pages.length });
3004
+ return result;
2958
3005
  };
3006
+ // The first pass is a pass too: let a cancel land before the caps run.
3007
+ const initial = runPass();
3008
+ yield;
2959
3009
  // --- Band caps (page-span blocks mid-page) -----------------------------
2960
3010
  // A span block that arrived in an uneven band proposes a cap; the driver
2961
3011
  // re-places the document with it (and grows / drops caps whose band
2962
3012
  // overflowed) before balancing runs. Documents without such blocks get
2963
3013
  // their first pass back untouched — no extra pass.
2964
- const bands = resolveBandCaps(runPass(), (bandCaps) => runPass({ bandCaps }));
3014
+ const bands = yield* resolveBandCapsGen(initial, (bandCaps) => runPass({ bandCaps }));
2965
3015
  let best = bands.result;
2966
3016
  const bandCaps = bands.bandCaps;
2967
3017
  let passCount = bands.passCount;
@@ -3154,7 +3204,7 @@ function buildDocumentBalanced(content, config, cache, options) {
3154
3204
  return blacklist(null);
3155
3205
  };
3156
3206
  let balancingPasses = 0;
3157
- const balance = () => {
3207
+ function* balance() {
3158
3208
  while (balancingPasses < MAX_BALANCING_PASSES_PER_DOCUMENT) {
3159
3209
  const active = segments.filter((s) => !s.done);
3160
3210
  if (active.length === 0)
@@ -3224,6 +3274,7 @@ function buildDocumentBalanced(content, config, cache, options) {
3224
3274
  const next = runPass(hintsFrom(proposal.lines, proposal.loose, proposal.looseBudget));
3225
3275
  passCount++;
3226
3276
  balancingPasses++;
3277
+ yield;
3227
3278
  const nextGaps = collectColumnGaps(next.doc, next.forcedBreakPages);
3228
3279
  const capPage = pageOfContent(best.doc);
3229
3280
  const accepted = [];
@@ -3287,9 +3338,9 @@ function buildDocumentBalanced(content, config, cache, options) {
3287
3338
  if (accepted.length > 0)
3288
3339
  best = spliceSegments(next, accepted);
3289
3340
  }
3290
- };
3341
+ }
3291
3342
  resetSegments();
3292
- balance();
3343
+ yield* balance();
3293
3344
  // --- Trailing bands (closing columns cut level) -------------------------
3294
3345
  // Once the balancing levers have settled the earlier pages, level the
3295
3346
  // closing band of every chapter / the document with a trailing cap. It is
@@ -3300,14 +3351,14 @@ function buildDocumentBalanced(content, config, cache, options) {
3300
3351
  // fill what the cut left short (a column ending a line under the cap).
3301
3352
  if (balancing.trailing) {
3302
3353
  const frozen = hintsFrom(applied.lines, applied.loose);
3303
- const trailing = resolveTrailingCaps(best, bandCaps, (caps) => runPass({ ...frozen, bandCaps: caps }));
3354
+ const trailing = yield* resolveTrailingCapsGen(best, bandCaps, (caps) => runPass({ ...frozen, bandCaps: caps }));
3304
3355
  passCount += trailing.passCount;
3305
3356
  if (trailing.result !== best) {
3306
3357
  best = trailing.result;
3307
3358
  for (const [i, cap] of trailing.caps)
3308
3359
  bandCaps.set(i, cap);
3309
3360
  resetSegments();
3310
- balance();
3361
+ yield* balance();
3311
3362
  }
3312
3363
  }
3313
3364
  best.doc.iterationCount = passCount;