circuit-to-canvas 0.0.30 → 0.0.31

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.
@@ -25,8 +25,14 @@ import type {
25
25
  PcbNoteLine,
26
26
  PcbRenderLayer,
27
27
  } from "circuit-json"
28
+ import {
29
+ identity,
30
+ compose,
31
+ translate,
32
+ scale,
33
+ applyToPoint,
34
+ } from "transformation-matrix"
28
35
  import { shouldDrawElement } from "./pcb-render-layer-filter"
29
- import { identity, compose, translate, scale } from "transformation-matrix"
30
36
  import type { Matrix } from "transformation-matrix"
31
37
  import {
32
38
  type CanvasContext,
@@ -41,6 +47,8 @@ import { drawPcbHole } from "./elements/pcb-hole"
41
47
  import { drawPcbSmtPad } from "./elements/pcb-smtpad"
42
48
  import { drawPcbTrace } from "./elements/pcb-trace"
43
49
  import { drawPcbBoard } from "./elements/pcb-board"
50
+ import { drawPath } from "./shapes/path"
51
+ import { drawRect } from "./shapes/rect"
44
52
  import { drawPcbSilkscreenText } from "./elements/pcb-silkscreen-text"
45
53
  import { drawPcbSilkscreenRect } from "./elements/pcb-silkscreen-rect"
46
54
  import { drawPcbSilkscreenCircle } from "./elements/pcb-silkscreen-circle"
@@ -148,8 +156,103 @@ export class CircuitToCanvasDrawer {
148
156
  elements: AnyCircuitElement[],
149
157
  options: DrawElementsOptions = {},
150
158
  ): void {
159
+ // Check if any pad has is_covered_with_solder_mask: true
160
+ const hasSoldermaskPads = elements.some(
161
+ (el) =>
162
+ el.type === "pcb_smtpad" &&
163
+ (el as PcbSmtPad).is_covered_with_solder_mask === true,
164
+ )
165
+
151
166
  for (const element of elements) {
152
- this.drawElement(element, options)
167
+ if (element.type === "pcb_board" && hasSoldermaskPads) {
168
+ // Draw board with soldermask fill when pads have soldermask
169
+ this.drawBoardWithSoldermask(element as PcbBoard)
170
+ } else {
171
+ this.drawElement(element, options)
172
+ }
173
+ }
174
+ }
175
+
176
+ private drawBoardWithSoldermask(board: PcbBoard): void {
177
+ const { width, height, center, outline } = board
178
+ const layer = "top" // Default to top layer for soldermask color
179
+
180
+ // If the board has a custom outline, draw it as a path with soldermask fill
181
+ if (outline && Array.isArray(outline) && outline.length >= 3) {
182
+ const soldermaskColor =
183
+ this.colorMap.soldermask[
184
+ layer as keyof typeof this.colorMap.soldermask
185
+ ] ?? this.colorMap.soldermask.top
186
+
187
+ // Draw filled path
188
+ const canvasPoints = outline.map((p) => {
189
+ const [x, y] = applyToPoint(this.realToCanvasMat, [p.x, p.y])
190
+ return { x, y }
191
+ })
192
+
193
+ this.ctx.beginPath()
194
+ const firstPoint = canvasPoints[0]
195
+ if (firstPoint) {
196
+ this.ctx.moveTo(firstPoint.x, firstPoint.y)
197
+ for (let i = 1; i < canvasPoints.length; i++) {
198
+ const point = canvasPoints[i]
199
+ if (point) {
200
+ this.ctx.lineTo(point.x, point.y)
201
+ }
202
+ }
203
+ this.ctx.closePath()
204
+ }
205
+
206
+ this.ctx.fillStyle = soldermaskColor
207
+ this.ctx.fill()
208
+
209
+ // Draw outline stroke
210
+ drawPath({
211
+ ctx: this.ctx,
212
+ points: outline.map((p) => ({ x: p.x, y: p.y })),
213
+ stroke: this.colorMap.boardOutline,
214
+ strokeWidth: 0.1,
215
+ realToCanvasMat: this.realToCanvasMat,
216
+ closePath: true,
217
+ })
218
+ return
219
+ }
220
+
221
+ // Otherwise draw a rectangle with soldermask fill
222
+ if (width !== undefined && height !== undefined && center) {
223
+ const soldermaskColor =
224
+ this.colorMap.soldermask[
225
+ layer as keyof typeof this.colorMap.soldermask
226
+ ] ?? this.colorMap.soldermask.top
227
+
228
+ // Draw filled rectangle
229
+ drawRect({
230
+ ctx: this.ctx,
231
+ center,
232
+ width,
233
+ height,
234
+ fill: soldermaskColor,
235
+ realToCanvasMat: this.realToCanvasMat,
236
+ })
237
+
238
+ // Draw the outline stroke separately using path
239
+ const halfWidth = width / 2
240
+ const halfHeight = height / 2
241
+ const corners = [
242
+ { x: center.x - halfWidth, y: center.y - halfHeight },
243
+ { x: center.x + halfWidth, y: center.y - halfHeight },
244
+ { x: center.x + halfWidth, y: center.y + halfHeight },
245
+ { x: center.x - halfWidth, y: center.y + halfHeight },
246
+ ]
247
+
248
+ drawPath({
249
+ ctx: this.ctx,
250
+ points: corners,
251
+ stroke: this.colorMap.boardOutline,
252
+ strokeWidth: 0.1,
253
+ realToCanvasMat: this.realToCanvasMat,
254
+ closePath: true,
255
+ })
153
256
  }
154
257
  }
155
258
 
@@ -8,6 +8,11 @@ export { drawPcbVia, type DrawPcbViaParams } from "./pcb-via"
8
8
  export { drawPcbHole, type DrawPcbHoleParams } from "./pcb-hole"
9
9
 
10
10
  export { drawPcbSmtPad, type DrawPcbSmtPadParams } from "./pcb-smtpad"
11
+ export {
12
+ drawSoldermaskRingForRect,
13
+ drawSoldermaskRingForCircle,
14
+ drawSoldermaskRingForPill,
15
+ } from "./soldermask-margin"
11
16
 
12
17
  export { drawPcbTrace, type DrawPcbTraceParams } from "./pcb-trace"
13
18
 
@@ -5,6 +5,11 @@ import { drawCircle } from "../shapes/circle"
5
5
  import { drawRect } from "../shapes/rect"
6
6
  import { drawPill } from "../shapes/pill"
7
7
  import { drawPolygon } from "../shapes/polygon"
8
+ import {
9
+ drawSoldermaskRingForRect,
10
+ drawSoldermaskRingForCircle,
11
+ drawSoldermaskRingForPill,
12
+ } from "./soldermask-margin"
8
13
 
9
14
  export interface DrawPcbSmtPadParams {
10
15
  ctx: CanvasContext
@@ -20,12 +25,45 @@ function layerToColor(layer: string, colorMap: PcbColorMap): string {
20
25
  )
21
26
  }
22
27
 
28
+ function getSoldermaskColor(layer: string, colorMap: PcbColorMap): string {
29
+ return (
30
+ colorMap.soldermaskOverCopper[
31
+ layer as keyof typeof colorMap.soldermaskOverCopper
32
+ ] ?? colorMap.soldermaskOverCopper.top
33
+ )
34
+ }
35
+
23
36
  export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
24
37
  const { ctx, pad, realToCanvasMat, colorMap } = params
25
38
 
26
39
  const color = layerToColor(pad.layer, colorMap)
40
+ const hasSoldermask =
41
+ pad.is_covered_with_solder_mask === true &&
42
+ pad.soldermask_margin !== undefined &&
43
+ pad.soldermask_margin !== 0
44
+ const margin = hasSoldermask ? pad.soldermask_margin! : 0
45
+ const soldermaskRingColor = getSoldermaskColor(pad.layer, colorMap)
46
+ const positiveMarginColor = colorMap.substrate
27
47
 
48
+ // Draw the copper pad
28
49
  if (pad.shape === "rect") {
50
+ // For positive margins, draw extended mask area first
51
+ if (hasSoldermask && margin > 0) {
52
+ drawRect({
53
+ ctx,
54
+ center: { x: pad.x, y: pad.y },
55
+ width: pad.width + margin * 2,
56
+ height: pad.height + margin * 2,
57
+ fill: positiveMarginColor,
58
+ realToCanvasMat,
59
+ borderRadius:
60
+ ((pad as { corner_radius?: number }).corner_radius ??
61
+ pad.rect_border_radius ??
62
+ 0) + margin,
63
+ })
64
+ }
65
+
66
+ // Draw the pad on top
29
67
  drawRect({
30
68
  ctx,
31
69
  center: { x: pad.x, y: pad.y },
@@ -38,10 +76,46 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
38
76
  pad.rect_border_radius ??
39
77
  0,
40
78
  })
79
+
80
+ // For negative margins, draw soldermask ring on top of the pad
81
+ if (hasSoldermask && margin < 0) {
82
+ drawSoldermaskRingForRect(
83
+ ctx,
84
+ { x: pad.x, y: pad.y },
85
+ pad.width,
86
+ pad.height,
87
+ margin,
88
+ (pad as { corner_radius?: number }).corner_radius ??
89
+ pad.rect_border_radius ??
90
+ 0,
91
+ 0,
92
+ realToCanvasMat,
93
+ soldermaskRingColor,
94
+ color,
95
+ )
96
+ }
41
97
  return
42
98
  }
43
99
 
44
100
  if (pad.shape === "rotated_rect") {
101
+ // For positive margins, draw extended mask area first
102
+ if (hasSoldermask && margin > 0) {
103
+ drawRect({
104
+ ctx,
105
+ center: { x: pad.x, y: pad.y },
106
+ width: pad.width + margin * 2,
107
+ height: pad.height + margin * 2,
108
+ fill: positiveMarginColor,
109
+ realToCanvasMat,
110
+ borderRadius:
111
+ ((pad as { corner_radius?: number }).corner_radius ??
112
+ pad.rect_border_radius ??
113
+ 0) + margin,
114
+ rotation: pad.ccw_rotation ?? 0,
115
+ })
116
+ }
117
+
118
+ // Draw the pad on top
45
119
  drawRect({
46
120
  ctx,
47
121
  center: { x: pad.x, y: pad.y },
@@ -55,10 +129,40 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
55
129
  0,
56
130
  rotation: pad.ccw_rotation ?? 0,
57
131
  })
132
+
133
+ // For negative margins, draw soldermask ring on top of the pad
134
+ if (hasSoldermask && margin < 0) {
135
+ drawSoldermaskRingForRect(
136
+ ctx,
137
+ { x: pad.x, y: pad.y },
138
+ pad.width,
139
+ pad.height,
140
+ margin,
141
+ (pad as { corner_radius?: number }).corner_radius ??
142
+ pad.rect_border_radius ??
143
+ 0,
144
+ pad.ccw_rotation ?? 0,
145
+ realToCanvasMat,
146
+ soldermaskRingColor,
147
+ color,
148
+ )
149
+ }
58
150
  return
59
151
  }
60
152
 
61
153
  if (pad.shape === "circle") {
154
+ // For positive margins, draw extended mask area first
155
+ if (hasSoldermask && margin > 0) {
156
+ drawCircle({
157
+ ctx,
158
+ center: { x: pad.x, y: pad.y },
159
+ radius: pad.radius + margin,
160
+ fill: positiveMarginColor,
161
+ realToCanvasMat,
162
+ })
163
+ }
164
+
165
+ // Draw the pad on top
62
166
  drawCircle({
63
167
  ctx,
64
168
  center: { x: pad.x, y: pad.y },
@@ -66,10 +170,36 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
66
170
  fill: color,
67
171
  realToCanvasMat,
68
172
  })
173
+
174
+ // For negative margins, draw soldermask ring on top of the pad
175
+ if (hasSoldermask && margin < 0) {
176
+ drawSoldermaskRingForCircle(
177
+ ctx,
178
+ { x: pad.x, y: pad.y },
179
+ pad.radius,
180
+ margin,
181
+ realToCanvasMat,
182
+ soldermaskRingColor,
183
+ color,
184
+ )
185
+ }
69
186
  return
70
187
  }
71
188
 
72
189
  if (pad.shape === "pill") {
190
+ // For positive margins, draw extended mask area first
191
+ if (hasSoldermask && margin > 0) {
192
+ drawPill({
193
+ ctx,
194
+ center: { x: pad.x, y: pad.y },
195
+ width: pad.width + margin * 2,
196
+ height: pad.height + margin * 2,
197
+ fill: positiveMarginColor,
198
+ realToCanvasMat,
199
+ })
200
+ }
201
+
202
+ // Draw the pad on top
73
203
  drawPill({
74
204
  ctx,
75
205
  center: { x: pad.x, y: pad.y },
@@ -78,10 +208,39 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
78
208
  fill: color,
79
209
  realToCanvasMat,
80
210
  })
211
+
212
+ // For negative margins, draw soldermask ring on top of the pad
213
+ if (hasSoldermask && margin < 0) {
214
+ drawSoldermaskRingForPill(
215
+ ctx,
216
+ { x: pad.x, y: pad.y },
217
+ pad.width,
218
+ pad.height,
219
+ margin,
220
+ 0,
221
+ realToCanvasMat,
222
+ soldermaskRingColor,
223
+ color,
224
+ )
225
+ }
81
226
  return
82
227
  }
83
228
 
84
229
  if (pad.shape === "rotated_pill") {
230
+ // For positive margins, draw extended mask area first
231
+ if (hasSoldermask && margin > 0) {
232
+ drawPill({
233
+ ctx,
234
+ center: { x: pad.x, y: pad.y },
235
+ width: pad.width + margin * 2,
236
+ height: pad.height + margin * 2,
237
+ fill: positiveMarginColor,
238
+ realToCanvasMat,
239
+ rotation: pad.ccw_rotation ?? 0,
240
+ })
241
+ }
242
+
243
+ // Draw the pad on top
85
244
  drawPill({
86
245
  ctx,
87
246
  center: { x: pad.x, y: pad.y },
@@ -91,6 +250,21 @@ export function drawPcbSmtPad(params: DrawPcbSmtPadParams): void {
91
250
  realToCanvasMat,
92
251
  rotation: pad.ccw_rotation ?? 0,
93
252
  })
253
+
254
+ // For negative margins, draw soldermask ring on top of the pad
255
+ if (hasSoldermask && margin < 0) {
256
+ drawSoldermaskRingForPill(
257
+ ctx,
258
+ { x: pad.x, y: pad.y },
259
+ pad.width,
260
+ pad.height,
261
+ margin,
262
+ pad.ccw_rotation ?? 0,
263
+ realToCanvasMat,
264
+ soldermaskRingColor,
265
+ color,
266
+ )
267
+ }
94
268
  return
95
269
  }
96
270
 
@@ -0,0 +1,266 @@
1
+ import type { Matrix } from "transformation-matrix"
2
+ import type { CanvasContext } from "../types"
3
+ import { applyToPoint } from "transformation-matrix"
4
+
5
+ /**
6
+ * Draws a soldermask ring for rectangular shapes with negative margin
7
+ * (soldermask appears inside the pad boundary)
8
+ */
9
+ export function drawSoldermaskRingForRect(
10
+ ctx: CanvasContext,
11
+ center: { x: number; y: number },
12
+ width: number,
13
+ height: number,
14
+ margin: number,
15
+ borderRadius: number,
16
+ rotation: number,
17
+ realToCanvasMat: Matrix,
18
+ soldermaskColor: string,
19
+ padColor: string,
20
+ ): void {
21
+ const [cx, cy] = applyToPoint(realToCanvasMat, [center.x, center.y])
22
+ const scaledWidth = width * Math.abs(realToCanvasMat.a)
23
+ const scaledHeight = height * Math.abs(realToCanvasMat.a)
24
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a)
25
+ const scaledRadius = borderRadius * Math.abs(realToCanvasMat.a)
26
+
27
+ ctx.save()
28
+ ctx.translate(cx, cy)
29
+
30
+ if (rotation !== 0) {
31
+ ctx.rotate(-rotation * (Math.PI / 180))
32
+ }
33
+
34
+ // For negative margins, outer is pad boundary, inner is reduced by margin
35
+ // Use source-atop so the ring only appears on the pad
36
+ const prevCompositeOp = ctx.globalCompositeOperation
37
+ if (ctx.globalCompositeOperation !== undefined) {
38
+ ctx.globalCompositeOperation = "source-atop"
39
+ }
40
+
41
+ // Draw outer rectangle filled (at pad boundary)
42
+ const outerWidth = scaledWidth
43
+ const outerHeight = scaledHeight
44
+ const outerRadius = scaledRadius
45
+
46
+ ctx.beginPath()
47
+ if (outerRadius > 0) {
48
+ const x = -outerWidth / 2
49
+ const y = -outerHeight / 2
50
+ const r = Math.min(outerRadius, outerWidth / 2, outerHeight / 2)
51
+
52
+ ctx.moveTo(x + r, y)
53
+ ctx.lineTo(x + outerWidth - r, y)
54
+ ctx.arcTo(x + outerWidth, y, x + outerWidth, y + r, r)
55
+ ctx.lineTo(x + outerWidth, y + outerHeight - r)
56
+ ctx.arcTo(
57
+ x + outerWidth,
58
+ y + outerHeight,
59
+ x + outerWidth - r,
60
+ y + outerHeight,
61
+ r,
62
+ )
63
+ ctx.lineTo(x + r, y + outerHeight)
64
+ ctx.arcTo(x, y + outerHeight, x, y + outerHeight - r, r)
65
+ ctx.lineTo(x, y + r)
66
+ ctx.arcTo(x, y, x + r, y, r)
67
+ } else {
68
+ ctx.rect(-outerWidth / 2, -outerHeight / 2, outerWidth, outerHeight)
69
+ }
70
+
71
+ ctx.fillStyle = soldermaskColor
72
+ ctx.fill()
73
+
74
+ // Reset composite operation and restore pad color in inner area
75
+ if (ctx.globalCompositeOperation !== undefined) {
76
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over"
77
+ }
78
+
79
+ // Restore pad color in inner rectangle (reduced by margin)
80
+ const innerWidth = scaledWidth - scaledMargin * 2
81
+ const innerHeight = scaledHeight - scaledMargin * 2
82
+ const innerRadius = Math.max(0, scaledRadius - scaledMargin)
83
+
84
+ if (innerWidth > 0 && innerHeight > 0) {
85
+ ctx.beginPath()
86
+ if (innerRadius > 0) {
87
+ const x = -innerWidth / 2
88
+ const y = -innerHeight / 2
89
+ const r = Math.min(innerRadius, innerWidth / 2, innerHeight / 2)
90
+
91
+ ctx.moveTo(x + r, y)
92
+ ctx.lineTo(x + innerWidth - r, y)
93
+ ctx.arcTo(x + innerWidth, y, x + innerWidth, y + r, r)
94
+ ctx.lineTo(x + innerWidth, y + innerHeight - r)
95
+ ctx.arcTo(
96
+ x + innerWidth,
97
+ y + innerHeight,
98
+ x + innerWidth - r,
99
+ y + innerHeight,
100
+ r,
101
+ )
102
+ ctx.lineTo(x + r, y + innerHeight)
103
+ ctx.arcTo(x, y + innerHeight, x, y + innerHeight - r, r)
104
+ ctx.lineTo(x, y + r)
105
+ ctx.arcTo(x, y, x + r, y, r)
106
+ } else {
107
+ ctx.rect(-innerWidth / 2, -innerHeight / 2, innerWidth, innerHeight)
108
+ }
109
+
110
+ ctx.fillStyle = padColor
111
+ ctx.fill()
112
+ }
113
+
114
+ ctx.restore()
115
+ }
116
+
117
+ /**
118
+ * Draws a soldermask ring for circular shapes with negative margin
119
+ * (soldermask appears inside the pad boundary)
120
+ */
121
+ export function drawSoldermaskRingForCircle(
122
+ ctx: CanvasContext,
123
+ center: { x: number; y: number },
124
+ radius: number,
125
+ margin: number,
126
+ realToCanvasMat: Matrix,
127
+ soldermaskColor: string,
128
+ padColor: string,
129
+ ): void {
130
+ const [cx, cy] = applyToPoint(realToCanvasMat, [center.x, center.y])
131
+ const scaledRadius = radius * Math.abs(realToCanvasMat.a)
132
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a)
133
+
134
+ ctx.save()
135
+
136
+ // For negative margins, outer is pad boundary, inner is reduced by margin
137
+ // Use source-atop so the ring only appears on the pad
138
+ const prevCompositeOp = ctx.globalCompositeOperation
139
+ if (ctx.globalCompositeOperation !== undefined) {
140
+ ctx.globalCompositeOperation = "source-atop"
141
+ }
142
+
143
+ // Draw outer circle filled (at pad boundary)
144
+ ctx.beginPath()
145
+ ctx.arc(cx, cy, scaledRadius, 0, Math.PI * 2)
146
+ ctx.fillStyle = soldermaskColor
147
+ ctx.fill()
148
+
149
+ // Reset composite operation and restore pad color in inner area
150
+ if (ctx.globalCompositeOperation !== undefined) {
151
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over"
152
+ }
153
+
154
+ // Restore pad color in inner circle (reduced by margin)
155
+ const innerRadius = Math.max(0, scaledRadius - scaledMargin)
156
+ if (innerRadius > 0) {
157
+ ctx.beginPath()
158
+ ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2)
159
+ ctx.fillStyle = padColor
160
+ ctx.fill()
161
+ }
162
+
163
+ ctx.restore()
164
+ }
165
+
166
+ /**
167
+ * Draws a soldermask ring for pill shapes with negative margin
168
+ * (soldermask appears inside the pad boundary)
169
+ */
170
+ export function drawSoldermaskRingForPill(
171
+ ctx: CanvasContext,
172
+ center: { x: number; y: number },
173
+ width: number,
174
+ height: number,
175
+ margin: number,
176
+ rotation: number,
177
+ realToCanvasMat: Matrix,
178
+ soldermaskColor: string,
179
+ padColor: string,
180
+ ): void {
181
+ const [cx, cy] = applyToPoint(realToCanvasMat, [center.x, center.y])
182
+ const scaledWidth = width * Math.abs(realToCanvasMat.a)
183
+ const scaledHeight = height * Math.abs(realToCanvasMat.a)
184
+ const scaledMargin = Math.abs(margin) * Math.abs(realToCanvasMat.a)
185
+
186
+ ctx.save()
187
+ ctx.translate(cx, cy)
188
+
189
+ if (rotation !== 0) {
190
+ ctx.rotate(-rotation * (Math.PI / 180))
191
+ }
192
+
193
+ // For negative margins, outer is pad boundary, inner is reduced by margin
194
+ // Use source-atop so the ring only appears on the pad
195
+ const prevCompositeOp = ctx.globalCompositeOperation
196
+ if (ctx.globalCompositeOperation !== undefined) {
197
+ ctx.globalCompositeOperation = "source-atop"
198
+ }
199
+
200
+ // Draw outer pill filled (at pad boundary)
201
+ const outerWidth = scaledWidth
202
+ const outerHeight = scaledHeight
203
+
204
+ ctx.beginPath()
205
+
206
+ if (outerWidth > outerHeight) {
207
+ const radius = outerHeight / 2
208
+ const straightLength = outerWidth - outerHeight
209
+ ctx.moveTo(-straightLength / 2, -radius)
210
+ ctx.lineTo(straightLength / 2, -radius)
211
+ ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2)
212
+ ctx.lineTo(-straightLength / 2, radius)
213
+ ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2)
214
+ } else if (outerHeight > outerWidth) {
215
+ const radius = outerWidth / 2
216
+ const straightLength = outerHeight - outerWidth
217
+ ctx.moveTo(radius, -straightLength / 2)
218
+ ctx.lineTo(radius, straightLength / 2)
219
+ ctx.arc(0, straightLength / 2, radius, 0, Math.PI)
220
+ ctx.lineTo(-radius, -straightLength / 2)
221
+ ctx.arc(0, -straightLength / 2, radius, Math.PI, 0)
222
+ } else {
223
+ ctx.arc(0, 0, outerWidth / 2, 0, Math.PI * 2)
224
+ }
225
+
226
+ ctx.fillStyle = soldermaskColor
227
+ ctx.fill()
228
+
229
+ // Reset composite operation and restore pad color in inner area
230
+ if (ctx.globalCompositeOperation !== undefined) {
231
+ ctx.globalCompositeOperation = prevCompositeOp || "source-over"
232
+ }
233
+
234
+ // Restore pad color in inner pill (reduced by margin)
235
+ const innerWidth = scaledWidth - scaledMargin * 2
236
+ const innerHeight = scaledHeight - scaledMargin * 2
237
+
238
+ if (innerWidth > 0 && innerHeight > 0) {
239
+ ctx.beginPath()
240
+
241
+ if (innerWidth > innerHeight) {
242
+ const radius = innerHeight / 2
243
+ const straightLength = innerWidth - innerHeight
244
+ ctx.moveTo(-straightLength / 2, -radius)
245
+ ctx.lineTo(straightLength / 2, -radius)
246
+ ctx.arc(straightLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2)
247
+ ctx.lineTo(-straightLength / 2, radius)
248
+ ctx.arc(-straightLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2)
249
+ } else if (innerHeight > innerWidth) {
250
+ const radius = innerWidth / 2
251
+ const straightLength = innerHeight - innerWidth
252
+ ctx.moveTo(radius, -straightLength / 2)
253
+ ctx.lineTo(radius, straightLength / 2)
254
+ ctx.arc(0, straightLength / 2, radius, 0, Math.PI)
255
+ ctx.lineTo(-radius, -straightLength / 2)
256
+ ctx.arc(0, -straightLength / 2, radius, Math.PI, 0)
257
+ } else {
258
+ ctx.arc(0, 0, innerWidth / 2, 0, Math.PI * 2)
259
+ }
260
+
261
+ ctx.fillStyle = padColor
262
+ ctx.fill()
263
+ }
264
+
265
+ ctx.restore()
266
+ }
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.30",
4
+ "version": "0.0.31",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",