circuit-to-canvas 0.0.30 → 0.0.32

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
@@ -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: {
@@ -132,24 +138,34 @@ function drawOval(params) {
132
138
  const {
133
139
  ctx,
134
140
  center,
135
- width,
136
- height,
141
+ radius_x,
142
+ radius_y,
137
143
  fill,
144
+ stroke,
145
+ strokeWidth = 0.1,
138
146
  realToCanvasMat,
139
147
  rotation = 0
140
148
  } = params;
141
149
  const [cx, cy] = applyToPoint3(realToCanvasMat, [center.x, center.y]);
142
- const scaledWidth = width * Math.abs(realToCanvasMat.a);
143
- const scaledHeight = height * Math.abs(realToCanvasMat.a);
150
+ const scaledRadiusX = radius_x * Math.abs(realToCanvasMat.a);
151
+ const scaledRadiusY = radius_y * Math.abs(realToCanvasMat.a);
152
+ const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
144
153
  ctx.save();
145
154
  ctx.translate(cx, cy);
146
155
  if (rotation !== 0) {
147
156
  ctx.rotate(-rotation * (Math.PI / 180));
148
157
  }
149
158
  ctx.beginPath();
150
- ctx.ellipse(0, 0, scaledWidth / 2, scaledHeight / 2, 0, 0, Math.PI * 2);
151
- ctx.fillStyle = fill;
152
- ctx.fill();
159
+ ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2);
160
+ if (fill) {
161
+ ctx.fillStyle = fill;
162
+ ctx.fill();
163
+ }
164
+ if (stroke) {
165
+ ctx.strokeStyle = stroke;
166
+ ctx.lineWidth = scaledStrokeWidth;
167
+ ctx.stroke();
168
+ }
153
169
  ctx.restore();
154
170
  }
155
171
 
@@ -257,8 +273,8 @@ function drawPcbPlatedHole(params) {
257
273
  drawOval({
258
274
  ctx,
259
275
  center: { x: hole.x, y: hole.y },
260
- width: hole.outer_width,
261
- height: hole.outer_height,
276
+ radius_x: hole.outer_width / 2,
277
+ radius_y: hole.outer_height / 2,
262
278
  fill: colorMap.copper.top,
263
279
  realToCanvasMat,
264
280
  rotation: hole.ccw_rotation
@@ -266,8 +282,8 @@ function drawPcbPlatedHole(params) {
266
282
  drawOval({
267
283
  ctx,
268
284
  center: { x: hole.x, y: hole.y },
269
- width: hole.hole_width,
270
- height: hole.hole_height,
285
+ radius_x: hole.hole_width / 2,
286
+ radius_y: hole.hole_height / 2,
271
287
  fill: colorMap.drill,
272
288
  realToCanvasMat,
273
289
  rotation: hole.ccw_rotation
@@ -391,8 +407,8 @@ function drawPcbPlatedHole(params) {
391
407
  drawOval({
392
408
  ctx,
393
409
  center: { x: holeX, y: holeY },
394
- width: hole.hole_width ?? 0,
395
- height: hole.hole_height ?? 0,
410
+ radius_x: (hole.hole_width ?? 0) / 2,
411
+ radius_y: (hole.hole_height ?? 0) / 2,
396
412
  fill: colorMap.drill,
397
413
  realToCanvasMat
398
414
  });
@@ -466,8 +482,8 @@ function drawPcbHole(params) {
466
482
  drawOval({
467
483
  ctx,
468
484
  center: { x: hole.x, y: hole.y },
469
- width: hole.hole_width,
470
- height: hole.hole_height,
485
+ radius_x: hole.hole_width / 2,
486
+ radius_y: hole.hole_height / 2,
471
487
  fill: colorMap.drill,
472
488
  realToCanvasMat
473
489
  });
@@ -509,14 +525,207 @@ function drawPcbHole(params) {
509
525
  }
510
526
  }
511
527
 
528
+ // lib/drawer/elements/soldermask-margin.ts
529
+ import { applyToPoint as applyToPoint6 } from "transformation-matrix";
530
+ function drawSoldermaskRingForRect(ctx, center, width, height, margin, borderRadius, rotation, realToCanvasMat, soldermaskColor, padColor) {
531
+ const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
532
+ const scaledWidth = width * Math.abs(realToCanvasMat.a);
533
+ const scaledHeight = height * Math.abs(realToCanvasMat.a);
534
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
535
+ const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a);
536
+ ctx.save();
537
+ ctx.translate(cx, cy);
538
+ if (rotation !== 0) {
539
+ ctx.rotate(-rotation * (Math.PI / 180));
540
+ }
541
+ const prevCompositeOp = ctx.globalCompositeOperation;
542
+ if (ctx.globalCompositeOperation !== void 0) {
543
+ ctx.globalCompositeOperation = "source-atop";
544
+ }
545
+ const outerWidth = scaledWidth;
546
+ const outerHeight = scaledHeight;
547
+ const outerRadius = scaledRadius;
548
+ ctx.beginPath();
549
+ if (outerRadius > 0) {
550
+ const x = -outerWidth / 2;
551
+ const y = -outerHeight / 2;
552
+ const r = Math.min(outerRadius, outerWidth / 2, outerHeight / 2);
553
+ ctx.moveTo(x + r, y);
554
+ ctx.lineTo(x + outerWidth - r, y);
555
+ ctx.arcTo(x + outerWidth, y, x + outerWidth, y + r, r);
556
+ ctx.lineTo(x + outerWidth, y + outerHeight - r);
557
+ ctx.arcTo(
558
+ x + outerWidth,
559
+ y + outerHeight,
560
+ x + outerWidth - r,
561
+ y + outerHeight,
562
+ r
563
+ );
564
+ ctx.lineTo(x + r, y + outerHeight);
565
+ ctx.arcTo(x, y + outerHeight, x, y + outerHeight - r, r);
566
+ ctx.lineTo(x, y + r);
567
+ ctx.arcTo(x, y, x + r, y, r);
568
+ } else {
569
+ ctx.rect(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight);
570
+ }
571
+ ctx.fillStyle = soldermaskColor;
572
+ ctx.fill();
573
+ if (ctx.globalCompositeOperation !== void 0) {
574
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over";
575
+ }
576
+ const innerWidth = scaledWidth - scaledMargin * 2;
577
+ const innerHeight = scaledHeight - scaledMargin * 2;
578
+ const innerRadius = Math.max(0, scaledRadius - scaledMargin);
579
+ if (innerWidth > 0 && innerHeight > 0) {
580
+ ctx.beginPath();
581
+ if (innerRadius > 0) {
582
+ const x = -innerWidth / 2;
583
+ const y = -innerHeight / 2;
584
+ const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2);
585
+ ctx.moveTo(x + r, y);
586
+ ctx.lineTo(x + innerWidth - r, y);
587
+ ctx.arcTo(x + innerWidth, y, x + innerWidth, y + r, r);
588
+ ctx.lineTo(x + innerWidth, y + innerHeight - r);
589
+ ctx.arcTo(
590
+ x + innerWidth,
591
+ y + innerHeight,
592
+ x + innerWidth - r,
593
+ y + innerHeight,
594
+ r
595
+ );
596
+ ctx.lineTo(x + r, y + innerHeight);
597
+ ctx.arcTo(x, y + innerHeight, x, y + innerHeight - r, r);
598
+ ctx.lineTo(x, y + r);
599
+ ctx.arcTo(x, y, x + r, y, r);
600
+ } else {
601
+ ctx.rect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight);
602
+ }
603
+ ctx.fillStyle = padColor;
604
+ ctx.fill();
605
+ }
606
+ ctx.restore();
607
+ }
608
+ function drawSoldermaskRingForCircle(ctx, center, radius, margin, realToCanvasMat, soldermaskColor, padColor) {
609
+ const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
610
+ const scaledRadius = radius * Math.abs(realToCanvasMat.a);
611
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
612
+ ctx.save();
613
+ const prevCompositeOp = ctx.globalCompositeOperation;
614
+ if (ctx.globalCompositeOperation !== void 0) {
615
+ ctx.globalCompositeOperation = "source-atop";
616
+ }
617
+ ctx.beginPath();
618
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2);
619
+ ctx.fillStyle = soldermaskColor;
620
+ ctx.fill();
621
+ if (ctx.globalCompositeOperation !== void 0) {
622
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over";
623
+ }
624
+ const innerRadius = Math.max(0, scaledRadius - scaledMargin);
625
+ if (innerRadius > 0) {
626
+ ctx.beginPath();
627
+ ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2);
628
+ ctx.fillStyle = padColor;
629
+ ctx.fill();
630
+ }
631
+ ctx.restore();
632
+ }
633
+ function drawSoldermaskRingForPill(ctx, center, width, height, margin, rotation, realToCanvasMat, soldermaskColor, padColor) {
634
+ const [cx, cy] = applyToPoint6(realToCanvasMat, [center.x, center.y]);
635
+ const scaledWidth = width * Math.abs(realToCanvasMat.a);
636
+ const scaledHeight = height * Math.abs(realToCanvasMat.a);
637
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a);
638
+ ctx.save();
639
+ ctx.translate(cx, cy);
640
+ if (rotation !== 0) {
641
+ ctx.rotate(-rotation * (Math.PI / 180));
642
+ }
643
+ const prevCompositeOp = ctx.globalCompositeOperation;
644
+ if (ctx.globalCompositeOperation !== void 0) {
645
+ ctx.globalCompositeOperation = "source-atop";
646
+ }
647
+ const outerWidth = scaledWidth;
648
+ const outerHeight = scaledHeight;
649
+ ctx.beginPath();
650
+ if (outerWidth > outerHeight) {
651
+ const radius = outerHeight / 2;
652
+ const straightLength = outerWidth - outerHeight;
653
+ ctx.moveTo(-straightLength / 2, -radius);
654
+ ctx.lineTo(straightLength / 2, -radius);
655
+ ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
656
+ ctx.lineTo(-straightLength / 2, radius);
657
+ ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
658
+ } else if (outerHeight > outerWidth) {
659
+ const radius = outerWidth / 2;
660
+ const straightLength = outerHeight - outerWidth;
661
+ ctx.moveTo(radius, -straightLength / 2);
662
+ ctx.lineTo(radius, straightLength / 2);
663
+ ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
664
+ ctx.lineTo(-radius, -straightLength / 2);
665
+ ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
666
+ } else {
667
+ ctx.arc(0, 0, outerWidth / 2, 0, Math.PI * 2);
668
+ }
669
+ ctx.fillStyle = soldermaskColor;
670
+ ctx.fill();
671
+ if (ctx.globalCompositeOperation !== void 0) {
672
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over";
673
+ }
674
+ const innerWidth = scaledWidth - scaledMargin * 2;
675
+ const innerHeight = scaledHeight - scaledMargin * 2;
676
+ if (innerWidth > 0 && innerHeight > 0) {
677
+ ctx.beginPath();
678
+ if (innerWidth > innerHeight) {
679
+ const radius = innerHeight / 2;
680
+ const straightLength = innerWidth - innerHeight;
681
+ ctx.moveTo(-straightLength / 2, -radius);
682
+ ctx.lineTo(straightLength / 2, -radius);
683
+ ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2);
684
+ ctx.lineTo(-straightLength / 2, radius);
685
+ ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2);
686
+ } else if (innerHeight > innerWidth) {
687
+ const radius = innerWidth / 2;
688
+ const straightLength = innerHeight - innerWidth;
689
+ ctx.moveTo(radius, -straightLength / 2);
690
+ ctx.lineTo(radius, straightLength / 2);
691
+ ctx.arc(0, straightLength / 2, radius, 0, Math.PI);
692
+ ctx.lineTo(-radius, -straightLength / 2);
693
+ ctx.arc(0, -straightLength / 2, radius, Math.PI, 0);
694
+ } else {
695
+ ctx.arc(0, 0, innerWidth / 2, 0, Math.PI * 2);
696
+ }
697
+ ctx.fillStyle = padColor;
698
+ ctx.fill();
699
+ }
700
+ ctx.restore();
701
+ }
702
+
512
703
  // lib/drawer/elements/pcb-smtpad.ts
513
704
  function layerToColor(layer, colorMap) {
514
705
  return colorMap.copper[layer] ?? colorMap.copper.top;
515
706
  }
707
+ function getSoldermaskColor(layer, colorMap) {
708
+ return colorMap.soldermaskOverCopper[layer] ?? colorMap.soldermaskOverCopper.top;
709
+ }
516
710
  function drawPcbSmtPad(params) {
517
711
  const { ctx, pad, realToCanvasMat, colorMap } = params;
518
712
  const color = layerToColor(pad.layer, colorMap);
713
+ const hasSoldermask = pad.is_covered_with_solder_mask === true && pad.soldermask_margin !== void 0 && pad.soldermask_margin !== 0;
714
+ const margin = hasSoldermask ? pad.soldermask_margin : 0;
715
+ const soldermaskRingColor = getSoldermaskColor(pad.layer, colorMap);
716
+ const positiveMarginColor = colorMap.substrate;
519
717
  if (pad.shape === "rect") {
718
+ if (hasSoldermask && margin > 0) {
719
+ drawRect({
720
+ ctx,
721
+ center: { x: pad.x, y: pad.y },
722
+ width: pad.width + margin * 2,
723
+ height: pad.height + margin * 2,
724
+ fill: positiveMarginColor,
725
+ realToCanvasMat,
726
+ borderRadius: (pad.corner_radius ?? pad.rect_border_radius ?? 0) + margin
727
+ });
728
+ }
520
729
  drawRect({
521
730
  ctx,
522
731
  center: { x: pad.x, y: pad.y },
@@ -526,9 +735,35 @@ function drawPcbSmtPad(params) {
526
735
  realToCanvasMat,
527
736
  borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0
528
737
  });
738
+ if (hasSoldermask && margin < 0) {
739
+ drawSoldermaskRingForRect(
740
+ ctx,
741
+ { x: pad.x, y: pad.y },
742
+ pad.width,
743
+ pad.height,
744
+ margin,
745
+ pad.corner_radius ?? pad.rect_border_radius ?? 0,
746
+ 0,
747
+ realToCanvasMat,
748
+ soldermaskRingColor,
749
+ color
750
+ );
751
+ }
529
752
  return;
530
753
  }
531
754
  if (pad.shape === "rotated_rect") {
755
+ if (hasSoldermask && margin > 0) {
756
+ drawRect({
757
+ ctx,
758
+ center: { x: pad.x, y: pad.y },
759
+ width: pad.width + margin * 2,
760
+ height: pad.height + margin * 2,
761
+ fill: positiveMarginColor,
762
+ realToCanvasMat,
763
+ borderRadius: (pad.corner_radius ?? pad.rect_border_radius ?? 0) + margin,
764
+ rotation: pad.ccw_rotation ?? 0
765
+ });
766
+ }
532
767
  drawRect({
533
768
  ctx,
534
769
  center: { x: pad.x, y: pad.y },
@@ -539,9 +774,32 @@ function drawPcbSmtPad(params) {
539
774
  borderRadius: pad.corner_radius ?? pad.rect_border_radius ?? 0,
540
775
  rotation: pad.ccw_rotation ?? 0
541
776
  });
777
+ if (hasSoldermask && margin < 0) {
778
+ drawSoldermaskRingForRect(
779
+ ctx,
780
+ { x: pad.x, y: pad.y },
781
+ pad.width,
782
+ pad.height,
783
+ margin,
784
+ pad.corner_radius ?? pad.rect_border_radius ?? 0,
785
+ pad.ccw_rotation ?? 0,
786
+ realToCanvasMat,
787
+ soldermaskRingColor,
788
+ color
789
+ );
790
+ }
542
791
  return;
543
792
  }
544
793
  if (pad.shape === "circle") {
794
+ if (hasSoldermask && margin > 0) {
795
+ drawCircle({
796
+ ctx,
797
+ center: { x: pad.x, y: pad.y },
798
+ radius: pad.radius + margin,
799
+ fill: positiveMarginColor,
800
+ realToCanvasMat
801
+ });
802
+ }
545
803
  drawCircle({
546
804
  ctx,
547
805
  center: { x: pad.x, y: pad.y },
@@ -549,9 +807,30 @@ function drawPcbSmtPad(params) {
549
807
  fill: color,
550
808
  realToCanvasMat
551
809
  });
810
+ if (hasSoldermask && margin < 0) {
811
+ drawSoldermaskRingForCircle(
812
+ ctx,
813
+ { x: pad.x, y: pad.y },
814
+ pad.radius,
815
+ margin,
816
+ realToCanvasMat,
817
+ soldermaskRingColor,
818
+ color
819
+ );
820
+ }
552
821
  return;
553
822
  }
554
823
  if (pad.shape === "pill") {
824
+ if (hasSoldermask && margin > 0) {
825
+ drawPill({
826
+ ctx,
827
+ center: { x: pad.x, y: pad.y },
828
+ width: pad.width + margin * 2,
829
+ height: pad.height + margin * 2,
830
+ fill: positiveMarginColor,
831
+ realToCanvasMat
832
+ });
833
+ }
555
834
  drawPill({
556
835
  ctx,
557
836
  center: { x: pad.x, y: pad.y },
@@ -560,9 +839,33 @@ function drawPcbSmtPad(params) {
560
839
  fill: color,
561
840
  realToCanvasMat
562
841
  });
842
+ if (hasSoldermask && margin < 0) {
843
+ drawSoldermaskRingForPill(
844
+ ctx,
845
+ { x: pad.x, y: pad.y },
846
+ pad.width,
847
+ pad.height,
848
+ margin,
849
+ 0,
850
+ realToCanvasMat,
851
+ soldermaskRingColor,
852
+ color
853
+ );
854
+ }
563
855
  return;
564
856
  }
565
857
  if (pad.shape === "rotated_pill") {
858
+ if (hasSoldermask && margin > 0) {
859
+ drawPill({
860
+ ctx,
861
+ center: { x: pad.x, y: pad.y },
862
+ width: pad.width + margin * 2,
863
+ height: pad.height + margin * 2,
864
+ fill: positiveMarginColor,
865
+ realToCanvasMat,
866
+ rotation: pad.ccw_rotation ?? 0
867
+ });
868
+ }
566
869
  drawPill({
567
870
  ctx,
568
871
  center: { x: pad.x, y: pad.y },
@@ -572,6 +875,19 @@ function drawPcbSmtPad(params) {
572
875
  realToCanvasMat,
573
876
  rotation: pad.ccw_rotation ?? 0
574
877
  });
878
+ if (hasSoldermask && margin < 0) {
879
+ drawSoldermaskRingForPill(
880
+ ctx,
881
+ { x: pad.x, y: pad.y },
882
+ pad.width,
883
+ pad.height,
884
+ margin,
885
+ pad.ccw_rotation ?? 0,
886
+ realToCanvasMat,
887
+ soldermaskRingColor,
888
+ color
889
+ );
890
+ }
575
891
  return;
576
892
  }
577
893
  if (pad.shape === "polygon") {
@@ -588,7 +904,7 @@ function drawPcbSmtPad(params) {
588
904
  }
589
905
 
590
906
  // lib/drawer/shapes/line.ts
591
- import { applyToPoint as applyToPoint6 } from "transformation-matrix";
907
+ import { applyToPoint as applyToPoint7 } from "transformation-matrix";
592
908
  function drawLine(params) {
593
909
  const {
594
910
  ctx,
@@ -599,8 +915,8 @@ function drawLine(params) {
599
915
  realToCanvasMat,
600
916
  lineCap = "round"
601
917
  } = params;
602
- const [x1, y1] = applyToPoint6(realToCanvasMat, [start.x, start.y]);
603
- const [x2, y2] = applyToPoint6(realToCanvasMat, [end.x, end.y]);
918
+ const [x1, y1] = applyToPoint7(realToCanvasMat, [start.x, start.y]);
919
+ const [x2, y2] = applyToPoint7(realToCanvasMat, [end.x, end.y]);
604
920
  const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
605
921
  ctx.beginPath();
606
922
  ctx.moveTo(x1, y1);
@@ -641,7 +957,7 @@ function drawPcbTrace(params) {
641
957
  }
642
958
 
643
959
  // lib/drawer/shapes/path.ts
644
- import { applyToPoint as applyToPoint7 } from "transformation-matrix";
960
+ import { applyToPoint as applyToPoint8 } from "transformation-matrix";
645
961
  function drawPath(params) {
646
962
  const {
647
963
  ctx,
@@ -655,7 +971,7 @@ function drawPath(params) {
655
971
  if (points.length < 2) return;
656
972
  ctx.beginPath();
657
973
  const canvasPoints = points.map(
658
- (p) => applyToPoint7(realToCanvasMat, [p.x, p.y])
974
+ (p) => applyToPoint8(realToCanvasMat, [p.x, p.y])
659
975
  );
660
976
  const firstPoint = canvasPoints[0];
661
977
  if (!firstPoint) return;
@@ -726,11 +1042,11 @@ function drawPcbBoard(params) {
726
1042
  }
727
1043
 
728
1044
  // lib/drawer/elements/pcb-silkscreen-text.ts
729
- import { applyToPoint as applyToPoint9 } from "transformation-matrix";
1045
+ import { applyToPoint as applyToPoint10 } from "transformation-matrix";
730
1046
 
731
1047
  // lib/drawer/shapes/text/text.ts
732
1048
  import { lineAlphabet } from "@tscircuit/alphabet";
733
- import { applyToPoint as applyToPoint8 } from "transformation-matrix";
1049
+ import { applyToPoint as applyToPoint9 } from "transformation-matrix";
734
1050
 
735
1051
  // lib/drawer/shapes/text/getAlphabetLayout.ts
736
1052
  var GLYPH_WIDTH_RATIO = 0.62;
@@ -827,7 +1143,7 @@ function drawText(params) {
827
1143
  rotation = 0
828
1144
  } = params;
829
1145
  if (!text) return;
830
- const [canvasX, canvasY] = applyToPoint8(realToCanvasMat, [x, y]);
1146
+ const [canvasX, canvasY] = applyToPoint9(realToCanvasMat, [x, y]);
831
1147
  const scale2 = Math.abs(realToCanvasMat.a);
832
1148
  const scaledFontSize = fontSize * scale2;
833
1149
  const layout = getAlphabetLayout(text, scaledFontSize);
@@ -864,7 +1180,7 @@ function drawPcbSilkscreenText(params) {
864
1180
  const content = text.text ?? "";
865
1181
  if (!content) return;
866
1182
  const color = layerToSilkscreenColor(text.layer, colorMap);
867
- const [x, y] = applyToPoint9(realToCanvasMat, [
1183
+ const [x, y] = applyToPoint10(realToCanvasMat, [
868
1184
  text.anchor_position.x,
869
1185
  text.anchor_position.y
870
1186
  ]);
@@ -969,13 +1285,32 @@ function drawPcbSilkscreenPath(params) {
969
1285
  }
970
1286
  }
971
1287
 
972
- // lib/drawer/elements/pcb-silkscreen-pill.ts
1288
+ // lib/drawer/elements/pcb-silkscreen-oval.ts
973
1289
  function layerToSilkscreenColor6(layer, colorMap) {
974
1290
  return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
975
1291
  }
1292
+ function drawPcbSilkscreenOval(params) {
1293
+ const { ctx, oval, realToCanvasMat, colorMap } = params;
1294
+ const color = layerToSilkscreenColor6(oval.layer, colorMap);
1295
+ drawOval({
1296
+ ctx,
1297
+ center: oval.center,
1298
+ radius_x: oval.radius_x,
1299
+ radius_y: oval.radius_y,
1300
+ stroke: color,
1301
+ strokeWidth: 0.1,
1302
+ realToCanvasMat,
1303
+ rotation: oval.ccw_rotation ?? 0
1304
+ });
1305
+ }
1306
+
1307
+ // lib/drawer/elements/pcb-silkscreen-pill.ts
1308
+ function layerToSilkscreenColor7(layer, colorMap) {
1309
+ return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
1310
+ }
976
1311
  function drawPcbSilkscreenPill(params) {
977
1312
  const { ctx, pill, realToCanvasMat, colorMap } = params;
978
- const color = layerToSilkscreenColor6(pill.layer, colorMap);
1313
+ const color = layerToSilkscreenColor7(pill.layer, colorMap);
979
1314
  const strokeWidth = 0.2;
980
1315
  drawPill({
981
1316
  ctx,
@@ -1027,7 +1362,7 @@ function drawPcbCutout(params) {
1027
1362
  }
1028
1363
 
1029
1364
  // lib/drawer/elements/pcb-copper-pour.ts
1030
- import { applyToPoint as applyToPoint10 } from "transformation-matrix";
1365
+ import { applyToPoint as applyToPoint11 } from "transformation-matrix";
1031
1366
  function layerToColor3(layer, colorMap) {
1032
1367
  return colorMap.copper[layer] ?? colorMap.copper.top;
1033
1368
  }
@@ -1036,7 +1371,7 @@ function drawPcbCopperPour(params) {
1036
1371
  const color = layerToColor3(pour.layer, colorMap);
1037
1372
  ctx.save();
1038
1373
  if (pour.shape === "rect") {
1039
- const [cx, cy] = applyToPoint10(realToCanvasMat, [
1374
+ const [cx, cy] = applyToPoint11(realToCanvasMat, [
1040
1375
  pour.center.x,
1041
1376
  pour.center.y
1042
1377
  ]);
@@ -1057,7 +1392,7 @@ function drawPcbCopperPour(params) {
1057
1392
  if (pour.shape === "polygon") {
1058
1393
  if (pour.points && pour.points.length >= 3) {
1059
1394
  const canvasPoints = pour.points.map(
1060
- (p) => applyToPoint10(realToCanvasMat, [p.x, p.y])
1395
+ (p) => applyToPoint11(realToCanvasMat, [p.x, p.y])
1061
1396
  );
1062
1397
  const firstPoint = canvasPoints[0];
1063
1398
  if (!firstPoint) {
@@ -1085,7 +1420,7 @@ function drawPcbCopperPour(params) {
1085
1420
  }
1086
1421
 
1087
1422
  // lib/drawer/elements/pcb-copper-text.ts
1088
- import { applyToPoint as applyToPoint11 } from "transformation-matrix";
1423
+ import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1089
1424
  var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
1090
1425
  function layerToCopperColor(layer, colorMap) {
1091
1426
  return colorMap.copper[layer] ?? colorMap.copper.top;
@@ -1100,7 +1435,7 @@ function drawPcbCopperText(params) {
1100
1435
  const { ctx, text, realToCanvasMat, colorMap } = params;
1101
1436
  const content = text.text ?? "";
1102
1437
  if (!content) return;
1103
- const [x, y] = applyToPoint11(realToCanvasMat, [
1438
+ const [x, y] = applyToPoint12(realToCanvasMat, [
1104
1439
  text.anchor_position.x,
1105
1440
  text.anchor_position.y
1106
1441
  ]);
@@ -1284,7 +1619,7 @@ function drawPcbNoteText(params) {
1284
1619
  }
1285
1620
 
1286
1621
  // lib/drawer/elements/pcb-note-dimension.ts
1287
- import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1622
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
1288
1623
 
1289
1624
  // lib/drawer/shapes/arrow.ts
1290
1625
  function drawArrow(params) {
@@ -1369,11 +1704,11 @@ function drawPcbNoteDimension(params) {
1369
1704
  stroke: color,
1370
1705
  realToCanvasMat
1371
1706
  });
1372
- const [canvasFromX, canvasFromY] = applyToPoint12(realToCanvasMat, [
1707
+ const [canvasFromX, canvasFromY] = applyToPoint13(realToCanvasMat, [
1373
1708
  fromX,
1374
1709
  fromY
1375
1710
  ]);
1376
- const [canvasToX, canvasToY] = applyToPoint12(realToCanvasMat, [toX, toY]);
1711
+ const [canvasToX, canvasToY] = applyToPoint13(realToCanvasMat, [toX, toY]);
1377
1712
  const canvasDx = canvasToX - canvasFromX;
1378
1713
  const canvasDy = canvasToY - canvasFromY;
1379
1714
  const lineAngle = Math.atan2(canvasDy, canvasDx);
@@ -1434,15 +1769,15 @@ function drawPcbNoteDimension(params) {
1434
1769
  }
1435
1770
 
1436
1771
  // lib/drawer/elements/pcb-note-line.ts
1437
- import { applyToPoint as applyToPoint13 } from "transformation-matrix";
1772
+ import { applyToPoint as applyToPoint14 } from "transformation-matrix";
1438
1773
  function drawPcbNoteLine(params) {
1439
1774
  const { ctx, line, realToCanvasMat, colorMap } = params;
1440
1775
  const defaultColor = "rgb(89, 148, 220)";
1441
1776
  const color = line.color ?? defaultColor;
1442
1777
  const strokeWidth = line.stroke_width ?? 0.1;
1443
1778
  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]);
1779
+ const [x1, y1] = applyToPoint14(realToCanvasMat, [line.x1, line.y1]);
1780
+ const [x2, y2] = applyToPoint14(realToCanvasMat, [line.x2, line.y2]);
1446
1781
  const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
1447
1782
  ctx.save();
1448
1783
  if (isDashed) {
@@ -1524,8 +1859,76 @@ var CircuitToCanvasDrawer = class {
1524
1859
  );
1525
1860
  }
1526
1861
  drawElements(elements, options = {}) {
1862
+ const hasSoldermaskPads = elements.some(
1863
+ (el) => el.type === "pcb_smtpad" && el.is_covered_with_solder_mask === true
1864
+ );
1527
1865
  for (const element of elements) {
1528
- this.drawElement(element, options);
1866
+ if (element.type === "pcb_board" && hasSoldermaskPads) {
1867
+ this.drawBoardWithSoldermask(element);
1868
+ } else {
1869
+ this.drawElement(element, options);
1870
+ }
1871
+ }
1872
+ }
1873
+ drawBoardWithSoldermask(board) {
1874
+ const { width, height, center, outline } = board;
1875
+ const layer = "top";
1876
+ if (outline && Array.isArray(outline) && outline.length >= 3) {
1877
+ const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
1878
+ const canvasPoints = outline.map((p) => {
1879
+ const [x, y] = applyToPoint15(this.realToCanvasMat, [p.x, p.y]);
1880
+ return { x, y };
1881
+ });
1882
+ this.ctx.beginPath();
1883
+ const firstPoint = canvasPoints[0];
1884
+ if (firstPoint) {
1885
+ this.ctx.moveTo(firstPoint.x, firstPoint.y);
1886
+ for (let i = 1; i < canvasPoints.length; i++) {
1887
+ const point = canvasPoints[i];
1888
+ if (point) {
1889
+ this.ctx.lineTo(point.x, point.y);
1890
+ }
1891
+ }
1892
+ this.ctx.closePath();
1893
+ }
1894
+ this.ctx.fillStyle = soldermaskColor;
1895
+ this.ctx.fill();
1896
+ drawPath({
1897
+ ctx: this.ctx,
1898
+ points: outline.map((p) => ({ x: p.x, y: p.y })),
1899
+ stroke: this.colorMap.boardOutline,
1900
+ strokeWidth: 0.1,
1901
+ realToCanvasMat: this.realToCanvasMat,
1902
+ closePath: true
1903
+ });
1904
+ return;
1905
+ }
1906
+ if (width !== void 0 && height !== void 0 && center) {
1907
+ const soldermaskColor = this.colorMap.soldermask[layer] ?? this.colorMap.soldermask.top;
1908
+ drawRect({
1909
+ ctx: this.ctx,
1910
+ center,
1911
+ width,
1912
+ height,
1913
+ fill: soldermaskColor,
1914
+ realToCanvasMat: this.realToCanvasMat
1915
+ });
1916
+ const halfWidth = width / 2;
1917
+ const halfHeight = height / 2;
1918
+ const corners = [
1919
+ { x: center.x - halfWidth, y: center.y - halfHeight },
1920
+ { x: center.x + halfWidth, y: center.y - halfHeight },
1921
+ { x: center.x + halfWidth, y: center.y + halfHeight },
1922
+ { x: center.x - halfWidth, y: center.y + halfHeight }
1923
+ ];
1924
+ drawPath({
1925
+ ctx: this.ctx,
1926
+ points: corners,
1927
+ stroke: this.colorMap.boardOutline,
1928
+ strokeWidth: 0.1,
1929
+ realToCanvasMat: this.realToCanvasMat,
1930
+ closePath: true
1931
+ });
1529
1932
  }
1530
1933
  }
1531
1934
  drawElement(element, options) {
@@ -1628,6 +2031,14 @@ var CircuitToCanvasDrawer = class {
1628
2031
  colorMap: this.colorMap
1629
2032
  });
1630
2033
  }
2034
+ if (element.type === "pcb_silkscreen_oval") {
2035
+ drawPcbSilkscreenOval({
2036
+ ctx: this.ctx,
2037
+ oval: element,
2038
+ realToCanvasMat: this.realToCanvasMat,
2039
+ colorMap: this.colorMap
2040
+ });
2041
+ }
1631
2042
  if (element.type === "pcb_cutout") {
1632
2043
  drawPcbCutout({
1633
2044
  ctx: this.ctx,
@@ -1741,6 +2152,7 @@ export {
1741
2152
  drawPcbPlatedHole,
1742
2153
  drawPcbSilkscreenCircle,
1743
2154
  drawPcbSilkscreenLine,
2155
+ drawPcbSilkscreenOval,
1744
2156
  drawPcbSilkscreenPath,
1745
2157
  drawPcbSilkscreenPill,
1746
2158
  drawPcbSilkscreenRect,
@@ -1751,6 +2163,9 @@ export {
1751
2163
  drawPill,
1752
2164
  drawPolygon,
1753
2165
  drawRect,
2166
+ drawSoldermaskRingForCircle,
2167
+ drawSoldermaskRingForPill,
2168
+ drawSoldermaskRingForRect,
1754
2169
  drawText,
1755
2170
  getAlphabetLayout,
1756
2171
  getTextStartPosition,