@graphrefly/graphrefly 0.24.0 → 0.25.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 (33) hide show
  1. package/dist/{chunk-IPLKX3L2.js → chunk-EVR6UFUV.js} +2 -2
  2. package/dist/{chunk-5WGT55R4.js → chunk-IAHGTNOZ.js} +4 -2
  3. package/dist/{chunk-5WGT55R4.js.map → chunk-IAHGTNOZ.js.map} +1 -1
  4. package/dist/{chunk-AOCBDH4T.js → chunk-L2GLW2U7.js} +68 -1
  5. package/dist/chunk-L2GLW2U7.js.map +1 -0
  6. package/dist/{chunk-TDEXAMGO.js → chunk-TKE3JGOH.js} +488 -16
  7. package/dist/chunk-TKE3JGOH.js.map +1 -0
  8. package/dist/compat/nestjs/index.cjs.map +1 -1
  9. package/dist/compat/nestjs/index.js +2 -2
  10. package/dist/extra/index.cjs +68 -0
  11. package/dist/extra/index.cjs.map +1 -1
  12. package/dist/extra/index.d.cts +1 -1
  13. package/dist/extra/index.d.ts +1 -1
  14. package/dist/extra/index.js +4 -2
  15. package/dist/{index-1z8vRTCt.d.cts → index-Ch0IpIO0.d.cts} +29 -2
  16. package/dist/{index-b5BYtczN.d.cts → index-DKE1EATr.d.cts} +222 -2
  17. package/dist/{index-BysCTzJz.d.ts → index-Ds23Wvou.d.ts} +29 -2
  18. package/dist/{index-D7XgsUt7.d.ts → index-OXImXMq6.d.ts} +222 -2
  19. package/dist/index.cjs +550 -15
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +3 -3
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.js +6 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/patterns/reactive-layout/index.cjs +488 -16
  26. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  27. package/dist/patterns/reactive-layout/index.d.cts +1 -1
  28. package/dist/patterns/reactive-layout/index.d.ts +1 -1
  29. package/dist/patterns/reactive-layout/index.js +16 -4
  30. package/package.json +1 -1
  31. package/dist/chunk-AOCBDH4T.js.map +0 -1
  32. package/dist/chunk-TDEXAMGO.js.map +0 -1
  33. /package/dist/{chunk-IPLKX3L2.js.map → chunk-EVR6UFUV.js.map} +0 -0
@@ -27,14 +27,20 @@ __export(reactive_layout_exports, {
27
27
  PrecomputedAdapter: () => PrecomputedAdapter,
28
28
  SvgBoundsAdapter: () => SvgBoundsAdapter,
29
29
  analyzeAndMeasure: () => analyzeAndMeasure,
30
+ carveTextLineSlots: () => carveTextLineSlots,
31
+ circleIntervalForBand: () => circleIntervalForBand,
30
32
  computeBlockFlow: () => computeBlockFlow,
31
33
  computeCharPositions: () => computeCharPositions,
34
+ computeFlowLines: () => computeFlowLines,
32
35
  computeLineBreaks: () => computeLineBreaks,
33
36
  computeTotalHeight: () => computeTotalHeight,
37
+ layoutNextLine: () => layoutNextLine,
34
38
  measureBlock: () => measureBlock,
35
39
  measureBlocks: () => measureBlocks,
36
40
  reactiveBlockLayout: () => reactiveBlockLayout,
37
- reactiveLayout: () => reactiveLayout
41
+ reactiveFlowLayout: () => reactiveFlowLayout,
42
+ reactiveLayout: () => reactiveLayout,
43
+ rectIntervalForBand: () => rectIntervalForBand
38
44
  });
39
45
  module.exports = __toCommonJS(reactive_layout_exports);
40
46
 
@@ -4958,7 +4964,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
4958
4964
  const normalized = normalizeWhitespace(text);
4959
4965
  if (normalized.length === 0) return [];
4960
4966
  const pieces = segmentText(normalized);
4961
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
4967
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
4962
4968
  granularity: "grapheme"
4963
4969
  });
4964
4970
  const rawTexts = [];
@@ -5002,7 +5008,8 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
5002
5008
  let w = fontCache.get(seg);
5003
5009
  if (w === void 0) {
5004
5010
  if (stats) stats.misses += 1;
5005
- w = adapter.measureSegment(seg, font).width;
5011
+ const raw = adapter.measureSegment(seg, font).width;
5012
+ w = Number.isFinite(raw) && raw >= 0 ? raw : 0;
5006
5013
  fontCache.set(seg, w);
5007
5014
  } else if (stats) {
5008
5015
  stats.hits += 1;
@@ -5024,7 +5031,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
5024
5031
  }
5025
5032
  if (isCJK(t)) {
5026
5033
  let unitText = "";
5027
- for (const gs of graphemeSegmenter.segment(t)) {
5034
+ for (const gs of graphemeSegmenter2.segment(t)) {
5028
5035
  const grapheme = gs.segment;
5029
5036
  if (unitText.length > 0 && kinsokuStart.has(grapheme)) {
5030
5037
  unitText += grapheme;
@@ -5056,7 +5063,7 @@ function analyzeAndMeasure(text, font, adapter, cache, stats) {
5056
5063
  let graphemeWidths = null;
5057
5064
  if (mergedWordLike[i] && t.length > 1) {
5058
5065
  const gWidths = [];
5059
- for (const gs of graphemeSegmenter.segment(t)) {
5066
+ for (const gs of graphemeSegmenter2.segment(t)) {
5060
5067
  gWidths.push(measureCached(gs.segment));
5061
5068
  }
5062
5069
  if (gWidths.length > 1) {
@@ -5096,10 +5103,10 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5096
5103
  const seg = segments[i];
5097
5104
  if (seg.kind === "soft-hyphen" || seg.kind === "hard-break") continue;
5098
5105
  if (i === lineStartSeg && lineStartGrapheme > 0 && seg.graphemeWidths) {
5099
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
5106
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
5100
5107
  granularity: "grapheme"
5101
5108
  });
5102
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
5109
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
5103
5110
  text += graphemes.slice(lineStartGrapheme).join("");
5104
5111
  } else {
5105
5112
  text += seg.text;
@@ -5107,10 +5114,10 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5107
5114
  }
5108
5115
  if (endGrapheme > 0 && endSeg < segments.length) {
5109
5116
  const seg = segments[endSeg];
5110
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
5117
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
5111
5118
  granularity: "grapheme"
5112
5119
  });
5113
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
5120
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
5114
5121
  const startG = lineStartSeg === endSeg ? lineStartGrapheme : 0;
5115
5122
  text += graphemes.slice(startG, endGrapheme).join("");
5116
5123
  }
@@ -5130,7 +5137,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5130
5137
  pendingBreakSeg = -1;
5131
5138
  pendingBreakWidth = 0;
5132
5139
  }
5133
- function canBreakAfter(kind) {
5140
+ function canBreakAfter2(kind) {
5134
5141
  return kind === "space" || kind === "zero-width-break" || kind === "soft-hyphen";
5135
5142
  }
5136
5143
  function startLine(segIdx, graphemeIdx, width) {
@@ -5175,7 +5182,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5175
5182
  } else {
5176
5183
  startLine(i, 0, w);
5177
5184
  }
5178
- if (canBreakAfter(seg.kind)) {
5185
+ if (canBreakAfter2(seg.kind)) {
5179
5186
  pendingBreakSeg = i + 1;
5180
5187
  pendingBreakWidth = seg.kind === "space" ? lineW - w : lineW;
5181
5188
  }
@@ -5183,7 +5190,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5183
5190
  }
5184
5191
  const newW = lineW + w;
5185
5192
  if (newW > maxWidth + 5e-3) {
5186
- if (canBreakAfter(seg.kind)) {
5193
+ if (canBreakAfter2(seg.kind)) {
5187
5194
  lineW += w;
5188
5195
  lineEndSeg = i + 1;
5189
5196
  lineEndGrapheme = 0;
@@ -5207,7 +5214,7 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5207
5214
  lineW = newW;
5208
5215
  lineEndSeg = i + 1;
5209
5216
  lineEndGrapheme = 0;
5210
- if (canBreakAfter(seg.kind)) {
5217
+ if (canBreakAfter2(seg.kind)) {
5211
5218
  pendingBreakSeg = i + 1;
5212
5219
  pendingBreakWidth = seg.kind === "space" ? lineW - w : lineW;
5213
5220
  }
@@ -5238,9 +5245,289 @@ function computeLineBreaks(segments, maxWidth, adapter, font, cache) {
5238
5245
  }
5239
5246
  }
5240
5247
  }
5248
+ function canBreakAfter(kind) {
5249
+ return kind === "space" || kind === "zero-width-break" || kind === "soft-hyphen";
5250
+ }
5251
+ var _graphemeSegmenter = null;
5252
+ function graphemeSegmenter() {
5253
+ if (_graphemeSegmenter === null) {
5254
+ _graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
5255
+ }
5256
+ return _graphemeSegmenter;
5257
+ }
5258
+ function sliceSegmentText(seg, startG, endG) {
5259
+ if (startG === 0 && endG < 0) return seg.text;
5260
+ const graphemes = [...graphemeSegmenter().segment(seg.text)].map((g) => g.segment);
5261
+ const stop = endG < 0 ? graphemes.length : endG;
5262
+ return graphemes.slice(startG, stop).join("");
5263
+ }
5264
+ function buildLineText(segments, startSeg, startG, endSeg, endG, appendHyphen) {
5265
+ let text = "";
5266
+ for (let i = startSeg; i < endSeg; i++) {
5267
+ const seg = segments[i];
5268
+ if (seg.kind === "soft-hyphen" || seg.kind === "hard-break") continue;
5269
+ if (i === startSeg && startG > 0) {
5270
+ text += sliceSegmentText(seg, startG, -1);
5271
+ } else {
5272
+ text += seg.text;
5273
+ }
5274
+ }
5275
+ if (endG > 0 && endSeg < segments.length) {
5276
+ const seg = segments[endSeg];
5277
+ const from = startSeg === endSeg ? startG : 0;
5278
+ text += sliceSegmentText(seg, from, endG);
5279
+ }
5280
+ if (appendHyphen) text += "-";
5281
+ return text;
5282
+ }
5283
+ function resolveHyphenWidth(ctx) {
5284
+ if (!ctx || !ctx.adapter || !ctx.font) return 0;
5285
+ const cache = ctx.cache;
5286
+ if (cache) {
5287
+ let fc = cache.get(ctx.font);
5288
+ if (!fc) {
5289
+ fc = /* @__PURE__ */ new Map();
5290
+ cache.set(ctx.font, fc);
5291
+ }
5292
+ let hw = fc.get("-");
5293
+ if (hw === void 0) {
5294
+ hw = ctx.adapter.measureSegment("-", ctx.font).width;
5295
+ fc.set("-", hw);
5296
+ }
5297
+ return hw;
5298
+ }
5299
+ return ctx.adapter.measureSegment("-", ctx.font).width;
5300
+ }
5301
+ function layoutNextLine(segments, cursor, slotWidth, ctx) {
5302
+ let i = cursor.segmentIndex;
5303
+ const initialG = cursor.graphemeIndex;
5304
+ if (i >= segments.length) return null;
5305
+ if (initialG === 0) {
5306
+ while (i < segments.length) {
5307
+ const seg = segments[i];
5308
+ if (seg.kind === "hard-break") {
5309
+ return {
5310
+ text: "",
5311
+ width: 0,
5312
+ start: { segmentIndex: cursor.segmentIndex, graphemeIndex: 0 },
5313
+ end: { segmentIndex: i + 1, graphemeIndex: 0 }
5314
+ };
5315
+ }
5316
+ if (seg.kind === "space" || seg.kind === "zero-width-break" || seg.kind === "soft-hyphen") {
5317
+ i += 1;
5318
+ continue;
5319
+ }
5320
+ break;
5321
+ }
5322
+ if (i >= segments.length) return null;
5323
+ }
5324
+ const hyphenWidth = resolveHyphenWidth(ctx);
5325
+ const startSeg = i;
5326
+ const startG = i === cursor.segmentIndex ? initialG : 0;
5327
+ let lineW = 0;
5328
+ let lineEndSeg = startSeg;
5329
+ let lineEndG = 0;
5330
+ let hasContent = false;
5331
+ let pendingBreakSeg = -1;
5332
+ let pendingBreakG = 0;
5333
+ let pendingBreakWidth = 0;
5334
+ let pendingBreakSoftHyphen = false;
5335
+ const recordPending = (sIdx, gIdx, widthAtBreak, kind) => {
5336
+ pendingBreakSeg = sIdx;
5337
+ pendingBreakG = gIdx;
5338
+ pendingBreakWidth = widthAtBreak;
5339
+ pendingBreakSoftHyphen = kind === "soft-hyphen";
5340
+ };
5341
+ const consumeBreakable = (segIdx, gStart, gWidths) => {
5342
+ for (let g = gStart; g < gWidths.length; g++) {
5343
+ const gw = gWidths[g];
5344
+ if (!hasContent) {
5345
+ lineW = gw;
5346
+ lineEndSeg = segIdx;
5347
+ lineEndG = g + 1;
5348
+ hasContent = true;
5349
+ continue;
5350
+ }
5351
+ if (lineW + gw > slotWidth + 5e-3) {
5352
+ return true;
5353
+ }
5354
+ lineW += gw;
5355
+ lineEndSeg = segIdx;
5356
+ lineEndG = g + 1;
5357
+ }
5358
+ if (lineEndSeg === segIdx && lineEndG === gWidths.length) {
5359
+ lineEndSeg = segIdx + 1;
5360
+ lineEndG = 0;
5361
+ }
5362
+ return false;
5363
+ };
5364
+ if (startG > 0 && startSeg < segments.length) {
5365
+ const seg = segments[startSeg];
5366
+ if (seg.graphemeWidths) {
5367
+ const overflowed = consumeBreakable(startSeg, startG, seg.graphemeWidths);
5368
+ if (overflowed) {
5369
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
5370
+ return {
5371
+ text: text2,
5372
+ width: lineW,
5373
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5374
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5375
+ };
5376
+ }
5377
+ i = lineEndSeg;
5378
+ } else {
5379
+ }
5380
+ }
5381
+ for (; i < segments.length; ) {
5382
+ const seg = segments[i];
5383
+ if (seg.kind === "hard-break") {
5384
+ if (hasContent) {
5385
+ const endsAtSoftHyphen2 = lineEndSeg > 0 && segments[lineEndSeg - 1]?.kind === "soft-hyphen";
5386
+ const text2 = buildLineText(
5387
+ segments,
5388
+ startSeg,
5389
+ startG,
5390
+ lineEndSeg,
5391
+ lineEndG,
5392
+ endsAtSoftHyphen2
5393
+ );
5394
+ return {
5395
+ text: text2,
5396
+ width: lineW + (endsAtSoftHyphen2 ? hyphenWidth : 0),
5397
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5398
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5399
+ };
5400
+ }
5401
+ return {
5402
+ text: "",
5403
+ width: 0,
5404
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5405
+ end: { segmentIndex: i + 1, graphemeIndex: 0 }
5406
+ };
5407
+ }
5408
+ const w = seg.width;
5409
+ if (!hasContent) {
5410
+ if (w > slotWidth && seg.graphemeWidths) {
5411
+ const overflowed = consumeBreakable(i, 0, seg.graphemeWidths);
5412
+ if (overflowed) {
5413
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
5414
+ return {
5415
+ text: text2,
5416
+ width: lineW,
5417
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5418
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5419
+ };
5420
+ }
5421
+ i = lineEndSeg;
5422
+ continue;
5423
+ }
5424
+ lineW = w;
5425
+ lineEndSeg = i + 1;
5426
+ lineEndG = 0;
5427
+ hasContent = true;
5428
+ if (canBreakAfter(seg.kind)) {
5429
+ recordPending(i + 1, 0, seg.kind === "space" ? lineW - w : lineW, seg.kind);
5430
+ }
5431
+ i += 1;
5432
+ continue;
5433
+ }
5434
+ const newW = lineW + w;
5435
+ if (newW > slotWidth + 5e-3) {
5436
+ if (canBreakAfter(seg.kind)) {
5437
+ lineEndSeg = i + 1;
5438
+ lineEndG = 0;
5439
+ const endsAtSoftHyphen2 = seg.kind === "soft-hyphen";
5440
+ const finalWidth = seg.kind === "space" ? lineW : lineW + (endsAtSoftHyphen2 ? hyphenWidth : 0);
5441
+ const text3 = buildLineText(
5442
+ segments,
5443
+ startSeg,
5444
+ startG,
5445
+ lineEndSeg,
5446
+ lineEndG,
5447
+ endsAtSoftHyphen2
5448
+ );
5449
+ return {
5450
+ text: text3,
5451
+ width: finalWidth,
5452
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5453
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5454
+ };
5455
+ }
5456
+ if (pendingBreakSeg >= 0) {
5457
+ const text3 = buildLineText(
5458
+ segments,
5459
+ startSeg,
5460
+ startG,
5461
+ pendingBreakSeg,
5462
+ pendingBreakG,
5463
+ pendingBreakSoftHyphen
5464
+ );
5465
+ return {
5466
+ text: text3,
5467
+ width: pendingBreakWidth + (pendingBreakSoftHyphen ? hyphenWidth : 0),
5468
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5469
+ end: { segmentIndex: pendingBreakSeg, graphemeIndex: pendingBreakG }
5470
+ };
5471
+ }
5472
+ if (w > slotWidth && seg.graphemeWidths) {
5473
+ const text3 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
5474
+ return {
5475
+ text: text3,
5476
+ width: lineW,
5477
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5478
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5479
+ };
5480
+ }
5481
+ const text2 = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);
5482
+ return {
5483
+ text: text2,
5484
+ width: lineW,
5485
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5486
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5487
+ };
5488
+ }
5489
+ lineW = newW;
5490
+ lineEndSeg = i + 1;
5491
+ lineEndG = 0;
5492
+ if (canBreakAfter(seg.kind)) {
5493
+ recordPending(i + 1, 0, seg.kind === "space" ? lineW - w : lineW, seg.kind);
5494
+ }
5495
+ i += 1;
5496
+ }
5497
+ if (!hasContent) return null;
5498
+ const endsAtSoftHyphen = lineEndSeg > 0 && segments[lineEndSeg - 1]?.kind === "soft-hyphen";
5499
+ const text = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, endsAtSoftHyphen);
5500
+ return {
5501
+ text,
5502
+ width: lineW + (endsAtSoftHyphen ? hyphenWidth : 0),
5503
+ start: { segmentIndex: startSeg, graphemeIndex: startG },
5504
+ end: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG }
5505
+ };
5506
+ }
5507
+ function carveTextLineSlots(base, blocked, minSlotWidth = 0) {
5508
+ let slots = [base];
5509
+ for (let bi = 0; bi < blocked.length; bi++) {
5510
+ const block = blocked[bi];
5511
+ const next = [];
5512
+ for (let si = 0; si < slots.length; si++) {
5513
+ const slot = slots[si];
5514
+ if (block.right <= slot.left || block.left >= slot.right) {
5515
+ next.push(slot);
5516
+ continue;
5517
+ }
5518
+ if (block.left > slot.left) next.push({ left: slot.left, right: block.left });
5519
+ if (block.right < slot.right) next.push({ left: block.right, right: slot.right });
5520
+ }
5521
+ slots = next;
5522
+ }
5523
+ if (minSlotWidth > 0) {
5524
+ return slots.filter((s) => s.right - s.left >= minSlotWidth);
5525
+ }
5526
+ return slots;
5527
+ }
5241
5528
  function computeCharPositions(lineBreaks, segments, lineHeight) {
5242
5529
  const positions = [];
5243
- const graphemeSegmenter = new Intl.Segmenter(void 0, {
5530
+ const graphemeSegmenter2 = new Intl.Segmenter(void 0, {
5244
5531
  granularity: "grapheme"
5245
5532
  });
5246
5533
  for (let lineIdx = 0; lineIdx < lineBreaks.lines.length; lineIdx++) {
@@ -5253,7 +5540,7 @@ function computeCharPositions(lineBreaks, segments, lineHeight) {
5253
5540
  if (si >= line.endSegment && line.endGrapheme === 0) break;
5254
5541
  continue;
5255
5542
  }
5256
- const graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);
5543
+ const graphemes = [...graphemeSegmenter2.segment(seg.text)].map((g) => g.segment);
5257
5544
  if (graphemes.length === 0) continue;
5258
5545
  const startG = si === line.startSegment ? line.startGrapheme : 0;
5259
5546
  let endG;
@@ -5609,6 +5896,185 @@ function reactiveBlockLayout(opts) {
5609
5896
  totalHeight: totalHeightNode
5610
5897
  };
5611
5898
  }
5899
+
5900
+ // src/patterns/reactive-layout/reactive-flow-layout.ts
5901
+ function circleIntervalForBand(o, bandTop, bandBottom) {
5902
+ const hPad = o.hPad ?? 0;
5903
+ const vPad = o.vPad ?? 0;
5904
+ const top = bandTop - vPad;
5905
+ const bottom = bandBottom + vPad;
5906
+ if (top >= o.cy + o.r || bottom <= o.cy - o.r) return null;
5907
+ const minDy = o.cy >= top && o.cy <= bottom ? 0 : o.cy < top ? top - o.cy : o.cy - bottom;
5908
+ if (minDy >= o.r) return null;
5909
+ const maxDx = Math.sqrt(o.r * o.r - minDy * minDy);
5910
+ return { left: o.cx - maxDx - hPad, right: o.cx + maxDx + hPad };
5911
+ }
5912
+ function rectIntervalForBand(o, bandTop, bandBottom) {
5913
+ const hPad = o.hPad ?? 0;
5914
+ const vPad = o.vPad ?? 0;
5915
+ if (bandBottom <= o.y - vPad) return null;
5916
+ if (bandTop >= o.y + o.h + vPad) return null;
5917
+ return { left: o.x - hPad, right: o.x + o.w + hPad };
5918
+ }
5919
+ function obstacleIntervalForBand(o, bandTop, bandBottom) {
5920
+ return o.kind === "circle" ? circleIntervalForBand(o, bandTop, bandBottom) : rectIntervalForBand(o, bandTop, bandBottom);
5921
+ }
5922
+ function computeFlowLines(segments, container, columns, obstacles, lineHeight, minSlotWidth) {
5923
+ const lines = [];
5924
+ let cursor = { segmentIndex: 0, graphemeIndex: 0 };
5925
+ if (segments.length === 0 || columns.count <= 0 || lineHeight <= 0) {
5926
+ return { lines, cursor };
5927
+ }
5928
+ const padX = container.paddingX ?? 0;
5929
+ const padY = container.paddingY ?? 0;
5930
+ const availWidth = Math.max(0, container.width - padX * 2);
5931
+ const availHeight = Math.max(0, container.height - padY * 2);
5932
+ const gapTotal = columns.gap * Math.max(0, columns.count - 1);
5933
+ const colWidth = Math.max(0, (availWidth - gapTotal) / columns.count);
5934
+ if (colWidth <= 0) return { lines, cursor };
5935
+ outerCol: for (let col = 0; col < columns.count; col++) {
5936
+ const colLeft = padX + col * (colWidth + columns.gap);
5937
+ const colRight = colLeft + colWidth;
5938
+ let bandTop = padY;
5939
+ while (bandTop + lineHeight <= padY + availHeight) {
5940
+ const bandBottom = bandTop + lineHeight;
5941
+ const blocked = [];
5942
+ for (let oi = 0; oi < obstacles.length; oi++) {
5943
+ const iv = obstacleIntervalForBand(obstacles[oi], bandTop, bandBottom);
5944
+ if (iv !== null) blocked.push(iv);
5945
+ }
5946
+ const slots = carveTextLineSlots({ left: colLeft, right: colRight }, blocked, minSlotWidth);
5947
+ if (slots.length === 0) {
5948
+ bandTop += lineHeight;
5949
+ continue;
5950
+ }
5951
+ let hardBreakThisBand = false;
5952
+ for (let si = 0; si < slots.length; si++) {
5953
+ const slot = slots[si];
5954
+ const slotW = slot.right - slot.left;
5955
+ const line = layoutNextLine(segments, cursor, slotW);
5956
+ if (line === null) {
5957
+ return { lines, cursor };
5958
+ }
5959
+ if (line.text.length === 0 && line.width === 0) {
5960
+ cursor = line.end;
5961
+ hardBreakThisBand = true;
5962
+ break;
5963
+ }
5964
+ lines.push({
5965
+ x: slot.left,
5966
+ y: bandTop,
5967
+ width: line.width,
5968
+ slotWidth: slotW,
5969
+ text: line.text,
5970
+ columnIndex: col,
5971
+ flushToRight: slot.right < colRight - 0.5
5972
+ });
5973
+ cursor = line.end;
5974
+ }
5975
+ bandTop += lineHeight;
5976
+ if (hardBreakThisBand) continue;
5977
+ if (cursor.segmentIndex >= segments.length) break outerCol;
5978
+ }
5979
+ if (cursor.segmentIndex >= segments.length) break;
5980
+ }
5981
+ return { lines, cursor };
5982
+ }
5983
+ function reactiveFlowLayout(opts) {
5984
+ const { adapter, name = "reactive-flow-layout", minSlotWidth = 20 } = opts;
5985
+ const g = new Graph(name);
5986
+ const measureCache = /* @__PURE__ */ new Map();
5987
+ const textNode = state(opts.text ?? "", { name: "text" });
5988
+ const fontNode = state(opts.font ?? "16px sans-serif", { name: "font" });
5989
+ const lineHeightNode = state(opts.lineHeight ?? 20, { name: "line-height" });
5990
+ const containerNode = state(
5991
+ opts.container ?? { width: 800, height: 600, paddingX: 0, paddingY: 0 },
5992
+ { name: "container" }
5993
+ );
5994
+ const columnsNode = state(opts.columns ?? { count: 1, gap: 0 }, {
5995
+ name: "columns"
5996
+ });
5997
+ const obstaclesNode = state(opts.obstacles ?? [], { name: "obstacles" });
5998
+ const segmentsNode = node(
5999
+ [textNode, fontNode],
6000
+ (data, actions, ctx) => {
6001
+ const b0 = data[0];
6002
+ const textVal = b0 != null && b0.length > 0 ? b0.at(-1) : ctx.prevData[0];
6003
+ const b1 = data[1];
6004
+ const fontVal = b1 != null && b1.length > 0 ? b1.at(-1) : ctx.prevData[1];
6005
+ const result = analyzeAndMeasure(textVal, fontVal, adapter, measureCache);
6006
+ actions.emit(result);
6007
+ return () => {
6008
+ measureCache.clear();
6009
+ adapter.clearCache?.();
6010
+ };
6011
+ },
6012
+ { name: "segments", describeKind: "derived" }
6013
+ );
6014
+ const flowLinesNode = derived(
6015
+ [segmentsNode, containerNode, columnsNode, obstaclesNode, lineHeightNode],
6016
+ ([segs, cont, cols, obs, lh]) => {
6017
+ const segments = segs;
6018
+ const t0 = monotonicNs();
6019
+ const { lines: result, cursor } = computeFlowLines(
6020
+ segments,
6021
+ cont,
6022
+ cols,
6023
+ obs,
6024
+ lh,
6025
+ minSlotWidth
6026
+ );
6027
+ const elapsed = monotonicNs() - t0;
6028
+ const overflow = Math.max(0, segments.length - cursor.segmentIndex);
6029
+ const meta = flowLinesNode.meta;
6030
+ if (meta) {
6031
+ emitToMeta(meta["line-count"], result.length);
6032
+ emitToMeta(meta["layout-time-ns"], elapsed);
6033
+ emitToMeta(meta["overflow-segments"], overflow);
6034
+ }
6035
+ return result;
6036
+ },
6037
+ {
6038
+ name: "flow-lines",
6039
+ meta: {
6040
+ "line-count": 0,
6041
+ "layout-time-ns": 0,
6042
+ "overflow-segments": 0
6043
+ },
6044
+ equals: (a, b) => {
6045
+ const la = a;
6046
+ const lb = b;
6047
+ if (la.length !== lb.length) return false;
6048
+ for (let i = 0; i < la.length; i++) {
6049
+ const pa = la[i];
6050
+ const pb = lb[i];
6051
+ if (pa.x !== pb.x || pa.y !== pb.y || pa.width !== pb.width || pa.slotWidth !== pb.slotWidth || pa.text !== pb.text || pa.columnIndex !== pb.columnIndex || pa.flushToRight !== pb.flushToRight)
6052
+ return false;
6053
+ }
6054
+ return true;
6055
+ }
6056
+ }
6057
+ );
6058
+ g.add("text", textNode);
6059
+ g.add("font", fontNode);
6060
+ g.add("line-height", lineHeightNode);
6061
+ g.add("container", containerNode);
6062
+ g.add("columns", columnsNode);
6063
+ g.add("obstacles", obstaclesNode);
6064
+ g.add("segments", segmentsNode);
6065
+ g.add("flow-lines", flowLinesNode);
6066
+ return {
6067
+ graph: g,
6068
+ setText: (t) => g.set("text", t),
6069
+ setFont: (f) => g.set("font", f),
6070
+ setLineHeight: (lh) => g.set("line-height", lh),
6071
+ setContainer: (c) => g.set("container", c),
6072
+ setColumns: (c) => g.set("columns", c),
6073
+ setObstacles: (o) => g.set("obstacles", o),
6074
+ segments: segmentsNode,
6075
+ flowLines: flowLinesNode
6076
+ };
6077
+ }
5612
6078
  // Annotate the CommonJS export names for ESM import in node:
5613
6079
  0 && (module.exports = {
5614
6080
  CanvasMeasureAdapter,
@@ -5618,13 +6084,19 @@ function reactiveBlockLayout(opts) {
5618
6084
  PrecomputedAdapter,
5619
6085
  SvgBoundsAdapter,
5620
6086
  analyzeAndMeasure,
6087
+ carveTextLineSlots,
6088
+ circleIntervalForBand,
5621
6089
  computeBlockFlow,
5622
6090
  computeCharPositions,
6091
+ computeFlowLines,
5623
6092
  computeLineBreaks,
5624
6093
  computeTotalHeight,
6094
+ layoutNextLine,
5625
6095
  measureBlock,
5626
6096
  measureBlocks,
5627
6097
  reactiveBlockLayout,
5628
- reactiveLayout
6098
+ reactiveFlowLayout,
6099
+ reactiveLayout,
6100
+ rectIntervalForBand
5629
6101
  });
5630
6102
  //# sourceMappingURL=index.cjs.map