circuit-to-canvas 0.0.27 → 0.0.29

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.
package/README.md CHANGED
@@ -53,6 +53,7 @@ This checklist tracks PCB drawing features from [circuit-to-svg](https://github.
53
53
  - [x] `pcb_silkscreen_circle` - Circles on silkscreen
54
54
  - [x] `pcb_silkscreen_line` - Lines on silkscreen
55
55
  - [x] `pcb_silkscreen_path` - Paths/routes on silkscreen
56
+ - [x] `pcb_silkscreen_pill` - Pill shapes (rounded rectangles) on silkscreen
56
57
 
57
58
  ### Copper Text
58
59
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnyCircuitElement, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension } from 'circuit-json';
1
+ import { AnyCircuitElement, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbSilkscreenPill, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension } from 'circuit-json';
2
2
  import { Matrix } from 'transformation-matrix';
3
3
 
4
4
  /**
@@ -154,9 +154,11 @@ interface DrawPillParams {
154
154
  };
155
155
  width: number;
156
156
  height: number;
157
- fill: string;
157
+ fill?: string;
158
158
  realToCanvasMat: Matrix;
159
159
  rotation?: number;
160
+ stroke?: string;
161
+ strokeWidth?: number;
160
162
  }
161
163
  declare function drawPill(params: DrawPillParams): void;
162
164
 
@@ -341,6 +343,14 @@ interface DrawPcbSilkscreenPathParams {
341
343
  }
342
344
  declare function drawPcbSilkscreenPath(params: DrawPcbSilkscreenPathParams): void;
343
345
 
346
+ interface DrawPcbSilkscreenPillParams {
347
+ ctx: CanvasContext;
348
+ pill: PcbSilkscreenPill;
349
+ realToCanvasMat: Matrix;
350
+ colorMap: PcbColorMap;
351
+ }
352
+ declare function drawPcbSilkscreenPill(params: DrawPcbSilkscreenPillParams): void;
353
+
344
354
  interface DrawPcbCutoutParams {
345
355
  ctx: CanvasContext;
346
356
  cutout: PcbCutout;
@@ -421,4 +431,4 @@ interface DrawPcbNoteDimensionParams {
421
431
  }
422
432
  declare function drawPcbNoteDimension(params: DrawPcbNoteDimensionParams): void;
423
433
 
424
- export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawArrowParams, type DrawCircleParams, type DrawContext, type DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawArrow, drawCircle, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenPath, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
434
+ export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawArrowParams, type DrawCircleParams, type DrawContext, type DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenPillParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawArrow, drawCircle, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenPath, drawPcbSilkscreenPill, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
package/dist/index.js CHANGED
@@ -150,11 +150,14 @@ function drawPill(params) {
150
150
  height,
151
151
  fill,
152
152
  realToCanvasMat,
153
- rotation = 0
153
+ rotation = 0,
154
+ stroke,
155
+ strokeWidth
154
156
  } = params;
155
157
  const [cx, cy] = applyToPoint4(realToCanvasMat, [center.x, center.y]);
156
158
  const scaledWidth = width * Math.abs(realToCanvasMat.a);
157
159
  const scaledHeight = height * Math.abs(realToCanvasMat.a);
160
+ const scaledStrokeWidth = strokeWidth ? strokeWidth * Math.abs(realToCanvasMat.a) : void 0;
158
161
  ctx.save();
159
162
  ctx.translate(cx, cy);
160
163
  if (rotation !== 0) {
@@ -181,9 +184,40 @@ function drawPill(params) {
181
184
  ctx.arc(0, 0, scaledWidth / 2, 0, Math.PI * 2);
182
185
  }
183
186
  ctx.closePath();
187
+ if (fill) {
188
+ ctx.fillStyle = fill;
189
+ ctx.fill();
190
+ }
191
+ if (stroke && scaledStrokeWidth) {
192
+ ctx.strokeStyle = stroke;
193
+ ctx.lineWidth = scaledStrokeWidth;
194
+ ctx.stroke();
195
+ }
196
+ ctx.restore();
197
+ }
198
+
199
+ // lib/drawer/shapes/polygon.ts
200
+ import { applyToPoint as applyToPoint5 } from "transformation-matrix";
201
+ function drawPolygon(params) {
202
+ const { ctx, points, fill, realToCanvasMat } = params;
203
+ if (points.length < 3) return;
204
+ ctx.beginPath();
205
+ const canvasPoints = points.map(
206
+ (p) => applyToPoint5(realToCanvasMat, [p.x, p.y])
207
+ );
208
+ const firstPoint = canvasPoints[0];
209
+ if (!firstPoint) return;
210
+ const [firstX, firstY] = firstPoint;
211
+ ctx.moveTo(firstX, firstY);
212
+ for (let i = 1; i < canvasPoints.length; i++) {
213
+ const point = canvasPoints[i];
214
+ if (!point) continue;
215
+ const [x, y] = point;
216
+ ctx.lineTo(x, y);
217
+ }
218
+ ctx.closePath();
184
219
  ctx.fillStyle = fill;
185
220
  ctx.fill();
186
- ctx.restore();
187
221
  }
188
222
 
189
223
  // lib/drawer/elements/pcb-plated-hole.ts
@@ -315,6 +349,61 @@ function drawPcbPlatedHole(params) {
315
349
  });
316
350
  return;
317
351
  }
352
+ if (hole.shape === "hole_with_polygon_pad") {
353
+ const padOutline = hole.pad_outline;
354
+ if (padOutline && padOutline.length >= 3) {
355
+ const padPoints = padOutline.map((point) => ({
356
+ x: hole.x + point.x,
357
+ y: hole.y + point.y
358
+ }));
359
+ drawPolygon({
360
+ ctx,
361
+ points: padPoints,
362
+ fill: colorMap.copper.top,
363
+ realToCanvasMat
364
+ });
365
+ }
366
+ const holeX = hole.x + (hole.hole_offset_x ?? 0);
367
+ const holeY = hole.y + (hole.hole_offset_y ?? 0);
368
+ const holeShape = hole.hole_shape;
369
+ if (holeShape === "circle") {
370
+ drawCircle({
371
+ ctx,
372
+ center: { x: holeX, y: holeY },
373
+ radius: (hole.hole_diameter ?? 0) / 2,
374
+ fill: colorMap.drill,
375
+ realToCanvasMat
376
+ });
377
+ } else if (holeShape === "oval") {
378
+ drawOval({
379
+ ctx,
380
+ center: { x: holeX, y: holeY },
381
+ width: hole.hole_width ?? 0,
382
+ height: hole.hole_height ?? 0,
383
+ fill: colorMap.drill,
384
+ realToCanvasMat
385
+ });
386
+ } else if (holeShape === "pill") {
387
+ drawPill({
388
+ ctx,
389
+ center: { x: holeX, y: holeY },
390
+ width: hole.hole_width ?? 0,
391
+ height: hole.hole_height ?? 0,
392
+ fill: colorMap.drill,
393
+ realToCanvasMat
394
+ });
395
+ } else if (holeShape === "rotated_pill") {
396
+ drawPill({
397
+ ctx,
398
+ center: { x: holeX, y: holeY },
399
+ width: hole.hole_width ?? 0,
400
+ height: hole.hole_height ?? 0,
401
+ fill: colorMap.drill,
402
+ realToCanvasMat
403
+ });
404
+ }
405
+ return;
406
+ }
318
407
  }
319
408
 
320
409
  // lib/drawer/elements/pcb-via.ts
@@ -407,30 +496,6 @@ function drawPcbHole(params) {
407
496
  }
408
497
  }
409
498
 
410
- // lib/drawer/shapes/polygon.ts
411
- import { applyToPoint as applyToPoint5 } from "transformation-matrix";
412
- function drawPolygon(params) {
413
- const { ctx, points, fill, realToCanvasMat } = params;
414
- if (points.length < 3) return;
415
- ctx.beginPath();
416
- const canvasPoints = points.map(
417
- (p) => applyToPoint5(realToCanvasMat, [p.x, p.y])
418
- );
419
- const firstPoint = canvasPoints[0];
420
- if (!firstPoint) return;
421
- const [firstX, firstY] = firstPoint;
422
- ctx.moveTo(firstX, firstY);
423
- for (let i = 1; i < canvasPoints.length; i++) {
424
- const point = canvasPoints[i];
425
- if (!point) continue;
426
- const [x, y] = point;
427
- ctx.lineTo(x, y);
428
- }
429
- ctx.closePath();
430
- ctx.fillStyle = fill;
431
- ctx.fill();
432
- }
433
-
434
499
  // lib/drawer/elements/pcb-smtpad.ts
435
500
  function layerToColor(layer, colorMap) {
436
501
  return colorMap.copper[layer] ?? colorMap.copper.top;
@@ -891,6 +956,25 @@ function drawPcbSilkscreenPath(params) {
891
956
  }
892
957
  }
893
958
 
959
+ // lib/drawer/elements/pcb-silkscreen-pill.ts
960
+ function layerToSilkscreenColor6(layer, colorMap) {
961
+ return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
962
+ }
963
+ function drawPcbSilkscreenPill(params) {
964
+ const { ctx, pill, realToCanvasMat, colorMap } = params;
965
+ const color = layerToSilkscreenColor6(pill.layer, colorMap);
966
+ const strokeWidth = 0.2;
967
+ drawPill({
968
+ ctx,
969
+ center: pill.center,
970
+ width: pill.width,
971
+ height: pill.height,
972
+ stroke: color,
973
+ strokeWidth,
974
+ realToCanvasMat
975
+ });
976
+ }
977
+
894
978
  // lib/drawer/elements/pcb-cutout.ts
895
979
  function drawPcbCutout(params) {
896
980
  const { ctx, cutout, realToCanvasMat, colorMap } = params;
@@ -1520,6 +1604,14 @@ var CircuitToCanvasDrawer = class {
1520
1604
  colorMap: this.colorMap
1521
1605
  });
1522
1606
  }
1607
+ if (element.type === "pcb_silkscreen_pill") {
1608
+ drawPcbSilkscreenPill({
1609
+ ctx: this.ctx,
1610
+ pill: element,
1611
+ realToCanvasMat: this.realToCanvasMat,
1612
+ colorMap: this.colorMap
1613
+ });
1614
+ }
1523
1615
  if (element.type === "pcb_cutout") {
1524
1616
  drawPcbCutout({
1525
1617
  ctx: this.ctx,
@@ -1634,6 +1726,7 @@ export {
1634
1726
  drawPcbSilkscreenCircle,
1635
1727
  drawPcbSilkscreenLine,
1636
1728
  drawPcbSilkscreenPath,
1729
+ drawPcbSilkscreenPill,
1637
1730
  drawPcbSilkscreenRect,
1638
1731
  drawPcbSilkscreenText,
1639
1732
  drawPcbSmtPad,
@@ -11,6 +11,7 @@ import type {
11
11
  PcbSilkscreenCircle,
12
12
  PcbSilkscreenLine,
13
13
  PcbSilkscreenPath,
14
+ PcbSilkscreenPill,
14
15
  PcbCutout,
15
16
  PcbCopperPour,
16
17
  PcbCopperText,
@@ -43,6 +44,7 @@ import { drawPcbSilkscreenRect } from "./elements/pcb-silkscreen-rect"
43
44
  import { drawPcbSilkscreenCircle } from "./elements/pcb-silkscreen-circle"
44
45
  import { drawPcbSilkscreenLine } from "./elements/pcb-silkscreen-line"
45
46
  import { drawPcbSilkscreenPath } from "./elements/pcb-silkscreen-path"
47
+ import { drawPcbSilkscreenPill } from "./elements/pcb-silkscreen-pill"
46
48
  import { drawPcbCutout } from "./elements/pcb-cutout"
47
49
  import { drawPcbCopperPour } from "./elements/pcb-copper-pour"
48
50
  import { drawPcbCopperText } from "./elements/pcb-copper-text"
@@ -252,6 +254,15 @@ export class CircuitToCanvasDrawer {
252
254
  })
253
255
  }
254
256
 
257
+ if (element.type === "pcb_silkscreen_pill") {
258
+ drawPcbSilkscreenPill({
259
+ ctx: this.ctx,
260
+ pill: element as PcbSilkscreenPill,
261
+ realToCanvasMat: this.realToCanvasMat,
262
+ colorMap: this.colorMap,
263
+ })
264
+ }
265
+
255
266
  if (element.type === "pcb_cutout") {
256
267
  drawPcbCutout({
257
268
  ctx: this.ctx,
@@ -38,6 +38,11 @@ export {
38
38
  type DrawPcbSilkscreenPathParams,
39
39
  } from "./pcb-silkscreen-path"
40
40
 
41
+ export {
42
+ drawPcbSilkscreenPill,
43
+ type DrawPcbSilkscreenPillParams,
44
+ } from "./pcb-silkscreen-pill"
45
+
41
46
  export { drawPcbCutout, type DrawPcbCutoutParams } from "./pcb-cutout"
42
47
 
43
48
  export {
@@ -5,6 +5,7 @@ import { drawCircle } from "../shapes/circle"
5
5
  import { drawRect } from "../shapes/rect"
6
6
  import { drawOval } from "../shapes/oval"
7
7
  import { drawPill } from "../shapes/pill"
8
+ import { drawPolygon } from "../shapes/polygon"
8
9
 
9
10
  export interface DrawPcbPlatedHoleParams {
10
11
  ctx: CanvasContext
@@ -96,12 +97,12 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
96
97
  height: hole.rect_pad_height,
97
98
  fill: colorMap.copper.top,
98
99
  realToCanvasMat,
99
- borderRadius: (hole as any).rect_border_radius ?? 0,
100
+ borderRadius: hole.rect_border_radius ?? 0,
100
101
  })
101
102
 
102
103
  // 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)
104
+ const holeX = hole.x + (hole.hole_offset_x ?? 0)
105
+ const holeY = hole.y + (hole.hole_offset_y ?? 0)
105
106
  drawCircle({
106
107
  ctx,
107
108
  center: { x: holeX, y: holeY },
@@ -121,12 +122,12 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
121
122
  height: hole.rect_pad_height,
122
123
  fill: colorMap.copper.top,
123
124
  realToCanvasMat,
124
- borderRadius: (hole as any).rect_border_radius ?? 0,
125
+ borderRadius: hole.rect_border_radius ?? 0,
125
126
  })
126
127
 
127
128
  // 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)
129
+ const holeX = hole.x + (hole.hole_offset_x ?? 0)
130
+ const holeY = hole.y + (hole.hole_offset_y ?? 0)
130
131
  drawPill({
131
132
  ctx,
132
133
  center: { x: holeX, y: holeY },
@@ -147,13 +148,13 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
147
148
  height: hole.rect_pad_height,
148
149
  fill: colorMap.copper.top,
149
150
  realToCanvasMat,
150
- borderRadius: (hole as any).rect_border_radius ?? 0,
151
+ borderRadius: hole.rect_border_radius ?? 0,
151
152
  rotation: hole.rect_ccw_rotation,
152
153
  })
153
154
 
154
155
  // 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)
156
+ const holeX = hole.x + (hole.hole_offset_x ?? 0)
157
+ const holeY = hole.y + (hole.hole_offset_y ?? 0)
157
158
  drawPill({
158
159
  ctx,
159
160
  center: { x: holeX, y: holeY },
@@ -165,4 +166,65 @@ export function drawPcbPlatedHole(params: DrawPcbPlatedHoleParams): void {
165
166
  })
166
167
  return
167
168
  }
169
+
170
+ if (hole.shape === "hole_with_polygon_pad") {
171
+ // Draw polygon pad
172
+ const padOutline = hole.pad_outline
173
+ if (padOutline && padOutline.length >= 3) {
174
+ // Transform pad_outline points to be relative to hole.x, hole.y
175
+ const padPoints = padOutline.map((point: { x: number; y: number }) => ({
176
+ x: hole.x + point.x,
177
+ y: hole.y + point.y,
178
+ }))
179
+ drawPolygon({
180
+ ctx,
181
+ points: padPoints,
182
+ fill: colorMap.copper.top,
183
+ realToCanvasMat,
184
+ })
185
+ }
186
+
187
+ // Draw drill hole (with offset)
188
+ const holeX = hole.x + (hole.hole_offset_x ?? 0)
189
+ const holeY = hole.y + (hole.hole_offset_y ?? 0)
190
+ const holeShape = hole.hole_shape
191
+
192
+ if (holeShape === "circle") {
193
+ drawCircle({
194
+ ctx,
195
+ center: { x: holeX, y: holeY },
196
+ radius: (hole.hole_diameter ?? 0) / 2,
197
+ fill: colorMap.drill,
198
+ realToCanvasMat,
199
+ })
200
+ } else if (holeShape === "oval") {
201
+ drawOval({
202
+ ctx,
203
+ center: { x: holeX, y: holeY },
204
+ width: hole.hole_width ?? 0,
205
+ height: hole.hole_height ?? 0,
206
+ fill: colorMap.drill,
207
+ realToCanvasMat,
208
+ })
209
+ } else if (holeShape === "pill") {
210
+ drawPill({
211
+ ctx,
212
+ center: { x: holeX, y: holeY },
213
+ width: hole.hole_width ?? 0,
214
+ height: hole.hole_height ?? 0,
215
+ fill: colorMap.drill,
216
+ realToCanvasMat,
217
+ })
218
+ } else if (holeShape === "rotated_pill") {
219
+ drawPill({
220
+ ctx,
221
+ center: { x: holeX, y: holeY },
222
+ width: hole.hole_width ?? 0,
223
+ height: hole.hole_height ?? 0,
224
+ fill: colorMap.drill,
225
+ realToCanvasMat,
226
+ })
227
+ }
228
+ return
229
+ }
168
230
  }
@@ -0,0 +1,37 @@
1
+ import type { PcbSilkscreenPill } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawPill } from "../shapes/pill"
5
+
6
+ export interface DrawPcbSilkscreenPillParams {
7
+ ctx: CanvasContext
8
+ pill: PcbSilkscreenPill
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 drawPcbSilkscreenPill(
20
+ params: DrawPcbSilkscreenPillParams,
21
+ ): void {
22
+ const { ctx, pill, realToCanvasMat, colorMap } = params
23
+
24
+ const color = layerToSilkscreenColor(pill.layer, colorMap)
25
+ const strokeWidth = 0.2
26
+
27
+ // Draw as boundary/outline, not filled
28
+ drawPill({
29
+ ctx,
30
+ center: pill.center,
31
+ width: pill.width,
32
+ height: pill.height,
33
+ stroke: color,
34
+ strokeWidth,
35
+ realToCanvasMat,
36
+ })
37
+ }
@@ -7,9 +7,11 @@ export interface DrawPillParams {
7
7
  center: { x: number; y: number }
8
8
  width: number
9
9
  height: number
10
- fill: string
10
+ fill?: string
11
11
  realToCanvasMat: Matrix
12
12
  rotation?: number
13
+ stroke?: string
14
+ strokeWidth?: number
13
15
  }
14
16
 
15
17
  export function drawPill(params: DrawPillParams): void {
@@ -21,11 +23,16 @@ export function drawPill(params: DrawPillParams): void {
21
23
  fill,
22
24
  realToCanvasMat,
23
25
  rotation = 0,
26
+ stroke,
27
+ strokeWidth,
24
28
  } = params
25
29
 
26
30
  const [cx, cy] = applyToPoint(realToCanvasMat, [center.x, center.y])
27
31
  const scaledWidth = width * Math.abs(realToCanvasMat.a)
28
32
  const scaledHeight = height * Math.abs(realToCanvasMat.a)
33
+ const scaledStrokeWidth = strokeWidth
34
+ ? strokeWidth * Math.abs(realToCanvasMat.a)
35
+ : undefined
29
36
 
30
37
  ctx.save()
31
38
  ctx.translate(cx, cy)
@@ -62,7 +69,17 @@ export function drawPill(params: DrawPillParams): void {
62
69
  }
63
70
 
64
71
  ctx.closePath()
65
- ctx.fillStyle = fill
66
- ctx.fill()
72
+
73
+ if (fill) {
74
+ ctx.fillStyle = fill
75
+ ctx.fill()
76
+ }
77
+
78
+ if (stroke && scaledStrokeWidth) {
79
+ ctx.strokeStyle = stroke
80
+ ctx.lineWidth = scaledStrokeWidth
81
+ ctx.stroke()
82
+ }
83
+
67
84
  ctx.restore()
68
85
  }
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.27",
4
+ "version": "0.0.29",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -17,7 +17,7 @@
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.335",
20
+ "circuit-json": "^0.0.342",
21
21
  "circuit-json-to-connectivity-map": "^0.0.23",
22
22
  "circuit-to-svg": "^0.0.297",
23
23
  "looks-same": "^10.0.1",
@@ -0,0 +1,111 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { PcbPlatedHole } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("draw hole with polygon pad - circle hole", async () => {
7
+ const canvas = createCanvas(100, 100)
8
+ const ctx = canvas.getContext("2d")
9
+ const drawer = new CircuitToCanvasDrawer(ctx)
10
+
11
+ ctx.fillStyle = "#1a1a1a"
12
+ ctx.fillRect(0, 0, 100, 100)
13
+
14
+ const hole: PcbPlatedHole = {
15
+ type: "pcb_plated_hole",
16
+ pcb_plated_hole_id: "hole1",
17
+ shape: "hole_with_polygon_pad",
18
+ x: 50,
19
+ y: 50,
20
+ hole_shape: "circle",
21
+ hole_diameter: 15,
22
+ pad_outline: [
23
+ { x: -20, y: -20 },
24
+ { x: 20, y: -20 },
25
+ { x: 25, y: 0 },
26
+ { x: 20, y: 20 },
27
+ { x: -20, y: 20 },
28
+ { x: -25, y: 0 },
29
+ ],
30
+ layers: ["top", "bottom"],
31
+ } as any
32
+
33
+ drawer.drawElements([hole])
34
+
35
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
36
+ import.meta.path,
37
+ "hole-with-polygon-pad-circle",
38
+ )
39
+ })
40
+
41
+ test("draw hole with polygon pad - pill hole with offset", async () => {
42
+ const canvas = createCanvas(100, 100)
43
+ const ctx = canvas.getContext("2d")
44
+ const drawer = new CircuitToCanvasDrawer(ctx)
45
+
46
+ ctx.fillStyle = "#1a1a1a"
47
+ ctx.fillRect(0, 0, 100, 100)
48
+
49
+ const hole: PcbPlatedHole = {
50
+ type: "pcb_plated_hole",
51
+ pcb_plated_hole_id: "hole1",
52
+ shape: "hole_with_polygon_pad",
53
+ x: 50,
54
+ y: 50,
55
+ hole_shape: "pill",
56
+ hole_width: 12,
57
+ hole_height: 8,
58
+ hole_offset_x: 5,
59
+ hole_offset_y: -3,
60
+ pad_outline: [
61
+ { x: -25, y: -15 },
62
+ { x: 25, y: -15 },
63
+ { x: 30, y: 0 },
64
+ { x: 25, y: 15 },
65
+ { x: -25, y: 15 },
66
+ { x: -30, y: 0 },
67
+ ],
68
+ layers: ["top", "bottom"],
69
+ } as any
70
+
71
+ drawer.drawElements([hole])
72
+
73
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
74
+ import.meta.path,
75
+ "hole-with-polygon-pad-pill-offset",
76
+ )
77
+ })
78
+
79
+ test("draw hole with polygon pad - pill hole", async () => {
80
+ const canvas = createCanvas(100, 100)
81
+ const ctx = canvas.getContext("2d")
82
+ const drawer = new CircuitToCanvasDrawer(ctx)
83
+
84
+ ctx.fillStyle = "#1a1a1a"
85
+ ctx.fillRect(0, 0, 100, 100)
86
+
87
+ const hole: PcbPlatedHole = {
88
+ type: "pcb_plated_hole",
89
+ pcb_plated_hole_id: "hole1",
90
+ shape: "hole_with_polygon_pad",
91
+ x: 50,
92
+ y: 50,
93
+ hole_shape: "rotated_pill",
94
+ hole_width: 15,
95
+ hole_height: 10,
96
+ pad_outline: [
97
+ { x: -20, y: -20 },
98
+ { x: 20, y: -20 },
99
+ { x: 20, y: 20 },
100
+ { x: -20, y: 20 },
101
+ ],
102
+ layers: ["top", "bottom"],
103
+ } as any
104
+
105
+ drawer.drawElements([hole])
106
+
107
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
108
+ import.meta.path,
109
+ "hole-with-polygon-pad-pill",
110
+ )
111
+ })
@@ -0,0 +1,55 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { PcbSilkscreenPill } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("draw silkscreen pill", async () => {
7
+ const canvas = createCanvas(400, 400)
8
+ const ctx = canvas.getContext("2d")
9
+ const drawer = new CircuitToCanvasDrawer(ctx)
10
+
11
+ ctx.fillStyle = "#1a1a1a"
12
+ ctx.fillRect(0, 0, 400, 400)
13
+
14
+ const pill: PcbSilkscreenPill = {
15
+ type: "pcb_silkscreen_pill",
16
+ pcb_silkscreen_pill_id: "pill1",
17
+ pcb_component_id: "component1",
18
+ layer: "top",
19
+ center: { x: 200, y: 200 },
20
+ width: 240,
21
+ height: 80,
22
+ }
23
+
24
+ drawer.drawElements([pill])
25
+
26
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
27
+ import.meta.path,
28
+ )
29
+ })
30
+
31
+ test("draw silkscreen pill bottom layer", async () => {
32
+ const canvas = createCanvas(400, 400)
33
+ const ctx = canvas.getContext("2d")
34
+ const drawer = new CircuitToCanvasDrawer(ctx)
35
+
36
+ ctx.fillStyle = "#1a1a1a"
37
+ ctx.fillRect(0, 0, 400, 400)
38
+
39
+ const pill: PcbSilkscreenPill = {
40
+ type: "pcb_silkscreen_pill",
41
+ pcb_silkscreen_pill_id: "pill1",
42
+ pcb_component_id: "component1",
43
+ layer: "bottom",
44
+ center: { x: 200, y: 200 },
45
+ width: 160,
46
+ height: 120,
47
+ }
48
+
49
+ drawer.drawElements([pill])
50
+
51
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
52
+ import.meta.path,
53
+ "silkscreen-pill-bottom",
54
+ )
55
+ })