@tsdraw/core 0.9.0 → 0.9.1

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/index.js CHANGED
@@ -559,7 +559,11 @@ function getLineDash(dash, width) {
559
559
  var DEFAULT_SPACING = 20;
560
560
  var DEFAULT_LINE_WIDTH = 0.5;
561
561
  var DEFAULT_DOT_RADIUS = 1;
562
- var DEFAULT_OPACITY = 0.25;
562
+ var DEFAULT_OPACITY = 1;
563
+ var LINE_GAP_FADE_IN = 3;
564
+ var LINE_GAP_FADE_FULL = 8;
565
+ var DOT_GAP_FADE_IN = 2;
566
+ var DOT_GAP_FADE_FULL = 16;
563
567
  function resolvePresetPatternColor(colorLight, colorDark, theme) {
564
568
  if (theme === "dark") return colorDark ?? colorLight ?? "#888888";
565
569
  return colorLight ?? "#c0c0c0";
@@ -572,56 +576,116 @@ function visiblePageRect(viewport, canvasWidth, canvasHeight) {
572
576
  maxY: (canvasHeight - viewport.y) / viewport.zoom
573
577
  };
574
578
  }
575
- function drawHorizontalLines(ctx, visible, spacing, lineWidth, color, opacity) {
579
+ function isOnGrid(value, gridSpacing) {
580
+ const remainder = (value % gridSpacing + gridSpacing) % gridSpacing;
581
+ return remainder < 0.5 || remainder > gridSpacing - 0.5;
582
+ }
583
+ function fadeForScreenGap(screenGap, fadeIn, fadeFull) {
584
+ return Math.min(1, Math.max(0, (screenGap - fadeIn) / (fadeFull - fadeIn)));
585
+ }
586
+ function buildLevels(baseSpacing, zoom, fadeIn, fadeFull) {
587
+ let topSpacing = baseSpacing;
588
+ while (topSpacing * zoom < fadeFull) {
589
+ topSpacing *= 2;
590
+ }
591
+ const levels = [];
592
+ for (let s = topSpacing; s >= baseSpacing; s /= 2) {
593
+ const fade = fadeForScreenGap(s * zoom, fadeIn, fadeFull);
594
+ if (fade < 0.01) break;
595
+ levels.push({ spacing: s, fade });
596
+ }
597
+ return levels;
598
+ }
599
+ function drawDotTile(ctx, physicalWidth, physicalHeight, panXPx, panYPx, exactTileSize, radiusPx, color, alpha) {
600
+ if (alpha < 5e-3 || exactTileSize < 2) return;
601
+ const tilePixels = Math.ceil(exactTileSize);
602
+ const tileScale = exactTileSize / tilePixels;
603
+ const center = tilePixels / 2;
604
+ const tile = new OffscreenCanvas(tilePixels, tilePixels);
605
+ const tctx = tile.getContext("2d");
606
+ tctx.fillStyle = color;
607
+ tctx.beginPath();
608
+ tctx.arc(center, center, radiusPx / tileScale, 0, Math.PI * 2);
609
+ tctx.fill();
610
+ const pattern = ctx.createPattern(tile, "repeat");
611
+ if (!pattern) return;
612
+ pattern.setTransform(
613
+ new DOMMatrix().translateSelf(panXPx - exactTileSize / 2, panYPx - exactTileSize / 2).scaleSelf(tileScale, tileScale)
614
+ );
615
+ ctx.save();
616
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
617
+ ctx.globalAlpha = alpha;
618
+ ctx.fillStyle = pattern;
619
+ ctx.fillRect(0, 0, physicalWidth, physicalHeight);
620
+ ctx.restore();
621
+ }
622
+ function drawMergingDots(ctx, viewport, canvasWidth, canvasHeight, baseSpacing, dotRadius, color, opacity) {
623
+ const dpr = ctx.getTransform().a;
624
+ const levels = buildLevels(baseSpacing, viewport.zoom, DOT_GAP_FADE_IN, DOT_GAP_FADE_FULL);
625
+ if (levels.length === 0) return;
626
+ const physicalWidth = canvasWidth * dpr;
627
+ const physicalHeight = canvasHeight * dpr;
628
+ const panXPx = viewport.x * dpr;
629
+ const panYPx = viewport.y * dpr;
630
+ const radiusPx = dotRadius * dpr;
631
+ for (const level of levels) {
632
+ const tileSize = level.spacing * viewport.zoom * dpr;
633
+ drawDotTile(ctx, physicalWidth, physicalHeight, panXPx, panYPx, tileSize, radiusPx, color, opacity * level.fade);
634
+ }
635
+ }
636
+ function drawHorizontalLinesForLevel(ctx, visible, spacing, skipSpacing, lineWidth, color, alpha) {
576
637
  const startY = Math.floor(visible.minY / spacing) * spacing;
577
638
  ctx.save();
578
639
  ctx.strokeStyle = color;
579
- ctx.lineWidth = lineWidth / ctx.getTransform().a;
580
- ctx.globalAlpha = opacity;
640
+ ctx.lineWidth = lineWidth;
641
+ ctx.globalAlpha = alpha;
581
642
  ctx.beginPath();
582
643
  for (let y = startY; y <= visible.maxY; y += spacing) {
644
+ if (skipSpacing > 0 && isOnGrid(y, skipSpacing)) continue;
583
645
  ctx.moveTo(visible.minX, y);
584
646
  ctx.lineTo(visible.maxX, y);
585
647
  }
586
648
  ctx.stroke();
587
649
  ctx.restore();
588
650
  }
589
- function drawGridLines(ctx, visible, spacing, lineWidth, color, opacity) {
651
+ function drawMergingLines(ctx, visible, baseSpacing, lineWidth, color, opacity, zoom) {
652
+ const compensatedWidth = lineWidth / ctx.getTransform().a;
653
+ const levels = buildLevels(baseSpacing, zoom, LINE_GAP_FADE_IN, LINE_GAP_FADE_FULL);
654
+ for (let i = 0; i < levels.length; i++) {
655
+ const level = levels[i];
656
+ const coarserSpacing = i > 0 ? levels[i - 1].spacing : 0;
657
+ drawHorizontalLinesForLevel(ctx, visible, level.spacing, coarserSpacing, compensatedWidth, color, opacity * level.fade);
658
+ }
659
+ }
660
+ function drawGridForLevel(ctx, visible, spacing, skipSpacing, lineWidth, color, alpha) {
590
661
  const startX = Math.floor(visible.minX / spacing) * spacing;
591
662
  const startY = Math.floor(visible.minY / spacing) * spacing;
592
- const compensatedWidth = lineWidth / ctx.getTransform().a;
593
663
  ctx.save();
594
664
  ctx.strokeStyle = color;
595
- ctx.lineWidth = compensatedWidth;
596
- ctx.globalAlpha = opacity;
665
+ ctx.lineWidth = lineWidth;
666
+ ctx.globalAlpha = alpha;
597
667
  ctx.beginPath();
598
668
  for (let x = startX; x <= visible.maxX; x += spacing) {
669
+ if (skipSpacing > 0 && isOnGrid(x, skipSpacing)) continue;
599
670
  ctx.moveTo(x, visible.minY);
600
671
  ctx.lineTo(x, visible.maxY);
601
672
  }
602
673
  for (let y = startY; y <= visible.maxY; y += spacing) {
674
+ if (skipSpacing > 0 && isOnGrid(y, skipSpacing)) continue;
603
675
  ctx.moveTo(visible.minX, y);
604
676
  ctx.lineTo(visible.maxX, y);
605
677
  }
606
678
  ctx.stroke();
607
679
  ctx.restore();
608
680
  }
609
- function drawDotPattern(ctx, visible, spacing, dotRadius, color, opacity) {
610
- const startX = Math.floor(visible.minX / spacing) * spacing;
611
- const startY = Math.floor(visible.minY / spacing) * spacing;
612
- const compensatedRadius = dotRadius / ctx.getTransform().a;
613
- ctx.save();
614
- ctx.fillStyle = color;
615
- ctx.globalAlpha = opacity;
616
- ctx.beginPath();
617
- for (let x = startX; x <= visible.maxX; x += spacing) {
618
- for (let y = startY; y <= visible.maxY; y += spacing) {
619
- ctx.moveTo(x + compensatedRadius, y);
620
- ctx.arc(x, y, compensatedRadius, 0, Math.PI * 2);
621
- }
681
+ function drawMergingGrid(ctx, visible, baseSpacing, lineWidth, color, opacity, zoom) {
682
+ const compensatedWidth = lineWidth / ctx.getTransform().a;
683
+ const levels = buildLevels(baseSpacing, zoom, LINE_GAP_FADE_IN, LINE_GAP_FADE_FULL);
684
+ for (let i = 0; i < levels.length; i++) {
685
+ const level = levels[i];
686
+ const coarserSpacing = i > 0 ? levels[i - 1].spacing : 0;
687
+ drawGridForLevel(ctx, visible, level.spacing, coarserSpacing, compensatedWidth, color, opacity * level.fade);
622
688
  }
623
- ctx.fill();
624
- ctx.restore();
625
689
  }
626
690
  function renderCanvasBackground(ctx, viewport, canvasWidth, canvasHeight, options, theme) {
627
691
  if (!options || options.type === "blank") return;
@@ -629,23 +693,24 @@ function renderCanvasBackground(ctx, viewport, canvasWidth, canvasHeight, option
629
693
  options.render(ctx, viewport, canvasWidth, canvasHeight);
630
694
  return;
631
695
  }
632
- const spacing = options.spacing ?? DEFAULT_SPACING;
633
- if (spacing <= 0) return;
696
+ const baseSpacing = options.spacing ?? DEFAULT_SPACING;
697
+ if (baseSpacing <= 0) return;
634
698
  const color = resolvePresetPatternColor(options.color, options.colorDark, theme);
635
699
  const opacity = options.opacity ?? DEFAULT_OPACITY;
700
+ if (options.type === "dots") {
701
+ drawMergingDots(ctx, viewport, canvasWidth, canvasHeight, baseSpacing, options.size ?? DEFAULT_DOT_RADIUS, color, opacity);
702
+ return;
703
+ }
636
704
  const visible = visiblePageRect(viewport, canvasWidth, canvasHeight);
637
705
  ctx.save();
638
706
  ctx.translate(viewport.x, viewport.y);
639
707
  ctx.scale(viewport.zoom, viewport.zoom);
640
708
  switch (options.type) {
641
709
  case "lines":
642
- drawHorizontalLines(ctx, visible, spacing, options.size ?? DEFAULT_LINE_WIDTH, color, opacity);
710
+ drawMergingLines(ctx, visible, baseSpacing, options.size ?? DEFAULT_LINE_WIDTH, color, opacity, viewport.zoom);
643
711
  break;
644
712
  case "grid":
645
- drawGridLines(ctx, visible, spacing, options.size ?? DEFAULT_LINE_WIDTH, color, opacity);
646
- break;
647
- case "dots":
648
- drawDotPattern(ctx, visible, spacing, options.size ?? DEFAULT_DOT_RADIUS, color, opacity);
713
+ drawMergingGrid(ctx, visible, baseSpacing, options.size ?? DEFAULT_LINE_WIDTH, color, opacity, viewport.zoom);
649
714
  break;
650
715
  }
651
716
  ctx.restore();