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 +14 -10
- package/dist/index.js +116 -39
- package/lib/drawer/elements/pcb-copper-text.ts +1 -0
- package/lib/drawer/elements/pcb-silkscreen-text.ts +23 -7
- package/lib/drawer/shapes/text/getAlphabetLayout.ts +28 -10
- package/lib/drawer/shapes/text/getTextStartPosition.ts +46 -4
- package/lib/drawer/shapes/text/index.ts +5 -1
- package/lib/drawer/shapes/text/text.ts +78 -21
- package/package.json +1 -1
- package/tests/elements/__snapshots__/pcb-comprehensive-soldermask-margin.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-copper-text-multiline-left.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-copper-text-multiline-right.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-copper-text-multiline.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-no-soldermask.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-silkscreen-text-anchor-alignment.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-silkscreen-text-multiline.snap.png +0 -0
- package/tests/elements/pcb-copper-text-multiline-left.test.ts +33 -0
- package/tests/elements/pcb-copper-text-multiline-right.test.ts +33 -0
- package/tests/elements/pcb-copper-text-multiline.test.ts +33 -0
- package/tests/elements/pcb-silkscreen-text-anchor-alignment.test.ts +221 -0
- package/tests/elements/pcb-silkscreen-text-multiline.test.ts +31 -0
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:
|
|
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
|
|
453
|
-
const { ctx,
|
|
454
|
-
const
|
|
455
|
-
const
|
|
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(
|
|
482
|
+
const characters = Array.from(line);
|
|
458
483
|
let cursor = startX + strokeWidth / 2;
|
|
459
484
|
characters.forEach((char, index) => {
|
|
460
|
-
const
|
|
485
|
+
const glyphLines = getGlyphLines(char);
|
|
461
486
|
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
462
|
-
if (
|
|
487
|
+
if (glyphLines?.length) {
|
|
463
488
|
ctx.beginPath();
|
|
464
|
-
for (const
|
|
465
|
-
const x1 = cursor +
|
|
466
|
-
const y1 = topY + (1 -
|
|
467
|
-
const x2 = cursor +
|
|
468
|
-
const y2 = topY + (1 -
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
26
|
+
const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35)
|
|
27
|
+
const lineHeight = fontSize * LINE_HEIGHT_RATIO
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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 (
|
|
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
|
+
}
|
|
@@ -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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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(
|
|
35
|
+
const characters = Array.from(line)
|
|
26
36
|
let cursor = startX + strokeWidth / 2
|
|
27
37
|
|
|
28
38
|
characters.forEach((char, index) => {
|
|
29
|
-
const
|
|
39
|
+
const glyphLines = getGlyphLines(char)
|
|
30
40
|
const advance = char === " " ? spaceWidth : glyphWidth
|
|
31
41
|
|
|
32
|
-
if (
|
|
42
|
+
if (glyphLines?.length) {
|
|
33
43
|
ctx.beginPath()
|
|
34
|
-
for (const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
})
|