circuit-to-canvas 0.0.3 → 0.0.5

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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +82 -3
  3. package/dist/index.js +357 -4
  4. package/lib/drawer/CircuitToCanvasDrawer.ts +55 -0
  5. package/lib/drawer/elements/index.ts +25 -0
  6. package/lib/drawer/elements/pcb-copper-text.ts +99 -0
  7. package/lib/drawer/elements/pcb-fabrication-note-path.ts +41 -0
  8. package/lib/drawer/elements/pcb-fabrication-note-rect.ts +39 -0
  9. package/lib/drawer/elements/pcb-fabrication-note-text.ts +42 -0
  10. package/lib/drawer/elements/pcb-note-rect.ts +37 -0
  11. package/lib/drawer/shapes/index.ts +9 -0
  12. package/lib/drawer/shapes/rect.ts +28 -3
  13. package/lib/drawer/shapes/text.ts +218 -0
  14. package/lib/drawer/types.ts +8 -0
  15. package/package.json +3 -1
  16. package/tests/elements/__snapshots__/pcb-copper-text-knockout.snap.png +0 -0
  17. package/tests/elements/__snapshots__/pcb-copper-text.snap.png +0 -0
  18. package/tests/elements/__snapshots__/pcb-fabrication-note-path-custom-color.snap.png +0 -0
  19. package/tests/elements/__snapshots__/pcb-fabrication-note-path-thick-stroke.snap.png +0 -0
  20. package/tests/elements/__snapshots__/pcb-fabrication-note-path.snap.png +0 -0
  21. package/tests/elements/__snapshots__/pcb-fabrication-note-rect-all-features.snap.png +0 -0
  22. package/tests/elements/__snapshots__/pcb-fabrication-note-rect-corner-radius.snap.png +0 -0
  23. package/tests/elements/__snapshots__/pcb-fabrication-note-rect-custom-color.snap.png +0 -0
  24. package/tests/elements/__snapshots__/pcb-fabrication-note-rect-dashed.snap.png +0 -0
  25. package/tests/elements/__snapshots__/pcb-fabrication-note-rect-default-color.snap.png +0 -0
  26. package/tests/elements/__snapshots__/pcb-fabrication-note-text-rgba-color.snap.png +0 -0
  27. package/tests/elements/__snapshots__/pcb-fabrication-note-text-small.snap.png +0 -0
  28. package/tests/elements/__snapshots__/pcb-note-rect-all-features.snap.png +0 -0
  29. package/tests/elements/__snapshots__/pcb-note-rect-dashed-stroke.snap.png +0 -0
  30. package/tests/elements/__snapshots__/pcb-note-rect-filled-no-stroke.snap.png +0 -0
  31. package/tests/elements/pcb-copper-text.test.ts +102 -0
  32. package/tests/elements/pcb-fabrication-note-path-custom-color.test.ts +37 -0
  33. package/tests/elements/pcb-fabrication-note-path-thick-stroke.test.ts +37 -0
  34. package/tests/elements/pcb-fabrication-note-path.test.ts +36 -0
  35. package/tests/elements/pcb-fabrication-note-rect-all-features.test.ts +35 -0
  36. package/tests/elements/pcb-fabrication-note-rect-corner-radius.test.ts +33 -0
  37. package/tests/elements/pcb-fabrication-note-rect-custom-color.test.ts +32 -0
  38. package/tests/elements/pcb-fabrication-note-rect-dashed.test.ts +33 -0
  39. package/tests/elements/pcb-fabrication-note-rect-default-color.test.ts +32 -0
  40. package/tests/elements/pcb-fabrication-note-text-rgba-color.test.ts +34 -0
  41. package/tests/elements/pcb-fabrication-note-text-small.test.ts +33 -0
  42. package/tests/elements/pcb-note-rect-all-features.test.ts +38 -0
  43. package/tests/elements/pcb-note-rect-dashed-stroke.test.ts +32 -0
  44. package/tests/elements/pcb-note-rect-filled-no-stroke.test.ts +32 -0
@@ -0,0 +1,99 @@
1
+ import type { PcbCopperText } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import { applyToPoint } from "transformation-matrix"
4
+ import type { PcbColorMap, CanvasContext } from "../types"
5
+ import {
6
+ getAlphabetLayout,
7
+ strokeAlphabetText,
8
+ getTextStartPosition,
9
+ type AnchorAlignment,
10
+ } from "../shapes/text"
11
+
12
+ export interface DrawPcbCopperTextParams {
13
+ ctx: CanvasContext
14
+ text: PcbCopperText
15
+ transform: Matrix
16
+ colorMap: PcbColorMap
17
+ }
18
+
19
+ const DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 }
20
+
21
+ function layerToCopperColor(layer: string, colorMap: PcbColorMap): string {
22
+ return (
23
+ colorMap.copper[layer as keyof typeof colorMap.copper] ??
24
+ colorMap.copper.top
25
+ )
26
+ }
27
+
28
+ function mapAnchorAlignment(alignment?: string): AnchorAlignment {
29
+ if (!alignment) return "center"
30
+ if (alignment.includes("left")) return "left"
31
+ if (alignment.includes("right")) return "right"
32
+ return "center"
33
+ }
34
+
35
+ export function drawPcbCopperText(params: DrawPcbCopperTextParams): void {
36
+ const { ctx, text, transform, colorMap } = params
37
+
38
+ const content = text.text ?? ""
39
+ if (!content) return
40
+
41
+ const [x, y] = applyToPoint(transform, [
42
+ text.anchor_position.x,
43
+ text.anchor_position.y,
44
+ ])
45
+ const scale = Math.abs(transform.a)
46
+ const fontSize = (text.font_size ?? 1) * scale
47
+ const rotation = text.ccw_rotation ?? 0
48
+ const padding = {
49
+ ...DEFAULT_PADDING,
50
+ ...text.knockout_padding,
51
+ }
52
+ const textColor = layerToCopperColor(text.layer, colorMap)
53
+ const layout = getAlphabetLayout(content, fontSize)
54
+ const totalWidth = layout.width + layout.strokeWidth
55
+ const totalHeight = layout.height + layout.strokeWidth
56
+ const alignment = mapAnchorAlignment(text.anchor_alignment)
57
+ const startPos = getTextStartPosition(alignment, layout)
58
+ // Copper text always centers vertically (startY=0), uses startPos.x for horizontal alignment
59
+ const startX = startPos.x
60
+ const startY = 0 // Centers vertically at y=0 (shared function calculates yOffset = startY + height/2)
61
+
62
+ ctx.save()
63
+ ctx.translate(x, y)
64
+ if (text.is_mirrored) ctx.scale(-1, 1)
65
+ if (rotation !== 0) ctx.rotate(-rotation * (Math.PI / 180))
66
+
67
+ ctx.lineWidth = layout.strokeWidth
68
+ ctx.lineCap = "round"
69
+ ctx.lineJoin = "round"
70
+
71
+ if (text.is_knockout) {
72
+ const paddingLeft = padding.left * scale
73
+ const paddingRight = padding.right * scale
74
+ const paddingTop = padding.top * scale
75
+ const paddingBottom = padding.bottom * scale
76
+ const xOffset = startX - paddingLeft
77
+ const yOffset = -(layout.height / 2) - layout.strokeWidth / 2 - paddingTop
78
+ const knockoutWidth = totalWidth + paddingLeft + paddingRight
79
+ const knockoutHeight = totalHeight + paddingTop + paddingBottom
80
+
81
+ ctx.fillStyle = textColor
82
+ ctx.fillRect(xOffset, yOffset, knockoutWidth, knockoutHeight)
83
+
84
+ const previousCompositeOperation = ctx.globalCompositeOperation
85
+ ctx.globalCompositeOperation = "destination-out"
86
+ ctx.fillStyle = "rgba(0,0,0,1)"
87
+ ctx.strokeStyle = "rgba(0,0,0,1)"
88
+ strokeAlphabetText(ctx, content, layout, startX, startY)
89
+ if (previousCompositeOperation) {
90
+ ctx.globalCompositeOperation = previousCompositeOperation
91
+ } else {
92
+ ctx.globalCompositeOperation = "source-over"
93
+ }
94
+ } else {
95
+ ctx.strokeStyle = textColor
96
+ strokeAlphabetText(ctx, content, layout, startX, startY)
97
+ }
98
+ ctx.restore()
99
+ }
@@ -0,0 +1,41 @@
1
+ import type { PcbFabricationNotePath } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawLine } from "../shapes/line"
5
+
6
+ export interface DrawPcbFabricationNotePathParams {
7
+ ctx: CanvasContext
8
+ path: PcbFabricationNotePath
9
+ transform: Matrix
10
+ colorMap: PcbColorMap
11
+ }
12
+
13
+ export function drawPcbFabricationNotePath(
14
+ params: DrawPcbFabricationNotePathParams,
15
+ ): void {
16
+ const { ctx, path, transform, colorMap } = params
17
+
18
+ // Use the color from the path if provided, otherwise use a default color
19
+ // Fabrication notes are typically shown in a distinct color
20
+ const defaultColor = "rgba(255,255,255,0.5)" // White color for fabrication notes
21
+ const color = path.color ?? defaultColor
22
+
23
+ if (!path.route || path.route.length < 2) return
24
+
25
+ // Draw each segment of the path
26
+ for (let i = 0; i < path.route.length - 1; i++) {
27
+ const start = path.route[i]
28
+ const end = path.route[i + 1]
29
+
30
+ if (!start || !end) continue
31
+
32
+ drawLine({
33
+ ctx,
34
+ start: { x: start.x, y: start.y },
35
+ end: { x: end.x, y: end.y },
36
+ strokeWidth: path.stroke_width ?? 0.1,
37
+ stroke: color,
38
+ transform,
39
+ })
40
+ }
41
+ }
@@ -0,0 +1,39 @@
1
+ import type { PcbFabricationNoteRect } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawRect } from "../shapes/rect"
5
+
6
+ export interface DrawPcbFabricationNoteRectParams {
7
+ ctx: CanvasContext
8
+ rect: PcbFabricationNoteRect
9
+ transform: Matrix
10
+ colorMap: PcbColorMap
11
+ }
12
+
13
+ export function drawPcbFabricationNoteRect(
14
+ params: DrawPcbFabricationNoteRectParams,
15
+ ): void {
16
+ const { ctx, rect, transform, colorMap } = params
17
+
18
+ // Use the color from the rect if provided, otherwise use a default color
19
+ // Fabrication notes are typically shown in a distinct color
20
+ const defaultColor = "rgba(255,255,255,0.5)" // White color for fabrication notes
21
+ const color = rect.color ?? defaultColor
22
+
23
+ const isFilled = rect.is_filled ?? false
24
+ const hasStroke = rect.has_stroke ?? true
25
+ const isStrokeDashed = rect.is_stroke_dashed ?? false
26
+
27
+ drawRect({
28
+ ctx,
29
+ center: rect.center,
30
+ width: rect.width,
31
+ height: rect.height,
32
+ fill: isFilled ? color : undefined,
33
+ stroke: hasStroke ? color : undefined,
34
+ strokeWidth: hasStroke ? rect.stroke_width : undefined,
35
+ borderRadius: rect.corner_radius,
36
+ transform,
37
+ isStrokeDashed,
38
+ })
39
+ }
@@ -0,0 +1,42 @@
1
+ import type { PcbFabricationNoteText } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawText } from "../shapes/text"
5
+
6
+ export interface DrawPcbFabricationNoteTextParams {
7
+ ctx: CanvasContext
8
+ text: PcbFabricationNoteText
9
+ transform: Matrix
10
+ colorMap: PcbColorMap
11
+ }
12
+
13
+ const DEFAULT_FABRICATION_NOTE_COLOR = "rgba(255,255,255,0.5)"
14
+
15
+ function layerToColor(layer: string, colorMap: PcbColorMap): string {
16
+ // For fabrication notes, we use a default color
17
+ // Could be extended to support per-layer colors in the future
18
+ return DEFAULT_FABRICATION_NOTE_COLOR
19
+ }
20
+
21
+ export function drawPcbFabricationNoteText(
22
+ params: DrawPcbFabricationNoteTextParams,
23
+ ): void {
24
+ const { ctx, text, transform, colorMap } = params
25
+
26
+ const defaultColor = layerToColor(text.layer, colorMap)
27
+ const color = text.color ?? defaultColor
28
+ const fontSize = text.font_size
29
+
30
+ // Use @tscircuit/alphabet to draw text
31
+ // Pass real-world coordinates and let drawText apply the transform
32
+ drawText({
33
+ ctx,
34
+ text: text.text,
35
+ x: text.anchor_position.x,
36
+ y: text.anchor_position.y,
37
+ fontSize,
38
+ color,
39
+ transform,
40
+ anchorAlignment: text.anchor_alignment,
41
+ })
42
+ }
@@ -0,0 +1,37 @@
1
+ import type { PcbNoteRect } from "circuit-json"
2
+ import type { Matrix } from "transformation-matrix"
3
+ import type { PcbColorMap, CanvasContext } from "../types"
4
+ import { drawRect } from "../shapes/rect"
5
+
6
+ export interface DrawPcbNoteRectParams {
7
+ ctx: CanvasContext
8
+ rect: PcbNoteRect
9
+ transform: Matrix
10
+ colorMap: PcbColorMap
11
+ }
12
+
13
+ export function drawPcbNoteRect(params: DrawPcbNoteRectParams): void {
14
+ const { ctx, rect, transform, colorMap } = params
15
+
16
+ // Use the color from the rect if provided, otherwise use a default color
17
+ // Notes are typically shown in a distinct color
18
+ const defaultColor = "rgb(89, 148, 220)" // White color for notes
19
+ const color = rect.color ?? defaultColor
20
+
21
+ const isFilled = rect.is_filled ?? false
22
+ const hasStroke = rect.has_stroke ?? true
23
+ const isStrokeDashed = rect.is_stroke_dashed ?? false
24
+
25
+ drawRect({
26
+ ctx,
27
+ center: rect.center,
28
+ width: rect.width,
29
+ height: rect.height,
30
+ fill: isFilled ? color : undefined,
31
+ stroke: hasStroke ? color : undefined,
32
+ strokeWidth: hasStroke ? rect.stroke_width : undefined,
33
+ borderRadius: rect.corner_radius,
34
+ transform,
35
+ isStrokeDashed,
36
+ })
37
+ }
@@ -5,3 +5,12 @@ export { drawPill, type DrawPillParams } from "./pill"
5
5
  export { drawPolygon, type DrawPolygonParams } from "./polygon"
6
6
  export { drawLine, type DrawLineParams } from "./line"
7
7
  export { drawPath, type DrawPathParams } from "./path"
8
+ export {
9
+ drawText,
10
+ type DrawTextParams,
11
+ getAlphabetLayout,
12
+ strokeAlphabetText,
13
+ getTextStartPosition,
14
+ type AlphabetLayout,
15
+ type AnchorAlignment,
16
+ } from "./text"
@@ -7,10 +7,13 @@ export interface DrawRectParams {
7
7
  center: { x: number; y: number }
8
8
  width: number
9
9
  height: number
10
- fill: string
10
+ fill?: string
11
11
  transform: Matrix
12
12
  borderRadius?: number
13
13
  rotation?: number
14
+ stroke?: string
15
+ strokeWidth?: number
16
+ isStrokeDashed?: boolean
14
17
  }
15
18
 
16
19
  export function drawRect(params: DrawRectParams): void {
@@ -23,12 +26,18 @@ export function drawRect(params: DrawRectParams): void {
23
26
  transform,
24
27
  borderRadius = 0,
25
28
  rotation = 0,
29
+ stroke,
30
+ strokeWidth,
31
+ isStrokeDashed = false,
26
32
  } = params
27
33
 
28
34
  const [cx, cy] = applyToPoint(transform, [center.x, center.y])
29
35
  const scaledWidth = width * Math.abs(transform.a)
30
36
  const scaledHeight = height * Math.abs(transform.a)
31
37
  const scaledRadius = borderRadius * Math.abs(transform.a)
38
+ const scaledStrokeWidth = strokeWidth
39
+ ? strokeWidth * Math.abs(transform.a)
40
+ : undefined
32
41
 
33
42
  ctx.save()
34
43
  ctx.translate(cx, cy)
@@ -37,6 +46,13 @@ export function drawRect(params: DrawRectParams): void {
37
46
  ctx.rotate(-rotation * (Math.PI / 180))
38
47
  }
39
48
 
49
+ // Set up dashed line if needed
50
+ if (isStrokeDashed && scaledStrokeWidth) {
51
+ ctx.setLineDash([scaledStrokeWidth * 2, scaledStrokeWidth * 2])
52
+ } else {
53
+ ctx.setLineDash([])
54
+ }
55
+
40
56
  ctx.beginPath()
41
57
 
42
58
  if (scaledRadius > 0) {
@@ -63,7 +79,16 @@ export function drawRect(params: DrawRectParams): void {
63
79
  ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight)
64
80
  }
65
81
 
66
- ctx.fillStyle = fill
67
- ctx.fill()
82
+ if (fill) {
83
+ ctx.fillStyle = fill
84
+ ctx.fill()
85
+ }
86
+
87
+ if (stroke && scaledStrokeWidth) {
88
+ ctx.strokeStyle = stroke
89
+ ctx.lineWidth = scaledStrokeWidth
90
+ ctx.stroke()
91
+ }
92
+
68
93
  ctx.restore()
69
94
  }
@@ -0,0 +1,218 @@
1
+ import { lineAlphabet } from "@tscircuit/alphabet"
2
+ import type { Matrix } from "transformation-matrix"
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
+ const CURVED_GLYPHS = new Set(["O", "o", "0"])
11
+
12
+ export type AlphabetLayout = {
13
+ width: number
14
+ height: number
15
+ glyphWidth: number
16
+ letterSpacing: number
17
+ spaceWidth: number
18
+ strokeWidth: number
19
+ }
20
+
21
+ export function getAlphabetLayout(
22
+ text: string,
23
+ fontSize: number,
24
+ ): AlphabetLayout {
25
+ const glyphWidth = fontSize * GLYPH_WIDTH_RATIO
26
+ const letterSpacing = glyphWidth * LETTER_SPACING_RATIO
27
+ const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO
28
+ const characters = Array.from(text)
29
+
30
+ let width = 0
31
+ characters.forEach((char, index) => {
32
+ const advance = char === " " ? spaceWidth : glyphWidth
33
+ width += advance
34
+ if (index < characters.length - 1) width += letterSpacing
35
+ })
36
+
37
+ const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35)
38
+
39
+ return {
40
+ width,
41
+ height: fontSize,
42
+ glyphWidth,
43
+ letterSpacing,
44
+ spaceWidth,
45
+ strokeWidth,
46
+ }
47
+ }
48
+
49
+ const getGlyphLines = (char: string) =>
50
+ lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()]
51
+
52
+ export type AnchorAlignment =
53
+ | "center"
54
+ | "top_left"
55
+ | "top_right"
56
+ | "bottom_left"
57
+ | "bottom_right"
58
+ | "left"
59
+ | "right"
60
+ | "top"
61
+ | "bottom"
62
+
63
+ export function getTextStartPosition(
64
+ alignment: AnchorAlignment,
65
+ layout: AlphabetLayout,
66
+ ): { x: number; y: number } {
67
+ const totalWidth = layout.width + layout.strokeWidth
68
+ const totalHeight = layout.height + layout.strokeWidth
69
+
70
+ let x = 0
71
+ let y = 0
72
+
73
+ // Horizontal alignment
74
+ if (alignment === "center") {
75
+ x = -totalWidth / 2
76
+ } else if (
77
+ alignment === "top_left" ||
78
+ alignment === "bottom_left" ||
79
+ alignment === "left"
80
+ ) {
81
+ x = 0
82
+ } else if (
83
+ alignment === "top_right" ||
84
+ alignment === "bottom_right" ||
85
+ alignment === "right"
86
+ ) {
87
+ x = -totalWidth
88
+ }
89
+
90
+ // Vertical alignment
91
+ if (alignment === "center") {
92
+ y = -totalHeight / 2
93
+ } else if (
94
+ alignment === "top_left" ||
95
+ alignment === "top_right" ||
96
+ alignment === "top"
97
+ ) {
98
+ y = 0
99
+ } else if (
100
+ alignment === "bottom_left" ||
101
+ alignment === "bottom_right" ||
102
+ alignment === "bottom"
103
+ ) {
104
+ y = -totalHeight
105
+ }
106
+
107
+ return { x, y }
108
+ }
109
+
110
+ export function strokeAlphabetText(
111
+ ctx: CanvasContext,
112
+ text: string,
113
+ layout: AlphabetLayout,
114
+ startX: number,
115
+ startY: number,
116
+ ): void {
117
+ const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout
118
+ const yOffset = startY + height / 2
119
+ const characters = Array.from(text)
120
+ let cursor = startX + strokeWidth / 2
121
+
122
+ characters.forEach((char, index) => {
123
+ const lines = getGlyphLines(char)
124
+ const advance = char === " " ? spaceWidth : glyphWidth
125
+
126
+ if (CURVED_GLYPHS.has(char)) {
127
+ const radiusX = Math.max(glyphWidth / 2 - strokeWidth / 2, strokeWidth)
128
+ const radiusY = Math.max(height / 2 - strokeWidth / 2, strokeWidth)
129
+ const centerY = yOffset - height / 2
130
+ ctx.beginPath()
131
+ ctx.ellipse(
132
+ cursor + glyphWidth / 2,
133
+ centerY,
134
+ radiusX,
135
+ radiusY,
136
+ 0,
137
+ 0,
138
+ Math.PI * 2,
139
+ )
140
+ ctx.stroke()
141
+ } else if (lines?.length) {
142
+ ctx.beginPath()
143
+ for (const line of lines) {
144
+ const x1 = cursor + line.x1 * glyphWidth
145
+ const y1 = yOffset - line.y1 * height
146
+ const x2 = cursor + line.x2 * glyphWidth
147
+ const y2 = yOffset - line.y2 * height
148
+ ctx.moveTo(x1, y1)
149
+ ctx.lineTo(x2, y2)
150
+ }
151
+ ctx.stroke()
152
+ }
153
+
154
+ // Move cursor by the character width
155
+ cursor += advance
156
+ // Add letter spacing after each character except the last one
157
+ // This spacing will be before the next character, creating visible gaps
158
+ if (index < characters.length - 1) {
159
+ cursor += letterSpacing
160
+ }
161
+ })
162
+ }
163
+
164
+ export interface DrawTextParams {
165
+ ctx: CanvasContext
166
+ text: string
167
+ x: number
168
+ y: number
169
+ fontSize: number
170
+ color: string
171
+ transform: Matrix
172
+ anchorAlignment: AnchorAlignment
173
+ rotation?: number
174
+ }
175
+
176
+ export function drawText(params: DrawTextParams): void {
177
+ const {
178
+ ctx,
179
+ text,
180
+ x,
181
+ y,
182
+ fontSize,
183
+ color,
184
+ transform,
185
+ anchorAlignment,
186
+ rotation = 0,
187
+ } = params
188
+
189
+ if (!text) return
190
+
191
+ const [transformedX, transformedY] = applyToPoint(transform, [x, y])
192
+ const scale = Math.abs(transform.a)
193
+ const scaledFontSize = fontSize * scale
194
+ const layout = getAlphabetLayout(text, scaledFontSize)
195
+ const startPos = getTextStartPosition(anchorAlignment, layout)
196
+
197
+ ctx.save()
198
+ ctx.translate(transformedX, transformedY)
199
+
200
+ if (rotation !== 0) {
201
+ ctx.rotate(-rotation * (Math.PI / 180))
202
+ }
203
+
204
+ ctx.lineWidth = layout.strokeWidth
205
+ ctx.lineCap = "round"
206
+ ctx.lineJoin = "round"
207
+ ctx.strokeStyle = color
208
+
209
+ strokeAlphabetText(
210
+ ctx,
211
+ text,
212
+ layout,
213
+ startPos.x,
214
+ startPos.y + layout.strokeWidth / 2,
215
+ )
216
+
217
+ ctx.restore()
218
+ }
@@ -34,13 +34,21 @@ export interface CanvasContext {
34
34
  translate(x: number, y: number): void
35
35
  rotate(angle: number): void
36
36
  scale(x: number, y: number): void
37
+ globalCompositeOperation?: string
37
38
  fillStyle: string | CanvasGradient | CanvasPattern
38
39
  strokeStyle: string | CanvasGradient | CanvasPattern
39
40
  lineWidth: number
40
41
  lineCap: "butt" | "round" | "square"
41
42
  lineJoin: "bevel" | "round" | "miter"
43
+ setLineDash(segments: number[]): void
42
44
  canvas: { width: number; height: number }
43
45
  fillText(text: string, x: number, y: number): void
46
+ fillRect(x: number, y: number, width: number, height: number): void
47
+ measureText?: (text: string) => {
48
+ width: number
49
+ actualBoundingBoxAscent?: number
50
+ actualBoundingBoxDescent?: number
51
+ }
44
52
  font: string
45
53
  textAlign: "start" | "end" | "left" | "right" | "center"
46
54
  textBaseline:
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.3",
4
+ "version": "0.0.5",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "devDependencies": {
12
12
  "@biomejs/biome": "^2.3.8",
13
+ "@tscircuit/alphabet": "^0.0.8",
13
14
  "@types/bun": "latest",
14
15
  "bun-match-svg": "^0.0.14",
15
16
  "canvas": "^3.2.0",
@@ -17,6 +18,7 @@
17
18
  "tsup": "^8.5.1"
18
19
  },
19
20
  "peerDependencies": {
21
+ "@tscircuit/alphabet": "*",
20
22
  "typescript": "^5"
21
23
  },
22
24
  "dependencies": {