circuit-to-canvas 0.0.17 → 0.0.19
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 +32 -8
- package/dist/index.js +179 -13
- package/lib/drawer/CircuitToCanvasDrawer.ts +14 -2
- package/lib/drawer/elements/index.ts +5 -0
- package/lib/drawer/elements/pcb-copper-text.ts +2 -2
- package/lib/drawer/elements/pcb-note-dimension.ts +201 -0
- package/lib/drawer/shapes/arrow.ts +36 -0
- package/lib/drawer/shapes/index.ts +1 -0
- package/lib/drawer/shapes/text/getAlphabetLayout.ts +41 -0
- package/lib/drawer/shapes/text/getTextStartPosition.ts +53 -0
- package/lib/drawer/shapes/text/index.ts +3 -0
- package/lib/drawer/shapes/{text.ts → text/text.ts} +5 -104
- package/package.json +2 -1
- package/tests/board-snapshot/__snapshots__/usb-c-flashlight-board.snap.png +0 -0
- package/tests/board-snapshot/usb-c-flashlight-board.test.ts +15 -0
- package/tests/board-snapshot/usb-c-flashlight.json +2456 -0
- 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-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-note-dimension-angled-and-vertical.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-dimension-basic.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-dimension-vertical.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-dimension-with-offset.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-text-anchor-alignment.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-text-custom-color.snap.png +0 -0
- package/tests/elements/__snapshots__/pcb-note-text-small.snap.png +0 -0
- package/tests/elements/pcb-note-dimension-angled-and-vertical.test.ts +37 -0
- package/tests/elements/pcb-note-dimension-basic.test.ts +36 -0
- package/tests/elements/pcb-note-dimension-vertical.test.ts +42 -0
- package/tests/elements/pcb-note-dimension-with-offset.test.ts +38 -0
- package/tests/fixtures/assets/label-circuit-to-canvas.png +0 -0
- package/tests/fixtures/assets/label-circuit-to-svg.png +0 -0
- package/tests/fixtures/getStackedPngSvgComparison.ts +62 -0
- package/tests/fixtures/stackPngsVertically.ts +82 -0
- package/tests/shapes/__snapshots__/oval.snap.png +0 -0
- package/tsconfig.json +1 -0
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, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText } from 'circuit-json';
|
|
1
|
+
import { AnyCircuitElement, NinePointAnchor, PcbPlatedHole, PCBVia, PCBHole, PcbSmtPad, PCBTrace, PcbBoard, PcbSilkscreenText, PcbSilkscreenRect, PcbSilkscreenCircle, PcbSilkscreenLine, PcbSilkscreenPath, PcbCutout, PcbCopperPour, PcbCopperText, PcbFabricationNoteText, PcbFabricationNoteRect, PcbNoteRect, PcbFabricationNotePath, PcbNotePath, PcbNoteText, PcbNoteDimension } from 'circuit-json';
|
|
2
2
|
import { Matrix } from 'transformation-matrix';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -202,6 +202,20 @@ interface DrawPathParams {
|
|
|
202
202
|
}
|
|
203
203
|
declare function drawPath(params: DrawPathParams): void;
|
|
204
204
|
|
|
205
|
+
interface DrawArrowParams {
|
|
206
|
+
ctx: CanvasContext;
|
|
207
|
+
x: number;
|
|
208
|
+
y: number;
|
|
209
|
+
angle: number;
|
|
210
|
+
arrowSize: number;
|
|
211
|
+
color: string;
|
|
212
|
+
strokeWidth: number;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Draw an arrow at a point along a line
|
|
216
|
+
*/
|
|
217
|
+
declare function drawArrow(params: DrawArrowParams): void;
|
|
218
|
+
|
|
205
219
|
type AlphabetLayout = {
|
|
206
220
|
width: number;
|
|
207
221
|
height: number;
|
|
@@ -211,11 +225,7 @@ type AlphabetLayout = {
|
|
|
211
225
|
strokeWidth: number;
|
|
212
226
|
};
|
|
213
227
|
declare function getAlphabetLayout(text: string, fontSize: number): AlphabetLayout;
|
|
214
|
-
|
|
215
|
-
declare function getTextStartPosition(alignment: AnchorAlignment, layout: AlphabetLayout): {
|
|
216
|
-
x: number;
|
|
217
|
-
y: number;
|
|
218
|
-
};
|
|
228
|
+
|
|
219
229
|
declare function strokeAlphabetText(ctx: CanvasContext, text: string, layout: AlphabetLayout, startX: number, startY: number): void;
|
|
220
230
|
interface DrawTextParams {
|
|
221
231
|
ctx: CanvasContext;
|
|
@@ -225,11 +235,17 @@ interface DrawTextParams {
|
|
|
225
235
|
fontSize: number;
|
|
226
236
|
color: string;
|
|
227
237
|
realToCanvasMat: Matrix;
|
|
228
|
-
anchorAlignment:
|
|
238
|
+
anchorAlignment: NinePointAnchor;
|
|
229
239
|
rotation?: number;
|
|
230
240
|
}
|
|
231
241
|
declare function drawText(params: DrawTextParams): void;
|
|
232
242
|
|
|
243
|
+
type AnchorAlignment = NinePointAnchor;
|
|
244
|
+
declare function getTextStartPosition(alignment: NinePointAnchor, layout: AlphabetLayout): {
|
|
245
|
+
x: number;
|
|
246
|
+
y: number;
|
|
247
|
+
};
|
|
248
|
+
|
|
233
249
|
interface DrawPcbPlatedHoleParams {
|
|
234
250
|
ctx: CanvasContext;
|
|
235
251
|
hole: PcbPlatedHole;
|
|
@@ -386,4 +402,12 @@ interface DrawPcbNoteTextParams {
|
|
|
386
402
|
}
|
|
387
403
|
declare function drawPcbNoteText(params: DrawPcbNoteTextParams): void;
|
|
388
404
|
|
|
389
|
-
|
|
405
|
+
interface DrawPcbNoteDimensionParams {
|
|
406
|
+
ctx: CanvasContext;
|
|
407
|
+
pcbNoteDimension: PcbNoteDimension;
|
|
408
|
+
realToCanvasMat: Matrix;
|
|
409
|
+
colorMap: PcbColorMap;
|
|
410
|
+
}
|
|
411
|
+
declare function drawPcbNoteDimension(params: DrawPcbNoteDimensionParams): void;
|
|
412
|
+
|
|
413
|
+
export { type AlphabetLayout, type AnchorAlignment, type CameraBounds, type CanvasContext, CircuitToCanvasDrawer, type CopperColorMap, type CopperLayerName, DEFAULT_PCB_COLOR_MAP, type DrawArrowParams, 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 DrawPcbNoteDimensionParams, type DrawPcbNotePathParams, type DrawPcbNoteRectParams, type DrawPcbNoteTextParams, 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, drawArrow, drawCircle, drawLine, drawOval, drawPath, drawPcbBoard, drawPcbCopperPour, drawPcbCopperText, drawPcbCutout, drawPcbFabricationNotePath, drawPcbFabricationNoteRect, drawPcbFabricationNoteText, drawPcbHole, drawPcbNoteDimension, drawPcbNotePath, drawPcbNoteRect, drawPcbNoteText, drawPcbPlatedHole, drawPcbSilkscreenCircle, drawPcbSilkscreenLine, drawPcbSilkscreenPath, drawPcbSilkscreenRect, drawPcbSilkscreenText, drawPcbSmtPad, drawPcbTrace, drawPcbVia, drawPill, drawPolygon, drawRect, drawText, getAlphabetLayout, getTextStartPosition, strokeAlphabetText };
|
package/dist/index.js
CHANGED
|
@@ -831,9 +831,11 @@ function drawPcbCopperPour(params) {
|
|
|
831
831
|
// lib/drawer/elements/pcb-copper-text.ts
|
|
832
832
|
import { applyToPoint as applyToPoint11 } from "transformation-matrix";
|
|
833
833
|
|
|
834
|
-
// lib/drawer/shapes/text.ts
|
|
834
|
+
// lib/drawer/shapes/text/text.ts
|
|
835
835
|
import { lineAlphabet } from "@tscircuit/alphabet";
|
|
836
836
|
import { applyToPoint as applyToPoint10 } from "transformation-matrix";
|
|
837
|
+
|
|
838
|
+
// lib/drawer/shapes/text/getAlphabetLayout.ts
|
|
837
839
|
var GLYPH_WIDTH_RATIO = 0.62;
|
|
838
840
|
var LETTER_SPACING_RATIO = 0.3;
|
|
839
841
|
var SPACE_WIDTH_RATIO = 1;
|
|
@@ -859,7 +861,8 @@ function getAlphabetLayout(text, fontSize) {
|
|
|
859
861
|
strokeWidth
|
|
860
862
|
};
|
|
861
863
|
}
|
|
862
|
-
|
|
864
|
+
|
|
865
|
+
// lib/drawer/shapes/text/getTextStartPosition.ts
|
|
863
866
|
function getTextStartPosition(alignment, layout) {
|
|
864
867
|
const totalWidth = layout.width + layout.strokeWidth;
|
|
865
868
|
const totalHeight = layout.height + layout.strokeWidth;
|
|
@@ -867,22 +870,25 @@ function getTextStartPosition(alignment, layout) {
|
|
|
867
870
|
let y = 0;
|
|
868
871
|
if (alignment === "center") {
|
|
869
872
|
x = -totalWidth / 2;
|
|
870
|
-
} else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "
|
|
873
|
+
} else if (alignment === "top_left" || alignment === "bottom_left" || alignment === "center_left") {
|
|
871
874
|
x = 0;
|
|
872
|
-
} else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "
|
|
875
|
+
} else if (alignment === "top_right" || alignment === "bottom_right" || alignment === "center_right") {
|
|
873
876
|
x = -totalWidth;
|
|
874
877
|
}
|
|
875
878
|
if (alignment === "center") {
|
|
876
879
|
y = -totalHeight / 2;
|
|
877
|
-
} else if (alignment === "top_left" || alignment === "top_right" || alignment === "
|
|
880
|
+
} else if (alignment === "top_left" || alignment === "top_right" || alignment === "top_center") {
|
|
878
881
|
y = 0;
|
|
879
|
-
} else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "
|
|
882
|
+
} else if (alignment === "bottom_left" || alignment === "bottom_right" || alignment === "bottom_center") {
|
|
880
883
|
y = -totalHeight;
|
|
881
884
|
} else {
|
|
882
885
|
y = 0;
|
|
883
886
|
}
|
|
884
887
|
return { x, y };
|
|
885
888
|
}
|
|
889
|
+
|
|
890
|
+
// lib/drawer/shapes/text/text.ts
|
|
891
|
+
var getGlyphLines = (char) => lineAlphabet[char] ?? lineAlphabet[char.toUpperCase()];
|
|
886
892
|
function strokeAlphabetText(ctx, text, layout, startX, startY) {
|
|
887
893
|
const { glyphWidth, letterSpacing, spaceWidth, height, strokeWidth } = layout;
|
|
888
894
|
const topY = startY;
|
|
@@ -947,8 +953,8 @@ function layerToCopperColor(layer, colorMap) {
|
|
|
947
953
|
}
|
|
948
954
|
function mapAnchorAlignment2(alignment) {
|
|
949
955
|
if (!alignment) return "center";
|
|
950
|
-
if (alignment.includes("left")) return "
|
|
951
|
-
if (alignment.includes("right")) return "
|
|
956
|
+
if (alignment.includes("left")) return "center_left";
|
|
957
|
+
if (alignment.includes("right")) return "center_right";
|
|
952
958
|
return "center";
|
|
953
959
|
}
|
|
954
960
|
function drawPcbCopperText(params) {
|
|
@@ -1138,16 +1144,166 @@ function drawPcbNoteText(params) {
|
|
|
1138
1144
|
});
|
|
1139
1145
|
}
|
|
1140
1146
|
|
|
1141
|
-
// lib/drawer/elements/pcb-note-
|
|
1147
|
+
// lib/drawer/elements/pcb-note-dimension.ts
|
|
1142
1148
|
import { applyToPoint as applyToPoint12 } from "transformation-matrix";
|
|
1149
|
+
|
|
1150
|
+
// lib/drawer/shapes/arrow.ts
|
|
1151
|
+
function drawArrow(params) {
|
|
1152
|
+
const { ctx, x, y, angle, arrowSize, color, strokeWidth } = params;
|
|
1153
|
+
ctx.save();
|
|
1154
|
+
ctx.translate(x, y);
|
|
1155
|
+
ctx.rotate(angle);
|
|
1156
|
+
ctx.beginPath();
|
|
1157
|
+
ctx.moveTo(0, 0);
|
|
1158
|
+
ctx.lineTo(-arrowSize, -arrowSize / 2);
|
|
1159
|
+
ctx.moveTo(0, 0);
|
|
1160
|
+
ctx.lineTo(-arrowSize, arrowSize / 2);
|
|
1161
|
+
ctx.lineWidth = strokeWidth;
|
|
1162
|
+
ctx.strokeStyle = color;
|
|
1163
|
+
ctx.lineCap = "round";
|
|
1164
|
+
ctx.lineJoin = "round";
|
|
1165
|
+
ctx.stroke();
|
|
1166
|
+
ctx.restore();
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// lib/drawer/elements/pcb-note-dimension.ts
|
|
1170
|
+
var DEFAULT_NOTE_COLOR = "rgba(255,255,255,0.5)";
|
|
1171
|
+
function drawPcbNoteDimension(params) {
|
|
1172
|
+
const { ctx, pcbNoteDimension, realToCanvasMat } = params;
|
|
1173
|
+
const color = pcbNoteDimension.color ?? DEFAULT_NOTE_COLOR;
|
|
1174
|
+
const arrowSize = pcbNoteDimension.arrow_size;
|
|
1175
|
+
const realFromX = pcbNoteDimension.from.x;
|
|
1176
|
+
const realFromY = pcbNoteDimension.from.y;
|
|
1177
|
+
const realToX = pcbNoteDimension.to.x;
|
|
1178
|
+
const realToY = pcbNoteDimension.to.y;
|
|
1179
|
+
let fromX = realFromX;
|
|
1180
|
+
let fromY = realFromY;
|
|
1181
|
+
let toX = realToX;
|
|
1182
|
+
let toY = realToY;
|
|
1183
|
+
let hasOffset = false;
|
|
1184
|
+
let offsetX = 0;
|
|
1185
|
+
let offsetY = 0;
|
|
1186
|
+
if (pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction) {
|
|
1187
|
+
const dirX = pcbNoteDimension.offset_direction.x;
|
|
1188
|
+
const dirY = pcbNoteDimension.offset_direction.y;
|
|
1189
|
+
const length = Math.hypot(dirX, dirY);
|
|
1190
|
+
if (length > 0) {
|
|
1191
|
+
const normX = dirX / length;
|
|
1192
|
+
const normY = dirY / length;
|
|
1193
|
+
hasOffset = true;
|
|
1194
|
+
offsetX = pcbNoteDimension.offset_distance * normX;
|
|
1195
|
+
offsetY = pcbNoteDimension.offset_distance * normY;
|
|
1196
|
+
fromX += offsetX;
|
|
1197
|
+
fromY += offsetY;
|
|
1198
|
+
toX += offsetX;
|
|
1199
|
+
toY += offsetY;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
const STROKE_WIDTH_RATIO2 = 0.13;
|
|
1203
|
+
const strokeWidth = Math.max(
|
|
1204
|
+
pcbNoteDimension.font_size * STROKE_WIDTH_RATIO2,
|
|
1205
|
+
0.35
|
|
1206
|
+
);
|
|
1207
|
+
if (hasOffset) {
|
|
1208
|
+
drawLine({
|
|
1209
|
+
ctx,
|
|
1210
|
+
start: { x: realFromX, y: realFromY },
|
|
1211
|
+
end: { x: fromX, y: fromY },
|
|
1212
|
+
strokeWidth,
|
|
1213
|
+
stroke: color,
|
|
1214
|
+
realToCanvasMat
|
|
1215
|
+
});
|
|
1216
|
+
drawLine({
|
|
1217
|
+
ctx,
|
|
1218
|
+
start: { x: realToX, y: realToY },
|
|
1219
|
+
end: { x: toX, y: toY },
|
|
1220
|
+
strokeWidth,
|
|
1221
|
+
stroke: color,
|
|
1222
|
+
realToCanvasMat
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
drawLine({
|
|
1226
|
+
ctx,
|
|
1227
|
+
start: { x: fromX, y: fromY },
|
|
1228
|
+
end: { x: toX, y: toY },
|
|
1229
|
+
strokeWidth,
|
|
1230
|
+
stroke: color,
|
|
1231
|
+
realToCanvasMat
|
|
1232
|
+
});
|
|
1233
|
+
const [canvasFromX, canvasFromY] = applyToPoint12(realToCanvasMat, [
|
|
1234
|
+
fromX,
|
|
1235
|
+
fromY
|
|
1236
|
+
]);
|
|
1237
|
+
const [canvasToX, canvasToY] = applyToPoint12(realToCanvasMat, [toX, toY]);
|
|
1238
|
+
const canvasDx = canvasToX - canvasFromX;
|
|
1239
|
+
const canvasDy = canvasToY - canvasFromY;
|
|
1240
|
+
const lineAngle = Math.atan2(canvasDy, canvasDx);
|
|
1241
|
+
const scale2 = Math.abs(realToCanvasMat.a);
|
|
1242
|
+
const scaledArrowSize = arrowSize * scale2;
|
|
1243
|
+
const scaledStrokeWidth = strokeWidth * scale2;
|
|
1244
|
+
drawArrow({
|
|
1245
|
+
ctx,
|
|
1246
|
+
x: canvasFromX,
|
|
1247
|
+
y: canvasFromY,
|
|
1248
|
+
angle: lineAngle + Math.PI,
|
|
1249
|
+
arrowSize: scaledArrowSize,
|
|
1250
|
+
color,
|
|
1251
|
+
strokeWidth: scaledStrokeWidth
|
|
1252
|
+
});
|
|
1253
|
+
drawArrow({
|
|
1254
|
+
ctx,
|
|
1255
|
+
x: canvasToX,
|
|
1256
|
+
y: canvasToY,
|
|
1257
|
+
angle: lineAngle,
|
|
1258
|
+
arrowSize: scaledArrowSize,
|
|
1259
|
+
color,
|
|
1260
|
+
strokeWidth: scaledStrokeWidth
|
|
1261
|
+
});
|
|
1262
|
+
if (pcbNoteDimension.text) {
|
|
1263
|
+
let textX = (fromX + toX) / 2;
|
|
1264
|
+
let textY = (fromY + toY) / 2;
|
|
1265
|
+
const perpX = toY - fromY;
|
|
1266
|
+
const perpY = -(toX - fromX);
|
|
1267
|
+
const perpLength = Math.sqrt(perpX * perpX + perpY * perpY);
|
|
1268
|
+
if (perpLength > 0) {
|
|
1269
|
+
const offsetDistance = pcbNoteDimension.font_size * 1.5;
|
|
1270
|
+
const normalizedPerpX = perpX / perpLength;
|
|
1271
|
+
const normalizedPerpY = perpY / perpLength;
|
|
1272
|
+
textX += normalizedPerpX * offsetDistance;
|
|
1273
|
+
textY += normalizedPerpY * offsetDistance;
|
|
1274
|
+
}
|
|
1275
|
+
const textRotation = -(() => {
|
|
1276
|
+
const raw = pcbNoteDimension.text_ccw_rotation ?? lineAngle * 180 / Math.PI;
|
|
1277
|
+
if (pcbNoteDimension.text_ccw_rotation !== void 0) return raw;
|
|
1278
|
+
let deg = (raw + 180) % 360 - 180;
|
|
1279
|
+
if (deg > 90) deg -= 180;
|
|
1280
|
+
if (deg < -90) deg += 180;
|
|
1281
|
+
return deg;
|
|
1282
|
+
})();
|
|
1283
|
+
drawText({
|
|
1284
|
+
ctx,
|
|
1285
|
+
text: pcbNoteDimension.text,
|
|
1286
|
+
x: textX,
|
|
1287
|
+
y: textY,
|
|
1288
|
+
fontSize: pcbNoteDimension.font_size,
|
|
1289
|
+
color,
|
|
1290
|
+
realToCanvasMat,
|
|
1291
|
+
anchorAlignment: "center",
|
|
1292
|
+
rotation: textRotation
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// lib/drawer/elements/pcb-note-line.ts
|
|
1298
|
+
import { applyToPoint as applyToPoint13 } from "transformation-matrix";
|
|
1143
1299
|
function drawPcbNoteLine(params) {
|
|
1144
1300
|
const { ctx, line, realToCanvasMat, colorMap } = params;
|
|
1145
1301
|
const defaultColor = "rgb(89, 148, 220)";
|
|
1146
1302
|
const color = line.color ?? defaultColor;
|
|
1147
1303
|
const strokeWidth = line.stroke_width ?? 0.1;
|
|
1148
1304
|
const isDashed = line.is_dashed ?? false;
|
|
1149
|
-
const [x1, y1] =
|
|
1150
|
-
const [x2, y2] =
|
|
1305
|
+
const [x1, y1] = applyToPoint13(realToCanvasMat, [line.x1, line.y1]);
|
|
1306
|
+
const [x2, y2] = applyToPoint13(realToCanvasMat, [line.x2, line.y2]);
|
|
1151
1307
|
const scaledStrokeWidth = strokeWidth * Math.abs(realToCanvasMat.a);
|
|
1152
1308
|
ctx.save();
|
|
1153
1309
|
if (isDashed) {
|
|
@@ -1224,8 +1380,8 @@ var CircuitToCanvasDrawer = class {
|
|
|
1224
1380
|
const offsetY = (canvasHeight - realHeight * uniformScale) / 2;
|
|
1225
1381
|
this.realToCanvasMat = compose(
|
|
1226
1382
|
translate(offsetX, offsetY),
|
|
1227
|
-
scale(uniformScale, uniformScale),
|
|
1228
|
-
translate(-bounds.minX, -bounds.
|
|
1383
|
+
scale(uniformScale, -uniformScale),
|
|
1384
|
+
translate(-bounds.minX, -bounds.maxY)
|
|
1229
1385
|
);
|
|
1230
1386
|
}
|
|
1231
1387
|
drawElements(elements, options = {}) {
|
|
@@ -1402,11 +1558,20 @@ var CircuitToCanvasDrawer = class {
|
|
|
1402
1558
|
colorMap: this.colorMap
|
|
1403
1559
|
});
|
|
1404
1560
|
}
|
|
1561
|
+
if (element.type === "pcb_note_dimension") {
|
|
1562
|
+
drawPcbNoteDimension({
|
|
1563
|
+
ctx: this.ctx,
|
|
1564
|
+
pcbNoteDimension: element,
|
|
1565
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
1566
|
+
colorMap: this.colorMap
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1405
1569
|
}
|
|
1406
1570
|
};
|
|
1407
1571
|
export {
|
|
1408
1572
|
CircuitToCanvasDrawer,
|
|
1409
1573
|
DEFAULT_PCB_COLOR_MAP,
|
|
1574
|
+
drawArrow,
|
|
1410
1575
|
drawCircle,
|
|
1411
1576
|
drawLine,
|
|
1412
1577
|
drawOval,
|
|
@@ -1419,6 +1584,7 @@ export {
|
|
|
1419
1584
|
drawPcbFabricationNoteRect,
|
|
1420
1585
|
drawPcbFabricationNoteText,
|
|
1421
1586
|
drawPcbHole,
|
|
1587
|
+
drawPcbNoteDimension,
|
|
1422
1588
|
drawPcbNotePath,
|
|
1423
1589
|
drawPcbNoteRect,
|
|
1424
1590
|
drawPcbNoteText,
|
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
PcbFabricationNotePath,
|
|
21
21
|
PcbNotePath,
|
|
22
22
|
PcbNoteText,
|
|
23
|
+
PcbNoteDimension,
|
|
23
24
|
PcbNoteLine,
|
|
24
25
|
} from "circuit-json"
|
|
25
26
|
import { identity, compose, translate, scale } from "transformation-matrix"
|
|
@@ -53,6 +54,7 @@ import { drawPcbNoteRect } from "./elements/pcb-note-rect"
|
|
|
53
54
|
import { drawPcbFabricationNotePath } from "./elements/pcb-fabrication-note-path"
|
|
54
55
|
import { drawPcbNotePath } from "./elements/pcb-note-path"
|
|
55
56
|
import { drawPcbNoteText } from "./elements/pcb-note-text"
|
|
57
|
+
import { drawPcbNoteDimension } from "./elements/pcb-note-dimension"
|
|
56
58
|
import { drawPcbNoteLine } from "./elements/pcb-note-line"
|
|
57
59
|
|
|
58
60
|
export interface DrawElementsOptions {
|
|
@@ -132,10 +134,11 @@ export class CircuitToCanvasDrawer {
|
|
|
132
134
|
const offsetX = (canvasWidth - realWidth * uniformScale) / 2
|
|
133
135
|
const offsetY = (canvasHeight - realHeight * uniformScale) / 2
|
|
134
136
|
|
|
137
|
+
// Flip Y axis: PCB uses Y-up, canvas uses Y-down
|
|
135
138
|
this.realToCanvasMat = compose(
|
|
136
139
|
translate(offsetX, offsetY),
|
|
137
|
-
scale(uniformScale, uniformScale),
|
|
138
|
-
translate(-bounds.minX, -bounds.
|
|
140
|
+
scale(uniformScale, -uniformScale),
|
|
141
|
+
translate(-bounds.minX, -bounds.maxY),
|
|
139
142
|
)
|
|
140
143
|
}
|
|
141
144
|
|
|
@@ -340,5 +343,14 @@ export class CircuitToCanvasDrawer {
|
|
|
340
343
|
colorMap: this.colorMap,
|
|
341
344
|
})
|
|
342
345
|
}
|
|
346
|
+
|
|
347
|
+
if (element.type === "pcb_note_dimension") {
|
|
348
|
+
drawPcbNoteDimension({
|
|
349
|
+
ctx: this.ctx,
|
|
350
|
+
pcbNoteDimension: element as PcbNoteDimension,
|
|
351
|
+
realToCanvasMat: this.realToCanvasMat,
|
|
352
|
+
colorMap: this.colorMap,
|
|
353
|
+
})
|
|
354
|
+
}
|
|
343
355
|
}
|
|
344
356
|
}
|
|
@@ -27,8 +27,8 @@ function layerToCopperColor(layer: string, colorMap: PcbColorMap): string {
|
|
|
27
27
|
|
|
28
28
|
function mapAnchorAlignment(alignment?: string): AnchorAlignment {
|
|
29
29
|
if (!alignment) return "center"
|
|
30
|
-
if (alignment.includes("left")) return "
|
|
31
|
-
if (alignment.includes("right")) return "
|
|
30
|
+
if (alignment.includes("left")) return "center_left"
|
|
31
|
+
if (alignment.includes("right")) return "center_right"
|
|
32
32
|
return "center"
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { PcbNoteDimension } 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 { drawLine } from "../shapes/line"
|
|
6
|
+
import { drawText } from "../shapes/text"
|
|
7
|
+
import { drawArrow } from "../shapes/arrow"
|
|
8
|
+
|
|
9
|
+
export interface DrawPcbNoteDimensionParams {
|
|
10
|
+
ctx: CanvasContext
|
|
11
|
+
pcbNoteDimension: PcbNoteDimension
|
|
12
|
+
realToCanvasMat: Matrix
|
|
13
|
+
colorMap: PcbColorMap
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DEFAULT_NOTE_COLOR = "rgba(255,255,255,0.5)"
|
|
17
|
+
|
|
18
|
+
export function drawPcbNoteDimension(params: DrawPcbNoteDimensionParams): void {
|
|
19
|
+
const { ctx, pcbNoteDimension, realToCanvasMat } = params
|
|
20
|
+
|
|
21
|
+
const color = pcbNoteDimension.color ?? DEFAULT_NOTE_COLOR
|
|
22
|
+
const arrowSize = pcbNoteDimension.arrow_size
|
|
23
|
+
|
|
24
|
+
// Store real (model) endpoints for extension lines
|
|
25
|
+
const realFromX = pcbNoteDimension.from.x
|
|
26
|
+
const realFromY = pcbNoteDimension.from.y
|
|
27
|
+
const realToX = pcbNoteDimension.to.x
|
|
28
|
+
const realToY = pcbNoteDimension.to.y
|
|
29
|
+
|
|
30
|
+
// Calculate the dimension line endpoints (real/model coords)
|
|
31
|
+
let fromX = realFromX
|
|
32
|
+
let fromY = realFromY
|
|
33
|
+
let toX = realToX
|
|
34
|
+
let toY = realToY
|
|
35
|
+
|
|
36
|
+
// Track if we have an offset (for drawing extension lines)
|
|
37
|
+
let hasOffset = false
|
|
38
|
+
let offsetX = 0
|
|
39
|
+
let offsetY = 0
|
|
40
|
+
|
|
41
|
+
// Apply offset if provided
|
|
42
|
+
if (pcbNoteDimension.offset_distance && pcbNoteDimension.offset_direction) {
|
|
43
|
+
const dirX = pcbNoteDimension.offset_direction.x
|
|
44
|
+
const dirY = pcbNoteDimension.offset_direction.y
|
|
45
|
+
const length = Math.hypot(dirX, dirY)
|
|
46
|
+
if (length > 0) {
|
|
47
|
+
const normX = dirX / length
|
|
48
|
+
const normY = dirY / length
|
|
49
|
+
hasOffset = true
|
|
50
|
+
offsetX = pcbNoteDimension.offset_distance * normX
|
|
51
|
+
offsetY = pcbNoteDimension.offset_distance * normY
|
|
52
|
+
fromX += offsetX
|
|
53
|
+
fromY += offsetY
|
|
54
|
+
toX += offsetX
|
|
55
|
+
toY += offsetY
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Calculate stroke width to match text stroke width
|
|
60
|
+
// Text uses fontSize * STROKE_WIDTH_RATIO (0.13) with minimum 0.35
|
|
61
|
+
const STROKE_WIDTH_RATIO = 0.13
|
|
62
|
+
|
|
63
|
+
const strokeWidth = Math.max(
|
|
64
|
+
pcbNoteDimension.font_size * STROKE_WIDTH_RATIO,
|
|
65
|
+
0.35,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// Draw extension lines if offset is provided
|
|
69
|
+
if (hasOffset) {
|
|
70
|
+
// Extension line from original 'from' point to offset 'from' point
|
|
71
|
+
drawLine({
|
|
72
|
+
ctx,
|
|
73
|
+
start: { x: realFromX, y: realFromY },
|
|
74
|
+
end: { x: fromX, y: fromY },
|
|
75
|
+
strokeWidth,
|
|
76
|
+
stroke: color,
|
|
77
|
+
realToCanvasMat: realToCanvasMat,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// Extension line from original 'to' point to offset 'to' point
|
|
81
|
+
drawLine({
|
|
82
|
+
ctx,
|
|
83
|
+
start: { x: realToX, y: realToY },
|
|
84
|
+
end: { x: toX, y: toY },
|
|
85
|
+
strokeWidth,
|
|
86
|
+
stroke: color,
|
|
87
|
+
realToCanvasMat: realToCanvasMat,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Draw the dimension line
|
|
92
|
+
drawLine({
|
|
93
|
+
ctx,
|
|
94
|
+
start: { x: fromX, y: fromY },
|
|
95
|
+
end: { x: toX, y: toY },
|
|
96
|
+
strokeWidth,
|
|
97
|
+
stroke: color,
|
|
98
|
+
realToCanvasMat: realToCanvasMat,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Draw arrows at both ends
|
|
102
|
+
const [canvasFromX, canvasFromY] = applyToPoint(realToCanvasMat, [
|
|
103
|
+
fromX,
|
|
104
|
+
fromY,
|
|
105
|
+
])
|
|
106
|
+
const [canvasToX, canvasToY] = applyToPoint(realToCanvasMat, [toX, toY])
|
|
107
|
+
// Calculate angle for arrows in canvas coordinates
|
|
108
|
+
const canvasDx = canvasToX - canvasFromX
|
|
109
|
+
const canvasDy = canvasToY - canvasFromY
|
|
110
|
+
const lineAngle = Math.atan2(canvasDy, canvasDx)
|
|
111
|
+
const scale = Math.abs(realToCanvasMat.a)
|
|
112
|
+
const scaledArrowSize = arrowSize * scale
|
|
113
|
+
const scaledStrokeWidth = strokeWidth * scale
|
|
114
|
+
|
|
115
|
+
// Arrow at 'from' point (pointing outward, away from the line center)
|
|
116
|
+
// This means pointing in the direction opposite to 'to'
|
|
117
|
+
drawArrow({
|
|
118
|
+
ctx,
|
|
119
|
+
x: canvasFromX,
|
|
120
|
+
y: canvasFromY,
|
|
121
|
+
angle: lineAngle + Math.PI,
|
|
122
|
+
arrowSize: scaledArrowSize,
|
|
123
|
+
color,
|
|
124
|
+
strokeWidth: scaledStrokeWidth,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Arrow at 'to' point (pointing outward, away from the line center)
|
|
128
|
+
// This means pointing in the direction toward 'to' (away from 'from')
|
|
129
|
+
drawArrow({
|
|
130
|
+
ctx,
|
|
131
|
+
x: canvasToX,
|
|
132
|
+
y: canvasToY,
|
|
133
|
+
angle: lineAngle,
|
|
134
|
+
arrowSize: scaledArrowSize,
|
|
135
|
+
color,
|
|
136
|
+
strokeWidth: scaledStrokeWidth,
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// Draw text if provided
|
|
140
|
+
if (pcbNoteDimension.text) {
|
|
141
|
+
// Calculate text position (midpoint of the dimension line)
|
|
142
|
+
// The line endpoints are already offset if offset was provided
|
|
143
|
+
let textX = (fromX + toX) / 2
|
|
144
|
+
let textY = (fromY + toY) / 2
|
|
145
|
+
|
|
146
|
+
// Offset text perpendicular to the dimension line so it appears above/outside
|
|
147
|
+
// Calculate perpendicular vector (rotate line direction by 90 degrees CW)
|
|
148
|
+
// For a line from (fromX, fromY) to (toX, toY), perpendicular is (dy, -dx)
|
|
149
|
+
// This ensures text appears above horizontal lines and to the right of vertical lines
|
|
150
|
+
const perpX = toY - fromY
|
|
151
|
+
const perpY = -(toX - fromX)
|
|
152
|
+
const perpLength = Math.sqrt(perpX * perpX + perpY * perpY)
|
|
153
|
+
|
|
154
|
+
// Normalize and offset by font size (plus a small gap)
|
|
155
|
+
if (perpLength > 0) {
|
|
156
|
+
const offsetDistance = pcbNoteDimension.font_size * 1.5 // Offset by 1.5x font size
|
|
157
|
+
const normalizedPerpX = perpX / perpLength
|
|
158
|
+
const normalizedPerpY = perpY / perpLength
|
|
159
|
+
textX += normalizedPerpX * offsetDistance
|
|
160
|
+
textY += normalizedPerpY * offsetDistance
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Calculate rotation (displayed CCW degrees). If the caller provided
|
|
164
|
+
// `text_ccw_rotation` use that directly; otherwise align with the line
|
|
165
|
+
// angle and keep the text upright by folding into [-90, 90]. `drawText`
|
|
166
|
+
// expects a rotation value that it will negate internally, so we pass
|
|
167
|
+
// `-deg` below.
|
|
168
|
+
// Compute the displayed CCW degrees. Use the explicit `text_ccw_rotation`
|
|
169
|
+
// when provided; otherwise derive from the line angle and fold into
|
|
170
|
+
// [-90, 90] so text stays upright. Finally, `drawText` negates the
|
|
171
|
+
// provided rotation when applying it to the canvas, so pass the
|
|
172
|
+
// negative of the displayed CCW degrees.
|
|
173
|
+
const textRotation = -(() => {
|
|
174
|
+
const raw =
|
|
175
|
+
pcbNoteDimension.text_ccw_rotation ?? (lineAngle * 180) / Math.PI
|
|
176
|
+
|
|
177
|
+
if (pcbNoteDimension.text_ccw_rotation !== undefined) return raw
|
|
178
|
+
|
|
179
|
+
// Normalize to [-180, 180]
|
|
180
|
+
let deg = ((raw + 180) % 360) - 180
|
|
181
|
+
|
|
182
|
+
// Fold into [-90, 90]
|
|
183
|
+
if (deg > 90) deg -= 180
|
|
184
|
+
if (deg < -90) deg += 180
|
|
185
|
+
|
|
186
|
+
return deg
|
|
187
|
+
})()
|
|
188
|
+
|
|
189
|
+
drawText({
|
|
190
|
+
ctx,
|
|
191
|
+
text: pcbNoteDimension.text,
|
|
192
|
+
x: textX,
|
|
193
|
+
y: textY,
|
|
194
|
+
fontSize: pcbNoteDimension.font_size,
|
|
195
|
+
color,
|
|
196
|
+
realToCanvasMat: realToCanvasMat,
|
|
197
|
+
anchorAlignment: "center",
|
|
198
|
+
rotation: textRotation,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CanvasContext } from "../types"
|
|
2
|
+
|
|
3
|
+
export interface DrawArrowParams {
|
|
4
|
+
ctx: CanvasContext
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
angle: number
|
|
8
|
+
arrowSize: number
|
|
9
|
+
color: string
|
|
10
|
+
strokeWidth: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Draw an arrow at a point along a line
|
|
15
|
+
*/
|
|
16
|
+
export function drawArrow(params: DrawArrowParams): void {
|
|
17
|
+
const { ctx, x, y, angle, arrowSize, color, strokeWidth } = params
|
|
18
|
+
|
|
19
|
+
ctx.save()
|
|
20
|
+
ctx.translate(x, y)
|
|
21
|
+
ctx.rotate(angle)
|
|
22
|
+
|
|
23
|
+
ctx.beginPath()
|
|
24
|
+
ctx.moveTo(0, 0)
|
|
25
|
+
ctx.lineTo(-arrowSize, -arrowSize / 2)
|
|
26
|
+
ctx.moveTo(0, 0)
|
|
27
|
+
ctx.lineTo(-arrowSize, arrowSize / 2)
|
|
28
|
+
|
|
29
|
+
ctx.lineWidth = strokeWidth
|
|
30
|
+
ctx.strokeStyle = color
|
|
31
|
+
ctx.lineCap = "round"
|
|
32
|
+
ctx.lineJoin = "round"
|
|
33
|
+
ctx.stroke()
|
|
34
|
+
|
|
35
|
+
ctx.restore()
|
|
36
|
+
}
|
|
@@ -5,6 +5,7 @@ export { drawPill, type DrawPillParams } from "./pill"
|
|
|
5
5
|
export { drawPolygon, type DrawPolygonParams } from "./polygon"
|
|
6
6
|
export { drawLine, type DrawLineParams } from "./line"
|
|
7
7
|
export { drawPath, type DrawPathParams } from "./path"
|
|
8
|
+
export { drawArrow, type DrawArrowParams } from "./arrow"
|
|
8
9
|
export {
|
|
9
10
|
drawText,
|
|
10
11
|
type DrawTextParams,
|