@tsdraw/core 0.8.5 → 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.cjs CHANGED
@@ -372,9 +372,12 @@ function resolveThemeColor(colorStyle, theme) {
372
372
  return DARK_COLORS[colorStyle] ?? lightThemeColor;
373
373
  }
374
374
  var CanvasRenderer = class {
375
- theme = "light";
375
+ _theme = "light";
376
+ get theme() {
377
+ return this._theme;
378
+ }
376
379
  setTheme(theme) {
377
- this.theme = theme;
380
+ this._theme = theme;
378
381
  }
379
382
  render(ctx, viewport, shapes) {
380
383
  ctx.save();
@@ -392,7 +395,7 @@ var CanvasRenderer = class {
392
395
  const width = (STROKE_WIDTHS[shape.props.size] ?? 3.5) * shape.props.scale;
393
396
  const samples = flattenSegments(shape);
394
397
  if (samples.length === 0) return;
395
- const color = resolveThemeColor(shape.props.color, this.theme);
398
+ const color = resolveThemeColor(shape.props.color, this._theme);
396
399
  const fillStyle = shape.props.fill ?? "none";
397
400
  if (shape.props.isClosed && fillStyle !== "none") {
398
401
  this.paintClosedShapeFill(ctx, samples, color, fillStyle);
@@ -458,7 +461,7 @@ var CanvasRenderer = class {
458
461
  ctx.fillStyle = color;
459
462
  ctx.globalAlpha = 0.55;
460
463
  } else if (fillStyle === "none") {
461
- ctx.fillStyle = this.theme === "dark" ? "#0f0f0f" : "#fafafa";
464
+ ctx.fillStyle = this._theme === "dark" ? "#0f0f0f" : "#fafafa";
462
465
  ctx.globalAlpha = 1;
463
466
  } else {
464
467
  ctx.fillStyle = color;
@@ -554,6 +557,167 @@ function getLineDash(dash, width) {
554
557
  }
555
558
  }
556
559
 
560
+ // src/canvas/backgroundRenderer.ts
561
+ var DEFAULT_SPACING = 20;
562
+ var DEFAULT_LINE_WIDTH = 0.5;
563
+ var DEFAULT_DOT_RADIUS = 1;
564
+ var DEFAULT_OPACITY = 1;
565
+ var LINE_GAP_FADE_IN = 3;
566
+ var LINE_GAP_FADE_FULL = 8;
567
+ var DOT_GAP_FADE_IN = 2;
568
+ var DOT_GAP_FADE_FULL = 16;
569
+ function resolvePresetPatternColor(colorLight, colorDark, theme) {
570
+ if (theme === "dark") return colorDark ?? colorLight ?? "#888888";
571
+ return colorLight ?? "#c0c0c0";
572
+ }
573
+ function visiblePageRect(viewport, canvasWidth, canvasHeight) {
574
+ return {
575
+ minX: (0 - viewport.x) / viewport.zoom,
576
+ minY: (0 - viewport.y) / viewport.zoom,
577
+ maxX: (canvasWidth - viewport.x) / viewport.zoom,
578
+ maxY: (canvasHeight - viewport.y) / viewport.zoom
579
+ };
580
+ }
581
+ function isOnGrid(value, gridSpacing) {
582
+ const remainder = (value % gridSpacing + gridSpacing) % gridSpacing;
583
+ return remainder < 0.5 || remainder > gridSpacing - 0.5;
584
+ }
585
+ function fadeForScreenGap(screenGap, fadeIn, fadeFull) {
586
+ return Math.min(1, Math.max(0, (screenGap - fadeIn) / (fadeFull - fadeIn)));
587
+ }
588
+ function buildLevels(baseSpacing, zoom, fadeIn, fadeFull) {
589
+ let topSpacing = baseSpacing;
590
+ while (topSpacing * zoom < fadeFull) {
591
+ topSpacing *= 2;
592
+ }
593
+ const levels = [];
594
+ for (let s = topSpacing; s >= baseSpacing; s /= 2) {
595
+ const fade = fadeForScreenGap(s * zoom, fadeIn, fadeFull);
596
+ if (fade < 0.01) break;
597
+ levels.push({ spacing: s, fade });
598
+ }
599
+ return levels;
600
+ }
601
+ function drawDotTile(ctx, physicalWidth, physicalHeight, panXPx, panYPx, exactTileSize, radiusPx, color, alpha) {
602
+ if (alpha < 5e-3 || exactTileSize < 2) return;
603
+ const tilePixels = Math.ceil(exactTileSize);
604
+ const tileScale = exactTileSize / tilePixels;
605
+ const center = tilePixels / 2;
606
+ const tile = new OffscreenCanvas(tilePixels, tilePixels);
607
+ const tctx = tile.getContext("2d");
608
+ tctx.fillStyle = color;
609
+ tctx.beginPath();
610
+ tctx.arc(center, center, radiusPx / tileScale, 0, Math.PI * 2);
611
+ tctx.fill();
612
+ const pattern = ctx.createPattern(tile, "repeat");
613
+ if (!pattern) return;
614
+ pattern.setTransform(
615
+ new DOMMatrix().translateSelf(panXPx - exactTileSize / 2, panYPx - exactTileSize / 2).scaleSelf(tileScale, tileScale)
616
+ );
617
+ ctx.save();
618
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
619
+ ctx.globalAlpha = alpha;
620
+ ctx.fillStyle = pattern;
621
+ ctx.fillRect(0, 0, physicalWidth, physicalHeight);
622
+ ctx.restore();
623
+ }
624
+ function drawMergingDots(ctx, viewport, canvasWidth, canvasHeight, baseSpacing, dotRadius, color, opacity) {
625
+ const dpr = ctx.getTransform().a;
626
+ const levels = buildLevels(baseSpacing, viewport.zoom, DOT_GAP_FADE_IN, DOT_GAP_FADE_FULL);
627
+ if (levels.length === 0) return;
628
+ const physicalWidth = canvasWidth * dpr;
629
+ const physicalHeight = canvasHeight * dpr;
630
+ const panXPx = viewport.x * dpr;
631
+ const panYPx = viewport.y * dpr;
632
+ const radiusPx = dotRadius * dpr;
633
+ for (const level of levels) {
634
+ const tileSize = level.spacing * viewport.zoom * dpr;
635
+ drawDotTile(ctx, physicalWidth, physicalHeight, panXPx, panYPx, tileSize, radiusPx, color, opacity * level.fade);
636
+ }
637
+ }
638
+ function drawHorizontalLinesForLevel(ctx, visible, spacing, skipSpacing, lineWidth, color, alpha) {
639
+ const startY = Math.floor(visible.minY / spacing) * spacing;
640
+ ctx.save();
641
+ ctx.strokeStyle = color;
642
+ ctx.lineWidth = lineWidth;
643
+ ctx.globalAlpha = alpha;
644
+ ctx.beginPath();
645
+ for (let y = startY; y <= visible.maxY; y += spacing) {
646
+ if (skipSpacing > 0 && isOnGrid(y, skipSpacing)) continue;
647
+ ctx.moveTo(visible.minX, y);
648
+ ctx.lineTo(visible.maxX, y);
649
+ }
650
+ ctx.stroke();
651
+ ctx.restore();
652
+ }
653
+ function drawMergingLines(ctx, visible, baseSpacing, lineWidth, color, opacity, zoom) {
654
+ const compensatedWidth = lineWidth / ctx.getTransform().a;
655
+ const levels = buildLevels(baseSpacing, zoom, LINE_GAP_FADE_IN, LINE_GAP_FADE_FULL);
656
+ for (let i = 0; i < levels.length; i++) {
657
+ const level = levels[i];
658
+ const coarserSpacing = i > 0 ? levels[i - 1].spacing : 0;
659
+ drawHorizontalLinesForLevel(ctx, visible, level.spacing, coarserSpacing, compensatedWidth, color, opacity * level.fade);
660
+ }
661
+ }
662
+ function drawGridForLevel(ctx, visible, spacing, skipSpacing, lineWidth, color, alpha) {
663
+ const startX = Math.floor(visible.minX / spacing) * spacing;
664
+ const startY = Math.floor(visible.minY / spacing) * spacing;
665
+ ctx.save();
666
+ ctx.strokeStyle = color;
667
+ ctx.lineWidth = lineWidth;
668
+ ctx.globalAlpha = alpha;
669
+ ctx.beginPath();
670
+ for (let x = startX; x <= visible.maxX; x += spacing) {
671
+ if (skipSpacing > 0 && isOnGrid(x, skipSpacing)) continue;
672
+ ctx.moveTo(x, visible.minY);
673
+ ctx.lineTo(x, visible.maxY);
674
+ }
675
+ for (let y = startY; y <= visible.maxY; y += spacing) {
676
+ if (skipSpacing > 0 && isOnGrid(y, skipSpacing)) continue;
677
+ ctx.moveTo(visible.minX, y);
678
+ ctx.lineTo(visible.maxX, y);
679
+ }
680
+ ctx.stroke();
681
+ ctx.restore();
682
+ }
683
+ function drawMergingGrid(ctx, visible, baseSpacing, lineWidth, color, opacity, zoom) {
684
+ const compensatedWidth = lineWidth / ctx.getTransform().a;
685
+ const levels = buildLevels(baseSpacing, zoom, LINE_GAP_FADE_IN, LINE_GAP_FADE_FULL);
686
+ for (let i = 0; i < levels.length; i++) {
687
+ const level = levels[i];
688
+ const coarserSpacing = i > 0 ? levels[i - 1].spacing : 0;
689
+ drawGridForLevel(ctx, visible, level.spacing, coarserSpacing, compensatedWidth, color, opacity * level.fade);
690
+ }
691
+ }
692
+ function renderCanvasBackground(ctx, viewport, canvasWidth, canvasHeight, options, theme) {
693
+ if (!options || options.type === "blank") return;
694
+ if (options.type === "custom") {
695
+ options.render(ctx, viewport, canvasWidth, canvasHeight);
696
+ return;
697
+ }
698
+ const baseSpacing = options.spacing ?? DEFAULT_SPACING;
699
+ if (baseSpacing <= 0) return;
700
+ const color = resolvePresetPatternColor(options.color, options.colorDark, theme);
701
+ const opacity = options.opacity ?? DEFAULT_OPACITY;
702
+ if (options.type === "dots") {
703
+ drawMergingDots(ctx, viewport, canvasWidth, canvasHeight, baseSpacing, options.size ?? DEFAULT_DOT_RADIUS, color, opacity);
704
+ return;
705
+ }
706
+ const visible = visiblePageRect(viewport, canvasWidth, canvasHeight);
707
+ ctx.save();
708
+ ctx.translate(viewport.x, viewport.y);
709
+ ctx.scale(viewport.zoom, viewport.zoom);
710
+ switch (options.type) {
711
+ case "lines":
712
+ drawMergingLines(ctx, visible, baseSpacing, options.size ?? DEFAULT_LINE_WIDTH, color, opacity, viewport.zoom);
713
+ break;
714
+ case "grid":
715
+ drawMergingGrid(ctx, visible, baseSpacing, options.size ?? DEFAULT_LINE_WIDTH, color, opacity, viewport.zoom);
716
+ break;
717
+ }
718
+ ctx.restore();
719
+ }
720
+
557
721
  // src/input/inputManager.ts
558
722
  var InputManager = class {
559
723
  _current = { x: 0, y: 0 };
@@ -2318,6 +2482,7 @@ exports.pageToScreen = pageToScreen;
2318
2482
  exports.panViewport = panViewport;
2319
2483
  exports.pointHitsShape = pointHitsShape;
2320
2484
  exports.recordsToDocumentSnapshot = recordsToDocumentSnapshot;
2485
+ exports.renderCanvasBackground = renderCanvasBackground;
2321
2486
  exports.resolveThemeColor = resolveThemeColor;
2322
2487
  exports.rotatePoint = rotatePoint;
2323
2488
  exports.screenToPage = screenToPage;