circuit-to-canvas 0.0.6 → 0.0.7
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 +0 -3
- package/dist/index.js +12 -39
- package/lib/drawer/elements/pcb-copper-text.ts +2 -4
- package/lib/drawer/elements/pcb-silkscreen.ts +0 -1
- package/lib/drawer/shapes/text.ts +11 -57
- package/lib/drawer/types.ts +0 -7
- package/package.json +2 -2
- package/tests/elements/__snapshots__/fabrication-note-text-descenders.snap.png +0 -0
- package/tests/elements/__snapshots__/fabrication-note-text-full-charset.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-copper-text-knockout.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-copper-text.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-text-rgba-color.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-text-small.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-silkscreen.snap.png +0 -0
- package/tests/elements/__snapshots__/silkscreen-text-bottom.snap.png +0 -0
- package/tests/elements/pcb-copper-text.test.ts +1 -1
- package/tests/elements/pcb-fabrication-note-text-descenders.test.ts +1 -1
- package/tests/elements/pcb-fabrication-note-text-full-charset.test.ts +84 -0
- package/tests/elements/pcb-fabrication-note-text-baseline-anchors.test.ts +0 -80
- package/tests/elements/pcb-fabrication-note-text-baseline.test.ts +0 -43
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,6 @@ interface CanvasContext {
|
|
|
41
41
|
};
|
|
42
42
|
font: string;
|
|
43
43
|
textAlign: "start" | "end" | "left" | "right" | "center";
|
|
44
|
-
textBaseline: "top" | "hanging" | "middle" | "alphabetic" | "ideographic" | "bottom";
|
|
45
44
|
}
|
|
46
45
|
type CopperLayerName = "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
|
|
47
46
|
type CopperColorMap = Record<CopperLayerName, string> & {
|
|
@@ -210,8 +209,6 @@ type AlphabetLayout = {
|
|
|
210
209
|
letterSpacing: number;
|
|
211
210
|
spaceWidth: number;
|
|
212
211
|
strokeWidth: number;
|
|
213
|
-
baselineOffset: number;
|
|
214
|
-
descenderDepth: number;
|
|
215
212
|
};
|
|
216
213
|
declare function getAlphabetLayout(text: string, fontSize: number): AlphabetLayout;
|
|
217
214
|
type AnchorAlignment = "center" | "top_left" | "top_right" | "bottom_left" | "bottom_right" | "left" | "right" | "top" | "bottom";
|
package/dist/index.js
CHANGED
|
@@ -659,7 +659,6 @@ function drawPcbSilkscreenText(params) {
|
|
|
659
659
|
ctx.font = `${fontSize}px sans-serif`;
|
|
660
660
|
ctx.fillStyle = color;
|
|
661
661
|
ctx.textAlign = mapAnchorAlignment(text.anchor_alignment);
|
|
662
|
-
ctx.textBaseline = "middle";
|
|
663
662
|
ctx.fillText(text.text, 0, 0);
|
|
664
663
|
ctx.restore();
|
|
665
664
|
}
|
|
@@ -821,16 +820,6 @@ var LETTER_SPACING_RATIO = 0.3;
|
|
|
821
820
|
var SPACE_WIDTH_RATIO = 1;
|
|
822
821
|
var STROKE_WIDTH_RATIO = 0.13;
|
|
823
822
|
var CURVED_GLYPHS = /* @__PURE__ */ new Set(["O", "o", "0"]);
|
|
824
|
-
var LOWERCASE_BASELINE_OFFSET = (() => {
|
|
825
|
-
const referenceLetters = ["a", "c", "e", "m", "n", "o", "r", "s", "u", "x"];
|
|
826
|
-
const offsets = referenceLetters.map((letter) => lineAlphabet[letter]).filter(
|
|
827
|
-
(lines) => lines !== void 0 && lines.length > 0
|
|
828
|
-
).map(
|
|
829
|
-
(lines) => Math.min(...lines.map((line) => Math.min(line.y1, line.y2)))
|
|
830
|
-
);
|
|
831
|
-
return offsets.length > 0 ? Math.min(...offsets) : 0;
|
|
832
|
-
})();
|
|
833
|
-
var getBaselineOffsetForLetter = (letter) => letter >= "a" && letter <= "z" ? LOWERCASE_BASELINE_OFFSET : 0;
|
|
834
823
|
function getAlphabetLayout(text, fontSize) {
|
|
835
824
|
const glyphWidth = fontSize * GLYPH_WIDTH_RATIO;
|
|
836
825
|
const letterSpacing = glyphWidth * LETTER_SPACING_RATIO;
|
|
@@ -843,24 +832,19 @@ function getAlphabetLayout(text, fontSize) {
|
|
|
843
832
|
if (index < characters.length - 1) width += letterSpacing;
|
|
844
833
|
});
|
|
845
834
|
const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35);
|
|
846
|
-
const hasLowercase = /[a-z]/.test(text);
|
|
847
|
-
const baselineOffset = hasLowercase ? (1 - LOWERCASE_BASELINE_OFFSET) * fontSize : fontSize;
|
|
848
|
-
const descenderDepth = hasLowercase ? LOWERCASE_BASELINE_OFFSET * fontSize : 0;
|
|
849
835
|
return {
|
|
850
836
|
width,
|
|
851
837
|
height: fontSize,
|
|
852
838
|
glyphWidth,
|
|
853
839
|
letterSpacing,
|
|
854
840
|
spaceWidth,
|
|
855
|
-
strokeWidth
|
|
856
|
-
baselineOffset,
|
|
857
|
-
descenderDepth
|
|
841
|
+
strokeWidth
|
|
858
842
|
};
|
|
859
843
|
}
|
|
860
844
|
var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
|
|
861
845
|
function getTextStartPosition(alignment, layout) {
|
|
862
846
|
const totalWidth = layout.width + layout.strokeWidth;
|
|
863
|
-
const totalHeight = layout.height + layout.
|
|
847
|
+
const totalHeight = layout.height + layout.strokeWidth;
|
|
864
848
|
let x = 0;
|
|
865
849
|
let y = 0;
|
|
866
850
|
if (alignment === "center") {
|
|
@@ -871,36 +855,27 @@ function getTextStartPosition(alignment, layout) {
|
|
|
871
855
|
x = -totalWidth;
|
|
872
856
|
}
|
|
873
857
|
if (alignment === "center") {
|
|
874
|
-
y =
|
|
858
|
+
y = -totalHeight / 2;
|
|
875
859
|
} else if (alignment === "top_left" || alignment === "top_right" || alignment === "top") {
|
|
876
|
-
y =
|
|
860
|
+
y = 0;
|
|
877
861
|
} else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom") {
|
|
878
|
-
y = -
|
|
862
|
+
y = -totalHeight;
|
|
879
863
|
} else {
|
|
880
864
|
y = 0;
|
|
881
865
|
}
|
|
882
866
|
return { x, y };
|
|
883
867
|
}
|
|
884
868
|
function strokeAlphabetText(ctx, text, layout, startX, startY) {
|
|
885
|
-
const {
|
|
886
|
-
|
|
887
|
-
letterSpacing,
|
|
888
|
-
spaceWidth,
|
|
889
|
-
height,
|
|
890
|
-
strokeWidth,
|
|
891
|
-
baselineOffset
|
|
892
|
-
} = layout;
|
|
893
|
-
const baselineY = startY;
|
|
869
|
+
const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout;
|
|
870
|
+
const topY = startY;
|
|
894
871
|
const characters = Array.from(text);
|
|
895
872
|
let cursor = startX + strokeWidth / 2;
|
|
896
873
|
characters.forEach((char, index) => {
|
|
897
874
|
const lines = getGlyphLines(char);
|
|
898
875
|
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
899
|
-
const charBaselineOffset = getBaselineOffsetForLetter(char);
|
|
900
876
|
if (CURVED_GLYPHS.has(char)) {
|
|
901
877
|
const normalizedCenterY = 0.5;
|
|
902
|
-
const
|
|
903
|
-
const centerY = baselineY - adjustedCenterY * height;
|
|
878
|
+
const centerY = topY + normalizedCenterY * height;
|
|
904
879
|
const radiusX = Math.max(glyphWidth / 2 - strokeWidth / 2, strokeWidth);
|
|
905
880
|
const radiusY = Math.max(height / 2 - strokeWidth / 2, strokeWidth);
|
|
906
881
|
ctx.beginPath();
|
|
@@ -917,12 +892,10 @@ function strokeAlphabetText(ctx, text, layout, startX, startY) {
|
|
|
917
892
|
} else if (lines?.length) {
|
|
918
893
|
ctx.beginPath();
|
|
919
894
|
for (const line of lines) {
|
|
920
|
-
const adjusted_y1 = line.y1 - charBaselineOffset;
|
|
921
|
-
const adjusted_y2 = line.y2 - charBaselineOffset;
|
|
922
895
|
const x1 = cursor + line.x1 * glyphWidth;
|
|
923
|
-
const y1 =
|
|
896
|
+
const y1 = topY + (1 - line.y1) * height;
|
|
924
897
|
const x2 = cursor + line.x2 * glyphWidth;
|
|
925
|
-
const y2 =
|
|
898
|
+
const y2 = topY + (1 - line.y2) * height;
|
|
926
899
|
ctx.moveTo(x1, y1);
|
|
927
900
|
ctx.lineTo(x2, y2);
|
|
928
901
|
}
|
|
@@ -1010,8 +983,8 @@ function drawPcbCopperText(params) {
|
|
|
1010
983
|
const paddingRight = padding.right * scale2;
|
|
1011
984
|
const paddingTop = padding.top * scale2;
|
|
1012
985
|
const paddingBottom = padding.bottom * scale2;
|
|
1013
|
-
const textBoxTop = startY - layout.
|
|
1014
|
-
const textBoxBottom = startY + layout.
|
|
986
|
+
const textBoxTop = startY - layout.strokeWidth / 2;
|
|
987
|
+
const textBoxBottom = startY + layout.height + layout.strokeWidth / 2;
|
|
1015
988
|
const textBoxHeight = textBoxBottom - textBoxTop;
|
|
1016
989
|
const xOffset = startX - paddingLeft;
|
|
1017
990
|
const yOffset = textBoxTop - paddingTop;
|
|
@@ -72,10 +72,8 @@ export function drawPcbCopperText(params: DrawPcbCopperTextParams): void {
|
|
|
72
72
|
const paddingTop = padding.top * scale
|
|
73
73
|
const paddingBottom = padding.bottom * scale
|
|
74
74
|
// Calculate knockout rectangle to cover the text box
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
const textBoxBottom =
|
|
78
|
-
startY + layout.descenderDepth + layout.strokeWidth / 2
|
|
75
|
+
const textBoxTop = startY - layout.strokeWidth / 2
|
|
76
|
+
const textBoxBottom = startY + layout.height + layout.strokeWidth / 2
|
|
79
77
|
const textBoxHeight = textBoxBottom - textBoxTop
|
|
80
78
|
|
|
81
79
|
const xOffset = startX - paddingLeft
|
|
@@ -9,25 +9,6 @@ const SPACE_WIDTH_RATIO = 1
|
|
|
9
9
|
const STROKE_WIDTH_RATIO = 0.13
|
|
10
10
|
const CURVED_GLYPHS = new Set(["O", "o", "0"])
|
|
11
11
|
|
|
12
|
-
// Calculate baseline offset from reference lowercase letters (same as working implementation)
|
|
13
|
-
const LOWERCASE_BASELINE_OFFSET = (() => {
|
|
14
|
-
const referenceLetters = ["a", "c", "e", "m", "n", "o", "r", "s", "u", "x"]
|
|
15
|
-
const offsets = referenceLetters
|
|
16
|
-
.map((letter) => lineAlphabet[letter])
|
|
17
|
-
.filter(
|
|
18
|
-
(lines): lines is NonNullable<typeof lines> =>
|
|
19
|
-
lines !== undefined && lines.length > 0,
|
|
20
|
-
)
|
|
21
|
-
.map((lines) =>
|
|
22
|
-
Math.min(...lines.map((line) => Math.min(line.y1, line.y2))),
|
|
23
|
-
)
|
|
24
|
-
return offsets.length > 0 ? Math.min(...offsets) : 0
|
|
25
|
-
})()
|
|
26
|
-
|
|
27
|
-
// Get baseline offset for a specific letter (only lowercase letters get offset)
|
|
28
|
-
const getBaselineOffsetForLetter = (letter: string) =>
|
|
29
|
-
letter >= "a" && letter <= "z" ? LOWERCASE_BASELINE_OFFSET : 0
|
|
30
|
-
|
|
31
12
|
export type AlphabetLayout = {
|
|
32
13
|
width: number
|
|
33
14
|
height: number
|
|
@@ -35,8 +16,6 @@ export type AlphabetLayout = {
|
|
|
35
16
|
letterSpacing: number
|
|
36
17
|
spaceWidth: number
|
|
37
18
|
strokeWidth: number
|
|
38
|
-
baselineOffset: number // Distance from top to baseline
|
|
39
|
-
descenderDepth: number // Distance from baseline to bottom (for descenders)
|
|
40
19
|
}
|
|
41
20
|
|
|
42
21
|
export function getAlphabetLayout(
|
|
@@ -56,15 +35,6 @@ export function getAlphabetLayout(
|
|
|
56
35
|
})
|
|
57
36
|
|
|
58
37
|
const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35)
|
|
59
|
-
// Calculate baseline offset: distance from top of text box to baseline
|
|
60
|
-
// In normalized coords: y=0 is bottom, y=1 is top
|
|
61
|
-
// LOWERCASE_BASELINE_OFFSET is the minimum y (baseline position) in normalized coords
|
|
62
|
-
const hasLowercase = /[a-z]/.test(text)
|
|
63
|
-
const baselineOffset = hasLowercase
|
|
64
|
-
? (1 - LOWERCASE_BASELINE_OFFSET) * fontSize
|
|
65
|
-
: fontSize
|
|
66
|
-
// Descender depth: distance from baseline to bottom (for descenders like g, j, p, q, y)
|
|
67
|
-
const descenderDepth = hasLowercase ? LOWERCASE_BASELINE_OFFSET * fontSize : 0
|
|
68
38
|
|
|
69
39
|
return {
|
|
70
40
|
width,
|
|
@@ -73,8 +43,6 @@ export function getAlphabetLayout(
|
|
|
73
43
|
letterSpacing,
|
|
74
44
|
spaceWidth,
|
|
75
45
|
strokeWidth,
|
|
76
|
-
baselineOffset,
|
|
77
|
-
descenderDepth,
|
|
78
46
|
}
|
|
79
47
|
}
|
|
80
48
|
|
|
@@ -97,8 +65,7 @@ export function getTextStartPosition(
|
|
|
97
65
|
layout: AlphabetLayout,
|
|
98
66
|
): { x: number; y: number } {
|
|
99
67
|
const totalWidth = layout.width + layout.strokeWidth
|
|
100
|
-
|
|
101
|
-
const totalHeight = layout.height + layout.descenderDepth + layout.strokeWidth
|
|
68
|
+
const totalHeight = layout.height + layout.strokeWidth
|
|
102
69
|
|
|
103
70
|
let x = 0
|
|
104
71
|
let y = 0
|
|
@@ -120,22 +87,21 @@ export function getTextStartPosition(
|
|
|
120
87
|
x = -totalWidth
|
|
121
88
|
}
|
|
122
89
|
|
|
123
|
-
// Vertical alignment
|
|
124
|
-
// Text extends from (baseline - baselineOffset) at top to (baseline + descenderDepth) at bottom
|
|
90
|
+
// Vertical alignment
|
|
125
91
|
if (alignment === "center") {
|
|
126
|
-
y =
|
|
92
|
+
y = -totalHeight / 2
|
|
127
93
|
} else if (
|
|
128
94
|
alignment === "top_left" ||
|
|
129
95
|
alignment === "top_right" ||
|
|
130
96
|
alignment === "top"
|
|
131
97
|
) {
|
|
132
|
-
y =
|
|
98
|
+
y = 0
|
|
133
99
|
} else if (
|
|
134
100
|
alignment === "bottom_left" ||
|
|
135
101
|
alignment === "bottom_right" ||
|
|
136
102
|
alignment === "bottom"
|
|
137
103
|
) {
|
|
138
|
-
y = -
|
|
104
|
+
y = -totalHeight
|
|
139
105
|
} else {
|
|
140
106
|
y = 0
|
|
141
107
|
}
|
|
@@ -150,29 +116,18 @@ export function strokeAlphabetText(
|
|
|
150
116
|
startX: number,
|
|
151
117
|
startY: number,
|
|
152
118
|
): void {
|
|
153
|
-
const {
|
|
154
|
-
|
|
155
|
-
letterSpacing,
|
|
156
|
-
spaceWidth,
|
|
157
|
-
height,
|
|
158
|
-
strokeWidth,
|
|
159
|
-
baselineOffset,
|
|
160
|
-
} = layout
|
|
161
|
-
const baselineY = startY
|
|
119
|
+
const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout
|
|
120
|
+
const topY = startY
|
|
162
121
|
const characters = Array.from(text)
|
|
163
122
|
let cursor = startX + strokeWidth / 2
|
|
164
123
|
|
|
165
124
|
characters.forEach((char, index) => {
|
|
166
125
|
const lines = getGlyphLines(char)
|
|
167
126
|
const advance = char === " " ? spaceWidth : glyphWidth
|
|
168
|
-
// Get normalized baseline offset for this specific character (0-1 range)
|
|
169
|
-
const charBaselineOffset = getBaselineOffsetForLetter(char)
|
|
170
127
|
|
|
171
128
|
if (CURVED_GLYPHS.has(char)) {
|
|
172
|
-
// For curved glyphs, adjust coordinates by baseline offset
|
|
173
129
|
const normalizedCenterY = 0.5
|
|
174
|
-
const
|
|
175
|
-
const centerY = baselineY - adjustedCenterY * height
|
|
130
|
+
const centerY = topY + normalizedCenterY * height
|
|
176
131
|
const radiusX = Math.max(glyphWidth / 2 - strokeWidth / 2, strokeWidth)
|
|
177
132
|
const radiusY = Math.max(height / 2 - strokeWidth / 2, strokeWidth)
|
|
178
133
|
ctx.beginPath()
|
|
@@ -190,12 +145,11 @@ export function strokeAlphabetText(
|
|
|
190
145
|
ctx.beginPath()
|
|
191
146
|
for (const line of lines) {
|
|
192
147
|
// Convert normalized y coordinates to canvas coordinates (inverted for canvas)
|
|
193
|
-
|
|
194
|
-
const adjusted_y2 = line.y2 - charBaselineOffset
|
|
148
|
+
// In normalized coords: y=0 is bottom, y=1 is top
|
|
195
149
|
const x1 = cursor + line.x1 * glyphWidth
|
|
196
|
-
const y1 =
|
|
150
|
+
const y1 = topY + (1 - line.y1) * height
|
|
197
151
|
const x2 = cursor + line.x2 * glyphWidth
|
|
198
|
-
const y2 =
|
|
152
|
+
const y2 = topY + (1 - line.y2) * height
|
|
199
153
|
ctx.moveTo(x1, y1)
|
|
200
154
|
ctx.lineTo(x2, y2)
|
|
201
155
|
}
|
package/lib/drawer/types.ts
CHANGED
|
@@ -51,13 +51,6 @@ export interface CanvasContext {
|
|
|
51
51
|
}
|
|
52
52
|
font: string
|
|
53
53
|
textAlign: "start" | "end" | "left" | "right" | "center"
|
|
54
|
-
textBaseline:
|
|
55
|
-
| "top"
|
|
56
|
-
| "hanging"
|
|
57
|
-
| "middle"
|
|
58
|
-
| "alphabetic"
|
|
59
|
-
| "ideographic"
|
|
60
|
-
| "bottom"
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
export type CopperLayerName =
|
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.
|
|
4
|
+
"version": "0.0.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@biomejs/biome": "^2.3.8",
|
|
13
|
-
"@tscircuit/alphabet": "^0.0.
|
|
13
|
+
"@tscircuit/alphabet": "^0.0.9",
|
|
14
14
|
"@types/bun": "latest",
|
|
15
15
|
"bun-match-svg": "^0.0.14",
|
|
16
16
|
"canvas": "^3.2.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -20,7 +20,7 @@ test("draw copper text", async () => {
|
|
|
20
20
|
pcb_copper_text_id: "copper-text-1",
|
|
21
21
|
pcb_component_id: "component1",
|
|
22
22
|
layer: "top",
|
|
23
|
-
text: "
|
|
23
|
+
text: "AabcbCdde",
|
|
24
24
|
anchor_position: { x: 40, y: 40 },
|
|
25
25
|
anchor_alignment: "center",
|
|
26
26
|
font: "tscircuit2024",
|
|
@@ -13,7 +13,7 @@ test("draw lowercase text with descenders", async () => {
|
|
|
13
13
|
ctx.fillStyle = "#1a1a1a"
|
|
14
14
|
ctx.fillRect(0, 0, canvas.width / SCALE, canvas.height / SCALE)
|
|
15
15
|
|
|
16
|
-
// Draw
|
|
16
|
+
// Draw reference line
|
|
17
17
|
ctx.strokeStyle = "#666666"
|
|
18
18
|
ctx.lineWidth = 0.5
|
|
19
19
|
ctx.beginPath()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { createCanvas } from "canvas"
|
|
3
|
+
import type { PcbFabricationNoteText } from "circuit-json"
|
|
4
|
+
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
+
|
|
6
|
+
test("draw full character set", async () => {
|
|
7
|
+
const SCALE = 4
|
|
8
|
+
const canvas = createCanvas(800 * 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
|
+
const fontSize = 20
|
|
17
|
+
const lineHeight = 40
|
|
18
|
+
let y = 30
|
|
19
|
+
|
|
20
|
+
// Lowercase letters
|
|
21
|
+
const lowercaseText: PcbFabricationNoteText = {
|
|
22
|
+
type: "pcb_fabrication_note_text",
|
|
23
|
+
pcb_fabrication_note_text_id: "fab-note-lowercase",
|
|
24
|
+
pcb_component_id: "component1",
|
|
25
|
+
layer: "top",
|
|
26
|
+
text: "abcdefghijklmnopqrstuvwxyz",
|
|
27
|
+
anchor_position: { x: 400, y },
|
|
28
|
+
anchor_alignment: "center",
|
|
29
|
+
font: "tscircuit2024",
|
|
30
|
+
font_size: fontSize,
|
|
31
|
+
}
|
|
32
|
+
drawer.drawElements([lowercaseText])
|
|
33
|
+
y += lineHeight
|
|
34
|
+
|
|
35
|
+
// Uppercase letters
|
|
36
|
+
const uppercaseText: PcbFabricationNoteText = {
|
|
37
|
+
type: "pcb_fabrication_note_text",
|
|
38
|
+
pcb_fabrication_note_text_id: "fab-note-uppercase",
|
|
39
|
+
pcb_component_id: "component2",
|
|
40
|
+
layer: "top",
|
|
41
|
+
text: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
42
|
+
anchor_position: { x: 400, y },
|
|
43
|
+
anchor_alignment: "center",
|
|
44
|
+
font: "tscircuit2024",
|
|
45
|
+
font_size: fontSize,
|
|
46
|
+
}
|
|
47
|
+
drawer.drawElements([uppercaseText])
|
|
48
|
+
y += lineHeight
|
|
49
|
+
|
|
50
|
+
// Numbers
|
|
51
|
+
const numbersText: PcbFabricationNoteText = {
|
|
52
|
+
type: "pcb_fabrication_note_text",
|
|
53
|
+
pcb_fabrication_note_text_id: "fab-note-numbers",
|
|
54
|
+
pcb_component_id: "component3",
|
|
55
|
+
layer: "top",
|
|
56
|
+
text: "0123456789",
|
|
57
|
+
anchor_position: { x: 400, y },
|
|
58
|
+
anchor_alignment: "center",
|
|
59
|
+
font: "tscircuit2024",
|
|
60
|
+
font_size: fontSize,
|
|
61
|
+
}
|
|
62
|
+
drawer.drawElements([numbersText])
|
|
63
|
+
y += lineHeight
|
|
64
|
+
|
|
65
|
+
// Common symbols
|
|
66
|
+
const symbolsText: PcbFabricationNoteText = {
|
|
67
|
+
type: "pcb_fabrication_note_text",
|
|
68
|
+
pcb_fabrication_note_text_id: "fab-note-symbols",
|
|
69
|
+
pcb_component_id: "component4",
|
|
70
|
+
layer: "top",
|
|
71
|
+
text: "()!@#$%^&*",
|
|
72
|
+
anchor_position: { x: 400, y },
|
|
73
|
+
anchor_alignment: "center",
|
|
74
|
+
font: "tscircuit2024",
|
|
75
|
+
font_size: fontSize,
|
|
76
|
+
}
|
|
77
|
+
drawer.drawElements([symbolsText])
|
|
78
|
+
y += lineHeight
|
|
79
|
+
|
|
80
|
+
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
81
|
+
import.meta.path,
|
|
82
|
+
"fabrication-note-text-full-charset",
|
|
83
|
+
)
|
|
84
|
+
})
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "bun:test"
|
|
2
|
-
import { createCanvas } from "canvas"
|
|
3
|
-
import type { PcbFabricationNoteText } from "circuit-json"
|
|
4
|
-
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
-
|
|
6
|
-
test("draw text baseline alignment with different anchor positions", async () => {
|
|
7
|
-
const SCALE = 4
|
|
8
|
-
const canvas = createCanvas(300 * SCALE, 200 * 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
|
-
// Top line
|
|
20
|
-
ctx.beginPath()
|
|
21
|
-
ctx.moveTo(10, 50)
|
|
22
|
-
ctx.lineTo(290, 50)
|
|
23
|
-
ctx.stroke()
|
|
24
|
-
// Center/baseline line
|
|
25
|
-
ctx.beginPath()
|
|
26
|
-
ctx.moveTo(10, 100)
|
|
27
|
-
ctx.lineTo(290, 100)
|
|
28
|
-
ctx.stroke()
|
|
29
|
-
// Bottom line
|
|
30
|
-
ctx.beginPath()
|
|
31
|
-
ctx.moveTo(10, 150)
|
|
32
|
-
ctx.lineTo(290, 150)
|
|
33
|
-
ctx.stroke()
|
|
34
|
-
|
|
35
|
-
// Test with top alignment
|
|
36
|
-
const textTop: PcbFabricationNoteText = {
|
|
37
|
-
type: "pcb_fabrication_note_text",
|
|
38
|
-
pcb_fabrication_note_text_id: "fab-note-top",
|
|
39
|
-
pcb_component_id: "component1",
|
|
40
|
-
layer: "top",
|
|
41
|
-
text: "gap",
|
|
42
|
-
anchor_position: { x: 50, y: 50 },
|
|
43
|
-
anchor_alignment: "top_left",
|
|
44
|
-
font: "tscircuit2024",
|
|
45
|
-
font_size: 16,
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Test with center alignment (baseline at center)
|
|
49
|
-
const textCenter: PcbFabricationNoteText = {
|
|
50
|
-
type: "pcb_fabrication_note_text",
|
|
51
|
-
pcb_fabrication_note_text_id: "fab-note-center",
|
|
52
|
-
pcb_component_id: "component2",
|
|
53
|
-
layer: "top",
|
|
54
|
-
text: "gap",
|
|
55
|
-
anchor_position: { x: 150, y: 100 },
|
|
56
|
-
anchor_alignment: "center",
|
|
57
|
-
font: "tscircuit2024",
|
|
58
|
-
font_size: 16,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Test with bottom alignment
|
|
62
|
-
const textBottom: PcbFabricationNoteText = {
|
|
63
|
-
type: "pcb_fabrication_note_text",
|
|
64
|
-
pcb_fabrication_note_text_id: "fab-note-bottom",
|
|
65
|
-
pcb_component_id: "component3",
|
|
66
|
-
layer: "top",
|
|
67
|
-
text: "gap",
|
|
68
|
-
anchor_position: { x: 250, y: 150 },
|
|
69
|
-
anchor_alignment: "bottom_left",
|
|
70
|
-
font: "tscircuit2024",
|
|
71
|
-
font_size: 16,
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
drawer.drawElements([textTop, textCenter, textBottom])
|
|
75
|
-
|
|
76
|
-
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
77
|
-
import.meta.path,
|
|
78
|
-
"fabrication-note-text-baseline-anchors",
|
|
79
|
-
)
|
|
80
|
-
})
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "bun:test"
|
|
2
|
-
import { createCanvas } from "canvas"
|
|
3
|
-
import type { PcbFabricationNoteText } from "circuit-json"
|
|
4
|
-
import { CircuitToCanvasDrawer } from "../../lib/drawer"
|
|
5
|
-
|
|
6
|
-
test("draw text with baseline alignment and descenders", async () => {
|
|
7
|
-
const SCALE = 4
|
|
8
|
-
const canvas = createCanvas(200 * SCALE, 150 * 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 baseline reference line
|
|
17
|
-
ctx.strokeStyle = "#444444"
|
|
18
|
-
ctx.lineWidth = 0.5
|
|
19
|
-
ctx.beginPath()
|
|
20
|
-
ctx.moveTo(10, 75)
|
|
21
|
-
ctx.lineTo(190, 75)
|
|
22
|
-
ctx.stroke()
|
|
23
|
-
|
|
24
|
-
// Test text with lowercase letters and descenders (g, j, p, q, y)
|
|
25
|
-
const text: PcbFabricationNoteText = {
|
|
26
|
-
type: "pcb_fabrication_note_text",
|
|
27
|
-
pcb_fabrication_note_text_id: "fab-note-baseline",
|
|
28
|
-
pcb_component_id: "component1",
|
|
29
|
-
layer: "top",
|
|
30
|
-
text: "gap jqpy",
|
|
31
|
-
anchor_position: { x: 100, y: 75 },
|
|
32
|
-
anchor_alignment: "center",
|
|
33
|
-
font: "tscircuit2024",
|
|
34
|
-
font_size: 20,
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
drawer.drawElements([text])
|
|
38
|
-
|
|
39
|
-
await expect(canvas.toBuffer("image/png")).toMatchPngSnapshot(
|
|
40
|
-
import.meta.path,
|
|
41
|
-
"fabrication-note-text-baseline",
|
|
42
|
-
)
|
|
43
|
-
})
|