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