circuit-to-canvas 0.0.52 → 0.0.54

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/dist/index.d.ts CHANGED
@@ -253,12 +253,26 @@ interface DrawDimensionLineParams {
253
253
  }
254
254
  declare function drawDimensionLine(params: DrawDimensionLineParams): void;
255
255
 
256
+ type AlphabetLayout = {
257
+ width: number;
258
+ height: number;
259
+ glyphWidth: number;
260
+ letterSpacing: number;
261
+ spaceWidth: number;
262
+ strokeWidth: number;
263
+ lineHeight: number;
264
+ lines: string[];
265
+ lineWidths: number[];
266
+ };
267
+ declare function getAlphabetLayout(text: string, fontSize: number): AlphabetLayout;
268
+
256
269
  interface StrokeAlphabetTextParams {
257
270
  ctx: CanvasContext;
258
271
  text: string;
259
272
  fontSize: number;
260
273
  startX: number;
261
274
  startY: number;
275
+ anchorAlignment?: NinePointAnchor;
262
276
  }
263
277
  declare function strokeAlphabetText(params: StrokeAlphabetTextParams): void;
264
278
  interface DrawTextParams {
@@ -274,16 +288,6 @@ interface DrawTextParams {
274
288
  }
275
289
  declare function drawText(params: DrawTextParams): void;
276
290
 
277
- type AlphabetLayout = {
278
- width: number;
279
- height: number;
280
- glyphWidth: number;
281
- letterSpacing: number;
282
- spaceWidth: number;
283
- strokeWidth: number;
284
- };
285
- declare function getAlphabetLayout(text: string, fontSize: number): AlphabetLayout;
286
-
287
291
  type AnchorAlignment = NinePointAnchor;
288
292
  declare function getTextStartPosition(alignment: NinePointAnchor, layout: AlphabetLayout): {
289
293
  x: number;
package/dist/index.js CHANGED
@@ -400,25 +400,38 @@ var GLYPH_WIDTH_RATIO = 0.62;
400
400
  var LETTER_SPACING_RATIO = 0.3;
401
401
  var SPACE_WIDTH_RATIO = 1;
402
402
  var STROKE_WIDTH_RATIO = 0.13;
403
+ var LINE_HEIGHT_RATIO = 1.2;
403
404
  function getAlphabetLayout(text, fontSize) {
404
405
  const glyphWidth = fontSize * GLYPH_WIDTH_RATIO;
405
406
  const letterSpacing = glyphWidth * LETTER_SPACING_RATIO;
406
407
  const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO;
407
- const characters = Array.from(text);
408
- let width = 0;
409
- characters.forEach((char, index) => {
410
- const advance = char === " " ? spaceWidth : glyphWidth;
411
- width += advance;
412
- if (index < characters.length - 1) width += letterSpacing;
413
- });
414
408
  const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35);
409
+ const lineHeight = fontSize * LINE_HEIGHT_RATIO;
410
+ const lines = text.split("\n");
411
+ const lineWidths = [];
412
+ let maxWidth = 0;
413
+ for (const line of lines) {
414
+ const characters = Array.from(line);
415
+ let lineWidth = 0;
416
+ characters.forEach((char, index) => {
417
+ const advance = char === " " ? spaceWidth : glyphWidth;
418
+ lineWidth += advance;
419
+ if (index < characters.length - 1) lineWidth += letterSpacing;
420
+ });
421
+ lineWidths.push(lineWidth);
422
+ if (lineWidth > maxWidth) maxWidth = lineWidth;
423
+ }
424
+ const totalHeight = lines.length > 1 ? fontSize + (lines.length - 1) * lineHeight : fontSize;
415
425
  return {
416
- width,
417
- height: fontSize,
426
+ width: maxWidth,
427
+ height: totalHeight,
418
428
  glyphWidth,
419
429
  letterSpacing,
420
430
  spaceWidth,
421
- strokeWidth
431
+ strokeWidth,
432
+ lineHeight,
433
+ lines,
434
+ lineWidths
422
435
  };
423
436
  }
424
437
 
@@ -428,44 +441,56 @@ function getTextStartPosition(alignment, layout) {
428
441
  const totalHeight = layout.height + layout.strokeWidth;
429
442
  let x = 0;
430
443
  let y = 0;
431
- if (alignment === "center") {
444
+ if (alignment === "center" || alignment === "top_center" || alignment === "bottom_center") {
432
445
  x = -totalWidth / 2;
433
446
  } else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "center_left") {
434
447
  x = 0;
435
448
  } else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "center_right") {
436
449
  x = -totalWidth;
450
+ } else if (alignment === "top_center" || alignment === "bottom_center") {
451
+ x = -totalWidth / 2;
437
452
  }
438
- if (alignment === "center") {
453
+ if (alignment === "center" || alignment === "center_left" || alignment === "center_right") {
439
454
  y = -totalHeight / 2;
440
455
  } else if (alignment === "top_left" || alignment === "top_right" || alignment === "top_center") {
441
456
  y = 0;
442
457
  } else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom_center") {
443
458
  y = -totalHeight;
444
- } else {
445
- y = 0;
446
459
  }
447
460
  return { x, y };
448
461
  }
462
+ function getLineStartX(params) {
463
+ const { alignment, lineWidth, maxWidth, strokeWidth } = params;
464
+ const totalLineWidth = lineWidth + strokeWidth;
465
+ const totalMaxWidth = maxWidth + strokeWidth;
466
+ if (alignment === "top_left" || alignment === "bottom_left" || alignment === "center_left") {
467
+ return 0;
468
+ }
469
+ if (alignment === "top_right" || alignment === "bottom_right" || alignment === "center_right") {
470
+ return totalMaxWidth - totalLineWidth;
471
+ }
472
+ return (totalMaxWidth - totalLineWidth) / 2;
473
+ }
449
474
 
450
475
  // lib/drawer/shapes/text/text.ts
451
476
  var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
452
- function strokeAlphabetText(params) {
453
- const { ctx, text, fontSize, startX, startY } = params;
454
- const layout = getAlphabetLayout(text, fontSize);
455
- const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout;
477
+ function strokeAlphabetLine(params) {
478
+ const { ctx, line, fontSize, startX, startY, layout } = params;
479
+ const { glyphWidth, letterSpacing, spaceWidth, strokeWidth } = layout;
480
+ const height = fontSize;
456
481
  const topY = startY;
457
- const characters = Array.from(text);
482
+ const characters = Array.from(line);
458
483
  let cursor = startX + strokeWidth / 2;
459
484
  characters.forEach((char, index) => {
460
- const lines = getGlyphLines(char);
485
+ const glyphLines = getGlyphLines(char);
461
486
  const advance = char === " " ? spaceWidth : glyphWidth;
462
- if (lines?.length) {
487
+ if (glyphLines?.length) {
463
488
  ctx.beginPath();
464
- for (const line of lines) {
465
- const x1 = cursor + line.x1 * glyphWidth;
466
- const y1 = topY + (1 - line.y1) * height;
467
- const x2 = cursor + line.x2 * glyphWidth;
468
- const y2 = topY + (1 - line.y2) * height;
489
+ for (const glyph of glyphLines) {
490
+ const x1 = cursor + glyph.x1 * glyphWidth;
491
+ const y1 = topY + (1 - glyph.y1) * height;
492
+ const x2 = cursor + glyph.x2 * glyphWidth;
493
+ const y2 = topY + (1 - glyph.y2) * height;
469
494
  ctx.moveTo(x1, y1);
470
495
  ctx.lineTo(x2, y2);
471
496
  }
@@ -477,6 +502,35 @@ function strokeAlphabetText(params) {
477
502
  }
478
503
  });
479
504
  }
505
+ function strokeAlphabetText(params) {
506
+ const {
507
+ ctx,
508
+ text,
509
+ fontSize,
510
+ startX,
511
+ startY,
512
+ anchorAlignment = "center"
513
+ } = params;
514
+ const layout = getAlphabetLayout(text, fontSize);
515
+ const { lines, lineWidths, lineHeight, width, strokeWidth } = layout;
516
+ lines.forEach((line, lineIndex) => {
517
+ const lineStartX = startX + getLineStartX({
518
+ alignment: anchorAlignment,
519
+ lineWidth: lineWidths[lineIndex],
520
+ maxWidth: width,
521
+ strokeWidth
522
+ });
523
+ const lineStartY = startY + lineIndex * lineHeight;
524
+ strokeAlphabetLine({
525
+ ctx,
526
+ line,
527
+ fontSize,
528
+ startX: lineStartX,
529
+ startY: lineStartY,
530
+ layout
531
+ });
532
+ });
533
+ }
480
534
  function drawText(params) {
481
535
  const {
482
536
  ctx,
@@ -504,12 +558,23 @@ function drawText(params) {
504
558
  ctx.lineCap = "round";
505
559
  ctx.lineJoin = "round";
506
560
  ctx.strokeStyle = color;
507
- strokeAlphabetText({
508
- ctx,
509
- text,
510
- fontSize: scaledFontSize,
511
- startX: startPos.x,
512
- startY: startPos.y
561
+ const { lines, lineWidths, lineHeight, width, strokeWidth } = layout;
562
+ lines.forEach((line, lineIndex) => {
563
+ const lineStartX = startPos.x + getLineStartX({
564
+ alignment: anchorAlignment,
565
+ lineWidth: lineWidths[lineIndex],
566
+ maxWidth: width,
567
+ strokeWidth
568
+ });
569
+ const lineStartY = startPos.y + lineIndex * lineHeight;
570
+ strokeAlphabetLine({
571
+ ctx,
572
+ line,
573
+ fontSize: scaledFontSize,
574
+ startX: lineStartX,
575
+ startY: lineStartY,
576
+ layout
577
+ });
513
578
  });
514
579
  ctx.restore();
515
580
  }
@@ -568,7 +633,8 @@ function drawPcbCopperText(params) {
568
633
  text: content,
569
634
  fontSize,
570
635
  startX: startPos.x,
571
- startY: startPos.y
636
+ startY: startPos.y,
637
+ anchorAlignment: alignment
572
638
  });
573
639
  ctx.restore();
574
640
  }
@@ -1886,12 +1952,23 @@ function drawPcbSilkscreenText(params) {
1886
1952
  ctx.lineCap = "round";
1887
1953
  ctx.lineJoin = "round";
1888
1954
  ctx.strokeStyle = color;
1889
- strokeAlphabetText({
1890
- ctx,
1891
- text: content,
1892
- fontSize,
1893
- startX: startPos.x,
1894
- startY: startPos.y
1955
+ const { lines, lineWidths, lineHeight, width, strokeWidth } = layout;
1956
+ lines.forEach((line, lineIndex) => {
1957
+ const lineStartX = startPos.x + getLineStartX({
1958
+ alignment,
1959
+ lineWidth: lineWidths[lineIndex],
1960
+ maxWidth: width,
1961
+ strokeWidth
1962
+ });
1963
+ const lineStartY = startPos.y + lineIndex * lineHeight;
1964
+ strokeAlphabetLine({
1965
+ ctx,
1966
+ line,
1967
+ fontSize,
1968
+ startX: lineStartX,
1969
+ startY: lineStartY,
1970
+ layout
1971
+ });
1895
1972
  });
1896
1973
  ctx.restore();
1897
1974
  }
@@ -83,6 +83,7 @@ export function drawPcbCopperText(params: DrawPcbCopperTextParams): void {
83
83
  fontSize,
84
84
  startX: startPos.x,
85
85
  startY: startPos.y,
86
+ anchorAlignment: alignment,
86
87
  })
87
88
  ctx.restore()
88
89
  }
@@ -4,8 +4,9 @@ import { applyToPoint } from "transformation-matrix"
4
4
  import type { PcbColorMap, CanvasContext } from "../types"
5
5
  import {
6
6
  getAlphabetLayout,
7
- strokeAlphabetText,
8
7
  getTextStartPosition,
8
+ getLineStartX,
9
+ strokeAlphabetLine,
9
10
  type AnchorAlignment,
10
11
  } from "../shapes/text"
11
12
 
@@ -65,12 +66,27 @@ export function drawPcbSilkscreenText(
65
66
  ctx.lineJoin = "round"
66
67
  ctx.strokeStyle = color
67
68
 
68
- strokeAlphabetText({
69
- ctx,
70
- text: content,
71
- fontSize,
72
- startX: startPos.x,
73
- startY: startPos.y,
69
+ const { lines, lineWidths, lineHeight, width, strokeWidth } = layout
70
+
71
+ lines.forEach((line, lineIndex) => {
72
+ const lineStartX =
73
+ startPos.x +
74
+ getLineStartX({
75
+ alignment,
76
+ lineWidth: lineWidths[lineIndex]!,
77
+ maxWidth: width,
78
+ strokeWidth,
79
+ })
80
+ const lineStartY = startPos.y + lineIndex * lineHeight
81
+
82
+ strokeAlphabetLine({
83
+ ctx,
84
+ line,
85
+ fontSize,
86
+ startX: lineStartX,
87
+ startY: lineStartY,
88
+ layout,
89
+ })
74
90
  })
75
91
 
76
92
  ctx.restore()
@@ -2,6 +2,7 @@ export const GLYPH_WIDTH_RATIO = 0.62
2
2
  export const LETTER_SPACING_RATIO = 0.3 // Letter spacing between characters (25% of glyph width)
3
3
  export const SPACE_WIDTH_RATIO = 1
4
4
  export const STROKE_WIDTH_RATIO = 0.13
5
+ export const LINE_HEIGHT_RATIO = 1.2 // Line height as a ratio of font size
5
6
 
6
7
  export type AlphabetLayout = {
7
8
  width: number
@@ -10,6 +11,9 @@ export type AlphabetLayout = {
10
11
  letterSpacing: number
11
12
  spaceWidth: number
12
13
  strokeWidth: number
14
+ lineHeight: number
15
+ lines: string[]
16
+ lineWidths: number[]
13
17
  }
14
18
 
15
19
  export function getAlphabetLayout(
@@ -19,23 +23,37 @@ export function getAlphabetLayout(
19
23
  const glyphWidth = fontSize * GLYPH_WIDTH_RATIO
20
24
  const letterSpacing = glyphWidth * LETTER_SPACING_RATIO
21
25
  const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO
22
- const characters = Array.from(text)
26
+ const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35)
27
+ const lineHeight = fontSize * LINE_HEIGHT_RATIO
23
28
 
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
- })
29
+ const lines = text.split("\n")
30
+ const lineWidths: number[] = []
30
31
 
31
- const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35)
32
+ let maxWidth = 0
33
+ for (const line of lines) {
34
+ const characters = Array.from(line)
35
+ let lineWidth = 0
36
+ characters.forEach((char, index) => {
37
+ const advance = char === " " ? spaceWidth : glyphWidth
38
+ lineWidth += advance
39
+ if (index < characters.length - 1) lineWidth += letterSpacing
40
+ })
41
+ lineWidths.push(lineWidth)
42
+ if (lineWidth > maxWidth) maxWidth = lineWidth
43
+ }
44
+
45
+ const totalHeight =
46
+ lines.length > 1 ? fontSize + (lines.length - 1) * lineHeight : fontSize
32
47
 
33
48
  return {
34
- width,
35
- height: fontSize,
49
+ width: maxWidth,
50
+ height: totalHeight,
36
51
  glyphWidth,
37
52
  letterSpacing,
38
53
  spaceWidth,
39
54
  strokeWidth,
55
+ lineHeight,
56
+ lines,
57
+ lineWidths,
40
58
  }
41
59
  }
@@ -14,7 +14,11 @@ export function getTextStartPosition(
14
14
  let y = 0
15
15
 
16
16
  // Horizontal alignment
17
- if (alignment === "center") {
17
+ if (
18
+ alignment === "center" ||
19
+ alignment === "top_center" ||
20
+ alignment === "bottom_center"
21
+ ) {
18
22
  x = -totalWidth / 2
19
23
  } else if (
20
24
  alignment === "top_left" ||
@@ -28,10 +32,16 @@ export function getTextStartPosition(
28
32
  alignment === "center_right"
29
33
  ) {
30
34
  x = -totalWidth
35
+ } else if (alignment === "top_center" || alignment === "bottom_center") {
36
+ x = -totalWidth / 2
31
37
  }
32
38
 
33
39
  // Vertical alignment
34
- if (alignment === "center") {
40
+ if (
41
+ alignment === "center" ||
42
+ alignment === "center_left" ||
43
+ alignment === "center_right"
44
+ ) {
35
45
  y = -totalHeight / 2
36
46
  } else if (
37
47
  alignment === "top_left" ||
@@ -45,9 +55,41 @@ export function getTextStartPosition(
45
55
  alignment === "bottom_center"
46
56
  ) {
47
57
  y = -totalHeight
48
- } else {
49
- y = 0
50
58
  }
51
59
 
52
60
  return { x, y }
53
61
  }
62
+
63
+ export interface GetLineStartXParams {
64
+ alignment: NinePointAnchor
65
+ lineWidth: number
66
+ maxWidth: number
67
+ strokeWidth: number
68
+ }
69
+
70
+ export function getLineStartX(params: GetLineStartXParams): number {
71
+ const { alignment, lineWidth, maxWidth, strokeWidth } = params
72
+ const totalLineWidth = lineWidth + strokeWidth
73
+ const totalMaxWidth = maxWidth + strokeWidth
74
+
75
+ // For left-aligned text, lines start at x=0 (relative to the start position)
76
+ if (
77
+ alignment === "top_left" ||
78
+ alignment === "bottom_left" ||
79
+ alignment === "center_left"
80
+ ) {
81
+ return 0
82
+ }
83
+
84
+ // For right-aligned text, lines end at the same position
85
+ if (
86
+ alignment === "top_right" ||
87
+ alignment === "bottom_right" ||
88
+ alignment === "center_right"
89
+ ) {
90
+ return totalMaxWidth - totalLineWidth
91
+ }
92
+
93
+ // For center-aligned text, center each line
94
+ return (totalMaxWidth - totalLineWidth) / 2
95
+ }
@@ -1,3 +1,7 @@
1
1
  export * from "./text"
2
2
  export * from "./getAlphabetLayout"
3
- export * from "./getTextStartPosition"
3
+ export {
4
+ getTextStartPosition,
5
+ getLineStartX,
6
+ type AnchorAlignment,
7
+ } from "./getTextStartPosition"
@@ -4,7 +4,7 @@ import { applyToPoint } from "transformation-matrix"
4
4
  import type { CanvasContext } from "../../types"
5
5
  import type { NinePointAnchor } from "circuit-json"
6
6
  import { getAlphabetLayout, type AlphabetLayout } from "./getAlphabetLayout"
7
- import { getTextStartPosition } from "./getTextStartPosition"
7
+ import { getTextStartPosition, getLineStartX } from "./getTextStartPosition"
8
8
 
9
9
  const getGlyphLines = (char: string) =>
10
10
  lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()]
@@ -15,29 +15,37 @@ export interface StrokeAlphabetTextParams {
15
15
  fontSize: number
16
16
  startX: number
17
17
  startY: number
18
+ anchorAlignment?: NinePointAnchor
18
19
  }
19
20
 
20
- export function strokeAlphabetText(params: StrokeAlphabetTextParams): void {
21
- const { ctx, text, fontSize, startX, startY } = params
22
- const layout = getAlphabetLayout(text, fontSize)
23
- const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout
21
+ export interface StrokeAlphabetLineParams {
22
+ ctx: CanvasContext
23
+ line: string
24
+ fontSize: number
25
+ startX: number
26
+ startY: number
27
+ layout: AlphabetLayout
28
+ }
29
+
30
+ export function strokeAlphabetLine(params: StrokeAlphabetLineParams): void {
31
+ const { ctx, line, fontSize, startX, startY, layout } = params
32
+ const { glyphWidth, letterSpacing, spaceWidth, strokeWidth } = layout
33
+ const height = fontSize
24
34
  const topY = startY
25
- const characters = Array.from(text)
35
+ const characters = Array.from(line)
26
36
  let cursor = startX + strokeWidth / 2
27
37
 
28
38
  characters.forEach((char, index) => {
29
- const lines = getGlyphLines(char)
39
+ const glyphLines = getGlyphLines(char)
30
40
  const advance = char === " " ? spaceWidth : glyphWidth
31
41
 
32
- if (lines?.length) {
42
+ if (glyphLines?.length) {
33
43
  ctx.beginPath()
34
- for (const line of lines) {
35
- // Convert normalized y coordinates to canvas coordinates (inverted for canvas)
36
- // In normalized coords: y=0 is bottom, y=1 is top
37
- const x1 = cursor + line.x1 * glyphWidth
38
- const y1 = topY + (1 - line.y1) * height
39
- const x2 = cursor + line.x2 * glyphWidth
40
- const y2 = topY + (1 - line.y2) * height
44
+ for (const glyph of glyphLines) {
45
+ const x1 = cursor + glyph.x1 * glyphWidth
46
+ const y1 = topY + (1 - glyph.y1) * height
47
+ const x2 = cursor + glyph.x2 * glyphWidth
48
+ const y2 = topY + (1 - glyph.y2) * height
41
49
  ctx.moveTo(x1, y1)
42
50
  ctx.lineTo(x2, y2)
43
51
  }
@@ -51,6 +59,40 @@ export function strokeAlphabetText(params: StrokeAlphabetTextParams): void {
51
59
  })
52
60
  }
53
61
 
62
+ export function strokeAlphabetText(params: StrokeAlphabetTextParams): void {
63
+ const {
64
+ ctx,
65
+ text,
66
+ fontSize,
67
+ startX,
68
+ startY,
69
+ anchorAlignment = "center",
70
+ } = params
71
+ const layout = getAlphabetLayout(text, fontSize)
72
+ const { lines, lineWidths, lineHeight, width, strokeWidth } = layout
73
+
74
+ lines.forEach((line, lineIndex) => {
75
+ const lineStartX =
76
+ startX +
77
+ getLineStartX({
78
+ alignment: anchorAlignment,
79
+ lineWidth: lineWidths[lineIndex]!,
80
+ maxWidth: width,
81
+ strokeWidth,
82
+ })
83
+ const lineStartY = startY + lineIndex * lineHeight
84
+
85
+ strokeAlphabetLine({
86
+ ctx,
87
+ line,
88
+ fontSize,
89
+ startX: lineStartX,
90
+ startY: lineStartY,
91
+ layout,
92
+ })
93
+ })
94
+ }
95
+
54
96
  export interface DrawTextParams {
55
97
  ctx: CanvasContext
56
98
  text: string
@@ -96,12 +138,27 @@ export function drawText(params: DrawTextParams): void {
96
138
  ctx.lineJoin = "round"
97
139
  ctx.strokeStyle = color
98
140
 
99
- strokeAlphabetText({
100
- ctx,
101
- text,
102
- fontSize: scaledFontSize,
103
- startX: startPos.x,
104
- startY: startPos.y,
141
+ const { lines, lineWidths, lineHeight, width, strokeWidth } = layout
142
+
143
+ lines.forEach((line, lineIndex) => {
144
+ const lineStartX =
145
+ startPos.x +
146
+ getLineStartX({
147
+ alignment: anchorAlignment,
148
+ lineWidth: lineWidths[lineIndex]!,
149
+ maxWidth: width,
150
+ strokeWidth,
151
+ })
152
+ const lineStartY = startPos.y + lineIndex * lineHeight
153
+
154
+ strokeAlphabetLine({
155
+ ctx,
156
+ line,
157
+ fontSize: scaledFontSize,
158
+ startX: lineStartX,
159
+ startY: lineStartY,
160
+ layout,
161
+ })
105
162
  })
106
163
 
107
164
  ctx.restore()
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.52",
4
+ "version": "0.0.54",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup-node ./lib/index.ts --format esm --dts",
@@ -0,0 +1,33 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { PcbCopperText } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("draw multiline copper text with left alignment", async () => {
7
+ const SCALE = 4
8
+ const canvas = createCanvas(150 * SCALE, 100 * SCALE)
9
+ const ctx = canvas.getContext("2d")
10
+ ctx.scale(SCALE, SCALE)
11
+ const drawer = new CircuitToCanvasDrawer(ctx)
12
+
13
+ ctx.fillStyle = "#1a1a1a"
14
+ ctx.fillRect(0, 0, canvas.width / SCALE, canvas.height / SCALE)
15
+
16
+ const text: PcbCopperText = {
17
+ type: "pcb_copper_text",
18
+ pcb_copper_text_id: "copper-text-1",
19
+ pcb_component_id: "component1",
20
+ layer: "top",
21
+ text: "SHORT\nLONGERTEXT\nMED",
22
+ anchor_position: { x: 20, y: 50 },
23
+ anchor_alignment: "center_left",
24
+ font: "tscircuit2024",
25
+ font_size: 6,
26
+ }
27
+
28
+ drawer.drawElements([text])
29
+
30
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
31
+ import.meta.path,
32
+ )
33
+ })
@@ -0,0 +1,33 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { PcbCopperText } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("draw multiline copper text with right alignment", async () => {
7
+ const SCALE = 4
8
+ const canvas = createCanvas(150 * SCALE, 100 * SCALE)
9
+ const ctx = canvas.getContext("2d")
10
+ ctx.scale(SCALE, SCALE)
11
+ const drawer = new CircuitToCanvasDrawer(ctx)
12
+
13
+ ctx.fillStyle = "#1a1a1a"
14
+ ctx.fillRect(0, 0, canvas.width / SCALE, canvas.height / SCALE)
15
+
16
+ const text: PcbCopperText = {
17
+ type: "pcb_copper_text",
18
+ pcb_copper_text_id: "copper-text-1",
19
+ pcb_component_id: "component1",
20
+ layer: "top",
21
+ text: "SHORT\nLONGERTEXT\nMED",
22
+ anchor_position: { x: 130, y: 50 },
23
+ anchor_alignment: "center_right",
24
+ font: "tscircuit2024",
25
+ font_size: 6,
26
+ }
27
+
28
+ drawer.drawElements([text])
29
+
30
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
31
+ import.meta.path,
32
+ )
33
+ })
@@ -0,0 +1,33 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { PcbCopperText } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("draw multiline copper text with center alignment", async () => {
7
+ const SCALE = 4
8
+ const canvas = createCanvas(150 * SCALE, 100 * SCALE)
9
+ const ctx = canvas.getContext("2d")
10
+ ctx.scale(SCALE, SCALE)
11
+ const drawer = new CircuitToCanvasDrawer(ctx)
12
+
13
+ ctx.fillStyle = "#1a1a1a"
14
+ ctx.fillRect(0, 0, canvas.width / SCALE, canvas.height / SCALE)
15
+
16
+ const text: PcbCopperText = {
17
+ type: "pcb_copper_text",
18
+ pcb_copper_text_id: "copper-text-1",
19
+ pcb_component_id: "component1",
20
+ layer: "top",
21
+ text: "LINE1\nLONGERLINE2\nL3",
22
+ anchor_position: { x: 75, y: 50 },
23
+ anchor_alignment: "center",
24
+ font: "tscircuit2024",
25
+ font_size: 6,
26
+ }
27
+
28
+ drawer.drawElements([text])
29
+
30
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
31
+ import.meta.path,
32
+ )
33
+ })
@@ -0,0 +1,221 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { PcbSilkscreenText } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("draw silkscreen text with different anchor alignments on top and bottom layers", async () => {
7
+ const SCALE = 4
8
+ const canvas = createCanvas(400 * SCALE, 300 * SCALE)
9
+ const ctx = canvas.getContext("2d")
10
+ ctx.scale(SCALE, SCALE)
11
+ const drawer = new CircuitToCanvasDrawer(ctx)
12
+
13
+ ctx.fillStyle = "#1a1a1a"
14
+ ctx.fillRect(0, 0, canvas.width / SCALE, canvas.height / SCALE)
15
+
16
+ // Draw reference lines
17
+ ctx.strokeStyle = "#444444"
18
+ ctx.lineWidth = 0.5
19
+ // Horizontal lines
20
+ ctx.beginPath()
21
+ ctx.moveTo(10, 50)
22
+ ctx.lineTo(390, 50)
23
+ ctx.stroke()
24
+ ctx.beginPath()
25
+ ctx.moveTo(10, 150)
26
+ ctx.lineTo(390, 150)
27
+ ctx.stroke()
28
+ ctx.beginPath()
29
+ ctx.moveTo(10, 250)
30
+ ctx.lineTo(390, 250)
31
+ ctx.stroke()
32
+ // Vertical lines
33
+ ctx.beginPath()
34
+ ctx.moveTo(100, 10)
35
+ ctx.lineTo(100, 290)
36
+ ctx.stroke()
37
+ ctx.beginPath()
38
+ ctx.moveTo(200, 10)
39
+ ctx.lineTo(200, 290)
40
+ ctx.stroke()
41
+ ctx.beginPath()
42
+ ctx.moveTo(300, 10)
43
+ ctx.lineTo(300, 290)
44
+ ctx.stroke()
45
+
46
+ const elements: PcbSilkscreenText[] = [
47
+ // Top layer tests
48
+ {
49
+ type: "pcb_silkscreen_text",
50
+ pcb_silkscreen_text_id: "top-top-left",
51
+ pcb_component_id: "component1",
52
+ layer: "top",
53
+ text: "TL",
54
+ anchor_position: { x: 100, y: 50 },
55
+ anchor_alignment: "top_left",
56
+ font: "tscircuit2024",
57
+ font_size: 12,
58
+ },
59
+ {
60
+ type: "pcb_silkscreen_text",
61
+ pcb_silkscreen_text_id: "top-center",
62
+ pcb_component_id: "component1",
63
+ layer: "top",
64
+ text: "TC",
65
+ anchor_position: { x: 200, y: 50 },
66
+ anchor_alignment: "top_center",
67
+ font: "tscircuit2024",
68
+ font_size: 12,
69
+ },
70
+ {
71
+ type: "pcb_silkscreen_text",
72
+ pcb_silkscreen_text_id: "top-top-right",
73
+ pcb_component_id: "component1",
74
+ layer: "top",
75
+ text: "TR",
76
+ anchor_position: { x: 300, y: 50 },
77
+ anchor_alignment: "top_right",
78
+ font: "tscircuit2024",
79
+ font_size: 12,
80
+ },
81
+ {
82
+ type: "pcb_silkscreen_text",
83
+ pcb_silkscreen_text_id: "top-center-left",
84
+ pcb_component_id: "component1",
85
+ layer: "top",
86
+ text: "CL",
87
+ anchor_position: { x: 100, y: 150 },
88
+ anchor_alignment: "center_left",
89
+ font: "tscircuit2024",
90
+ font_size: 12,
91
+ },
92
+ {
93
+ type: "pcb_silkscreen_text",
94
+ pcb_silkscreen_text_id: "top-center",
95
+ pcb_component_id: "component1",
96
+ layer: "top",
97
+ text: "C",
98
+ anchor_position: { x: 200, y: 150 },
99
+ anchor_alignment: "center",
100
+ font: "tscircuit2024",
101
+ font_size: 12,
102
+ },
103
+ {
104
+ type: "pcb_silkscreen_text",
105
+ pcb_silkscreen_text_id: "top-center-right",
106
+ pcb_component_id: "component1",
107
+ layer: "top",
108
+ text: "CR",
109
+ anchor_position: { x: 300, y: 150 },
110
+ anchor_alignment: "center_right",
111
+ font: "tscircuit2024",
112
+ font_size: 12,
113
+ },
114
+ {
115
+ type: "pcb_silkscreen_text",
116
+ pcb_silkscreen_text_id: "top-bottom-left",
117
+ pcb_component_id: "component1",
118
+ layer: "top",
119
+ text: "BL",
120
+ anchor_position: { x: 100, y: 250 },
121
+ anchor_alignment: "bottom_left",
122
+ font: "tscircuit2024",
123
+ font_size: 12,
124
+ },
125
+ {
126
+ type: "pcb_silkscreen_text",
127
+ pcb_silkscreen_text_id: "top-bottom-center",
128
+ pcb_component_id: "component1",
129
+ layer: "top",
130
+ text: "BC",
131
+ anchor_position: { x: 200, y: 250 },
132
+ anchor_alignment: "bottom_center",
133
+ font: "tscircuit2024",
134
+ font_size: 12,
135
+ },
136
+ {
137
+ type: "pcb_silkscreen_text",
138
+ pcb_silkscreen_text_id: "top-bottom-right",
139
+ pcb_component_id: "component1",
140
+ layer: "top",
141
+ text: "BR",
142
+ anchor_position: { x: 300, y: 250 },
143
+ anchor_alignment: "bottom_right",
144
+ font: "tscircuit2024",
145
+ font_size: 12,
146
+ },
147
+ // Bottom layer tests (should appear mirrored horizontally)
148
+ {
149
+ type: "pcb_silkscreen_text",
150
+ pcb_silkscreen_text_id: "bottom-top-left",
151
+ pcb_component_id: "component1",
152
+ layer: "bottom",
153
+ text: "BL-TL",
154
+ anchor_position: { x: 50, y: 50 },
155
+ anchor_alignment: "top_left",
156
+ font: "tscircuit2024",
157
+ font_size: 10,
158
+ },
159
+ {
160
+ type: "pcb_silkscreen_text",
161
+ pcb_silkscreen_text_id: "bottom-top-right",
162
+ pcb_component_id: "component1",
163
+ layer: "bottom",
164
+ text: "BL-TR",
165
+ anchor_position: { x: 350, y: 50 },
166
+ anchor_alignment: "top_right",
167
+ font: "tscircuit2024",
168
+ font_size: 10,
169
+ },
170
+ {
171
+ type: "pcb_silkscreen_text",
172
+ pcb_silkscreen_text_id: "bottom-center-left",
173
+ pcb_component_id: "component1",
174
+ layer: "bottom",
175
+ text: "BL-CL",
176
+ anchor_position: { x: 50, y: 150 },
177
+ anchor_alignment: "center_left",
178
+ font: "tscircuit2024",
179
+ font_size: 10,
180
+ },
181
+ {
182
+ type: "pcb_silkscreen_text",
183
+ pcb_silkscreen_text_id: "bottom-center-right",
184
+ pcb_component_id: "component1",
185
+ layer: "bottom",
186
+ text: "BL-CR",
187
+ anchor_position: { x: 350, y: 150 },
188
+ anchor_alignment: "center_right",
189
+ font: "tscircuit2024",
190
+ font_size: 10,
191
+ },
192
+ {
193
+ type: "pcb_silkscreen_text",
194
+ pcb_silkscreen_text_id: "bottom-bottom-left",
195
+ pcb_component_id: "component1",
196
+ layer: "bottom",
197
+ text: "BL-BL",
198
+ anchor_position: { x: 50, y: 250 },
199
+ anchor_alignment: "bottom_left",
200
+ font: "tscircuit2024",
201
+ font_size: 10,
202
+ },
203
+ {
204
+ type: "pcb_silkscreen_text",
205
+ pcb_silkscreen_text_id: "bottom-bottom-right",
206
+ pcb_component_id: "component1",
207
+ layer: "bottom",
208
+ text: "BL-BR",
209
+ anchor_position: { x: 350, y: 250 },
210
+ anchor_alignment: "bottom_right",
211
+ font: "tscircuit2024",
212
+ font_size: 10,
213
+ },
214
+ ]
215
+
216
+ drawer.drawElements(elements)
217
+
218
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
219
+ import.meta.path,
220
+ )
221
+ })
@@ -0,0 +1,31 @@
1
+ import { expect, test } from "bun:test"
2
+ import { createCanvas } from "@napi-rs/canvas"
3
+ import type { PcbSilkscreenText } from "circuit-json"
4
+ import { CircuitToCanvasDrawer } from "../../lib/drawer"
5
+
6
+ test("draw silkscreen text with multiple lines", async () => {
7
+ const canvas = createCanvas(200, 200)
8
+ const ctx = canvas.getContext("2d")
9
+ const drawer = new CircuitToCanvasDrawer(ctx)
10
+
11
+ ctx.fillStyle = "#1a1a1a"
12
+ ctx.fillRect(0, 0, 200, 200)
13
+
14
+ const text: PcbSilkscreenText = {
15
+ type: "pcb_silkscreen_text",
16
+ pcb_silkscreen_text_id: "text1",
17
+ pcb_component_id: "component1",
18
+ layer: "top",
19
+ text: "TOP\nMIDDLE\nBOTTOM",
20
+ anchor_position: { x: 100, y: 100 },
21
+ anchor_alignment: "center",
22
+ font: "tscircuit2024",
23
+ font_size: 10,
24
+ }
25
+
26
+ drawer.drawElements([text])
27
+
28
+ await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
29
+ import.meta.path,
30
+ )
31
+ })