circuit-to-canvas 0.0.30 → 0.0.31

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.d.ts CHANGED
@@ -99,6 +99,7 @@ declare class CircuitToCanvasDrawer {
99
99
  configure(config: DrawerConfig): void;
100
100
  setCameraBounds(bounds: CameraBounds): void;
101
101
  drawElements(elements: AnyCircuitElement[], options?: DrawElementsOptions): void;
102
+ private drawBoardWithSoldermask;
102
103
  private drawElement;
103
104
  }
104
105
 
@@ -287,6 +288,31 @@ interface DrawPcbSmtPadParams {
287
288
  }
288
289
  declare function drawPcbSmtPad(params: DrawPcbSmtPadParams): void;
289
290
 
291
+ /**
292
+ * Draws a soldermask ring for rectangular shapes with negative margin
293
+ * (soldermask appears inside the pad boundary)
294
+ */
295
+ declare function drawSoldermaskRingForRect(ctx: CanvasContext, center: {
296
+ x: number;
297
+ y: number;
298
+ }, width: number, height: number, margin: number, borderRadius: number, rotation: number, realToCanvasMat: Matrix, soldermaskColor: string, padColor: string): void;
299
+ /**
300
+ * Draws a soldermask ring for circular shapes with negative margin
301
+ * (soldermask appears inside the pad boundary)
302
+ */
303
+ declare function drawSoldermaskRingForCircle(ctx: CanvasContext, center: {
304
+ x: number;
305
+ y: number;
306
+ }, radius: number, margin: number, realToCanvasMat: Matrix, soldermaskColor: string, padColor: string): void;
307
+ /**
308
+ * Draws a soldermask ring for pill shapes with negative margin
309
+ * (soldermask appears inside the pad boundary)
310
+ */
311
+ declare function drawSoldermaskRingForPill(ctx: CanvasContext, center: {
312
+ x: number;
313
+ y: number;
314
+ }, width: number, height: number, margin: number, rotation: number, realToCanvasMat: Matrix, soldermaskColor: string, padColor: string): void;
315
+
290
316
  interface DrawPcbTraceParams {
291
317
  ctx: CanvasContext;
292
318
  trace: PCBTrace;
@@ -431,4 +457,4 @@ interface DrawPcbNoteDimensionParams {
431
457
  }
432
458
  declare function drawPcbNoteDimension(params: DrawPcbNoteDimensionParams): void;
433
459
 
434
- export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawArrowParams, type DrawCircleParams, type DrawContext, type DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenPillParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawArrow, drawCircle, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenPath, drawPcbSilkscreenPill, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
460
+ export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawArrowParams, type DrawCircleParams, type DrawContext, type DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenPillParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawArrow, drawCircle, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenPath, drawPcbSilkscreenPill, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawSoldermaskRingForCircle, drawSoldermaskRingForPill, drawSoldermaskRingForRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
package/dist/index.js CHANGED
@@ -1,3 +1,12 @@
1
+ // lib/drawer/CircuitToCanvasDrawer.ts
2
+ import {
3
+ identity,
4
+ compose,
5
+ translate,
6
+ scale,
7
+ applyToPoint as applyToPoint15
8
+ } from "transformation-matrix";
9
+
1
10
  // lib/drawer/pcb-render-layer-filter.ts
2
11
  import { getElementRenderLayers } from "@tscircuit/circuit-json-util";
3
12
  function shouldDrawElement(element, options) {
@@ -11,9 +20,6 @@ function shouldDrawElement(element, options) {
11
20
  return elementLayers.some((layer) => options.layers.includes(layer));
12
21
  }
13
22
 
14
- // lib/drawer/CircuitToCanvasDrawer.ts
15
- import { identity, compose, translate, scale } from "transformation-matrix";
16
-
17
23
  // lib/drawer/types.ts
18
24
  var DEFAULT_PCB_COLOR_MAP = {
19
25
  copper: {
@@ -509,14 +515,207 @@ function drawPcbHole(params) {
509
515
  }
510
516
  }
511
517
 
518
+ // lib/drawer/elements/soldermask-margin.ts
519
+ import { applyToPoint as applyToPoint6 } from "transformation-matrix";
520
+ function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor) {
521
+ const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
522
+ const scaledWidth = width * Math.abs(realToCanvasMat.a);
523
+ const scaledHeight = height * Math.abs(realToCanvasMat.a);
524
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
525
+ const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
526
+ ctx.save();
527
+ ctx.translate(cx, cy);
528
+ if (rotation !== 0) {
529
+ ctx.rotate(-rotation * (Math.PI / 180));
530
+ }
531
+ const prevCompositeOp = ctx.globalCompositeOperation;
532
+ if (ctx.globalCompositeOperation !== void 0) {
533
+ ctx.globalCompositeOperation = "source-atop";
534
+ }
535
+ const outerWidth = scaledWidth;
536
+ const outerHeight = scaledHeight;
537
+ const outerRadius = scaledRadius;
538
+ ctx.beginPath();
539
+ if (outerRadius > 0) {
540
+ const x = -outerWidth / 2;
541
+ const y = -outerHeight / 2;
542
+ const r = Math.min(outerRadius, outerWidth / 2, outerHeight / 2);
543
+ ctx.moveTo(x + r, y);
544
+ ctx.lineTo(x + outerWidth - r, y);
545
+ ctx.arcTo(x + outerWidth, y, x + outerWidth, y + r, r);
546
+ ctx.lineTo(x + outerWidth, y + outerHeight - r);
547
+ ctx.arcTo(
548
+ x + outerWidth,
549
+ y + outerHeight,
550
+ x + outerWidth - r,
551
+ y + outerHeight,
552
+ r
553
+ );
554
+ ctx.lineTo(x + r, y + outerHeight);
555
+ ctx.arcTo(x, y + outerHeight, x, y + outerHeight - r, r);
556
+ ctx.lineTo(x, y + r);
557
+ ctx.arcTo(x, y, x + r, y, r);
558
+ } else {
559
+ ctx.rect(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight);
560
+ }
561
+ ctx.fillStyle = soldermaskColor;
562
+ ctx.fill();
563
+ if (ctx.globalCompositeOperation !== void 0) {
564
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over";
565
+ }
566
+ const innerWidth = scaledWidth - scaledMargin * 2;
567
+ const innerHeight = scaledHeight - scaledMargin * 2;
568
+ const innerRadius = Math.max(0, scaledRadius - scaledMargin);
569
+ if (innerWidth > 0 && innerHeight > 0) {
570
+ ctx.beginPath();
571
+ if (innerRadius > 0) {
572
+ const x = -innerWidth / 2;
573
+ const y = -innerHeight / 2;
574
+ const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2);
575
+ ctx.moveTo(x + r, y);
576
+ ctx.lineTo(x + innerWidth - r, y);
577
+ ctx.arcTo(x + innerWidth, y, x + innerWidth, y + r, r);
578
+ ctx.lineTo(x + innerWidth, y + innerHeight - r);
579
+ ctx.arcTo(
580
+ x + innerWidth,
581
+ y + innerHeight,
582
+ x + innerWidth - r,
583
+ y + innerHeight,
584
+ r
585
+ );
586
+ ctx.lineTo(x + r, y + innerHeight);
587
+ ctx.arcTo(x, y + innerHeight, x, y + innerHeight - r, r);
588
+ ctx.lineTo(x, y + r);
589
+ ctx.arcTo(x, y, x + r, y, r);
590
+ } else {
591
+ ctx.rect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight);
592
+ }
593
+ ctx.fillStyle = padColor;
594
+ ctx.fill();
595
+ }
596
+ ctx.restore();
597
+ }
598
+ function drawSoldermaskRingForCircle(ctx, center, radius, margin, realToCanvasMat, soldermaskColor, padColor) {
599
+ const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
600
+ const scaledRadius = radius * Math.abs(realToCanvasMat.a);
601
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
602
+ ctx.save();
603
+ const prevCompositeOp = ctx.globalCompositeOperation;
604
+ if (ctx.globalCompositeOperation !== void 0) {
605
+ ctx.globalCompositeOperation = "source-atop";
606
+ }
607
+ ctx.beginPath();
608
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
609
+ ctx.fillStyle = soldermaskColor;
610
+ ctx.fill();
611
+ if (ctx.globalCompositeOperation !== void 0) {
612
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over";
613
+ }
614
+ const innerRadius = Math.max(0, scaledRadius - scaledMargin);
615
+ if (innerRadius > 0) {
616
+ ctx.beginPath();
617
+ ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2);
618
+ ctx.fillStyle = padColor;
619
+ ctx.fill();
620
+ }
621
+ ctx.restore();
622
+ }
623
+ function drawSoldermaskRingForPill(ctx, center, width, height, margin, rotation, realToCanvasMat, soldermaskColor, padColor) {
624
+ const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
625
+ const scaledWidth = width * Math.abs(realToCanvasMat.a);
626
+ const scaledHeight = height * Math.abs(realToCanvasMat.a);
627
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
628
+ ctx.save();
629
+ ctx.translate(cx, cy);
630
+ if (rotation !== 0) {
631
+ ctx.rotate(-rotation * (Math.PI / 180));
632
+ }
633
+ const prevCompositeOp = ctx.globalCompositeOperation;
634
+ if (ctx.globalCompositeOperation !== void 0) {
635
+ ctx.globalCompositeOperation = "source-atop";
636
+ }
637
+ const outerWidth = scaledWidth;
638
+ const outerHeight = scaledHeight;
639
+ ctx.beginPath();
640
+ if (outerWidth > outerHeight) {
641
+ const radius = outerHeight / 2;
642
+ const straightLength = outerWidth - outerHeight;
643
+ ctx.moveTo(-straightLength / 2, -radius);
644
+ ctx.lineTo(straightLength / 2, -radius);
645
+ ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
646
+ ctx.lineTo(-straightLength / 2, radius);
647
+ ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
648
+ } else if (outerHeight > outerWidth) {
649
+ const radius = outerWidth / 2;
650
+ const straightLength = outerHeight - outerWidth;
651
+ ctx.moveTo(radius, -straightLength / 2);
652
+ ctx.lineTo(radius, straightLength / 2);
653
+ ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
654
+ ctx.lineTo(-radius, -straightLength / 2);
655
+ ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
656
+ } else {
657
+ ctx.arc(0, 0, outerWidth / 2, 0, Math.PI * 2);
658
+ }
659
+ ctx.fillStyle = soldermaskColor;
660
+ ctx.fill();
661
+ if (ctx.globalCompositeOperation !== void 0) {
662
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over";
663
+ }
664
+ const innerWidth = scaledWidth - scaledMargin * 2;
665
+ const innerHeight = scaledHeight - scaledMargin * 2;
666
+ if (innerWidth > 0 && innerHeight > 0) {
667
+ ctx.beginPath();
668
+ if (innerWidth > innerHeight) {
669
+ const radius = innerHeight / 2;
670
+ const straightLength = innerWidth - innerHeight;
671
+ ctx.moveTo(-straightLength / 2, -radius);
672
+ ctx.lineTo(straightLength / 2, -radius);
673
+ ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
674
+ ctx.lineTo(-straightLength / 2, radius);
675
+ ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
676
+ } else if (innerHeight > innerWidth) {
677
+ const radius = innerWidth / 2;
678
+ const straightLength = innerHeight - innerWidth;
679
+ ctx.moveTo(radius, -straightLength / 2);
680
+ ctx.lineTo(radius, straightLength / 2);
681
+ ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
682
+ ctx.lineTo(-radius, -straightLength / 2);
683
+ ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
684
+ } else {
685
+ ctx.arc(0, 0, innerWidth / 2, 0, Math.PI * 2);
686
+ }
687
+ ctx.fillStyle = padColor;
688
+ ctx.fill();
689
+ }
690
+ ctx.restore();
691
+ }
692
+
512
693
  // lib/drawer/elements/pcb-smtpad.ts
513
694
  function layerToColor(layer, colorMap) {
514
695
  return colorMap.copper[layer] ?? colorMap.copper.top;
515
696
  }
697
+ function getSoldermaskColor(layer, colorMap) {
698
+ return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
699
+ }
516
700
  function drawPcbSmtPad(params) {
517
701
  const { ctx, pad, realToCanvasMat, colorMap } = params;
518
702
  const color = layerToColor(pad.layer, colorMap);
703
+ const hasSoldermask = pad.is_covered_with_solder_mask === true && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
704
+ const margin = hasSoldermask ? pad.soldermask_margin : 0;
705
+ const soldermaskRingColor = getSoldermaskColor(pad.layer, colorMap);
706
+ const positiveMarginColor = colorMap.substrate;
519
707
  if (pad.shape === "rect") {
708
+ if (hasSoldermask && margin > 0) {
709
+ drawRect({
710
+ ctx,
711
+ center: { x: pad.x, y: pad.y },
712
+ width: pad.width + margin * 2,
713
+ height: pad.height + margin * 2,
714
+ fill: positiveMarginColor,
715
+ realToCanvasMat,
716
+ borderRadius: (pad.corner_radius ?? pad.rect_border_radius ?? 0) + margin
717
+ });
718
+ }
520
719
  drawRect({
521
720
  ctx,
522
721
  center: { x: pad.x, y: pad.y },
@@ -526,9 +725,35 @@ function drawPcbSmtPad(params) {
526
725
  realToCanvasMat,
527
726
  borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0
528
727
  });
728
+ if (hasSoldermask && margin < 0) {
729
+ drawSoldermaskRingForRect(
730
+ ctx,
731
+ { x: pad.x, y: pad.y },
732
+ pad.width,
733
+ pad.height,
734
+ margin,
735
+ pad.corner_radius ?? pad.rect_border_radius ?? 0,
736
+ 0,
737
+ realToCanvasMat,
738
+ soldermaskRingColor,
739
+ color
740
+ );
741
+ }
529
742
  return;
530
743
  }
531
744
  if (pad.shape === "rotated_rect") {
745
+ if (hasSoldermask && margin > 0) {
746
+ drawRect({
747
+ ctx,
748
+ center: { x: pad.x, y: pad.y },
749
+ width: pad.width + margin * 2,
750
+ height: pad.height + margin * 2,
751
+ fill: positiveMarginColor,
752
+ realToCanvasMat,
753
+ borderRadius: (pad.corner_radius ?? pad.rect_border_radius ?? 0) + margin,
754
+ rotation: pad.ccw_rotation ?? 0
755
+ });
756
+ }
532
757
  drawRect({
533
758
  ctx,
534
759
  center: { x: pad.x, y: pad.y },
@@ -539,9 +764,32 @@ function drawPcbSmtPad(params) {
539
764
  borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0,
540
765
  rotation: pad.ccw_rotation ?? 0
541
766
  });
767
+ if (hasSoldermask && margin < 0) {
768
+ drawSoldermaskRingForRect(
769
+ ctx,
770
+ { x: pad.x, y: pad.y },
771
+ pad.width,
772
+ pad.height,
773
+ margin,
774
+ pad.corner_radius ?? pad.rect_border_radius ?? 0,
775
+ pad.ccw_rotation ?? 0,
776
+ realToCanvasMat,
777
+ soldermaskRingColor,
778
+ color
779
+ );
780
+ }
542
781
  return;
543
782
  }
544
783
  if (pad.shape === "circle") {
784
+ if (hasSoldermask && margin > 0) {
785
+ drawCircle({
786
+ ctx,
787
+ center: { x: pad.x, y: pad.y },
788
+ radius: pad.radius + margin,
789
+ fill: positiveMarginColor,
790
+ realToCanvasMat
791
+ });
792
+ }
545
793
  drawCircle({
546
794
  ctx,
547
795
  center: { x: pad.x, y: pad.y },
@@ -549,9 +797,30 @@ function drawPcbSmtPad(params) {
549
797
  fill: color,
550
798
  realToCanvasMat
551
799
  });
800
+ if (hasSoldermask && margin < 0) {
801
+ drawSoldermaskRingForCircle(
802
+ ctx,
803
+ { x: pad.x, y: pad.y },
804
+ pad.radius,
805
+ margin,
806
+ realToCanvasMat,
807
+ soldermaskRingColor,
808
+ color
809
+ );
810
+ }
552
811
  return;
553
812
  }
554
813
  if (pad.shape === "pill") {
814
+ if (hasSoldermask && margin > 0) {
815
+ drawPill({
816
+ ctx,
817
+ center: { x: pad.x, y: pad.y },
818
+ width: pad.width + margin * 2,
819
+ height: pad.height + margin * 2,
820
+ fill: positiveMarginColor,
821
+ realToCanvasMat
822
+ });
823
+ }
555
824
  drawPill({
556
825
  ctx,
557
826
  center: { x: pad.x, y: pad.y },
@@ -560,9 +829,33 @@ function drawPcbSmtPad(params) {
560
829
  fill: color,
561
830
  realToCanvasMat
562
831
  });
832
+ if (hasSoldermask && margin < 0) {
833
+ drawSoldermaskRingForPill(
834
+ ctx,
835
+ { x: pad.x, y: pad.y },
836
+ pad.width,
837
+ pad.height,
838
+ margin,
839
+ 0,
840
+ realToCanvasMat,
841
+ soldermaskRingColor,
842
+ color
843
+ );
844
+ }
563
845
  return;
564
846
  }
565
847
  if (pad.shape === "rotated_pill") {
848
+ if (hasSoldermask && margin > 0) {
849
+ drawPill({
850
+ ctx,
851
+ center: { x: pad.x, y: pad.y },
852
+ width: pad.width + margin * 2,
853
+ height: pad.height + margin * 2,
854
+ fill: positiveMarginColor,
855
+ realToCanvasMat,
856
+ rotation: pad.ccw_rotation ?? 0
857
+ });
858
+ }
566
859
  drawPill({
567
860
  ctx,
568
861
  center: { x: pad.x, y: pad.y },
@@ -572,6 +865,19 @@ function drawPcbSmtPad(params) {
572
865
  realToCanvasMat,
573
866
  rotation: pad.ccw_rotation ?? 0
574
867
  });
868
+ if (hasSoldermask && margin < 0) {
869
+ drawSoldermaskRingForPill(
870
+ ctx,
871
+ { x: pad.x, y: pad.y },
872
+ pad.width,
873
+ pad.height,
874
+ margin,
875
+ pad.ccw_rotation ?? 0,
876
+ realToCanvasMat,
877
+ soldermaskRingColor,
878
+ color
879
+ );
880
+ }
575
881
  return;
576
882
  }
577
883
  if (pad.shape === "polygon") {
@@ -588,7 +894,7 @@ function drawPcbSmtPad(params) {
588
894
  }
589
895
 
590
896
  // lib/drawer/shapes/line.ts
591
- import { applyToPoint as applyToPoint6 } from "transformation-matrix";
897
+ import { applyToPoint as applyToPoint7 } from "transformation-matrix";
592
898
  function drawLine(params) {
593
899
  const {
594
900
  ctx,
@@ -599,8 +905,8 @@ function drawLine(params) {
599
905
  realToCanvasMat,
600
906
  lineCap = "round"
601
907
  } = params;
602
- const [x1, y1] = applyToPoint6(realToCanvasMat, [start.x, start.y]);
603
- const [x2, y2] = applyToPoint6(realToCanvasMat, [end.x, end.y]);
908
+ const [x1, y1] = applyToPoint7(realToCanvasMat, [start.x, start.y]);
909
+ const [x2, y2] = applyToPoint7(realToCanvasMat, [end.x, end.y]);
604
910
  const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
605
911
  ctx.beginPath();
606
912
  ctx.moveTo(x1, y1);
@@ -641,7 +947,7 @@ function drawPcbTrace(params) {
641
947
  }
642
948
 
643
949
  // lib/drawer/shapes/path.ts
644
- import { applyToPoint as applyToPoint7 } from "transformation-matrix";
950
+ import { applyToPoint as applyToPoint8 } from "transformation-matrix";
645
951
  function drawPath(params) {
646
952
  const {
647
953
  ctx,
@@ -655,7 +961,7 @@ function drawPath(params) {
655
961
  if (points.length < 2) return;
656
962
  ctx.beginPath();
657
963
  const canvasPoints = points.map(
658
- (p) => applyToPoint7(realToCanvasMat, [p.x, p.y])
964
+ (p) => applyToPoint8(realToCanvasMat, [p.x, p.y])
659
965
  );
660
966
  const firstPoint = canvasPoints[0];
661
967
  if (!firstPoint) return;
@@ -726,11 +1032,11 @@ function drawPcbBoard(params) {
726
1032
  }
727
1033
 
728
1034
  // lib/drawer/elements/pcb-silkscreen-text.ts
729
- import { applyToPoint as applyToPoint9 } from "transformation-matrix";
1035
+ import { applyToPoint as applyToPoint10 } from "transformation-matrix";
730
1036
 
731
1037
  // lib/drawer/shapes/text/text.ts
732
1038
  import { lineAlphabet } from "@tscircuit/alphabet";
733
- import { applyToPoint as applyToPoint8 } from "transformation-matrix";
1039
+ import { applyToPoint as applyToPoint9 } from "transformation-matrix";
734
1040
 
735
1041
  // lib/drawer/shapes/text/getAlphabetLayout.ts
736
1042
  var GLYPH_WIDTH_RATIO = 0.62;
@@ -827,7 +1133,7 @@ function drawText(params) {
827
1133
  rotation = 0
828
1134
  } = params;
829
1135
  if (!text) return;
830
- const [canvasX, canvasY] = applyToPoint8(realToCanvasMat, [x, y]);
1136
+ const [canvasX, canvasY] = applyToPoint9(realToCanvasMat, [x, y]);
831
1137
  const scale2 = Math.abs(realToCanvasMat.a);
832
1138
  const scaledFontSize = fontSize * scale2;
833
1139
  const layout = getAlphabetLayout(text, scaledFontSize);
@@ -864,7 +1170,7 @@ function drawPcbSilkscreenText(params) {
864
1170
  const content = text.text ?? "";
865
1171
  if (!content) return;
866
1172
  const color = layerToSilkscreenColor(text.layer, colorMap);
867
- const [x, y] = applyToPoint9(realToCanvasMat, [
1173
+ const [x, y] = applyToPoint10(realToCanvasMat, [
868
1174
  text.anchor_position.x,
869
1175
  text.anchor_position.y
870
1176
  ]);
@@ -1027,7 +1333,7 @@ function drawPcbCutout(params) {
1027
1333
  }
1028
1334
 
1029
1335
  // lib/drawer/elements/pcb-copper-pour.ts
1030
- import { applyToPoint as applyToPoint10 } from "transformation-matrix";
1336
+ import { applyToPoint as applyToPoint11 } from "transformation-matrix";
1031
1337
  function layerToColor3(layer, colorMap) {
1032
1338
  return colorMap.copper[layer] ?? colorMap.copper.top;
1033
1339
  }
@@ -1036,7 +1342,7 @@ function drawPcbCopperPour(params) {
1036
1342
  const color = layerToColor3(pour.layer, colorMap);
1037
1343
  ctx.save();
1038
1344
  if (pour.shape === "rect") {
1039
- const [cx, cy] = applyToPoint10(realToCanvasMat, [
1345
+ const [cx, cy] = applyToPoint11(realToCanvasMat, [
1040
1346
  pour.center.x,
1041
1347
  pour.center.y
1042
1348
  ]);
@@ -1057,7 +1363,7 @@ function drawPcbCopperPour(params) {
1057
1363
  if (pour.shape === "polygon") {
1058
1364
  if (pour.points && pour.points.length >= 3) {
1059
1365
  const canvasPoints = pour.points.map(
1060
- (p) => applyToPoint10(realToCanvasMat, [p.x, p.y])
1366
+ (p) => applyToPoint11(realToCanvasMat, [p.x, p.y])
1061
1367
  );
1062
1368
  const firstPoint = canvasPoints[0];
1063
1369
  if (!firstPoint) {
@@ -1085,7 +1391,7 @@ function drawPcbCopperPour(params) {
1085
1391
  }
1086
1392
 
1087
1393
  // lib/drawer/elements/pcb-copper-text.ts
1088
- import { applyToPoint as applyToPoint11 } from "transformation-matrix";
1394
+ import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1089
1395
  var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
1090
1396
  function layerToCopperColor(layer, colorMap) {
1091
1397
  return colorMap.copper[layer] ?? colorMap.copper.top;
@@ -1100,7 +1406,7 @@ function drawPcbCopperText(params) {
1100
1406
  const { ctx, text, realToCanvasMat, colorMap } = params;
1101
1407
  const content = text.text ?? "";
1102
1408
  if (!content) return;
1103
- const [x, y] = applyToPoint11(realToCanvasMat, [
1409
+ const [x, y] = applyToPoint12(realToCanvasMat, [
1104
1410
  text.anchor_position.x,
1105
1411
  text.anchor_position.y
1106
1412
  ]);
@@ -1284,7 +1590,7 @@ function drawPcbNoteText(params) {
1284
1590
  }
1285
1591
 
1286
1592
  // lib/drawer/elements/pcb-note-dimension.ts
1287
- import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1593
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
1288
1594
 
1289
1595
  // lib/drawer/shapes/arrow.ts
1290
1596
  function drawArrow(params) {
@@ -1369,11 +1675,11 @@ function drawPcbNoteDimension(params) {
1369
1675
  stroke: color,
1370
1676
  realToCanvasMat
1371
1677
  });
1372
- const [canvasFromX, canvasFromY] = applyToPoint12(realToCanvasMat, [
1678
+ const [canvasFromX, canvasFromY] = applyToPoint13(realToCanvasMat, [
1373
1679
  fromX,
1374
1680
  fromY
1375
1681
  ]);
1376
- const [canvasToX, canvasToY] = applyToPoint12(realToCanvasMat, [toX, toY]);
1682
+ const [canvasToX, canvasToY] = applyToPoint13(realToCanvasMat, [toX, toY]);
1377
1683
  const canvasDx = canvasToX - canvasFromX;
1378
1684
  const canvasDy = canvasToY - canvasFromY;
1379
1685
  const lineAngle = Math.atan2(canvasDy, canvasDx);
@@ -1434,15 +1740,15 @@ function drawPcbNoteDimension(params) {
1434
1740
  }
1435
1741
 
1436
1742
  // lib/drawer/elements/pcb-note-line.ts
1437
- import { applyToPoint as applyToPoint13 } from "transformation-matrix";
1743
+ import { applyToPoint as applyToPoint14 } from "transformation-matrix";
1438
1744
  function drawPcbNoteLine(params) {
1439
1745
  const { ctx, line, realToCanvasMat, colorMap } = params;
1440
1746
  const defaultColor = "rgb(89, 148, 220)";
1441
1747
  const color = line.color ?? defaultColor;
1442
1748
  const strokeWidth = line.stroke_width ?? 0.1;
1443
1749
  const isDashed = line.is_dashed ?? false;
1444
- const [x1, y1] = applyToPoint13(realToCanvasMat, [line.x1, line.y1]);
1445
- const [x2, y2] = applyToPoint13(realToCanvasMat, [line.x2, line.y2]);
1750
+ const [x1, y1] = applyToPoint14(realToCanvasMat, [line.x1, line.y1]);
1751
+ const [x2, y2] = applyToPoint14(realToCanvasMat, [line.x2, line.y2]);
1446
1752
  const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
1447
1753
  ctx.save();
1448
1754
  if (isDashed) {
@@ -1524,8 +1830,76 @@ var CircuitToCanvasDrawer = class {
1524
1830
  );
1525
1831
  }
1526
1832
  drawElements(elements, options = {}) {
1833
+ const hasSoldermaskPads = elements.some(
1834
+ (el) => el.type === "pcb_smtpad" && el.is_covered_with_solder_mask === true
1835
+ );
1527
1836
  for (const element of elements) {
1528
- this.drawElement(element, options);
1837
+ if (element.type === "pcb_board" && hasSoldermaskPads) {
1838
+ this.drawBoardWithSoldermask(element);
1839
+ } else {
1840
+ this.drawElement(element, options);
1841
+ }
1842
+ }
1843
+ }
1844
+ drawBoardWithSoldermask(board) {
1845
+ const { width, height, center, outline } = board;
1846
+ const layer = "top";
1847
+ if (outline && Array.isArray(outline) && outline.length >= 3) {
1848
+ const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
1849
+ const canvasPoints = outline.map((p) => {
1850
+ const [x, y] = applyToPoint15(this.realToCanvasMat, [p.x, p.y]);
1851
+ return { x, y };
1852
+ });
1853
+ this.ctx.beginPath();
1854
+ const firstPoint = canvasPoints[0];
1855
+ if (firstPoint) {
1856
+ this.ctx.moveTo(firstPoint.x, firstPoint.y);
1857
+ for (let i = 1; i < canvasPoints.length; i++) {
1858
+ const point = canvasPoints[i];
1859
+ if (point) {
1860
+ this.ctx.lineTo(point.x, point.y);
1861
+ }
1862
+ }
1863
+ this.ctx.closePath();
1864
+ }
1865
+ this.ctx.fillStyle = soldermaskColor;
1866
+ this.ctx.fill();
1867
+ drawPath({
1868
+ ctx: this.ctx,
1869
+ points: outline.map((p) => ({ x: p.x, y: p.y })),
1870
+ stroke: this.colorMap.boardOutline,
1871
+ strokeWidth: 0.1,
1872
+ realToCanvasMat: this.realToCanvasMat,
1873
+ closePath: true
1874
+ });
1875
+ return;
1876
+ }
1877
+ if (width !== void 0 && height !== void 0 && center) {
1878
+ const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
1879
+ drawRect({
1880
+ ctx: this.ctx,
1881
+ center,
1882
+ width,
1883
+ height,
1884
+ fill: soldermaskColor,
1885
+ realToCanvasMat: this.realToCanvasMat
1886
+ });
1887
+ const halfWidth = width / 2;
1888
+ const halfHeight = height / 2;
1889
+ const corners = [
1890
+ { x: center.x - halfWidth, y: center.y - halfHeight },
1891
+ { x: center.x + halfWidth, y: center.y - halfHeight },
1892
+ { x: center.x + halfWidth, y: center.y + halfHeight },
1893
+ { x: center.x - halfWidth, y: center.y + halfHeight }
1894
+ ];
1895
+ drawPath({
1896
+ ctx: this.ctx,
1897
+ points: corners,
1898
+ stroke: this.colorMap.boardOutline,
1899
+ strokeWidth: 0.1,
1900
+ realToCanvasMat: this.realToCanvasMat,
1901
+ closePath: true
1902
+ });
1529
1903
  }
1530
1904
  }
1531
1905
  drawElement(element, options) {
@@ -1751,6 +2125,9 @@ export {
1751
2125
  drawPill,
1752
2126
  drawPolygon,
1753
2127
  drawRect,
2128
+ drawSoldermaskRingForCircle,
2129
+ drawSoldermaskRingForPill,
2130
+ drawSoldermaskRingForRect,
1754
2131
  drawText,
1755
2132
  getAlphabetLayout,
1756
2133
  getTextStartPosition,