circuit-to-canvas 0.0.29 → 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
@@ -1,4 +1,4 @@
1
- import { AnyCircuitElement, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension } from 'circuit-json';
1
+ import { AnyCircuitElement, PcbRenderLayer, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension } from 'circuit-json';
2
2
  import { Matrix } from 'transformation-matrix';
3
3
 
4
4
  /**
@@ -86,7 +86,7 @@ interface DrawContext {
86
86
  }
87
87
 
88
88
  interface DrawElementsOptions {
89
- layers?: string[];
89
+ layers?: PcbRenderLayer[];
90
90
  }
91
91
  interface CanvasLike {
92
92
  getContext(contextId: "2d"): CanvasContext | null;
@@ -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,5 +1,24 @@
1
1
  // lib/drawer/CircuitToCanvasDrawer.ts
2
- import { identity, compose, translate, scale } from "transformation-matrix";
2
+ import {
3
+ identity,
4
+ compose,
5
+ translate,
6
+ scale,
7
+ applyToPoint as applyToPoint15
8
+ } from "transformation-matrix";
9
+
10
+ // lib/drawer/pcb-render-layer-filter.ts
11
+ import { getElementRenderLayers } from "@tscircuit/circuit-json-util";
12
+ function shouldDrawElement(element, options) {
13
+ if (!options.layers || options.layers.length === 0) {
14
+ return true;
15
+ }
16
+ const elementLayers = getElementRenderLayers(element);
17
+ if (elementLayers.length === 0) {
18
+ return true;
19
+ }
20
+ return elementLayers.some((layer) => options.layers.includes(layer));
21
+ }
3
22
 
4
23
  // lib/drawer/types.ts
5
24
  var DEFAULT_PCB_COLOR_MAP = {
@@ -496,14 +515,207 @@ function drawPcbHole(params) {
496
515
  }
497
516
  }
498
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
+
499
693
  // lib/drawer/elements/pcb-smtpad.ts
500
694
  function layerToColor(layer, colorMap) {
501
695
  return colorMap.copper[layer] ?? colorMap.copper.top;
502
696
  }
697
+ function getSoldermaskColor(layer, colorMap) {
698
+ return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
699
+ }
503
700
  function drawPcbSmtPad(params) {
504
701
  const { ctx, pad, realToCanvasMat, colorMap } = params;
505
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;
506
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
+ }
507
719
  drawRect({
508
720
  ctx,
509
721
  center: { x: pad.x, y: pad.y },
@@ -513,9 +725,35 @@ function drawPcbSmtPad(params) {
513
725
  realToCanvasMat,
514
726
  borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0
515
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
+ }
516
742
  return;
517
743
  }
518
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
+ }
519
757
  drawRect({
520
758
  ctx,
521
759
  center: { x: pad.x, y: pad.y },
@@ -526,9 +764,32 @@ function drawPcbSmtPad(params) {
526
764
  borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0,
527
765
  rotation: pad.ccw_rotation ?? 0
528
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
+ }
529
781
  return;
530
782
  }
531
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
+ }
532
793
  drawCircle({
533
794
  ctx,
534
795
  center: { x: pad.x, y: pad.y },
@@ -536,9 +797,30 @@ function drawPcbSmtPad(params) {
536
797
  fill: color,
537
798
  realToCanvasMat
538
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
+ }
539
811
  return;
540
812
  }
541
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
+ }
542
824
  drawPill({
543
825
  ctx,
544
826
  center: { x: pad.x, y: pad.y },
@@ -547,9 +829,33 @@ function drawPcbSmtPad(params) {
547
829
  fill: color,
548
830
  realToCanvasMat
549
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
+ }
550
845
  return;
551
846
  }
552
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
+ }
553
859
  drawPill({
554
860
  ctx,
555
861
  center: { x: pad.x, y: pad.y },
@@ -559,6 +865,19 @@ function drawPcbSmtPad(params) {
559
865
  realToCanvasMat,
560
866
  rotation: pad.ccw_rotation ?? 0
561
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
+ }
562
881
  return;
563
882
  }
564
883
  if (pad.shape === "polygon") {
@@ -575,7 +894,7 @@ function drawPcbSmtPad(params) {
575
894
  }
576
895
 
577
896
  // lib/drawer/shapes/line.ts
578
- import { applyToPoint as applyToPoint6 } from "transformation-matrix";
897
+ import { applyToPoint as applyToPoint7 } from "transformation-matrix";
579
898
  function drawLine(params) {
580
899
  const {
581
900
  ctx,
@@ -586,8 +905,8 @@ function drawLine(params) {
586
905
  realToCanvasMat,
587
906
  lineCap = "round"
588
907
  } = params;
589
- const [x1, y1] = applyToPoint6(realToCanvasMat, [start.x, start.y]);
590
- 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]);
591
910
  const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
592
911
  ctx.beginPath();
593
912
  ctx.moveTo(x1, y1);
@@ -628,7 +947,7 @@ function drawPcbTrace(params) {
628
947
  }
629
948
 
630
949
  // lib/drawer/shapes/path.ts
631
- import { applyToPoint as applyToPoint7 } from "transformation-matrix";
950
+ import { applyToPoint as applyToPoint8 } from "transformation-matrix";
632
951
  function drawPath(params) {
633
952
  const {
634
953
  ctx,
@@ -642,7 +961,7 @@ function drawPath(params) {
642
961
  if (points.length < 2) return;
643
962
  ctx.beginPath();
644
963
  const canvasPoints = points.map(
645
- (p) => applyToPoint7(realToCanvasMat, [p.x, p.y])
964
+ (p) => applyToPoint8(realToCanvasMat, [p.x, p.y])
646
965
  );
647
966
  const firstPoint = canvasPoints[0];
648
967
  if (!firstPoint) return;
@@ -713,11 +1032,11 @@ function drawPcbBoard(params) {
713
1032
  }
714
1033
 
715
1034
  // lib/drawer/elements/pcb-silkscreen-text.ts
716
- import { applyToPoint as applyToPoint9 } from "transformation-matrix";
1035
+ import { applyToPoint as applyToPoint10 } from "transformation-matrix";
717
1036
 
718
1037
  // lib/drawer/shapes/text/text.ts
719
1038
  import { lineAlphabet } from "@tscircuit/alphabet";
720
- import { applyToPoint as applyToPoint8 } from "transformation-matrix";
1039
+ import { applyToPoint as applyToPoint9 } from "transformation-matrix";
721
1040
 
722
1041
  // lib/drawer/shapes/text/getAlphabetLayout.ts
723
1042
  var GLYPH_WIDTH_RATIO = 0.62;
@@ -814,7 +1133,7 @@ function drawText(params) {
814
1133
  rotation = 0
815
1134
  } = params;
816
1135
  if (!text) return;
817
- const [canvasX, canvasY] = applyToPoint8(realToCanvasMat, [x, y]);
1136
+ const [canvasX, canvasY] = applyToPoint9(realToCanvasMat, [x, y]);
818
1137
  const scale2 = Math.abs(realToCanvasMat.a);
819
1138
  const scaledFontSize = fontSize * scale2;
820
1139
  const layout = getAlphabetLayout(text, scaledFontSize);
@@ -851,7 +1170,7 @@ function drawPcbSilkscreenText(params) {
851
1170
  const content = text.text ?? "";
852
1171
  if (!content) return;
853
1172
  const color = layerToSilkscreenColor(text.layer, colorMap);
854
- const [x, y] = applyToPoint9(realToCanvasMat, [
1173
+ const [x, y] = applyToPoint10(realToCanvasMat, [
855
1174
  text.anchor_position.x,
856
1175
  text.anchor_position.y
857
1176
  ]);
@@ -1014,7 +1333,7 @@ function drawPcbCutout(params) {
1014
1333
  }
1015
1334
 
1016
1335
  // lib/drawer/elements/pcb-copper-pour.ts
1017
- import { applyToPoint as applyToPoint10 } from "transformation-matrix";
1336
+ import { applyToPoint as applyToPoint11 } from "transformation-matrix";
1018
1337
  function layerToColor3(layer, colorMap) {
1019
1338
  return colorMap.copper[layer] ?? colorMap.copper.top;
1020
1339
  }
@@ -1023,7 +1342,7 @@ function drawPcbCopperPour(params) {
1023
1342
  const color = layerToColor3(pour.layer, colorMap);
1024
1343
  ctx.save();
1025
1344
  if (pour.shape === "rect") {
1026
- const [cx, cy] = applyToPoint10(realToCanvasMat, [
1345
+ const [cx, cy] = applyToPoint11(realToCanvasMat, [
1027
1346
  pour.center.x,
1028
1347
  pour.center.y
1029
1348
  ]);
@@ -1044,7 +1363,7 @@ function drawPcbCopperPour(params) {
1044
1363
  if (pour.shape === "polygon") {
1045
1364
  if (pour.points && pour.points.length >= 3) {
1046
1365
  const canvasPoints = pour.points.map(
1047
- (p) => applyToPoint10(realToCanvasMat, [p.x, p.y])
1366
+ (p) => applyToPoint11(realToCanvasMat, [p.x, p.y])
1048
1367
  );
1049
1368
  const firstPoint = canvasPoints[0];
1050
1369
  if (!firstPoint) {
@@ -1072,7 +1391,7 @@ function drawPcbCopperPour(params) {
1072
1391
  }
1073
1392
 
1074
1393
  // lib/drawer/elements/pcb-copper-text.ts
1075
- import { applyToPoint as applyToPoint11 } from "transformation-matrix";
1394
+ import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1076
1395
  var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
1077
1396
  function layerToCopperColor(layer, colorMap) {
1078
1397
  return colorMap.copper[layer] ?? colorMap.copper.top;
@@ -1087,7 +1406,7 @@ function drawPcbCopperText(params) {
1087
1406
  const { ctx, text, realToCanvasMat, colorMap } = params;
1088
1407
  const content = text.text ?? "";
1089
1408
  if (!content) return;
1090
- const [x, y] = applyToPoint11(realToCanvasMat, [
1409
+ const [x, y] = applyToPoint12(realToCanvasMat, [
1091
1410
  text.anchor_position.x,
1092
1411
  text.anchor_position.y
1093
1412
  ]);
@@ -1271,7 +1590,7 @@ function drawPcbNoteText(params) {
1271
1590
  }
1272
1591
 
1273
1592
  // lib/drawer/elements/pcb-note-dimension.ts
1274
- import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1593
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
1275
1594
 
1276
1595
  // lib/drawer/shapes/arrow.ts
1277
1596
  function drawArrow(params) {
@@ -1356,11 +1675,11 @@ function drawPcbNoteDimension(params) {
1356
1675
  stroke: color,
1357
1676
  realToCanvasMat
1358
1677
  });
1359
- const [canvasFromX, canvasFromY] = applyToPoint12(realToCanvasMat, [
1678
+ const [canvasFromX, canvasFromY] = applyToPoint13(realToCanvasMat, [
1360
1679
  fromX,
1361
1680
  fromY
1362
1681
  ]);
1363
- const [canvasToX, canvasToY] = applyToPoint12(realToCanvasMat, [toX, toY]);
1682
+ const [canvasToX, canvasToY] = applyToPoint13(realToCanvasMat, [toX, toY]);
1364
1683
  const canvasDx = canvasToX - canvasFromX;
1365
1684
  const canvasDy = canvasToY - canvasFromY;
1366
1685
  const lineAngle = Math.atan2(canvasDy, canvasDx);
@@ -1421,15 +1740,15 @@ function drawPcbNoteDimension(params) {
1421
1740
  }
1422
1741
 
1423
1742
  // lib/drawer/elements/pcb-note-line.ts
1424
- import { applyToPoint as applyToPoint13 } from "transformation-matrix";
1743
+ import { applyToPoint as applyToPoint14 } from "transformation-matrix";
1425
1744
  function drawPcbNoteLine(params) {
1426
1745
  const { ctx, line, realToCanvasMat, colorMap } = params;
1427
1746
  const defaultColor = "rgb(89, 148, 220)";
1428
1747
  const color = line.color ?? defaultColor;
1429
1748
  const strokeWidth = line.stroke_width ?? 0.1;
1430
1749
  const isDashed = line.is_dashed ?? false;
1431
- const [x1, y1] = applyToPoint13(realToCanvasMat, [line.x1, line.y1]);
1432
- 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]);
1433
1752
  const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
1434
1753
  ctx.save();
1435
1754
  if (isDashed) {
@@ -1511,11 +1830,82 @@ var CircuitToCanvasDrawer = class {
1511
1830
  );
1512
1831
  }
1513
1832
  drawElements(elements, options = {}) {
1833
+ const hasSoldermaskPads = elements.some(
1834
+ (el) => el.type === "pcb_smtpad" && el.is_covered_with_solder_mask === true
1835
+ );
1514
1836
  for (const element of elements) {
1515
- 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
+ });
1516
1903
  }
1517
1904
  }
1518
1905
  drawElement(element, options) {
1906
+ if (!shouldDrawElement(element, options)) {
1907
+ return;
1908
+ }
1519
1909
  if (element.type === "pcb_plated_hole") {
1520
1910
  drawPcbPlatedHole({
1521
1911
  ctx: this.ctx,
@@ -1735,6 +2125,9 @@ export {
1735
2125
  drawPill,
1736
2126
  drawPolygon,
1737
2127
  drawRect,
2128
+ drawSoldermaskRingForCircle,
2129
+ drawSoldermaskRingForPill,
2130
+ drawSoldermaskRingForRect,
1738
2131
  drawText,
1739
2132
  getAlphabetLayout,
1740
2133
  getTextStartPosition,