@santjc/react-pretext 0.1.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.
- package/README.md +347 -0
- package/dist/chunk-4TMIB6R6.js +51 -0
- package/dist/chunk-6P7OEVAC.js +51 -0
- package/dist/editorial.d.ts +163 -0
- package/dist/editorial.js +684 -0
- package/dist/index.d.ts +157 -0
- package/dist/index.js +393 -0
- package/dist/pretext.d.ts +1 -0
- package/dist/pretext.js +2 -0
- package/dist/usePreparedText-DmUr2kss.d.ts +20 -0
- package/package.json +58 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useElementWidth,
|
|
3
|
+
usePreparedSegments
|
|
4
|
+
} from "./chunk-6P7OEVAC.js";
|
|
5
|
+
|
|
6
|
+
// src/editorial/components/EditorialColumns.tsx
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
|
|
9
|
+
// src/editorial/lib/flowText.ts
|
|
10
|
+
import { layoutNextLine } from "@chenglou/pretext";
|
|
11
|
+
var initialCursor = {
|
|
12
|
+
segmentIndex: 0,
|
|
13
|
+
graphemeIndex: 0
|
|
14
|
+
};
|
|
15
|
+
function flowText({
|
|
16
|
+
prepared,
|
|
17
|
+
lineHeight,
|
|
18
|
+
getLineSlotAtY,
|
|
19
|
+
startY = 0,
|
|
20
|
+
startCursor = initialCursor,
|
|
21
|
+
maxLines,
|
|
22
|
+
maxY,
|
|
23
|
+
maxSteps = 2e3
|
|
24
|
+
}) {
|
|
25
|
+
const lines = [];
|
|
26
|
+
let y = startY;
|
|
27
|
+
let cursor = startCursor;
|
|
28
|
+
let exhausted = false;
|
|
29
|
+
let truncated = false;
|
|
30
|
+
let step = 0;
|
|
31
|
+
for (; step < maxSteps; step += 1) {
|
|
32
|
+
if (maxLines !== void 0 && lines.length >= maxLines) {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (maxY !== void 0 && y + lineHeight > maxY) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
const lineSlot = getLineSlotAtY(y);
|
|
39
|
+
if (lineSlot === null || lineSlot.right <= lineSlot.left) {
|
|
40
|
+
y += lineHeight;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const line = layoutNextLine(prepared, cursor, Math.max(1, Math.floor(lineSlot.right - lineSlot.left)));
|
|
44
|
+
if (line === null) {
|
|
45
|
+
exhausted = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
lines.push({
|
|
49
|
+
text: line.text,
|
|
50
|
+
x: lineSlot.left,
|
|
51
|
+
y,
|
|
52
|
+
width: line.width,
|
|
53
|
+
slotLeft: lineSlot.left,
|
|
54
|
+
slotRight: lineSlot.right,
|
|
55
|
+
slotWidth: lineSlot.right - lineSlot.left,
|
|
56
|
+
start: line.start,
|
|
57
|
+
end: line.end
|
|
58
|
+
});
|
|
59
|
+
cursor = line.end;
|
|
60
|
+
y += lineHeight;
|
|
61
|
+
}
|
|
62
|
+
if (!exhausted && step >= maxSteps) {
|
|
63
|
+
truncated = true;
|
|
64
|
+
}
|
|
65
|
+
if (!exhausted && !truncated && layoutNextLine(prepared, cursor, 1) === null) {
|
|
66
|
+
exhausted = true;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
lines,
|
|
70
|
+
height: Math.max(0, y - startY),
|
|
71
|
+
exhausted,
|
|
72
|
+
truncated,
|
|
73
|
+
endCursor: cursor
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/editorial/lib/editorialJustify.ts
|
|
78
|
+
var supportedGapKinds = /* @__PURE__ */ new Set(["space", "preserved-space"]);
|
|
79
|
+
var unsupportedKinds = /* @__PURE__ */ new Set(["tab", "soft-hyphen", "hard-break", "zero-width-break"]);
|
|
80
|
+
function getEditorialJustification({
|
|
81
|
+
prepared,
|
|
82
|
+
line,
|
|
83
|
+
maxWordSpacing = 24
|
|
84
|
+
}) {
|
|
85
|
+
if (line.text.trimStart() !== line.text) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
let lastSegmentIndex = line.end.graphemeIndex > 0 ? line.end.segmentIndex : line.end.segmentIndex - 1;
|
|
89
|
+
while (lastSegmentIndex >= line.start.segmentIndex) {
|
|
90
|
+
const trailingKind = prepared.kinds[lastSegmentIndex];
|
|
91
|
+
const trailingSegment = prepared.segments[lastSegmentIndex];
|
|
92
|
+
if ((trailingKind === "space" || trailingKind === "preserved-space") && trailingSegment === " ") {
|
|
93
|
+
lastSegmentIndex -= 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
if (lastSegmentIndex < line.start.segmentIndex) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
let gapCount = 0;
|
|
102
|
+
for (let segmentIndex = line.start.segmentIndex; segmentIndex <= lastSegmentIndex; segmentIndex += 1) {
|
|
103
|
+
const kind = prepared.kinds[segmentIndex];
|
|
104
|
+
const segment = prepared.segments[segmentIndex];
|
|
105
|
+
if (kind === void 0 || segment === void 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const isPartialStart = segmentIndex === line.start.segmentIndex && line.start.graphemeIndex > 0;
|
|
109
|
+
const isPartialEnd = segmentIndex === line.end.segmentIndex && line.end.graphemeIndex > 0;
|
|
110
|
+
if ((isPartialStart || isPartialEnd) && kind !== "text") {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
if (unsupportedKinds.has(kind)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (!supportedGapKinds.has(kind)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (segment !== " ") {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const previousKind = prepared.kinds[segmentIndex - 1];
|
|
123
|
+
const nextKind = prepared.kinds[segmentIndex + 1];
|
|
124
|
+
if (supportedGapKinds.has(previousKind ?? "") || supportedGapKinds.has(nextKind ?? "")) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
gapCount += 1;
|
|
128
|
+
}
|
|
129
|
+
if (gapCount === 0) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const extraWidth = line.slotWidth - line.width;
|
|
133
|
+
if (extraWidth <= 0) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const wordSpacing = extraWidth / gapCount;
|
|
137
|
+
if (wordSpacing > maxWordSpacing) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return wordSpacing;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/editorial/lib/editorialLineAnnotation.ts
|
|
144
|
+
function annotateEditorialLines(prepared, lines, preserveParagraphBreaks) {
|
|
145
|
+
return lines.map((line, index) => {
|
|
146
|
+
const isTerminal = index === lines.length - 1;
|
|
147
|
+
const isParagraphTerminal = preserveParagraphBreaks && /\n\s*$/.test(line.text);
|
|
148
|
+
const justifyWordSpacing = !isTerminal && !isParagraphTerminal ? getEditorialJustification({ prepared, line }) : null;
|
|
149
|
+
return {
|
|
150
|
+
...line,
|
|
151
|
+
justifyWordSpacing,
|
|
152
|
+
isTerminal,
|
|
153
|
+
isParagraphTerminal
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/editorial/lib/lineSlots.ts
|
|
159
|
+
function carveLineSlots(baseLineSlot, blockedLineRanges, minWidth = 24) {
|
|
160
|
+
let lineSlots = [baseLineSlot];
|
|
161
|
+
for (let blockedIndex = 0; blockedIndex < blockedLineRanges.length; blockedIndex += 1) {
|
|
162
|
+
const blockedLineRange = blockedLineRanges[blockedIndex];
|
|
163
|
+
const nextLineSlots = [];
|
|
164
|
+
for (let slotIndex = 0; slotIndex < lineSlots.length; slotIndex += 1) {
|
|
165
|
+
const lineSlot = lineSlots[slotIndex];
|
|
166
|
+
if (blockedLineRange.right <= lineSlot.left || blockedLineRange.left >= lineSlot.right) {
|
|
167
|
+
nextLineSlots.push(lineSlot);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (blockedLineRange.left > lineSlot.left) {
|
|
171
|
+
nextLineSlots.push({ left: lineSlot.left, right: blockedLineRange.left });
|
|
172
|
+
}
|
|
173
|
+
if (blockedLineRange.right < lineSlot.right) {
|
|
174
|
+
nextLineSlots.push({ left: blockedLineRange.right, right: lineSlot.right });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
lineSlots = nextLineSlots;
|
|
178
|
+
}
|
|
179
|
+
return lineSlots.filter((lineSlot) => lineSlot.right - lineSlot.left >= minWidth);
|
|
180
|
+
}
|
|
181
|
+
function getCircleBlockedLineRangeForRow({
|
|
182
|
+
cx,
|
|
183
|
+
cy,
|
|
184
|
+
radius,
|
|
185
|
+
lineTop,
|
|
186
|
+
lineBottom,
|
|
187
|
+
horizontalPadding = 0,
|
|
188
|
+
verticalPadding = 0
|
|
189
|
+
}) {
|
|
190
|
+
const top = lineTop - verticalPadding;
|
|
191
|
+
const bottom = lineBottom + verticalPadding;
|
|
192
|
+
if (bottom <= cy - radius || top >= cy + radius) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const minDy = cy >= top && cy <= bottom ? 0 : cy < top ? top - cy : cy - bottom;
|
|
196
|
+
if (minDy >= radius) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
const maxDx = Math.sqrt(radius * radius - minDy * minDy);
|
|
200
|
+
return {
|
|
201
|
+
left: cx - maxDx - horizontalPadding,
|
|
202
|
+
right: cx + maxDx + horizontalPadding
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function pickWidestLineSlot(lineSlots) {
|
|
206
|
+
if (lineSlots.length === 0) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
let widestLineSlot = lineSlots[0];
|
|
210
|
+
for (let index = 1; index < lineSlots.length; index += 1) {
|
|
211
|
+
const lineSlot = lineSlots[index];
|
|
212
|
+
if (lineSlot.right - lineSlot.left > widestLineSlot.right - widestLineSlot.left) {
|
|
213
|
+
widestLineSlot = lineSlot;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return widestLineSlot;
|
|
217
|
+
}
|
|
218
|
+
function createLineSlotResolver({
|
|
219
|
+
baseLineSlot,
|
|
220
|
+
lineHeight,
|
|
221
|
+
minWidth = 24,
|
|
222
|
+
getBlockedLineRanges = () => []
|
|
223
|
+
}) {
|
|
224
|
+
return (y) => {
|
|
225
|
+
const lineTop = y;
|
|
226
|
+
const lineBottom = y + lineHeight;
|
|
227
|
+
const blockedLineRanges = getBlockedLineRanges(lineTop, lineBottom);
|
|
228
|
+
const lineSlots = carveLineSlots(baseLineSlot, blockedLineRanges, minWidth);
|
|
229
|
+
return pickWidestLineSlot(lineSlots);
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/editorial/lib/editorialFigures.ts
|
|
234
|
+
function resolveEditorialPlacement(figure, bounds) {
|
|
235
|
+
const placement = figure.placement ?? "top-right";
|
|
236
|
+
const freeWidth = Math.max(0, bounds.width - figure.width);
|
|
237
|
+
const freeHeight = Math.max(0, bounds.height - figure.height);
|
|
238
|
+
const horizontal = (() => {
|
|
239
|
+
switch (placement) {
|
|
240
|
+
case "top-left":
|
|
241
|
+
case "center-left":
|
|
242
|
+
case "bottom-left":
|
|
243
|
+
return 0;
|
|
244
|
+
case "top-center":
|
|
245
|
+
case "center":
|
|
246
|
+
case "bottom-center":
|
|
247
|
+
return freeWidth / 2;
|
|
248
|
+
case "top-right":
|
|
249
|
+
case "center-right":
|
|
250
|
+
case "bottom-right":
|
|
251
|
+
return freeWidth;
|
|
252
|
+
}
|
|
253
|
+
})();
|
|
254
|
+
const vertical = (() => {
|
|
255
|
+
switch (placement) {
|
|
256
|
+
case "top-left":
|
|
257
|
+
case "top-center":
|
|
258
|
+
case "top-right":
|
|
259
|
+
return 0;
|
|
260
|
+
case "center-left":
|
|
261
|
+
case "center":
|
|
262
|
+
case "center-right":
|
|
263
|
+
return freeHeight / 2;
|
|
264
|
+
case "bottom-left":
|
|
265
|
+
case "bottom-center":
|
|
266
|
+
case "bottom-right":
|
|
267
|
+
return freeHeight;
|
|
268
|
+
}
|
|
269
|
+
})();
|
|
270
|
+
return {
|
|
271
|
+
x: Math.min(Math.max(figure.x ?? horizontal, 0), freeWidth),
|
|
272
|
+
y: Math.min(Math.max(figure.y ?? vertical, 0), freeHeight)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function getRectBlockedLineRangeForRow({
|
|
276
|
+
x,
|
|
277
|
+
y,
|
|
278
|
+
width,
|
|
279
|
+
height,
|
|
280
|
+
lineTop,
|
|
281
|
+
lineBottom,
|
|
282
|
+
horizontalPadding = 0,
|
|
283
|
+
verticalPadding = 0
|
|
284
|
+
}) {
|
|
285
|
+
const top = y - verticalPadding;
|
|
286
|
+
const bottom = y + height + verticalPadding;
|
|
287
|
+
if (lineBottom <= top || lineTop >= bottom) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
left: x - horizontalPadding,
|
|
292
|
+
right: x + width + horizontalPadding
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function getBlockedLineRangesForEditorialFigures(figures, lineTop, lineBottom) {
|
|
296
|
+
const blocked = [];
|
|
297
|
+
for (let index = 0; index < figures.length; index += 1) {
|
|
298
|
+
const figure = figures[index];
|
|
299
|
+
if (figure.shape === "circle") {
|
|
300
|
+
const range2 = getCircleBlockedLineRangeForRow({
|
|
301
|
+
cx: figure.x + figure.width / 2,
|
|
302
|
+
cy: figure.y + figure.height / 2,
|
|
303
|
+
radius: Math.min(figure.width, figure.height) / 2,
|
|
304
|
+
lineTop,
|
|
305
|
+
lineBottom,
|
|
306
|
+
horizontalPadding: figure.linePadding ?? 0
|
|
307
|
+
});
|
|
308
|
+
if (range2 !== null) {
|
|
309
|
+
blocked.push(range2);
|
|
310
|
+
}
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const range = getRectBlockedLineRangeForRow({
|
|
314
|
+
x: figure.x,
|
|
315
|
+
y: figure.y,
|
|
316
|
+
width: figure.width,
|
|
317
|
+
height: figure.height,
|
|
318
|
+
lineTop,
|
|
319
|
+
lineBottom,
|
|
320
|
+
horizontalPadding: figure.linePadding ?? 0
|
|
321
|
+
});
|
|
322
|
+
if (range !== null) {
|
|
323
|
+
blocked.push(range);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return blocked;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/editorial/lib/layoutEditorialTrack.ts
|
|
330
|
+
function layoutEditorialTrack({
|
|
331
|
+
prepared,
|
|
332
|
+
figures: figureDefs = [],
|
|
333
|
+
width,
|
|
334
|
+
height,
|
|
335
|
+
lineHeight,
|
|
336
|
+
paddingInline = 0,
|
|
337
|
+
paddingBlock = 0,
|
|
338
|
+
startCursor = initialCursor,
|
|
339
|
+
startY = paddingBlock,
|
|
340
|
+
maxY,
|
|
341
|
+
preserveParagraphBreaks = false
|
|
342
|
+
}) {
|
|
343
|
+
const innerWidth = Math.max(24, width - paddingInline * 2);
|
|
344
|
+
const innerHeight = Math.max(0, height - paddingBlock * 2);
|
|
345
|
+
const figures = figureDefs.map((figure) => {
|
|
346
|
+
const resolved = resolveEditorialPlacement(figure, {
|
|
347
|
+
width: innerWidth,
|
|
348
|
+
height: innerHeight
|
|
349
|
+
});
|
|
350
|
+
return {
|
|
351
|
+
...figure,
|
|
352
|
+
x: paddingInline + resolved.x,
|
|
353
|
+
y: paddingBlock + resolved.y
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
const getLineSlotAtY = createLineSlotResolver({
|
|
357
|
+
baseLineSlot: { left: paddingInline, right: width - paddingInline },
|
|
358
|
+
lineHeight,
|
|
359
|
+
minWidth: 24,
|
|
360
|
+
getBlockedLineRanges: (lineTop, lineBottom) => getBlockedLineRangesForEditorialFigures(figures, lineTop, lineBottom)
|
|
361
|
+
});
|
|
362
|
+
const rawBody = flowText({
|
|
363
|
+
prepared,
|
|
364
|
+
lineHeight,
|
|
365
|
+
startCursor,
|
|
366
|
+
startY,
|
|
367
|
+
getLineSlotAtY,
|
|
368
|
+
maxY
|
|
369
|
+
});
|
|
370
|
+
return {
|
|
371
|
+
figures,
|
|
372
|
+
body: {
|
|
373
|
+
...rawBody,
|
|
374
|
+
lines: annotateEditorialLines(prepared, rawBody.lines, preserveParagraphBreaks)
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/editorial/lib/editorialTracks.ts
|
|
380
|
+
function resolveEditorialTracks(tracks, gap, availableWidth) {
|
|
381
|
+
const fixedWidth = tracks.reduce((total, track) => total + (track.width ?? 0), 0);
|
|
382
|
+
const totalGap = Math.max(0, tracks.length - 1) * gap;
|
|
383
|
+
const flexibleTracks = tracks.filter((track) => track.width === void 0);
|
|
384
|
+
const totalFr = flexibleTracks.reduce((total, track) => total + (track.fr ?? 1), 0);
|
|
385
|
+
const remainingWidth = Math.max(0, availableWidth - fixedWidth - totalGap);
|
|
386
|
+
let x = 0;
|
|
387
|
+
return tracks.map((track) => {
|
|
388
|
+
const resolvedWidth = track.width ?? (totalFr === 0 ? 0 : remainingWidth * (track.fr ?? 1) / totalFr);
|
|
389
|
+
const resolved = {
|
|
390
|
+
...track,
|
|
391
|
+
width: resolvedWidth,
|
|
392
|
+
x
|
|
393
|
+
};
|
|
394
|
+
x += resolvedWidth + gap;
|
|
395
|
+
return resolved;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
function getEditorialTracksWidth(tracks, gap) {
|
|
399
|
+
if (tracks.length === 0) {
|
|
400
|
+
return 0;
|
|
401
|
+
}
|
|
402
|
+
return tracks.reduce((total, track) => total + track.width, 0) + gap * (tracks.length - 1);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/editorial/components/FlowLines.tsx
|
|
406
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
407
|
+
function getFlowLineText(line, lineRenderMode) {
|
|
408
|
+
if (lineRenderMode === "justify" && line.justifyWordSpacing !== null && line.justifyWordSpacing !== void 0) {
|
|
409
|
+
return line.text.trimEnd();
|
|
410
|
+
}
|
|
411
|
+
return line.text;
|
|
412
|
+
}
|
|
413
|
+
function getFlowLineStyle(line, font, lineHeight, lineRenderMode) {
|
|
414
|
+
return {
|
|
415
|
+
position: "absolute",
|
|
416
|
+
left: `${line.slotLeft}px`,
|
|
417
|
+
top: `${line.y}px`,
|
|
418
|
+
width: `${Math.ceil(line.slotWidth)}px`,
|
|
419
|
+
font,
|
|
420
|
+
lineHeight: `${lineHeight}px`,
|
|
421
|
+
whiteSpace: "pre",
|
|
422
|
+
textAlign: "left",
|
|
423
|
+
wordSpacing: lineRenderMode === "justify" && line.justifyWordSpacing !== null && line.justifyWordSpacing !== void 0 ? `${line.justifyWordSpacing}px` : void 0
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function getFlowLineKey(line, index) {
|
|
427
|
+
return `${line.start.segmentIndex}-${line.start.graphemeIndex}-${index}`;
|
|
428
|
+
}
|
|
429
|
+
function FlowLines({
|
|
430
|
+
lines,
|
|
431
|
+
font,
|
|
432
|
+
lineHeight,
|
|
433
|
+
lineClassName,
|
|
434
|
+
lineRenderMode = "natural",
|
|
435
|
+
renderLine
|
|
436
|
+
}) {
|
|
437
|
+
return /* @__PURE__ */ jsx(Fragment, { children: lines.map((line, index) => {
|
|
438
|
+
const key = getFlowLineKey(line, index);
|
|
439
|
+
const text = getFlowLineText(line, lineRenderMode);
|
|
440
|
+
const style = getFlowLineStyle(line, font, lineHeight, lineRenderMode);
|
|
441
|
+
if (renderLine !== void 0) {
|
|
442
|
+
return renderLine({ key, line, text, style });
|
|
443
|
+
}
|
|
444
|
+
return /* @__PURE__ */ jsx("div", { className: lineClassName, style, children: text }, key);
|
|
445
|
+
}) });
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/editorial/components/renderResolvedEditorialFigure.tsx
|
|
449
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
450
|
+
function renderResolvedEditorialFigure(figure, index) {
|
|
451
|
+
return /* @__PURE__ */ jsx2(
|
|
452
|
+
"div",
|
|
453
|
+
{
|
|
454
|
+
className: figure.className,
|
|
455
|
+
style: {
|
|
456
|
+
position: "absolute",
|
|
457
|
+
left: `${figure.x}px`,
|
|
458
|
+
top: `${figure.y}px`,
|
|
459
|
+
width: `${figure.width}px`,
|
|
460
|
+
height: `${figure.height}px`,
|
|
461
|
+
...figure.style
|
|
462
|
+
},
|
|
463
|
+
children: figure.content
|
|
464
|
+
},
|
|
465
|
+
index
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/editorial/components/EditorialColumns.tsx
|
|
470
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
471
|
+
function createEmptyEditorialBody() {
|
|
472
|
+
return {
|
|
473
|
+
lines: [],
|
|
474
|
+
height: 0,
|
|
475
|
+
exhausted: false,
|
|
476
|
+
truncated: false,
|
|
477
|
+
endCursor: initialCursor
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function EditorialColumns({
|
|
481
|
+
text,
|
|
482
|
+
font,
|
|
483
|
+
lineHeight,
|
|
484
|
+
gap = 24,
|
|
485
|
+
lineRenderMode = "natural",
|
|
486
|
+
prepareOptions,
|
|
487
|
+
className,
|
|
488
|
+
style,
|
|
489
|
+
tracks: trackDefs
|
|
490
|
+
}) {
|
|
491
|
+
const { ref, width: availableWidth } = useElementWidth();
|
|
492
|
+
const tracks = useMemo(() => resolveEditorialTracks(trackDefs, gap, availableWidth), [availableWidth, gap, trackDefs]);
|
|
493
|
+
const { prepared } = usePreparedSegments({ text, font, options: prepareOptions });
|
|
494
|
+
const renderedTracks = useMemo(() => {
|
|
495
|
+
if (prepared === null || availableWidth <= 0) {
|
|
496
|
+
return tracks.map((track) => ({
|
|
497
|
+
...track,
|
|
498
|
+
figures: [],
|
|
499
|
+
body: createEmptyEditorialBody()
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
let cursor = initialCursor;
|
|
503
|
+
const preserveParagraphBreaks = prepareOptions?.whiteSpace === "pre-wrap";
|
|
504
|
+
return tracks.map((track) => {
|
|
505
|
+
const paddingInline = track.paddingInline ?? 16;
|
|
506
|
+
const paddingBlock = track.paddingBlock ?? 0;
|
|
507
|
+
const { figures, body } = layoutEditorialTrack({
|
|
508
|
+
prepared,
|
|
509
|
+
figures: track.figures,
|
|
510
|
+
width: track.width,
|
|
511
|
+
height: track.minHeight ?? 320,
|
|
512
|
+
lineHeight,
|
|
513
|
+
startCursor: cursor,
|
|
514
|
+
startY: paddingBlock,
|
|
515
|
+
maxY: track.minHeight === void 0 ? void 0 : track.minHeight - paddingBlock,
|
|
516
|
+
paddingInline,
|
|
517
|
+
paddingBlock,
|
|
518
|
+
preserveParagraphBreaks
|
|
519
|
+
});
|
|
520
|
+
cursor = body.endCursor;
|
|
521
|
+
return {
|
|
522
|
+
...track,
|
|
523
|
+
figures,
|
|
524
|
+
body
|
|
525
|
+
};
|
|
526
|
+
});
|
|
527
|
+
}, [availableWidth, lineHeight, prepared, prepareOptions?.whiteSpace, tracks]);
|
|
528
|
+
const width = getEditorialTracksWidth(tracks, gap);
|
|
529
|
+
const minHeight = renderedTracks.reduce((current, track) => Math.max(current, track.minHeight ?? track.body.height), 0);
|
|
530
|
+
return /* @__PURE__ */ jsx3(
|
|
531
|
+
"div",
|
|
532
|
+
{
|
|
533
|
+
ref,
|
|
534
|
+
className,
|
|
535
|
+
style: {
|
|
536
|
+
position: "relative",
|
|
537
|
+
width: "100%",
|
|
538
|
+
minHeight: `${minHeight}px`,
|
|
539
|
+
...style
|
|
540
|
+
},
|
|
541
|
+
children: /* @__PURE__ */ jsx3(
|
|
542
|
+
"div",
|
|
543
|
+
{
|
|
544
|
+
style: {
|
|
545
|
+
position: "relative",
|
|
546
|
+
width: `${width}px`,
|
|
547
|
+
minHeight: `${minHeight}px`
|
|
548
|
+
},
|
|
549
|
+
children: renderedTracks.map((track, trackIndex) => /* @__PURE__ */ jsxs(
|
|
550
|
+
"div",
|
|
551
|
+
{
|
|
552
|
+
className: track.className,
|
|
553
|
+
style: {
|
|
554
|
+
position: "absolute",
|
|
555
|
+
left: `${track.x}px`,
|
|
556
|
+
top: 0,
|
|
557
|
+
width: `${track.width}px`,
|
|
558
|
+
minHeight: `${track.minHeight ?? track.body.height}px`,
|
|
559
|
+
...track.style
|
|
560
|
+
},
|
|
561
|
+
children: [
|
|
562
|
+
track.figures.map(renderResolvedEditorialFigure),
|
|
563
|
+
/* @__PURE__ */ jsx3(FlowLines, { lines: track.body.lines, font, lineHeight, lineRenderMode })
|
|
564
|
+
]
|
|
565
|
+
},
|
|
566
|
+
trackIndex
|
|
567
|
+
))
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
}
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/editorial/components/EditorialSurface.tsx
|
|
575
|
+
import { useMemo as useMemo2 } from "react";
|
|
576
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
577
|
+
function EditorialSurface({
|
|
578
|
+
text,
|
|
579
|
+
font,
|
|
580
|
+
lineHeight,
|
|
581
|
+
startY = 0,
|
|
582
|
+
maxY,
|
|
583
|
+
minHeight = 320,
|
|
584
|
+
lineRenderMode = "natural",
|
|
585
|
+
prepareOptions,
|
|
586
|
+
className,
|
|
587
|
+
style,
|
|
588
|
+
figures
|
|
589
|
+
}) {
|
|
590
|
+
const { ref, width } = useElementWidth();
|
|
591
|
+
const { prepared } = usePreparedSegments({ text, font, options: prepareOptions });
|
|
592
|
+
const baseHeight = Math.max(minHeight, maxY ?? startY + 320);
|
|
593
|
+
const preserveParagraphBreaks = prepareOptions?.whiteSpace === "pre-wrap";
|
|
594
|
+
const layout = useMemo2(() => {
|
|
595
|
+
if (prepared === null || width <= 0) {
|
|
596
|
+
return {
|
|
597
|
+
figures: [],
|
|
598
|
+
body: {
|
|
599
|
+
lines: [],
|
|
600
|
+
height: 0
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
return layoutEditorialTrack({
|
|
605
|
+
prepared,
|
|
606
|
+
figures,
|
|
607
|
+
width,
|
|
608
|
+
height: baseHeight,
|
|
609
|
+
lineHeight,
|
|
610
|
+
startY,
|
|
611
|
+
maxY,
|
|
612
|
+
preserveParagraphBreaks
|
|
613
|
+
});
|
|
614
|
+
}, [baseHeight, figures, lineHeight, maxY, preserveParagraphBreaks, prepared, startY, width]);
|
|
615
|
+
const height = Math.max(baseHeight, startY + layout.body.height);
|
|
616
|
+
return /* @__PURE__ */ jsxs2(
|
|
617
|
+
"div",
|
|
618
|
+
{
|
|
619
|
+
ref,
|
|
620
|
+
className,
|
|
621
|
+
style: {
|
|
622
|
+
position: "relative",
|
|
623
|
+
minHeight: `${height}px`,
|
|
624
|
+
...style
|
|
625
|
+
},
|
|
626
|
+
children: [
|
|
627
|
+
layout.figures.map(renderResolvedEditorialFigure),
|
|
628
|
+
/* @__PURE__ */ jsx4(FlowLines, { lines: layout.body.lines, font, lineHeight, lineRenderMode })
|
|
629
|
+
]
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/editorial/hooks/useTextFlow.ts
|
|
635
|
+
import { useMemo as useMemo3 } from "react";
|
|
636
|
+
function useTextFlow({
|
|
637
|
+
prepared,
|
|
638
|
+
lineHeight,
|
|
639
|
+
getLineSlotAtY,
|
|
640
|
+
startY,
|
|
641
|
+
startCursor,
|
|
642
|
+
maxLines,
|
|
643
|
+
maxY,
|
|
644
|
+
maxSteps,
|
|
645
|
+
enabled = true
|
|
646
|
+
}) {
|
|
647
|
+
return useMemo3(() => {
|
|
648
|
+
if (!enabled || prepared === null) {
|
|
649
|
+
return {
|
|
650
|
+
lines: [],
|
|
651
|
+
height: 0,
|
|
652
|
+
exhausted: false,
|
|
653
|
+
truncated: false,
|
|
654
|
+
isReady: false,
|
|
655
|
+
endCursor: initialCursor
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
const result = flowText({
|
|
659
|
+
prepared,
|
|
660
|
+
lineHeight,
|
|
661
|
+
getLineSlotAtY,
|
|
662
|
+
startY,
|
|
663
|
+
startCursor,
|
|
664
|
+
maxLines,
|
|
665
|
+
maxY,
|
|
666
|
+
maxSteps
|
|
667
|
+
});
|
|
668
|
+
return {
|
|
669
|
+
...result,
|
|
670
|
+
isReady: true
|
|
671
|
+
};
|
|
672
|
+
}, [enabled, getLineSlotAtY, lineHeight, maxLines, maxSteps, maxY, prepared, startCursor, startY]);
|
|
673
|
+
}
|
|
674
|
+
export {
|
|
675
|
+
EditorialColumns,
|
|
676
|
+
EditorialSurface,
|
|
677
|
+
FlowLines,
|
|
678
|
+
carveLineSlots,
|
|
679
|
+
createLineSlotResolver,
|
|
680
|
+
flowText,
|
|
681
|
+
getCircleBlockedLineRangeForRow,
|
|
682
|
+
pickWidestLineSlot,
|
|
683
|
+
useTextFlow
|
|
684
|
+
};
|