circuit-to-canvas 0.0.49 → 0.0.51

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 (52) hide show
  1. package/dist/index.d.ts +13 -5
  2. package/dist/index.js +1450 -1226
  3. package/lib/drawer/CircuitToCanvasDrawer.ts +262 -312
  4. package/lib/drawer/elements/helper-functions/draw-pill.ts +39 -0
  5. package/lib/drawer/elements/helper-functions/draw-polygon.ts +25 -0
  6. package/lib/drawer/elements/helper-functions/draw-rounded-rect.ts +34 -0
  7. package/lib/drawer/elements/helper-functions/index.ts +3 -0
  8. package/lib/drawer/elements/pcb-board.ts +13 -3
  9. package/lib/drawer/elements/pcb-hole.ts +56 -338
  10. package/lib/drawer/elements/pcb-plated-hole.ts +154 -442
  11. package/lib/drawer/elements/pcb-smtpad.ts +5 -271
  12. package/lib/drawer/elements/pcb-soldermask/board.ts +44 -0
  13. package/lib/drawer/elements/pcb-soldermask/cutout.ts +74 -0
  14. package/lib/drawer/elements/pcb-soldermask/hole.ts +288 -0
  15. package/lib/drawer/elements/pcb-soldermask/index.ts +140 -0
  16. package/lib/drawer/elements/pcb-soldermask/plated-hole.ts +365 -0
  17. package/lib/drawer/elements/pcb-soldermask/smt-pad.ts +354 -0
  18. package/lib/drawer/elements/pcb-soldermask/via.ts +27 -0
  19. package/lib/drawer/elements/soldermask-margin.ts +39 -8
  20. package/package.json +2 -2
  21. package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
  22. package/tests/board-snapshot/usb-c-flashlight-board.test.ts +1 -0
  23. package/tests/elements/__snapshots__/board-with-elements.snap.png +0 -0
  24. package/tests/elements/__snapshots__/brep-copper-pours.snap.png +0 -0
  25. package/tests/elements/__snapshots__/custom-outline-board.snap.png +0 -0
  26. package/tests/elements/__snapshots__/oval-plated-hole.snap.png +0 -0
  27. package/tests/elements/__snapshots__/pcb-board.snap.png +0 -0
  28. package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
  29. package/tests/elements/__snapshots__/pcb-fabrication-note-dimension.snap.png +0 -0
  30. package/tests/elements/__snapshots__/pcb-hole-soldermask-margin.snap.png +0 -0
  31. package/tests/elements/__snapshots__/pcb-keepout-layer-filter.snap.png +0 -0
  32. package/tests/elements/__snapshots__/pcb-keepout-multiple-layers.snap.png +0 -0
  33. package/tests/elements/__snapshots__/pcb-keepout-rect-and-circle.snap.png +0 -0
  34. package/tests/elements/__snapshots__/pcb-keepout-with-group-id.snap.png +0 -0
  35. package/tests/elements/__snapshots__/pcb-no-soldermask.snap.png +0 -0
  36. package/tests/elements/__snapshots__/pcb-plated-hole-soldermask-margin.snap.png +0 -0
  37. package/tests/elements/__snapshots__/pcb-plated-hole.snap.png +0 -0
  38. package/tests/elements/__snapshots__/pcb-silkscreen-on-component.snap.png +0 -0
  39. package/tests/elements/__snapshots__/pcb-silkscreen-oval.snap.png +0 -0
  40. package/tests/elements/__snapshots__/pcb-smtpad-asymmetric-soldermask-margin.snap.png +0 -0
  41. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-coverage.snap.png +0 -0
  42. package/tests/elements/__snapshots__/pcb-smtpad-soldermask-margin.snap.png +0 -0
  43. package/tests/elements/__snapshots__/pill-plated-hole.snap.png +0 -0
  44. package/tests/elements/pcb-comprehensive-soldermask-margin.test.ts +2 -2
  45. package/tests/elements/pcb-hole-soldermask-margin.test.ts +155 -2
  46. package/tests/elements/pcb-no-soldermask.test.ts +1281 -0
  47. package/tests/elements/pcb-plated-hole-soldermask-margin.test.ts +1 -1
  48. package/tests/elements/pcb-plated-hole.test.ts +40 -4
  49. package/tests/elements/pcb-smtpad-asymmetric-soldermask-margin.test.ts +140 -0
  50. package/tests/elements/pcb-smtpad-soldermask-coverage.test.ts +1 -1
  51. package/tests/elements/pcb-smtpad-soldermask-margin.test.ts +18 -2
  52. package/tests/fixtures/getStackedPngSvgComparison.ts +8 -2
@@ -72,9 +72,12 @@ import { drawPcbNoteText } from "./elements/pcb-note-text"
72
72
  import { drawPcbNoteDimension } from "./elements/pcb-note-dimension"
73
73
  import { drawPcbFabricationNoteDimension } from "./elements/pcb-fabrication-note-dimension"
74
74
  import { drawPcbNoteLine } from "./elements/pcb-note-line"
75
+ import { drawPcbSoldermask } from "./elements/pcb-soldermask"
75
76
 
76
77
  export interface DrawElementsOptions {
77
78
  layers?: PcbRenderLayer[]
79
+ /** Whether to render the soldermask layer. Defaults to false. */
80
+ showSoldermask?: boolean
78
81
  }
79
82
 
80
83
  interface CanvasLike {
@@ -162,358 +165,305 @@ export class CircuitToCanvasDrawer {
162
165
  elements: AnyCircuitElement[],
163
166
  options: DrawElementsOptions = {},
164
167
  ): void {
165
- // Check if any pad or hole has is_covered_with_solder_mask: true
166
- const hasSoldermaskPads = elements.some(
167
- (el) =>
168
- el.type === "pcb_smtpad" &&
169
- (el as PcbSmtPad).is_covered_with_solder_mask === true,
170
- )
171
- const hasSoldermaskHoles = elements.some(
172
- (el) =>
173
- el.type === "pcb_hole" &&
174
- (el as PcbHole & { is_covered_with_solder_mask?: boolean })
175
- .is_covered_with_solder_mask === true,
176
- )
177
- const hasSoldermaskPlatedHoles = elements.some(
178
- (el) =>
179
- el.type === "pcb_plated_hole" &&
180
- (el as PcbPlatedHole & { is_covered_with_solder_mask?: boolean })
181
- .is_covered_with_solder_mask === true,
182
- )
183
-
184
- for (const element of elements) {
185
- if (element.type === "pcb_board") {
186
- this.drawBoardWithSoldermask(element as PcbBoard)
187
- } else {
188
- this.drawElement(element, options)
189
- }
190
- }
191
- }
192
-
193
- private drawBoardWithSoldermask(board: PcbBoard): void {
194
- const { width, height, center, outline } = board
195
- const layer = "top" // Default to top layer for soldermask color
196
-
197
- // If the board has a custom outline, draw it as a path with soldermask fill
198
- if (outline && Array.isArray(outline) && outline.length >= 3) {
199
- const soldermaskColor =
200
- this.colorMap.soldermask[
201
- layer as keyof typeof this.colorMap.soldermask
202
- ] ?? this.colorMap.soldermask.top
203
-
204
- // Draw filled path
205
- const canvasPoints = outline.map((p) => {
206
- const [x, y] = applyToPoint(this.realToCanvasMat, [p.x, p.y])
207
- return { x, y }
208
- })
209
-
210
- this.ctx.beginPath()
211
- const firstPoint = canvasPoints[0]
212
- if (firstPoint) {
213
- this.ctx.moveTo(firstPoint.x, firstPoint.y)
214
- for (let i = 1; i < canvasPoints.length; i++) {
215
- const point = canvasPoints[i]
216
- if (point) {
217
- this.ctx.lineTo(point.x, point.y)
218
- }
219
- }
220
- this.ctx.closePath()
221
- }
222
-
223
- this.ctx.fillStyle = soldermaskColor
224
- this.ctx.fill()
225
-
226
- // Draw outline stroke
227
- drawPath({
228
- ctx: this.ctx,
229
- points: outline.map((p) => ({ x: p.x, y: p.y })),
230
- stroke: this.colorMap.boardOutline,
231
- strokeWidth: 0.1,
232
- realToCanvasMat: this.realToCanvasMat,
233
- closePath: true,
234
- })
235
- return
236
- }
237
-
238
- // Otherwise draw a rectangle with soldermask fill
239
- if (width !== undefined && height !== undefined && center) {
240
- const soldermaskColor =
241
- this.colorMap.soldermask[
242
- layer as keyof typeof this.colorMap.soldermask
243
- ] ?? this.colorMap.soldermask.top
244
-
245
- // Draw filled rectangle
246
- drawRect({
247
- ctx: this.ctx,
248
- center,
249
- width,
250
- height,
251
- fill: soldermaskColor,
252
- realToCanvasMat: this.realToCanvasMat,
253
- })
254
-
255
- // Draw the outline stroke separately using path
256
- const halfWidth = width / 2
257
- const halfHeight = height / 2
258
- const corners = [
259
- { x: center.x - halfWidth, y: center.y - halfHeight },
260
- { x: center.x + halfWidth, y: center.y - halfHeight },
261
- { x: center.x + halfWidth, y: center.y + halfHeight },
262
- { x: center.x - halfWidth, y: center.y + halfHeight },
263
- ]
264
-
265
- drawPath({
266
- ctx: this.ctx,
267
- points: corners,
268
- stroke: this.colorMap.boardOutline,
269
- strokeWidth: 0.1,
270
- realToCanvasMat: this.realToCanvasMat,
271
- closePath: true,
272
- })
273
- }
274
- }
275
-
276
- private drawElement(
277
- element: AnyCircuitElement,
278
- options: DrawElementsOptions,
279
- ): void {
280
- // Check if element should be drawn based on layer options
281
- if (!shouldDrawElement(element, options)) {
282
- return
283
- }
284
-
285
- if (element.type === "pcb_plated_hole") {
286
- drawPcbPlatedHole({
168
+ // Find the board element
169
+ const board = elements.find((el) => el.type === "pcb_board") as
170
+ | PcbBoard
171
+ | undefined
172
+
173
+ // Drawing order:
174
+ // 1. Board outline (just the outline stroke, no fill)
175
+ // 2. Copper elements underneath soldermask (pads, copper text)
176
+ // 3. Soldermask (covers everything except openings)
177
+ // 4. Silkscreen (on soldermask, under top copper layers)
178
+ // 5. Copper pour and traces (drawn on top of soldermask and silkscreen)
179
+ // 6. Plated holes, vias (copper ring + drill hole on top of soldermask)
180
+ // 7. Holes and cutouts (punch through everything)
181
+ // 8. Other annotations
182
+
183
+ // Step 1: Draw board outline
184
+ if (board) {
185
+ drawPcbBoard({
287
186
  ctx: this.ctx,
288
- hole: element as PcbPlatedHole,
187
+ board,
289
188
  realToCanvasMat: this.realToCanvasMat,
290
189
  colorMap: this.colorMap,
291
190
  })
292
191
  }
293
192
 
294
- if (element.type === "pcb_via") {
295
- drawPcbVia({
296
- ctx: this.ctx,
297
- via: element as PcbVia,
298
- realToCanvasMat: this.realToCanvasMat,
299
- colorMap: this.colorMap,
300
- })
301
- }
193
+ // Step 2: Draw copper elements underneath soldermask (pads, copper text)
194
+ for (const element of elements) {
195
+ if (!shouldDrawElement(element, options)) continue
196
+
197
+ if (element.type === "pcb_smtpad") {
198
+ drawPcbSmtPad({
199
+ ctx: this.ctx,
200
+ pad: element as PcbSmtPad,
201
+ realToCanvasMat: this.realToCanvasMat,
202
+ colorMap: this.colorMap,
203
+ })
204
+ }
302
205
 
303
- if (element.type === "pcb_hole") {
304
- drawPcbHole({
305
- ctx: this.ctx,
306
- hole: element as PcbHole,
307
- realToCanvasMat: this.realToCanvasMat,
308
- colorMap: this.colorMap,
309
- })
206
+ if (element.type === "pcb_copper_text") {
207
+ drawPcbCopperText({
208
+ ctx: this.ctx,
209
+ text: element as PcbCopperText,
210
+ realToCanvasMat: this.realToCanvasMat,
211
+ colorMap: this.colorMap,
212
+ })
213
+ }
310
214
  }
311
215
 
312
- if (element.type === "pcb_smtpad") {
313
- drawPcbSmtPad({
216
+ // Step 3: Draw soldermask layer (only if showSoldermask is true)
217
+ const showSoldermask = options.showSoldermask ?? false
218
+ if (board) {
219
+ drawPcbSoldermask({
314
220
  ctx: this.ctx,
315
- pad: element as PcbSmtPad,
221
+ board,
222
+ elements,
316
223
  realToCanvasMat: this.realToCanvasMat,
317
224
  colorMap: this.colorMap,
225
+ layer: "top",
226
+ showSoldermask,
318
227
  })
319
228
  }
320
229
 
321
- if (element.type === "pcb_trace") {
322
- drawPcbTrace({
323
- ctx: this.ctx,
324
- trace: element as PcbTrace,
325
- realToCanvasMat: this.realToCanvasMat,
326
- colorMap: this.colorMap,
327
- })
328
- }
230
+ // Step 4: Draw silkscreen (on soldermask, under top copper layers)
231
+ for (const element of elements) {
232
+ if (!shouldDrawElement(element, options)) continue
233
+
234
+ if (element.type === "pcb_silkscreen_text") {
235
+ drawPcbSilkscreenText({
236
+ ctx: this.ctx,
237
+ text: element as PcbSilkscreenText,
238
+ realToCanvasMat: this.realToCanvasMat,
239
+ colorMap: this.colorMap,
240
+ })
241
+ }
329
242
 
330
- if (element.type === "pcb_board") {
331
- drawPcbBoard({
332
- ctx: this.ctx,
333
- board: element as PcbBoard,
334
- realToCanvasMat: this.realToCanvasMat,
335
- colorMap: this.colorMap,
336
- })
337
- }
243
+ if (element.type === "pcb_silkscreen_rect") {
244
+ drawPcbSilkscreenRect({
245
+ ctx: this.ctx,
246
+ rect: element as PcbSilkscreenRect,
247
+ realToCanvasMat: this.realToCanvasMat,
248
+ colorMap: this.colorMap,
249
+ })
250
+ }
338
251
 
339
- if (element.type === "pcb_silkscreen_text") {
340
- drawPcbSilkscreenText({
341
- ctx: this.ctx,
342
- text: element as PcbSilkscreenText,
343
- realToCanvasMat: this.realToCanvasMat,
344
- colorMap: this.colorMap,
345
- })
346
- }
252
+ if (element.type === "pcb_silkscreen_circle") {
253
+ drawPcbSilkscreenCircle({
254
+ ctx: this.ctx,
255
+ circle: element as PcbSilkscreenCircle,
256
+ realToCanvasMat: this.realToCanvasMat,
257
+ colorMap: this.colorMap,
258
+ })
259
+ }
347
260
 
348
- if (element.type === "pcb_silkscreen_rect") {
349
- drawPcbSilkscreenRect({
350
- ctx: this.ctx,
351
- rect: element as PcbSilkscreenRect,
352
- realToCanvasMat: this.realToCanvasMat,
353
- colorMap: this.colorMap,
354
- })
355
- }
261
+ if (element.type === "pcb_silkscreen_line") {
262
+ drawPcbSilkscreenLine({
263
+ ctx: this.ctx,
264
+ line: element as PcbSilkscreenLine,
265
+ realToCanvasMat: this.realToCanvasMat,
266
+ colorMap: this.colorMap,
267
+ })
268
+ }
356
269
 
357
- if (element.type === "pcb_silkscreen_circle") {
358
- drawPcbSilkscreenCircle({
359
- ctx: this.ctx,
360
- circle: element as PcbSilkscreenCircle,
361
- realToCanvasMat: this.realToCanvasMat,
362
- colorMap: this.colorMap,
363
- })
364
- }
270
+ if (element.type === "pcb_silkscreen_path") {
271
+ drawPcbSilkscreenPath({
272
+ ctx: this.ctx,
273
+ path: element as PcbSilkscreenPath,
274
+ realToCanvasMat: this.realToCanvasMat,
275
+ colorMap: this.colorMap,
276
+ })
277
+ }
365
278
 
366
- if (element.type === "pcb_silkscreen_line") {
367
- drawPcbSilkscreenLine({
368
- ctx: this.ctx,
369
- line: element as PcbSilkscreenLine,
370
- realToCanvasMat: this.realToCanvasMat,
371
- colorMap: this.colorMap,
372
- })
373
- }
279
+ if (element.type === "pcb_silkscreen_pill") {
280
+ drawPcbSilkscreenPill({
281
+ ctx: this.ctx,
282
+ pill: element as PcbSilkscreenPill,
283
+ realToCanvasMat: this.realToCanvasMat,
284
+ colorMap: this.colorMap,
285
+ })
286
+ }
374
287
 
375
- if (element.type === "pcb_silkscreen_path") {
376
- drawPcbSilkscreenPath({
377
- ctx: this.ctx,
378
- path: element as PcbSilkscreenPath,
379
- realToCanvasMat: this.realToCanvasMat,
380
- colorMap: this.colorMap,
381
- })
288
+ if (element.type === "pcb_silkscreen_oval") {
289
+ drawPcbSilkscreenOval({
290
+ ctx: this.ctx,
291
+ oval: element as PcbSilkscreenOval,
292
+ realToCanvasMat: this.realToCanvasMat,
293
+ colorMap: this.colorMap,
294
+ })
295
+ }
382
296
  }
383
297
 
384
- if (element.type === "pcb_silkscreen_pill") {
385
- drawPcbSilkscreenPill({
386
- ctx: this.ctx,
387
- pill: element as PcbSilkscreenPill,
388
- realToCanvasMat: this.realToCanvasMat,
389
- colorMap: this.colorMap,
390
- })
391
- }
298
+ // Step 5: Draw copper pour and traces (on top of soldermask and silkscreen)
299
+ for (const element of elements) {
300
+ if (!shouldDrawElement(element, options)) continue
301
+
302
+ if (element.type === "pcb_copper_pour") {
303
+ drawPcbCopperPour({
304
+ ctx: this.ctx,
305
+ pour: element as PcbCopperPour,
306
+ realToCanvasMat: this.realToCanvasMat,
307
+ colorMap: this.colorMap,
308
+ })
309
+ }
392
310
 
393
- if (element.type === "pcb_silkscreen_oval") {
394
- drawPcbSilkscreenOval({
395
- ctx: this.ctx,
396
- oval: element as PcbSilkscreenOval,
397
- realToCanvasMat: this.realToCanvasMat,
398
- colorMap: this.colorMap,
399
- })
311
+ if (element.type === "pcb_trace") {
312
+ drawPcbTrace({
313
+ ctx: this.ctx,
314
+ trace: element as PcbTrace,
315
+ realToCanvasMat: this.realToCanvasMat,
316
+ colorMap: this.colorMap,
317
+ })
318
+ }
400
319
  }
401
320
 
402
- if (element.type === "pcb_cutout") {
403
- drawPcbCutout({
404
- ctx: this.ctx,
405
- cutout: element as PcbCutout,
406
- realToCanvasMat: this.realToCanvasMat,
407
- colorMap: this.colorMap,
408
- })
409
- }
321
+ // Step 6: Draw plated holes, vias (copper ring + drill hole on top of soldermask)
322
+ for (const element of elements) {
323
+ if (!shouldDrawElement(element, options)) continue
324
+
325
+ if (element.type === "pcb_plated_hole") {
326
+ drawPcbPlatedHole({
327
+ ctx: this.ctx,
328
+ hole: element as PcbPlatedHole,
329
+ realToCanvasMat: this.realToCanvasMat,
330
+ colorMap: this.colorMap,
331
+ soldermaskMargin: showSoldermask
332
+ ? (element as PcbPlatedHole).soldermask_margin
333
+ : undefined,
334
+ showSoldermask,
335
+ })
336
+ }
410
337
 
411
- if (element.type === "pcb_keepout") {
412
- drawPcbKeepout({
413
- ctx: this.ctx,
414
- keepout: element as PCBKeepout,
415
- realToCanvasMat: this.realToCanvasMat,
416
- colorMap: this.colorMap,
417
- })
338
+ if (element.type === "pcb_via") {
339
+ drawPcbVia({
340
+ ctx: this.ctx,
341
+ via: element as PcbVia,
342
+ realToCanvasMat: this.realToCanvasMat,
343
+ colorMap: this.colorMap,
344
+ })
345
+ }
418
346
  }
419
347
 
420
- if (element.type === "pcb_copper_pour") {
421
- drawPcbCopperPour({
422
- ctx: this.ctx,
423
- pour: element as PcbCopperPour,
424
- realToCanvasMat: this.realToCanvasMat,
425
- colorMap: this.colorMap,
426
- })
427
- }
348
+ // Step 7: Draw holes and cutouts (these punch through everything)
349
+ for (const element of elements) {
350
+ if (!shouldDrawElement(element, options)) continue
351
+
352
+ if (element.type === "pcb_hole") {
353
+ drawPcbHole({
354
+ ctx: this.ctx,
355
+ hole: element as PcbHole,
356
+ realToCanvasMat: this.realToCanvasMat,
357
+ colorMap: this.colorMap,
358
+ soldermaskMargin: showSoldermask
359
+ ? element.soldermask_margin
360
+ : undefined,
361
+ })
362
+ }
428
363
 
429
- if (element.type === "pcb_copper_text") {
430
- drawPcbCopperText({
431
- ctx: this.ctx,
432
- text: element as PcbCopperText,
433
- realToCanvasMat: this.realToCanvasMat,
434
- colorMap: this.colorMap,
435
- })
364
+ if (element.type === "pcb_cutout") {
365
+ drawPcbCutout({
366
+ ctx: this.ctx,
367
+ cutout: element as PcbCutout,
368
+ realToCanvasMat: this.realToCanvasMat,
369
+ colorMap: this.colorMap,
370
+ })
371
+ }
436
372
  }
437
373
 
438
- if (element.type === "pcb_fabrication_note_text") {
439
- drawPcbFabricationNoteText({
440
- ctx: this.ctx,
441
- text: element as PcbFabricationNoteText,
442
- realToCanvasMat: this.realToCanvasMat,
443
- colorMap: this.colorMap,
444
- })
445
- }
374
+ // Step 8: Draw other annotations
375
+ for (const element of elements) {
376
+ if (!shouldDrawElement(element, options)) continue
377
+
378
+ if (element.type === "pcb_keepout") {
379
+ drawPcbKeepout({
380
+ ctx: this.ctx,
381
+ keepout: element as PCBKeepout,
382
+ realToCanvasMat: this.realToCanvasMat,
383
+ colorMap: this.colorMap,
384
+ })
385
+ }
446
386
 
447
- if (element.type === "pcb_fabrication_note_rect") {
448
- drawPcbFabricationNoteRect({
449
- ctx: this.ctx,
450
- rect: element as PcbFabricationNoteRect,
451
- realToCanvasMat: this.realToCanvasMat,
452
- colorMap: this.colorMap,
453
- })
454
- }
387
+ if (element.type === "pcb_fabrication_note_text") {
388
+ drawPcbFabricationNoteText({
389
+ ctx: this.ctx,
390
+ text: element as PcbFabricationNoteText,
391
+ realToCanvasMat: this.realToCanvasMat,
392
+ colorMap: this.colorMap,
393
+ })
394
+ }
455
395
 
456
- if (element.type === "pcb_note_rect") {
457
- drawPcbNoteRect({
458
- realToCanvasMat: this.realToCanvasMat,
459
- colorMap: this.colorMap,
460
- ctx: this.ctx,
461
- rect: element as PcbNoteRect,
462
- })
463
- }
396
+ if (element.type === "pcb_fabrication_note_rect") {
397
+ drawPcbFabricationNoteRect({
398
+ ctx: this.ctx,
399
+ rect: element as PcbFabricationNoteRect,
400
+ realToCanvasMat: this.realToCanvasMat,
401
+ colorMap: this.colorMap,
402
+ })
403
+ }
464
404
 
465
- if (element.type === "pcb_fabrication_note_path") {
466
- drawPcbFabricationNotePath({
467
- ctx: this.ctx,
468
- path: element as PcbFabricationNotePath,
469
- realToCanvasMat: this.realToCanvasMat,
470
- colorMap: this.colorMap,
471
- })
472
- }
405
+ if (element.type === "pcb_note_rect") {
406
+ drawPcbNoteRect({
407
+ realToCanvasMat: this.realToCanvasMat,
408
+ colorMap: this.colorMap,
409
+ ctx: this.ctx,
410
+ rect: element as PcbNoteRect,
411
+ })
412
+ }
473
413
 
474
- if (element.type === "pcb_note_path") {
475
- drawPcbNotePath({
476
- ctx: this.ctx,
477
- path: element as PcbNotePath,
478
- realToCanvasMat: this.realToCanvasMat,
479
- colorMap: this.colorMap,
480
- })
481
- }
414
+ if (element.type === "pcb_fabrication_note_path") {
415
+ drawPcbFabricationNotePath({
416
+ ctx: this.ctx,
417
+ path: element as PcbFabricationNotePath,
418
+ realToCanvasMat: this.realToCanvasMat,
419
+ colorMap: this.colorMap,
420
+ })
421
+ }
482
422
 
483
- if (element.type === "pcb_note_text") {
484
- drawPcbNoteText({
485
- ctx: this.ctx,
486
- text: element as PcbNoteText,
487
- realToCanvasMat: this.realToCanvasMat,
488
- colorMap: this.colorMap,
489
- })
490
- }
423
+ if (element.type === "pcb_note_path") {
424
+ drawPcbNotePath({
425
+ ctx: this.ctx,
426
+ path: element as PcbNotePath,
427
+ realToCanvasMat: this.realToCanvasMat,
428
+ colorMap: this.colorMap,
429
+ })
430
+ }
491
431
 
492
- if (element.type === "pcb_note_line") {
493
- drawPcbNoteLine({
494
- ctx: this.ctx,
495
- line: element as PcbNoteLine,
496
- realToCanvasMat: this.realToCanvasMat,
497
- colorMap: this.colorMap,
498
- })
499
- }
432
+ if (element.type === "pcb_note_text") {
433
+ drawPcbNoteText({
434
+ ctx: this.ctx,
435
+ text: element as PcbNoteText,
436
+ realToCanvasMat: this.realToCanvasMat,
437
+ colorMap: this.colorMap,
438
+ })
439
+ }
500
440
 
501
- if (element.type === "pcb_note_dimension") {
502
- drawPcbNoteDimension({
503
- ctx: this.ctx,
504
- pcbNoteDimension: element as PcbNoteDimension,
505
- realToCanvasMat: this.realToCanvasMat,
506
- colorMap: this.colorMap,
507
- })
508
- }
441
+ if (element.type === "pcb_note_line") {
442
+ drawPcbNoteLine({
443
+ ctx: this.ctx,
444
+ line: element as PcbNoteLine,
445
+ realToCanvasMat: this.realToCanvasMat,
446
+ colorMap: this.colorMap,
447
+ })
448
+ }
509
449
 
510
- if (element.type === "pcb_fabrication_note_dimension") {
511
- drawPcbFabricationNoteDimension({
512
- ctx: this.ctx,
513
- pcbFabricationNoteDimension: element as PcbFabricationNoteDimension,
514
- realToCanvasMat: this.realToCanvasMat,
515
- colorMap: this.colorMap,
516
- })
450
+ if (element.type === "pcb_note_dimension") {
451
+ drawPcbNoteDimension({
452
+ ctx: this.ctx,
453
+ pcbNoteDimension: element as PcbNoteDimension,
454
+ realToCanvasMat: this.realToCanvasMat,
455
+ colorMap: this.colorMap,
456
+ })
457
+ }
458
+
459
+ if (element.type === "pcb_fabrication_note_dimension") {
460
+ drawPcbFabricationNoteDimension({
461
+ ctx: this.ctx,
462
+ pcbFabricationNoteDimension: element as PcbFabricationNoteDimension,
463
+ realToCanvasMat: this.realToCanvasMat,
464
+ colorMap: this.colorMap,
465
+ })
466
+ }
517
467
  }
518
468
  }
519
469
  }
@@ -0,0 +1,39 @@
1
+ import type { CanvasContext } from "../../types"
2
+
3
+ /**
4
+ * Draws a pill/stadium shape path centered at (cx, cy).
5
+ * A pill is a rectangle with fully rounded ends (semicircular caps).
6
+ * The path is not filled or stroked - call ctx.fill() or ctx.stroke() after.
7
+ */
8
+ export function drawPillPath(params: {
9
+ ctx: CanvasContext
10
+ cx: number
11
+ cy: number
12
+ width: number
13
+ height: number
14
+ }): void {
15
+ const { ctx, cx, cy, width, height } = params
16
+ if (width > height) {
17
+ // Horizontal pill
18
+ const radius = height / 2
19
+ const straightLength = width - height
20
+ ctx.moveTo(cx - straightLength / 2, cy - radius)
21
+ ctx.lineTo(cx + straightLength / 2, cy - radius)
22
+ ctx.arc(cx + straightLength / 2, cy, radius, -Math.PI / 2, Math.PI / 2)
23
+ ctx.lineTo(cx - straightLength / 2, cy + radius)
24
+ ctx.arc(cx - straightLength / 2, cy, radius, Math.PI / 2, -Math.PI / 2)
25
+ } else if (height > width) {
26
+ // Vertical pill
27
+ const radius = width / 2
28
+ const straightLength = height - width
29
+ ctx.moveTo(cx + radius, cy - straightLength / 2)
30
+ ctx.lineTo(cx + radius, cy + straightLength / 2)
31
+ ctx.arc(cx, cy + straightLength / 2, radius, 0, Math.PI)
32
+ ctx.lineTo(cx - radius, cy - straightLength / 2)
33
+ ctx.arc(cx, cy - straightLength / 2, radius, Math.PI, 0)
34
+ } else {
35
+ // Square dimensions = circle
36
+ ctx.arc(cx, cy, width / 2, 0, Math.PI * 2)
37
+ }
38
+ ctx.closePath()
39
+ }