circuit-to-canvas 0.0.1

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 (38) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/README.md +30 -0
  3. package/biome.json +93 -0
  4. package/bunfig.toml +6 -0
  5. package/dist/index.d.ts +151 -0
  6. package/dist/index.js +375 -0
  7. package/lib/drawer/CircuitToCanvasDrawer.ts +119 -0
  8. package/lib/drawer/elements/index.ts +4 -0
  9. package/lib/drawer/elements/pcb-plated-hole.ts +168 -0
  10. package/lib/drawer/index.ts +5 -0
  11. package/lib/drawer/shapes/circle.ts +23 -0
  12. package/lib/drawer/shapes/index.ts +4 -0
  13. package/lib/drawer/shapes/oval.ts +34 -0
  14. package/lib/drawer/shapes/pill.ts +60 -0
  15. package/lib/drawer/shapes/rect.ts +69 -0
  16. package/lib/drawer/types.ts +125 -0
  17. package/lib/index.ts +1 -0
  18. package/lib/pcb/index.ts +5 -0
  19. package/package.json +25 -0
  20. package/tests/__snapshots__/svg.snap.svg +3 -0
  21. package/tests/elements/__snapshots__/oval-plated-hole.snap.png +0 -0
  22. package/tests/elements/__snapshots__/pcb-plated-hole.snap.png +0 -0
  23. package/tests/elements/__snapshots__/pill-plated-hole.snap.png +0 -0
  24. package/tests/elements/pcb-plated-hole.test.ts +90 -0
  25. package/tests/fixtures/png-matcher.ts +159 -0
  26. package/tests/fixtures/preload.ts +2 -0
  27. package/tests/shapes/__snapshots__/circle.snap.png +0 -0
  28. package/tests/shapes/__snapshots__/oval.snap.png +0 -0
  29. package/tests/shapes/__snapshots__/pill-vertical.snap.png +0 -0
  30. package/tests/shapes/__snapshots__/pill.snap.png +0 -0
  31. package/tests/shapes/__snapshots__/rect-rounded.snap.png +0 -0
  32. package/tests/shapes/__snapshots__/rect.snap.png +0 -0
  33. package/tests/shapes/circle.test.ts +24 -0
  34. package/tests/shapes/oval.test.ts +25 -0
  35. package/tests/shapes/pill.test.ts +47 -0
  36. package/tests/shapes/rect.test.ts +48 -0
  37. package/tests/svg.test.ts +11 -0
  38. package/tsconfig.json +31 -0
@@ -0,0 +1,119 @@
1
+ import type { AnyCircuitElement, PcbPlatedHole } from "circuit-json"
2
+ import { identity, compose, translate, scale } from "transformation-matrix"
3
+ import type { Matrix } from "transformation-matrix"
4
+ import {
5
+ type CanvasContext,
6
+ type PcbColorMap,
7
+ type DrawerConfig,
8
+ type CameraBounds,
9
+ DEFAULT_PCB_COLOR_MAP,
10
+ } from "./types"
11
+ import { drawPcbPlatedHole } from "./elements/pcb-plated-hole"
12
+
13
+ export interface DrawElementsOptions {
14
+ layers?: string[]
15
+ }
16
+
17
+ interface CanvasLike {
18
+ getContext(contextId: "2d"): CanvasContext | null
19
+ }
20
+
21
+ export class CircuitToCanvasDrawer {
22
+ private ctx: CanvasContext
23
+ private colorMap: PcbColorMap
24
+ public realToCanvasMat: Matrix
25
+
26
+ constructor(canvasOrContext: CanvasLike | CanvasContext) {
27
+ // Check if it's a canvas element (works in both browser and Node.js)
28
+ if (
29
+ "getContext" in canvasOrContext &&
30
+ typeof canvasOrContext.getContext === "function"
31
+ ) {
32
+ const ctx = canvasOrContext.getContext("2d")
33
+ if (!ctx) {
34
+ throw new Error("Failed to get 2D rendering context from canvas")
35
+ }
36
+ this.ctx = ctx
37
+ } else {
38
+ this.ctx = canvasOrContext as CanvasContext
39
+ }
40
+
41
+ this.colorMap = { ...DEFAULT_PCB_COLOR_MAP }
42
+ this.realToCanvasMat = identity()
43
+ }
44
+
45
+ configure(config: DrawerConfig): void {
46
+ if (config.colorOverrides) {
47
+ this.colorMap = {
48
+ ...this.colorMap,
49
+ ...config.colorOverrides,
50
+ copper: {
51
+ ...this.colorMap.copper,
52
+ ...config.colorOverrides.copper,
53
+ },
54
+ silkscreen: {
55
+ ...this.colorMap.silkscreen,
56
+ ...config.colorOverrides.silkscreen,
57
+ },
58
+ soldermask: {
59
+ ...this.colorMap.soldermask,
60
+ ...config.colorOverrides.soldermask,
61
+ },
62
+ soldermaskWithCopperUnderneath: {
63
+ ...this.colorMap.soldermaskWithCopperUnderneath,
64
+ ...config.colorOverrides.soldermaskWithCopperUnderneath,
65
+ },
66
+ soldermaskOverCopper: {
67
+ ...this.colorMap.soldermaskOverCopper,
68
+ ...config.colorOverrides.soldermaskOverCopper,
69
+ },
70
+ }
71
+ }
72
+ }
73
+
74
+ setCameraBounds(bounds: CameraBounds): void {
75
+ const canvas = this.ctx.canvas
76
+ const canvasWidth = canvas.width
77
+ const canvasHeight = canvas.height
78
+
79
+ const realWidth = bounds.maxX - bounds.minX
80
+ const realHeight = bounds.maxY - bounds.minY
81
+
82
+ const scaleX = canvasWidth / realWidth
83
+ const scaleY = canvasHeight / realHeight
84
+ const uniformScale = Math.min(scaleX, scaleY)
85
+
86
+ // Center the view
87
+ const offsetX = (canvasWidth - realWidth * uniformScale) / 2
88
+ const offsetY = (canvasHeight - realHeight * uniformScale) / 2
89
+
90
+ this.realToCanvasMat = compose(
91
+ translate(offsetX, offsetY),
92
+ scale(uniformScale, uniformScale),
93
+ translate(-bounds.minX, -bounds.minY),
94
+ )
95
+ }
96
+
97
+ drawElements(
98
+ elements: AnyCircuitElement[],
99
+ options: DrawElementsOptions = {},
100
+ ): void {
101
+ for (const element of elements) {
102
+ this.drawElement(element, options)
103
+ }
104
+ }
105
+
106
+ private drawElement(
107
+ element: AnyCircuitElement,
108
+ options: DrawElementsOptions,
109
+ ): void {
110
+ if (element.type === "pcb_plated_hole") {
111
+ drawPcbPlatedHole({
112
+ ctx: this.ctx,
113
+ hole: element as PcbPlatedHole,
114
+ transform: this.realToCanvasMat,
115
+ colorMap: this.colorMap,
116
+ })
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,4 @@
1
+ export {
2
+ drawPcbPlatedHole,
3
+ type DrawPcbPlatedHoleParams,
4
+ } from "./pcb-plated-hole"
@@ -0,0 +1,168 @@
1
+ import type { PcbPlatedHole } 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 DrawPcbPlatedHoleParams {
10
+ ctx: CanvasContext
11
+ hole: PcbPlatedHole
12
+ transform: Matrix
13
+ colorMap: PcbColorMap
14
+ }
15
+
16
+ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
17
+ const { ctx, hole, transform, colorMap } = params
18
+
19
+ if (hole.shape === "circle") {
20
+ // Draw outer copper ring
21
+ drawCircle({
22
+ ctx,
23
+ center: { x: hole.x, y: hole.y },
24
+ radius: hole.outer_diameter / 2,
25
+ fill: colorMap.copper.top,
26
+ transform,
27
+ })
28
+
29
+ // Draw inner drill hole
30
+ drawCircle({
31
+ ctx,
32
+ center: { x: hole.x, y: hole.y },
33
+ radius: hole.hole_diameter / 2,
34
+ fill: colorMap.drill,
35
+ transform,
36
+ })
37
+ return
38
+ }
39
+
40
+ if (hole.shape === "oval") {
41
+ // Draw outer copper oval
42
+ drawOval({
43
+ ctx,
44
+ center: { x: hole.x, y: hole.y },
45
+ width: hole.outer_width,
46
+ height: hole.outer_height,
47
+ fill: colorMap.copper.top,
48
+ transform,
49
+ rotation: hole.ccw_rotation,
50
+ })
51
+
52
+ // Draw inner drill hole
53
+ drawOval({
54
+ ctx,
55
+ center: { x: hole.x, y: hole.y },
56
+ width: hole.hole_width,
57
+ height: hole.hole_height,
58
+ fill: colorMap.drill,
59
+ transform,
60
+ rotation: hole.ccw_rotation,
61
+ })
62
+ return
63
+ }
64
+
65
+ if (hole.shape === "pill") {
66
+ // Draw outer copper pill
67
+ drawPill({
68
+ ctx,
69
+ center: { x: hole.x, y: hole.y },
70
+ width: hole.outer_width,
71
+ height: hole.outer_height,
72
+ fill: colorMap.copper.top,
73
+ transform,
74
+ rotation: hole.ccw_rotation,
75
+ })
76
+
77
+ // Draw inner drill hole
78
+ drawPill({
79
+ ctx,
80
+ center: { x: hole.x, y: hole.y },
81
+ width: hole.hole_width,
82
+ height: hole.hole_height,
83
+ fill: colorMap.drill,
84
+ transform,
85
+ rotation: hole.ccw_rotation,
86
+ })
87
+ return
88
+ }
89
+
90
+ if (hole.shape === "circular_hole_with_rect_pad") {
91
+ // Draw rectangular pad
92
+ drawRect({
93
+ ctx,
94
+ center: { x: hole.x, y: hole.y },
95
+ width: hole.rect_pad_width,
96
+ height: hole.rect_pad_height,
97
+ fill: colorMap.copper.top,
98
+ transform,
99
+ borderRadius: (hole as any).rect_border_radius ?? 0,
100
+ })
101
+
102
+ // Draw circular drill hole (with offset)
103
+ const holeX = hole.x + ((hole as any).hole_offset_x ?? 0)
104
+ const holeY = hole.y + ((hole as any).hole_offset_y ?? 0)
105
+ drawCircle({
106
+ ctx,
107
+ center: { x: holeX, y: holeY },
108
+ radius: hole.hole_diameter / 2,
109
+ fill: colorMap.drill,
110
+ transform,
111
+ })
112
+ return
113
+ }
114
+
115
+ if (hole.shape === "pill_hole_with_rect_pad") {
116
+ // Draw rectangular pad
117
+ drawRect({
118
+ ctx,
119
+ center: { x: hole.x, y: hole.y },
120
+ width: hole.rect_pad_width,
121
+ height: hole.rect_pad_height,
122
+ fill: colorMap.copper.top,
123
+ transform,
124
+ borderRadius: (hole as any).rect_border_radius ?? 0,
125
+ })
126
+
127
+ // Draw pill drill hole (with offset)
128
+ const holeX = hole.x + ((hole as any).hole_offset_x ?? 0)
129
+ const holeY = hole.y + ((hole as any).hole_offset_y ?? 0)
130
+ drawPill({
131
+ ctx,
132
+ center: { x: holeX, y: holeY },
133
+ width: hole.hole_width,
134
+ height: hole.hole_height,
135
+ fill: colorMap.drill,
136
+ transform,
137
+ })
138
+ return
139
+ }
140
+
141
+ if (hole.shape === "rotated_pill_hole_with_rect_pad") {
142
+ // Draw rotated rectangular pad
143
+ drawRect({
144
+ ctx,
145
+ center: { x: hole.x, y: hole.y },
146
+ width: hole.rect_pad_width,
147
+ height: hole.rect_pad_height,
148
+ fill: colorMap.copper.top,
149
+ transform,
150
+ borderRadius: (hole as any).rect_border_radius ?? 0,
151
+ rotation: hole.rect_ccw_rotation,
152
+ })
153
+
154
+ // Draw rotated pill drill hole (with offset)
155
+ const holeX = hole.x + ((hole as any).hole_offset_x ?? 0)
156
+ const holeY = hole.y + ((hole as any).hole_offset_y ?? 0)
157
+ drawPill({
158
+ ctx,
159
+ center: { x: holeX, y: holeY },
160
+ width: hole.hole_width,
161
+ height: hole.hole_height,
162
+ fill: colorMap.drill,
163
+ transform,
164
+ rotation: hole.hole_ccw_rotation,
165
+ })
166
+ return
167
+ }
168
+ }
@@ -0,0 +1,5 @@
1
+ export { CircuitToCanvasDrawer } from "./CircuitToCanvasDrawer"
2
+ export type { DrawElementsOptions } from "./CircuitToCanvasDrawer"
3
+ export * from "./types"
4
+ export * from "./shapes"
5
+ export * from "./elements"
@@ -0,0 +1,23 @@
1
+ import type { Matrix } from "transformation-matrix"
2
+ import { applyToPoint } from "transformation-matrix"
3
+ import type { CanvasContext } from "../types"
4
+
5
+ export interface DrawCircleParams {
6
+ ctx: CanvasContext
7
+ center: { x: number; y: number }
8
+ radius: number
9
+ fill: string
10
+ transform: Matrix
11
+ }
12
+
13
+ export function drawCircle(params: DrawCircleParams): void {
14
+ const { ctx, center, radius, fill, transform } = params
15
+
16
+ const [cx, cy] = applyToPoint(transform, [center.x, center.y])
17
+ const scaledRadius = radius * Math.abs(transform.a)
18
+
19
+ ctx.beginPath()
20
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2)
21
+ ctx.fillStyle = fill
22
+ ctx.fill()
23
+ }
@@ -0,0 +1,4 @@
1
+ export { drawCircle, type DrawCircleParams } from "./circle"
2
+ export { drawRect, type DrawRectParams } from "./rect"
3
+ export { drawOval, type DrawOvalParams } from "./oval"
4
+ export { drawPill, type DrawPillParams } from "./pill"
@@ -0,0 +1,34 @@
1
+ import type { Matrix } from "transformation-matrix"
2
+ import { applyToPoint } from "transformation-matrix"
3
+ import type { CanvasContext } from "../types"
4
+
5
+ export interface DrawOvalParams {
6
+ ctx: CanvasContext
7
+ center: { x: number; y: number }
8
+ width: number
9
+ height: number
10
+ fill: string
11
+ transform: Matrix
12
+ rotation?: number
13
+ }
14
+
15
+ export function drawOval(params: DrawOvalParams): void {
16
+ const { ctx, center, width, height, fill, transform, rotation = 0 } = params
17
+
18
+ const [cx, cy] = applyToPoint(transform, [center.x, center.y])
19
+ const scaledWidth = width * Math.abs(transform.a)
20
+ const scaledHeight = height * Math.abs(transform.a)
21
+
22
+ ctx.save()
23
+ ctx.translate(cx, cy)
24
+
25
+ if (rotation !== 0) {
26
+ ctx.rotate(-rotation * (Math.PI / 180))
27
+ }
28
+
29
+ ctx.beginPath()
30
+ ctx.ellipse(0, 0, scaledWidth / 2, scaledHeight / 2, 0, 0, Math.PI * 2)
31
+ ctx.fillStyle = fill
32
+ ctx.fill()
33
+ ctx.restore()
34
+ }
@@ -0,0 +1,60 @@
1
+ import type { Matrix } from "transformation-matrix"
2
+ import { applyToPoint } from "transformation-matrix"
3
+ import type { CanvasContext } from "../types"
4
+
5
+ export interface DrawPillParams {
6
+ ctx: CanvasContext
7
+ center: { x: number; y: number }
8
+ width: number
9
+ height: number
10
+ fill: string
11
+ transform: Matrix
12
+ rotation?: number
13
+ }
14
+
15
+ export function drawPill(params: DrawPillParams): void {
16
+ const { ctx, center, width, height, fill, transform, rotation = 0 } = params
17
+
18
+ const [cx, cy] = applyToPoint(transform, [center.x, center.y])
19
+ const scaledWidth = width * Math.abs(transform.a)
20
+ const scaledHeight = height * Math.abs(transform.a)
21
+
22
+ ctx.save()
23
+ ctx.translate(cx, cy)
24
+
25
+ if (rotation !== 0) {
26
+ ctx.rotate(-rotation * (Math.PI / 180))
27
+ }
28
+
29
+ ctx.beginPath()
30
+
31
+ if (scaledWidth > scaledHeight) {
32
+ // Horizontal pill
33
+ const radius = scaledHeight / 2
34
+ const straightLength = scaledWidth - scaledHeight
35
+
36
+ ctx.moveTo(-straightLength / 2, -radius)
37
+ ctx.lineTo(straightLength / 2, -radius)
38
+ ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2)
39
+ ctx.lineTo(-straightLength / 2, radius)
40
+ ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2)
41
+ } else if (scaledHeight > scaledWidth) {
42
+ // Vertical pill
43
+ const radius = scaledWidth / 2
44
+ const straightLength = scaledHeight - scaledWidth
45
+
46
+ ctx.moveTo(radius, -straightLength / 2)
47
+ ctx.lineTo(radius, straightLength / 2)
48
+ ctx.arc(0, straightLength / 2, radius, 0, Math.PI)
49
+ ctx.lineTo(-radius, -straightLength / 2)
50
+ ctx.arc(0, -straightLength / 2, radius, Math.PI, 0)
51
+ } else {
52
+ // Circle (width === height)
53
+ ctx.arc(0, 0, scaledWidth / 2, 0, Math.PI * 2)
54
+ }
55
+
56
+ ctx.closePath()
57
+ ctx.fillStyle = fill
58
+ ctx.fill()
59
+ ctx.restore()
60
+ }
@@ -0,0 +1,69 @@
1
+ import type { Matrix } from "transformation-matrix"
2
+ import { applyToPoint } from "transformation-matrix"
3
+ import type { CanvasContext } from "../types"
4
+
5
+ export interface DrawRectParams {
6
+ ctx: CanvasContext
7
+ center: { x: number; y: number }
8
+ width: number
9
+ height: number
10
+ fill: string
11
+ transform: Matrix
12
+ borderRadius?: number
13
+ rotation?: number
14
+ }
15
+
16
+ export function drawRect(params: DrawRectParams): void {
17
+ const {
18
+ ctx,
19
+ center,
20
+ width,
21
+ height,
22
+ fill,
23
+ transform,
24
+ borderRadius = 0,
25
+ rotation = 0,
26
+ } = params
27
+
28
+ const [cx, cy] = applyToPoint(transform, [center.x, center.y])
29
+ const scaledWidth = width * Math.abs(transform.a)
30
+ const scaledHeight = height * Math.abs(transform.a)
31
+ const scaledRadius = borderRadius * Math.abs(transform.a)
32
+
33
+ ctx.save()
34
+ ctx.translate(cx, cy)
35
+
36
+ if (rotation !== 0) {
37
+ ctx.rotate(-rotation * (Math.PI / 180))
38
+ }
39
+
40
+ ctx.beginPath()
41
+
42
+ if (scaledRadius > 0) {
43
+ const x = -scaledWidth / 2
44
+ const y = -scaledHeight / 2
45
+ const r = Math.min(scaledRadius, scaledWidth / 2, scaledHeight / 2)
46
+
47
+ ctx.moveTo(x + r, y)
48
+ ctx.lineTo(x + scaledWidth - r, y)
49
+ ctx.arcTo(x + scaledWidth, y, x + scaledWidth, y + r, r)
50
+ ctx.lineTo(x + scaledWidth, y + scaledHeight - r)
51
+ ctx.arcTo(
52
+ x + scaledWidth,
53
+ y + scaledHeight,
54
+ x + scaledWidth - r,
55
+ y + scaledHeight,
56
+ r,
57
+ )
58
+ ctx.lineTo(x + r, y + scaledHeight)
59
+ ctx.arcTo(x, y + scaledHeight, x, y + scaledHeight - r, r)
60
+ ctx.lineTo(x, y + r)
61
+ ctx.arcTo(x, y, x + r, y, r)
62
+ } else {
63
+ ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight)
64
+ }
65
+
66
+ ctx.fillStyle = fill
67
+ ctx.fill()
68
+ ctx.restore()
69
+ }
@@ -0,0 +1,125 @@
1
+ import type { Matrix } from "transformation-matrix"
2
+
3
+ /**
4
+ * Canvas context type that works with both browser and node-canvas.
5
+ * Uses a subset of CanvasRenderingContext2D methods that are common to both.
6
+ */
7
+ export interface CanvasContext {
8
+ beginPath(): void
9
+ closePath(): void
10
+ arc(
11
+ x: number,
12
+ y: number,
13
+ radius: number,
14
+ startAngle: number,
15
+ endAngle: number,
16
+ ): void
17
+ arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void
18
+ ellipse(
19
+ x: number,
20
+ y: number,
21
+ radiusX: number,
22
+ radiusY: number,
23
+ rotation: number,
24
+ startAngle: number,
25
+ endAngle: number,
26
+ ): void
27
+ fill(): void
28
+ rect(x: number, y: number, w: number, h: number): void
29
+ lineTo(x: number, y: number): void
30
+ moveTo(x: number, y: number): void
31
+ save(): void
32
+ restore(): void
33
+ translate(x: number, y: number): void
34
+ rotate(angle: number): void
35
+ fillStyle: string | CanvasGradient | CanvasPattern
36
+ canvas: { width: number; height: number }
37
+ }
38
+
39
+ export type CopperLayerName =
40
+ | "top"
41
+ | "bottom"
42
+ | "inner1"
43
+ | "inner2"
44
+ | "inner3"
45
+ | "inner4"
46
+ | "inner5"
47
+ | "inner6"
48
+
49
+ export type CopperColorMap = Record<CopperLayerName, string> & {
50
+ [layer: string]: string
51
+ }
52
+
53
+ export interface PcbColorMap {
54
+ copper: CopperColorMap
55
+ drill: string
56
+ silkscreen: {
57
+ top: string
58
+ bottom: string
59
+ }
60
+ boardOutline: string
61
+ soldermask: {
62
+ top: string
63
+ bottom: string
64
+ }
65
+ soldermaskWithCopperUnderneath: {
66
+ top: string
67
+ bottom: string
68
+ }
69
+ soldermaskOverCopper: {
70
+ top: string
71
+ bottom: string
72
+ }
73
+ substrate: string
74
+ courtyard: string
75
+ }
76
+
77
+ export const DEFAULT_PCB_COLOR_MAP: PcbColorMap = {
78
+ copper: {
79
+ top: "rgb(200, 52, 52)",
80
+ inner1: "rgb(255, 140, 0)",
81
+ inner2: "rgb(255, 215, 0)",
82
+ inner3: "rgb(50, 205, 50)",
83
+ inner4: "rgb(64, 224, 208)",
84
+ inner5: "rgb(138, 43, 226)",
85
+ inner6: "rgb(255, 105, 180)",
86
+ bottom: "rgb(77, 127, 196)",
87
+ },
88
+ soldermaskWithCopperUnderneath: {
89
+ top: "rgb(18, 82, 50)",
90
+ bottom: "rgb(77, 127, 196)",
91
+ },
92
+ soldermask: {
93
+ top: "rgb(12, 55, 33)",
94
+ bottom: "rgb(12, 55, 33)",
95
+ },
96
+ soldermaskOverCopper: {
97
+ top: "rgb(52, 135, 73)",
98
+ bottom: "rgb(52, 135, 73)",
99
+ },
100
+ substrate: "rgb(201, 162, 110)",
101
+ drill: "#FF26E2",
102
+ silkscreen: {
103
+ top: "#f2eda1",
104
+ bottom: "#5da9e9",
105
+ },
106
+ boardOutline: "rgba(255, 255, 255, 0.5)",
107
+ courtyard: "#FF00FF",
108
+ }
109
+
110
+ export interface DrawerConfig {
111
+ colorOverrides?: Partial<PcbColorMap>
112
+ }
113
+
114
+ export interface CameraBounds {
115
+ minX: number
116
+ maxX: number
117
+ minY: number
118
+ maxY: number
119
+ }
120
+
121
+ export interface DrawContext {
122
+ ctx: CanvasRenderingContext2D
123
+ transform: Matrix
124
+ colorMap: PcbColorMap
125
+ }
package/lib/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./drawer"
@@ -0,0 +1,5 @@
1
+ // PCB-specific exports (re-exported from drawer)
2
+ export {
3
+ drawPcbPlatedHole,
4
+ type DrawPcbPlatedHoleParams,
5
+ } from "../drawer/elements/pcb-plated-hole"
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "circuit-to-canvas",
3
+ "main": "dist/index.js",
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsup-node ./lib/index.ts --format esm --dts",
8
+ "format": "biome format --write .",
9
+ "format:check": "biome format --check ."
10
+ },
11
+ "devDependencies": {
12
+ "@biomejs/biome": "^2.3.8",
13
+ "@types/bun": "latest",
14
+ "bun-match-svg": "^0.0.14",
15
+ "canvas": "^3.2.0",
16
+ "circuit-json": "^0.0.327",
17
+ "tsup": "^8.5.1"
18
+ },
19
+ "peerDependencies": {
20
+ "typescript": "^5"
21
+ },
22
+ "dependencies": {
23
+ "transformation-matrix": "^3.1.0"
24
+ }
25
+ }
@@ -0,0 +1,3 @@
1
+ <svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
3
+ </svg>