schematic-symbols 0.0.1
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/.github/CODEOWNERS +1 -0
- package/.github/workflows/bun-pver-release.yml +25 -0
- package/README.md +88 -0
- package/assets/symbols-svg-json/boxresistor.json +90 -0
- package/assets/symbols-svg-json/capacitor.json +76 -0
- package/assets/symbols-svg-json/capacitor_polarized.json +83 -0
- package/assets/symbols-svg-json/diode.json +91 -0
- package/assets/symbols-svg-json/diode_bipolar_zener.json +166 -0
- package/assets/symbols-svg-json/diode_schottky.json +76 -0
- package/assets/symbols-svg-json/diode_zener.json +31 -0
- package/assets/symbols-svg-json/fuse.json +84 -0
- package/assets/symbols-svg-json/led.json +205 -0
- package/assets/symbols-svg-json/mosfet_depletion_normally_on.json +267 -0
- package/assets/symbols-svg-json/potentiometer.json +137 -0
- package/assets/symbols-svg-json/potentiometer2.json +160 -0
- package/assets/symbols-svg-json/testshape.json +35 -0
- package/assets/symbols-svg-json/varistor.json +129 -0
- package/assets/symbols.svg +965 -0
- package/biome.json +44 -0
- package/bun.lockb +0 -0
- package/dev-server.ts +22 -0
- package/drawing/arrow.ts +41 -0
- package/drawing/box.ts +5 -0
- package/drawing/circle.ts +7 -0
- package/drawing/defineSymbol.ts +5 -0
- package/drawing/getBoundsOfSvgJson.ts +44 -0
- package/drawing/getSvg.ts +133 -0
- package/drawing/index.ts +11 -0
- package/drawing/mapColor.ts +10 -0
- package/drawing/ninePointAnchorToSvgAnchor.ts +11 -0
- package/drawing/path.ts +5 -0
- package/drawing/pathToSvgD.ts +9 -0
- package/drawing/resizeSymbol.ts +66 -0
- package/drawing/rotateSymbol.ts +94 -0
- package/drawing/svgPathToPoints.ts +50 -0
- package/drawing/text.ts +8 -0
- package/drawing/types.ts +90 -0
- package/index.ts +1 -0
- package/package.json +25 -0
- package/scripts/build.ts +18 -0
- package/scripts/convertToObjectWithOrderedPositionIds.ts +52 -0
- package/scripts/generate-symbols-from-asset-svgs.ts +145 -0
- package/scripts/lib/applyGroupTransformsToChildren.ts +118 -0
- package/scripts/lib/findInnerText.ts +11 -0
- package/scripts/lib/generate-web-page.ts +60 -0
- package/scripts/lib/getTsFileContentForSvgGroup.ts +33 -0
- package/scripts/lib/serializeSvgPathCommands.ts +47 -0
- package/symbols/boxresistor_horz.ts +35 -0
- package/symbols/boxresistor_vert.ts +4 -0
- package/symbols/fuse_horz.ts +24 -0
- package/symbols/fuse_vert.ts +4 -0
- package/symbols/index.ts +31 -0
- package/symbols/led_horz.ts +18 -0
- package/symbols/led_vert.ts +4 -0
- package/symbols/mosfet_depletion_normally_on_horz.ts +19 -0
- package/symbols/mosfet_depletion_normally_on_vert.ts +21 -0
- package/symbols/potentiometer2_horz.ts +14 -0
- package/symbols/potentiometer2_vert.ts +4 -0
- package/symbols/potentiometer_horz.ts +18 -0
- package/symbols/potentiometer_vert.ts +6 -0
- package/symbols/varistor_horz.ts +24 -0
- package/symbols/varistor_vert.ts +4 -0
- package/tests/assets/boxresistor-untransformed.json +187 -0
- package/tests/assets/testshape-untransformed.json +25 -0
- package/tests/normalize-svg.test.ts +29 -0
- package/tsconfig.json +28 -0
package/biome.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
|
|
3
|
+
"organizeImports": {
|
|
4
|
+
"enabled": true
|
|
5
|
+
},
|
|
6
|
+
"formatter": {
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"indentStyle": "space"
|
|
9
|
+
},
|
|
10
|
+
"files": {
|
|
11
|
+
"ignore": ["cosmos-export", "dist", "package.json"]
|
|
12
|
+
},
|
|
13
|
+
"javascript": {
|
|
14
|
+
"formatter": {
|
|
15
|
+
"jsxQuoteStyle": "double",
|
|
16
|
+
"quoteProperties": "asNeeded",
|
|
17
|
+
"trailingCommas": "all",
|
|
18
|
+
"semicolons": "asNeeded",
|
|
19
|
+
"arrowParentheses": "always",
|
|
20
|
+
"bracketSpacing": true,
|
|
21
|
+
"bracketSameLine": false
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"linter": {
|
|
25
|
+
"enabled": false,
|
|
26
|
+
"rules": {
|
|
27
|
+
"recommended": true,
|
|
28
|
+
"suspicious": {
|
|
29
|
+
"noExplicitAny": "off"
|
|
30
|
+
},
|
|
31
|
+
"style": {
|
|
32
|
+
"noNonNullAssertion": "off",
|
|
33
|
+
"useFilenamingConvention": {
|
|
34
|
+
"level": "error",
|
|
35
|
+
"options": {
|
|
36
|
+
"strictCase": true,
|
|
37
|
+
"requireAscii": true,
|
|
38
|
+
"filenameCases": ["kebab-case", "export"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
package/bun.lockb
ADDED
|
Binary file
|
package/dev-server.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { generateWebPage } from "./scripts/lib/generate-web-page"
|
|
2
|
+
|
|
3
|
+
console.log(`Serving on http://localhost:3077`)
|
|
4
|
+
Bun.serve({
|
|
5
|
+
port: 3077,
|
|
6
|
+
async fetch(req) {
|
|
7
|
+
const url = new URL(req.url)
|
|
8
|
+
|
|
9
|
+
if (url.pathname === "/") {
|
|
10
|
+
const html = generateWebPage()
|
|
11
|
+
return new Response(html, {
|
|
12
|
+
headers: { "Content-Type": "text/html" },
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Serve static files
|
|
17
|
+
return new Response("", {
|
|
18
|
+
status: 404,
|
|
19
|
+
})
|
|
20
|
+
// return new Response(await Bun.file(url.pathname))
|
|
21
|
+
},
|
|
22
|
+
})
|
package/drawing/arrow.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Point, Primitive } from "drawing"
|
|
2
|
+
import { path } from "./path"
|
|
3
|
+
|
|
4
|
+
export const arrow = (
|
|
5
|
+
start: Point,
|
|
6
|
+
end: Point,
|
|
7
|
+
color: string = "black",
|
|
8
|
+
headSize: number = 10,
|
|
9
|
+
): Primitive[] => {
|
|
10
|
+
// Calculate the angle and length of the arrow
|
|
11
|
+
const dx = end.x - start.x
|
|
12
|
+
const dy = end.y - start.y
|
|
13
|
+
const angle = Math.atan2(dy, dx)
|
|
14
|
+
const length = Math.sqrt(dx * dx + dy * dy)
|
|
15
|
+
|
|
16
|
+
// Calculate the points for the arrow head
|
|
17
|
+
const headAngle = Math.PI / 6 // 30 degrees
|
|
18
|
+
const headPoint1 = {
|
|
19
|
+
x: end.x - headSize * Math.cos(angle - headAngle),
|
|
20
|
+
y: end.y - headSize * Math.sin(angle - headAngle),
|
|
21
|
+
}
|
|
22
|
+
const headPoint2 = {
|
|
23
|
+
x: end.x - headSize * Math.cos(angle + headAngle),
|
|
24
|
+
y: end.y - headSize * Math.sin(angle + headAngle),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return [
|
|
28
|
+
// Arrow body
|
|
29
|
+
path({
|
|
30
|
+
points: [start, end],
|
|
31
|
+
color: color,
|
|
32
|
+
fill: false,
|
|
33
|
+
}),
|
|
34
|
+
// Arrow head
|
|
35
|
+
path({
|
|
36
|
+
points: [headPoint1, end, headPoint2],
|
|
37
|
+
color: color,
|
|
38
|
+
fill: true,
|
|
39
|
+
}),
|
|
40
|
+
]
|
|
41
|
+
}
|
package/drawing/box.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { INode } from "svgson"
|
|
2
|
+
import { svgPathToPoints } from "./svgPathToPoints"
|
|
3
|
+
|
|
4
|
+
export function getBoundsOfSvgJson(svgJson: INode): {
|
|
5
|
+
minX: number
|
|
6
|
+
maxX: number
|
|
7
|
+
minY: number
|
|
8
|
+
maxY: number
|
|
9
|
+
width: number
|
|
10
|
+
height: number
|
|
11
|
+
centerX: number
|
|
12
|
+
centerY: number
|
|
13
|
+
} {
|
|
14
|
+
let minX = Infinity
|
|
15
|
+
let maxX = -Infinity
|
|
16
|
+
let minY = Infinity
|
|
17
|
+
let maxY = -Infinity
|
|
18
|
+
|
|
19
|
+
function processNode(node: INode) {
|
|
20
|
+
if (node.name === "path" && node.attributes.d) {
|
|
21
|
+
const points = svgPathToPoints(node.attributes.d)
|
|
22
|
+
|
|
23
|
+
for (const point of points) {
|
|
24
|
+
minX = Math.min(minX, point.x)
|
|
25
|
+
maxX = Math.max(maxX, point.x)
|
|
26
|
+
minY = Math.min(minY, point.y)
|
|
27
|
+
maxY = Math.max(maxY, point.y)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (node.children) {
|
|
32
|
+
node.children.forEach(processNode)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
processNode(svgJson)
|
|
37
|
+
|
|
38
|
+
const width = maxX - minX
|
|
39
|
+
const height = maxY - minY
|
|
40
|
+
const centerX = (minX + maxX) / 2
|
|
41
|
+
const centerY = (minY + maxY) / 2
|
|
42
|
+
|
|
43
|
+
return { minX, maxX, minY, maxY, width, height, centerX, centerY }
|
|
44
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { pathToSvgD } from "./pathToSvgD"
|
|
2
|
+
import { mapColor } from "./mapColor"
|
|
3
|
+
import type {
|
|
4
|
+
NinePointAnchor,
|
|
5
|
+
TextPrimitive,
|
|
6
|
+
Port,
|
|
7
|
+
SchSymbol,
|
|
8
|
+
Point,
|
|
9
|
+
} from "./types"
|
|
10
|
+
|
|
11
|
+
function createDiamondElement(center: Point, size: number = 0.05): string {
|
|
12
|
+
const { x, y } = center
|
|
13
|
+
const halfSize = size / 2
|
|
14
|
+
return `<path d="M ${x} ${y - halfSize} L ${x + halfSize} ${y} L ${x} ${y + halfSize} L ${x - halfSize} ${y} Z" fill="green" />`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createTextElement(primitive: TextPrimitive): {
|
|
18
|
+
text: string
|
|
19
|
+
anchor: string
|
|
20
|
+
} {
|
|
21
|
+
const { x, y, text, fontSize = 0.1, anchor } = primitive
|
|
22
|
+
let textAnchor: string
|
|
23
|
+
let dx: number = 0
|
|
24
|
+
let dy: number = 0
|
|
25
|
+
|
|
26
|
+
const capHeight = fontSize * 0.75
|
|
27
|
+
|
|
28
|
+
switch (anchor) {
|
|
29
|
+
case "top_left":
|
|
30
|
+
textAnchor = "start"
|
|
31
|
+
dy = fontSize
|
|
32
|
+
break
|
|
33
|
+
case "top_right":
|
|
34
|
+
textAnchor = "end"
|
|
35
|
+
dy = fontSize
|
|
36
|
+
break
|
|
37
|
+
case "bottom_left":
|
|
38
|
+
textAnchor = "start"
|
|
39
|
+
break
|
|
40
|
+
case "bottom_right":
|
|
41
|
+
textAnchor = "end"
|
|
42
|
+
break
|
|
43
|
+
case "center":
|
|
44
|
+
textAnchor = "middle"
|
|
45
|
+
dy = fontSize / 2
|
|
46
|
+
break
|
|
47
|
+
case "middle_top":
|
|
48
|
+
textAnchor = "middle"
|
|
49
|
+
dy = capHeight
|
|
50
|
+
break
|
|
51
|
+
case "middle_bottom":
|
|
52
|
+
textAnchor = "middle"
|
|
53
|
+
break
|
|
54
|
+
case "middle_left":
|
|
55
|
+
textAnchor = "start"
|
|
56
|
+
dy = capHeight / 2
|
|
57
|
+
break
|
|
58
|
+
case "middle_right":
|
|
59
|
+
textAnchor = "end"
|
|
60
|
+
dy = capHeight / 2
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
text: `<text x="${x}" y="${y}" dx="${dx}" dy="${dy}" text-anchor="${textAnchor}" style="font: ${fontSize ?? 0.1}px monospace; fill: ${mapColor("primary")}">${text}</text>`,
|
|
66
|
+
anchor: `<rect x="${x - 0.025 / 2}" y="${y - 0.025 / 2}" width="0.025" height="0.025" fill="blue" />`,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createPortElement(port: Port): string {
|
|
71
|
+
const { x, y, labels } = port
|
|
72
|
+
const rectSize = 0.05
|
|
73
|
+
const labelFontSize = 0.08
|
|
74
|
+
const label = labels[0] || ""
|
|
75
|
+
|
|
76
|
+
return `
|
|
77
|
+
<rect x="${x - rectSize / 2}" y="${y - rectSize / 2}" width="${rectSize}" height="${rectSize}" fill="red" />
|
|
78
|
+
<text x="${x - labelFontSize / 2}" y="${y + rectSize + labelFontSize / 2}" text-anchor="middle" style="font: ${labelFontSize}px monospace; fill: #833;">${label}</text>
|
|
79
|
+
`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getSvg(
|
|
83
|
+
symbol: SchSymbol,
|
|
84
|
+
options: { width?: number; height?: number; debug?: boolean } = {},
|
|
85
|
+
): string {
|
|
86
|
+
const { debug = false } = options
|
|
87
|
+
const { primitives, size, ports } = symbol
|
|
88
|
+
const svgElements = primitives.map((primitive) => {
|
|
89
|
+
switch (primitive.type) {
|
|
90
|
+
case "path":
|
|
91
|
+
return `<path d="${pathToSvgD(primitive.points, primitive.closed)}" fill="${primitive.fill ? mapColor(primitive.color) : "none"}" stroke="${mapColor(primitive.color)}" stroke-width="0.02" stroke-linecap="round" stroke-linejoin="round" />`
|
|
92
|
+
case "text":
|
|
93
|
+
const textElements = createTextElement(primitive)
|
|
94
|
+
return textElements.text + (debug ? textElements.anchor : "")
|
|
95
|
+
case "circle":
|
|
96
|
+
return `<circle cx="${primitive.x}" cy="${primitive.y}" r="${primitive.radius}" fill="${mapColor("primary")}" />`
|
|
97
|
+
case "box":
|
|
98
|
+
return `<rect x="${primitive.x}" y="${primitive.y}" width="${primitive.width}" height="${primitive.height}" fill="${mapColor("primary")}" />`
|
|
99
|
+
default:
|
|
100
|
+
return ""
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Use the center and the size to calculate the viewBox
|
|
105
|
+
const bufferMultiple = 1.1
|
|
106
|
+
const w = size.width * bufferMultiple
|
|
107
|
+
const h = size.height * bufferMultiple
|
|
108
|
+
const viewBox = {
|
|
109
|
+
x: symbol.center.x - w / 2,
|
|
110
|
+
y: symbol.center.y - h / 2,
|
|
111
|
+
width: w,
|
|
112
|
+
height: h,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (options.width && !options.height) {
|
|
116
|
+
options.height = options.width! * (viewBox.height / viewBox.width)
|
|
117
|
+
} else if (!options.width && options.height) {
|
|
118
|
+
options.width = options.height! * (viewBox.width / viewBox.height)
|
|
119
|
+
} else if (!options.width && !options.height) {
|
|
120
|
+
options.width = viewBox.width
|
|
121
|
+
options.height = viewBox.height
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const portElements = ports.map(createPortElement).join("\n ")
|
|
125
|
+
|
|
126
|
+
const centerDiamond = createDiamondElement(symbol.center)
|
|
127
|
+
|
|
128
|
+
return `<svg width="${options.width}" height="${options.height}" viewBox="${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}" xmlns="http://www.w3.org/2000/svg">
|
|
129
|
+
${svgElements.join("\n ")}
|
|
130
|
+
${portElements}
|
|
131
|
+
${centerDiamond}
|
|
132
|
+
</svg>`
|
|
133
|
+
}
|
package/drawing/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./types"
|
|
2
|
+
export * from "./box"
|
|
3
|
+
export * from "./arrow"
|
|
4
|
+
export * from "./getBoundsOfSvgJson"
|
|
5
|
+
export * from "./circle"
|
|
6
|
+
export * from "./getSvg"
|
|
7
|
+
export * from "./mapColor"
|
|
8
|
+
export * from "./resizeSymbol"
|
|
9
|
+
export * from "./svgPathToPoints"
|
|
10
|
+
export * from "./pathToSvgD"
|
|
11
|
+
export * from "./text"
|
package/drawing/path.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Point } from "drawing"
|
|
2
|
+
|
|
3
|
+
export function pathToSvgD(points: Point[], closed: boolean = false): string {
|
|
4
|
+
const pathCommands = points
|
|
5
|
+
.map((point, index) => `${index === 0 ? "M" : "L"}${point.x},${point.y}`)
|
|
6
|
+
.join(" ")
|
|
7
|
+
|
|
8
|
+
return closed ? `${pathCommands} Z` : pathCommands
|
|
9
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SchSymbol } from "./types"
|
|
2
|
+
export function resizeSymbol(
|
|
3
|
+
symbol: SchSymbol,
|
|
4
|
+
newSize: { width?: number; height?: number },
|
|
5
|
+
): SchSymbol {
|
|
6
|
+
const { width: oldWidth, height: oldHeight } = symbol.size
|
|
7
|
+
let scaleX = 1,
|
|
8
|
+
scaleY = 1
|
|
9
|
+
|
|
10
|
+
if (newSize.width && newSize.height) {
|
|
11
|
+
scaleX = newSize.width / oldWidth
|
|
12
|
+
scaleY = newSize.height / oldHeight
|
|
13
|
+
} else if (newSize.width) {
|
|
14
|
+
scaleX = scaleY = newSize.width / oldWidth
|
|
15
|
+
} else if (newSize.height) {
|
|
16
|
+
scaleX = scaleY = newSize.height / oldHeight
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const resizedPrimitives = symbol.primitives.map((primitive) => {
|
|
20
|
+
switch (primitive.type) {
|
|
21
|
+
case "path":
|
|
22
|
+
return {
|
|
23
|
+
...primitive,
|
|
24
|
+
points: primitive.points.map((p) => ({
|
|
25
|
+
x: p.x * scaleX,
|
|
26
|
+
y: p.y * scaleY,
|
|
27
|
+
})),
|
|
28
|
+
}
|
|
29
|
+
case "text":
|
|
30
|
+
case "circle":
|
|
31
|
+
return {
|
|
32
|
+
...primitive,
|
|
33
|
+
x: primitive.x * scaleX,
|
|
34
|
+
y: primitive.y * scaleY,
|
|
35
|
+
}
|
|
36
|
+
case "box":
|
|
37
|
+
return {
|
|
38
|
+
...primitive,
|
|
39
|
+
x: primitive.x * scaleX,
|
|
40
|
+
y: primitive.y * scaleY,
|
|
41
|
+
width: primitive.width * scaleX,
|
|
42
|
+
height: primitive.height * scaleY,
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
return primitive
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...symbol,
|
|
51
|
+
primitives: resizedPrimitives,
|
|
52
|
+
center: {
|
|
53
|
+
x: symbol.center.x * scaleX,
|
|
54
|
+
y: symbol.center.y * scaleY,
|
|
55
|
+
},
|
|
56
|
+
ports: symbol.ports.map((port) => ({
|
|
57
|
+
...port,
|
|
58
|
+
x: port.x * scaleX,
|
|
59
|
+
y: port.y * scaleY,
|
|
60
|
+
})),
|
|
61
|
+
size: {
|
|
62
|
+
width: oldWidth * scaleX,
|
|
63
|
+
height: oldHeight * scaleY,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { rotate, applyToPoint } from "transformation-matrix"
|
|
2
|
+
import type {
|
|
3
|
+
SchSymbol,
|
|
4
|
+
Primitive,
|
|
5
|
+
Point,
|
|
6
|
+
Port,
|
|
7
|
+
NinePointAnchor,
|
|
8
|
+
} from "./types"
|
|
9
|
+
|
|
10
|
+
const rotateAnchor = (anchor: NinePointAnchor): NinePointAnchor => {
|
|
11
|
+
switch (anchor) {
|
|
12
|
+
case "middle_top":
|
|
13
|
+
return "middle_right"
|
|
14
|
+
case "middle_bottom":
|
|
15
|
+
return "middle_left"
|
|
16
|
+
}
|
|
17
|
+
return anchor
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const rotateSymbol = (
|
|
21
|
+
symbol: SchSymbol,
|
|
22
|
+
overrides?: Partial<SchSymbol>,
|
|
23
|
+
): SchSymbol => {
|
|
24
|
+
const transform = rotate(Math.PI / 2, symbol.center.x, symbol.center.y)
|
|
25
|
+
|
|
26
|
+
const { primitives, center, size, ports } = symbol
|
|
27
|
+
|
|
28
|
+
const rotatedPrimitives = primitives.map((primitive): Primitive => {
|
|
29
|
+
primitive = { ...primitive }
|
|
30
|
+
switch (primitive.type) {
|
|
31
|
+
case "path":
|
|
32
|
+
return {
|
|
33
|
+
...primitive,
|
|
34
|
+
points: primitive.points.map(
|
|
35
|
+
(point) => applyToPoint(transform, point) as Point,
|
|
36
|
+
),
|
|
37
|
+
}
|
|
38
|
+
case "text":
|
|
39
|
+
const rotatedPoint = applyToPoint(transform, {
|
|
40
|
+
x: primitive.x,
|
|
41
|
+
y: primitive.y,
|
|
42
|
+
}) as Point
|
|
43
|
+
|
|
44
|
+
primitive.anchor = rotateAnchor(primitive.anchor)
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
...primitive,
|
|
48
|
+
x: rotatedPoint.x,
|
|
49
|
+
y: rotatedPoint.y,
|
|
50
|
+
}
|
|
51
|
+
case "circle":
|
|
52
|
+
const rotatedCenter = applyToPoint(transform, {
|
|
53
|
+
x: primitive.x,
|
|
54
|
+
y: primitive.y,
|
|
55
|
+
}) as Point
|
|
56
|
+
return {
|
|
57
|
+
...primitive,
|
|
58
|
+
x: rotatedCenter.x,
|
|
59
|
+
y: rotatedCenter.y,
|
|
60
|
+
}
|
|
61
|
+
case "box":
|
|
62
|
+
const rotatedCorner = applyToPoint(transform, {
|
|
63
|
+
x: primitive.x,
|
|
64
|
+
y: primitive.y,
|
|
65
|
+
}) as Point
|
|
66
|
+
return {
|
|
67
|
+
...primitive,
|
|
68
|
+
x: rotatedCorner.x,
|
|
69
|
+
y: rotatedCorner.y,
|
|
70
|
+
width: primitive.height,
|
|
71
|
+
height: primitive.width,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const rotatedPorts = ports.map(
|
|
77
|
+
(port): Port => ({
|
|
78
|
+
...port,
|
|
79
|
+
...(applyToPoint(transform, port) as Point),
|
|
80
|
+
}),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
primitives: rotatedPrimitives,
|
|
85
|
+
center,
|
|
86
|
+
ports: rotatedPorts,
|
|
87
|
+
// TODO recompute size using overrides
|
|
88
|
+
size: {
|
|
89
|
+
width: size.height,
|
|
90
|
+
height: size.width,
|
|
91
|
+
},
|
|
92
|
+
...overrides,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { parseSVG, makeAbsolute } from "svg-path-parser"
|
|
2
|
+
|
|
3
|
+
export function svgPathToPoints(d: string): Array<{ x: number; y: number }> {
|
|
4
|
+
const parsedPath = parseSVG(d)
|
|
5
|
+
const absolutePath = makeAbsolute(parsedPath)
|
|
6
|
+
|
|
7
|
+
const points: Array<{ x: number; y: number }> = []
|
|
8
|
+
let firstPoint: { x: number; y: number } | null = null
|
|
9
|
+
|
|
10
|
+
for (const command of absolutePath) {
|
|
11
|
+
switch (command.code) {
|
|
12
|
+
case "M":
|
|
13
|
+
if (!firstPoint) {
|
|
14
|
+
firstPoint = { x: command.x, y: command.y }
|
|
15
|
+
}
|
|
16
|
+
points.push({ x: command.x, y: command.y })
|
|
17
|
+
break
|
|
18
|
+
case "L":
|
|
19
|
+
case "H":
|
|
20
|
+
case "V":
|
|
21
|
+
points.push({ x: command.x, y: command.y })
|
|
22
|
+
break
|
|
23
|
+
case "C":
|
|
24
|
+
points.push(
|
|
25
|
+
{ x: command.x1, y: command.y1 },
|
|
26
|
+
{ x: command.x2, y: command.y2 },
|
|
27
|
+
{ x: command.x, y: command.y },
|
|
28
|
+
)
|
|
29
|
+
break
|
|
30
|
+
case "S":
|
|
31
|
+
case "Q":
|
|
32
|
+
points.push(
|
|
33
|
+
{ x: command.x0, y: command.y0 },
|
|
34
|
+
{ x: command.x, y: command.y },
|
|
35
|
+
)
|
|
36
|
+
break
|
|
37
|
+
case "T":
|
|
38
|
+
case "A":
|
|
39
|
+
points.push({ x: command.x, y: command.y })
|
|
40
|
+
break
|
|
41
|
+
case "Z":
|
|
42
|
+
if (firstPoint) {
|
|
43
|
+
points.push(firstPoint)
|
|
44
|
+
}
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return points
|
|
50
|
+
}
|
package/drawing/text.ts
ADDED
package/drawing/types.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export interface Point {
|
|
2
|
+
x: number
|
|
3
|
+
y: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface Port {
|
|
7
|
+
x: number
|
|
8
|
+
y: number
|
|
9
|
+
labels: string[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PathPrimitive {
|
|
13
|
+
type: "path"
|
|
14
|
+
fill: boolean
|
|
15
|
+
points: Point[]
|
|
16
|
+
color: string
|
|
17
|
+
closed?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TextPrimitive {
|
|
21
|
+
type: "text"
|
|
22
|
+
text: string
|
|
23
|
+
fontSize?: number
|
|
24
|
+
x: number
|
|
25
|
+
y: number
|
|
26
|
+
anchor: NinePointAnchor
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CirclePrimitive {
|
|
30
|
+
type: "circle"
|
|
31
|
+
x: number
|
|
32
|
+
y: number
|
|
33
|
+
radius: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface BoxPrimitive {
|
|
37
|
+
type: "box"
|
|
38
|
+
x: number
|
|
39
|
+
y: number
|
|
40
|
+
width: number
|
|
41
|
+
height: number
|
|
42
|
+
anchor: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type Primitive =
|
|
46
|
+
| PathPrimitive
|
|
47
|
+
| TextPrimitive
|
|
48
|
+
| CirclePrimitive
|
|
49
|
+
| BoxPrimitive
|
|
50
|
+
|
|
51
|
+
export interface SchSymbol {
|
|
52
|
+
primitives: Primitive[]
|
|
53
|
+
center: Point
|
|
54
|
+
ports: Port[]
|
|
55
|
+
size: {
|
|
56
|
+
width: number
|
|
57
|
+
height: number
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type Bounds = {
|
|
62
|
+
minX: number
|
|
63
|
+
maxX: number
|
|
64
|
+
minY: number
|
|
65
|
+
maxY: number
|
|
66
|
+
width: number
|
|
67
|
+
height: number
|
|
68
|
+
centerX: number
|
|
69
|
+
centerY: number
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SvgData {
|
|
73
|
+
paths: Record<string, PathPrimitive>
|
|
74
|
+
texts: Record<string, TextPrimitive>
|
|
75
|
+
refblocks: Record<string, Point>
|
|
76
|
+
bounds: Bounds
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type SvgAnchor = "start" | "middle" | "end"
|
|
80
|
+
|
|
81
|
+
export type NinePointAnchor =
|
|
82
|
+
| "top_left"
|
|
83
|
+
| "top_right"
|
|
84
|
+
| "bottom_left"
|
|
85
|
+
| "bottom_right"
|
|
86
|
+
| "center"
|
|
87
|
+
| "middle_top"
|
|
88
|
+
| "middle_bottom"
|
|
89
|
+
| "middle_left"
|
|
90
|
+
| "middle_right"
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as symbols } from "./symbols"
|