diff --git a/bun.lockb b/bun.lockb index 9388ddf..1438137 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/lib/sch/arial-text-metrics.ts b/lib/sch/arial-text-metrics.ts new file mode 100644 index 0000000..c4264dd --- /dev/null +++ b/lib/sch/arial-text-metrics.ts @@ -0,0 +1,763 @@ +// https://claude.ai/share/5e327d90-627f-48d5-9a51-ba6117479007 +export const arialTextMetrics = { + "0": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "1": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -3, + right: 9, + }, + "2": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "3": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "4": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 12, + }, + "5": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "6": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "7": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "8": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "9": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + " ": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 0, + }, + "!": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 5, + }, + '"': { + width: 9, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 7, + }, + "#": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 13, + }, + $: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "%": { + width: 21, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 20, + }, + "&": { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 15, + }, + "'": { + width: 5, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 3, + }, + "(": { + width: 8, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 7, + }, + ")": { + width: 8, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 7, + }, + "*": { + width: 9, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 8, + }, + "+": { + width: 14, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 13, + }, + ",": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 5, + }, + "-": { + width: 8, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 7, + }, + ".": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 5, + }, + "/": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 7, + }, + ":": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 5, + }, + ";": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 5, + }, + "<": { + width: 14, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 13, + }, + "=": { + width: 14, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 13, + }, + ">": { + width: 14, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 13, + }, + "?": { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + "@": { + width: 24, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 23, + }, + A: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 16, + }, + B: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 15, + }, + C: { + width: 17, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 16, + }, + D: { + width: 17, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 16, + }, + E: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 15, + }, + F: { + width: 15, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 14, + }, + G: { + width: 19, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 17, + }, + H: { + width: 17, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 15, + }, + I: { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 5, + }, + J: { + width: 12, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 10, + }, + K: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 16, + }, + L: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 12, + }, + M: { + width: 20, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 18, + }, + N: { + width: 17, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 15, + }, + O: { + width: 19, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 18, + }, + P: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 15, + }, + Q: { + width: 19, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 18, + }, + R: { + width: 17, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 17, + }, + S: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 15, + }, + T: { + width: 15, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 14, + }, + U: { + width: 17, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 15, + }, + V: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 16, + }, + W: { + width: 23, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 22, + }, + X: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 16, + }, + Y: { + width: 16, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 16, + }, + Z: { + width: 15, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 14, + }, + "[": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 6, + }, + "\\": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 7, + }, + "]": { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 5, + }, + "^": { + width: 11, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 11, + }, + _: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 14, + }, + "`": { + width: 8, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 5, + }, + a: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + b: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 12, + }, + c: { + width: 12, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + d: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + e: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + f: { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 8, + }, + g: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + h: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 12, + }, + i: { + width: 5, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 4, + }, + j: { + width: 5, + height: 27, + ascent: 22, + descent: 5, + left: 1, + right: 4, + }, + k: { + width: 12, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 12, + }, + l: { + width: 5, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 4, + }, + m: { + width: 20, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 18, + }, + n: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 12, + }, + o: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + p: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 12, + }, + q: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 12, + }, + r: { + width: 8, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 8, + }, + s: { + width: 12, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 11, + }, + t: { + width: 7, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 6, + }, + u: { + width: 13, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 12, + }, + v: { + width: 12, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 12, + }, + w: { + width: 17, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 17, + }, + x: { + width: 12, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 12, + }, + y: { + width: 12, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 12, + }, + z: { + width: 12, + height: 27, + ascent: 22, + descent: 5, + left: 0, + right: 11, + }, + "{": { + width: 8, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 7, + }, + "|": { + width: 6, + height: 27, + ascent: 22, + descent: 5, + left: -2, + right: 4, + }, + "}": { + width: 8, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 7, + }, + "~": { + width: 14, + height: 27, + ascent: 22, + descent: 5, + left: -1, + right: 13, + }, +} diff --git a/lib/sch/convert-circuit-json-to-schematic-svg.ts b/lib/sch/convert-circuit-json-to-schematic-svg.ts index 7e6c399..1535cb6 100644 --- a/lib/sch/convert-circuit-json-to-schematic-svg.ts +++ b/lib/sch/convert-circuit-json-to-schematic-svg.ts @@ -99,16 +99,6 @@ export function convertCircuitJsonToSchematicSvg( ) } - // Add labeled points if provided - if (options?.labeledPoints) { - svgChildren.push( - drawSchematicLabeledPoints({ - points: options.labeledPoints, - transform, - }), - ) - } - const schDebugObjectSvgs: SvgObject[] = [] const schComponentSvgs: SvgObject[] = [] const schTraceSvgs: SvgObject[] = [] @@ -142,6 +132,16 @@ export function convertCircuitJsonToSchematicSvg( ...schNetLabel, ) + // Add labeled points if provided + if (options?.labeledPoints) { + svgChildren.push( + drawSchematicLabeledPoints({ + points: options.labeledPoints, + transform, + }), + ) + } + const svgObject: SvgObject = { name: "svg", type: "element", diff --git a/lib/sch/estimate-text-width.ts b/lib/sch/estimate-text-width.ts new file mode 100644 index 0000000..5782269 --- /dev/null +++ b/lib/sch/estimate-text-width.ts @@ -0,0 +1,19 @@ +import { arialTextMetrics } from "./arial-text-metrics" + +export const estimateTextWidth = (text: string): number => { + if (!text) return 0 + + let totalWidth = 0 + for (const char of text) { + const metrics = arialTextMetrics[char as keyof typeof arialTextMetrics] + if (metrics) { + totalWidth += metrics.width + } else { + // Default width for unknown characters + totalWidth += arialTextMetrics["?"].width + } + } + + // Return width normalized to font size 1 + return totalWidth / 27 // Normalize by font height from metrics +} diff --git a/lib/sch/svg-object-fns/create-svg-objects-for-sch-net-label.ts b/lib/sch/svg-object-fns/create-svg-objects-for-sch-net-label.ts index 06713bc..efd535e 100644 --- a/lib/sch/svg-object-fns/create-svg-objects-for-sch-net-label.ts +++ b/lib/sch/svg-object-fns/create-svg-objects-for-sch-net-label.ts @@ -1,79 +1,141 @@ import type { AnyCircuitElement, SchematicNetLabel } from "circuit-json" import type { SvgObject } from "lib/svg-object" import { colorMap } from "lib/utils/colors" -import { getSchFontSize } from "lib/utils/get-sch-font-size" +import { + getSchMmFontSize, + getSchScreenFontSize, +} from "lib/utils/get-sch-font-size" import { getSchStrokeSize } from "lib/utils/get-sch-stroke-size" -import { applyToPoint, type Matrix } from "transformation-matrix" +import { getUnitVectorFromEdgeToOutside } from "lib/utils/get-unit-vector-from-edge-to-outside" +import { getUnitVectorFromOutsideToEdge } from "lib/utils/get-unit-vector-from-outside-to-edge" +import { + applyToPoint, + compose, + rotate, + scale, + translate, + type Matrix, +} from "transformation-matrix" +import { estimateTextWidth } from "../estimate-text-width" + +/** + * Arrow point width as a fraction of font size (Font Size Ratio) + */ +const ARROW_POINT_WIDTH_FSR = 0.3 + +/** + * End padding as a fraction of font size (Font Size Ratio) + */ +const END_PADDING_FSR = 0.3 +const END_PADDING_EXTRA_PER_CHARACTER_FSR = 0.06 export const createSvgObjectsForSchNetLabel = ( schNetLabel: SchematicNetLabel, - transform: Matrix, + realToScreenTransform: Matrix, ): SvgObject[] => { + if (!schNetLabel.text) return [] const svgObjects: SvgObject[] = [] + const fontSizePx = getSchScreenFontSize(realToScreenTransform, "net_label") + const fontSizeMm = getSchMmFontSize("net_label") + const textWidthFSR = estimateTextWidth(schNetLabel.text || "") + // Transform the center position to screen coordinates - const screenPos = applyToPoint(transform, schNetLabel.center) + const screenCenter = applyToPoint(realToScreenTransform, schNetLabel.center) - // Get font size for text - const fontSize = getSchFontSize(transform, "net_label") + const realTextGrowthVec = getUnitVectorFromOutsideToEdge( + schNetLabel.anchor_side, + ) - // Calculate label dimensions based on text - const textWidth = (schNetLabel.text?.length || 0) * fontSize * 0.7 - const padding = fontSize * 0.5 - // Increase height to better accommodate text - const screenLabelHeight = fontSize * 1.2 - const screenArrowPoint = fontSize * 0.3 - const screenLabelWidth = textWidth + padding * 2 + screenArrowPoint + const screenTextGrowthVec = { ...realTextGrowthVec } + screenTextGrowthVec.y *= -1 // Invert y direction because anchor_side is pre-transform - // Get rotation angle based on anchor_side - let rotation = 0 - let baseX = screenPos.x - let baseY = screenPos.y - - switch (schNetLabel.anchor_side) { - case "left": - rotation = 0 - break - case "right": - rotation = 180 - baseX -= screenLabelWidth - break - case "top": - rotation = -90 - baseX -= screenLabelHeight / 2 - baseY += screenLabelWidth / 2 - break - case "bottom": - rotation = 90 - baseX -= screenLabelHeight / 2 - baseY -= screenLabelWidth / 2 - break - default: - rotation = 0 + const fullWidthFsr = + textWidthFSR + + ARROW_POINT_WIDTH_FSR * 2 + + END_PADDING_EXTRA_PER_CHARACTER_FSR * schNetLabel.text.length + + END_PADDING_FSR + const screenAnchorPosition = schNetLabel.anchor_position + ? applyToPoint(realToScreenTransform, schNetLabel.anchor_position) + : { + x: + screenCenter.x - + (screenTextGrowthVec.x * fullWidthFsr * fontSizePx) / 2, + y: + screenCenter.y - + (screenTextGrowthVec.y * fullWidthFsr * fontSizePx) / 2, + } + const realAnchorPosition = schNetLabel.anchor_position ?? { + x: + schNetLabel.center.x - + (realTextGrowthVec.x * fullWidthFsr * fontSizeMm) / 2, + y: + schNetLabel.center.y - + (realTextGrowthVec.y * fullWidthFsr * fontSizeMm) / 2, } - // Create transform string for rotation - const transformString = `rotate(${rotation} ${screenPos.x} ${screenPos.y})` + // Get rotation angle based on anchor_side + const pathRotation = { + left: 0, + top: -90, + bottom: 90, + right: 180, + }[schNetLabel.anchor_side] - // Calculate the points for the path - const points = [ - { x: baseX, y: baseY }, // Left edge - { x: baseX + screenLabelWidth - screenArrowPoint, y: baseY }, // Top edge - { x: baseX + screenLabelWidth, y: baseY + screenLabelHeight / 2 }, // Arrow point + // Calculate the points for the outline + const screenOutlinePoints: Array<{ x: number; y: number }> = [ + // Arrow point in font-relative coordinates + { + x: 0, + y: 0, + }, + // Top left corner in font-relative coordinates + { + x: ARROW_POINT_WIDTH_FSR, + y: 0.6, + }, + // Top right corner in font-relative coordinates { - x: baseX + screenLabelWidth - screenArrowPoint, - y: baseY + screenLabelHeight, - }, // Bottom after arrow - { x: baseX, y: baseY + screenLabelHeight }, // Bottom left corner - ] + x: + ARROW_POINT_WIDTH_FSR * 2 + + END_PADDING_FSR + + END_PADDING_EXTRA_PER_CHARACTER_FSR * schNetLabel.text.length + + textWidthFSR, + y: 0.6, + }, + // Bottom right corner in font-relative coordinates + { + x: + ARROW_POINT_WIDTH_FSR * 2 + + END_PADDING_FSR + + END_PADDING_EXTRA_PER_CHARACTER_FSR * schNetLabel.text.length + + textWidthFSR, + y: -0.6, + }, + // Bottom left corner in font-relative coordinates + { + x: ARROW_POINT_WIDTH_FSR, + y: -0.6, + }, + ].map((fontRelativePoint) => + applyToPoint( + compose( + realToScreenTransform, + translate(realAnchorPosition.x, realAnchorPosition.y), + scale(fontSizeMm), + rotate((pathRotation / 180) * Math.PI), + ), + fontRelativePoint, + ), + ) // Create the label path const pathD = ` - M ${points[0]?.x},${points[0]?.y} - L ${points[1]?.x},${points[1]?.y} - L ${points[2]?.x},${points[2]?.y} - L ${points[3]?.x},${points[3]?.y} - L ${points[4]?.x},${points[4]?.y} + M ${screenOutlinePoints[0]!.x},${screenOutlinePoints[0]!.y} + L ${screenOutlinePoints[1]!.x},${screenOutlinePoints[1]!.y} + L ${screenOutlinePoints[2]!.x},${screenOutlinePoints[2]!.y} + L ${screenOutlinePoints[3]!.x},${screenOutlinePoints[3]!.y} + L ${screenOutlinePoints[4]!.x},${screenOutlinePoints[4]!.y} Z ` @@ -86,17 +148,30 @@ export const createSvgObjectsForSchNetLabel = ( d: pathD, fill: "white", stroke: colorMap.schematic.label_global, - "stroke-width": `${getSchStrokeSize(transform)}px`, - transform: transformString, + "stroke-width": `${getSchStrokeSize(realToScreenTransform)}px`, }, value: "", children: [], }) - // Calculate text position (centered in label, accounting for arrow) - const screenTextX = baseX + (screenLabelWidth - screenArrowPoint) / 2 - // Adjust text Y position for better vertical centering - const screenTextY = baseY + screenLabelHeight / 2 + fontSize * 0.05 + const screenTextPos = { + x: screenAnchorPosition.x + screenTextGrowthVec.x * fontSizePx * 0.5, + y: screenAnchorPosition.y + screenTextGrowthVec.y * fontSizePx * 0.5, + } + + const textAnchor = { + left: "start", + top: "start", + bottom: "start", + right: "end", + }[schNetLabel.anchor_side] + + const textTransformString = { + left: "", + right: "", + top: `rotate(90 ${screenTextPos.x} ${screenTextPos.y})`, + bottom: `rotate(-90 ${screenTextPos.x} ${screenTextPos.y})`, + }[schNetLabel.anchor_side] // Add the label text svgObjects.push({ @@ -104,14 +179,15 @@ export const createSvgObjectsForSchNetLabel = ( type: "element", attributes: { class: "net-label-text", - x: screenTextX.toString(), - y: screenTextY.toString(), + x: screenTextPos.x.toString(), + y: screenTextPos.y.toString(), fill: colorMap.schematic.label_global, - "text-anchor": "middle", - "dominant-baseline": "central", // Changed to central for better vertical alignment + "text-anchor": textAnchor, + "dominant-baseline": "central", "font-family": "sans-serif", - "font-size": `${fontSize}px`, - transform: transformString, + "font-variant-numeric": "tabular-nums", + "font-size": `${fontSizePx}px`, + transform: textTransformString, }, children: [ { diff --git a/lib/sch/svg-object-fns/create-svg-objects-for-sch-port-pin-label.ts b/lib/sch/svg-object-fns/create-svg-objects-for-sch-port-pin-label.ts index a66dc4f..1734e09 100644 --- a/lib/sch/svg-object-fns/create-svg-objects-for-sch-port-pin-label.ts +++ b/lib/sch/svg-object-fns/create-svg-objects-for-sch-port-pin-label.ts @@ -5,7 +5,7 @@ import type { } from "circuit-json" import type { SvgObject } from "lib/svg-object" import { colorMap } from "lib/utils/colors" -import { getSchFontSize } from "lib/utils/get-sch-font-size" +import { getSchScreenFontSize } from "lib/utils/get-sch-font-size" import { getUnitVectorFromOutsideToEdge } from "lib/utils/get-unit-vector-from-outside-to-edge" import { applyToPoint, type Matrix } from "transformation-matrix" @@ -61,7 +61,7 @@ export const createSvgObjectsForSchPortPinLabel = (params: { ? "start" : "end", "dominant-baseline": "middle", - "font-size": `${getSchFontSize(transform, "pin_number")}px`, + "font-size": `${getSchScreenFontSize(transform, "pin_number")}px`, transform: schPort.side_of_component === "top" || schPort.side_of_component === "bottom" diff --git a/lib/sch/svg-object-fns/create-svg-objects-for-sch-port-pin-number-text.ts b/lib/sch/svg-object-fns/create-svg-objects-for-sch-port-pin-number-text.ts index 1edfbfb..a2f67b0 100644 --- a/lib/sch/svg-object-fns/create-svg-objects-for-sch-port-pin-number-text.ts +++ b/lib/sch/svg-object-fns/create-svg-objects-for-sch-port-pin-number-text.ts @@ -5,7 +5,7 @@ import type { } from "circuit-json" import type { SvgObject } from "lib/svg-object" import { colorMap } from "lib/utils/colors" -import { getSchFontSize } from "lib/utils/get-sch-font-size" +import { getSchScreenFontSize } from "lib/utils/get-sch-font-size" import { getUnitVectorFromOutsideToEdge } from "lib/utils/get-unit-vector-from-outside-to-edge" import { applyToPoint, type Matrix } from "transformation-matrix" @@ -58,7 +58,7 @@ export const createSvgObjectsForSchPortPinNumberText = (params: { fill: colorMap.schematic.pin_number, "text-anchor": "middle", "dominant-baseline": "auto", - "font-size": `${getSchFontSize(transform, "pin_number")}px`, + "font-size": `${getSchScreenFontSize(transform, "pin_number")}px`, transform: schPort.side_of_component === "top" || schPort.side_of_component === "bottom" diff --git a/lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-box.ts b/lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-box.ts index 3473434..343b4fe 100644 --- a/lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-box.ts +++ b/lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-box.ts @@ -13,7 +13,7 @@ import { applyToPoint, type Matrix } from "transformation-matrix" import { createSvgObjectsFromSchematicComponentWithSymbol } from "./create-svg-objects-from-sch-component-with-symbol" import { createSvgObjectsFromSchPortOnBox } from "./create-svg-objects-from-sch-port-on-box" import { getSchStrokeSize } from "lib/utils/get-sch-stroke-size" -import { getSchFontSize } from "lib/utils/get-sch-font-size" +import { getSchScreenFontSize } from "lib/utils/get-sch-font-size" export const createSvgObjectsFromSchematicComponentWithBox = ({ component: schComponent, @@ -62,7 +62,7 @@ export const createSvgObjectsFromSchematicComponentWithBox = ({ x: schComponent.center.x + schComponent.size.width / 2, y: schComponent.center.y + schComponent.size.height / 2 + 0.5, // Above the component top edge }) - const fontSizePx = getSchFontSize(transform, "manufacturer_number") + const fontSizePx = getSchScreenFontSize(transform, "manufacturer_number") // Add manufacturer number and component name text if (srcComponent?.manufacturer_part_number) { diff --git a/lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-symbol.ts b/lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-symbol.ts index c9375ae..539b6b3 100644 --- a/lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-symbol.ts +++ b/lib/sch/svg-object-fns/create-svg-objects-from-sch-component-with-symbol.ts @@ -17,7 +17,7 @@ import { import { getSchStrokeSize } from "lib/utils/get-sch-stroke-size" import { matchSchPortsToSymbolPorts } from "lib/utils/match-sch-ports-with-symbol-ports" import { pointPairsToMatrix } from "lib/utils/point-pairs-to-matrix" -import { getSchFontSize } from "lib/utils/get-sch-font-size" +import { getSchScreenFontSize } from "lib/utils/get-sch-font-size" import type { TextPrimitive } from "schematic-symbols" const ninePointAnchorToTextAnchor: Record< @@ -138,7 +138,7 @@ export const createSvgObjectsFromSchematicComponentWithSymbol = ({ "dominant-baseline": ninePointAnchorToDominantBaseline[text.anchor], "text-anchor": ninePointAnchorToTextAnchor[text.anchor], "font-family": "sans-serif", - "font-size": `${getSchFontSize(realToScreenTransform, "reference_designator")}px`, + "font-size": `${getSchScreenFontSize(realToScreenTransform, "reference_designator")}px`, }, value: "", children: [ diff --git a/lib/utils/get-sch-font-size.ts b/lib/utils/get-sch-font-size.ts index cb230cc..cae31f3 100644 --- a/lib/utils/get-sch-font-size.ts +++ b/lib/utils/get-sch-font-size.ts @@ -1,13 +1,19 @@ import type { Matrix } from "transformation-matrix" import { applyToPoint } from "transformation-matrix" -export const getSchFontSize = ( +type SchTextType = + | "pin_number" + | "reference_designator" + | "manufacturer_number" + | "net_label" + +export const getSchMmFontSize = (textType: SchTextType) => { + return textType === "pin_number" ? 0.15 : 0.18 +} + +export const getSchScreenFontSize = ( transform: Matrix, - textType: - | "pin_number" - | "reference_designator" - | "manufacturer_number" - | "net_label", + textType: SchTextType, ) => { - return Math.abs(transform.a) * (textType === "pin_number" ? 0.15 : 0.18) + return Math.abs(transform.a) * getSchMmFontSize(textType) } diff --git a/lib/utils/get-unit-vector-from-edge-to-outside.ts b/lib/utils/get-unit-vector-from-edge-to-outside.ts new file mode 100644 index 0000000..d05ac63 --- /dev/null +++ b/lib/utils/get-unit-vector-from-edge-to-outside.ts @@ -0,0 +1,15 @@ +export const getUnitVectorFromEdgeToOutside = ( + side: "top" | "bottom" | "left" | "right", +) => { + switch (side) { + case "top": + return { x: 0, y: 1 } + case "bottom": + return { x: 0, y: -1 } + case "left": + return { x: -1, y: 0 } + case "right": + return { x: 1, y: 0 } + } + throw new Error(`Invalid side: ${side}`) +} diff --git a/package.json b/package.json index de3f28a..55ca236 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "vite-tsconfig-paths": "^5.0.1" }, "peerDependencies": { - "circuit-json": "^0.0.98" + "circuit-json": "^0.0.99" }, "dependencies": { "@tscircuit/footprinter": "^0.0.57", diff --git a/tests/fixtures/get-test-fixture.ts b/tests/fixtures/get-test-fixture.ts index 3c0a32f..893540a 100644 --- a/tests/fixtures/get-test-fixture.ts +++ b/tests/fixtures/get-test-fixture.ts @@ -1,10 +1,9 @@ import { Circuit } from "@tscircuit/core" export const getTestFixture = () => { - const project = new Circuit() + const circuit = new Circuit() return { - project, - circuit: project, + circuit, } } diff --git a/tests/sch/__snapshots__/net-label.snap.svg b/tests/sch/__snapshots__/net-label.snap.svg index a47b2e3..0f8d51f 100644 --- a/tests/sch/__snapshots__/net-label.snap.svg +++ b/tests/sch/__snapshots__/net-label.snap.svg @@ -9,11 +9,81 @@ .pin-number { fill: rgb(169, 0, 0); } .port-label { fill: rgb(0, 100, 100); } .component-name { fill: rgb(0, 100, 100); } - GND \ No newline at end of file + -1,-1-1,0-1,1-1,2-1,3-1,4-1,50,-10,00,10,20,30,40,51,-11,01,11,21,31,41,52,-12,02,12,22,32,42,53,-13,03,13,23,33,43,54,-14,04,14,24,34,44,55,-15,05,15,25,35,45,5VCCGNDSDASCLshort labelWWWWWWWWWWsuperlong label. This is a long one!CENTER1CENTER2CENTER3CENTER4anchor_side=topanchor_side=bottomanchor_side=leftanchor_side=rightcenter1center2center3center4 \ No newline at end of file diff --git a/tests/sch/net-label.test.tsx b/tests/sch/net-label.test.tsx index 5469c6e..4bf1052 100644 --- a/tests/sch/net-label.test.tsx +++ b/tests/sch/net-label.test.tsx @@ -1,18 +1,117 @@ import { expect, test } from "bun:test" +import type { SchematicNetLabel } from "circuit-json" import { convertCircuitJsonToSchematicSvg } from "lib/index" -import { getTestFixture } from "tests/fixtures/get-test-fixture" test("schematic net label", () => { - const { project } = getTestFixture() - - project.add( - - - , - ) + // NOTE: center isn't computed properly where anchor_position is defined but + // this doesn't affect the implementation, which prefers to use anchor_position. + // There are some net labels missing anchor_position intentionally to test + // computing the anchor position + const circuitJson: SchematicNetLabel[] = [ + { + type: "schematic_net_label", + source_net_id: "net1", + center: { x: 0, y: 3 }, + anchor_position: { x: 0, y: 3 }, + anchor_side: "top", + text: "VCC", + }, + { + type: "schematic_net_label", + source_net_id: "net2", + center: { x: 1, y: 3 }, + anchor_position: { x: 1, y: 3 }, + anchor_side: "bottom", + text: "GND", + }, + { + type: "schematic_net_label", + source_net_id: "net3", + center: { x: 0, y: 4 }, + anchor_position: { x: 0, y: 4 }, + anchor_side: "left", + text: "SDA", + }, + { + type: "schematic_net_label", + source_net_id: "net4", + center: { x: 2, y: 4 }, + anchor_position: { x: 2, y: 4 }, + anchor_side: "right", + text: "SCL", + }, + { + type: "schematic_net_label", + source_net_id: "net5", + center: { x: 0, y: 1 }, + anchor_position: { x: 0, y: 1 }, + anchor_side: "left", + text: "short label", + }, + { + type: "schematic_net_label", + source_net_id: "net6", + center: { x: 0, y: 0 }, + anchor_position: { x: 0, y: 0 }, + anchor_side: "left", + text: "WWWWWWWWWW", // W is a wide character + }, + { + type: "schematic_net_label", + source_net_id: "net5", + center: { x: 2.5, y: 0 }, + anchor_position: { x: 2.5, y: 0 }, + anchor_side: "bottom", + text: "superlong label. This is a long one!", + }, + // MISSING ANCHOR POSITION + { + type: "schematic_net_label", + source_net_id: "net5", + center: { x: 4, y: 1 }, + anchor_side: "left", + text: "CENTER1", + }, + { + type: "schematic_net_label", + source_net_id: "net5", + center: { x: 4, y: 2 }, + anchor_side: "right", + text: "CENTER2", + }, + { + type: "schematic_net_label", + source_net_id: "net5", + center: { x: 4, y: 3 }, + anchor_side: "top", + text: "CENTER3", + }, + { + type: "schematic_net_label", + source_net_id: "net5", + center: { x: 4, y: 4 }, + anchor_side: "bottom", + text: "CENTER4", + }, + ] expect( // @ts-ignore - convertCircuitJsonToSchematicSvg(project.getCircuitJson()), + convertCircuitJsonToSchematicSvg(circuitJson, { + grid: { + cellSize: 1, + labelCells: true, + }, + labeledPoints: [ + { x: 0, y: 3, label: "anchor_side=top" }, + { x: 1, y: 3, label: "anchor_side=bottom" }, + { x: 0, y: 4, label: "anchor_side=left" }, + { x: 2, y: 4, label: "anchor_side=right" }, + { x: 4, y: 1, label: "center1" }, + { x: 4, y: 2, label: "center2" }, + { x: 4, y: 3, label: "center3" }, + { x: 4, y: 4, label: "center4" }, + ], + }), ).toMatchSvgSnapshot(import.meta.path) }) diff --git a/tests/sch/resistor.test.tsx b/tests/sch/resistor.test.tsx index 56db49b..38da687 100644 --- a/tests/sch/resistor.test.tsx +++ b/tests/sch/resistor.test.tsx @@ -3,9 +3,9 @@ import { convertCircuitJsonToSchematicSvg } from "lib/index" import { getTestFixture } from "tests/fixtures/get-test-fixture" test("schematic resistor", () => { - const { project } = getTestFixture() + const { circuit } = getTestFixture() - project.add( + circuit.add( { expect( // @ts-ignore convertCircuitJsonToSchematicSvg( - project + circuit .getCircuitJson() // TEMPORARY HACK: until @tscircuit/core supports symbol_display_value .map((elm) => { diff --git a/tests/sch/trace-overlap.test.tsx b/tests/sch/trace-overlap.test.tsx index 5cc4f6d..a1c5e11 100644 --- a/tests/sch/trace-overlap.test.tsx +++ b/tests/sch/trace-overlap.test.tsx @@ -3,9 +3,9 @@ import { convertCircuitJsonToSchematicSvg } from "lib" import { getTestFixture } from "tests/fixtures/get-test-fixture" test("schematic trace overlap", () => { - const { project } = getTestFixture() + const { circuit } = getTestFixture() - project.add( + circuit.add( { ) expect( - convertCircuitJsonToSchematicSvg(project.getCircuitJson()), + convertCircuitJsonToSchematicSvg(circuit.getCircuitJson()), ).toMatchSvgSnapshot(import.meta.path) })