circuit-to-canvas 0.0.1 → 0.0.2

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.
Files changed (66) hide show
  1. package/.github/workflows/bun-formatcheck.yml +26 -0
  2. package/.github/workflows/bun-pver-release.yml +77 -0
  3. package/.github/workflows/bun-test.yml +31 -0
  4. package/.github/workflows/bun-typecheck.yml +26 -0
  5. package/LICENSE +21 -0
  6. package/README.md +2 -0
  7. package/dist/index.d.ts +146 -2
  8. package/dist/index.js +620 -0
  9. package/lib/drawer/CircuitToCanvasDrawer.ts +138 -1
  10. package/lib/drawer/elements/index.ts +30 -0
  11. package/lib/drawer/elements/pcb-board.ts +62 -0
  12. package/lib/drawer/elements/pcb-copper-pour.ts +84 -0
  13. package/lib/drawer/elements/pcb-cutout.ts +53 -0
  14. package/lib/drawer/elements/pcb-hole.ts +90 -0
  15. package/lib/drawer/elements/pcb-silkscreen.ts +171 -0
  16. package/lib/drawer/elements/pcb-smtpad.ts +108 -0
  17. package/lib/drawer/elements/pcb-trace.ts +59 -0
  18. package/lib/drawer/elements/pcb-via.ts +33 -0
  19. package/lib/drawer/shapes/index.ts +3 -0
  20. package/lib/drawer/shapes/line.ts +37 -0
  21. package/lib/drawer/shapes/path.ts +61 -0
  22. package/lib/drawer/shapes/polygon.ts +38 -0
  23. package/lib/drawer/types.ts +16 -0
  24. package/package.json +2 -2
  25. package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
  26. package/tests/elements/__snapshots__/bottom-layer-pad.snap.png +0 -0
  27. package/tests/elements/__snapshots__/bottom-layer-trace.snap.png +0 -0
  28. package/tests/elements/__snapshots__/circular-cutout.snap.png +0 -0
  29. package/tests/elements/__snapshots__/circular-pad.snap.png +0 -0
  30. package/tests/elements/__snapshots__/copper-pour-with-trace.snap.png +0 -0
  31. package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
  32. package/tests/elements/__snapshots__/multi-segment-trace.snap.png +0 -0
  33. package/tests/elements/__snapshots__/multiple-traces.snap.png +0 -0
  34. package/tests/elements/__snapshots__/multiple-vias.snap.png +0 -0
  35. package/tests/elements/__snapshots__/oval-hole.snap.png +0 -0
  36. package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
  37. package/tests/elements/__snapshots__/pcb-copper-pour.snap.png +0 -0
  38. package/tests/elements/__snapshots__/pcb-cutout.snap.png +0 -0
  39. package/tests/elements/__snapshots__/pcb-hole.snap.png +0 -0
  40. package/tests/elements/__snapshots__/pcb-silkscreen.snap.png +0 -0
  41. package/tests/elements/__snapshots__/pcb-smtpad.snap.png +0 -0
  42. package/tests/elements/__snapshots__/pcb-trace.snap.png +0 -0
  43. package/tests/elements/__snapshots__/pcb-via.snap.png +0 -0
  44. package/tests/elements/__snapshots__/pill-hole.snap.png +0 -0
  45. package/tests/elements/__snapshots__/pill-pad.snap.png +0 -0
  46. package/tests/elements/__snapshots__/polygon-copper-pour.snap.png +0 -0
  47. package/tests/elements/__snapshots__/polygon-cutout.snap.png +0 -0
  48. package/tests/elements/__snapshots__/polygon-pad.snap.png +0 -0
  49. package/tests/elements/__snapshots__/rect-pad-with-border-radius.snap.png +0 -0
  50. package/tests/elements/__snapshots__/rotated-rect-pad.snap.png +0 -0
  51. package/tests/elements/__snapshots__/silkscreen-circle.snap.png +0 -0
  52. package/tests/elements/__snapshots__/silkscreen-line.snap.png +0 -0
  53. package/tests/elements/__snapshots__/silkscreen-on-component.snap.png +0 -0
  54. package/tests/elements/__snapshots__/silkscreen-path.snap.png +0 -0
  55. package/tests/elements/__snapshots__/silkscreen-rect.snap.png +0 -0
  56. package/tests/elements/__snapshots__/silkscreen-text-bottom.snap.png +0 -0
  57. package/tests/elements/__snapshots__/square-hole.snap.png +0 -0
  58. package/tests/elements/pcb-board.test.ts +155 -0
  59. package/tests/elements/pcb-copper-pour.test.ts +120 -0
  60. package/tests/elements/pcb-cutout.test.ts +82 -0
  61. package/tests/elements/pcb-hole.test.ts +105 -0
  62. package/tests/elements/pcb-silkscreen.test.ts +245 -0
  63. package/tests/elements/pcb-smtpad.test.ts +198 -0
  64. package/tests/elements/pcb-trace.test.ts +116 -0
  65. package/tests/elements/pcb-via.test.ts +75 -0
  66. package/.claude/settings.local.json +0 -7
@@ -1,4 +1,19 @@
1
- import type { AnyCircuitElement, PcbPlatedHole } from "circuit-json"
1
+ import type {
2
+ AnyCircuitElement,
3
+ PcbPlatedHole,
4
+ PCBVia,
5
+ PCBHole,
6
+ PcbSmtPad,
7
+ PCBTrace,
8
+ PcbBoard,
9
+ PcbSilkscreenText,
10
+ PcbSilkscreenRect,
11
+ PcbSilkscreenCircle,
12
+ PcbSilkscreenLine,
13
+ PcbSilkscreenPath,
14
+ PcbCutout,
15
+ PcbCopperPour,
16
+ } from "circuit-json"
2
17
  import { identity, compose, translate, scale } from "transformation-matrix"
3
18
  import type { Matrix } from "transformation-matrix"
4
19
  import {
@@ -9,6 +24,20 @@ import {
9
24
  DEFAULT_PCB_COLOR_MAP,
10
25
  } from "./types"
11
26
  import { drawPcbPlatedHole } from "./elements/pcb-plated-hole"
27
+ import { drawPcbVia } from "./elements/pcb-via"
28
+ import { drawPcbHole } from "./elements/pcb-hole"
29
+ import { drawPcbSmtPad } from "./elements/pcb-smtpad"
30
+ import { drawPcbTrace } from "./elements/pcb-trace"
31
+ import { drawPcbBoard } from "./elements/pcb-board"
32
+ import {
33
+ drawPcbSilkscreenText,
34
+ drawPcbSilkscreenRect,
35
+ drawPcbSilkscreenCircle,
36
+ drawPcbSilkscreenLine,
37
+ drawPcbSilkscreenPath,
38
+ } from "./elements/pcb-silkscreen"
39
+ import { drawPcbCutout } from "./elements/pcb-cutout"
40
+ import { drawPcbCopperPour } from "./elements/pcb-copper-pour"
12
41
 
13
42
  export interface DrawElementsOptions {
14
43
  layers?: string[]
@@ -115,5 +144,113 @@ export class CircuitToCanvasDrawer {
115
144
  colorMap: this.colorMap,
116
145
  })
117
146
  }
147
+
148
+ if (element.type === "pcb_via") {
149
+ drawPcbVia({
150
+ ctx: this.ctx,
151
+ via: element as PCBVia,
152
+ transform: this.realToCanvasMat,
153
+ colorMap: this.colorMap,
154
+ })
155
+ }
156
+
157
+ if (element.type === "pcb_hole") {
158
+ drawPcbHole({
159
+ ctx: this.ctx,
160
+ hole: element as PCBHole,
161
+ transform: this.realToCanvasMat,
162
+ colorMap: this.colorMap,
163
+ })
164
+ }
165
+
166
+ if (element.type === "pcb_smtpad") {
167
+ drawPcbSmtPad({
168
+ ctx: this.ctx,
169
+ pad: element as PcbSmtPad,
170
+ transform: this.realToCanvasMat,
171
+ colorMap: this.colorMap,
172
+ })
173
+ }
174
+
175
+ if (element.type === "pcb_trace") {
176
+ drawPcbTrace({
177
+ ctx: this.ctx,
178
+ trace: element as PCBTrace,
179
+ transform: this.realToCanvasMat,
180
+ colorMap: this.colorMap,
181
+ })
182
+ }
183
+
184
+ if (element.type === "pcb_board") {
185
+ drawPcbBoard({
186
+ ctx: this.ctx,
187
+ board: element as PcbBoard,
188
+ transform: this.realToCanvasMat,
189
+ colorMap: this.colorMap,
190
+ })
191
+ }
192
+
193
+ if (element.type === "pcb_silkscreen_text") {
194
+ drawPcbSilkscreenText({
195
+ ctx: this.ctx,
196
+ text: element as PcbSilkscreenText,
197
+ transform: this.realToCanvasMat,
198
+ colorMap: this.colorMap,
199
+ })
200
+ }
201
+
202
+ if (element.type === "pcb_silkscreen_rect") {
203
+ drawPcbSilkscreenRect({
204
+ ctx: this.ctx,
205
+ rect: element as PcbSilkscreenRect,
206
+ transform: this.realToCanvasMat,
207
+ colorMap: this.colorMap,
208
+ })
209
+ }
210
+
211
+ if (element.type === "pcb_silkscreen_circle") {
212
+ drawPcbSilkscreenCircle({
213
+ ctx: this.ctx,
214
+ circle: element as PcbSilkscreenCircle,
215
+ transform: this.realToCanvasMat,
216
+ colorMap: this.colorMap,
217
+ })
218
+ }
219
+
220
+ if (element.type === "pcb_silkscreen_line") {
221
+ drawPcbSilkscreenLine({
222
+ ctx: this.ctx,
223
+ line: element as PcbSilkscreenLine,
224
+ transform: this.realToCanvasMat,
225
+ colorMap: this.colorMap,
226
+ })
227
+ }
228
+
229
+ if (element.type === "pcb_silkscreen_path") {
230
+ drawPcbSilkscreenPath({
231
+ ctx: this.ctx,
232
+ path: element as PcbSilkscreenPath,
233
+ transform: this.realToCanvasMat,
234
+ colorMap: this.colorMap,
235
+ })
236
+ }
237
+
238
+ if (element.type === "pcb_cutout") {
239
+ drawPcbCutout({
240
+ ctx: this.ctx,
241
+ cutout: element as PcbCutout,
242
+ transform: this.realToCanvasMat,
243
+ colorMap: this.colorMap,
244
+ })
245
+ }
246
+
247
+ if (element.type === "pcb_copper_pour") {
248
+ drawPcbCopperPour({
249
+ ctx: this.ctx,
250
+ pour: element as PcbCopperPour,
251
+ transform: this.realToCanvasMat,
252
+ colorMap: this.colorMap,
253
+ })
254
+ }
118
255
  }
119
256
  }
@@ -2,3 +2,33 @@ export {
2
2
  drawPcbPlatedHole,
3
3
  type DrawPcbPlatedHoleParams,
4
4
  } from "./pcb-plated-hole"
5
+
6
+ export { drawPcbVia, type DrawPcbViaParams } from "./pcb-via"
7
+
8
+ export { drawPcbHole, type DrawPcbHoleParams } from "./pcb-hole"
9
+
10
+ export { drawPcbSmtPad, type DrawPcbSmtPadParams } from "./pcb-smtpad"
11
+
12
+ export { drawPcbTrace, type DrawPcbTraceParams } from "./pcb-trace"
13
+
14
+ export { drawPcbBoard, type DrawPcbBoardParams } from "./pcb-board"
15
+
16
+ export {
17
+ drawPcbSilkscreenText,
18
+ drawPcbSilkscreenRect,
19
+ drawPcbSilkscreenCircle,
20
+ drawPcbSilkscreenLine,
21
+ drawPcbSilkscreenPath,
22
+ type DrawPcbSilkscreenTextParams,
23
+ type DrawPcbSilkscreenRectParams,
24
+ type DrawPcbSilkscreenCircleParams,
25
+ type DrawPcbSilkscreenLineParams,
26
+ type DrawPcbSilkscreenPathParams,
27
+ } from "./pcb-silkscreen"
28
+
29
+ export { drawPcbCutout, type DrawPcbCutoutParams } from "./pcb-cutout"
30
+
31
+ export {
32
+ drawPcbCopperPour,
33
+ type DrawPcbCopperPourParams,
34
+ } from "./pcb-copper-pour"
@@ -0,0 +1,62 @@
1
+ import type { PcbBoard } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawPath } from "../shapes/path"
5
+ import { drawRect } from "../shapes/rect"
6
+
7
+ export interface DrawPcbBoardParams {
8
+ ctx: CanvasContext
9
+ board: PcbBoard
10
+ transform: Matrix
11
+ colorMap: PcbColorMap
12
+ }
13
+
14
+ export function drawPcbBoard(params: DrawPcbBoardParams): void {
15
+ const { ctx, board, transform, colorMap } = params
16
+ const { width, height, center, outline } = board
17
+
18
+ // If the board has a custom outline, draw it as a path
19
+ if (outline && Array.isArray(outline) && outline.length >= 3) {
20
+ drawPath({
21
+ ctx,
22
+ points: outline.map((p) => ({ x: p.x, y: p.y })),
23
+ stroke: colorMap.boardOutline,
24
+ strokeWidth: 0.1,
25
+ transform,
26
+ closePath: true,
27
+ })
28
+ return
29
+ }
30
+
31
+ // Otherwise draw a rectangle
32
+ if (width !== undefined && height !== undefined && center) {
33
+ // Draw the board outline as a rectangle stroke
34
+ drawRect({
35
+ ctx,
36
+ center,
37
+ width,
38
+ height,
39
+ fill: "transparent",
40
+ transform,
41
+ })
42
+
43
+ // Draw the outline stroke separately using path
44
+ const halfWidth = width / 2
45
+ const halfHeight = height / 2
46
+ const corners = [
47
+ { x: center.x - halfWidth, y: center.y - halfHeight },
48
+ { x: center.x + halfWidth, y: center.y - halfHeight },
49
+ { x: center.x + halfWidth, y: center.y + halfHeight },
50
+ { x: center.x - halfWidth, y: center.y + halfHeight },
51
+ ]
52
+
53
+ drawPath({
54
+ ctx,
55
+ points: corners,
56
+ stroke: colorMap.boardOutline,
57
+ strokeWidth: 0.1,
58
+ transform,
59
+ closePath: true,
60
+ })
61
+ }
62
+ }
@@ -0,0 +1,84 @@
1
+ import type { PcbCopperPour } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import { applyToPoint } from "transformation-matrix"
4
+ import type { PcbColorMap, CanvasContext } from "../types"
5
+ import { drawRect } from "../shapes/rect"
6
+ import { drawPolygon } from "../shapes/polygon"
7
+
8
+ export interface DrawPcbCopperPourParams {
9
+ ctx: CanvasContext
10
+ pour: PcbCopperPour
11
+ transform: Matrix
12
+ colorMap: PcbColorMap
13
+ }
14
+
15
+ function layerToColor(layer: string, colorMap: PcbColorMap): string {
16
+ return (
17
+ colorMap.copper[layer as keyof typeof colorMap.copper] ??
18
+ colorMap.copper.top
19
+ )
20
+ }
21
+
22
+ export function drawPcbCopperPour(params: DrawPcbCopperPourParams): void {
23
+ const { ctx, pour, transform, colorMap } = params
24
+
25
+ const color = layerToColor(pour.layer, colorMap)
26
+
27
+ // Save context to apply opacity
28
+ ctx.save()
29
+
30
+ if (pour.shape === "rect") {
31
+ // Draw the copper pour rectangle with 50% opacity
32
+ const [cx, cy] = applyToPoint(transform, [pour.center.x, pour.center.y])
33
+ const scaledWidth = pour.width * Math.abs(transform.a)
34
+ const scaledHeight = pour.height * Math.abs(transform.a)
35
+
36
+ ctx.translate(cx, cy)
37
+
38
+ if (pour.rotation) {
39
+ ctx.rotate(-pour.rotation * (Math.PI / 180))
40
+ }
41
+
42
+ ctx.beginPath()
43
+ ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight)
44
+ ctx.fillStyle = color
45
+ ;(ctx as any).globalAlpha = 0.5
46
+ ctx.fill()
47
+ ctx.restore()
48
+ return
49
+ }
50
+
51
+ if (pour.shape === "polygon") {
52
+ if (pour.points && pour.points.length >= 3) {
53
+ const transformedPoints = pour.points.map((p: { x: number; y: number }) =>
54
+ applyToPoint(transform, [p.x, p.y]),
55
+ )
56
+
57
+ const firstPoint = transformedPoints[0]
58
+ if (!firstPoint) {
59
+ ctx.restore()
60
+ return
61
+ }
62
+
63
+ ctx.beginPath()
64
+ const [firstX, firstY] = firstPoint
65
+ ctx.moveTo(firstX, firstY)
66
+
67
+ for (let i = 1; i < transformedPoints.length; i++) {
68
+ const point = transformedPoints[i]
69
+ if (!point) continue
70
+ const [x, y] = point
71
+ ctx.lineTo(x, y)
72
+ }
73
+
74
+ ctx.closePath()
75
+ ctx.fillStyle = color
76
+ ;(ctx as any).globalAlpha = 0.5
77
+ ctx.fill()
78
+ }
79
+ ctx.restore()
80
+ return
81
+ }
82
+
83
+ ctx.restore()
84
+ }
@@ -0,0 +1,53 @@
1
+ import type { PcbCutout } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawRect } from "../shapes/rect"
5
+ import { drawCircle } from "../shapes/circle"
6
+ import { drawPolygon } from "../shapes/polygon"
7
+
8
+ export interface DrawPcbCutoutParams {
9
+ ctx: CanvasContext
10
+ cutout: PcbCutout
11
+ transform: Matrix
12
+ colorMap: PcbColorMap
13
+ }
14
+
15
+ export function drawPcbCutout(params: DrawPcbCutoutParams): void {
16
+ const { ctx, cutout, transform, colorMap } = params
17
+
18
+ if (cutout.shape === "rect") {
19
+ drawRect({
20
+ ctx,
21
+ center: cutout.center,
22
+ width: cutout.width,
23
+ height: cutout.height,
24
+ fill: colorMap.drill,
25
+ transform,
26
+ rotation: cutout.rotation ?? 0,
27
+ })
28
+ return
29
+ }
30
+
31
+ if (cutout.shape === "circle") {
32
+ drawCircle({
33
+ ctx,
34
+ center: cutout.center,
35
+ radius: cutout.radius,
36
+ fill: colorMap.drill,
37
+ transform,
38
+ })
39
+ return
40
+ }
41
+
42
+ if (cutout.shape === "polygon") {
43
+ if (cutout.points && cutout.points.length >= 3) {
44
+ drawPolygon({
45
+ ctx,
46
+ points: cutout.points,
47
+ fill: colorMap.drill,
48
+ transform,
49
+ })
50
+ }
51
+ return
52
+ }
53
+ }
@@ -0,0 +1,90 @@
1
+ import type { PCBHole } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawCircle } from "../shapes/circle"
5
+ import { drawRect } from "../shapes/rect"
6
+ import { drawOval } from "../shapes/oval"
7
+ import { drawPill } from "../shapes/pill"
8
+
9
+ export interface DrawPcbHoleParams {
10
+ ctx: CanvasContext
11
+ hole: PCBHole
12
+ transform: Matrix
13
+ colorMap: PcbColorMap
14
+ }
15
+
16
+ export function drawPcbHole(params: DrawPcbHoleParams): void {
17
+ const { ctx, hole, transform, colorMap } = params
18
+
19
+ if (hole.hole_shape === "circle") {
20
+ drawCircle({
21
+ ctx,
22
+ center: { x: hole.x, y: hole.y },
23
+ radius: hole.hole_diameter / 2,
24
+ fill: colorMap.drill,
25
+ transform,
26
+ })
27
+ return
28
+ }
29
+
30
+ if (hole.hole_shape === "square") {
31
+ drawRect({
32
+ ctx,
33
+ center: { x: hole.x, y: hole.y },
34
+ width: hole.hole_diameter,
35
+ height: hole.hole_diameter,
36
+ fill: colorMap.drill,
37
+ transform,
38
+ })
39
+ return
40
+ }
41
+
42
+ if (hole.hole_shape === "oval") {
43
+ drawOval({
44
+ ctx,
45
+ center: { x: hole.x, y: hole.y },
46
+ width: hole.hole_width,
47
+ height: hole.hole_height,
48
+ fill: colorMap.drill,
49
+ transform,
50
+ })
51
+ return
52
+ }
53
+
54
+ if (hole.hole_shape === "rect") {
55
+ drawRect({
56
+ ctx,
57
+ center: { x: hole.x, y: hole.y },
58
+ width: hole.hole_width,
59
+ height: hole.hole_height,
60
+ fill: colorMap.drill,
61
+ transform,
62
+ })
63
+ return
64
+ }
65
+
66
+ if (hole.hole_shape === "pill") {
67
+ drawPill({
68
+ ctx,
69
+ center: { x: hole.x, y: hole.y },
70
+ width: hole.hole_width,
71
+ height: hole.hole_height,
72
+ fill: colorMap.drill,
73
+ transform,
74
+ })
75
+ return
76
+ }
77
+
78
+ if (hole.hole_shape === "rotated_pill") {
79
+ drawPill({
80
+ ctx,
81
+ center: { x: hole.x, y: hole.y },
82
+ width: hole.hole_width,
83
+ height: hole.hole_height,
84
+ fill: colorMap.drill,
85
+ transform,
86
+ rotation: (hole as any).ccw_rotation ?? 0,
87
+ })
88
+ return
89
+ }
90
+ }
@@ -0,0 +1,171 @@
1
+ import type {
2
+ PcbSilkscreenText,
3
+ PcbSilkscreenRect,
4
+ PcbSilkscreenCircle,
5
+ PcbSilkscreenLine,
6
+ PcbSilkscreenPath,
7
+ } from "circuit-json"
8
+ import type { Matrix } from "transformation-matrix"
9
+ import { applyToPoint } from "transformation-matrix"
10
+ import type { PcbColorMap, CanvasContext } from "../types"
11
+ import { drawRect } from "../shapes/rect"
12
+ import { drawCircle } from "../shapes/circle"
13
+ import { drawLine } from "../shapes/line"
14
+ import { drawPath } from "../shapes/path"
15
+
16
+ export interface DrawPcbSilkscreenTextParams {
17
+ ctx: CanvasContext
18
+ text: PcbSilkscreenText
19
+ transform: Matrix
20
+ colorMap: PcbColorMap
21
+ }
22
+
23
+ export interface DrawPcbSilkscreenRectParams {
24
+ ctx: CanvasContext
25
+ rect: PcbSilkscreenRect
26
+ transform: Matrix
27
+ colorMap: PcbColorMap
28
+ }
29
+
30
+ export interface DrawPcbSilkscreenCircleParams {
31
+ ctx: CanvasContext
32
+ circle: PcbSilkscreenCircle
33
+ transform: Matrix
34
+ colorMap: PcbColorMap
35
+ }
36
+
37
+ export interface DrawPcbSilkscreenLineParams {
38
+ ctx: CanvasContext
39
+ line: PcbSilkscreenLine
40
+ transform: Matrix
41
+ colorMap: PcbColorMap
42
+ }
43
+
44
+ export interface DrawPcbSilkscreenPathParams {
45
+ ctx: CanvasContext
46
+ path: PcbSilkscreenPath
47
+ transform: Matrix
48
+ colorMap: PcbColorMap
49
+ }
50
+
51
+ function layerToSilkscreenColor(layer: string, colorMap: PcbColorMap): string {
52
+ return layer === "bottom"
53
+ ? colorMap.silkscreen.bottom
54
+ : colorMap.silkscreen.top
55
+ }
56
+
57
+ function mapAnchorAlignment(
58
+ alignment?: string,
59
+ ): "start" | "end" | "left" | "right" | "center" {
60
+ if (!alignment) return "center"
61
+ if (alignment.includes("left")) return "left"
62
+ if (alignment.includes("right")) return "right"
63
+ return "center"
64
+ }
65
+
66
+ export function drawPcbSilkscreenText(
67
+ params: DrawPcbSilkscreenTextParams,
68
+ ): void {
69
+ const { ctx, text, transform, colorMap } = params
70
+
71
+ const color = layerToSilkscreenColor(text.layer, colorMap)
72
+ const [x, y] = applyToPoint(transform, [
73
+ text.anchor_position.x,
74
+ text.anchor_position.y,
75
+ ])
76
+
77
+ const fontSize = (text.font_size ?? 1) * Math.abs(transform.a)
78
+ const rotation = text.ccw_rotation ?? 0
79
+
80
+ ctx.save()
81
+ ctx.translate(x, y)
82
+
83
+ // Apply rotation (CCW rotation in degrees)
84
+ if (rotation !== 0) {
85
+ ctx.rotate(-rotation * (Math.PI / 180))
86
+ }
87
+
88
+ ctx.font = `${fontSize}px sans-serif`
89
+ ctx.fillStyle = color
90
+ ctx.textAlign = mapAnchorAlignment(text.anchor_alignment)
91
+ ctx.textBaseline = "middle"
92
+ ctx.fillText(text.text, 0, 0)
93
+ ctx.restore()
94
+ }
95
+
96
+ export function drawPcbSilkscreenRect(
97
+ params: DrawPcbSilkscreenRectParams,
98
+ ): void {
99
+ const { ctx, rect, transform, colorMap } = params
100
+
101
+ const color = layerToSilkscreenColor(rect.layer, colorMap)
102
+
103
+ drawRect({
104
+ ctx,
105
+ center: rect.center,
106
+ width: rect.width,
107
+ height: rect.height,
108
+ fill: color,
109
+ transform,
110
+ })
111
+ }
112
+
113
+ export function drawPcbSilkscreenCircle(
114
+ params: DrawPcbSilkscreenCircleParams,
115
+ ): void {
116
+ const { ctx, circle, transform, colorMap } = params
117
+
118
+ const color = layerToSilkscreenColor(circle.layer, colorMap)
119
+
120
+ drawCircle({
121
+ ctx,
122
+ center: circle.center,
123
+ radius: circle.radius,
124
+ fill: color,
125
+ transform,
126
+ })
127
+ }
128
+
129
+ export function drawPcbSilkscreenLine(
130
+ params: DrawPcbSilkscreenLineParams,
131
+ ): void {
132
+ const { ctx, line, transform, colorMap } = params
133
+
134
+ const color = layerToSilkscreenColor(line.layer, colorMap)
135
+
136
+ drawLine({
137
+ ctx,
138
+ start: { x: line.x1, y: line.y1 },
139
+ end: { x: line.x2, y: line.y2 },
140
+ strokeWidth: line.stroke_width ?? 0.1,
141
+ stroke: color,
142
+ transform,
143
+ })
144
+ }
145
+
146
+ export function drawPcbSilkscreenPath(
147
+ params: DrawPcbSilkscreenPathParams,
148
+ ): void {
149
+ const { ctx, path, transform, colorMap } = params
150
+
151
+ const color = layerToSilkscreenColor(path.layer, colorMap)
152
+
153
+ if (!path.route || path.route.length < 2) return
154
+
155
+ // Draw each segment of the path
156
+ for (let i = 0; i < path.route.length - 1; i++) {
157
+ const start = path.route[i]
158
+ const end = path.route[i + 1]
159
+
160
+ if (!start || !end) continue
161
+
162
+ drawLine({
163
+ ctx,
164
+ start: { x: start.x, y: start.y },
165
+ end: { x: end.x, y: end.y },
166
+ strokeWidth: path.stroke_width ?? 0.1,
167
+ stroke: color,
168
+ transform,
169
+ })
170
+ }
171
+ }