canvu-react 0.3.32 → 0.3.33

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/dist/native.cjs CHANGED
@@ -354,24 +354,153 @@ function svgNumber(value) {
354
354
  const rounded = Math.round(value * 100) / 100;
355
355
  return Number.isInteger(rounded) ? String(rounded) : String(rounded);
356
356
  }
357
- function approximateEllipsePerimeter(rx, ry) {
358
- if (rx <= 0 || ry <= 0) return 0;
359
- return Math.PI * (3 * (rx + ry) - Math.sqrt((3 * rx + ry) * (rx + 3 * ry)));
360
- }
361
- function architecturalCloudScallopCount(rx, ry, amplitude) {
362
- const perimeter = approximateEllipsePerimeter(rx, ry);
363
- const targetScallopLength = Math.max(10, amplitude * 2);
357
+ function architecturalCloudScallopCount(perimeter, amplitude) {
358
+ const targetScallopLength = Math.max(18, amplitude * 2.45);
364
359
  let count = Math.max(12, Math.round(perimeter / targetScallopLength));
365
360
  if (count % 2 === 1) count += 1;
366
361
  return count;
367
362
  }
368
- function pointOnSuperellipse(centerX, centerY, radiusX, radiusY, theta, exponent) {
369
- const cosTheta = Math.cos(theta);
370
- const sinTheta = Math.sin(theta);
371
- return [
372
- centerX + Math.sign(cosTheta) * radiusX * Math.abs(cosTheta) ** (2 / exponent),
373
- centerY + Math.sign(sinTheta) * radiusY * Math.abs(sinTheta) ** (2 / exponent)
374
- ];
363
+ function roundedRectMetrics(width, height, inset, radius) {
364
+ const left = inset;
365
+ const top = inset;
366
+ const right = width - inset;
367
+ const bottom = height - inset;
368
+ const rectWidth = Math.max(0, right - left);
369
+ const rectHeight = Math.max(0, bottom - top);
370
+ const normalizedRadius = Math.max(
371
+ 0,
372
+ Math.min(radius, rectWidth / 2, rectHeight / 2)
373
+ );
374
+ const centerX = width / 2;
375
+ const topHalfLength = Math.max(0, right - normalizedRadius - centerX);
376
+ const horizontalLength = Math.max(0, rectWidth - normalizedRadius * 2);
377
+ const verticalLength = Math.max(0, rectHeight - normalizedRadius * 2);
378
+ const arcLength = normalizedRadius * (Math.PI / 2);
379
+ return {
380
+ left,
381
+ top,
382
+ right,
383
+ bottom,
384
+ radius: normalizedRadius,
385
+ centerX,
386
+ topHalfLength,
387
+ horizontalLength,
388
+ verticalLength,
389
+ arcLength,
390
+ perimeter: horizontalLength * 2 + verticalLength * 2 + Math.PI * 2 * normalizedRadius
391
+ };
392
+ }
393
+ function pointOnLine(startX, startY, endX, endY, t) {
394
+ return [startX + (endX - startX) * t, startY + (endY - startY) * t];
395
+ }
396
+ function pointOnArc(centerX, centerY, radius, startAngle, endAngle, t) {
397
+ const theta = startAngle + (endAngle - startAngle) * t;
398
+ return [centerX + Math.cos(theta) * radius, centerY + Math.sin(theta) * radius];
399
+ }
400
+ function pointOnRoundedRectPath(metrics, distance) {
401
+ if (metrics.perimeter <= 0) return [metrics.centerX, metrics.top];
402
+ let remaining = (distance % metrics.perimeter + metrics.perimeter) % metrics.perimeter;
403
+ const consume = (length) => {
404
+ if (length <= 1e-9) return null;
405
+ if (remaining <= length) return remaining / length;
406
+ remaining -= length;
407
+ return null;
408
+ };
409
+ let t = consume(metrics.topHalfLength);
410
+ if (t != null) {
411
+ return pointOnLine(
412
+ metrics.centerX,
413
+ metrics.top,
414
+ metrics.right - metrics.radius,
415
+ metrics.top,
416
+ t
417
+ );
418
+ }
419
+ t = consume(metrics.arcLength);
420
+ if (t != null) {
421
+ return pointOnArc(
422
+ metrics.right - metrics.radius,
423
+ metrics.top + metrics.radius,
424
+ metrics.radius,
425
+ -Math.PI / 2,
426
+ 0,
427
+ t
428
+ );
429
+ }
430
+ t = consume(metrics.verticalLength);
431
+ if (t != null) {
432
+ return pointOnLine(
433
+ metrics.right,
434
+ metrics.top + metrics.radius,
435
+ metrics.right,
436
+ metrics.bottom - metrics.radius,
437
+ t
438
+ );
439
+ }
440
+ t = consume(metrics.arcLength);
441
+ if (t != null) {
442
+ return pointOnArc(
443
+ metrics.right - metrics.radius,
444
+ metrics.bottom - metrics.radius,
445
+ metrics.radius,
446
+ 0,
447
+ Math.PI / 2,
448
+ t
449
+ );
450
+ }
451
+ t = consume(metrics.horizontalLength);
452
+ if (t != null) {
453
+ return pointOnLine(
454
+ metrics.right - metrics.radius,
455
+ metrics.bottom,
456
+ metrics.left + metrics.radius,
457
+ metrics.bottom,
458
+ t
459
+ );
460
+ }
461
+ t = consume(metrics.arcLength);
462
+ if (t != null) {
463
+ return pointOnArc(
464
+ metrics.left + metrics.radius,
465
+ metrics.bottom - metrics.radius,
466
+ metrics.radius,
467
+ Math.PI / 2,
468
+ Math.PI,
469
+ t
470
+ );
471
+ }
472
+ t = consume(metrics.verticalLength);
473
+ if (t != null) {
474
+ return pointOnLine(
475
+ metrics.left,
476
+ metrics.bottom - metrics.radius,
477
+ metrics.left,
478
+ metrics.top + metrics.radius,
479
+ t
480
+ );
481
+ }
482
+ t = consume(metrics.arcLength);
483
+ if (t != null) {
484
+ return pointOnArc(
485
+ metrics.left + metrics.radius,
486
+ metrics.top + metrics.radius,
487
+ metrics.radius,
488
+ Math.PI,
489
+ Math.PI * 1.5,
490
+ t
491
+ );
492
+ }
493
+ t = consume(metrics.topHalfLength);
494
+ if (t != null) {
495
+ return pointOnLine(
496
+ metrics.left + metrics.radius,
497
+ metrics.top,
498
+ metrics.centerX,
499
+ metrics.top,
500
+ t
501
+ );
502
+ }
503
+ return [metrics.centerX, metrics.top];
375
504
  }
376
505
  function buildRectSvg(width, height, style = DEFAULT_STROKE_STYLE) {
377
506
  return `<rect width="${width}" height="${height}" fill="none" stroke="${style.stroke}" stroke-width="${style.strokeWidth}" rx="4"${strokeOpacityAttr(style)} />`;
@@ -386,35 +515,34 @@ function buildArchitecturalCloudPathD(width, height, strokeWidth = DEFAULT_STROK
386
515
  const h = Math.max(0, height);
387
516
  if (w <= 0 || h <= 0) return "";
388
517
  const inset = Math.max(0.5, strokeWidth / 2);
389
- const outerRx = Math.max(0, w / 2 - inset);
390
- const outerRy = Math.max(0, h / 2 - inset);
391
- if (outerRx <= 0 || outerRy <= 0) return "";
392
- const baseExponent = 3.6;
518
+ const outerWidth = Math.max(0, w - inset * 2);
519
+ const outerHeight = Math.max(0, h - inset * 2);
520
+ if (outerWidth <= 0 || outerHeight <= 0) return "";
393
521
  const amplitude = Math.min(
394
- outerRx * 0.45,
395
- outerRy * 0.45,
396
- Math.max(2, Math.min(7, Math.min(w, h) * 0.035))
522
+ outerWidth * 0.12,
523
+ outerHeight * 0.12,
524
+ Math.max(5, Math.min(14, Math.min(w, h) * 0.07))
397
525
  );
398
- const innerRx = Math.max(0, outerRx - amplitude);
399
- const innerRy = Math.max(0, outerRy - amplitude);
400
- const scallopCount = architecturalCloudScallopCount(innerRx, innerRy, amplitude);
401
- const angleStep = Math.PI * 2 / scallopCount;
402
- const cx = w / 2;
403
- const cy = h / 2;
404
- const startAngle = -Math.PI / 2;
405
- const pointOnArchitecturalCloud = (theta, radiusX, radiusY) => pointOnSuperellipse(cx, cy, radiusX, radiusY, theta, baseExponent);
406
- const [startX, startY] = pointOnArchitecturalCloud(startAngle, innerRx, innerRy);
526
+ const radius = Math.min(
527
+ outerWidth / 2,
528
+ outerHeight / 2,
529
+ Math.max(amplitude * 2.5, outerHeight * 0.43)
530
+ );
531
+ const outer = roundedRectMetrics(w, h, inset, radius);
532
+ const inner = roundedRectMetrics(
533
+ w,
534
+ h,
535
+ inset + amplitude,
536
+ Math.max(0, radius - amplitude)
537
+ );
538
+ const scallopCount = architecturalCloudScallopCount(outer.perimeter, amplitude);
539
+ const [startX, startY] = pointOnRoundedRectPath(inner, 0);
407
540
  const segments = [`M${svgNumber(startX)} ${svgNumber(startY)}`];
408
541
  for (let index = 0; index < scallopCount; index += 1) {
409
- const segmentStart = startAngle + index * angleStep;
410
- const segmentMid = segmentStart + angleStep / 2;
411
- const segmentEnd = segmentStart + angleStep;
412
- const [controlX, controlY] = pointOnArchitecturalCloud(
413
- segmentMid,
414
- outerRx,
415
- outerRy
416
- );
417
- const [endX, endY] = pointOnArchitecturalCloud(segmentEnd, innerRx, innerRy);
542
+ const controlDistance = (index + 0.5) / scallopCount * outer.perimeter;
543
+ const endDistance = (index + 1) / scallopCount * inner.perimeter;
544
+ const [controlX, controlY] = pointOnRoundedRectPath(outer, controlDistance);
545
+ const [endX, endY] = pointOnRoundedRectPath(inner, endDistance);
418
546
  segments.push(
419
547
  `Q${svgNumber(controlX)} ${svgNumber(controlY)} ${svgNumber(endX)} ${svgNumber(endY)}`
420
548
  );