Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comprehensive Netlabel Size and Position Calculations with center and anchor_position support #113

Merged
merged 7 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
763 changes: 763 additions & 0 deletions lib/sch/arial-text-metrics.ts

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions lib/sch/convert-circuit-json-to-schematic-svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = []
Expand Down Expand Up @@ -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",
Expand Down
19 changes: 19 additions & 0 deletions lib/sch/estimate-text-width.ts
Original file line number Diff line number Diff line change
@@ -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
}
210 changes: 143 additions & 67 deletions lib/sch/svg-object-fns/create-svg-objects-for-sch-net-label.ts
Original file line number Diff line number Diff line change
@@ -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
`

Expand All @@ -86,32 +148,46 @@ 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({
name: "text",
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: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down Expand Up @@ -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: [
Expand Down
Loading
Loading