circuit-to-canvas 0.0.16 → 0.0.18

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 (26) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +9 -7
  3. package/dist/index.js +16 -10
  4. package/lib/drawer/CircuitToCanvasDrawer.ts +3 -2
  5. package/lib/drawer/elements/pcb-copper-text.ts +2 -2
  6. package/lib/drawer/shapes/text/getAlphabetLayout.ts +41 -0
  7. package/lib/drawer/shapes/text/getTextStartPosition.ts +53 -0
  8. package/lib/drawer/shapes/text/index.ts +3 -0
  9. package/lib/drawer/shapes/{text.ts → text/text.ts} +5 -104
  10. package/package.json +2 -1
  11. package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
  12. package/tests/board-snapshot/usb-c-flashlight-board.test.ts +15 -0
  13. package/tests/board-snapshot/usb-c-flashlight.json +2456 -0
  14. package/tests/elements/__snapshots__/fabrication-note-text-descenders.snap.png +0 -0
  15. package/tests/elements/__snapshots__/fabrication-note-text-full-charset.snap.png +0 -0
  16. package/tests/elements/__snapshots__/pcb-fabrication-note-text-rgba-color.snap.png +0 -0
  17. package/tests/elements/__snapshots__/pcb-fabrication-note-text-small.snap.png +0 -0
  18. package/tests/elements/__snapshots__/pcb-note-text-anchor-alignment.snap.png +0 -0
  19. package/tests/elements/__snapshots__/pcb-note-text-custom-color.snap.png +0 -0
  20. package/tests/elements/__snapshots__/pcb-note-text-small.snap.png +0 -0
  21. package/tests/fixtures/assets/label-circuit-to-canvas.png +0 -0
  22. package/tests/fixtures/assets/label-circuit-to-svg.png +0 -0
  23. package/tests/fixtures/getStackedPngSvgComparison.ts +62 -0
  24. package/tests/fixtures/stackPngsVertically.ts +82 -0
  25. package/tests/shapes/__snapshots__/oval.snap.png +0 -0
  26. package/tsconfig.json +1 -0
package/README.md CHANGED
@@ -56,7 +56,7 @@ This checklist tracks PCB drawing features from [circuit-to-svg](https://github.
56
56
 
57
57
  ### Copper Text
58
58
 
59
- - [ ] `pcb_copper_text` - Text rendered on copper layers (supports knockout mode, mirroring)
59
+ - [x] `pcb_copper_text` - Text rendered on copper layers (supports knockout mode, mirroring)
60
60
 
61
61
  ### Error Visualization
62
62
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnyCircuitElement, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText } from 'circuit-json';
1
+ import { AnyCircuitElement, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText } from 'circuit-json';
2
2
  import { Matrix } from 'transformation-matrix';
3
3
 
4
4
  /**
@@ -211,11 +211,7 @@ type AlphabetLayout = {
211
211
  strokeWidth: number;
212
212
  };
213
213
  declare function getAlphabetLayout(text: string, fontSize: number): AlphabetLayout;
214
- type AnchorAlignment = "center" | "top_left" | "top_right" | "bottom_left" | "bottom_right" | "left" | "right" | "top" | "bottom";
215
- declare function getTextStartPosition(alignment: AnchorAlignment, layout: AlphabetLayout): {
216
- x: number;
217
- y: number;
218
- };
214
+
219
215
  declare function strokeAlphabetText(ctx: CanvasContext, text: string, layout: AlphabetLayout, startX: number, startY: number): void;
220
216
  interface DrawTextParams {
221
217
  ctx: CanvasContext;
@@ -225,11 +221,17 @@ interface DrawTextParams {
225
221
  fontSize: number;
226
222
  color: string;
227
223
  realToCanvasMat: Matrix;
228
- anchorAlignment: AnchorAlignment;
224
+ anchorAlignment: NinePointAnchor;
229
225
  rotation?: number;
230
226
  }
231
227
  declare function drawText(params: DrawTextParams): void;
232
228
 
229
+ type AnchorAlignment = NinePointAnchor;
230
+ declare function getTextStartPosition(alignment: NinePointAnchor, layout: AlphabetLayout): {
231
+ x: number;
232
+ y: number;
233
+ };
234
+
233
235
  interface DrawPcbPlatedHoleParams {
234
236
  ctx: CanvasContext;
235
237
  hole: PcbPlatedHole;
package/dist/index.js CHANGED
@@ -831,9 +831,11 @@ function drawPcbCopperPour(params) {
831
831
  // lib/drawer/elements/pcb-copper-text.ts
832
832
  import { applyToPoint as applyToPoint11 } from "transformation-matrix";
833
833
 
834
- // lib/drawer/shapes/text.ts
834
+ // lib/drawer/shapes/text/text.ts
835
835
  import { lineAlphabet } from "@tscircuit/alphabet";
836
836
  import { applyToPoint as applyToPoint10 } from "transformation-matrix";
837
+
838
+ // lib/drawer/shapes/text/getAlphabetLayout.ts
837
839
  var GLYPH_WIDTH_RATIO = 0.62;
838
840
  var LETTER_SPACING_RATIO = 0.3;
839
841
  var SPACE_WIDTH_RATIO = 1;
@@ -859,7 +861,8 @@ function getAlphabetLayout(text, fontSize) {
859
861
  strokeWidth
860
862
  };
861
863
  }
862
- var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
864
+
865
+ // lib/drawer/shapes/text/getTextStartPosition.ts
863
866
  function getTextStartPosition(alignment, layout) {
864
867
  const totalWidth = layout.width + layout.strokeWidth;
865
868
  const totalHeight = layout.height + layout.strokeWidth;
@@ -867,22 +870,25 @@ function getTextStartPosition(alignment, layout) {
867
870
  let y = 0;
868
871
  if (alignment === "center") {
869
872
  x = -totalWidth / 2;
870
- } else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "left") {
873
+ } else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "center_left") {
871
874
  x = 0;
872
- } else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "right") {
875
+ } else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "center_right") {
873
876
  x = -totalWidth;
874
877
  }
875
878
  if (alignment === "center") {
876
879
  y = -totalHeight / 2;
877
- } else if (alignment === "top_left" || alignment === "top_right" || alignment === "top") {
880
+ } else if (alignment === "top_left" || alignment === "top_right" || alignment === "top_center") {
878
881
  y = 0;
879
- } else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom") {
882
+ } else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom_center") {
880
883
  y = -totalHeight;
881
884
  } else {
882
885
  y = 0;
883
886
  }
884
887
  return { x, y };
885
888
  }
889
+
890
+ // lib/drawer/shapes/text/text.ts
891
+ var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
886
892
  function strokeAlphabetText(ctx, text, layout, startX, startY) {
887
893
  const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout;
888
894
  const topY = startY;
@@ -947,8 +953,8 @@ function layerToCopperColor(layer, colorMap) {
947
953
  }
948
954
  function mapAnchorAlignment2(alignment) {
949
955
  if (!alignment) return "center";
950
- if (alignment.includes("left")) return "left";
951
- if (alignment.includes("right")) return "right";
956
+ if (alignment.includes("left")) return "center_left";
957
+ if (alignment.includes("right")) return "center_right";
952
958
  return "center";
953
959
  }
954
960
  function drawPcbCopperText(params) {
@@ -1224,8 +1230,8 @@ var CircuitToCanvasDrawer = class {
1224
1230
  const offsetY = (canvasHeight - realHeight * uniformScale) / 2;
1225
1231
  this.realToCanvasMat = compose(
1226
1232
  translate(offsetX, offsetY),
1227
- scale(uniformScale, uniformScale),
1228
- translate(-bounds.minX, -bounds.minY)
1233
+ scale(uniformScale, -uniformScale),
1234
+ translate(-bounds.minX, -bounds.maxY)
1229
1235
  );
1230
1236
  }
1231
1237
  drawElements(elements, options = {}) {
@@ -132,10 +132,11 @@ export class CircuitToCanvasDrawer {
132
132
  const offsetX = (canvasWidth - realWidth * uniformScale) / 2
133
133
  const offsetY = (canvasHeight - realHeight * uniformScale) / 2
134
134
 
135
+ // Flip Y axis: PCB uses Y-up, canvas uses Y-down
135
136
  this.realToCanvasMat = compose(
136
137
  translate(offsetX, offsetY),
137
- scale(uniformScale, uniformScale),
138
- translate(-bounds.minX, -bounds.minY),
138
+ scale(uniformScale, -uniformScale),
139
+ translate(-bounds.minX, -bounds.maxY),
139
140
  )
140
141
  }
141
142
 
@@ -27,8 +27,8 @@ function layerToCopperColor(layer: string, colorMap: PcbColorMap): string {
27
27
 
28
28
  function mapAnchorAlignment(alignment?: string): AnchorAlignment {
29
29
  if (!alignment) return "center"
30
- if (alignment.includes("left")) return "left"
31
- if (alignment.includes("right")) return "right"
30
+ if (alignment.includes("left")) return "center_left"
31
+ if (alignment.includes("right")) return "center_right"
32
32
  return "center"
33
33
  }
34
34
 
@@ -0,0 +1,41 @@
1
+ export const GLYPH_WIDTH_RATIO = 0.62
2
+ export const LETTER_SPACING_RATIO = 0.3 // Letter spacing between characters (25% of glyph width)
3
+ export const SPACE_WIDTH_RATIO = 1
4
+ export const STROKE_WIDTH_RATIO = 0.13
5
+
6
+ export type AlphabetLayout = {
7
+ width: number
8
+ height: number
9
+ glyphWidth: number
10
+ letterSpacing: number
11
+ spaceWidth: number
12
+ strokeWidth: number
13
+ }
14
+
15
+ export function getAlphabetLayout(
16
+ text: string,
17
+ fontSize: number,
18
+ ): AlphabetLayout {
19
+ const glyphWidth = fontSize * GLYPH_WIDTH_RATIO
20
+ const letterSpacing = glyphWidth * LETTER_SPACING_RATIO
21
+ const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO
22
+ const characters = Array.from(text)
23
+
24
+ let width = 0
25
+ characters.forEach((char, index) => {
26
+ const advance = char === " " ? spaceWidth : glyphWidth
27
+ width += advance
28
+ if (index < characters.length - 1) width += letterSpacing
29
+ })
30
+
31
+ const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35)
32
+
33
+ return {
34
+ width,
35
+ height: fontSize,
36
+ glyphWidth,
37
+ letterSpacing,
38
+ spaceWidth,
39
+ strokeWidth,
40
+ }
41
+ }
@@ -0,0 +1,53 @@
1
+ import type { NinePointAnchor } from "circuit-json"
2
+ import type { AlphabetLayout } from "./getAlphabetLayout"
3
+
4
+ export type AnchorAlignment = NinePointAnchor
5
+
6
+ export function getTextStartPosition(
7
+ alignment: NinePointAnchor,
8
+ layout: AlphabetLayout,
9
+ ): { x: number; y: number } {
10
+ const totalWidth = layout.width + layout.strokeWidth
11
+ const totalHeight = layout.height + layout.strokeWidth
12
+
13
+ let x = 0
14
+ let y = 0
15
+
16
+ // Horizontal alignment
17
+ if (alignment === "center") {
18
+ x = -totalWidth / 2
19
+ } else if (
20
+ alignment === "top_left" ||
21
+ alignment === "bottom_left" ||
22
+ alignment === "center_left"
23
+ ) {
24
+ x = 0
25
+ } else if (
26
+ alignment === "top_right" ||
27
+ alignment === "bottom_right" ||
28
+ alignment === "center_right"
29
+ ) {
30
+ x = -totalWidth
31
+ }
32
+
33
+ // Vertical alignment
34
+ if (alignment === "center") {
35
+ y = -totalHeight / 2
36
+ } else if (
37
+ alignment === "top_left" ||
38
+ alignment === "top_right" ||
39
+ alignment === "top_center"
40
+ ) {
41
+ y = 0
42
+ } else if (
43
+ alignment === "bottom_left" ||
44
+ alignment === "bottom_right" ||
45
+ alignment === "bottom_center"
46
+ ) {
47
+ y = -totalHeight
48
+ } else {
49
+ y = 0
50
+ }
51
+
52
+ return { x, y }
53
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./text"
2
+ export * from "./getAlphabetLayout"
3
+ export * from "./getTextStartPosition"
@@ -1,113 +1,14 @@
1
1
  import { lineAlphabet } from "@tscircuit/alphabet"
2
2
  import type { Matrix } from "transformation-matrix"
3
3
  import { applyToPoint } from "transformation-matrix"
4
- import type { CanvasContext } from "../types"
5
-
6
- const GLYPH_WIDTH_RATIO = 0.62
7
- const LETTER_SPACING_RATIO = 0.3 // Letter spacing between characters (25% of glyph width)
8
- const SPACE_WIDTH_RATIO = 1
9
- const STROKE_WIDTH_RATIO = 0.13
10
-
11
- export type AlphabetLayout = {
12
- width: number
13
- height: number
14
- glyphWidth: number
15
- letterSpacing: number
16
- spaceWidth: number
17
- strokeWidth: number
18
- }
19
-
20
- export function getAlphabetLayout(
21
- text: string,
22
- fontSize: number,
23
- ): AlphabetLayout {
24
- const glyphWidth = fontSize * GLYPH_WIDTH_RATIO
25
- const letterSpacing = glyphWidth * LETTER_SPACING_RATIO
26
- const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO
27
- const characters = Array.from(text)
28
-
29
- let width = 0
30
- characters.forEach((char, index) => {
31
- const advance = char === " " ? spaceWidth : glyphWidth
32
- width += advance
33
- if (index < characters.length - 1) width += letterSpacing
34
- })
35
-
36
- const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35)
37
-
38
- return {
39
- width,
40
- height: fontSize,
41
- glyphWidth,
42
- letterSpacing,
43
- spaceWidth,
44
- strokeWidth,
45
- }
46
- }
4
+ import type { CanvasContext } from "../../types"
5
+ import type { NinePointAnchor } from "circuit-json"
6
+ import { getAlphabetLayout, type AlphabetLayout } from "./getAlphabetLayout"
7
+ import { getTextStartPosition } from "./getTextStartPosition"
47
8
 
48
9
  const getGlyphLines = (char: string) =>
49
10
  lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()]
50
11
 
51
- export type AnchorAlignment =
52
- | "center"
53
- | "top_left"
54
- | "top_right"
55
- | "bottom_left"
56
- | "bottom_right"
57
- | "left"
58
- | "right"
59
- | "top"
60
- | "bottom"
61
-
62
- export function getTextStartPosition(
63
- alignment: AnchorAlignment,
64
- layout: AlphabetLayout,
65
- ): { x: number; y: number } {
66
- const totalWidth = layout.width + layout.strokeWidth
67
- const totalHeight = layout.height + layout.strokeWidth
68
-
69
- let x = 0
70
- let y = 0
71
-
72
- // Horizontal alignment
73
- if (alignment === "center") {
74
- x = -totalWidth / 2
75
- } else if (
76
- alignment === "top_left" ||
77
- alignment === "bottom_left" ||
78
- alignment === "left"
79
- ) {
80
- x = 0
81
- } else if (
82
- alignment === "top_right" ||
83
- alignment === "bottom_right" ||
84
- alignment === "right"
85
- ) {
86
- x = -totalWidth
87
- }
88
-
89
- // Vertical alignment
90
- if (alignment === "center") {
91
- y = -totalHeight / 2
92
- } else if (
93
- alignment === "top_left" ||
94
- alignment === "top_right" ||
95
- alignment === "top"
96
- ) {
97
- y = 0
98
- } else if (
99
- alignment === "bottom_left" ||
100
- alignment === "bottom_right" ||
101
- alignment === "bottom"
102
- ) {
103
- y = -totalHeight
104
- } else {
105
- y = 0
106
- }
107
-
108
- return { x, y }
109
- }
110
-
111
12
  export function strokeAlphabetText(
112
13
  ctx: CanvasContext,
113
14
  text: string,
@@ -154,7 +55,7 @@ export interface DrawTextParams {
154
55
  fontSize: number
155
56
  color: string
156
57
  realToCanvasMat: Matrix
157
- anchorAlignment: AnchorAlignment
58
+ anchorAlignment: NinePointAnchor
158
59
  rotation?: number
159
60
  }
160
61
 
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.16",
4
+ "version": "0.0.18",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -20,6 +20,7 @@
20
20
  "circuit-json": "^0.0.335",
21
21
  "circuit-json-to-connectivity-map": "^0.0.23",
22
22
  "circuit-to-svg": "^0.0.297",
23
+ "looks-same": "^10.0.1",
23
24
  "schematic-symbols": "^0.0.202",
24
25
  "tsup": "^8.5.1"
25
26
  },
@@ -0,0 +1,15 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { AnyCircuitElement } from "circuit-json"
3
+ import { getStackedPngSvgComparison } from "../fixtures/getStackedPngSvgComparison"
4
+ import usbcFlashlightCircuit from "./usb-c-flashlight.json"
5
+
6
+ const circuitElements = usbcFlashlightCircuit as AnyCircuitElement[]
7
+
8
+ test.skip("USB-C flashlight - comprehensive comparison (circuit-to-canvas vs circuit-to-svg)", async () => {
9
+ const stackedPng = await getStackedPngSvgComparison(circuitElements, {
10
+ width: 400,
11
+ height: 800,
12
+ })
13
+
14
+ await expect(stackedPng).toMatchPngSnapshot(import.meta.path)
15
+ })