postext 1.35.1 → 1.37.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 (273) hide show
  1. package/dist/__bench__/composedPage.bench.test.d.ts +2 -0
  2. package/dist/__bench__/composedPage.bench.test.d.ts.map +1 -0
  3. package/dist/__bench__/composedPage.bench.test.js +95 -0
  4. package/dist/__bench__/composedPage.bench.test.js.map +1 -0
  5. package/dist/__tests__/composed/continuation.test.d.ts +2 -0
  6. package/dist/__tests__/composed/continuation.test.d.ts.map +1 -0
  7. package/dist/__tests__/composed/continuation.test.js +352 -0
  8. package/dist/__tests__/composed/continuation.test.js.map +1 -0
  9. package/dist/__tests__/composed/contrast.test.d.ts +2 -0
  10. package/dist/__tests__/composed/contrast.test.d.ts.map +1 -0
  11. package/dist/__tests__/composed/contrast.test.js +124 -0
  12. package/dist/__tests__/composed/contrast.test.js.map +1 -0
  13. package/dist/__tests__/composed/corpus.test.js +7 -1
  14. package/dist/__tests__/composed/corpus.test.js.map +1 -1
  15. package/dist/__tests__/composed/fit.test.d.ts +2 -0
  16. package/dist/__tests__/composed/fit.test.d.ts.map +1 -0
  17. package/dist/__tests__/composed/fit.test.js +408 -0
  18. package/dist/__tests__/composed/fit.test.js.map +1 -0
  19. package/dist/__tests__/composed/groups.test.js +14 -0
  20. package/dist/__tests__/composed/groups.test.js.map +1 -1
  21. package/dist/__tests__/composed/headingDesigns.test.d.ts +2 -0
  22. package/dist/__tests__/composed/headingDesigns.test.d.ts.map +1 -0
  23. package/dist/__tests__/composed/headingDesigns.test.js +262 -0
  24. package/dist/__tests__/composed/headingDesigns.test.js.map +1 -0
  25. package/dist/__tests__/composed/labels.test.d.ts +2 -0
  26. package/dist/__tests__/composed/labels.test.d.ts.map +1 -0
  27. package/dist/__tests__/composed/labels.test.js +326 -0
  28. package/dist/__tests__/composed/labels.test.js.map +1 -0
  29. package/dist/__tests__/composed/layout.test.js +2 -1
  30. package/dist/__tests__/composed/layout.test.js.map +1 -1
  31. package/dist/__tests__/composed/outsidePrint.test.js +5 -2
  32. package/dist/__tests__/composed/outsidePrint.test.js.map +1 -1
  33. package/dist/__tests__/composed/pageMemo.test.d.ts +2 -0
  34. package/dist/__tests__/composed/pageMemo.test.d.ts.map +1 -0
  35. package/dist/__tests__/composed/pageMemo.test.js +128 -0
  36. package/dist/__tests__/composed/pageMemo.test.js.map +1 -0
  37. package/dist/__tests__/composed/sourceMap.test.d.ts +2 -0
  38. package/dist/__tests__/composed/sourceMap.test.d.ts.map +1 -0
  39. package/dist/__tests__/composed/sourceMap.test.js +395 -0
  40. package/dist/__tests__/composed/sourceMap.test.js.map +1 -0
  41. package/dist/__tests__/composed/spreadLabels.test.d.ts +2 -0
  42. package/dist/__tests__/composed/spreadLabels.test.d.ts.map +1 -0
  43. package/dist/__tests__/composed/spreadLabels.test.js +131 -0
  44. package/dist/__tests__/composed/spreadLabels.test.js.map +1 -0
  45. package/dist/__tests__/composed/templateFrameRects.test.d.ts +2 -0
  46. package/dist/__tests__/composed/templateFrameRects.test.d.ts.map +1 -0
  47. package/dist/__tests__/composed/templateFrameRects.test.js +37 -0
  48. package/dist/__tests__/composed/templateFrameRects.test.js.map +1 -0
  49. package/dist/__tests__/design/imageAltText.test.js +2 -1
  50. package/dist/__tests__/design/imageAltText.test.js.map +1 -1
  51. package/dist/__tests__/exports.test.js +33 -0
  52. package/dist/__tests__/exports.test.js.map +1 -1
  53. package/dist/__tests__/migrate/fixtures/textbookPort.d.ts +14 -0
  54. package/dist/__tests__/migrate/fixtures/textbookPort.d.ts.map +1 -0
  55. package/dist/__tests__/migrate/fixtures/textbookPort.js +116 -0
  56. package/dist/__tests__/migrate/fixtures/textbookPort.js.map +1 -0
  57. package/dist/__tests__/migrate/textToMarkdown.test.d.ts +2 -0
  58. package/dist/__tests__/migrate/textToMarkdown.test.d.ts.map +1 -0
  59. package/dist/__tests__/migrate/textToMarkdown.test.js +268 -0
  60. package/dist/__tests__/migrate/textToMarkdown.test.js.map +1 -0
  61. package/dist/__tests__/paragraphStyleLetterSpacing.test.d.ts +2 -0
  62. package/dist/__tests__/paragraphStyleLetterSpacing.test.d.ts.map +1 -0
  63. package/dist/__tests__/paragraphStyleLetterSpacing.test.js +89 -0
  64. package/dist/__tests__/paragraphStyleLetterSpacing.test.js.map +1 -0
  65. package/dist/__tests__/pictureBelowPrintSize.test.d.ts +2 -0
  66. package/dist/__tests__/pictureBelowPrintSize.test.d.ts.map +1 -0
  67. package/dist/__tests__/pictureBelowPrintSize.test.js +82 -0
  68. package/dist/__tests__/pictureBelowPrintSize.test.js.map +1 -0
  69. package/dist/__tests__/pipeline/contourWrap.test.d.ts +2 -0
  70. package/dist/__tests__/pipeline/contourWrap.test.d.ts.map +1 -0
  71. package/dist/__tests__/pipeline/contourWrap.test.js +412 -0
  72. package/dist/__tests__/pipeline/contourWrap.test.js.map +1 -0
  73. package/dist/__tests__/pipeline/paragraphStyleFaces.test.js +1 -1
  74. package/dist/__tests__/pipeline/paragraphStyleFaces.test.js.map +1 -1
  75. package/dist/__tests__/toc.test.js +3 -2
  76. package/dist/__tests__/toc.test.js.map +1 -1
  77. package/dist/bundle/codec.d.ts +16 -0
  78. package/dist/bundle/codec.d.ts.map +1 -1
  79. package/dist/bundle/codec.js +44 -2
  80. package/dist/bundle/codec.js.map +1 -1
  81. package/dist/bundle/index.d.ts +1 -1
  82. package/dist/bundle/index.d.ts.map +1 -1
  83. package/dist/bundle/index.js +1 -1
  84. package/dist/bundle/index.js.map +1 -1
  85. package/dist/canvas-backend/composed.d.ts +5 -2
  86. package/dist/canvas-backend/composed.d.ts.map +1 -1
  87. package/dist/canvas-backend/composed.js +10 -5
  88. package/dist/canvas-backend/composed.js.map +1 -1
  89. package/dist/canvas-backend/index.d.ts.map +1 -1
  90. package/dist/canvas-backend/index.js +37 -19
  91. package/dist/canvas-backend/index.js.map +1 -1
  92. package/dist/canvas-backend/renderResourceBlock.d.ts +5 -1
  93. package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
  94. package/dist/canvas-backend/renderResourceBlock.js +60 -1
  95. package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
  96. package/dist/composed/continuation.d.ts +102 -0
  97. package/dist/composed/continuation.d.ts.map +1 -0
  98. package/dist/composed/continuation.js +87 -0
  99. package/dist/composed/continuation.js.map +1 -0
  100. package/dist/composed/contour.d.ts +21 -0
  101. package/dist/composed/contour.d.ts.map +1 -0
  102. package/dist/composed/contour.js +34 -0
  103. package/dist/composed/contour.js.map +1 -0
  104. package/dist/composed/geometry.d.ts +8 -0
  105. package/dist/composed/geometry.d.ts.map +1 -1
  106. package/dist/composed/geometry.js +26 -0
  107. package/dist/composed/geometry.js.map +1 -1
  108. package/dist/composed/ids.d.ts +22 -0
  109. package/dist/composed/ids.d.ts.map +1 -0
  110. package/dist/composed/ids.js +32 -0
  111. package/dist/composed/ids.js.map +1 -0
  112. package/dist/composed/labels.d.ts +154 -0
  113. package/dist/composed/labels.d.ts.map +1 -0
  114. package/dist/composed/labels.js +524 -0
  115. package/dist/composed/labels.js.map +1 -0
  116. package/dist/composed/pageMemo.d.ts +73 -0
  117. package/dist/composed/pageMemo.d.ts.map +1 -0
  118. package/dist/composed/pageMemo.js +289 -0
  119. package/dist/composed/pageMemo.js.map +1 -0
  120. package/dist/composed/paint.d.ts +29 -9
  121. package/dist/composed/paint.d.ts.map +1 -1
  122. package/dist/composed/paint.js +41 -7
  123. package/dist/composed/paint.js.map +1 -1
  124. package/dist/composed/reflowLog.d.ts +14 -2
  125. package/dist/composed/reflowLog.d.ts.map +1 -1
  126. package/dist/composed/reflowLog.js +64 -7
  127. package/dist/composed/reflowLog.js.map +1 -1
  128. package/dist/composed/slotChecks.d.ts.map +1 -1
  129. package/dist/composed/slotChecks.js +3 -1
  130. package/dist/composed/slotChecks.js.map +1 -1
  131. package/dist/composed/sourceMap.d.ts +97 -0
  132. package/dist/composed/sourceMap.d.ts.map +1 -0
  133. package/dist/composed/sourceMap.js +748 -0
  134. package/dist/composed/sourceMap.js.map +1 -0
  135. package/dist/composed/spread.d.ts +3 -0
  136. package/dist/composed/spread.d.ts.map +1 -1
  137. package/dist/composed/spread.js +11 -12
  138. package/dist/composed/spread.js.map +1 -1
  139. package/dist/composed/templates.d.ts +1 -0
  140. package/dist/composed/templates.d.ts.map +1 -1
  141. package/dist/composed/templates.js +4 -0
  142. package/dist/composed/templates.js.map +1 -1
  143. package/dist/composed/types.d.ts +15 -32
  144. package/dist/composed/types.d.ts.map +1 -1
  145. package/dist/composed/validate.d.ts.map +1 -1
  146. package/dist/composed/validate.js +3 -2
  147. package/dist/composed/validate.js.map +1 -1
  148. package/dist/composed/wrap.d.ts.map +1 -1
  149. package/dist/composed/wrap.js +7 -1
  150. package/dist/composed/wrap.js.map +1 -1
  151. package/dist/configWarnings.d.ts +15 -0
  152. package/dist/configWarnings.d.ts.map +1 -1
  153. package/dist/configWarnings.js +29 -17
  154. package/dist/configWarnings.js.map +1 -1
  155. package/dist/contour/index.d.ts +134 -0
  156. package/dist/contour/index.d.ts.map +1 -0
  157. package/dist/contour/index.js +354 -0
  158. package/dist/contour/index.js.map +1 -0
  159. package/dist/contour/png.d.ts +16 -0
  160. package/dist/contour/png.d.ts.map +1 -0
  161. package/dist/contour/png.js +173 -0
  162. package/dist/contour/png.js.map +1 -0
  163. package/dist/contrast.d.ts +5 -3
  164. package/dist/contrast.d.ts.map +1 -1
  165. package/dist/contrast.js +46 -5
  166. package/dist/contrast.js.map +1 -1
  167. package/dist/defaults/paragraphStyles.d.ts +1 -1
  168. package/dist/defaults/paragraphStyles.d.ts.map +1 -1
  169. package/dist/defaults/paragraphStyles.js +5 -1
  170. package/dist/defaults/paragraphStyles.js.map +1 -1
  171. package/dist/design/slots.d.ts +12 -10
  172. package/dist/design/slots.d.ts.map +1 -1
  173. package/dist/design/slots.js +26 -2
  174. package/dist/design/slots.js.map +1 -1
  175. package/dist/fonts/faces.d.ts.map +1 -1
  176. package/dist/fonts/faces.js +6 -0
  177. package/dist/fonts/faces.js.map +1 -1
  178. package/dist/highlightMarks.d.ts.map +1 -1
  179. package/dist/highlightMarks.js +6 -0
  180. package/dist/highlightMarks.js.map +1 -1
  181. package/dist/html-backend.d.ts.map +1 -1
  182. package/dist/html-backend.js +42 -4
  183. package/dist/html-backend.js.map +1 -1
  184. package/dist/index.d.ts +18 -7
  185. package/dist/index.d.ts.map +1 -1
  186. package/dist/index.js +18 -4
  187. package/dist/index.js.map +1 -1
  188. package/dist/measure/rich.d.ts.map +1 -1
  189. package/dist/measure/rich.js +2 -0
  190. package/dist/measure/rich.js.map +1 -1
  191. package/dist/migrate/markdownRuns.d.ts +70 -0
  192. package/dist/migrate/markdownRuns.d.ts.map +1 -0
  193. package/dist/migrate/markdownRuns.js +215 -0
  194. package/dist/migrate/markdownRuns.js.map +1 -0
  195. package/dist/migrate/pageDesign.d.ts +119 -0
  196. package/dist/migrate/pageDesign.d.ts.map +1 -0
  197. package/dist/migrate/pageDesign.js +446 -0
  198. package/dist/migrate/pageDesign.js.map +1 -0
  199. package/dist/migrate/textToMarkdown.d.ts +124 -0
  200. package/dist/migrate/textToMarkdown.d.ts.map +1 -0
  201. package/dist/migrate/textToMarkdown.js +716 -0
  202. package/dist/migrate/textToMarkdown.js.map +1 -0
  203. package/dist/parse/blockParser.d.ts.map +1 -1
  204. package/dist/parse/blockParser.js +44 -3
  205. package/dist/parse/blockParser.js.map +1 -1
  206. package/dist/parse/types.d.ts +20 -0
  207. package/dist/parse/types.d.ts.map +1 -1
  208. package/dist/pipeline/build.d.ts.map +1 -1
  209. package/dist/pipeline/build.js +298 -30
  210. package/dist/pipeline/build.js.map +1 -1
  211. package/dist/pipeline/buildMeasurement.d.ts +7 -2
  212. package/dist/pipeline/buildMeasurement.d.ts.map +1 -1
  213. package/dist/pipeline/buildMeasurement.js +21 -5
  214. package/dist/pipeline/buildMeasurement.js.map +1 -1
  215. package/dist/pipeline/calloutLayout.d.ts +39 -14
  216. package/dist/pipeline/calloutLayout.d.ts.map +1 -1
  217. package/dist/pipeline/calloutLayout.js +50 -4
  218. package/dist/pipeline/calloutLayout.js.map +1 -1
  219. package/dist/pipeline/composedLayout.d.ts +51 -6
  220. package/dist/pipeline/composedLayout.d.ts.map +1 -1
  221. package/dist/pipeline/composedLayout.js +633 -78
  222. package/dist/pipeline/composedLayout.js.map +1 -1
  223. package/dist/pipeline/contentWarnings.d.ts +15 -0
  224. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  225. package/dist/pipeline/contentWarnings.js +98 -4
  226. package/dist/pipeline/contentWarnings.js.map +1 -1
  227. package/dist/pipeline/floatPlacement.d.ts +2 -0
  228. package/dist/pipeline/floatPlacement.d.ts.map +1 -1
  229. package/dist/pipeline/floatPlacement.js +1 -0
  230. package/dist/pipeline/floatPlacement.js.map +1 -1
  231. package/dist/pipeline/frameLayout.d.ts +7 -4
  232. package/dist/pipeline/frameLayout.d.ts.map +1 -1
  233. package/dist/pipeline/frameLayout.js +5 -2
  234. package/dist/pipeline/frameLayout.js.map +1 -1
  235. package/dist/pipeline/headerFooter.d.ts +20 -1
  236. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  237. package/dist/pipeline/headerFooter.js +225 -70
  238. package/dist/pipeline/headerFooter.js.map +1 -1
  239. package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
  240. package/dist/pipeline/measureContentBlock.js +1 -0
  241. package/dist/pipeline/measureContentBlock.js.map +1 -1
  242. package/dist/pipeline/mirrorFrame.d.ts.map +1 -1
  243. package/dist/pipeline/mirrorFrame.js +25 -2
  244. package/dist/pipeline/mirrorFrame.js.map +1 -1
  245. package/dist/pipeline/outline.d.ts.map +1 -1
  246. package/dist/pipeline/outline.js +2 -0
  247. package/dist/pipeline/outline.js.map +1 -1
  248. package/dist/pipeline/resourceLayout.d.ts +28 -0
  249. package/dist/pipeline/resourceLayout.d.ts.map +1 -1
  250. package/dist/pipeline/resourceLayout.js +21 -0
  251. package/dist/pipeline/resourceLayout.js.map +1 -1
  252. package/dist/pipeline/styles.d.ts +1 -1
  253. package/dist/pipeline/styles.d.ts.map +1 -1
  254. package/dist/pipeline/styles.js +4 -0
  255. package/dist/pipeline/styles.js.map +1 -1
  256. package/dist/pipeline/textWrap.d.ts +4 -1
  257. package/dist/pipeline/textWrap.d.ts.map +1 -1
  258. package/dist/pipeline/textWrap.js +17 -3
  259. package/dist/pipeline/textWrap.js.map +1 -1
  260. package/dist/pipeline/toc.d.ts.map +1 -1
  261. package/dist/pipeline/toc.js +1 -0
  262. package/dist/pipeline/toc.js.map +1 -1
  263. package/dist/types.d.ts +77 -2
  264. package/dist/types.d.ts.map +1 -1
  265. package/dist/vdt.d.ts +290 -15
  266. package/dist/vdt.d.ts.map +1 -1
  267. package/dist/vdt.js.map +1 -1
  268. package/dist/word/toDocx.d.ts.map +1 -1
  269. package/dist/word/toDocx.js +14 -0
  270. package/dist/word/toDocx.js.map +1 -1
  271. package/dist/word/word.test.js +11 -1
  272. package/dist/word/word.test.js.map +1 -1
  273. package/package.json +1 -1
@@ -0,0 +1,748 @@
1
+ /**
2
+ * The source map of a laid-out document (#857, DESIGN §6): what a point of
3
+ * a page shows and where it is written, and the reverse, what a range of
4
+ * the Markdown became on the pages.
5
+ *
6
+ * Nothing here lays anything out: it reads what the layout stamped on the
7
+ * VDT. Body text (in the flow or in a frame) carries `sourceMap` per block
8
+ * and `plainStart`/`plainEnd` per line; a chip carries the offsets of its
9
+ * words (`VDTChip.sourceOffsets`); a frame its template entry
10
+ * (`VDTFrame.configPath`) and the range of its slot's content; a composed
11
+ * page its template and its page fence; a design block (decoration, running
12
+ * heads) its element (`elementId`, `configPath`) and, when it shows an
13
+ * attribute, the attribute's name and value range (`attr`); a contents
14
+ * entry the heading it lists (`tocEntry.headingSource`).
15
+ *
16
+ * `sourceAt` answers with the innermost element first and its ancestors
17
+ * after it (text → paragraph → frame → group → template), each with a
18
+ * stable id ({@link ./ids}), so a host can widen a selection level by
19
+ * level and find the same element again after a relayout or in another
20
+ * locale of the chapter.
21
+ */
22
+ import { labelGlyphAt } from './labels';
23
+ import { lineTextAlign, pageIsMirrored, pageToFlow } from '../vdt';
24
+ import { resolvePageTemplate } from './templates';
25
+ import { composedPaintOrder, frameContentRect, frameMatrix, frameOfBlock } from './paint';
26
+ import { composedPageElementId, designElementId, groupElementId } from './ids';
27
+ const inside = (r, p, slop = 0) => p.x >= r.x - slop && p.x <= r.x + r.width + slop && p.y >= r.y - slop && p.y <= r.y + r.height + slop;
28
+ const fileOf = (options, pageIndex) => {
29
+ const f = typeof options?.file === 'function' ? options.file(pageIndex) : options?.file;
30
+ return f !== undefined ? { file: f } : {};
31
+ };
32
+ /** The plain characters a segment stands for: none for what the layout
33
+ * added (a book title's brackets, a tab leader, the hyphen of a break),
34
+ * one for a reference, a swatch, a chip, a formula. */
35
+ function segmentPlainLength(seg, dropTrailingHyphen, dropLeadingHyphen) {
36
+ if (seg.refContinues || seg.inserted || seg.leader !== undefined)
37
+ return 0;
38
+ if (seg.refResourceId !== undefined || seg.kind === 'swatch')
39
+ return 1;
40
+ let len = seg.text.length - (seg.kashida?.length ?? 0);
41
+ if (dropLeadingHyphen && seg.text.startsWith('-'))
42
+ len--;
43
+ if (dropTrailingHyphen && seg.text.endsWith('-') && len > 0)
44
+ len--;
45
+ return Math.max(0, len);
46
+ }
47
+ function placeSegments(block, line) {
48
+ const segs = line.segments ?? [];
49
+ if (segs.length === 0)
50
+ return [];
51
+ const span = line.measure;
52
+ const lineX = span ? span.x : line.bbox.x;
53
+ const effectiveWidth = span ? span.width : block.bbox.x + block.bbox.width - lineX;
54
+ let natural = 0;
55
+ let words = 0;
56
+ let spaces = 0;
57
+ for (const s of segs) {
58
+ natural += s.width;
59
+ if (s.kind === 'space')
60
+ spaces++;
61
+ else
62
+ words += s.width;
63
+ }
64
+ const justify = block.textAlign === 'justify' && spaces > 0 && !line.tabbed
65
+ && ((line.isLastLine === false && !line.ragged) || natural > effectiveWidth);
66
+ const spaceWidth = justify ? (effectiveWidth - words) / spaces : undefined;
67
+ const align = lineTextAlign(line, block.textAlign);
68
+ const slack = Math.max(0, effectiveWidth - natural);
69
+ const start = lineX + (justify ? 0 : align === 'center' ? slack / 2 : align === 'right' ? slack : 0);
70
+ const widths = segs.map((s) => (s.kind === 'space' && spaceWidth !== undefined ? spaceWidth : s.width));
71
+ const order = line.order && line.order.length === segs.length ? line.order : segs.map((_, i) => i);
72
+ const xs = new Array(segs.length).fill(start);
73
+ let x = start;
74
+ for (const i of order) {
75
+ xs[i] = x;
76
+ x += widths[i];
77
+ }
78
+ const out = [];
79
+ const dropTrailing = line.hyphenated === true && line.hardHyphen !== true;
80
+ let cum = 0;
81
+ segs.forEach((seg, i) => {
82
+ const plainLen = segmentPlainLength(seg, i === segs.length - 1 && dropTrailing, i === 0 && line.repeatedHyphen === true);
83
+ out.push({ seg, index: i, x: xs[i], width: widths[i], plainStart: cum, plainLen });
84
+ cum += plainLen;
85
+ });
86
+ return out;
87
+ }
88
+ /** The source offset of plain character `plain` (block-relative, its
89
+ * number prefix counted) of `block`. */
90
+ function sourceOfPlain(block, plain) {
91
+ const map = block.sourceMap;
92
+ if (!map || map.length === 0)
93
+ return undefined;
94
+ const k = plain - (block.plainPrefixLen ?? 0);
95
+ if (k < 0)
96
+ return map[0];
97
+ return map[Math.min(k, map.length - 1)];
98
+ }
99
+ /** The source range of the glyph of `block`'s line under `x` (flow
100
+ * frame), with the plain offset it stands at; undefined off the text. */
101
+ function glyphAt(block, line, x) {
102
+ if (line.plainStart === undefined || line.plainEnd === undefined)
103
+ return undefined;
104
+ const placed = placeSegments(block, line).filter((p) => p.plainLen > 0);
105
+ if (placed.length === 0)
106
+ return undefined;
107
+ let hit = placed[0];
108
+ let best = Infinity;
109
+ for (const p of placed) {
110
+ const d = x < p.x ? p.x - x : x > p.x + p.width ? x - p.x - p.width : 0;
111
+ if (d < best) {
112
+ best = d;
113
+ hit = p;
114
+ }
115
+ if (d === 0)
116
+ break;
117
+ }
118
+ const seg = hit.seg;
119
+ const along = Math.max(0, Math.min(hit.width, x - hit.x));
120
+ const rtl = seg.rtl === true;
121
+ const plainOf = (k) => line.plainStart + hit.plainStart + k;
122
+ // A chip: the character of its words under `x`, through its own map.
123
+ if (seg.chip) {
124
+ const chipAt = sourceOfPlain(block, plainOf(0));
125
+ if (chipAt === undefined)
126
+ return undefined;
127
+ const chip = seg.chip;
128
+ const offsets = chip.sourceOffsets;
129
+ const nextAt = sourceOfPlain(block, plainOf(1));
130
+ const whole = { start: chipAt, end: nextAt !== undefined && nextAt > chipAt ? nextAt : chipAt + 1, plain: plainOf(0) };
131
+ if (!offsets)
132
+ return whole;
133
+ let rx = hit.x + chip.marginLeft + chip.paddingX + chip.borderWidth;
134
+ let k = 0;
135
+ const order = chip.order ?? chip.runs.map((_, i) => i);
136
+ const logicalStart = [];
137
+ let acc = 0;
138
+ for (const r of chip.runs) {
139
+ logicalStart.push(acc);
140
+ acc += r.text.length;
141
+ }
142
+ for (const ri of order) {
143
+ const run = chip.runs[ri];
144
+ if (x <= rx + run.width || ri === order[order.length - 1]) {
145
+ const n = run.text.length;
146
+ const frac = run.width > 0 ? Math.max(0, Math.min(0.9999, (x - rx) / run.width)) : 0;
147
+ const at = Math.floor((run.rtl ? 1 - frac : frac) * n);
148
+ k = logicalStart[ri] + Math.max(0, Math.min(n - 1, at));
149
+ break;
150
+ }
151
+ rx += run.width;
152
+ }
153
+ const off = offsets[k];
154
+ return off === undefined ? whole : { start: chipAt + off, end: chipAt + off + 1, plain: plainOf(0) };
155
+ }
156
+ // A placeholder (reference, swatch, formula): the whole construct, up to
157
+ // the next character's source.
158
+ if (hit.plainLen === 1 && (seg.refResourceId !== undefined || seg.kind === 'swatch' || seg.kind === 'math' || seg.text.length !== 1)) {
159
+ const at = sourceOfPlain(block, plainOf(0));
160
+ if (at === undefined)
161
+ return undefined;
162
+ const next = sourceOfPlain(block, plainOf(1));
163
+ return { start: at, end: next !== undefined && next > at ? next : at + 1, plain: plainOf(0) };
164
+ }
165
+ const frac = hit.width > 0 ? Math.max(0, Math.min(0.9999, along / hit.width)) : 0;
166
+ const k = Math.min(hit.plainLen - 1, Math.floor((rtl ? 1 - frac : frac) * hit.plainLen));
167
+ const at = sourceOfPlain(block, plainOf(k));
168
+ if (at === undefined)
169
+ return undefined;
170
+ return { start: at, end: at + 1, plain: plainOf(k) };
171
+ }
172
+ /** The line of `block` under `y` (the nearest one in a gap). */
173
+ function lineAt(block, y) {
174
+ let best;
175
+ let bestDy = Infinity;
176
+ block.lines.forEach((line, index) => {
177
+ const top = line.bbox.y;
178
+ const bot = top + line.bbox.height;
179
+ const dy = y < top ? top - y : y > bot ? y - bot : 0;
180
+ if (dy < bestDy) {
181
+ bestDy = dy;
182
+ best = { line, index };
183
+ }
184
+ });
185
+ return best;
186
+ }
187
+ /** The source range a line covers. */
188
+ function lineRange(block, line) {
189
+ if (line.plainStart !== undefined && line.plainEnd !== undefined && line.plainEnd > line.plainStart) {
190
+ const a = sourceOfPlain(block, line.plainStart);
191
+ const b = sourceOfPlain(block, line.plainEnd - 1);
192
+ if (a !== undefined && b !== undefined)
193
+ return { start: Math.min(a, b), end: Math.max(a, b) + 1 };
194
+ }
195
+ if (line.sourceStart !== undefined && line.sourceEnd !== undefined)
196
+ return { start: line.sourceStart, end: line.sourceEnd };
197
+ return undefined;
198
+ }
199
+ // ---------------------------------------------------------------------------
200
+ // Elements
201
+ // ---------------------------------------------------------------------------
202
+ const blockElement = (block, file) => {
203
+ if (block.sourceStart === undefined || block.sourceEnd === undefined)
204
+ return undefined;
205
+ return {
206
+ level: block.type === 'callout' ? 'box' : 'block',
207
+ id: block.id,
208
+ source: { kind: 'markdown', start: block.sourceStart, end: block.sourceEnd, ...file },
209
+ rect: { ...block.bbox },
210
+ ...(block.tocEntry?.headingSource !== undefined ? { heading: block.tocEntry.headingSource } : {}),
211
+ };
212
+ };
213
+ /** What a text block of the body shows at `p` (flow frame): the glyph, the
214
+ * line and the block, innermost first. */
215
+ function textChain(block, p, file) {
216
+ const out = [];
217
+ if (block.resourceBlock) {
218
+ const resourceId = block.resourceBlock.resource.id;
219
+ // A label laid on the picture (#860): its glyph, then the label.
220
+ const label = block.resourceBlock.labels?.find((l) => l.rect && inside(l.rect, p));
221
+ if (label)
222
+ out.push(...labelChain(label, resourceId, block.id, p, file, block));
223
+ out.push({ level: 'resource', id: `${block.id}:resource`, source: { kind: 'resource', resourceId }, rect: { ...block.bbox } });
224
+ }
225
+ else {
226
+ const hit = lineAt(block, p.y);
227
+ if (hit) {
228
+ const glyph = glyphAt(block, hit.line, p.x);
229
+ if (glyph)
230
+ out.push({ level: 'text', id: `${block.id}@${glyph.plain}`, source: { kind: 'markdown', start: glyph.start, end: glyph.end, ...file } });
231
+ const range = lineRange(block, hit.line);
232
+ if (range)
233
+ out.push({ level: 'line', id: `${block.id}:l${hit.index}`, source: { kind: 'markdown', ...range, ...file }, rect: { ...hit.line.bbox } });
234
+ }
235
+ }
236
+ const el = blockElement(block, file);
237
+ if (el)
238
+ out.push(el);
239
+ return out;
240
+ }
241
+ /** A figure label's glyph at `p` (its text spread over its rect) and the
242
+ * label itself, its source the resource at its anchor. */
243
+ function labelChain(label, resourceId, blockId, p, file, block) {
244
+ const id = label.id ?? `${blockId}:label:${'anchor' in label.at ? label.at.anchor : label.sourceStart}`;
245
+ const anchor = label.anchorRef ? label.anchorRef.slice(label.anchorRef.indexOf('#') + 1) : undefined;
246
+ const out = [];
247
+ const map = label.sourceMap;
248
+ if (label.rect && map && map.length > 0) {
249
+ // The glyph under the point on the label's lines, through its turn
250
+ // (#860); else its text spread over its box.
251
+ const k = block && label.layout ? labelGlyphAt(label, block, p.x, p.y) : undefined;
252
+ const frac = label.rect.width > 0 ? Math.max(0, Math.min(0.9999, (p.x - label.rect.x) / label.rect.width)) : 0;
253
+ const at = map[Math.min(map.length - 1, k ?? Math.floor(frac * map.length))];
254
+ out.push({ level: 'text', id: `${id}@${at}`, source: { kind: 'markdown', start: at, end: at + 1, ...file } });
255
+ }
256
+ out.push({
257
+ level: 'label',
258
+ id,
259
+ source: { kind: 'resource', resourceId, ...(anchor !== undefined ? { anchor } : {}) },
260
+ content: { start: label.sourceStart, end: label.sourceEnd },
261
+ ...(label.rect ? { rect: { ...label.rect } } : {}),
262
+ });
263
+ return out;
264
+ }
265
+ /** The boxes enclosing `block` among `boxes` (innermost first). */
266
+ function enclosingBoxes(block, boxes, file) {
267
+ const out = [];
268
+ let id = block.id;
269
+ for (let guard = 0; guard < 32; guard++) {
270
+ const parent = boxes.find((b) => b.callout?.childIds.includes(id));
271
+ if (!parent)
272
+ break;
273
+ const el = blockElement(parent, file);
274
+ if (el)
275
+ out.push({ ...el, level: 'box' });
276
+ id = parent.id;
277
+ }
278
+ return out;
279
+ }
280
+ /** A design text block's glyph under `p`: its per-character map when it
281
+ * has one (lines laid from its bbox's left, their `xOffset`), else its
282
+ * whole range. */
283
+ function designTextSource(b, p) {
284
+ if (b.sourceStart === undefined || b.sourceEnd === undefined)
285
+ return undefined;
286
+ const map = b.sourceMap;
287
+ const text = b.sourceText;
288
+ if (!map || !text || map.length !== text.length || b.lines.length === 0)
289
+ return { start: b.sourceStart, end: b.sourceEnd };
290
+ // The printed lines are the source text cut at its line breaks / wraps.
291
+ let best = 0;
292
+ let bestDy = Infinity;
293
+ const lineH = b.bbox.height / Math.max(1, b.lines.length);
294
+ b.lines.forEach((l, i) => {
295
+ const mid = l.baselineY - lineH * 0.35;
296
+ const dy = Math.abs(p.y - mid);
297
+ if (dy < bestDy) {
298
+ bestDy = dy;
299
+ best = i;
300
+ }
301
+ });
302
+ let at = 0;
303
+ for (let i = 0; i < best; i++) {
304
+ const t = b.lines[i].text;
305
+ const found = text.indexOf(t, at);
306
+ at = found >= 0 ? found + t.length : at + t.length;
307
+ while (at < text.length && /\s/.test(text[at]))
308
+ at++;
309
+ }
310
+ const line = b.lines[best];
311
+ const lineStart = text.indexOf(line.text, at) >= 0 ? text.indexOf(line.text, at) : at;
312
+ const x0 = b.bbox.x + line.xOffset;
313
+ const frac = line.width > 0 ? Math.max(0, Math.min(0.9999, (p.x - x0) / line.width)) : 0;
314
+ const k = Math.min(text.length - 1, lineStart + Math.floor(frac * Math.max(1, line.text.length)));
315
+ const s = map[Math.max(0, k)];
316
+ return { start: s, end: s + 1 };
317
+ }
318
+ /** What a heading's own design shows at `p` (#906): the glyph of the
319
+ * title it prints (`{titleText}`, or an attribute), then the heading. The
320
+ * heading's lines are not painted: its design is what the page shows. */
321
+ function headingDesignChain(block, p, file) {
322
+ const overlay = block.designOverlay;
323
+ if (block.type !== 'heading' || !overlay || block.hidden)
324
+ return undefined;
325
+ for (let i = overlay.blocks.length - 1; i >= 0; i--) {
326
+ const b = overlay.blocks[i];
327
+ if (b.kind !== 'text' || !inside(b.bbox, p))
328
+ continue;
329
+ const src = designTextSource(b, p);
330
+ if (!src)
331
+ continue;
332
+ const source = b.attr !== undefined ? { kind: 'attr', name: b.attr, ...src, ...file } : { kind: 'markdown', ...src, ...file };
333
+ const el = blockElement(block, file);
334
+ return [{ level: 'text', id: `${block.id}:${b.attr ?? 'title'}@${src.start}`, source }, ...(el ? [el] : [])];
335
+ }
336
+ return undefined;
337
+ }
338
+ /** The chain of a design block at `p` (`idOf` names its element),
339
+ * innermost first, or undefined when `p` misses it. */
340
+ function designChain(b, index, p, idOf, composed, file) {
341
+ if (!inside(b.bbox, p))
342
+ return undefined;
343
+ const out = [];
344
+ const elementId = idOf(b.elementId ?? `#${index}`);
345
+ if (b.kind === 'text') {
346
+ const src = designTextSource(b, p);
347
+ if (src) {
348
+ out.push(b.attr !== undefined
349
+ ? { level: 'text', id: `${elementId}@${src.start}`, source: { kind: 'attr', name: b.attr, ...src, ...file } }
350
+ : { level: 'text', id: `${elementId}@${src.start}`, source: { kind: 'markdown', ...src, ...file } });
351
+ }
352
+ }
353
+ else if (b.kind === 'image' && b.attr !== undefined && composed?.attrSources[b.attr]) {
354
+ const a = composed.attrSources[b.attr];
355
+ out.push({ level: 'attr', id: `${elementId}:attr`, source: { kind: 'attr', name: b.attr, start: a.sourceStart, end: a.sourceEnd, ...file } });
356
+ }
357
+ if (b.kind === 'image' && b.attr !== undefined && composed?.attrs[b.attr]) {
358
+ out.push({ level: 'resource', id: `${elementId}:resource`, source: { kind: 'resource', resourceId: composed.attrs[b.attr] } });
359
+ }
360
+ if (b.configPath !== undefined)
361
+ out.push({ level: 'design', id: elementId, source: { kind: 'config', path: b.configPath }, rect: { ...b.bbox } });
362
+ return out.length > 0 ? out : undefined;
363
+ }
364
+ /** The slot element of a design slot, by the pointer its blocks share
365
+ * (`…/elements/<i>` taken off). */
366
+ function slotElement(slot, id) {
367
+ const path = slot.blocks.find((b) => b.configPath !== undefined)?.configPath;
368
+ const at = path?.lastIndexOf('/elements/') ?? -1;
369
+ if (!path || at < 0)
370
+ return undefined;
371
+ return { level: 'slot', id, source: { kind: 'config', path: path.slice(0, at) }, rect: { ...slot.bbox } };
372
+ }
373
+ const resolvedTemplates = new WeakMap();
374
+ /** The template a composed page was set with, as the layout resolved it. */
375
+ function templateOf(doc, page) {
376
+ const composed = page.composed;
377
+ const parity = (page.index + (doc.pageIndexOffset ?? 0)) % 2 === 0 ? 'recto' : 'verso';
378
+ const key = `${composed.template}|${parity}`;
379
+ let cache = resolvedTemplates.get(doc);
380
+ if (!cache) {
381
+ cache = new Map();
382
+ resolvedTemplates.set(doc, cache);
383
+ }
384
+ if (!cache.has(key)) {
385
+ cache.set(key, resolvePageTemplate({ pageTemplates: doc.config.pageTemplates, calloutStyles: doc.config.calloutStyles }, composed.template, { parity }));
386
+ }
387
+ return cache.get(key);
388
+ }
389
+ /** The frame element, its groups and the template, for a frame of `page`. */
390
+ function frameAncestors(doc, page, frame) {
391
+ const composed = page.composed;
392
+ const out = [{
393
+ level: 'frame',
394
+ id: frame.id,
395
+ source: { kind: 'config', path: frame.configPath },
396
+ ...(frame.sourceStart !== undefined && frame.sourceEnd !== undefined ? { content: { start: frame.sourceStart, end: frame.sourceEnd } } : {}),
397
+ rect: { ...frame.rect },
398
+ ...(frame.rotate ? { rotate: frame.rotate } : {}),
399
+ }];
400
+ const template = templateOf(doc, page);
401
+ for (const g of template?.groups ?? []) {
402
+ if (!g.frames.includes(frame.name))
403
+ continue;
404
+ out.push({ level: 'group', id: groupElementId(composed.fenceIndex, g.name), source: { kind: 'config', path: g.configPath } });
405
+ }
406
+ out.push(templateElement(composed));
407
+ return out;
408
+ }
409
+ function templateElement(composed) {
410
+ return {
411
+ level: 'template',
412
+ id: composedPageElementId(composed.fenceIndex),
413
+ source: { kind: 'config', path: composed.configPath },
414
+ content: { start: composed.sourceStart, end: composed.sourceEnd },
415
+ };
416
+ }
417
+ /** `p` in the upright frame of a turned frame. */
418
+ function intoFrame(frame, p) {
419
+ const m = frameMatrix(frame);
420
+ if (!m)
421
+ return p;
422
+ const [a, b, c, d, e, f] = m;
423
+ const det = a * d - b * c;
424
+ const x = p.x - e;
425
+ const y = p.y - f;
426
+ return { x: (d * x - c * y) / det, y: (-b * x + a * y) / det };
427
+ }
428
+ /** A box in a frame's blocks' own coordinates on the page, upright: a
429
+ * frame whose text the reflow ladder scaled (#858) lays its blocks out
430
+ * `1 / scale` as large from its top-left; the box is scaled back, the
431
+ * turn (`rotate`) left to the element's `rotate`. */
432
+ function uprightRect(frame, r) {
433
+ const s = frame?.scale;
434
+ if (!frame || s === undefined || !(s > 0) || Math.abs(s - 1) < 1e-9)
435
+ return { ...r };
436
+ const o = frame.rect;
437
+ return { x: o.x + (r.x - o.x) * s, y: o.y + (r.y - o.y) * s, width: r.width * s, height: r.height * s };
438
+ }
439
+ /** The elements of a frame's chain with their boxes upright on the page
440
+ * (`uprightRect`); the frame, its groups and the template keep theirs. */
441
+ function uprightChain(frame, chain) {
442
+ if (frame.scale === undefined || Math.abs(frame.scale - 1) < 1e-9)
443
+ return chain;
444
+ return chain.map((e) => (e.rect && e.level !== 'frame' && e.level !== 'group' && e.level !== 'template' ? { ...e, rect: uprightRect(frame, e.rect) } : e));
445
+ }
446
+ /** What a frame shows at `p` (page px), innermost first. */
447
+ function frameChain(doc, page, frame, blocks, pagePoint, file, facing = false) {
448
+ const raw = frameChainOf(doc, page, frame, blocks, pagePoint, file);
449
+ const chain = raw && uprightChain(frame, raw);
450
+ // The half of a spread frame read on the facing page (#861): the ids of
451
+ // the blocks it copies, flagged.
452
+ return chain && facing ? chain.map((e) => (e.level === 'frame' || e.level === 'group' || e.level === 'template' ? e : { ...e, id: unfacing(e.id), facing: true })) : chain;
453
+ }
454
+ /** The id of the block a facing copy (`…-facing`) shows. */
455
+ const unfacing = (id) => id.replace(/-facing(?=$|[:@])/, '');
456
+ function frameChainOf(doc, page, frame, blocks, pagePoint, file) {
457
+ const p = intoFrame(frame, pagePoint);
458
+ const box = blocks.find((b) => b.type === 'callout' && (b.id === frame.blockIds[0] || frame.blockIds.length === 0));
459
+ const boxId = box?.id;
460
+ // The tab and title of the frame's box (above its top edge, for a tab).
461
+ const overlay = box?.designOverlay;
462
+ if (overlay) {
463
+ for (let i = overlay.blocks.length - 1; i >= 0; i--) {
464
+ const b = overlay.blocks[i];
465
+ if (b.kind !== 'text' || !inside(b.bbox, p))
466
+ continue;
467
+ const src = designTextSource(b, p);
468
+ if (!src)
469
+ continue;
470
+ const source = b.attr !== undefined ? { kind: 'attr', name: b.attr, ...src, ...file } : { kind: 'markdown', ...src, ...file };
471
+ return [{ level: 'text', id: `${frame.id}:${b.attr ?? 'title'}@${src.start}`, source }, ...frameAncestors(doc, page, frame)];
472
+ }
473
+ }
474
+ const boxes = blocks.filter((b) => b.type === 'callout');
475
+ // The title a heading's design prints in the frame (#906), a tab above
476
+ // the heading included.
477
+ for (const b of blocks) {
478
+ const chain = headingDesignChain(b, p, file);
479
+ if (chain)
480
+ return [...chain, ...enclosingBoxes(b, boxes, file).filter((e) => e.id !== boxId), ...frameAncestors(doc, page, frame)];
481
+ }
482
+ // `p` is in the blocks' own coordinates: a scaled frame's are larger.
483
+ if (!inside(frameContentRect(frame), p))
484
+ return undefined;
485
+ // Content: the leaf block under `p` (text, a picture), then its boxes.
486
+ for (const b of blocks) {
487
+ if (b.type === 'callout' || b.hidden)
488
+ continue;
489
+ if (!inside(b.bbox, p))
490
+ continue;
491
+ const chain = textChain(b, p, file);
492
+ const nested = enclosingBoxes(b, boxes, file).filter((e) => e.id !== boxId);
493
+ return [...chain, ...nested, ...frameAncestors(doc, page, frame)];
494
+ }
495
+ return frameAncestors(doc, page, frame);
496
+ }
497
+ /**
498
+ * What page `pageIndex` of `doc` shows at `(x, y)` (page px, the sheet's
499
+ * top-left origin), innermost first, each element followed by the ones
500
+ * that hold it: on a composed page a glyph, its line, its paragraph, the
501
+ * boxes round it, its frame, the frame's groups and the template; a design
502
+ * element and its slot; on any other page the flow's text, pictures and
503
+ * boxes, the running heads. Empty when nothing is there.
504
+ */
505
+ export function sourceAt(doc, pageIndex, x, y, options) {
506
+ const page = doc.pages[pageIndex];
507
+ if (!page)
508
+ return [];
509
+ const file = fileOf(options, pageIndex);
510
+ const p = { x, y };
511
+ const composed = page.composed;
512
+ const furniture = (slot, name) => {
513
+ if (!slot)
514
+ return undefined;
515
+ const idOf = (el) => (composed ? designElementId(composed.fenceIndex, name, el) : `p${pageIndex}:${name}:${el}`);
516
+ for (let i = slot.blocks.length - 1; i >= 0; i--) {
517
+ const chain = designChain(slot.blocks[i], i, p, idOf, composed, file);
518
+ if (!chain)
519
+ continue;
520
+ const own = slotElement(slot, composed ? `cp${composed.fenceIndex}:${name}` : `p${pageIndex}:${name}`);
521
+ return [...chain, ...(own ? [own] : []), ...(composed ? [templateElement(composed)] : [])];
522
+ }
523
+ return undefined;
524
+ };
525
+ // The running heads paint over everything else.
526
+ const head = furniture(page.header, 'header') ?? furniture(page.footer, 'footer');
527
+ if (head)
528
+ return head;
529
+ if (composed) {
530
+ // A page of a spread paints its frames and decoration clipped to its
531
+ // part of the sheet (#861).
532
+ const clip = composed.sheetClip;
533
+ const items = clip && !inside(clip, p) ? [] : composedPaintOrder(page);
534
+ // Topmost first: the paint order read backwards.
535
+ for (let i = items.length - 1; i >= 0; i--) {
536
+ const item = items[i];
537
+ if (item.kind === 'frame') {
538
+ const chain = frameChain(doc, page, item.frame, item.blocks, p, file, item.facing);
539
+ if (chain)
540
+ return chain;
541
+ continue;
542
+ }
543
+ const deco = composed.decoration;
544
+ if (!deco)
545
+ continue;
546
+ for (let k = deco.blocks.length - 1; k >= 0; k--) {
547
+ const chain = designChain(deco.blocks[k], k, p, (el) => designElementId(composed.fenceIndex, 'decoration', el), composed, file);
548
+ if (!chain)
549
+ continue;
550
+ const own = slotElement(deco, `cp${composed.fenceIndex}:decoration`);
551
+ return [...chain, ...(own ? [own] : []), templateElement(composed)];
552
+ }
553
+ }
554
+ return inside({ x: 0, y: 0, width: page.width, height: page.height }, p) ? [templateElement(composed)] : [];
555
+ }
556
+ // The flow: blocks are in the page's flow frame.
557
+ const fp = pageIsMirrored(page) || page.flow ? pageToFlow(page, x, y) : p;
558
+ const onPage = doc.blocks.filter((b) => b.pageIndex === pageIndex);
559
+ const floats = page.floats ?? [];
560
+ const all = [...onPage, ...floats.filter((f) => !onPage.includes(f))];
561
+ const boxes = all.filter((b) => b.type === 'callout');
562
+ // A heading drawn by its design, in a column or a box (#906).
563
+ for (const b of all) {
564
+ const chain = headingDesignChain(b, fp, file);
565
+ if (chain)
566
+ return [...chain, ...enclosingBoxes(b, boxes, file)];
567
+ }
568
+ for (const b of all) {
569
+ if (b.type === 'callout' || b.hidden || b.designOverlay)
570
+ continue;
571
+ if (!inside(b.bbox, fp))
572
+ continue;
573
+ return [...textChain(b, fp, file), ...enclosingBoxes(b, boxes, file)];
574
+ }
575
+ for (const b of boxes) {
576
+ if (!inside(b.bbox, fp))
577
+ continue;
578
+ const el = blockElement(b, file);
579
+ if (el)
580
+ return [el, ...enclosingBoxes(b, boxes, file)];
581
+ }
582
+ return [];
583
+ }
584
+ const overlaps = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
585
+ const meets = (a, start, end) => end > start ? a.start < end && a.end > start : a.start <= start && a.end >= start;
586
+ /**
587
+ * Everything of `doc` laid out from the Markdown range `[start, end)`
588
+ * (absolute offsets; an empty range finds what holds the offset): the
589
+ * lines and blocks of text, the frames whose slot content it touches, the
590
+ * composed pages whose fence it touches, the design text that shows an
591
+ * attribute written there; in page order, frames and pages before their
592
+ * content. Lines laid past their frame's foot come with `clipped`.
593
+ */
594
+ export function elementsForSource(doc, start, end, options) {
595
+ const out = [];
596
+ const blockFrame = new Map();
597
+ for (const page of doc.pages) {
598
+ const composed = page.composed;
599
+ if (!composed)
600
+ continue;
601
+ const file = fileOf(options, page.index);
602
+ if (meets({ start: composed.sourceStart, end: composed.sourceEnd }, start, end))
603
+ out.push({ pageIndex: page.index, element: templateElement(composed) });
604
+ for (const frame of composed.frames) {
605
+ for (const id of frame.blockIds)
606
+ blockFrame.set(id, { frame, page });
607
+ if (frame.sourceStart === undefined || frame.sourceEnd === undefined)
608
+ continue;
609
+ if (!meets({ start: frame.sourceStart, end: frame.sourceEnd }, start, end))
610
+ continue;
611
+ const el = frameAncestors(doc, page, frame)[0];
612
+ // On a spread, the part of the frame this page paints (#877): a band
613
+ // that crosses the spine has most of its box on the facing page.
614
+ const shown = composed.sheetClip && el.rect && !frame.rotate ? intersect(el.rect, composed.sheetClip) : el.rect;
615
+ if (!shown)
616
+ continue;
617
+ out.push({ pageIndex: page.index, element: { ...el, rect: shown } });
618
+ }
619
+ for (const [name, slot] of [['decoration', composed.decoration], ['header', page.header], ['footer', page.footer]]) {
620
+ slot?.blocks.forEach((b, i) => {
621
+ if (b.kind !== 'text' || b.sourceStart === undefined || b.sourceEnd === undefined)
622
+ return;
623
+ if (!meets({ start: b.sourceStart, end: b.sourceEnd }, start, end))
624
+ return;
625
+ out.push({
626
+ pageIndex: page.index,
627
+ element: {
628
+ level: 'text',
629
+ id: designElementId(composed.fenceIndex, name, b.elementId ?? `#${i}`),
630
+ source: b.attr !== undefined ? { kind: 'attr', name: b.attr, start: b.sourceStart, end: b.sourceEnd, ...file } : { kind: 'markdown', start: b.sourceStart, end: b.sourceEnd, ...file },
631
+ rect: { ...b.bbox },
632
+ },
633
+ });
634
+ });
635
+ }
636
+ }
637
+ /** The lines, block and labels of `block` the range touches. */
638
+ const blockMatches = (block, pageIndex, held, facing) => {
639
+ if (block.sourceStart === undefined || block.sourceEnd === undefined || block.type === 'callout')
640
+ return;
641
+ if (!meets({ start: block.sourceStart, end: block.sourceEnd }, start, end))
642
+ return;
643
+ const file = fileOf(options, pageIndex);
644
+ // The frame's foot in its blocks' own coordinates (larger when the
645
+ // frame's text is scaled, #858).
646
+ const foot = held ? frameContentRect(held.frame).y + frameContentRect(held.frame).height : Infinity;
647
+ const extra = {
648
+ ...(held?.frame.rotate ? { rotate: held.frame.rotate } : {}),
649
+ ...(facing ? { facing: true } : {}),
650
+ };
651
+ const id = facing ? unfacing(block.id) : block.id;
652
+ // A page of a spread shows only its part of the sheet (#861): what a
653
+ // frame across the spine sets on the other half is the facing page's.
654
+ const clip = doc.pages[pageIndex]?.composed?.sheetClip;
655
+ const shown = (r) => !clip || overlaps(clip, r);
656
+ block.lines.forEach((line, index) => {
657
+ const range = lineRange(block, line);
658
+ if (!range || !meets(range, start, end) || !shown(uprightRect(held?.frame, line.bbox)))
659
+ return;
660
+ out.push({
661
+ pageIndex,
662
+ element: {
663
+ level: 'line',
664
+ id: `${id}:l${index}`,
665
+ source: { kind: 'markdown', ...range, ...file },
666
+ rect: uprightRect(held?.frame, line.bbox),
667
+ ...extra,
668
+ ...(line.bbox.y + line.bbox.height > foot + 0.5 ? { clipped: true } : {}),
669
+ },
670
+ });
671
+ });
672
+ // The labels of a figure (#857), by their text.
673
+ const rb = block.resourceBlock;
674
+ for (const label of rb?.labels ?? []) {
675
+ if (!meets({ start: label.sourceStart, end: label.sourceEnd }, start, end))
676
+ continue;
677
+ const el = labelChain(label, rb.resource.id, id, { x: 0, y: 0 }, file).find((e) => e.level === 'label');
678
+ const rect = el.rect ? uprightRect(held?.frame, el.rect) : undefined;
679
+ if (rect && !shown(rect))
680
+ continue;
681
+ out.push({ pageIndex, element: { ...el, ...(rect ? { rect } : {}), ...extra } });
682
+ }
683
+ const el = blockElement(block, file);
684
+ if (el)
685
+ out.push({ pageIndex, element: { ...el, id, ...(el.rect ? { rect: uprightRect(held?.frame, el.rect) } : {}), ...extra } });
686
+ };
687
+ const seen = new Set();
688
+ for (const block of doc.blocks) {
689
+ if (block.pageIndex < 0 || seen.has(block.id))
690
+ continue;
691
+ seen.add(block.id);
692
+ blockMatches(block, block.pageIndex, blockFrame.get(block.id), false);
693
+ }
694
+ // The halves of spread frames shown on the facing page (#861).
695
+ for (const page of doc.pages) {
696
+ for (const frame of page.composed?.frames ?? []) {
697
+ for (const b of frame.facingBlocks ?? [])
698
+ blockMatches(b, page.index, { frame }, true);
699
+ }
700
+ }
701
+ return out.sort((a, b) => a.pageIndex - b.pageIndex);
702
+ }
703
+ /** The overlap of two rectangles, or undefined when they do not meet. */
704
+ function intersect(a, b) {
705
+ const x = Math.max(a.x, b.x);
706
+ const y = Math.max(a.y, b.y);
707
+ const right = Math.min(a.x + a.width, b.x + b.width);
708
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
709
+ return right > x && bottom > y ? { x, y, width: right - x, height: bottom - y } : undefined;
710
+ }
711
+ /** The element of `doc` with stable id `id` (a frame `cp0:dinero`, a
712
+ * composed page `cp0`, a block, a design element `cp0:decoration:tema`,
713
+ * a figure label `cp0:mapa:label:madrid`),
714
+ * on its page: what a host looks up again after a relayout. */
715
+ export function findElement(doc, id, options) {
716
+ for (const page of doc.pages) {
717
+ const composed = page.composed;
718
+ if (!composed)
719
+ continue;
720
+ if (composedPageElementId(composed.fenceIndex) === id)
721
+ return { pageIndex: page.index, element: templateElement(composed) };
722
+ for (const frame of composed.frames)
723
+ if (frame.id === id)
724
+ return { pageIndex: page.index, element: frameAncestors(doc, page, frame)[0] };
725
+ for (const [name, slot] of [['decoration', composed.decoration], ['header', page.header], ['footer', page.footer]]) {
726
+ const i = slot?.blocks.findIndex((b, k) => designElementId(composed.fenceIndex, name, b.elementId ?? `#${k}`) === id) ?? -1;
727
+ const b = i >= 0 ? slot.blocks[i] : undefined;
728
+ if (b)
729
+ return { pageIndex: page.index, element: { level: 'design', id, source: b.configPath !== undefined ? { kind: 'config', path: b.configPath } : { kind: 'config', path: '' }, rect: { ...b.bbox } } };
730
+ }
731
+ }
732
+ for (const b of doc.blocks) {
733
+ const label = b.pageIndex >= 0 ? b.resourceBlock?.labels?.find((l) => l.id === id) : undefined;
734
+ if (label) {
735
+ const el = labelChain(label, b.resourceBlock.resource.id, b.id, { x: 0, y: 0 }, fileOf(options, b.pageIndex)).find((e) => e.level === 'label');
736
+ const frame = frameOfBlock(doc.pages[b.pageIndex]?.composed, b.id);
737
+ return { pageIndex: b.pageIndex, element: el.rect && frame ? { ...el, rect: uprightRect(frame, el.rect) } : el };
738
+ }
739
+ }
740
+ const block = doc.blocks.find((b) => b.id === id && b.pageIndex >= 0);
741
+ if (!block)
742
+ return undefined;
743
+ const el = blockElement(block, fileOf(options, block.pageIndex));
744
+ // Upright on the page when its frame's text is scaled (#858).
745
+ const frame = frameOfBlock(doc.pages[block.pageIndex]?.composed, block.id);
746
+ return el ? { pageIndex: block.pageIndex, element: el.rect && frame ? { ...el, rect: uprightRect(frame, el.rect) } : el } : undefined;
747
+ }
748
+ //# sourceMappingURL=sourceMap.js.map