circuit-to-canvas 0.0.31 → 0.0.33

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.
@@ -0,0 +1,42 @@
1
+ import type { PcbFabricationNoteDimension } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawDimensionLine } from "../shapes/dimension-line"
5
+
6
+ export interface DrawPcbFabricationNoteDimensionParams {
7
+ ctx: CanvasContext
8
+ pcbFabricationNoteDimension: PcbFabricationNoteDimension
9
+ realToCanvasMat: Matrix
10
+ colorMap: PcbColorMap
11
+ }
12
+
13
+ const DEFAULT_FABRICATION_NOTE_COLOR = "rgba(255,255,255,0.5)"
14
+
15
+ export function drawPcbFabricationNoteDimension(
16
+ params: DrawPcbFabricationNoteDimensionParams,
17
+ ): void {
18
+ const { ctx, pcbFabricationNoteDimension, realToCanvasMat } = params
19
+
20
+ const color =
21
+ pcbFabricationNoteDimension.color ?? DEFAULT_FABRICATION_NOTE_COLOR
22
+
23
+ drawDimensionLine({
24
+ ctx,
25
+ from: pcbFabricationNoteDimension.from,
26
+ to: pcbFabricationNoteDimension.to,
27
+ realToCanvasMat,
28
+ color,
29
+ fontSize: pcbFabricationNoteDimension.font_size ?? 1,
30
+ arrowSize: pcbFabricationNoteDimension.arrow_size ?? 1,
31
+ text: pcbFabricationNoteDimension.text,
32
+ textRotation: pcbFabricationNoteDimension.text_ccw_rotation,
33
+ offset:
34
+ pcbFabricationNoteDimension.offset_distance &&
35
+ pcbFabricationNoteDimension.offset_direction
36
+ ? {
37
+ distance: pcbFabricationNoteDimension.offset_distance,
38
+ direction: pcbFabricationNoteDimension.offset_direction,
39
+ }
40
+ : undefined,
41
+ })
42
+ }
@@ -43,8 +43,8 @@ export function drawPcbHole(params: DrawPcbHoleParams): void {
43
43
  drawOval({
44
44
  ctx,
45
45
  center: { x: hole.x, y: hole.y },
46
- width: hole.hole_width,
47
- height: hole.hole_height,
46
+ radius_x: hole.hole_width / 2,
47
+ radius_y: hole.hole_height / 2,
48
48
  fill: colorMap.drill,
49
49
  realToCanvasMat,
50
50
  })
@@ -1,10 +1,7 @@
1
1
  import type { PcbNoteDimension } from "circuit-json"
2
2
  import type { Matrix } from "transformation-matrix"
3
- import { applyToPoint } from "transformation-matrix"
4
3
  import type { PcbColorMap, CanvasContext } from "../types"
5
- import { drawLine } from "../shapes/line"
6
- import { drawText } from "../shapes/text"
7
- import { drawArrow } from "../shapes/arrow"
4
+ import { drawDimensionLine } from "../shapes/dimension-line"
8
5
 
9
6
  export interface DrawPcbNoteDimensionParams {
10
7
  ctx: CanvasContext
@@ -19,183 +16,23 @@ export function drawPcbNoteDimension(params: DrawPcbNoteDimensionParams): void {
19
16
  const { ctx, pcbNoteDimension, realToCanvasMat } = params
20
17
 
21
18
  const color = pcbNoteDimension.color ?? DEFAULT_NOTE_COLOR
22
- const arrowSize = pcbNoteDimension.arrow_size
23
19
 
24
- // Store real (model) endpoints for extension lines
25
- const realFromX = pcbNoteDimension.from.x
26
- const realFromY = pcbNoteDimension.from.y
27
- const realToX = pcbNoteDimension.to.x
28
- const realToY = pcbNoteDimension.to.y
29
-
30
- // Calculate the dimension line endpoints (real/model coords)
31
- let fromX = realFromX
32
- let fromY = realFromY
33
- let toX = realToX
34
- let toY = realToY
35
-
36
- // Track if we have an offset (for drawing extension lines)
37
- let hasOffset = false
38
- let offsetX = 0
39
- let offsetY = 0
40
-
41
- // Apply offset if provided
42
- if (pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction) {
43
- const dirX = pcbNoteDimension.offset_direction.x
44
- const dirY = pcbNoteDimension.offset_direction.y
45
- const length = Math.hypot(dirX, dirY)
46
- if (length > 0) {
47
- const normX = dirX / length
48
- const normY = dirY / length
49
- hasOffset = true
50
- offsetX = pcbNoteDimension.offset_distance * normX
51
- offsetY = pcbNoteDimension.offset_distance * normY
52
- fromX += offsetX
53
- fromY += offsetY
54
- toX += offsetX
55
- toY += offsetY
56
- }
57
- }
58
-
59
- // Calculate stroke width to match text stroke width
60
- // Text uses fontSize * STROKE_WIDTH_RATIO (0.13) with minimum 0.35
61
- const STROKE_WIDTH_RATIO = 0.13
62
-
63
- const strokeWidth = Math.max(
64
- pcbNoteDimension.font_size * STROKE_WIDTH_RATIO,
65
- 0.35,
66
- )
67
-
68
- // Draw extension lines if offset is provided
69
- if (hasOffset) {
70
- // Extension line from original 'from' point to offset 'from' point
71
- drawLine({
72
- ctx,
73
- start: { x: realFromX, y: realFromY },
74
- end: { x: fromX, y: fromY },
75
- strokeWidth,
76
- stroke: color,
77
- realToCanvasMat: realToCanvasMat,
78
- })
79
-
80
- // Extension line from original 'to' point to offset 'to' point
81
- drawLine({
82
- ctx,
83
- start: { x: realToX, y: realToY },
84
- end: { x: toX, y: toY },
85
- strokeWidth,
86
- stroke: color,
87
- realToCanvasMat: realToCanvasMat,
88
- })
89
- }
90
-
91
- // Draw the dimension line
92
- drawLine({
93
- ctx,
94
- start: { x: fromX, y: fromY },
95
- end: { x: toX, y: toY },
96
- strokeWidth,
97
- stroke: color,
98
- realToCanvasMat: realToCanvasMat,
99
- })
100
-
101
- // Draw arrows at both ends
102
- const [canvasFromX, canvasFromY] = applyToPoint(realToCanvasMat, [
103
- fromX,
104
- fromY,
105
- ])
106
- const [canvasToX, canvasToY] = applyToPoint(realToCanvasMat, [toX, toY])
107
- // Calculate angle for arrows in canvas coordinates
108
- const canvasDx = canvasToX - canvasFromX
109
- const canvasDy = canvasToY - canvasFromY
110
- const lineAngle = Math.atan2(canvasDy, canvasDx)
111
- const scale = Math.abs(realToCanvasMat.a)
112
- const scaledArrowSize = arrowSize * scale
113
- const scaledStrokeWidth = strokeWidth * scale
114
-
115
- // Arrow at 'from' point (pointing outward, away from the line center)
116
- // This means pointing in the direction opposite to 'to'
117
- drawArrow({
20
+ drawDimensionLine({
118
21
  ctx,
119
- x: canvasFromX,
120
- y: canvasFromY,
121
- angle: lineAngle + Math.PI,
122
- arrowSize: scaledArrowSize,
22
+ from: pcbNoteDimension.from,
23
+ to: pcbNoteDimension.to,
24
+ realToCanvasMat,
123
25
  color,
124
- strokeWidth: scaledStrokeWidth,
26
+ fontSize: pcbNoteDimension.font_size,
27
+ arrowSize: pcbNoteDimension.arrow_size,
28
+ text: pcbNoteDimension.text,
29
+ textRotation: pcbNoteDimension.text_ccw_rotation,
30
+ offset:
31
+ pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction
32
+ ? {
33
+ distance: pcbNoteDimension.offset_distance,
34
+ direction: pcbNoteDimension.offset_direction,
35
+ }
36
+ : undefined,
125
37
  })
126
-
127
- // Arrow at 'to' point (pointing outward, away from the line center)
128
- // This means pointing in the direction toward 'to' (away from 'from')
129
- drawArrow({
130
- ctx,
131
- x: canvasToX,
132
- y: canvasToY,
133
- angle: lineAngle,
134
- arrowSize: scaledArrowSize,
135
- color,
136
- strokeWidth: scaledStrokeWidth,
137
- })
138
-
139
- // Draw text if provided
140
- if (pcbNoteDimension.text) {
141
- // Calculate text position (midpoint of the dimension line)
142
- // The line endpoints are already offset if offset was provided
143
- let textX = (fromX + toX) / 2
144
- let textY = (fromY + toY) / 2
145
-
146
- // Offset text perpendicular to the dimension line so it appears above/outside
147
- // Calculate perpendicular vector (rotate line direction by 90 degrees CW)
148
- // For a line from (fromX, fromY) to (toX, toY), perpendicular is (dy, -dx)
149
- // This ensures text appears above horizontal lines and to the right of vertical lines
150
- const perpX = toY - fromY
151
- const perpY = -(toX - fromX)
152
- const perpLength = Math.sqrt(perpX * perpX + perpY * perpY)
153
-
154
- // Normalize and offset by font size (plus a small gap)
155
- if (perpLength > 0) {
156
- const offsetDistance = pcbNoteDimension.font_size * 1.5 // Offset by 1.5x font size
157
- const normalizedPerpX = perpX / perpLength
158
- const normalizedPerpY = perpY / perpLength
159
- textX += normalizedPerpX * offsetDistance
160
- textY += normalizedPerpY * offsetDistance
161
- }
162
-
163
- // Calculate rotation (displayed CCW degrees). If the caller provided
164
- // `text_ccw_rotation` use that directly; otherwise align with the line
165
- // angle and keep the text upright by folding into [-90, 90]. `drawText`
166
- // expects a rotation value that it will negate internally, so we pass
167
- // `-deg` below.
168
- // Compute the displayed CCW degrees. Use the explicit `text_ccw_rotation`
169
- // when provided; otherwise derive from the line angle and fold into
170
- // [-90, 90] so text stays upright. Finally, `drawText` negates the
171
- // provided rotation when applying it to the canvas, so pass the
172
- // negative of the displayed CCW degrees.
173
- const textRotation = -(() => {
174
- const raw =
175
- pcbNoteDimension.text_ccw_rotation ?? (lineAngle * 180) / Math.PI
176
-
177
- if (pcbNoteDimension.text_ccw_rotation !== undefined) return raw
178
-
179
- // Normalize to [-180, 180]
180
- let deg = ((raw + 180) % 360) - 180
181
-
182
- // Fold into [-90, 90]
183
- if (deg > 90) deg -= 180
184
- if (deg < -90) deg += 180
185
-
186
- return deg
187
- })()
188
-
189
- drawText({
190
- ctx,
191
- text: pcbNoteDimension.text,
192
- x: textX,
193
- y: textY,
194
- fontSize: pcbNoteDimension.font_size,
195
- color,
196
- realToCanvasMat: realToCanvasMat,
197
- anchorAlignment: "center",
198
- rotation: textRotation,
199
- })
200
- }
201
38
  }
@@ -43,8 +43,8 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
43
43
  drawOval({
44
44
  ctx,
45
45
  center: { x: hole.x, y: hole.y },
46
- width: hole.outer_width,
47
- height: hole.outer_height,
46
+ radius_x: hole.outer_width / 2,
47
+ radius_y: hole.outer_height / 2,
48
48
  fill: colorMap.copper.top,
49
49
  realToCanvasMat,
50
50
  rotation: hole.ccw_rotation,
@@ -54,8 +54,8 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
54
54
  drawOval({
55
55
  ctx,
56
56
  center: { x: hole.x, y: hole.y },
57
- width: hole.hole_width,
58
- height: hole.hole_height,
57
+ radius_x: hole.hole_width / 2,
58
+ radius_y: hole.hole_height / 2,
59
59
  fill: colorMap.drill,
60
60
  realToCanvasMat,
61
61
  rotation: hole.ccw_rotation,
@@ -201,8 +201,8 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
201
201
  drawOval({
202
202
  ctx,
203
203
  center: { x: holeX, y: holeY },
204
- width: hole.hole_width ?? 0,
205
- height: hole.hole_height ?? 0,
204
+ radius_x: (hole.hole_width ?? 0) / 2,
205
+ radius_y: (hole.hole_height ?? 0) / 2,
206
206
  fill: colorMap.drill,
207
207
  realToCanvasMat,
208
208
  })
@@ -0,0 +1,35 @@
1
+ import type { PcbSilkscreenOval } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawOval } from "../shapes/oval"
5
+
6
+ export interface DrawPcbSilkscreenOvalParams {
7
+ ctx: CanvasContext
8
+ oval: PcbSilkscreenOval
9
+ realToCanvasMat: Matrix
10
+ colorMap: PcbColorMap
11
+ }
12
+
13
+ function layerToSilkscreenColor(layer: string, colorMap: PcbColorMap): string {
14
+ return layer === "bottom"
15
+ ? colorMap.silkscreen.bottom
16
+ : colorMap.silkscreen.top
17
+ }
18
+
19
+ export function drawPcbSilkscreenOval(
20
+ params: DrawPcbSilkscreenOvalParams,
21
+ ): void {
22
+ const { ctx, oval, realToCanvasMat, colorMap } = params
23
+ const color = layerToSilkscreenColor(oval.layer, colorMap)
24
+
25
+ drawOval({
26
+ ctx,
27
+ center: oval.center,
28
+ radius_x: oval.radius_x,
29
+ radius_y: oval.radius_y,
30
+ stroke: color,
31
+ strokeWidth: 0.1,
32
+ realToCanvasMat,
33
+ rotation: oval.ccw_rotation ?? 0,
34
+ })
35
+ }
@@ -0,0 +1,228 @@
1
+ import type { Matrix } from "transformation-matrix"
2
+ import { applyToPoint } from "transformation-matrix"
3
+ import type { CanvasContext } from "../types"
4
+ import { drawLine } from "./line"
5
+ import { drawText } from "./text"
6
+ import { drawArrow } from "./arrow"
7
+
8
+ export interface DrawDimensionLineParams {
9
+ ctx: CanvasContext
10
+ from: { x: number; y: number }
11
+ to: { x: number; y: number }
12
+ realToCanvasMat: Matrix
13
+ color: string
14
+ fontSize: number
15
+ arrowSize?: number
16
+ strokeWidth?: number
17
+ text?: string
18
+ textRotation?: number // CCW rotation in degrees
19
+ offset?: {
20
+ distance: number
21
+ direction: { x: number; y: number }
22
+ }
23
+ }
24
+
25
+ const TEXT_OFFSET_MULTIPLIER = 1.5
26
+ const CHARACTER_WIDTH_MULTIPLIER = 0.6
27
+ const TEXT_INTERSECTION_PADDING_MULTIPLIER = 0.3
28
+
29
+ function normalize(v: { x: number; y: number }) {
30
+ const len = Math.hypot(v.x, v.y) || 1
31
+ return { x: v.x / len, y: v.y / len }
32
+ }
33
+
34
+ export function drawDimensionLine(params: DrawDimensionLineParams): void {
35
+ const {
36
+ ctx,
37
+ from,
38
+ to,
39
+ realToCanvasMat,
40
+ color,
41
+ fontSize,
42
+ arrowSize = 1,
43
+ strokeWidth: manualStrokeWidth,
44
+ text,
45
+ textRotation,
46
+ offset,
47
+ } = params
48
+
49
+ const direction = normalize({ x: to.x - from.x, y: to.y - from.y })
50
+ const perpendicular = { x: -direction.y, y: direction.x }
51
+
52
+ const hasOffsetDirection =
53
+ offset?.direction &&
54
+ typeof offset.direction.x === "number" &&
55
+ typeof offset.direction.y === "number"
56
+
57
+ const normalizedOffsetDirection = hasOffsetDirection
58
+ ? normalize(offset!.direction)
59
+ : { x: 0, y: 0 }
60
+
61
+ const offsetMagnitude = offset?.distance ?? 0
62
+ const offsetVector = {
63
+ x: normalizedOffsetDirection.x * offsetMagnitude,
64
+ y: normalizedOffsetDirection.y * offsetMagnitude,
65
+ }
66
+
67
+ const fromOffset = { x: from.x + offsetVector.x, y: from.y + offsetVector.y }
68
+ const toOffset = { x: to.x + offsetVector.x, y: to.y + offsetVector.y }
69
+
70
+ const fromBase = {
71
+ x: fromOffset.x + direction.x * arrowSize,
72
+ y: fromOffset.y + direction.y * arrowSize,
73
+ }
74
+ const toBase = {
75
+ x: toOffset.x - direction.x * arrowSize,
76
+ y: toOffset.y - direction.y * arrowSize,
77
+ }
78
+
79
+ const scaleValue = Math.abs(realToCanvasMat.a)
80
+ const strokeWidth = manualStrokeWidth ?? arrowSize / 5
81
+ const lineColor = color || "rgba(255,255,255,0.5)"
82
+
83
+ // Extension lines (ticks)
84
+ const extensionDirection =
85
+ hasOffsetDirection &&
86
+ (Math.abs(normalizedOffsetDirection.x) > Number.EPSILON ||
87
+ Math.abs(normalizedOffsetDirection.y) > Number.EPSILON)
88
+ ? normalizedOffsetDirection
89
+ : perpendicular
90
+
91
+ const extensionLength = offsetMagnitude + 0.5
92
+
93
+ const drawExtension = (anchor: { x: number; y: number }) => {
94
+ const endPoint = {
95
+ x: anchor.x + extensionDirection.x * extensionLength,
96
+ y: anchor.y + extensionDirection.y * extensionLength,
97
+ }
98
+ drawLine({
99
+ ctx,
100
+ start: anchor,
101
+ end: endPoint,
102
+ strokeWidth,
103
+ stroke: lineColor,
104
+ realToCanvasMat,
105
+ })
106
+ }
107
+
108
+ drawExtension(from)
109
+ drawExtension(to)
110
+
111
+ // Main dimension line
112
+ drawLine({
113
+ ctx,
114
+ start: fromBase,
115
+ end: toBase,
116
+ strokeWidth,
117
+ stroke: lineColor,
118
+ realToCanvasMat,
119
+ })
120
+
121
+ // Arrows (Keep V-shaped but matching size)
122
+ const [canvasFromX, canvasFromY] = applyToPoint(realToCanvasMat, [
123
+ fromOffset.x,
124
+ fromOffset.y,
125
+ ])
126
+ const [canvasToX, canvasToY] = applyToPoint(realToCanvasMat, [
127
+ toOffset.x,
128
+ toOffset.y,
129
+ ])
130
+ const [canvasToDirX, canvasToDirY] = applyToPoint(realToCanvasMat, [
131
+ toOffset.x + direction.x,
132
+ toOffset.y + direction.y,
133
+ ])
134
+
135
+ const canvasLineAngle = Math.atan2(
136
+ canvasToDirY - canvasToY,
137
+ canvasToDirX - canvasToX,
138
+ )
139
+
140
+ drawArrow({
141
+ ctx,
142
+ x: canvasFromX,
143
+ y: canvasFromY,
144
+ angle: canvasLineAngle + Math.PI,
145
+ arrowSize: arrowSize * scaleValue,
146
+ color: lineColor,
147
+ strokeWidth: strokeWidth * scaleValue,
148
+ })
149
+
150
+ drawArrow({
151
+ ctx,
152
+ x: canvasToX,
153
+ y: canvasToY,
154
+ angle: canvasLineAngle,
155
+ arrowSize: arrowSize * scaleValue,
156
+ color: lineColor,
157
+ strokeWidth: strokeWidth * scaleValue,
158
+ })
159
+
160
+ // Text
161
+ if (text) {
162
+ const midPoint = {
163
+ x: (from.x + to.x) / 2 + offsetVector.x,
164
+ y: (from.y + to.y) / 2 + offsetVector.y,
165
+ }
166
+
167
+ const [screenFromX, screenFromY] = applyToPoint(realToCanvasMat, [
168
+ fromOffset.x,
169
+ fromOffset.y,
170
+ ])
171
+ const [screenToX, screenToY] = applyToPoint(realToCanvasMat, [
172
+ toOffset.x,
173
+ toOffset.y,
174
+ ])
175
+
176
+ const screenDirection = normalize({
177
+ x: screenToX - screenFromX,
178
+ y: screenToY - screenFromY,
179
+ })
180
+
181
+ let textAngle =
182
+ (Math.atan2(screenDirection.y, screenDirection.x) * 180) / Math.PI
183
+ if (textAngle > 90 || textAngle < -90) {
184
+ textAngle += 180
185
+ }
186
+
187
+ const finalTextAngle =
188
+ typeof textRotation === "number" && Number.isFinite(textRotation)
189
+ ? textAngle - textRotation
190
+ : textAngle
191
+
192
+ let additionalOffset = 0
193
+ if (
194
+ text &&
195
+ typeof textRotation === "number" &&
196
+ Number.isFinite(textRotation)
197
+ ) {
198
+ const textWidth = text.length * fontSize * CHARACTER_WIDTH_MULTIPLIER
199
+ const textHeight = fontSize
200
+ const rotationRad = (textRotation * Math.PI) / 180
201
+ const sinRot = Math.abs(Math.sin(rotationRad))
202
+ const cosRot = Math.abs(Math.cos(rotationRad))
203
+ const halfWidth = textWidth / 2
204
+ const halfHeight = textHeight / 2
205
+ const maxExtension = halfWidth * sinRot + halfHeight * cosRot
206
+ additionalOffset =
207
+ maxExtension + fontSize * TEXT_INTERSECTION_PADDING_MULTIPLIER
208
+ }
209
+
210
+ const textOffset = arrowSize * TEXT_OFFSET_MULTIPLIER + additionalOffset
211
+ const textPoint = {
212
+ x: midPoint.x + perpendicular.x * textOffset,
213
+ y: midPoint.y + perpendicular.y * textOffset,
214
+ }
215
+
216
+ drawText({
217
+ ctx,
218
+ text,
219
+ x: textPoint.x,
220
+ y: textPoint.y,
221
+ fontSize,
222
+ color: lineColor,
223
+ realToCanvasMat,
224
+ anchorAlignment: "center",
225
+ rotation: -finalTextAngle, // drawText expects CCW rotation in degrees
226
+ })
227
+ }
228
+ }
@@ -6,6 +6,10 @@ export { drawPolygon, type DrawPolygonParams } from "./polygon"
6
6
  export { drawLine, type DrawLineParams } from "./line"
7
7
  export { drawPath, type DrawPathParams } from "./path"
8
8
  export { drawArrow, type DrawArrowParams } from "./arrow"
9
+ export {
10
+ drawDimensionLine,
11
+ type DrawDimensionLineParams,
12
+ } from "./dimension-line"
9
13
  export {
10
14
  drawText,
11
15
  type DrawTextParams,
@@ -5,9 +5,11 @@ import type { CanvasContext } from "../types"
5
5
  export interface DrawOvalParams {
6
6
  ctx: CanvasContext
7
7
  center: { x: number; y: number }
8
- width: number
9
- height: number
10
- fill: string
8
+ radius_x: number
9
+ radius_y: number
10
+ fill?: string
11
+ stroke?: string
12
+ strokeWidth?: number
11
13
  realToCanvasMat: Matrix
12
14
  rotation?: number
13
15
  }
@@ -16,16 +18,19 @@ export function drawOval(params: DrawOvalParams): void {
16
18
  const {
17
19
  ctx,
18
20
  center,
19
- width,
20
- height,
21
+ radius_x,
22
+ radius_y,
21
23
  fill,
24
+ stroke,
25
+ strokeWidth = 0.1,
22
26
  realToCanvasMat,
23
27
  rotation = 0,
24
28
  } = params
25
29
 
26
30
  const [cx, cy] = applyToPoint(realToCanvasMat, [center.x, center.y])
27
- const scaledWidth = width * Math.abs(realToCanvasMat.a)
28
- const scaledHeight = height * Math.abs(realToCanvasMat.a)
31
+ const scaledRadiusX = radius_x * Math.abs(realToCanvasMat.a)
32
+ const scaledRadiusY = radius_y * Math.abs(realToCanvasMat.a)
33
+ const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a)
29
34
 
30
35
  ctx.save()
31
36
  ctx.translate(cx, cy)
@@ -35,8 +40,18 @@ export function drawOval(params: DrawOvalParams): void {
35
40
  }
36
41
 
37
42
  ctx.beginPath()
38
- ctx.ellipse(0, 0, scaledWidth / 2, scaledHeight / 2, 0, 0, Math.PI * 2)
39
- ctx.fillStyle = fill
40
- ctx.fill()
43
+ ctx.ellipse(0, 0, scaledRadiusX, scaledRadiusY, 0, 0, Math.PI * 2)
44
+
45
+ if (fill) {
46
+ ctx.fillStyle = fill
47
+ ctx.fill()
48
+ }
49
+
50
+ if (stroke) {
51
+ ctx.strokeStyle = stroke
52
+ ctx.lineWidth = scaledStrokeWidth
53
+ ctx.stroke()
54
+ }
55
+
41
56
  ctx.restore()
42
57
  }
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.31",
4
+ "version": "0.0.33",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -17,9 +17,9 @@
17
17
  "@tscircuit/math-utils": "^0.0.29",
18
18
  "@types/bun": "latest",
19
19
  "bun-match-svg": "^0.0.14",
20
- "circuit-json": "^0.0.346",
20
+ "circuit-json": "^0.0.348",
21
21
  "circuit-json-to-connectivity-map": "^0.0.23",
22
- "circuit-to-svg": "^0.0.297",
22
+ "circuit-to-svg": "^0.0.303",
23
23
  "looks-same": "^10.0.1",
24
24
  "schematic-symbols": "^0.0.202",
25
25
  "tsup": "^8.5.1"