circuit-to-canvas 0.0.20 → 0.0.21
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.js +148 -136
- package/lib/drawer/elements/pcb-silkscreen.ts +29 -14
- package/package.json +2 -2
- package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
- package/tests/board-snapshot/usb-c-flashlight-board.test.ts +1 -1
- package/tests/elements/__snapshots__/pcb-note-dimension-angled-and-vertical.snap.png +0 -0
- package/tests/fixtures/stackPngsVertically.ts +4 -43
package/dist/index.js
CHANGED
|
@@ -647,141 +647,36 @@ function drawPcbBoard(params) {
|
|
|
647
647
|
}
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
// lib/drawer/shapes/text/text.ts
|
|
651
|
-
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
652
|
-
import { applyToPoint as applyToPoint8 } from "transformation-matrix";
|
|
653
|
-
|
|
654
|
-
// lib/drawer/shapes/text/getAlphabetLayout.ts
|
|
655
|
-
var GLYPH_WIDTH_RATIO = 0.62;
|
|
656
|
-
var LETTER_SPACING_RATIO = 0.3;
|
|
657
|
-
var SPACE_WIDTH_RATIO = 1;
|
|
658
|
-
var STROKE_WIDTH_RATIO = 0.13;
|
|
659
|
-
function getAlphabetLayout(text, fontSize) {
|
|
660
|
-
const glyphWidth = fontSize * GLYPH_WIDTH_RATIO;
|
|
661
|
-
const letterSpacing = glyphWidth * LETTER_SPACING_RATIO;
|
|
662
|
-
const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO;
|
|
663
|
-
const characters = Array.from(text);
|
|
664
|
-
let width = 0;
|
|
665
|
-
characters.forEach((char, index) => {
|
|
666
|
-
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
667
|
-
width += advance;
|
|
668
|
-
if (index < characters.length - 1) width += letterSpacing;
|
|
669
|
-
});
|
|
670
|
-
const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35);
|
|
671
|
-
return {
|
|
672
|
-
width,
|
|
673
|
-
height: fontSize,
|
|
674
|
-
glyphWidth,
|
|
675
|
-
letterSpacing,
|
|
676
|
-
spaceWidth,
|
|
677
|
-
strokeWidth
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// lib/drawer/shapes/text/getTextStartPosition.ts
|
|
682
|
-
function getTextStartPosition(alignment, layout) {
|
|
683
|
-
const totalWidth = layout.width + layout.strokeWidth;
|
|
684
|
-
const totalHeight = layout.height + layout.strokeWidth;
|
|
685
|
-
let x = 0;
|
|
686
|
-
let y = 0;
|
|
687
|
-
if (alignment === "center") {
|
|
688
|
-
x = -totalWidth / 2;
|
|
689
|
-
} else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "center_left") {
|
|
690
|
-
x = 0;
|
|
691
|
-
} else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "center_right") {
|
|
692
|
-
x = -totalWidth;
|
|
693
|
-
}
|
|
694
|
-
if (alignment === "center") {
|
|
695
|
-
y = -totalHeight / 2;
|
|
696
|
-
} else if (alignment === "top_left" || alignment === "top_right" || alignment === "top_center") {
|
|
697
|
-
y = 0;
|
|
698
|
-
} else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom_center") {
|
|
699
|
-
y = -totalHeight;
|
|
700
|
-
} else {
|
|
701
|
-
y = 0;
|
|
702
|
-
}
|
|
703
|
-
return { x, y };
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// lib/drawer/shapes/text/text.ts
|
|
707
|
-
var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
|
|
708
|
-
function strokeAlphabetText(ctx, text, layout, startX, startY) {
|
|
709
|
-
const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout;
|
|
710
|
-
const topY = startY;
|
|
711
|
-
const characters = Array.from(text);
|
|
712
|
-
let cursor = startX + strokeWidth / 2;
|
|
713
|
-
characters.forEach((char, index) => {
|
|
714
|
-
const lines = getGlyphLines(char);
|
|
715
|
-
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
716
|
-
if (lines?.length) {
|
|
717
|
-
ctx.beginPath();
|
|
718
|
-
for (const line of lines) {
|
|
719
|
-
const x1 = cursor + line.x1 * glyphWidth;
|
|
720
|
-
const y1 = topY + (1 - line.y1) * height;
|
|
721
|
-
const x2 = cursor + line.x2 * glyphWidth;
|
|
722
|
-
const y2 = topY + (1 - line.y2) * height;
|
|
723
|
-
ctx.moveTo(x1, y1);
|
|
724
|
-
ctx.lineTo(x2, y2);
|
|
725
|
-
}
|
|
726
|
-
ctx.stroke();
|
|
727
|
-
}
|
|
728
|
-
cursor += advance;
|
|
729
|
-
if (index < characters.length - 1) {
|
|
730
|
-
cursor += letterSpacing;
|
|
731
|
-
}
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
function drawText(params) {
|
|
735
|
-
const {
|
|
736
|
-
ctx,
|
|
737
|
-
text,
|
|
738
|
-
x,
|
|
739
|
-
y,
|
|
740
|
-
fontSize,
|
|
741
|
-
color,
|
|
742
|
-
realToCanvasMat,
|
|
743
|
-
anchorAlignment,
|
|
744
|
-
rotation = 0
|
|
745
|
-
} = params;
|
|
746
|
-
if (!text) return;
|
|
747
|
-
const [canvasX, canvasY] = applyToPoint8(realToCanvasMat, [x, y]);
|
|
748
|
-
const scale2 = Math.abs(realToCanvasMat.a);
|
|
749
|
-
const scaledFontSize = fontSize * scale2;
|
|
750
|
-
const layout = getAlphabetLayout(text, scaledFontSize);
|
|
751
|
-
const startPos = getTextStartPosition(anchorAlignment, layout);
|
|
752
|
-
ctx.save();
|
|
753
|
-
ctx.translate(canvasX, canvasY);
|
|
754
|
-
if (rotation !== 0) {
|
|
755
|
-
ctx.rotate(-rotation * (Math.PI / 180));
|
|
756
|
-
}
|
|
757
|
-
ctx.lineWidth = layout.strokeWidth;
|
|
758
|
-
ctx.lineCap = "round";
|
|
759
|
-
ctx.lineJoin = "round";
|
|
760
|
-
ctx.strokeStyle = color;
|
|
761
|
-
strokeAlphabetText(ctx, text, layout, startPos.x, startPos.y);
|
|
762
|
-
ctx.restore();
|
|
763
|
-
}
|
|
764
|
-
|
|
765
650
|
// lib/drawer/elements/pcb-silkscreen.ts
|
|
651
|
+
import { applyToPoint as applyToPoint8 } from "transformation-matrix";
|
|
766
652
|
function layerToSilkscreenColor(layer, colorMap) {
|
|
767
653
|
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
768
654
|
}
|
|
655
|
+
function mapAnchorAlignment(alignment) {
|
|
656
|
+
if (!alignment) return "center";
|
|
657
|
+
if (alignment.includes("left")) return "left";
|
|
658
|
+
if (alignment.includes("right")) return "right";
|
|
659
|
+
return "center";
|
|
660
|
+
}
|
|
769
661
|
function drawPcbSilkscreenText(params) {
|
|
770
662
|
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
771
663
|
const color = layerToSilkscreenColor(text.layer, colorMap);
|
|
772
|
-
const
|
|
664
|
+
const [x, y] = applyToPoint8(realToCanvasMat, [
|
|
665
|
+
text.anchor_position.x,
|
|
666
|
+
text.anchor_position.y
|
|
667
|
+
]);
|
|
668
|
+
const fontSize = (text.font_size ?? 1) * Math.abs(realToCanvasMat.a);
|
|
773
669
|
const rotation = text.ccw_rotation ?? 0;
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
});
|
|
670
|
+
ctx.save();
|
|
671
|
+
ctx.translate(x, y);
|
|
672
|
+
if (rotation !== 0) {
|
|
673
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
674
|
+
}
|
|
675
|
+
ctx.font = `${fontSize}px sans-serif`;
|
|
676
|
+
ctx.fillStyle = color;
|
|
677
|
+
ctx.textAlign = mapAnchorAlignment(text.anchor_alignment);
|
|
678
|
+
ctx.fillText(text.text, 0, 0);
|
|
679
|
+
ctx.restore();
|
|
785
680
|
}
|
|
786
681
|
function drawPcbSilkscreenRect(params) {
|
|
787
682
|
const { ctx, rect, realToCanvasMat, colorMap } = params;
|
|
@@ -934,12 +829,129 @@ function drawPcbCopperPour(params) {
|
|
|
934
829
|
}
|
|
935
830
|
|
|
936
831
|
// lib/drawer/elements/pcb-copper-text.ts
|
|
832
|
+
import { applyToPoint as applyToPoint11 } from "transformation-matrix";
|
|
833
|
+
|
|
834
|
+
// lib/drawer/shapes/text/text.ts
|
|
835
|
+
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
937
836
|
import { applyToPoint as applyToPoint10 } from "transformation-matrix";
|
|
837
|
+
|
|
838
|
+
// lib/drawer/shapes/text/getAlphabetLayout.ts
|
|
839
|
+
var GLYPH_WIDTH_RATIO = 0.62;
|
|
840
|
+
var LETTER_SPACING_RATIO = 0.3;
|
|
841
|
+
var SPACE_WIDTH_RATIO = 1;
|
|
842
|
+
var STROKE_WIDTH_RATIO = 0.13;
|
|
843
|
+
function getAlphabetLayout(text, fontSize) {
|
|
844
|
+
const glyphWidth = fontSize * GLYPH_WIDTH_RATIO;
|
|
845
|
+
const letterSpacing = glyphWidth * LETTER_SPACING_RATIO;
|
|
846
|
+
const spaceWidth = glyphWidth * SPACE_WIDTH_RATIO;
|
|
847
|
+
const characters = Array.from(text);
|
|
848
|
+
let width = 0;
|
|
849
|
+
characters.forEach((char, index) => {
|
|
850
|
+
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
851
|
+
width += advance;
|
|
852
|
+
if (index < characters.length - 1) width += letterSpacing;
|
|
853
|
+
});
|
|
854
|
+
const strokeWidth = Math.max(fontSize * STROKE_WIDTH_RATIO, 0.35);
|
|
855
|
+
return {
|
|
856
|
+
width,
|
|
857
|
+
height: fontSize,
|
|
858
|
+
glyphWidth,
|
|
859
|
+
letterSpacing,
|
|
860
|
+
spaceWidth,
|
|
861
|
+
strokeWidth
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// lib/drawer/shapes/text/getTextStartPosition.ts
|
|
866
|
+
function getTextStartPosition(alignment, layout) {
|
|
867
|
+
const totalWidth = layout.width + layout.strokeWidth;
|
|
868
|
+
const totalHeight = layout.height + layout.strokeWidth;
|
|
869
|
+
let x = 0;
|
|
870
|
+
let y = 0;
|
|
871
|
+
if (alignment === "center") {
|
|
872
|
+
x = -totalWidth / 2;
|
|
873
|
+
} else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "center_left") {
|
|
874
|
+
x = 0;
|
|
875
|
+
} else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "center_right") {
|
|
876
|
+
x = -totalWidth;
|
|
877
|
+
}
|
|
878
|
+
if (alignment === "center") {
|
|
879
|
+
y = -totalHeight / 2;
|
|
880
|
+
} else if (alignment === "top_left" || alignment === "top_right" || alignment === "top_center") {
|
|
881
|
+
y = 0;
|
|
882
|
+
} else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom_center") {
|
|
883
|
+
y = -totalHeight;
|
|
884
|
+
} else {
|
|
885
|
+
y = 0;
|
|
886
|
+
}
|
|
887
|
+
return { x, y };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// lib/drawer/shapes/text/text.ts
|
|
891
|
+
var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
|
|
892
|
+
function strokeAlphabetText(ctx, text, layout, startX, startY) {
|
|
893
|
+
const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout;
|
|
894
|
+
const topY = startY;
|
|
895
|
+
const characters = Array.from(text);
|
|
896
|
+
let cursor = startX + strokeWidth / 2;
|
|
897
|
+
characters.forEach((char, index) => {
|
|
898
|
+
const lines = getGlyphLines(char);
|
|
899
|
+
const advance = char === " " ? spaceWidth : glyphWidth;
|
|
900
|
+
if (lines?.length) {
|
|
901
|
+
ctx.beginPath();
|
|
902
|
+
for (const line of lines) {
|
|
903
|
+
const x1 = cursor + line.x1 * glyphWidth;
|
|
904
|
+
const y1 = topY + (1 - line.y1) * height;
|
|
905
|
+
const x2 = cursor + line.x2 * glyphWidth;
|
|
906
|
+
const y2 = topY + (1 - line.y2) * height;
|
|
907
|
+
ctx.moveTo(x1, y1);
|
|
908
|
+
ctx.lineTo(x2, y2);
|
|
909
|
+
}
|
|
910
|
+
ctx.stroke();
|
|
911
|
+
}
|
|
912
|
+
cursor += advance;
|
|
913
|
+
if (index < characters.length - 1) {
|
|
914
|
+
cursor += letterSpacing;
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
function drawText(params) {
|
|
919
|
+
const {
|
|
920
|
+
ctx,
|
|
921
|
+
text,
|
|
922
|
+
x,
|
|
923
|
+
y,
|
|
924
|
+
fontSize,
|
|
925
|
+
color,
|
|
926
|
+
realToCanvasMat,
|
|
927
|
+
anchorAlignment,
|
|
928
|
+
rotation = 0
|
|
929
|
+
} = params;
|
|
930
|
+
if (!text) return;
|
|
931
|
+
const [canvasX, canvasY] = applyToPoint10(realToCanvasMat, [x, y]);
|
|
932
|
+
const scale2 = Math.abs(realToCanvasMat.a);
|
|
933
|
+
const scaledFontSize = fontSize * scale2;
|
|
934
|
+
const layout = getAlphabetLayout(text, scaledFontSize);
|
|
935
|
+
const startPos = getTextStartPosition(anchorAlignment, layout);
|
|
936
|
+
ctx.save();
|
|
937
|
+
ctx.translate(canvasX, canvasY);
|
|
938
|
+
if (rotation !== 0) {
|
|
939
|
+
ctx.rotate(-rotation * (Math.PI / 180));
|
|
940
|
+
}
|
|
941
|
+
ctx.lineWidth = layout.strokeWidth;
|
|
942
|
+
ctx.lineCap = "round";
|
|
943
|
+
ctx.lineJoin = "round";
|
|
944
|
+
ctx.strokeStyle = color;
|
|
945
|
+
strokeAlphabetText(ctx, text, layout, startPos.x, startPos.y);
|
|
946
|
+
ctx.restore();
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// lib/drawer/elements/pcb-copper-text.ts
|
|
938
950
|
var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
|
|
939
951
|
function layerToCopperColor(layer, colorMap) {
|
|
940
952
|
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
941
953
|
}
|
|
942
|
-
function
|
|
954
|
+
function mapAnchorAlignment2(alignment) {
|
|
943
955
|
if (!alignment) return "center";
|
|
944
956
|
if (alignment.includes("left")) return "center_left";
|
|
945
957
|
if (alignment.includes("right")) return "center_right";
|
|
@@ -949,7 +961,7 @@ function drawPcbCopperText(params) {
|
|
|
949
961
|
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
950
962
|
const content = text.text ?? "";
|
|
951
963
|
if (!content) return;
|
|
952
|
-
const [x, y] =
|
|
964
|
+
const [x, y] = applyToPoint11(realToCanvasMat, [
|
|
953
965
|
text.anchor_position.x,
|
|
954
966
|
text.anchor_position.y
|
|
955
967
|
]);
|
|
@@ -963,7 +975,7 @@ function drawPcbCopperText(params) {
|
|
|
963
975
|
const textColor = layerToCopperColor(text.layer, colorMap);
|
|
964
976
|
const layout = getAlphabetLayout(content, fontSize);
|
|
965
977
|
const totalWidth = layout.width + layout.strokeWidth;
|
|
966
|
-
const alignment =
|
|
978
|
+
const alignment = mapAnchorAlignment2(text.anchor_alignment);
|
|
967
979
|
const startPos = getTextStartPosition(alignment, layout);
|
|
968
980
|
const startX = startPos.x;
|
|
969
981
|
const startY = startPos.y;
|
|
@@ -1133,7 +1145,7 @@ function drawPcbNoteText(params) {
|
|
|
1133
1145
|
}
|
|
1134
1146
|
|
|
1135
1147
|
// lib/drawer/elements/pcb-note-dimension.ts
|
|
1136
|
-
import { applyToPoint as
|
|
1148
|
+
import { applyToPoint as applyToPoint12 } from "transformation-matrix";
|
|
1137
1149
|
|
|
1138
1150
|
// lib/drawer/shapes/arrow.ts
|
|
1139
1151
|
function drawArrow(params) {
|
|
@@ -1218,11 +1230,11 @@ function drawPcbNoteDimension(params) {
|
|
|
1218
1230
|
stroke: color,
|
|
1219
1231
|
realToCanvasMat
|
|
1220
1232
|
});
|
|
1221
|
-
const [canvasFromX, canvasFromY] =
|
|
1233
|
+
const [canvasFromX, canvasFromY] = applyToPoint12(realToCanvasMat, [
|
|
1222
1234
|
fromX,
|
|
1223
1235
|
fromY
|
|
1224
1236
|
]);
|
|
1225
|
-
const [canvasToX, canvasToY] =
|
|
1237
|
+
const [canvasToX, canvasToY] = applyToPoint12(realToCanvasMat, [toX, toY]);
|
|
1226
1238
|
const canvasDx = canvasToX - canvasFromX;
|
|
1227
1239
|
const canvasDy = canvasToY - canvasFromY;
|
|
1228
1240
|
const lineAngle = Math.atan2(canvasDy, canvasDx);
|
|
@@ -1283,15 +1295,15 @@ function drawPcbNoteDimension(params) {
|
|
|
1283
1295
|
}
|
|
1284
1296
|
|
|
1285
1297
|
// lib/drawer/elements/pcb-note-line.ts
|
|
1286
|
-
import { applyToPoint as
|
|
1298
|
+
import { applyToPoint as applyToPoint13 } from "transformation-matrix";
|
|
1287
1299
|
function drawPcbNoteLine(params) {
|
|
1288
1300
|
const { ctx, line, realToCanvasMat, colorMap } = params;
|
|
1289
1301
|
const defaultColor = "rgb(89, 148, 220)";
|
|
1290
1302
|
const color = line.color ?? defaultColor;
|
|
1291
1303
|
const strokeWidth = line.stroke_width ?? 0.1;
|
|
1292
1304
|
const isDashed = line.is_dashed ?? false;
|
|
1293
|
-
const [x1, y1] =
|
|
1294
|
-
const [x2, y2] =
|
|
1305
|
+
const [x1, y1] = applyToPoint13(realToCanvasMat, [line.x1, line.y1]);
|
|
1306
|
+
const [x2, y2] = applyToPoint13(realToCanvasMat, [line.x2, line.y2]);
|
|
1295
1307
|
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
1296
1308
|
ctx.save();
|
|
1297
1309
|
if (isDashed) {
|
|
@@ -6,12 +6,12 @@ import type {
|
|
|
6
6
|
PcbSilkscreenPath,
|
|
7
7
|
} from "circuit-json"
|
|
8
8
|
import type { Matrix } from "transformation-matrix"
|
|
9
|
+
import { applyToPoint } from "transformation-matrix"
|
|
9
10
|
import type { PcbColorMap, CanvasContext } from "../types"
|
|
10
11
|
import { drawRect } from "../shapes/rect"
|
|
11
12
|
import { drawCircle } from "../shapes/circle"
|
|
12
13
|
import { drawLine } from "../shapes/line"
|
|
13
14
|
import { drawPath } from "../shapes/path"
|
|
14
|
-
import { drawText } from "../shapes/text"
|
|
15
15
|
|
|
16
16
|
export interface DrawPcbSilkscreenTextParams {
|
|
17
17
|
ctx: CanvasContext
|
|
@@ -54,27 +54,42 @@ function layerToSilkscreenColor(layer: string, colorMap: PcbColorMap): string {
|
|
|
54
54
|
: colorMap.silkscreen.top
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function mapAnchorAlignment(
|
|
58
|
+
alignment?: string,
|
|
59
|
+
): "start" | "end" | "left" | "right" | "center" {
|
|
60
|
+
if (!alignment) return "center"
|
|
61
|
+
if (alignment.includes("left")) return "left"
|
|
62
|
+
if (alignment.includes("right")) return "right"
|
|
63
|
+
return "center"
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
export function drawPcbSilkscreenText(
|
|
58
67
|
params: DrawPcbSilkscreenTextParams,
|
|
59
68
|
): void {
|
|
60
69
|
const { ctx, text, realToCanvasMat, colorMap } = params
|
|
61
70
|
|
|
62
71
|
const color = layerToSilkscreenColor(text.layer, colorMap)
|
|
63
|
-
const
|
|
72
|
+
const [x, y] = applyToPoint(realToCanvasMat, [
|
|
73
|
+
text.anchor_position.x,
|
|
74
|
+
text.anchor_position.y,
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
const fontSize = (text.font_size ?? 1) * Math.abs(realToCanvasMat.a)
|
|
64
78
|
const rotation = text.ccw_rotation ?? 0
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
ctx.save()
|
|
81
|
+
ctx.translate(x, y)
|
|
82
|
+
|
|
83
|
+
// Apply rotation (CCW rotation in degrees)
|
|
84
|
+
if (rotation !== 0) {
|
|
85
|
+
ctx.rotate(-rotation * (Math.PI / 180))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ctx.font = `${fontSize}px sans-serif`
|
|
89
|
+
ctx.fillStyle = color
|
|
90
|
+
ctx.textAlign = mapAnchorAlignment(text.anchor_alignment)
|
|
91
|
+
ctx.fillText(text.text, 0, 0)
|
|
92
|
+
ctx.restore()
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
export function drawPcbSilkscreenRect(
|
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.21",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@biomejs/biome": "^2.3.8",
|
|
13
13
|
"@napi-rs/canvas": "^0.1.84",
|
|
14
14
|
"@resvg/resvg-js": "^2.6.2",
|
|
15
|
-
"@tscircuit/alphabet": "^0.0.
|
|
15
|
+
"@tscircuit/alphabet": "^0.0.9",
|
|
16
16
|
"@tscircuit/circuit-json-util": "^0.0.73",
|
|
17
17
|
"@tscircuit/math-utils": "^0.0.29",
|
|
18
18
|
"@types/bun": "latest",
|
|
Binary file
|
|
@@ -5,7 +5,7 @@ import usbcFlashlightCircuit from "./usb-c-flashlight.json"
|
|
|
5
5
|
|
|
6
6
|
const circuitElements = usbcFlashlightCircuit as AnyCircuitElement[]
|
|
7
7
|
|
|
8
|
-
test("USB-C flashlight - comprehensive comparison (circuit-to-canvas vs circuit-to-svg)", async () => {
|
|
8
|
+
test.skip("USB-C flashlight - comprehensive comparison (circuit-to-canvas vs circuit-to-svg)", async () => {
|
|
9
9
|
const stackedPng = await getStackedPngSvgComparison(circuitElements, {
|
|
10
10
|
width: 400,
|
|
11
11
|
height: 800,
|
|
Binary file
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { createCanvas, loadImage } from "@napi-rs/canvas"
|
|
2
|
-
import { Resvg
|
|
2
|
+
import { Resvg } from "@resvg/resvg-js"
|
|
3
3
|
import * as fs from "node:fs"
|
|
4
|
-
import * as os from "node:os"
|
|
5
4
|
import * as path from "node:path"
|
|
6
|
-
import tscircuitFont from "@tscircuit/alphabet/base64font"
|
|
7
5
|
|
|
8
6
|
// Pre-generated label PNGs for common labels
|
|
9
7
|
const labelPngCache: Map<string, Buffer> = new Map()
|
|
@@ -78,44 +76,7 @@ export const stackPngsVertically = async (
|
|
|
78
76
|
return canvas.toBuffer("image/png")
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
export const svgToPng = (
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
let tempFontPath: string | undefined
|
|
85
|
-
let cleanupFn: (() => void) | undefined
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "resvg-font-"))
|
|
89
|
-
tempFontPath = path.join(tempDir, "tscircuit-font.ttf")
|
|
90
|
-
fs.writeFileSync(tempFontPath, fontBuffer)
|
|
91
|
-
|
|
92
|
-
cleanupFn = () => {
|
|
93
|
-
try {
|
|
94
|
-
fs.unlinkSync(tempFontPath!)
|
|
95
|
-
} catch {
|
|
96
|
-
// Ignore errors during cleanup
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const opts: ResvgRenderOptions = {
|
|
101
|
-
font: {
|
|
102
|
-
fontFiles: [tempFontPath],
|
|
103
|
-
loadSystemFonts: false,
|
|
104
|
-
defaultFontFamily: "TscircuitAlphabet",
|
|
105
|
-
monospaceFamily: "TscircuitAlphabet",
|
|
106
|
-
sansSerifFamily: "TscircuitAlphabet",
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const resvg = new Resvg(svgString, opts)
|
|
111
|
-
const pngData = resvg.render()
|
|
112
|
-
const pngBuffer = pngData.asPng()
|
|
113
|
-
|
|
114
|
-
return Buffer.from(pngBuffer)
|
|
115
|
-
} finally {
|
|
116
|
-
// Clean up temporary font file
|
|
117
|
-
if (cleanupFn) {
|
|
118
|
-
cleanupFn()
|
|
119
|
-
}
|
|
120
|
-
}
|
|
79
|
+
export const svgToPng = (svg: string): Buffer => {
|
|
80
|
+
const resvg = new Resvg(svg)
|
|
81
|
+
return resvg.render().asPng()
|
|
121
82
|
}
|