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 +45 -25
- package/lib/drawer/elements/pcb-silkscreen.ts +45 -14
- package/package.json +1 -1
- package/tests/elements/__snapshots__/pcb-silkscreen.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-text-bottom-rotated.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-text-bottom.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-text-rotated.snap.png +0 -0
- package/tests/elements/pcb-silkscreen.test.ts +110 -0
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
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
|
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] =
|
|
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) =>
|
|
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
|
|
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
|
|
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] =
|
|
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 =
|
|
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
|
|
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] =
|
|
1241
|
+
const [canvasFromX, canvasFromY] = applyToPoint12(realToCanvasMat, [
|
|
1222
1242
|
fromX,
|
|
1223
1243
|
fromY
|
|
1224
1244
|
]);
|
|
1225
|
-
const [canvasToX, canvasToY] =
|
|
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
|
|
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] =
|
|
1294
|
-
const [x2, 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 {
|
|
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
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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")
|