@zzclub/pipeline 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.
Files changed (98) hide show
  1. package/README.md +559 -0
  2. package/dist/assets/imgx/assets/browser/obstacle-flow.d.ts +27 -0
  3. package/dist/assets/imgx/assets/browser/obstacle-flow.js +326 -0
  4. package/dist/assets/imgx/assets/icons/avatar_jinx_cartoon.jpg +0 -0
  5. package/dist/assets/imgx/assets/icons/clover.svg +15 -0
  6. package/dist/assets/imgx/assets/icons/fishbone-logo-square.png +0 -0
  7. package/dist/assets/imgx/assets/icons/fishbone-logo.jpg +0 -0
  8. package/dist/assets/imgx/assets/icons/fishbone-logo.png +0 -0
  9. package/dist/assets/imgx/assets/icons/openclaw-logo.svg +22 -0
  10. package/dist/assets/imgx/assets/icons/zzclub-logo-black.jpg +0 -0
  11. package/dist/assets/imgx/assets/icons/zzclub-logo-gray.svg +25 -0
  12. package/dist/assets/imgx/assets/templates/ascii-portrait-3-4.html +342 -0
  13. package/dist/assets/imgx/assets/templates/ascii-portrait-tile.html +144 -0
  14. package/dist/assets/imgx/assets/templates/longform-3-4.html +179 -0
  15. package/dist/assets/imgx/assets/templates/poster-3-4.html +359 -0
  16. package/dist/assets/imgx/assets/templates/tips-3-4.html +300 -0
  17. package/dist/assets/imgx/assets/templates/wechat-cover-split.html +307 -0
  18. package/dist/assets/imgx/assets/templates/x-like-posts.html +246 -0
  19. package/dist/assets/wechat-preview/assets/browser-dist/.vite/manifest.json +314 -0
  20. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  21. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  22. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  23. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  24. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  25. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  26. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  27. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  28. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  29. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  30. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  31. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  32. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  33. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  34. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  35. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  36. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  37. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  38. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  39. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  40. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  41. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  42. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  43. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  44. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  45. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  46. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  47. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  48. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  49. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  50. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  51. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  52. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  53. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  54. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  55. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  56. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  57. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  58. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  59. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  60. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  61. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  62. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  63. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  64. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  65. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  66. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  67. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  68. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  69. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  70. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  71. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  72. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  73. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  74. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  75. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  76. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  77. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  78. package/dist/assets/wechat-preview/assets/browser-dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  79. package/dist/assets/wechat-preview/assets/browser-dist/assets/SweiCurveLegCJKsc-Regular-B3Ut5bwH.woff +0 -0
  80. package/dist/assets/wechat-preview/assets/browser-dist/assets/style-BYNXnUvc.css +1 -0
  81. package/dist/assets/wechat-preview/assets/browser-dist/editor-export.js +969 -0
  82. package/dist/assets/wechat-preview/assets/templates/export-shell.html +38 -0
  83. package/dist/cli.js +404 -0
  84. package/dist/node_modules/@chenglou/pretext/dist/analysis.d.ts +33 -0
  85. package/dist/node_modules/@chenglou/pretext/dist/analysis.js +1063 -0
  86. package/dist/node_modules/@chenglou/pretext/dist/bidi.d.ts +1 -0
  87. package/dist/node_modules/@chenglou/pretext/dist/bidi.js +175 -0
  88. package/dist/node_modules/@chenglou/pretext/dist/generated/bidi-data.d.ts +4 -0
  89. package/dist/node_modules/@chenglou/pretext/dist/generated/bidi-data.js +979 -0
  90. package/dist/node_modules/@chenglou/pretext/dist/layout.d.ts +70 -0
  91. package/dist/node_modules/@chenglou/pretext/dist/layout.js +496 -0
  92. package/dist/node_modules/@chenglou/pretext/dist/line-break.d.ts +36 -0
  93. package/dist/node_modules/@chenglou/pretext/dist/line-break.js +820 -0
  94. package/dist/node_modules/@chenglou/pretext/dist/measurement.d.ts +28 -0
  95. package/dist/node_modules/@chenglou/pretext/dist/measurement.js +219 -0
  96. package/dist/node_modules/@chenglou/pretext/dist/rich-inline.d.ts +51 -0
  97. package/dist/node_modules/@chenglou/pretext/dist/rich-inline.js +401 -0
  98. package/package.json +42 -0
@@ -0,0 +1,326 @@
1
+ export function createObstacleFlowRuntime(options) {
2
+ const {
3
+ prepareWithSegments,
4
+ layoutNextLineRange,
5
+ obstacleGap,
6
+ minSlotWidth,
7
+ renderLine,
8
+ renderImage,
9
+ } = options;
10
+
11
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
12
+
13
+ function compareCursor(a, b) {
14
+ if (a.segmentIndex !== b.segmentIndex) return a.segmentIndex - b.segmentIndex;
15
+ return a.graphemeIndex - b.graphemeIndex;
16
+ }
17
+
18
+ function clampIntervals(intervals, width) {
19
+ const sorted = intervals
20
+ .map(interval => ({
21
+ left: Math.max(0, interval.left),
22
+ right: Math.min(width, interval.right),
23
+ }))
24
+ .filter(interval => interval.right - interval.left >= minSlotWidth)
25
+ .sort((a, b) => a.left - b.left);
26
+
27
+ const merged = [];
28
+ for (const interval of sorted) {
29
+ const last = merged[merged.length - 1];
30
+ if (last && interval.left <= last.right) {
31
+ last.right = Math.max(last.right, interval.right);
32
+ continue;
33
+ }
34
+ merged.push({ ...interval });
35
+ }
36
+
37
+ return merged.map(interval => ({ left: interval.left, width: interval.right - interval.left }));
38
+ }
39
+
40
+ function getIntervalsForBand(y, lineHeight, width, obstacles) {
41
+ let intervals = [{ left: 0, right: width }];
42
+ const bandTop = y;
43
+ const bandBottom = y + lineHeight;
44
+
45
+ for (const obstacle of obstacles) {
46
+ const obstacleTop = obstacle.y;
47
+ const obstacleBottom = obstacle.y + (obstacle.occupiedHeight ?? obstacle.height);
48
+ if (bandBottom <= obstacleTop || bandTop >= obstacleBottom) continue;
49
+
50
+ const next = [];
51
+ for (const interval of intervals) {
52
+ const obstacleLeft = Math.max(interval.left, obstacle.x - obstacleGap);
53
+ const obstacleRight = Math.min(interval.right, obstacle.x + obstacle.width + obstacleGap);
54
+ if (obstacleRight <= interval.left || obstacleLeft >= interval.right) {
55
+ next.push(interval);
56
+ continue;
57
+ }
58
+ if (obstacleLeft - interval.left >= minSlotWidth) {
59
+ next.push({ left: interval.left, right: obstacleLeft });
60
+ }
61
+ if (interval.right - obstacleRight >= minSlotWidth) {
62
+ next.push({ left: obstacleRight, right: interval.right });
63
+ }
64
+ }
65
+ intervals = next.length > 0 ? next : intervals;
66
+ }
67
+
68
+ const clamped = clampIntervals(intervals, width);
69
+ return clamped.length > 0 ? clamped : [{ left: 0, width }];
70
+ }
71
+
72
+ function chooseLine(prepared, cursor, intervals) {
73
+ let best = null;
74
+ for (const interval of intervals) {
75
+ const line = layoutNextLineRange(prepared, cursor, interval.width);
76
+ if (line === null) continue;
77
+ if (best === null || compareCursor(line.end, best.line.end) > 0) {
78
+ best = { line, interval };
79
+ }
80
+ }
81
+ return best;
82
+ }
83
+
84
+ function applyTextIndent(intervals, indent) {
85
+ if (!indent || indent <= 0) return intervals;
86
+ return intervals
87
+ .map(interval => ({
88
+ left: interval.left + indent,
89
+ width: interval.width - indent,
90
+ }))
91
+ .filter(interval => interval.width >= minSlotWidth);
92
+ }
93
+
94
+ function segmentToGraphemes(text) {
95
+ return Array.from(graphemeSegmenter.segment(text), entry => entry.segment);
96
+ }
97
+
98
+ function materializeLineText(prepared, line) {
99
+ if (line.start.segmentIndex === line.end.segmentIndex) {
100
+ const segment = prepared.segments[line.start.segmentIndex] || "";
101
+ const graphemes = segmentToGraphemes(segment);
102
+ return graphemes.slice(line.start.graphemeIndex, line.end.graphemeIndex).join("");
103
+ }
104
+
105
+ const parts = [];
106
+ for (let segmentIndex = line.start.segmentIndex; segmentIndex <= line.end.segmentIndex; segmentIndex++) {
107
+ const segment = prepared.segments[segmentIndex] || "";
108
+ const graphemes = segmentToGraphemes(segment);
109
+ if (segmentIndex === line.start.segmentIndex) {
110
+ parts.push(graphemes.slice(line.start.graphemeIndex).join(""));
111
+ continue;
112
+ }
113
+ if (segmentIndex === line.end.segmentIndex) {
114
+ parts.push(graphemes.slice(0, line.end.graphemeIndex).join(""));
115
+ continue;
116
+ }
117
+ parts.push(segment);
118
+ }
119
+ return parts.join("");
120
+ }
121
+
122
+ function normalizeObstacles(width, height, bodyImages) {
123
+ const obstacles = [];
124
+ for (const bodyImage of bodyImages) {
125
+ const obstacle = {
126
+ x: Math.max(0, Math.min(width - bodyImage.width, bodyImage.x)),
127
+ y: Math.max(0, Math.min(height - bodyImage.height, bodyImage.y)),
128
+ width: Math.min(width, bodyImage.width),
129
+ height: Math.min(height, bodyImage.height),
130
+ occupiedHeight: Math.min(height, bodyImage.height),
131
+ src: bodyImage.src,
132
+ alt: bodyImage.alt || "body image",
133
+ caption: bodyImage.caption || "",
134
+ captionHeight: bodyImage.caption ? 50 : 0,
135
+ };
136
+ obstacles.push(obstacle);
137
+ }
138
+ return obstacles;
139
+ }
140
+
141
+ function toCaptionLines(obstacles, width, height) {
142
+ const lines = [];
143
+ for (const obstacle of obstacles) {
144
+ if (!obstacle.caption) continue;
145
+ const captionY = obstacle.y + obstacle.height + 10;
146
+ if (captionY + 40 > height) continue;
147
+ lines.push({
148
+ text: obstacle.caption,
149
+ x: obstacle.x,
150
+ y: captionY,
151
+ className: "body-caption",
152
+ maxWidth: Math.min(obstacle.width, width - obstacle.x),
153
+ });
154
+ obstacle.occupiedHeight += obstacle.captionHeight + 10;
155
+ }
156
+ return lines;
157
+ }
158
+
159
+ function layoutPage(preparedBlocks, width, height, bodyImages, startBlockIndex, startCursor) {
160
+ const obstacles = normalizeObstacles(width, height, bodyImages);
161
+ const captionLines = toCaptionLines(obstacles, width, height);
162
+ const lines = [];
163
+ let y = 0;
164
+ let currentBlockIndex = startBlockIndex;
165
+ let cursor = startCursor;
166
+ let progressed = false;
167
+
168
+ for (let index = startBlockIndex; index < preparedBlocks.length; index++) {
169
+ const block = preparedBlocks[index];
170
+ currentBlockIndex = index;
171
+ const textIndent = block.textIndent ?? 0;
172
+ let firstLineOfBlock = cursor.segmentIndex === 0 && cursor.graphemeIndex === 0;
173
+ if (cursor.segmentIndex === 0 && cursor.graphemeIndex === 0) {
174
+ y += block.gapBefore ?? 0;
175
+ }
176
+
177
+ while (true) {
178
+ if (y + block.lineHeight > height) {
179
+ return {
180
+ lines,
181
+ images: obstacles,
182
+ captionLines,
183
+ nextBlockIndex: currentBlockIndex,
184
+ nextCursor: cursor,
185
+ done: false,
186
+ progressed,
187
+ textBottom: lines.length > 0 ? lines[lines.length - 1].y : 0,
188
+ };
189
+ }
190
+ const intervals = applyTextIndent(
191
+ getIntervalsForBand(y, block.lineHeight, width, obstacles),
192
+ textIndent,
193
+ );
194
+ const choice = chooseLine(block.prepared, cursor, intervals);
195
+ if (choice === null || compareCursor(choice.line.end, cursor) <= 0) break;
196
+ lines.push({
197
+ text: materializeLineText(block.prepared, choice.line),
198
+ x: choice.interval.left,
199
+ y,
200
+ className: block.className,
201
+ bullet: firstLineOfBlock ? block.bullet : undefined,
202
+ bulletX: firstLineOfBlock && textIndent > 0 ? choice.interval.left - textIndent : undefined,
203
+ });
204
+ cursor = choice.line.end;
205
+ y += block.lineHeight;
206
+ progressed = true;
207
+ firstLineOfBlock = false;
208
+ }
209
+
210
+ const intervals = applyTextIndent(
211
+ getIntervalsForBand(y, block.lineHeight, width, obstacles),
212
+ textIndent,
213
+ );
214
+ const nextChoice = y + block.lineHeight <= height ? chooseLine(block.prepared, cursor, intervals) : null;
215
+ if (nextChoice !== null && compareCursor(nextChoice.line.end, cursor) > 0) {
216
+ return {
217
+ lines,
218
+ images: obstacles,
219
+ captionLines,
220
+ nextBlockIndex: currentBlockIndex,
221
+ nextCursor: cursor,
222
+ done: false,
223
+ progressed,
224
+ textBottom: lines.length > 0 ? lines[lines.length - 1].y : 0,
225
+ };
226
+ }
227
+
228
+ y += block.gapAfter ?? 0;
229
+ cursor = { segmentIndex: 0, graphemeIndex: 0 };
230
+ currentBlockIndex = index + 1;
231
+ if (y > height) {
232
+ return {
233
+ lines,
234
+ images: obstacles,
235
+ captionLines,
236
+ nextBlockIndex: currentBlockIndex,
237
+ nextCursor: cursor,
238
+ done: false,
239
+ progressed,
240
+ textBottom: lines.length > 0 ? lines[lines.length - 1].y : 0,
241
+ };
242
+ }
243
+ }
244
+
245
+ // textBottom: y position after the last rendered line (before gapAfter of last block)
246
+ const textBottom = lines.length > 0 ? lines[lines.length - 1].y : 0;
247
+ return {
248
+ lines,
249
+ images: obstacles,
250
+ captionLines,
251
+ nextBlockIndex: preparedBlocks.length,
252
+ nextCursor: { segmentIndex: 0, graphemeIndex: 0 },
253
+ done: true,
254
+ progressed,
255
+ textBottom,
256
+ };
257
+ }
258
+
259
+ function layoutBlocks(blocks, width, height, bodyImages) {
260
+ const preparedBlocks = blocks.map(block => ({
261
+ ...block,
262
+ prepared: prepareWithSegments(block.text, block.font, { whiteSpace: "pre-wrap" }),
263
+ }));
264
+ const result = layoutPage(
265
+ preparedBlocks,
266
+ width,
267
+ height,
268
+ bodyImages,
269
+ 0,
270
+ { segmentIndex: 0, graphemeIndex: 0 },
271
+ );
272
+ for (const obstacle of result.images) {
273
+ renderImage(obstacle);
274
+ }
275
+ for (const line of result.captionLines) {
276
+ renderLine(line.text, line.x, line.y, {
277
+ className: line.className,
278
+ maxWidth: line.maxWidth,
279
+ });
280
+ }
281
+ for (const line of result.lines) {
282
+ renderLine(line.text, line.x, line.y, {
283
+ className: line.className,
284
+ });
285
+ }
286
+ }
287
+
288
+ function paginateBlocks(blocks, width, height, bodyImages, options = {}) {
289
+ const preparedBlocks = blocks.map(block => ({
290
+ ...block,
291
+ prepared: prepareWithSegments(block.text, block.font, { whiteSpace: "pre-wrap" }),
292
+ }));
293
+ const pages = [];
294
+ let blockIndex = 0;
295
+ let cursor = { segmentIndex: 0, graphemeIndex: 0 };
296
+ let imageIndex = 0;
297
+ const pageImageLimit = typeof options.pageImageLimit === "number" ? options.pageImageLimit : 2;
298
+ const pageImageGroups = Array.isArray(options.pageImageGroups) ? options.pageImageGroups : null;
299
+
300
+ while (blockIndex < blocks.length) {
301
+ const pageImages = pageImageGroups
302
+ ? pageImageGroups[pages.length] ?? []
303
+ : bodyImages.slice(imageIndex, imageIndex + pageImageLimit);
304
+ const result = layoutPage(preparedBlocks, width, height, pageImages, blockIndex, cursor);
305
+ if (!result.progressed && result.lines.length === 0) break;
306
+ pages.push({
307
+ lines: [...result.lines, ...result.captionLines],
308
+ images: result.images,
309
+ textBottom: result.textBottom ?? 0,
310
+ });
311
+ blockIndex = result.nextBlockIndex;
312
+ cursor = result.nextCursor;
313
+ if (!pageImageGroups) {
314
+ imageIndex += pageImages.length;
315
+ }
316
+ if (result.done) break;
317
+ }
318
+
319
+ return pages;
320
+ }
321
+
322
+ return {
323
+ layoutBlocks,
324
+ paginateBlocks,
325
+ };
326
+ }
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
2
+ <!-- 四叶草 - 四个圆形叶片 + 茎 -->
3
+ <!-- 左叶 -->
4
+ <ellipse cx="35" cy="50" rx="18" ry="13" fill="#22a854" transform="rotate(-45 35 50)"/>
5
+ <!-- 右叶 -->
6
+ <ellipse cx="65" cy="50" rx="18" ry="13" fill="#22a854" transform="rotate(45 65 50)"/>
7
+ <!-- 上叶 -->
8
+ <ellipse cx="50" cy="35" rx="13" ry="18" fill="#27c25e" transform="rotate(45 50 35)"/>
9
+ <!-- 下叶 -->
10
+ <ellipse cx="50" cy="65" rx="13" ry="18" fill="#27c25e" transform="rotate(-45 50 65)"/>
11
+ <!-- 中心 -->
12
+ <circle cx="50" cy="50" r="7" fill="#1a8a40"/>
13
+ <!-- 茎 -->
14
+ <path d="M50 57 Q55 70 52 82" stroke="#1a8a40" stroke-width="3" fill="none" stroke-linecap="round"/>
15
+ </svg>
@@ -0,0 +1,22 @@
1
+ <svg viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- Lobster Claw Silhouette -->
3
+ <path d="M60 10 C30 10 15 35 15 55 C15 75 30 95 45 100 L45 110 L55 110 L55 100 C55 100 60 102 65 100 L65 110 L75 110 L75 100 C90 95 105 75 105 55 C105 35 90 10 60 10Z" fill="url(#lobster-gradient)" class="claw-body"/>
4
+ <!-- Left Claw -->
5
+ <path d="M20 45 C5 40 0 50 5 60 C10 70 20 65 25 55 C28 48 25 45 20 45Z" fill="url(#lobster-gradient)" class="claw-left"/>
6
+ <!-- Right Claw -->
7
+ <path d="M100 45 C115 40 120 50 115 60 C110 70 100 65 95 55 C92 48 95 45 100 45Z" fill="url(#lobster-gradient)" class="claw-right"/>
8
+ <!-- Antenna -->
9
+ <path d="M45 15 Q35 5 30 8" stroke="#ff6b6b" stroke-width="2" stroke-linecap="round"/>
10
+ <path d="M75 15 Q85 5 90 8" stroke="#ff6b6b" stroke-width="2" stroke-linecap="round"/>
11
+ <!-- Eyes -->
12
+ <circle cx="45" cy="35" r="6" fill="#0d1117"/>
13
+ <circle cx="75" cy="35" r="6" fill="#0d1117"/>
14
+ <circle cx="46" cy="34" r="2" fill="#00e5ff"/>
15
+ <circle cx="76" cy="34" r="2" fill="#00e5ff"/>
16
+ <defs>
17
+ <linearGradient id="lobster-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
18
+ <stop offset="0%" stop-color="#ff4d4d"/>
19
+ <stop offset="100%" stop-color="#e8312a"/>
20
+ </linearGradient>
21
+ </defs>
22
+ </svg>
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="_图层_2" data-name="图层 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 446.8 446.8">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill: #ccc;
7
+ }
8
+
9
+ .cls-2 {
10
+ fill: none;
11
+ stroke: #ccc;
12
+ stroke-miterlimit: 10;
13
+ stroke-width: 10px;
14
+ }
15
+ </style>
16
+ </defs>
17
+ <g id="_图层_1-2" data-name="图层 1">
18
+ <path class="cls-2" d="M55.51,363.1C23.97,325.24,5,276.54,5,223.4,5,102.78,102.78,5,223.4,5c54.85,0,104.98,20.22,143.34,53.61"/>
19
+ <path class="cls-2" d="M387.72,79.53c33.67,38.43,54.08,88.77,54.08,143.87,0,120.62-97.78,218.4-218.4,218.4-58.26,0-111.2-22.82-150.36-60"/>
20
+ <g>
21
+ <path class="cls-1" d="M89.56,81.07v46.78c0,4.2,3.94,7.6,8.8,7.6h132.97c7.57,0,11.61,7.71,6.61,12.62l-157.95,155.15c-1.41,1.39-2.19,3.17-2.19,5.02v12.26c0,6.92,9.85,10.24,15.28,5.15l255.31-239.42c5.19-4.87,1.19-12.75-6.48-12.75H98.36c-4.86,0-8.8,3.4-8.8,7.6Z"/>
22
+ <path class="cls-1" d="M363.12,133.88v-16.59c0-6.92-9.85-10.24-15.28-5.15l-255.69,239.78c-5.19,4.87-1.19,12.75,6.48,12.75h261.57c4.86,0,8.8-3.4,8.8-7.6v-47.32c0-4.2-3.94-7.6-8.8-7.6h-148.54c-7.61,0-11.63-7.77-6.56-12.66l155.79-150.55c1.44-1.39,2.24-3.2,2.24-5.06Z"/>
23
+ </g>
24
+ </g>
25
+ </svg>