postext 0.3.5 → 0.3.7

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 (147) hide show
  1. package/dist/__tests__/exports.test.js +1 -0
  2. package/dist/__tests__/exports.test.js.map +1 -1
  3. package/dist/canvas-backend/blockRender.d.ts +3 -0
  4. package/dist/canvas-backend/blockRender.d.ts.map +1 -0
  5. package/dist/canvas-backend/blockRender.js +176 -0
  6. package/dist/canvas-backend/blockRender.js.map +1 -0
  7. package/dist/canvas-backend/decorations.d.ts +6 -0
  8. package/dist/canvas-backend/decorations.d.ts.map +1 -0
  9. package/dist/canvas-backend/decorations.js +106 -0
  10. package/dist/canvas-backend/decorations.js.map +1 -0
  11. package/dist/{canvas-backend.d.ts → canvas-backend/index.d.ts} +2 -2
  12. package/dist/canvas-backend/index.d.ts.map +1 -0
  13. package/dist/canvas-backend/index.js +66 -0
  14. package/dist/canvas-backend/index.js.map +1 -0
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/knuthPlass/breakpoints.d.ts +6 -0
  20. package/dist/knuthPlass/breakpoints.d.ts.map +1 -0
  21. package/dist/knuthPlass/breakpoints.js +284 -0
  22. package/dist/knuthPlass/breakpoints.js.map +1 -0
  23. package/dist/knuthPlass/constants.d.ts +8 -0
  24. package/dist/knuthPlass/constants.d.ts.map +1 -0
  25. package/dist/knuthPlass/constants.js +8 -0
  26. package/dist/knuthPlass/constants.js.map +1 -0
  27. package/dist/knuthPlass/index.d.ts +12 -0
  28. package/dist/knuthPlass/index.d.ts.map +1 -0
  29. package/dist/knuthPlass/index.js +11 -0
  30. package/dist/knuthPlass/index.js.map +1 -0
  31. package/dist/knuthPlass/pretextAdapter.d.ts +12 -0
  32. package/dist/knuthPlass/pretextAdapter.d.ts.map +1 -0
  33. package/dist/knuthPlass/pretextAdapter.js +178 -0
  34. package/dist/knuthPlass/pretextAdapter.js.map +1 -0
  35. package/dist/knuthPlass/richAdapter.d.ts +26 -0
  36. package/dist/knuthPlass/richAdapter.d.ts.map +1 -0
  37. package/dist/knuthPlass/richAdapter.js +189 -0
  38. package/dist/knuthPlass/richAdapter.js.map +1 -0
  39. package/dist/{knuthPlass.d.ts → knuthPlass/types.d.ts} +1 -31
  40. package/dist/knuthPlass/types.d.ts.map +1 -0
  41. package/dist/knuthPlass/types.js +5 -0
  42. package/dist/knuthPlass/types.js.map +1 -0
  43. package/dist/knuthPlass/utils.d.ts +2 -0
  44. package/dist/knuthPlass/utils.d.ts.map +1 -0
  45. package/dist/knuthPlass/utils.js +4 -0
  46. package/dist/knuthPlass/utils.js.map +1 -0
  47. package/dist/math/rasterCache.d.ts.map +1 -1
  48. package/dist/math/rasterCache.js +29 -6
  49. package/dist/math/rasterCache.js.map +1 -1
  50. package/dist/measure/cache.d.ts +5 -0
  51. package/dist/measure/cache.d.ts.map +1 -0
  52. package/dist/measure/cache.js +38 -0
  53. package/dist/measure/cache.js.map +1 -0
  54. package/dist/measure/canvas.d.ts +8 -0
  55. package/dist/measure/canvas.d.ts.map +1 -0
  56. package/dist/measure/canvas.js +30 -0
  57. package/dist/measure/canvas.js.map +1 -0
  58. package/dist/measure/font.d.ts +19 -0
  59. package/dist/measure/font.d.ts.map +1 -0
  60. package/dist/measure/font.js +34 -0
  61. package/dist/measure/font.js.map +1 -0
  62. package/dist/measure/index.d.ts +7 -0
  63. package/dist/measure/index.d.ts.map +1 -0
  64. package/dist/measure/index.js +6 -0
  65. package/dist/measure/index.js.map +1 -0
  66. package/dist/measure/plain.d.ts +13 -0
  67. package/dist/measure/plain.d.ts.map +1 -0
  68. package/dist/measure/plain.js +167 -0
  69. package/dist/measure/plain.js.map +1 -0
  70. package/dist/measure/rich.d.ts +25 -0
  71. package/dist/measure/rich.d.ts.map +1 -0
  72. package/dist/measure/rich.js +246 -0
  73. package/dist/measure/rich.js.map +1 -0
  74. package/dist/measure/types.d.ts +28 -0
  75. package/dist/measure/types.d.ts.map +1 -0
  76. package/dist/measure/types.js +2 -0
  77. package/dist/measure/types.js.map +1 -0
  78. package/dist/parse/blockParser.d.ts +28 -0
  79. package/dist/parse/blockParser.d.ts.map +1 -0
  80. package/dist/parse/blockParser.js +302 -0
  81. package/dist/parse/blockParser.js.map +1 -0
  82. package/dist/parse/index.d.ts +4 -0
  83. package/dist/parse/index.d.ts.map +1 -0
  84. package/dist/parse/index.js +3 -0
  85. package/dist/parse/index.js.map +1 -0
  86. package/dist/parse/inlineFormatting.d.ts +12 -0
  87. package/dist/parse/inlineFormatting.d.ts.map +1 -0
  88. package/dist/parse/inlineFormatting.js +90 -0
  89. package/dist/parse/inlineFormatting.js.map +1 -0
  90. package/dist/parse/inlineMath.d.ts +29 -0
  91. package/dist/parse/inlineMath.d.ts.map +1 -0
  92. package/dist/parse/inlineMath.js +141 -0
  93. package/dist/parse/inlineMath.js.map +1 -0
  94. package/dist/parse/sourceMapping.d.ts +12 -0
  95. package/dist/parse/sourceMapping.d.ts.map +1 -0
  96. package/dist/parse/sourceMapping.js +130 -0
  97. package/dist/parse/sourceMapping.js.map +1 -0
  98. package/dist/{parse.d.ts → parse/types.d.ts} +2 -25
  99. package/dist/parse/types.d.ts.map +1 -0
  100. package/dist/parse/types.js +2 -0
  101. package/dist/parse/types.js.map +1 -0
  102. package/dist/pipeline/build.d.ts +13 -1
  103. package/dist/pipeline/build.d.ts.map +1 -1
  104. package/dist/pipeline/build.js +43 -277
  105. package/dist/pipeline/build.js.map +1 -1
  106. package/dist/pipeline/buildBlockKind.d.ts +33 -0
  107. package/dist/pipeline/buildBlockKind.d.ts.map +1 -0
  108. package/dist/pipeline/buildBlockKind.js +82 -0
  109. package/dist/pipeline/buildBlockKind.js.map +1 -0
  110. package/dist/pipeline/buildHelpers.d.ts +47 -0
  111. package/dist/pipeline/buildHelpers.d.ts.map +1 -0
  112. package/dist/pipeline/buildHelpers.js +169 -0
  113. package/dist/pipeline/buildHelpers.js.map +1 -0
  114. package/dist/pipeline/buildMeasurement.d.ts +27 -0
  115. package/dist/pipeline/buildMeasurement.d.ts.map +1 -0
  116. package/dist/pipeline/buildMeasurement.js +49 -0
  117. package/dist/pipeline/buildMeasurement.js.map +1 -0
  118. package/dist/pipeline/index.d.ts +2 -1
  119. package/dist/pipeline/index.d.ts.map +1 -1
  120. package/dist/pipeline/index.js +1 -1
  121. package/dist/pipeline/index.js.map +1 -1
  122. package/dist/worker/client.d.ts +21 -0
  123. package/dist/worker/client.d.ts.map +1 -0
  124. package/dist/worker/client.js +98 -0
  125. package/dist/worker/client.js.map +1 -0
  126. package/dist/worker/layout.worker.d.ts +2 -0
  127. package/dist/worker/layout.worker.d.ts.map +1 -0
  128. package/dist/worker/layout.worker.js +121 -0
  129. package/dist/worker/layout.worker.js.map +1 -0
  130. package/dist/worker/protocol.d.ts +43 -0
  131. package/dist/worker/protocol.d.ts.map +1 -0
  132. package/dist/worker/protocol.js +2 -0
  133. package/dist/worker/protocol.js.map +1 -0
  134. package/package.json +9 -1
  135. package/dist/canvas-backend.d.ts.map +0 -1
  136. package/dist/canvas-backend.js +0 -370
  137. package/dist/canvas-backend.js.map +0 -1
  138. package/dist/knuthPlass.d.ts.map +0 -1
  139. package/dist/knuthPlass.js +0 -665
  140. package/dist/knuthPlass.js.map +0 -1
  141. package/dist/measure.d.ts +0 -61
  142. package/dist/measure.d.ts.map +0 -1
  143. package/dist/measure.js +0 -512
  144. package/dist/measure.js.map +0 -1
  145. package/dist/parse.d.ts.map +0 -1
  146. package/dist/parse.js +0 -653
  147. package/dist/parse.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourceMapping.d.ts","sourceRoot":"","sources":["../../src/parse/sourceMapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AA+F1C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,UAAU,EAAE,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CA0B5D"}
@@ -0,0 +1,130 @@
1
+ import { MATH_PLACEHOLDER } from './inlineMath';
2
+ /**
3
+ * Build a per-character map from plain text to absolute source offsets.
4
+ * Greedy matches each plain char against the raw source (delimited by
5
+ * [blockSrcStart, blockSrcEnd)), skipping markdown markers and treating
6
+ * newlines/tabs as spaces for paragraph line joins.
7
+ */
8
+ function computeSourceMap(markdown, blockSrcStart, blockSrcEnd, plainText) {
9
+ const map = new Array(plainText.length);
10
+ let r = blockSrcStart;
11
+ for (let p = 0; p < plainText.length; p++) {
12
+ const ch = plainText[p];
13
+ // Math placeholder: the plain char represents `$...$` in the markdown.
14
+ // Advance to the opening `$`, map to it, then skip past the closing `$`
15
+ // so subsequent plain chars can keep aligning with the source.
16
+ if (ch === MATH_PLACEHOLDER) {
17
+ while (r < blockSrcEnd && markdown[r] !== '$')
18
+ r++;
19
+ if (r >= blockSrcEnd) {
20
+ map[p] = blockSrcEnd;
21
+ continue;
22
+ }
23
+ map[p] = r;
24
+ let j = r + 1;
25
+ while (j < blockSrcEnd) {
26
+ if (markdown[j] === '\\' && markdown[j + 1] === '$') {
27
+ j += 2;
28
+ continue;
29
+ }
30
+ if (markdown[j] === '$') {
31
+ j++;
32
+ break;
33
+ }
34
+ if (markdown[j] === '\n')
35
+ break;
36
+ j++;
37
+ }
38
+ r = j;
39
+ continue;
40
+ }
41
+ const isSpace = ch === ' ';
42
+ while (r < blockSrcEnd) {
43
+ const rc = markdown[r];
44
+ if (rc === ch)
45
+ break;
46
+ if (isSpace && (rc === '\n' || rc === '\t'))
47
+ break;
48
+ r++;
49
+ }
50
+ if (r >= blockSrcEnd) {
51
+ map[p] = blockSrcEnd;
52
+ }
53
+ else {
54
+ map[p] = r;
55
+ r++;
56
+ }
57
+ }
58
+ return map;
59
+ }
60
+ const COLLAPSIBLE_WS_RE = /[ \t\n\r\f]/;
61
+ /**
62
+ * Collapse runs of `[ \t\n\r\f]` to a single space and strip leading/trailing
63
+ * whitespace across spans. Mirrors pretext's `normalizeWhitespaceNormal` so
64
+ * that `spans` and the block's plain text stay aligned with what the layout
65
+ * engine actually renders — otherwise cursor/selection mapping drifts by one
66
+ * character per collapsed whitespace character.
67
+ */
68
+ function normalizeWhitespaceInSpans(spans) {
69
+ const out = [];
70
+ let inSpace = true; // start true to strip leading whitespace
71
+ for (const span of spans) {
72
+ let result = '';
73
+ for (const ch of span.text) {
74
+ if (COLLAPSIBLE_WS_RE.test(ch)) {
75
+ if (!inSpace) {
76
+ result += ' ';
77
+ inSpace = true;
78
+ }
79
+ }
80
+ else {
81
+ result += ch;
82
+ inSpace = false;
83
+ }
84
+ }
85
+ out.push({ ...span, text: result });
86
+ }
87
+ // Strip trailing space from the last span that contributed content
88
+ for (let i = out.length - 1; i >= 0; i--) {
89
+ const text = out[i].text;
90
+ if (text.length === 0)
91
+ continue;
92
+ if (text.endsWith(' ')) {
93
+ out[i] = { ...out[i], text: text.slice(0, -1) };
94
+ }
95
+ break;
96
+ }
97
+ return out.filter((s) => s.text.length > 0);
98
+ }
99
+ /**
100
+ * Build normalized text, spans, and sourceMap for a block. The plain text is
101
+ * whitespace-normalized to match pretext's internal normalization so that the
102
+ * per-character `sourceMap` aligns with rendered line segments.
103
+ */
104
+ export function buildBlockMapping(markdown, blockSrcStart, blockSrcEnd, rawSpans) {
105
+ const rawText = rawSpans.map((s) => s.text).join('');
106
+ const rawSourceMap = computeSourceMap(markdown, blockSrcStart, blockSrcEnd, rawText);
107
+ const spans = normalizeWhitespaceInSpans(rawSpans);
108
+ const text = spans.map((s) => s.text).join('');
109
+ // Walk the raw text building the same normalization, and project each kept
110
+ // normalized character onto the rawSourceMap to obtain the source offset.
111
+ const sourceMap = [];
112
+ let inSpace = true;
113
+ for (let i = 0; i < rawText.length; i++) {
114
+ const ch = rawText[i];
115
+ if (COLLAPSIBLE_WS_RE.test(ch)) {
116
+ if (!inSpace) {
117
+ sourceMap.push(rawSourceMap[i] ?? blockSrcEnd);
118
+ inSpace = true;
119
+ }
120
+ }
121
+ else {
122
+ sourceMap.push(rawSourceMap[i] ?? blockSrcEnd);
123
+ inSpace = false;
124
+ }
125
+ }
126
+ if (sourceMap.length > text.length)
127
+ sourceMap.length = text.length;
128
+ return { text, spans, sourceMap };
129
+ }
130
+ //# sourceMappingURL=sourceMapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourceMapping.js","sourceRoot":"","sources":["../../src/parse/sourceMapping.ts"],"names":[],"mappings":"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"}
@@ -18,7 +18,7 @@ export interface InlineSpan {
18
18
  math?: MathMeta;
19
19
  /** Resolved math render — populated by the pipeline before measurement
20
20
  * so the parser remains free of MathJax dependencies. */
21
- mathRender?: import('./math/types').MathRender;
21
+ mathRender?: import('../math/types').MathRender;
22
22
  }
23
23
  /** Convenience discriminants for inline span iteration. */
24
24
  export type TextSpan = InlineSpan & {
@@ -66,27 +66,4 @@ export interface ContentBlock {
66
66
  */
67
67
  sourceMap: number[];
68
68
  }
69
- /** Object Replacement Character — atomic plain-text placeholder for a math
70
- * span. One code unit per formula so `sourceMap` stays 1-to-1. */
71
- export declare const MATH_PLACEHOLDER = "\uFFFC";
72
- /**
73
- * Memoized wrapper around parseMarkdown: returns the cached result when the
74
- * input string is byte-for-byte identical to the previous call. This avoids
75
- * reparsing the whole document on each keystroke when upstream recomputes
76
- * only because a sibling state changed.
77
- */
78
- export declare function parseMarkdownMemo(markdown: string): ContentBlock[];
79
- export declare function parseMarkdownWithIssuesMemo(markdown: string): {
80
- blocks: ContentBlock[];
81
- issues: ParseIssue[];
82
- };
83
- export declare function parseMarkdown(markdown: string): ContentBlock[];
84
- /**
85
- * Merge consecutive blockquote lines into a single block, and consecutive
86
- * non-blank, non-special lines into paragraphs.
87
- */
88
- export declare function parseMarkdownWithIssues(markdown: string): {
89
- blocks: ContentBlock[];
90
- issues: ParseIssue[];
91
- };
92
- //# sourceMappingURL=parse.d.ts.map
69
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/parse/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,WAAW,GACX,YAAY,GACZ,UAAU,GACV,aAAa,CAAC;AAElB;;sEAEsE;AACtE,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB;uEACmE;IACnE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB;8DAC0D;IAC1D,UAAU,CAAC,EAAE,OAAO,eAAe,EAAE,UAAU,CAAC;CACjD;AAED,2DAA2D;AAC3D,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,mBAAmB,CAAC;AAElE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,GAAG,GAAG,IAAI,CAAC;IACtB,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB;mCAC+B;IAC/B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/parse/types.ts"],"names":[],"mappings":""}
@@ -1,5 +1,17 @@
1
1
  import type { PostextContent, PostextConfig } from '../types';
2
2
  import { type VDTDocument } from '../vdt';
3
3
  import type { MeasurementCache } from '../measure';
4
- export declare function buildDocument(content: PostextContent, config?: PostextConfig, cache?: MeasurementCache): VDTDocument;
4
+ export interface BuildDocumentOptions {
5
+ /**
6
+ * Cooperative cancellation hook. Called once per top-level content block
7
+ * during placement. Throw (or return a truthy value checked by the caller)
8
+ * to abort. Intended for running `buildDocument` inside a Web Worker where
9
+ * a newer request has superseded this one.
10
+ */
11
+ shouldCancel?: () => boolean;
12
+ }
13
+ export declare class BuildCancelledError extends Error {
14
+ constructor();
15
+ }
16
+ export declare function buildDocument(content: PostextContent, config?: PostextConfig, cache?: MeasurementCache, options?: BuildDocumentOptions): VDTDocument;
5
17
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/pipeline/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9D,OAAO,EAIL,KAAK,WAAW,EAEjB,MAAM,QAAQ,CAAC;AAKhB,OAAO,KAAK,EAAE,gBAAgB,EAAiB,MAAM,YAAY,CAAC;AAuBlE,wBAAgB,aAAa,CAC3B,OAAO,EAAE,cAAc,EACvB,MAAM,CAAC,EAAE,aAAa,EACtB,KAAK,CAAC,EAAE,gBAAgB,GACvB,WAAW,CAktBb"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/pipeline/build.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9D,OAAO,EAGL,KAAK,WAAW,EAEjB,MAAM,QAAQ,CAAC;AAKhB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA4BnD,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC;CAC9B;AAED,qBAAa,mBAAoB,SAAQ,KAAK;;CAK7C;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,cAAc,EACvB,MAAM,CAAC,EAAE,aAAa,EACtB,KAAK,CAAC,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,WAAW,CAifb"}
@@ -1,16 +1,24 @@
1
1
  import { dimensionToPx } from '../units';
2
- import { createBoundingBox, createVDTDocument, createVDTBlock, } from '../vdt';
2
+ import { createVDTDocument, createVDTBlock, } from '../vdt';
3
3
  import { parseMarkdownMemo } from '../parse';
4
4
  import { computeHeadingNumbers } from '../numbering';
5
5
  import { extractFrontmatter } from '../frontmatter';
6
- import { measureBlock, measureRichBlock, cachedMeasureBlock, cachedMeasureRichBlock, initHyphenator } from '../measure';
6
+ import { initHyphenator } from '../measure';
7
7
  import { resolveAllConfig, computeBaselineGrid } from './config';
8
- import { resolveBodyStyle, resolveHeadingStyle, resolveBlockquoteStyle, resolveMathDisplayStyle } from './styles';
9
- import { renderMath, isMathReady } from '../math';
10
- import { computeLevelIndentsPx, computeOrderedLevelIndentsPx, computeOrderedListRunMetrics, resolveUnorderedListItemStyle, resolveOrderedListItemStyle, } from './lists';
8
+ import { resolveBodyStyle, resolveBlockquoteStyle } from './styles';
9
+ import { computeLevelIndentsPx, computeOrderedLevelIndentsPx, computeOrderedListRunMetrics, } from './lists';
11
10
  import { resetLinePositions, createPageWithColumns, currentColumn, advanceToNextColumn, placeBlockInColumn, } from './placement';
12
11
  import { chooseParagraphSplit } from './orphanWidow';
13
- export function buildDocument(content, config, cache) {
12
+ import { applyStyleAttrs, computeMeasureViewport, computePageMetrics, enrichMathSpans, rollbackTrailingBlocks, stampSourceRanges, } from './buildHelpers';
13
+ import { resolveBlockKind } from './buildBlockKind';
14
+ import { runMeasurement } from './buildMeasurement';
15
+ export class BuildCancelledError extends Error {
16
+ constructor() {
17
+ super('Build cancelled');
18
+ this.name = 'BuildCancelledError';
19
+ }
20
+ }
21
+ export function buildDocument(content, config, cache, options) {
14
22
  const resolved = resolveAllConfig(config);
15
23
  const dpi = resolved.page.dpi;
16
24
  // Initialize hyphenator if needed
@@ -21,26 +29,7 @@ export function buildDocument(content, config, cache) {
21
29
  const baselineGrid = computeBaselineGrid(resolved);
22
30
  // Create document
23
31
  const doc = createVDTDocument(resolved, baselineGrid);
24
- // Page dimensions in px (trim size)
25
- const trimWidthPx = dimensionToPx(resolved.page.width, dpi);
26
- const trimHeightPx = dimensionToPx(resolved.page.height, dpi);
27
- // Cut lines expansion: canvas grows to fit bleed + mark offset + mark length
28
- let trimOffset = 0;
29
- if (resolved.page.cutLines.enabled) {
30
- const bleedPx = dimensionToPx(resolved.page.cutLines.bleed, dpi);
31
- const markOffsetPx = dimensionToPx(resolved.page.cutLines.markOffset, dpi);
32
- const markLengthPx = dimensionToPx(resolved.page.cutLines.markLength, dpi);
33
- trimOffset = bleedPx + markOffsetPx + markLengthPx;
34
- }
35
- const pageWidthPx = trimWidthPx + trimOffset * 2;
36
- const pageHeightPx = trimHeightPx + trimOffset * 2;
37
- // Margins in px
38
- const marginTop = dimensionToPx(resolved.page.margins.top, dpi);
39
- const marginBottom = dimensionToPx(resolved.page.margins.bottom, dpi);
40
- const marginLeft = dimensionToPx(resolved.page.margins.left, dpi);
41
- const marginRight = dimensionToPx(resolved.page.margins.right, dpi);
42
- // Content area (offset by trimOffset so content sits inside the trim area)
43
- const contentArea = createBoundingBox(marginLeft + trimOffset, marginTop + trimOffset, trimWidthPx - marginLeft - marginRight, trimHeightPx - marginTop - marginBottom);
32
+ const { pageWidthPx, pageHeightPx, trimOffset, contentArea } = computePageMetrics(resolved);
44
33
  doc.trimOffset = trimOffset;
45
34
  // Create first page
46
35
  const firstPage = createPageWithColumns(0, resolved, contentArea, pageWidthPx, pageHeightPx);
@@ -67,112 +56,30 @@ export function buildDocument(content, config, cache) {
67
56
  let blockIdCounter = 0;
68
57
  let pendingSpacing = 0;
69
58
  for (let blockIdx = 0; blockIdx < contentBlocks.length; blockIdx++) {
59
+ if (options?.shouldCancel?.())
60
+ throw new BuildCancelledError();
70
61
  const rawBlock = contentBlocks[blockIdx];
71
62
  const id = `block-${blockIdCounter++}`;
72
- let style;
73
- let vdtType;
74
- let headingLevel;
75
- let numberPrefix;
76
- let contentBlock = rawBlock;
77
- let listBullet;
78
- let listDepth;
79
- let listKind;
80
- let bulletXOffsetInColumn = 0;
81
- let strikethroughText = false;
82
- switch (rawBlock.type) {
83
- case 'heading': {
84
- style = resolveHeadingStyle(rawBlock.level ?? 1, resolved);
85
- vdtType = 'heading';
86
- headingLevel = rawBlock.level;
87
- numberPrefix = headingPrefixes[blockIdx];
88
- if (numberPrefix) {
89
- const sep = `${numberPrefix} `;
90
- const firstSpan = rawBlock.spans[0];
91
- const newSpans = firstSpan
92
- ? [{ text: sep + firstSpan.text, bold: firstSpan.bold, italic: firstSpan.italic }, ...rawBlock.spans.slice(1)]
93
- : [{ text: sep, bold: false, italic: false }];
94
- contentBlock = { ...rawBlock, text: sep + rawBlock.text, spans: newSpans };
95
- }
96
- break;
97
- }
98
- case 'blockquote':
99
- style = blockquoteStyle;
100
- vdtType = 'blockquote';
101
- break;
102
- case 'mathDisplay': {
103
- style = resolveMathDisplayStyle(resolved);
104
- vdtType = 'mathDisplay';
105
- break;
106
- }
107
- case 'listItem': {
108
- const depth = rawBlock.depth ?? 1;
109
- const kind = rawBlock.listKind ?? 'unordered';
110
- let resolvedList;
111
- if (kind === 'ordered') {
112
- const metric = orderedMetrics.perBlock.get(blockIdx) ??
113
- { numberText: '', numberWidthPx: 0, maxNumberWidthPx: 0 };
114
- resolvedList = resolveOrderedListItemStyle(depth, resolved, orderedLevelIndentsPx, metric);
115
- }
116
- else {
117
- resolvedList = resolveUnorderedListItemStyle(depth, resolved, listLevelIndentsPx, rawBlock.checked ?? false, kind === 'task');
118
- }
119
- style = resolvedList.text;
120
- listBullet = resolvedList.bullet;
121
- listDepth = depth;
122
- listKind = kind;
123
- bulletXOffsetInColumn = resolvedList.bulletXOffsetInColumn;
124
- strikethroughText = resolvedList.strikethroughText;
125
- vdtType = 'listItem';
126
- break;
127
- }
128
- default:
129
- style = bodyStyle;
130
- vdtType = 'paragraph';
131
- break;
132
- }
63
+ const kind = resolveBlockKind(rawBlock, {
64
+ resolved,
65
+ bodyStyle,
66
+ blockquoteStyle,
67
+ headingPrefixes,
68
+ blockIdx,
69
+ listLevelIndentsPx,
70
+ orderedLevelIndentsPx,
71
+ orderedMetrics,
72
+ });
73
+ const { style, vdtType, headingLevel, numberPrefix, listBullet, listDepth, listKind, bulletXOffsetInColumn, strikethroughText } = kind;
74
+ let contentBlock = kind.contentBlock;
133
75
  // Measure text — use rich measurement for blocks with bold spans
134
76
  const col = currentColumn(doc, cursor);
135
- // Resolve inline math: renderMath each `span.math` and attach mathRender.
136
- // When math is disabled, drop the math metadata so spans fall back to
137
- // the raw TeX (visible as literal `$...$`).
77
+ // Resolve inline math on spans (no-op when the block has no math).
138
78
  const mathEnabled = resolved.math.enabled;
139
- if (contentBlock.spans.some((s) => s.math)) {
140
- const mathFontSizePx = style.fontSizePx * resolved.math.fontSizeScale;
141
- const mathColor = resolved.math.color?.hex ?? style.color;
142
- const enrichedSpans = contentBlock.spans.map((s) => {
143
- if (!s.math)
144
- return s;
145
- if (!mathEnabled) {
146
- return { text: `$${s.math.tex}$`, bold: s.bold, italic: s.italic };
147
- }
148
- const render = isMathReady()
149
- ? renderMath(s.math.tex, false, mathFontSizePx, { lineBoxPx: style.lineHeightPx, color: mathColor })
150
- : undefined;
151
- return { ...s, mathRender: render };
152
- });
153
- contentBlock = { ...contentBlock, spans: enrichedSpans };
154
- }
79
+ contentBlock = enrichMathSpans(contentBlock, style, resolved);
155
80
  const hasRichSpans = contentBlock.spans.some((s) => s.bold || s.italic || s.mathRender);
156
81
  // List items reserve horizontal space for indent + bullet + gap.
157
- let measureMaxWidth = col.bbox.width;
158
- let lineXShift = 0;
159
- let measureFirstLineIndent = style.firstLineIndentPx;
160
- let measureHangingIndent = style.hangingIndent;
161
- if (listBullet) {
162
- const textGap = listBullet.bulletWidthPx + listBullet.gapPx;
163
- if (listBullet.hangingIndent) {
164
- measureMaxWidth = Math.max(1, col.bbox.width - listBullet.indentPx - textGap);
165
- lineXShift = listBullet.indentPx + textGap;
166
- measureFirstLineIndent = 0;
167
- measureHangingIndent = false;
168
- }
169
- else {
170
- measureMaxWidth = Math.max(1, col.bbox.width - listBullet.indentPx);
171
- lineXShift = listBullet.indentPx;
172
- measureFirstLineIndent = textGap;
173
- measureHangingIndent = false;
174
- }
175
- }
82
+ const { measureMaxWidth, lineXShift, measureFirstLineIndent, measureHangingIndent, } = computeMeasureViewport(col.bbox.width, style, listBullet);
176
83
  const runtActive = resolved.bodyText.avoidRunts
177
84
  && (vdtType === 'paragraph'
178
85
  || (vdtType === 'listItem' && resolved.bodyText.avoidRuntsInLists));
@@ -187,50 +94,10 @@ export function buildDocument(content, config, cache) {
187
94
  runtPenalty: runtActive ? resolved.bodyText.runtPenalty : 0,
188
95
  runtMinCharacters: runtActive ? resolved.bodyText.runtMinCharacters : 0,
189
96
  };
190
- const useRich = hasRichSpans && style.boldFontString && style.italicFontString && style.boldItalicFontString;
191
- // Math display block: bypass text layout entirely. Produce a single
192
- // VDTLine whose bbox is the math render's pixel box, centred later.
193
- let mathDisplayRender;
194
- let measured;
195
- if (vdtType === 'mathDisplay') {
196
- const tex = rawBlock.tex ?? '';
197
- if (!mathEnabled) {
198
- // Fallback: render the literal TeX as a paragraph-like run.
199
- measured = useRich
200
- ? measureRichBlock([{ text: `$$${tex}$$`, bold: false, italic: false }], style.fontString, style.fontString, style.fontString, style.fontString, measureMaxWidth, style.lineHeightPx, measureOptions)
201
- : measureBlock(`$$${tex}$$`, style.fontString, measureMaxWidth, style.lineHeightPx, measureOptions);
202
- }
203
- else {
204
- // `renderMath` internally returns a cheap placeholder when MathJax
205
- // isn't initialised yet — no need to gate the call here. When the
206
- // real engine lands later, `CanvasPreview` bumps `resizeKey` and the
207
- // pipeline rebuilds with the genuine render.
208
- const render = renderMath(tex, true, style.fontSizePx, { color: style.color });
209
- mathDisplayRender = render;
210
- const width = Math.min(render.widthPx, measureMaxWidth);
211
- const height = render.heightPx;
212
- measured = {
213
- lines: [{
214
- text: '',
215
- bbox: { x: 0, y: 0, width, height },
216
- baseline: render.ascentPx,
217
- hyphenated: false,
218
- segments: [{ kind: 'math', text: '\uFFFC', width, mathRender: render }],
219
- isLastLine: true,
220
- }],
221
- totalHeight: height,
222
- };
223
- }
224
- }
225
- else {
226
- measured = cache
227
- ? (useRich
228
- ? cachedMeasureRichBlock(contentBlock.spans, style.fontString, style.boldFontString, style.italicFontString, style.boldItalicFontString, measureMaxWidth, style.lineHeightPx, measureOptions, cache)
229
- : cachedMeasureBlock(contentBlock.text, style.fontString, measureMaxWidth, style.lineHeightPx, measureOptions, cache))
230
- : (useRich
231
- ? measureRichBlock(contentBlock.spans, style.fontString, style.boldFontString, style.italicFontString, style.boldItalicFontString, measureMaxWidth, style.lineHeightPx, measureOptions)
232
- : measureBlock(contentBlock.text, style.fontString, measureMaxWidth, style.lineHeightPx, measureOptions));
233
- }
97
+ const useRich = !!(hasRichSpans && style.boldFontString && style.italicFontString && style.boldItalicFontString);
98
+ const { measured, mathDisplayRender } = runMeasurement({
99
+ vdtType, rawBlock, contentBlock, style, measureMaxWidth, measureOptions, mathEnabled, useRich, cache,
100
+ });
234
101
  if (measured.lines.length === 0)
235
102
  continue;
236
103
  if (lineXShift > 0) {
@@ -240,46 +107,7 @@ export function buildDocument(content, config, cache) {
240
107
  }
241
108
  // Per-line source-range mapping using the block's plain→source map.
242
109
  // Accounts for heading numbering prefix which prepends chars with no source.
243
- const blockSrcStart = rawBlock.sourceStart + bodyOffset;
244
- const blockSrcEnd = rawBlock.sourceEnd + bodyOffset;
245
- const srcMap = rawBlock.sourceMap;
246
- const prefixLen = contentBlock.text.length - rawBlock.text.length;
247
- const plainToSrc = (p) => {
248
- const idx = p - prefixLen;
249
- if (idx <= 0)
250
- return blockSrcStart;
251
- if (idx >= srcMap.length)
252
- return blockSrcEnd;
253
- return srcMap[idx] + bodyOffset;
254
- };
255
- let cumPlain = 0;
256
- const lastLineIdx = measured.lines.length - 1;
257
- for (let li = 0; li < measured.lines.length; li++) {
258
- const line = measured.lines[li];
259
- // If segments are present, prefer their aggregate text length for a more
260
- // accurate plain-char count (excludes trailing hyphen for hyphenated lines).
261
- let lineLen;
262
- if (line.segments && line.segments.length > 0) {
263
- lineLen = line.segments.reduce((s, seg) => s + seg.text.length, 0);
264
- if (line.hyphenated) {
265
- const last = line.segments[line.segments.length - 1];
266
- if (last.text.endsWith('-'))
267
- lineLen -= 1;
268
- }
269
- }
270
- else {
271
- lineLen = line.text.length - (line.hyphenated ? 1 : 0);
272
- }
273
- line.plainStart = cumPlain;
274
- line.plainEnd = cumPlain + lineLen;
275
- // Advance past the separator space that was consumed to break the line
276
- // (skip when hyphenated — break was at a soft hyphen — or on the last line).
277
- const skipSeparator = !line.hyphenated && li !== lastLineIdx ? 1 : 0;
278
- cumPlain = line.plainEnd + skipSeparator;
279
- line.sourceStart = plainToSrc(line.plainStart);
280
- line.sourceEnd = plainToSrc(line.plainEnd);
281
- }
282
- const absoluteSourceMap = srcMap.map((o) => o + bodyOffset);
110
+ const { prefixLen, absoluteSourceMap } = stampSourceRanges(measured, rawBlock, contentBlock, bodyOffset);
283
111
  const finalizeListItem = (blk, isFirstPart) => {
284
112
  if (!listBullet)
285
113
  return;
@@ -387,16 +215,7 @@ export function buildDocument(content, config, cache) {
387
215
  curCol.availableHeight -= spacingBefore;
388
216
  const splitLines = remainingLines.slice(0, splitAt);
389
217
  const blk = createVDTBlock(id, vdtType, style.fontString, style.color, style.textAlign);
390
- if (style.boldFontString)
391
- blk.boldFontString = style.boldFontString;
392
- if (style.italicFontString)
393
- blk.italicFontString = style.italicFontString;
394
- if (style.boldItalicFontString)
395
- blk.boldItalicFontString = style.boldItalicFontString;
396
- if (style.boldColor)
397
- blk.boldColor = style.boldColor;
398
- if (style.italicColor)
399
- blk.italicColor = style.italicColor;
218
+ applyStyleAttrs(blk, style);
400
219
  blk.headingLevel = headingLevel;
401
220
  if (numberPrefix)
402
221
  blk.numberPrefix = numberPrefix;
@@ -483,21 +302,8 @@ export function buildDocument(content, config, cache) {
483
302
  if (remainAfterHeading < minSpaceAfter) {
484
303
  // Roll back any immediately-preceding heading blocks in this
485
304
  // column so they travel with this one.
486
- let rollbackCount = 0;
487
- for (let j = curCol.blocks.length - 1; j >= 0; j--) {
488
- if (curCol.blocks[j].type === 'heading')
489
- rollbackCount++;
490
- else
491
- break;
492
- }
305
+ const rollbackCount = rollbackTrailingBlocks(curCol, doc.blocks, (b) => b.type === 'heading');
493
306
  if (rollbackCount > 0) {
494
- const popped = curCol.blocks.splice(curCol.blocks.length - rollbackCount);
495
- for (const p of popped) {
496
- const idx = doc.blocks.indexOf(p);
497
- if (idx !== -1)
498
- doc.blocks.splice(idx, 1);
499
- curCol.availableHeight += p.bbox.height;
500
- }
501
307
  // Rewind so the for-loop's blockIdx++ lands on the first
502
308
  // rolled-back heading.
503
309
  blockIdx -= rollbackCount + 1;
@@ -516,16 +322,7 @@ export function buildDocument(content, config, cache) {
516
322
  }
517
323
  const partId = partIndex === 0 ? id : `${id}-cont-${partIndex}`;
518
324
  const blk = createVDTBlock(partId, vdtType, style.fontString, style.color, style.textAlign);
519
- if (style.boldFontString)
520
- blk.boldFontString = style.boldFontString;
521
- if (style.italicFontString)
522
- blk.italicFontString = style.italicFontString;
523
- if (style.boldItalicFontString)
524
- blk.boldItalicFontString = style.boldItalicFontString;
525
- if (style.boldColor)
526
- blk.boldColor = style.boldColor;
527
- if (style.italicColor)
528
- blk.italicColor = style.italicColor;
325
+ applyStyleAttrs(blk, style);
529
326
  if (partIndex === 0) {
530
327
  blk.headingLevel = headingLevel;
531
328
  if (numberPrefix)
@@ -605,16 +402,7 @@ export function buildDocument(content, config, cache) {
605
402
  const partId = partIndex === 0 ? id : `${id}-cont-${partIndex}`;
606
403
  const splitLines = remainingLines.slice(0, choice.splitAt);
607
404
  const blk = createVDTBlock(partId, vdtType, style.fontString, style.color, style.textAlign);
608
- if (style.boldFontString)
609
- blk.boldFontString = style.boldFontString;
610
- if (style.italicFontString)
611
- blk.italicFontString = style.italicFontString;
612
- if (style.boldItalicFontString)
613
- blk.boldItalicFontString = style.boldItalicFontString;
614
- if (style.boldColor)
615
- blk.boldColor = style.boldColor;
616
- if (style.italicColor)
617
- blk.italicColor = style.italicColor;
405
+ applyStyleAttrs(blk, style);
618
406
  if (partIndex === 0) {
619
407
  blk.headingLevel = headingLevel;
620
408
  if (numberPrefix)
@@ -648,21 +436,8 @@ export function buildDocument(content, config, cache) {
648
436
  // pull those headings along so they don't remain stranded as orphans
649
437
  // at the column's bottom. Mirrors the rollback inside the "fits" path.
650
438
  if (vdtType === 'heading' && resolved.headings.keepWithNext) {
651
- let rollbackCount = 0;
652
- for (let j = curCol.blocks.length - 1; j >= 0; j--) {
653
- if (curCol.blocks[j].type === 'heading')
654
- rollbackCount++;
655
- else
656
- break;
657
- }
439
+ const rollbackCount = rollbackTrailingBlocks(curCol, doc.blocks, (b) => b.type === 'heading');
658
440
  if (rollbackCount > 0) {
659
- const popped = curCol.blocks.splice(curCol.blocks.length - rollbackCount);
660
- for (const p of popped) {
661
- const idx = doc.blocks.indexOf(p);
662
- if (idx !== -1)
663
- doc.blocks.splice(idx, 1);
664
- curCol.availableHeight += p.bbox.height;
665
- }
666
441
  blockIdx -= rollbackCount + 1;
667
442
  pendingSpacing = 0;
668
443
  advanceToNextColumn(doc, cursor, resolved, contentArea, pageWidthPx, pageHeightPx);
@@ -676,16 +451,7 @@ export function buildDocument(content, config, cache) {
676
451
  // Empty column but block still doesn't fit (block taller than page) — place anyway
677
452
  const partId = partIndex === 0 ? id : `${id}-cont-${partIndex}`;
678
453
  const blk = createVDTBlock(partId, vdtType, style.fontString, style.color, style.textAlign);
679
- if (style.boldFontString)
680
- blk.boldFontString = style.boldFontString;
681
- if (style.italicFontString)
682
- blk.italicFontString = style.italicFontString;
683
- if (style.boldItalicFontString)
684
- blk.boldItalicFontString = style.boldItalicFontString;
685
- if (style.boldColor)
686
- blk.boldColor = style.boldColor;
687
- if (style.italicColor)
688
- blk.italicColor = style.italicColor;
454
+ applyStyleAttrs(blk, style);
689
455
  if (partIndex === 0)
690
456
  blk.headingLevel = headingLevel;
691
457
  blk.lines = resetLinePositions(remainingLines, style.lineHeightPx);