circuit-to-canvas 0.0.19 → 0.0.20
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 +136 -148
- package/lib/drawer/elements/pcb-silkscreen.ts +14 -29
- 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 +43 -4
package/dist/index.js
CHANGED
|
@@ -647,37 +647,142 @@ function drawPcbBoard(params) {
|
|
|
647
647
|
}
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
-
// lib/drawer/
|
|
650
|
+
// lib/drawer/shapes/text/text.ts
|
|
651
|
+
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
651
652
|
import { applyToPoint as applyToPoint8 } from "transformation-matrix";
|
|
652
|
-
|
|
653
|
-
|
|
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
|
+
};
|
|
654
679
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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 };
|
|
660
704
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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);
|
|
670
752
|
ctx.save();
|
|
671
|
-
ctx.translate(
|
|
753
|
+
ctx.translate(canvasX, canvasY);
|
|
672
754
|
if (rotation !== 0) {
|
|
673
755
|
ctx.rotate(-rotation * (Math.PI / 180));
|
|
674
756
|
}
|
|
675
|
-
ctx.
|
|
676
|
-
ctx.
|
|
677
|
-
ctx.
|
|
678
|
-
ctx.
|
|
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);
|
|
679
762
|
ctx.restore();
|
|
680
763
|
}
|
|
764
|
+
|
|
765
|
+
// lib/drawer/elements/pcb-silkscreen.ts
|
|
766
|
+
function layerToSilkscreenColor(layer, colorMap) {
|
|
767
|
+
return layer === "bottom" ? colorMap.silkscreen.bottom : colorMap.silkscreen.top;
|
|
768
|
+
}
|
|
769
|
+
function drawPcbSilkscreenText(params) {
|
|
770
|
+
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
771
|
+
const color = layerToSilkscreenColor(text.layer, colorMap);
|
|
772
|
+
const fontSize = text.font_size ?? 1;
|
|
773
|
+
const rotation = text.ccw_rotation ?? 0;
|
|
774
|
+
drawText({
|
|
775
|
+
ctx,
|
|
776
|
+
text: text.text,
|
|
777
|
+
x: text.anchor_position.x,
|
|
778
|
+
y: text.anchor_position.y,
|
|
779
|
+
fontSize,
|
|
780
|
+
color,
|
|
781
|
+
realToCanvasMat,
|
|
782
|
+
anchorAlignment: text.anchor_alignment ?? "center",
|
|
783
|
+
rotation
|
|
784
|
+
});
|
|
785
|
+
}
|
|
681
786
|
function drawPcbSilkscreenRect(params) {
|
|
682
787
|
const { ctx, rect, realToCanvasMat, colorMap } = params;
|
|
683
788
|
const color = layerToSilkscreenColor(rect.layer, colorMap);
|
|
@@ -829,129 +934,12 @@ function drawPcbCopperPour(params) {
|
|
|
829
934
|
}
|
|
830
935
|
|
|
831
936
|
// 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";
|
|
836
937
|
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
|
|
950
938
|
var DEFAULT_PADDING = { left: 0.2, right: 0.2, top: 0.2, bottom: 0.2 };
|
|
951
939
|
function layerToCopperColor(layer, colorMap) {
|
|
952
940
|
return colorMap.copper[layer] ?? colorMap.copper.top;
|
|
953
941
|
}
|
|
954
|
-
function
|
|
942
|
+
function mapAnchorAlignment(alignment) {
|
|
955
943
|
if (!alignment) return "center";
|
|
956
944
|
if (alignment.includes("left")) return "center_left";
|
|
957
945
|
if (alignment.includes("right")) return "center_right";
|
|
@@ -961,7 +949,7 @@ function drawPcbCopperText(params) {
|
|
|
961
949
|
const { ctx, text, realToCanvasMat, colorMap } = params;
|
|
962
950
|
const content = text.text ?? "";
|
|
963
951
|
if (!content) return;
|
|
964
|
-
const [x, y] =
|
|
952
|
+
const [x, y] = applyToPoint10(realToCanvasMat, [
|
|
965
953
|
text.anchor_position.x,
|
|
966
954
|
text.anchor_position.y
|
|
967
955
|
]);
|
|
@@ -975,7 +963,7 @@ function drawPcbCopperText(params) {
|
|
|
975
963
|
const textColor = layerToCopperColor(text.layer, colorMap);
|
|
976
964
|
const layout = getAlphabetLayout(content, fontSize);
|
|
977
965
|
const totalWidth = layout.width + layout.strokeWidth;
|
|
978
|
-
const alignment =
|
|
966
|
+
const alignment = mapAnchorAlignment(text.anchor_alignment);
|
|
979
967
|
const startPos = getTextStartPosition(alignment, layout);
|
|
980
968
|
const startX = startPos.x;
|
|
981
969
|
const startY = startPos.y;
|
|
@@ -1145,7 +1133,7 @@ function drawPcbNoteText(params) {
|
|
|
1145
1133
|
}
|
|
1146
1134
|
|
|
1147
1135
|
// lib/drawer/elements/pcb-note-dimension.ts
|
|
1148
|
-
import { applyToPoint as
|
|
1136
|
+
import { applyToPoint as applyToPoint11 } from "transformation-matrix";
|
|
1149
1137
|
|
|
1150
1138
|
// lib/drawer/shapes/arrow.ts
|
|
1151
1139
|
function drawArrow(params) {
|
|
@@ -1230,11 +1218,11 @@ function drawPcbNoteDimension(params) {
|
|
|
1230
1218
|
stroke: color,
|
|
1231
1219
|
realToCanvasMat
|
|
1232
1220
|
});
|
|
1233
|
-
const [canvasFromX, canvasFromY] =
|
|
1221
|
+
const [canvasFromX, canvasFromY] = applyToPoint11(realToCanvasMat, [
|
|
1234
1222
|
fromX,
|
|
1235
1223
|
fromY
|
|
1236
1224
|
]);
|
|
1237
|
-
const [canvasToX, canvasToY] =
|
|
1225
|
+
const [canvasToX, canvasToY] = applyToPoint11(realToCanvasMat, [toX, toY]);
|
|
1238
1226
|
const canvasDx = canvasToX - canvasFromX;
|
|
1239
1227
|
const canvasDy = canvasToY - canvasFromY;
|
|
1240
1228
|
const lineAngle = Math.atan2(canvasDy, canvasDx);
|
|
@@ -1295,15 +1283,15 @@ function drawPcbNoteDimension(params) {
|
|
|
1295
1283
|
}
|
|
1296
1284
|
|
|
1297
1285
|
// lib/drawer/elements/pcb-note-line.ts
|
|
1298
|
-
import { applyToPoint as
|
|
1286
|
+
import { applyToPoint as applyToPoint12 } from "transformation-matrix";
|
|
1299
1287
|
function drawPcbNoteLine(params) {
|
|
1300
1288
|
const { ctx, line, realToCanvasMat, colorMap } = params;
|
|
1301
1289
|
const defaultColor = "rgb(89, 148, 220)";
|
|
1302
1290
|
const color = line.color ?? defaultColor;
|
|
1303
1291
|
const strokeWidth = line.stroke_width ?? 0.1;
|
|
1304
1292
|
const isDashed = line.is_dashed ?? false;
|
|
1305
|
-
const [x1, y1] =
|
|
1306
|
-
const [x2, y2] =
|
|
1293
|
+
const [x1, y1] = applyToPoint12(realToCanvasMat, [line.x1, line.y1]);
|
|
1294
|
+
const [x2, y2] = applyToPoint12(realToCanvasMat, [line.x2, line.y2]);
|
|
1307
1295
|
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
1308
1296
|
ctx.save();
|
|
1309
1297
|
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"
|
|
10
9
|
import type { PcbColorMap, CanvasContext } from "../types"
|
|
11
10
|
import { drawRect } from "../shapes/rect"
|
|
12
11
|
import { drawCircle } from "../shapes/circle"
|
|
13
12
|
import { drawLine } from "../shapes/line"
|
|
14
13
|
import { drawPath } from "../shapes/path"
|
|
14
|
+
import { drawText } from "../shapes/text"
|
|
15
15
|
|
|
16
16
|
export interface DrawPcbSilkscreenTextParams {
|
|
17
17
|
ctx: CanvasContext
|
|
@@ -54,42 +54,27 @@ 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
|
-
|
|
66
57
|
export function drawPcbSilkscreenText(
|
|
67
58
|
params: DrawPcbSilkscreenTextParams,
|
|
68
59
|
): void {
|
|
69
60
|
const { ctx, text, realToCanvasMat, colorMap } = params
|
|
70
61
|
|
|
71
62
|
const color = layerToSilkscreenColor(text.layer, colorMap)
|
|
72
|
-
const
|
|
73
|
-
text.anchor_position.x,
|
|
74
|
-
text.anchor_position.y,
|
|
75
|
-
])
|
|
76
|
-
|
|
77
|
-
const fontSize = (text.font_size ?? 1) * Math.abs(realToCanvasMat.a)
|
|
63
|
+
const fontSize = text.font_size ?? 1
|
|
78
64
|
const rotation = text.ccw_rotation ?? 0
|
|
79
65
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
ctx.restore()
|
|
66
|
+
// Use @tscircuit/alphabet to draw text (font-independent, stroke-based rendering)
|
|
67
|
+
drawText({
|
|
68
|
+
ctx,
|
|
69
|
+
text: text.text,
|
|
70
|
+
x: text.anchor_position.x,
|
|
71
|
+
y: text.anchor_position.y,
|
|
72
|
+
fontSize,
|
|
73
|
+
color,
|
|
74
|
+
realToCanvasMat,
|
|
75
|
+
anchorAlignment: text.anchor_alignment ?? "center",
|
|
76
|
+
rotation,
|
|
77
|
+
})
|
|
93
78
|
}
|
|
94
79
|
|
|
95
80
|
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.20",
|
|
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.17",
|
|
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
|
|
8
|
+
test("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,7 +1,9 @@
|
|
|
1
1
|
import { createCanvas, loadImage } from "@napi-rs/canvas"
|
|
2
|
-
import { Resvg } from "@resvg/resvg-js"
|
|
2
|
+
import { Resvg, type ResvgRenderOptions } from "@resvg/resvg-js"
|
|
3
3
|
import * as fs from "node:fs"
|
|
4
|
+
import * as os from "node:os"
|
|
4
5
|
import * as path from "node:path"
|
|
6
|
+
import tscircuitFont from "@tscircuit/alphabet/base64font"
|
|
5
7
|
|
|
6
8
|
// Pre-generated label PNGs for common labels
|
|
7
9
|
const labelPngCache: Map<string, Buffer> = new Map()
|
|
@@ -76,7 +78,44 @@ export const stackPngsVertically = async (
|
|
|
76
78
|
return canvas.toBuffer("image/png")
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
export const svgToPng = (
|
|
80
|
-
const
|
|
81
|
-
|
|
81
|
+
export const svgToPng = (svgString: string): Buffer => {
|
|
82
|
+
const fontBuffer = Buffer.from(tscircuitFont, "base64")
|
|
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
|
+
}
|
|
82
121
|
}
|