circuit-to-canvas 0.0.53 → 0.0.55
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 +114 -35
- 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 +36 -0
- 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-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-multiline.test.ts +45 -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.replace(/\\n/g, "\n").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
|
|
|
@@ -434,6 +447,8 @@ function getTextStartPosition(alignment, layout) {
|
|
|
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
453
|
if (alignment === "center" || alignment === "center_left" || alignment === "center_right") {
|
|
439
454
|
y = -totalHeight / 2;
|
|
@@ -444,26 +459,38 @@ function getTextStartPosition(alignment, layout) {
|
|
|
444
459
|
}
|
|
445
460
|
return { x, y };
|
|
446
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
|
+
}
|
|
447
474
|
|
|
448
475
|
// lib/drawer/shapes/text/text.ts
|
|
449
476
|
var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
|
|
450
|
-
function
|
|
451
|
-
const { ctx,
|
|
452
|
-
const
|
|
453
|
-
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;
|
|
454
481
|
const topY = startY;
|
|
455
|
-
const characters = Array.from(
|
|
482
|
+
const characters = Array.from(line);
|
|
456
483
|
let cursor = startX + strokeWidth / 2;
|
|
457
484
|
characters.forEach((char, index) => {
|
|
458
|
-
const
|
|
485
|
+
const glyphLines = getGlyphLines(char);
|
|
459
486
|
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
460
|
-
if (
|
|
487
|
+
if (glyphLines?.length) {
|
|
461
488
|
ctx.beginPath();
|
|
462
|
-
for (const
|
|
463
|
-
const x1 = cursor +
|
|
464
|
-
const y1 = topY + (1 -
|
|
465
|
-
const x2 = cursor +
|
|
466
|
-
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;
|
|
467
494
|
ctx.moveTo(x1, y1);
|
|
468
495
|
ctx.lineTo(x2, y2);
|
|
469
496
|
}
|
|
@@ -475,6 +502,35 @@ function strokeAlphabetText(params) {
|
|
|
475
502
|
}
|
|
476
503
|
});
|
|
477
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
|
+
}
|
|
478
534
|
function drawText(params) {
|
|
479
535
|
const {
|
|
480
536
|
ctx,
|
|
@@ -502,12 +558,23 @@ function drawText(params) {
|
|
|
502
558
|
ctx.lineCap = "round";
|
|
503
559
|
ctx.lineJoin = "round";
|
|
504
560
|
ctx.strokeStyle = color;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
+
});
|
|
511
578
|
});
|
|
512
579
|
ctx.restore();
|
|
513
580
|
}
|
|
@@ -566,7 +633,8 @@ function drawPcbCopperText(params) {
|
|
|
566
633
|
text: content,
|
|
567
634
|
fontSize,
|
|
568
635
|
startX: startPos.x,
|
|
569
|
-
startY: startPos.y
|
|
636
|
+
startY: startPos.y,
|
|
637
|
+
anchorAlignment: alignment
|
|
570
638
|
});
|
|
571
639
|
ctx.restore();
|
|
572
640
|
}
|
|
@@ -1884,12 +1952,23 @@ function drawPcbSilkscreenText(params) {
|
|
|
1884
1952
|
ctx.lineCap = "round";
|
|
1885
1953
|
ctx.lineJoin = "round";
|
|
1886
1954
|
ctx.strokeStyle = color;
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
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
|
+
});
|
|
1893
1972
|
});
|
|
1894
1973
|
ctx.restore();
|
|
1895
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.replace(/\\n/g, "\n").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
|
}
|
|
@@ -32,6 +32,8 @@ export function getTextStartPosition(
|
|
|
32
32
|
alignment === "center_right"
|
|
33
33
|
) {
|
|
34
34
|
x = -totalWidth
|
|
35
|
+
} else if (alignment === "top_center" || alignment === "bottom_center") {
|
|
36
|
+
x = -totalWidth / 2
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
// Vertical alignment
|
|
@@ -57,3 +59,37 @@ export function getTextStartPosition(
|
|
|
57
59
|
|
|
58
60
|
return { x, y }
|
|
59
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
|
|
@@ -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,45 @@
|
|
|
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 multiline silkscreen text", 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 text1: PcbSilkscreenText = {
|
|
17
|
+
type: "pcb_silkscreen_text",
|
|
18
|
+
pcb_silkscreen_text_id: "silkscreen-text-1",
|
|
19
|
+
pcb_component_id: "component1",
|
|
20
|
+
layer: "top",
|
|
21
|
+
text: "Top\\nLeft",
|
|
22
|
+
anchor_position: { x: 40, y: 50 },
|
|
23
|
+
anchor_alignment: "center",
|
|
24
|
+
font: "tscircuit2024",
|
|
25
|
+
font_size: 4,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const text2: PcbSilkscreenText = {
|
|
29
|
+
type: "pcb_silkscreen_text",
|
|
30
|
+
pcb_silkscreen_text_id: "silkscreen-text-2",
|
|
31
|
+
pcb_component_id: "component1",
|
|
32
|
+
layer: "top",
|
|
33
|
+
text: "Top\nLeft",
|
|
34
|
+
anchor_position: { x: 110, y: 50 },
|
|
35
|
+
anchor_alignment: "center",
|
|
36
|
+
font: "tscircuit2024",
|
|
37
|
+
font_size: 4,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
drawer.drawElements([text1, text2])
|
|
41
|
+
|
|
42
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
43
|
+
import.meta.path,
|
|
44
|
+
)
|
|
45
|
+
})
|