circuit-to-canvas 0.0.3 → 0.0.4
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/README.md +1 -1
- package/dist/index.d.ts +74 -3
- package/dist/index.js +326 -4
- package/lib/drawer/CircuitToCanvasDrawer.ts +44 -0
- package/lib/drawer/elements/index.ts +20 -0
- package/lib/drawer/elements/pcb-copper-text.ts +99 -0
- package/lib/drawer/elements/pcb-fabrication-note-path.ts +41 -0
- package/lib/drawer/elements/pcb-fabrication-note-rect.ts +39 -0
- package/lib/drawer/elements/pcb-fabrication-note-text.ts +42 -0
- package/lib/drawer/shapes/index.ts +9 -0
- package/lib/drawer/shapes/rect.ts +28 -3
- package/lib/drawer/shapes/text.ts +218 -0
- package/lib/drawer/types.ts +8 -0
- package/package.json +3 -1
- 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-path-custom-color.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-path-thick-stroke.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-path.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-rect-all-features.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-rect-corner-radius.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-rect-custom-color.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-rect-dashed.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-fabrication-note-rect-default-color.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/pcb-copper-text.test.ts +102 -0
- package/tests/elements/pcb-fabrication-note-path-custom-color.test.ts +37 -0
- package/tests/elements/pcb-fabrication-note-path-thick-stroke.test.ts +37 -0
- package/tests/elements/pcb-fabrication-note-path.test.ts +36 -0
- package/tests/elements/pcb-fabrication-note-rect-all-features.test.ts +35 -0
- package/tests/elements/pcb-fabrication-note-rect-corner-radius.test.ts +33 -0
- package/tests/elements/pcb-fabrication-note-rect-custom-color.test.ts +32 -0
- package/tests/elements/pcb-fabrication-note-rect-dashed.test.ts +33 -0
- package/tests/elements/pcb-fabrication-note-rect-default-color.test.ts +32 -0
- package/tests/elements/pcb-fabrication-note-text-rgba-color.test.ts +34 -0
- package/tests/elements/pcb-fabrication-note-text-small.test.ts +33 -0
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ This checklist tracks PCB drawing features from [circuit-to-svg](https://github.
|
|
|
71
71
|
|
|
72
72
|
### Fabrication Notes
|
|
73
73
|
|
|
74
|
-
- [
|
|
74
|
+
- [x] `pcb_fabrication_note_text` - Fabrication note text
|
|
75
75
|
- [ ] `pcb_fabrication_note_rect` - Fabrication note rectangles
|
|
76
76
|
- [ ] `pcb_fabrication_note_path` - Fabrication note paths
|
|
77
77
|
- [ ] `pcb_fabrication_note_dimension` - Fabrication dimension annotations
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyCircuitElement, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbCutout, PcbCopperPour } from 'circuit-json';
|
|
1
|
+
import { AnyCircuitElement, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbFabricationNotePath } from 'circuit-json';
|
|
2
2
|
import { Matrix } from 'transformation-matrix';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -21,16 +21,24 @@ interface CanvasContext {
|
|
|
21
21
|
translate(x: number, y: number): void;
|
|
22
22
|
rotate(angle: number): void;
|
|
23
23
|
scale(x: number, y: number): void;
|
|
24
|
+
globalCompositeOperation?: string;
|
|
24
25
|
fillStyle: string | CanvasGradient | CanvasPattern;
|
|
25
26
|
strokeStyle: string | CanvasGradient | CanvasPattern;
|
|
26
27
|
lineWidth: number;
|
|
27
28
|
lineCap: "butt" | "round" | "square";
|
|
28
29
|
lineJoin: "bevel" | "round" | "miter";
|
|
30
|
+
setLineDash(segments: number[]): void;
|
|
29
31
|
canvas: {
|
|
30
32
|
width: number;
|
|
31
33
|
height: number;
|
|
32
34
|
};
|
|
33
35
|
fillText(text: string, x: number, y: number): void;
|
|
36
|
+
fillRect(x: number, y: number, width: number, height: number): void;
|
|
37
|
+
measureText?: (text: string) => {
|
|
38
|
+
width: number;
|
|
39
|
+
actualBoundingBoxAscent?: number;
|
|
40
|
+
actualBoundingBoxDescent?: number;
|
|
41
|
+
};
|
|
34
42
|
font: string;
|
|
35
43
|
textAlign: "start" | "end" | "left" | "right" | "center";
|
|
36
44
|
textBaseline: "top" | "hanging" | "middle" | "alphabetic" | "ideographic" | "bottom";
|
|
@@ -115,10 +123,13 @@ interface DrawRectParams {
|
|
|
115
123
|
};
|
|
116
124
|
width: number;
|
|
117
125
|
height: number;
|
|
118
|
-
fill
|
|
126
|
+
fill?: string;
|
|
119
127
|
transform: Matrix;
|
|
120
128
|
borderRadius?: number;
|
|
121
129
|
rotation?: number;
|
|
130
|
+
stroke?: string;
|
|
131
|
+
strokeWidth?: number;
|
|
132
|
+
isStrokeDashed?: boolean;
|
|
122
133
|
}
|
|
123
134
|
declare function drawRect(params: DrawRectParams): void;
|
|
124
135
|
|
|
@@ -192,6 +203,34 @@ interface DrawPathParams {
|
|
|
192
203
|
}
|
|
193
204
|
declare function drawPath(params: DrawPathParams): void;
|
|
194
205
|
|
|
206
|
+
type AlphabetLayout = {
|
|
207
|
+
width: number;
|
|
208
|
+
height: number;
|
|
209
|
+
glyphWidth: number;
|
|
210
|
+
letterSpacing: number;
|
|
211
|
+
spaceWidth: number;
|
|
212
|
+
strokeWidth: number;
|
|
213
|
+
};
|
|
214
|
+
declare function getAlphabetLayout(text: string, fontSize: number): AlphabetLayout;
|
|
215
|
+
type AnchorAlignment = "center" | "top_left" | "top_right" | "bottom_left" | "bottom_right" | "left" | "right" | "top" | "bottom";
|
|
216
|
+
declare function getTextStartPosition(alignment: AnchorAlignment, layout: AlphabetLayout): {
|
|
217
|
+
x: number;
|
|
218
|
+
y: number;
|
|
219
|
+
};
|
|
220
|
+
declare function strokeAlphabetText(ctx: CanvasContext, text: string, layout: AlphabetLayout, startX: number, startY: number): void;
|
|
221
|
+
interface DrawTextParams {
|
|
222
|
+
ctx: CanvasContext;
|
|
223
|
+
text: string;
|
|
224
|
+
x: number;
|
|
225
|
+
y: number;
|
|
226
|
+
fontSize: number;
|
|
227
|
+
color: string;
|
|
228
|
+
transform: Matrix;
|
|
229
|
+
anchorAlignment: AnchorAlignment;
|
|
230
|
+
rotation?: number;
|
|
231
|
+
}
|
|
232
|
+
declare function drawText(params: DrawTextParams): void;
|
|
233
|
+
|
|
195
234
|
interface DrawPcbPlatedHoleParams {
|
|
196
235
|
ctx: CanvasContext;
|
|
197
236
|
hole: PcbPlatedHole;
|
|
@@ -292,4 +331,36 @@ interface DrawPcbCopperPourParams {
|
|
|
292
331
|
}
|
|
293
332
|
declare function drawPcbCopperPour(params: DrawPcbCopperPourParams): void;
|
|
294
333
|
|
|
295
|
-
|
|
334
|
+
interface DrawPcbCopperTextParams {
|
|
335
|
+
ctx: CanvasContext;
|
|
336
|
+
text: PcbCopperText;
|
|
337
|
+
transform: Matrix;
|
|
338
|
+
colorMap: PcbColorMap;
|
|
339
|
+
}
|
|
340
|
+
declare function drawPcbCopperText(params: DrawPcbCopperTextParams): void;
|
|
341
|
+
|
|
342
|
+
interface DrawPcbFabricationNoteTextParams {
|
|
343
|
+
ctx: CanvasContext;
|
|
344
|
+
text: PcbFabricationNoteText;
|
|
345
|
+
transform: Matrix;
|
|
346
|
+
colorMap: PcbColorMap;
|
|
347
|
+
}
|
|
348
|
+
declare function drawPcbFabricationNoteText(params: DrawPcbFabricationNoteTextParams): void;
|
|
349
|
+
|
|
350
|
+
interface DrawPcbFabricationNoteRectParams {
|
|
351
|
+
ctx: CanvasContext;
|
|
352
|
+
rect: PcbFabricationNoteRect;
|
|
353
|
+
transform: Matrix;
|
|
354
|
+
colorMap: PcbColorMap;
|
|
355
|
+
}
|
|
356
|
+
declare function drawPcbFabricationNoteRect(params: DrawPcbFabricationNoteRectParams): void;
|
|
357
|
+
|
|
358
|
+
interface DrawPcbFabricationNotePathParams {
|
|
359
|
+
ctx: CanvasContext;
|
|
360
|
+
path: PcbFabricationNotePath;
|
|
361
|
+
transform: Matrix;
|
|
362
|
+
colorMap: PcbColorMap;
|
|
363
|
+
}
|
|
364
|
+
declare function drawPcbFabricationNotePath(params: DrawPcbFabricationNotePathParams): void;
|
|
365
|
+
|
|
366
|
+
export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawCircleParams, type DrawContext, type DrawElementsOptions, type DrawLineParams, type DrawOvalParams, type DrawPathParams, type DrawPcbBoardParams, type DrawPcbCopperPourParams, type DrawPcbCopperTextParams, type DrawPcbCutoutParams, type DrawPcbFabricationNotePathParams, type DrawPcbFabricationNoteRectParams, type DrawPcbFabricationNoteTextParams, type DrawPcbHoleParams, type DrawPcbPlatedHoleParams, type DrawPcbSilkscreenCircleParams, type DrawPcbSilkscreenLineParams, type DrawPcbSilkscreenPathParams, type DrawPcbSilkscreenRectParams, type DrawPcbSilkscreenTextParams, type DrawPcbSmtPadParams, type DrawPcbTraceParams, type DrawPcbViaParams, type DrawPillParams, type DrawPolygonParams, type DrawRectParams, type DrawTextParams, type DrawerConfig, type PcbColorMap, drawCircle, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenPath, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
|
package/dist/index.js
CHANGED
|
@@ -58,17 +58,26 @@ function drawRect(params) {
|
|
|
58
58
|
fill,
|
|
59
59
|
transform,
|
|
60
60
|
borderRadius = 0,
|
|
61
|
-
rotation = 0
|
|
61
|
+
rotation = 0,
|
|
62
|
+
stroke,
|
|
63
|
+
strokeWidth,
|
|
64
|
+
isStrokeDashed = false
|
|
62
65
|
} = params;
|
|
63
66
|
const [cx, cy] = applyToPoint2(transform, [center.x, center.y]);
|
|
64
67
|
const scaledWidth = width * Math.abs(transform.a);
|
|
65
68
|
const scaledHeight = height * Math.abs(transform.a);
|
|
66
69
|
const scaledRadius = borderRadius * Math.abs(transform.a);
|
|
70
|
+
const scaledStrokeWidth = strokeWidth ? strokeWidth * Math.abs(transform.a) : void 0;
|
|
67
71
|
ctx.save();
|
|
68
72
|
ctx.translate(cx, cy);
|
|
69
73
|
if (rotation !== 0) {
|
|
70
74
|
ctx.rotate(-rotation * (Math.PI / 180));
|
|
71
75
|
}
|
|
76
|
+
if (isStrokeDashed && scaledStrokeWidth) {
|
|
77
|
+
ctx.setLineDash([scaledStrokeWidth * 2, scaledStrokeWidth * 2]);
|
|
78
|
+
} else {
|
|
79
|
+
ctx.setLineDash([]);
|
|
80
|
+
}
|
|
72
81
|
ctx.beginPath();
|
|
73
82
|
if (scaledRadius > 0) {
|
|
74
83
|
const x = -scaledWidth / 2;
|
|
@@ -92,8 +101,15 @@ function drawRect(params) {
|
|
|
92
101
|
} else {
|
|
93
102
|
ctx.rect(-scaledWidth / 2, -scaledHeight / 2, scaledWidth, scaledHeight);
|
|
94
103
|
}
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
if (fill) {
|
|
105
|
+
ctx.fillStyle = fill;
|
|
106
|
+
ctx.fill();
|
|
107
|
+
}
|
|
108
|
+
if (stroke && scaledStrokeWidth) {
|
|
109
|
+
ctx.strokeStyle = stroke;
|
|
110
|
+
ctx.lineWidth = scaledStrokeWidth;
|
|
111
|
+
ctx.stroke();
|
|
112
|
+
}
|
|
97
113
|
ctx.restore();
|
|
98
114
|
}
|
|
99
115
|
|
|
@@ -794,6 +810,272 @@ function drawPcbCopperPour(params) {
|
|
|
794
810
|
ctx.restore();
|
|
795
811
|
}
|
|
796
812
|
|
|
813
|
+
// lib/drawer/elements/pcb-copper-text.ts
|
|
814
|
+
import { applyToPoint as applyToPoint11 } from "transformation-matrix";
|
|
815
|
+
|
|
816
|
+
// lib/drawer/shapes/text.ts
|
|
817
|
+
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
818
|
+
import { applyToPoint as applyToPoint10 } from "transformation-matrix";
|
|
819
|
+
var GLYPH_WIDTH_RATIO = 0.62;
|
|
820
|
+
var LETTER_SPACING_RATIO = 0.3;
|
|
821
|
+
var SPACE_WIDTH_RATIO = 1;
|
|
822
|
+
var STROKE_WIDTH_RATIO = 0.13;
|
|
823
|
+
var CURVED_GLYPHS = /* @__PURE__ */ new Set(["O", "o", "0"]);
|
|
824
|
+
function getAlphabetLayout(text, fontSize) {
|
|
825
|
+
const glyphWidth = fontSize * GLYPH_WIDTH_RATIO;
|
|
826
|
+
const letterSpacing = glyphWidth * LETTER_SPACING_RATIO;
|
|
827
|
+
const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO;
|
|
828
|
+
const characters = Array.from(text);
|
|
829
|
+
let width = 0;
|
|
830
|
+
characters.forEach((char, index) => {
|
|
831
|
+
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
832
|
+
width += advance;
|
|
833
|
+
if (index < characters.length - 1) width += letterSpacing;
|
|
834
|
+
});
|
|
835
|
+
const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35);
|
|
836
|
+
return {
|
|
837
|
+
width,
|
|
838
|
+
height: fontSize,
|
|
839
|
+
glyphWidth,
|
|
840
|
+
letterSpacing,
|
|
841
|
+
spaceWidth,
|
|
842
|
+
strokeWidth
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
|
|
846
|
+
function getTextStartPosition(alignment, layout) {
|
|
847
|
+
const totalWidth = layout.width + layout.strokeWidth;
|
|
848
|
+
const totalHeight = layout.height + layout.strokeWidth;
|
|
849
|
+
let x = 0;
|
|
850
|
+
let y = 0;
|
|
851
|
+
if (alignment === "center") {
|
|
852
|
+
x = -totalWidth / 2;
|
|
853
|
+
} else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "left") {
|
|
854
|
+
x = 0;
|
|
855
|
+
} else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "right") {
|
|
856
|
+
x = -totalWidth;
|
|
857
|
+
}
|
|
858
|
+
if (alignment === "center") {
|
|
859
|
+
y = -totalHeight / 2;
|
|
860
|
+
} else if (alignment === "top_left" || alignment === "top_right" || alignment === "top") {
|
|
861
|
+
y = 0;
|
|
862
|
+
} else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom") {
|
|
863
|
+
y = -totalHeight;
|
|
864
|
+
}
|
|
865
|
+
return { x, y };
|
|
866
|
+
}
|
|
867
|
+
function strokeAlphabetText(ctx, text, layout, startX, startY) {
|
|
868
|
+
const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout;
|
|
869
|
+
const yOffset = startY + height / 2;
|
|
870
|
+
const characters = Array.from(text);
|
|
871
|
+
let cursor = startX + strokeWidth / 2;
|
|
872
|
+
characters.forEach((char, index) => {
|
|
873
|
+
const lines = getGlyphLines(char);
|
|
874
|
+
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
875
|
+
if (CURVED_GLYPHS.has(char)) {
|
|
876
|
+
const radiusX = Math.max(glyphWidth / 2 - strokeWidth / 2, strokeWidth);
|
|
877
|
+
const radiusY = Math.max(height / 2 - strokeWidth / 2, strokeWidth);
|
|
878
|
+
const centerY = yOffset - height / 2;
|
|
879
|
+
ctx.beginPath();
|
|
880
|
+
ctx.ellipse(
|
|
881
|
+
cursor + glyphWidth / 2,
|
|
882
|
+
centerY,
|
|
883
|
+
radiusX,
|
|
884
|
+
radiusY,
|
|
885
|
+
0,
|
|
886
|
+
0,
|
|
887
|
+
Math.PI * 2
|
|
888
|
+
);
|
|
889
|
+
ctx.stroke();
|
|
890
|
+
} else if (lines?.length) {
|
|
891
|
+
ctx.beginPath();
|
|
892
|
+
for (const line of lines) {
|
|
893
|
+
const x1 = cursor + line.x1 * glyphWidth;
|
|
894
|
+
const y1 = yOffset - line.y1 * height;
|
|
895
|
+
const x2 = cursor + line.x2 * glyphWidth;
|
|
896
|
+
const y2 = yOffset - line.y2 * height;
|
|
897
|
+
ctx.moveTo(x1, y1);
|
|
898
|
+
ctx.lineTo(x2, y2);
|
|
899
|
+
}
|
|
900
|
+
ctx.stroke();
|
|
901
|
+
}
|
|
902
|
+
cursor += advance;
|
|
903
|
+
if (index < characters.length - 1) {
|
|
904
|
+
cursor += letterSpacing;
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
function drawText(params) {
|
|
909
|
+
const {
|
|
910
|
+
ctx,
|
|
911
|
+
text,
|
|
912
|
+
x,
|
|
913
|
+
y,
|
|
914
|
+
fontSize,
|
|
915
|
+
color,
|
|
916
|
+
transform,
|
|
917
|
+
anchorAlignment,
|
|
918
|
+
rotation = 0
|
|
919
|
+
} = params;
|
|
920
|
+
if (!text) return;
|
|
921
|
+
const [transformedX, transformedY] = applyToPoint10(transform, [x, y]);
|
|
922
|
+
const scale2 = Math.abs(transform.a);
|
|
923
|
+
const scaledFontSize = fontSize * scale2;
|
|
924
|
+
const layout = getAlphabetLayout(text, scaledFontSize);
|
|
925
|
+
const startPos = getTextStartPosition(anchorAlignment, layout);
|
|
926
|
+
ctx.save();
|
|
927
|
+
ctx.translate(transformedX, transformedY);
|
|
928
|
+
if (rotation !== 0) {
|
|
929
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
930
|
+
}
|
|
931
|
+
ctx.lineWidth = layout.strokeWidth;
|
|
932
|
+
ctx.lineCap = "round";
|
|
933
|
+
ctx.lineJoin = "round";
|
|
934
|
+
ctx.strokeStyle = color;
|
|
935
|
+
strokeAlphabetText(
|
|
936
|
+
ctx,
|
|
937
|
+
text,
|
|
938
|
+
layout,
|
|
939
|
+
startPos.x,
|
|
940
|
+
startPos.y + layout.strokeWidth / 2
|
|
941
|
+
);
|
|
942
|
+
ctx.restore();
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// lib/drawer/elements/pcb-copper-text.ts
|
|
946
|
+
var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
|
|
947
|
+
function layerToCopperColor(layer, colorMap) {
|
|
948
|
+
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
949
|
+
}
|
|
950
|
+
function mapAnchorAlignment2(alignment) {
|
|
951
|
+
if (!alignment) return "center";
|
|
952
|
+
if (alignment.includes("left")) return "left";
|
|
953
|
+
if (alignment.includes("right")) return "right";
|
|
954
|
+
return "center";
|
|
955
|
+
}
|
|
956
|
+
function drawPcbCopperText(params) {
|
|
957
|
+
const { ctx, text, transform, colorMap } = params;
|
|
958
|
+
const content = text.text ?? "";
|
|
959
|
+
if (!content) return;
|
|
960
|
+
const [x, y] = applyToPoint11(transform, [
|
|
961
|
+
text.anchor_position.x,
|
|
962
|
+
text.anchor_position.y
|
|
963
|
+
]);
|
|
964
|
+
const scale2 = Math.abs(transform.a);
|
|
965
|
+
const fontSize = (text.font_size ?? 1) * scale2;
|
|
966
|
+
const rotation = text.ccw_rotation ?? 0;
|
|
967
|
+
const padding = {
|
|
968
|
+
...DEFAULT_PADDING,
|
|
969
|
+
...text.knockout_padding
|
|
970
|
+
};
|
|
971
|
+
const textColor = layerToCopperColor(text.layer, colorMap);
|
|
972
|
+
const layout = getAlphabetLayout(content, fontSize);
|
|
973
|
+
const totalWidth = layout.width + layout.strokeWidth;
|
|
974
|
+
const totalHeight = layout.height + layout.strokeWidth;
|
|
975
|
+
const alignment = mapAnchorAlignment2(text.anchor_alignment);
|
|
976
|
+
const startPos = getTextStartPosition(alignment, layout);
|
|
977
|
+
const startX = startPos.x;
|
|
978
|
+
const startY = 0;
|
|
979
|
+
ctx.save();
|
|
980
|
+
ctx.translate(x, y);
|
|
981
|
+
if (text.is_mirrored) ctx.scale(-1, 1);
|
|
982
|
+
if (rotation !== 0) ctx.rotate(-rotation * (Math.PI / 180));
|
|
983
|
+
ctx.lineWidth = layout.strokeWidth;
|
|
984
|
+
ctx.lineCap = "round";
|
|
985
|
+
ctx.lineJoin = "round";
|
|
986
|
+
if (text.is_knockout) {
|
|
987
|
+
const paddingLeft = padding.left * scale2;
|
|
988
|
+
const paddingRight = padding.right * scale2;
|
|
989
|
+
const paddingTop = padding.top * scale2;
|
|
990
|
+
const paddingBottom = padding.bottom * scale2;
|
|
991
|
+
const xOffset = startX - paddingLeft;
|
|
992
|
+
const yOffset = -(layout.height / 2) - layout.strokeWidth / 2 - paddingTop;
|
|
993
|
+
const knockoutWidth = totalWidth + paddingLeft + paddingRight;
|
|
994
|
+
const knockoutHeight = totalHeight + paddingTop + paddingBottom;
|
|
995
|
+
ctx.fillStyle = textColor;
|
|
996
|
+
ctx.fillRect(xOffset, yOffset, knockoutWidth, knockoutHeight);
|
|
997
|
+
const previousCompositeOperation = ctx.globalCompositeOperation;
|
|
998
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
999
|
+
ctx.fillStyle = "rgba(0,0,0,1)";
|
|
1000
|
+
ctx.strokeStyle = "rgba(0,0,0,1)";
|
|
1001
|
+
strokeAlphabetText(ctx, content, layout, startX, startY);
|
|
1002
|
+
if (previousCompositeOperation) {
|
|
1003
|
+
ctx.globalCompositeOperation = previousCompositeOperation;
|
|
1004
|
+
} else {
|
|
1005
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1006
|
+
}
|
|
1007
|
+
} else {
|
|
1008
|
+
ctx.strokeStyle = textColor;
|
|
1009
|
+
strokeAlphabetText(ctx, content, layout, startX, startY);
|
|
1010
|
+
}
|
|
1011
|
+
ctx.restore();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// lib/drawer/elements/pcb-fabrication-note-text.ts
|
|
1015
|
+
var DEFAULT_FABRICATION_NOTE_COLOR = "rgba(255,255,255,0.5)";
|
|
1016
|
+
function layerToColor4(layer, colorMap) {
|
|
1017
|
+
return DEFAULT_FABRICATION_NOTE_COLOR;
|
|
1018
|
+
}
|
|
1019
|
+
function drawPcbFabricationNoteText(params) {
|
|
1020
|
+
const { ctx, text, transform, colorMap } = params;
|
|
1021
|
+
const defaultColor = layerToColor4(text.layer, colorMap);
|
|
1022
|
+
const color = text.color ?? defaultColor;
|
|
1023
|
+
const fontSize = text.font_size;
|
|
1024
|
+
drawText({
|
|
1025
|
+
ctx,
|
|
1026
|
+
text: text.text,
|
|
1027
|
+
x: text.anchor_position.x,
|
|
1028
|
+
y: text.anchor_position.y,
|
|
1029
|
+
fontSize,
|
|
1030
|
+
color,
|
|
1031
|
+
transform,
|
|
1032
|
+
anchorAlignment: text.anchor_alignment
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// lib/drawer/elements/pcb-fabrication-note-rect.ts
|
|
1037
|
+
function drawPcbFabricationNoteRect(params) {
|
|
1038
|
+
const { ctx, rect, transform, colorMap } = params;
|
|
1039
|
+
const defaultColor = "rgba(255,255,255,0.5)";
|
|
1040
|
+
const color = rect.color ?? defaultColor;
|
|
1041
|
+
const isFilled = rect.is_filled ?? false;
|
|
1042
|
+
const hasStroke = rect.has_stroke ?? true;
|
|
1043
|
+
const isStrokeDashed = rect.is_stroke_dashed ?? false;
|
|
1044
|
+
drawRect({
|
|
1045
|
+
ctx,
|
|
1046
|
+
center: rect.center,
|
|
1047
|
+
width: rect.width,
|
|
1048
|
+
height: rect.height,
|
|
1049
|
+
fill: isFilled ? color : void 0,
|
|
1050
|
+
stroke: hasStroke ? color : void 0,
|
|
1051
|
+
strokeWidth: hasStroke ? rect.stroke_width : void 0,
|
|
1052
|
+
borderRadius: rect.corner_radius,
|
|
1053
|
+
transform,
|
|
1054
|
+
isStrokeDashed
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// lib/drawer/elements/pcb-fabrication-note-path.ts
|
|
1059
|
+
function drawPcbFabricationNotePath(params) {
|
|
1060
|
+
const { ctx, path, transform, colorMap } = params;
|
|
1061
|
+
const defaultColor = "rgba(255,255,255,0.5)";
|
|
1062
|
+
const color = path.color ?? defaultColor;
|
|
1063
|
+
if (!path.route || path.route.length < 2) return;
|
|
1064
|
+
for (let i = 0; i < path.route.length - 1; i++) {
|
|
1065
|
+
const start = path.route[i];
|
|
1066
|
+
const end = path.route[i + 1];
|
|
1067
|
+
if (!start || !end) continue;
|
|
1068
|
+
drawLine({
|
|
1069
|
+
ctx,
|
|
1070
|
+
start: { x: start.x, y: start.y },
|
|
1071
|
+
end: { x: end.x, y: end.y },
|
|
1072
|
+
strokeWidth: path.stroke_width ?? 0.1,
|
|
1073
|
+
stroke: color,
|
|
1074
|
+
transform
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
797
1079
|
// lib/drawer/CircuitToCanvasDrawer.ts
|
|
798
1080
|
var CircuitToCanvasDrawer = class {
|
|
799
1081
|
ctx;
|
|
@@ -967,6 +1249,38 @@ var CircuitToCanvasDrawer = class {
|
|
|
967
1249
|
colorMap: this.colorMap
|
|
968
1250
|
});
|
|
969
1251
|
}
|
|
1252
|
+
if (element.type === "pcb_copper_text") {
|
|
1253
|
+
drawPcbCopperText({
|
|
1254
|
+
ctx: this.ctx,
|
|
1255
|
+
text: element,
|
|
1256
|
+
transform: this.realToCanvasMat,
|
|
1257
|
+
colorMap: this.colorMap
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
if (element.type === "pcb_fabrication_note_text") {
|
|
1261
|
+
drawPcbFabricationNoteText({
|
|
1262
|
+
ctx: this.ctx,
|
|
1263
|
+
text: element,
|
|
1264
|
+
transform: this.realToCanvasMat,
|
|
1265
|
+
colorMap: this.colorMap
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
if (element.type === "pcb_fabrication_note_rect") {
|
|
1269
|
+
drawPcbFabricationNoteRect({
|
|
1270
|
+
ctx: this.ctx,
|
|
1271
|
+
rect: element,
|
|
1272
|
+
transform: this.realToCanvasMat,
|
|
1273
|
+
colorMap: this.colorMap
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
if (element.type === "pcb_fabrication_note_path") {
|
|
1277
|
+
drawPcbFabricationNotePath({
|
|
1278
|
+
ctx: this.ctx,
|
|
1279
|
+
path: element,
|
|
1280
|
+
transform: this.realToCanvasMat,
|
|
1281
|
+
colorMap: this.colorMap
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
970
1284
|
}
|
|
971
1285
|
};
|
|
972
1286
|
export {
|
|
@@ -978,7 +1292,11 @@ export {
|
|
|
978
1292
|
drawPath,
|
|
979
1293
|
drawPcbBoard,
|
|
980
1294
|
drawPcbCopperPour,
|
|
1295
|
+
drawPcbCopperText,
|
|
981
1296
|
drawPcbCutout,
|
|
1297
|
+
drawPcbFabricationNotePath,
|
|
1298
|
+
drawPcbFabricationNoteRect,
|
|
1299
|
+
drawPcbFabricationNoteText,
|
|
982
1300
|
drawPcbHole,
|
|
983
1301
|
drawPcbPlatedHole,
|
|
984
1302
|
drawPcbSilkscreenCircle,
|
|
@@ -991,5 +1309,9 @@ export {
|
|
|
991
1309
|
drawPcbVia,
|
|
992
1310
|
drawPill,
|
|
993
1311
|
drawPolygon,
|
|
994
|
-
drawRect
|
|
1312
|
+
drawRect,
|
|
1313
|
+
drawText,
|
|
1314
|
+
getAlphabetLayout,
|
|
1315
|
+
getTextStartPosition,
|
|
1316
|
+
strokeAlphabetText
|
|
995
1317
|
};
|
|
@@ -13,6 +13,10 @@ import type {
|
|
|
13
13
|
PcbSilkscreenPath,
|
|
14
14
|
PcbCutout,
|
|
15
15
|
PcbCopperPour,
|
|
16
|
+
PcbCopperText,
|
|
17
|
+
PcbFabricationNoteText,
|
|
18
|
+
PcbFabricationNoteRect,
|
|
19
|
+
PcbFabricationNotePath,
|
|
16
20
|
} from "circuit-json"
|
|
17
21
|
import { identity, compose, translate, scale } from "transformation-matrix"
|
|
18
22
|
import type { Matrix } from "transformation-matrix"
|
|
@@ -38,6 +42,10 @@ import {
|
|
|
38
42
|
} from "./elements/pcb-silkscreen"
|
|
39
43
|
import { drawPcbCutout } from "./elements/pcb-cutout"
|
|
40
44
|
import { drawPcbCopperPour } from "./elements/pcb-copper-pour"
|
|
45
|
+
import { drawPcbCopperText } from "./elements/pcb-copper-text"
|
|
46
|
+
import { drawPcbFabricationNoteText } from "./elements/pcb-fabrication-note-text"
|
|
47
|
+
import { drawPcbFabricationNoteRect } from "./elements/pcb-fabrication-note-rect"
|
|
48
|
+
import { drawPcbFabricationNotePath } from "./elements/pcb-fabrication-note-path"
|
|
41
49
|
|
|
42
50
|
export interface DrawElementsOptions {
|
|
43
51
|
layers?: string[]
|
|
@@ -252,5 +260,41 @@ export class CircuitToCanvasDrawer {
|
|
|
252
260
|
colorMap: this.colorMap,
|
|
253
261
|
})
|
|
254
262
|
}
|
|
263
|
+
|
|
264
|
+
if (element.type === "pcb_copper_text") {
|
|
265
|
+
drawPcbCopperText({
|
|
266
|
+
ctx: this.ctx,
|
|
267
|
+
text: element as PcbCopperText,
|
|
268
|
+
transform: this.realToCanvasMat,
|
|
269
|
+
colorMap: this.colorMap,
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (element.type === "pcb_fabrication_note_text") {
|
|
274
|
+
drawPcbFabricationNoteText({
|
|
275
|
+
ctx: this.ctx,
|
|
276
|
+
text: element as PcbFabricationNoteText,
|
|
277
|
+
transform: this.realToCanvasMat,
|
|
278
|
+
colorMap: this.colorMap,
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (element.type === "pcb_fabrication_note_rect") {
|
|
283
|
+
drawPcbFabricationNoteRect({
|
|
284
|
+
ctx: this.ctx,
|
|
285
|
+
rect: element as PcbFabricationNoteRect,
|
|
286
|
+
transform: this.realToCanvasMat,
|
|
287
|
+
colorMap: this.colorMap,
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (element.type === "pcb_fabrication_note_path") {
|
|
292
|
+
drawPcbFabricationNotePath({
|
|
293
|
+
ctx: this.ctx,
|
|
294
|
+
path: element as PcbFabricationNotePath,
|
|
295
|
+
transform: this.realToCanvasMat,
|
|
296
|
+
colorMap: this.colorMap,
|
|
297
|
+
})
|
|
298
|
+
}
|
|
255
299
|
}
|
|
256
300
|
}
|
|
@@ -32,3 +32,23 @@ export {
|
|
|
32
32
|
drawPcbCopperPour,
|
|
33
33
|
type DrawPcbCopperPourParams,
|
|
34
34
|
} from "./pcb-copper-pour"
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
drawPcbCopperText,
|
|
38
|
+
type DrawPcbCopperTextParams,
|
|
39
|
+
} from "./pcb-copper-text"
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
drawPcbFabricationNoteText,
|
|
43
|
+
type DrawPcbFabricationNoteTextParams,
|
|
44
|
+
} from "./pcb-fabrication-note-text"
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
drawPcbFabricationNoteRect,
|
|
48
|
+
type DrawPcbFabricationNoteRectParams,
|
|
49
|
+
} from "./pcb-fabrication-note-rect"
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
drawPcbFabricationNotePath,
|
|
53
|
+
type DrawPcbFabricationNotePathParams,
|
|
54
|
+
} from "./pcb-fabrication-note-path"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { PcbCopperText } from "circuit-json"
|
|
2
|
+
import type { Matrix } from "transformation-matrix"
|
|
3
|
+
import { applyToPoint } from "transformation-matrix"
|
|
4
|
+
import type { PcbColorMap, CanvasContext } from "../types"
|
|
5
|
+
import {
|
|
6
|
+
getAlphabetLayout,
|
|
7
|
+
strokeAlphabetText,
|
|
8
|
+
getTextStartPosition,
|
|
9
|
+
type AnchorAlignment,
|
|
10
|
+
} from "../shapes/text"
|
|
11
|
+
|
|
12
|
+
export interface DrawPcbCopperTextParams {
|
|
13
|
+
ctx: CanvasContext
|
|
14
|
+
text: PcbCopperText
|
|
15
|
+
transform: Matrix
|
|
16
|
+
colorMap: PcbColorMap
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 }
|
|
20
|
+
|
|
21
|
+
function layerToCopperColor(layer: string, colorMap: PcbColorMap): string {
|
|
22
|
+
return (
|
|
23
|
+
colorMap.copper[layer as keyof typeof colorMap.copper] ??
|
|
24
|
+
colorMap.copper.top
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function mapAnchorAlignment(alignment?: string): AnchorAlignment {
|
|
29
|
+
if (!alignment) return "center"
|
|
30
|
+
if (alignment.includes("left")) return "left"
|
|
31
|
+
if (alignment.includes("right")) return "right"
|
|
32
|
+
return "center"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function drawPcbCopperText(params: DrawPcbCopperTextParams): void {
|
|
36
|
+
const { ctx, text, transform, colorMap } = params
|
|
37
|
+
|
|
38
|
+
const content = text.text ?? ""
|
|
39
|
+
if (!content) return
|
|
40
|
+
|
|
41
|
+
const [x, y] = applyToPoint(transform, [
|
|
42
|
+
text.anchor_position.x,
|
|
43
|
+
text.anchor_position.y,
|
|
44
|
+
])
|
|
45
|
+
const scale = Math.abs(transform.a)
|
|
46
|
+
const fontSize = (text.font_size ?? 1) * scale
|
|
47
|
+
const rotation = text.ccw_rotation ?? 0
|
|
48
|
+
const padding = {
|
|
49
|
+
...DEFAULT_PADDING,
|
|
50
|
+
...text.knockout_padding,
|
|
51
|
+
}
|
|
52
|
+
const textColor = layerToCopperColor(text.layer, colorMap)
|
|
53
|
+
const layout = getAlphabetLayout(content, fontSize)
|
|
54
|
+
const totalWidth = layout.width + layout.strokeWidth
|
|
55
|
+
const totalHeight = layout.height + layout.strokeWidth
|
|
56
|
+
const alignment = mapAnchorAlignment(text.anchor_alignment)
|
|
57
|
+
const startPos = getTextStartPosition(alignment, layout)
|
|
58
|
+
// Copper text always centers vertically (startY=0), uses startPos.x for horizontal alignment
|
|
59
|
+
const startX = startPos.x
|
|
60
|
+
const startY = 0 // Centers vertically at y=0 (shared function calculates yOffset = startY + height/2)
|
|
61
|
+
|
|
62
|
+
ctx.save()
|
|
63
|
+
ctx.translate(x, y)
|
|
64
|
+
if (text.is_mirrored) ctx.scale(-1, 1)
|
|
65
|
+
if (rotation !== 0) ctx.rotate(-rotation * (Math.PI / 180))
|
|
66
|
+
|
|
67
|
+
ctx.lineWidth = layout.strokeWidth
|
|
68
|
+
ctx.lineCap = "round"
|
|
69
|
+
ctx.lineJoin = "round"
|
|
70
|
+
|
|
71
|
+
if (text.is_knockout) {
|
|
72
|
+
const paddingLeft = padding.left * scale
|
|
73
|
+
const paddingRight = padding.right * scale
|
|
74
|
+
const paddingTop = padding.top * scale
|
|
75
|
+
const paddingBottom = padding.bottom * scale
|
|
76
|
+
const xOffset = startX - paddingLeft
|
|
77
|
+
const yOffset = -(layout.height / 2) - layout.strokeWidth / 2 - paddingTop
|
|
78
|
+
const knockoutWidth = totalWidth + paddingLeft + paddingRight
|
|
79
|
+
const knockoutHeight = totalHeight + paddingTop + paddingBottom
|
|
80
|
+
|
|
81
|
+
ctx.fillStyle = textColor
|
|
82
|
+
ctx.fillRect(xOffset, yOffset, knockoutWidth, knockoutHeight)
|
|
83
|
+
|
|
84
|
+
const previousCompositeOperation = ctx.globalCompositeOperation
|
|
85
|
+
ctx.globalCompositeOperation = "destination-out"
|
|
86
|
+
ctx.fillStyle = "rgba(0,0,0,1)"
|
|
87
|
+
ctx.strokeStyle = "rgba(0,0,0,1)"
|
|
88
|
+
strokeAlphabetText(ctx, content, layout, startX, startY)
|
|
89
|
+
if (previousCompositeOperation) {
|
|
90
|
+
ctx.globalCompositeOperation = previousCompositeOperation
|
|
91
|
+
} else {
|
|
92
|
+
ctx.globalCompositeOperation = "source-over"
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
ctx.strokeStyle = textColor
|
|
96
|
+
strokeAlphabetText(ctx, content, layout, startX, startY)
|
|
97
|
+
}
|
|
98
|
+
ctx.restore()
|
|
99
|
+
}
|