circuit-to-canvas 0.0.20 → 0.0.22

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
@@ -647,6 +647,9 @@ function drawPcbBoard(params) {
647
647
  }
648
648
  }
649
649
 
650
+ // lib/drawer/elements/pcb-silkscreen.ts
651
+ import { applyToPoint as applyToPoint9 } from "transformation-matrix";
652
+
650
653
  // lib/drawer/shapes/text/text.ts
651
654
  import { lineAlphabet } from "@tscircuit/alphabet";
652
655
  import { applyToPoint as applyToPoint8 } from "transformation-matrix";
@@ -766,22 +769,39 @@ function drawText(params) {
766
769
  function layerToSilkscreenColor(layer, colorMap) {
767
770
  return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
768
771
  }
772
+ function mapAnchorAlignment(alignment) {
773
+ if (!alignment) return "center";
774
+ return alignment;
775
+ }
769
776
  function drawPcbSilkscreenText(params) {
770
777
  const { ctx, text, realToCanvasMat, colorMap } = params;
778
+ const content = text.text ?? "";
779
+ if (!content) return;
771
780
  const color = layerToSilkscreenColor(text.layer, colorMap);
772
- const fontSize = text.font_size ?? 1;
781
+ const [x, y] = applyToPoint9(realToCanvasMat, [
782
+ text.anchor_position.x,
783
+ text.anchor_position.y
784
+ ]);
785
+ const scale2 = Math.abs(realToCanvasMat.a);
786
+ const fontSize = (text.font_size ?? 1) * scale2;
773
787
  const rotation = text.ccw_rotation ?? 0;
774
- drawText({
775
- ctx,
776
- text: text.text,
777
- x: text.anchor_position.x,
778
- y: text.anchor_position.y,
779
- fontSize,
780
- color,
781
- realToCanvasMat,
782
- anchorAlignment: text.anchor_alignment ?? "center",
783
- rotation
784
- });
788
+ const layout = getAlphabetLayout(content, fontSize);
789
+ const alignment = mapAnchorAlignment(text.anchor_alignment);
790
+ const startPos = getTextStartPosition(alignment, layout);
791
+ ctx.save();
792
+ ctx.translate(x, y);
793
+ if (rotation !== 0) {
794
+ ctx.rotate(-rotation * (Math.PI / 180));
795
+ }
796
+ if (text.layer === "bottom") {
797
+ ctx.scale(-1, 1);
798
+ }
799
+ ctx.lineWidth = layout.strokeWidth;
800
+ ctx.lineCap = "round";
801
+ ctx.lineJoin = "round";
802
+ ctx.strokeStyle = color;
803
+ strokeAlphabetText(ctx, content, layout, startPos.x, startPos.y);
804
+ ctx.restore();
785
805
  }
786
806
  function drawPcbSilkscreenRect(params) {
787
807
  const { ctx, rect, realToCanvasMat, colorMap } = params;
@@ -876,7 +896,7 @@ function drawPcbCutout(params) {
876
896
  }
877
897
 
878
898
  // lib/drawer/elements/pcb-copper-pour.ts
879
- import { applyToPoint as applyToPoint9 } from "transformation-matrix";
899
+ import { applyToPoint as applyToPoint10 } from "transformation-matrix";
880
900
  function layerToColor3(layer, colorMap) {
881
901
  return colorMap.copper[layer] ?? colorMap.copper.top;
882
902
  }
@@ -885,7 +905,7 @@ function drawPcbCopperPour(params) {
885
905
  const color = layerToColor3(pour.layer, colorMap);
886
906
  ctx.save();
887
907
  if (pour.shape === "rect") {
888
- const [cx, cy] = applyToPoint9(realToCanvasMat, [
908
+ const [cx, cy] = applyToPoint10(realToCanvasMat, [
889
909
  pour.center.x,
890
910
  pour.center.y
891
911
  ]);
@@ -906,7 +926,7 @@ function drawPcbCopperPour(params) {
906
926
  if (pour.shape === "polygon") {
907
927
  if (pour.points && pour.points.length >= 3) {
908
928
  const canvasPoints = pour.points.map(
909
- (p) => applyToPoint9(realToCanvasMat, [p.x, p.y])
929
+ (p) => applyToPoint10(realToCanvasMat, [p.x, p.y])
910
930
  );
911
931
  const firstPoint = canvasPoints[0];
912
932
  if (!firstPoint) {
@@ -934,12 +954,12 @@ function drawPcbCopperPour(params) {
934
954
  }
935
955
 
936
956
  // lib/drawer/elements/pcb-copper-text.ts
937
- import { applyToPoint as applyToPoint10 } from "transformation-matrix";
957
+ import { applyToPoint as applyToPoint11 } from "transformation-matrix";
938
958
  var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
939
959
  function layerToCopperColor(layer, colorMap) {
940
960
  return colorMap.copper[layer] ?? colorMap.copper.top;
941
961
  }
942
- function mapAnchorAlignment(alignment) {
962
+ function mapAnchorAlignment2(alignment) {
943
963
  if (!alignment) return "center";
944
964
  if (alignment.includes("left")) return "center_left";
945
965
  if (alignment.includes("right")) return "center_right";
@@ -949,7 +969,7 @@ function drawPcbCopperText(params) {
949
969
  const { ctx, text, realToCanvasMat, colorMap } = params;
950
970
  const content = text.text ?? "";
951
971
  if (!content) return;
952
- const [x, y] = applyToPoint10(realToCanvasMat, [
972
+ const [x, y] = applyToPoint11(realToCanvasMat, [
953
973
  text.anchor_position.x,
954
974
  text.anchor_position.y
955
975
  ]);
@@ -963,7 +983,7 @@ function drawPcbCopperText(params) {
963
983
  const textColor = layerToCopperColor(text.layer, colorMap);
964
984
  const layout = getAlphabetLayout(content, fontSize);
965
985
  const totalWidth = layout.width + layout.strokeWidth;
966
- const alignment = mapAnchorAlignment(text.anchor_alignment);
986
+ const alignment = mapAnchorAlignment2(text.anchor_alignment);
967
987
  const startPos = getTextStartPosition(alignment, layout);
968
988
  const startX = startPos.x;
969
989
  const startY = startPos.y;
@@ -1133,7 +1153,7 @@ function drawPcbNoteText(params) {
1133
1153
  }
1134
1154
 
1135
1155
  // lib/drawer/elements/pcb-note-dimension.ts
1136
- import { applyToPoint as applyToPoint11 } from "transformation-matrix";
1156
+ import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1137
1157
 
1138
1158
  // lib/drawer/shapes/arrow.ts
1139
1159
  function drawArrow(params) {
@@ -1218,11 +1238,11 @@ function drawPcbNoteDimension(params) {
1218
1238
  stroke: color,
1219
1239
  realToCanvasMat
1220
1240
  });
1221
- const [canvasFromX, canvasFromY] = applyToPoint11(realToCanvasMat, [
1241
+ const [canvasFromX, canvasFromY] = applyToPoint12(realToCanvasMat, [
1222
1242
  fromX,
1223
1243
  fromY
1224
1244
  ]);
1225
- const [canvasToX, canvasToY] = applyToPoint11(realToCanvasMat, [toX, toY]);
1245
+ const [canvasToX, canvasToY] = applyToPoint12(realToCanvasMat, [toX, toY]);
1226
1246
  const canvasDx = canvasToX - canvasFromX;
1227
1247
  const canvasDy = canvasToY - canvasFromY;
1228
1248
  const lineAngle = Math.atan2(canvasDy, canvasDx);
@@ -1283,15 +1303,15 @@ function drawPcbNoteDimension(params) {
1283
1303
  }
1284
1304
 
1285
1305
  // lib/drawer/elements/pcb-note-line.ts
1286
- import { applyToPoint as applyToPoint12 } from "transformation-matrix";
1306
+ import { applyToPoint as applyToPoint13 } from "transformation-matrix";
1287
1307
  function drawPcbNoteLine(params) {
1288
1308
  const { ctx, line, realToCanvasMat, colorMap } = params;
1289
1309
  const defaultColor = "rgb(89, 148, 220)";
1290
1310
  const color = line.color ?? defaultColor;
1291
1311
  const strokeWidth = line.stroke_width ?? 0.1;
1292
1312
  const isDashed = line.is_dashed ?? false;
1293
- const [x1, y1] = applyToPoint12(realToCanvasMat, [line.x1, line.y1]);
1294
- const [x2, y2] = applyToPoint12(realToCanvasMat, [line.x2, line.y2]);
1313
+ const [x1, y1] = applyToPoint13(realToCanvasMat, [line.x1, line.y1]);
1314
+ const [x2, y2] = applyToPoint13(realToCanvasMat, [line.x2, line.y2]);
1295
1315
  const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
1296
1316
  ctx.save();
1297
1317
  if (isDashed) {
@@ -6,12 +6,18 @@ import type {
6
6
  PcbSilkscreenPath,
7
7
  } from "circuit-json"
8
8
  import type { Matrix } from "transformation-matrix"
9
+ import { applyToPoint } from "transformation-matrix"
9
10
  import type { PcbColorMap, CanvasContext } from "../types"
10
11
  import { drawRect } from "../shapes/rect"
11
12
  import { drawCircle } from "../shapes/circle"
12
13
  import { drawLine } from "../shapes/line"
13
14
  import { drawPath } from "../shapes/path"
14
- import { drawText } from "../shapes/text"
15
+ import {
16
+ getAlphabetLayout,
17
+ strokeAlphabetText,
18
+ getTextStartPosition,
19
+ type AnchorAlignment,
20
+ } from "../shapes/text"
15
21
 
16
22
  export interface DrawPcbSilkscreenTextParams {
17
23
  ctx: CanvasContext
@@ -54,27 +60,52 @@ function layerToSilkscreenColor(layer: string, colorMap: PcbColorMap): string {
54
60
  : colorMap.silkscreen.top
55
61
  }
56
62
 
63
+ function mapAnchorAlignment(alignment?: string): AnchorAlignment {
64
+ if (!alignment) return "center"
65
+ return alignment as AnchorAlignment
66
+ }
67
+
57
68
  export function drawPcbSilkscreenText(
58
69
  params: DrawPcbSilkscreenTextParams,
59
70
  ): void {
60
71
  const { ctx, text, realToCanvasMat, colorMap } = params
61
72
 
73
+ const content = text.text ?? ""
74
+ if (!content) return
75
+
62
76
  const color = layerToSilkscreenColor(text.layer, colorMap)
63
- const fontSize = text.font_size ?? 1
77
+ const [x, y] = applyToPoint(realToCanvasMat, [
78
+ text.anchor_position.x,
79
+ text.anchor_position.y,
80
+ ])
81
+ const scale = Math.abs(realToCanvasMat.a)
82
+ const fontSize = (text.font_size ?? 1) * scale
64
83
  const rotation = text.ccw_rotation ?? 0
65
84
 
66
- // Use @tscircuit/alphabet to draw text (font-independent, stroke-based rendering)
67
- drawText({
68
- ctx,
69
- text: text.text,
70
- x: text.anchor_position.x,
71
- y: text.anchor_position.y,
72
- fontSize,
73
- color,
74
- realToCanvasMat,
75
- anchorAlignment: text.anchor_alignment ?? "center",
76
- rotation,
77
- })
85
+ const layout = getAlphabetLayout(content, fontSize)
86
+ const alignment = mapAnchorAlignment(text.anchor_alignment)
87
+ const startPos = getTextStartPosition(alignment, layout)
88
+
89
+ ctx.save()
90
+ ctx.translate(x, y)
91
+
92
+ // Apply rotation (CCW rotation in degrees)
93
+ if (rotation !== 0) {
94
+ ctx.rotate(-rotation * (Math.PI / 180))
95
+ }
96
+
97
+ if (text.layer === "bottom") {
98
+ ctx.scale(-1, 1)
99
+ }
100
+
101
+ ctx.lineWidth = layout.strokeWidth
102
+ ctx.lineCap = "round"
103
+ ctx.lineJoin = "round"
104
+ ctx.strokeStyle = color
105
+
106
+ strokeAlphabetText(ctx, content, layout, startPos.x, startPos.y)
107
+
108
+ ctx.restore()
78
109
  }
79
110
 
80
111
  export function drawPcbSilkscreenRect(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "circuit-to-canvas",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.20",
4
+ "version": "0.0.22",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -64,6 +64,116 @@ test("draw silkscreen text bottom layer", async () => {
64
64
  )
65
65
  })
66
66
 
67
+ test("draw silkscreen text with rotation", async () => {
68
+ const canvas = createCanvas(100, 100)
69
+ const ctx = canvas.getContext("2d")
70
+ const drawer = new CircuitToCanvasDrawer(ctx)
71
+
72
+ ctx.fillStyle = "#1a1a1a"
73
+ ctx.fillRect(0, 0, 100, 100)
74
+
75
+ const text: PcbSilkscreenText = {
76
+ type: "pcb_silkscreen_text",
77
+ pcb_silkscreen_text_id: "text1",
78
+ pcb_component_id: "component1",
79
+ layer: "top",
80
+ text: "ROT45",
81
+ anchor_position: { x: 50, y: 50 },
82
+ anchor_alignment: "center",
83
+ font: "tscircuit2024",
84
+ font_size: 8,
85
+ ccw_rotation: 45,
86
+ }
87
+
88
+ drawer.drawElements([text])
89
+
90
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
91
+ import.meta.path,
92
+ "silkscreen-text-rotated",
93
+ )
94
+ })
95
+
96
+ test("draw silkscreen text bottom layer with rotation - tests transform order", async () => {
97
+ const canvas = createCanvas(150, 150)
98
+ const ctx = canvas.getContext("2d")
99
+ const drawer = new CircuitToCanvasDrawer(ctx)
100
+
101
+ ctx.fillStyle = "#1a1a1a"
102
+ ctx.fillRect(0, 0, 150, 150)
103
+
104
+ // This test verifies the transform order (translate -> rotate -> scale) is correct
105
+ // by testing bottom layer text with various rotations
106
+ const texts: PcbSilkscreenText[] = [
107
+ {
108
+ type: "pcb_silkscreen_text",
109
+ pcb_silkscreen_text_id: "text1",
110
+ pcb_component_id: "component1",
111
+ layer: "bottom",
112
+ text: "0",
113
+ anchor_position: { x: 75, y: 30 },
114
+ anchor_alignment: "center",
115
+ font: "tscircuit2024",
116
+ font_size: 6,
117
+ ccw_rotation: 0,
118
+ },
119
+ {
120
+ type: "pcb_silkscreen_text",
121
+ pcb_silkscreen_text_id: "text2",
122
+ pcb_component_id: "component1",
123
+ layer: "bottom",
124
+ text: "90",
125
+ anchor_position: { x: 120, y: 75 },
126
+ anchor_alignment: "center",
127
+ font: "tscircuit2024",
128
+ font_size: 6,
129
+ ccw_rotation: 90,
130
+ },
131
+ {
132
+ type: "pcb_silkscreen_text",
133
+ pcb_silkscreen_text_id: "text3",
134
+ pcb_component_id: "component1",
135
+ layer: "bottom",
136
+ text: "180",
137
+ anchor_position: { x: 75, y: 120 },
138
+ anchor_alignment: "center",
139
+ font: "tscircuit2024",
140
+ font_size: 6,
141
+ ccw_rotation: 180,
142
+ },
143
+ {
144
+ type: "pcb_silkscreen_text",
145
+ pcb_silkscreen_text_id: "text4",
146
+ pcb_component_id: "component1",
147
+ layer: "bottom",
148
+ text: "270",
149
+ anchor_position: { x: 30, y: 75 },
150
+ anchor_alignment: "center",
151
+ font: "tscircuit2024",
152
+ font_size: 6,
153
+ ccw_rotation: 270,
154
+ },
155
+ {
156
+ type: "pcb_silkscreen_text",
157
+ pcb_silkscreen_text_id: "text5",
158
+ pcb_component_id: "component1",
159
+ layer: "bottom",
160
+ text: "BTM",
161
+ anchor_position: { x: 75, y: 75 },
162
+ anchor_alignment: "center",
163
+ font: "tscircuit2024",
164
+ font_size: 8,
165
+ ccw_rotation: 45,
166
+ },
167
+ ]
168
+
169
+ drawer.drawElements(texts)
170
+
171
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
172
+ import.meta.path,
173
+ "silkscreen-text-bottom-rotated",
174
+ )
175
+ })
176
+
67
177
  test("draw silkscreen rect", async () => {
68
178
  const canvas = createCanvas(100, 100)
69
179
  const ctx = canvas.getContext("2d")