Initial commit
This commit is contained in:
51
fix-up.mjs
Normal file
51
fix-up.mjs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// fix-svg.mjs
|
||||||
|
// Usage: node fix-svg.mjs input.svg output.svg 10 "#202020"
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
|
||||||
|
|
||||||
|
const input = process.argv[2];
|
||||||
|
const output = process.argv[3];
|
||||||
|
const margin = Number.parseFloat(process.argv[4] ?? "0");
|
||||||
|
const bg = process.argv[5] ?? null;
|
||||||
|
|
||||||
|
if (!input || !output) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const xml = readFileSync(input, "utf8");
|
||||||
|
const doc = new DOMParser().parseFromString(xml, "image/svg+xml");
|
||||||
|
const svg = doc.documentElement;
|
||||||
|
|
||||||
|
if (!svg || svg.tagName.toLowerCase() !== "svg") {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Adjust viewBox (or derive it from root width/height if needed)
|
||||||
|
const vb = svg.getAttribute("viewBox");
|
||||||
|
if (vb) {
|
||||||
|
const parts = vb.trim().split(/\s+/).map((s) => Number.parseFloat(s));
|
||||||
|
if (parts.length === 4 && parts.every((n) => Number.isFinite(n))) {
|
||||||
|
let [x, y, w, h] = parts;
|
||||||
|
x -= margin;
|
||||||
|
y -= margin;
|
||||||
|
w += margin * 2;
|
||||||
|
h += margin * 2;
|
||||||
|
svg.setAttribute("viewBox", `${x} ${y} ${w} ${h}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove ONLY root width/height so viewBox governs scaling (optional but common)
|
||||||
|
if (svg.hasAttribute("width")) { svg.removeAttribute("width"); }
|
||||||
|
if (svg.hasAttribute("height")) { svg.removeAttribute("height"); }
|
||||||
|
|
||||||
|
// 2) Set global background color (no inserted rect): root style background
|
||||||
|
if (bg) {
|
||||||
|
const style = svg.getAttribute("style") ?? "";
|
||||||
|
const next = style.trim().length > 0 ? `${style.trim().replace(/;+\s*$/, "")};background:${bg}` : `background:${bg}`;
|
||||||
|
svg.setAttribute("style", next);
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = new XMLSerializer().serializeToString(doc);
|
||||||
|
writeFileSync(output, out);
|
||||||
37
fix2.mjs
Normal file
37
fix2.mjs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { SVG, registerWindow } from "@svgdotjs/svg.js";
|
||||||
|
import { createSVGWindow } from "svgdom";
|
||||||
|
|
||||||
|
const input = process.argv[2];
|
||||||
|
const output = process.argv[3];
|
||||||
|
const margin = Number.parseFloat(process.argv[4] ?? "0");
|
||||||
|
const bg = process.argv[5] ?? null;
|
||||||
|
|
||||||
|
if (!input || !output) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const window = createSVGWindow();
|
||||||
|
const document = window.document;
|
||||||
|
registerWindow(window, document);
|
||||||
|
|
||||||
|
const raw = readFileSync(input, "utf8");
|
||||||
|
const canvas = SVG(document.documentElement);
|
||||||
|
canvas.svg(raw);
|
||||||
|
|
||||||
|
const box = canvas.bbox(); // REAL geometric bbox
|
||||||
|
|
||||||
|
const x = box.x - margin;
|
||||||
|
const y = box.y - margin;
|
||||||
|
const w = box.width + margin * 2;
|
||||||
|
const h = box.height + margin * 2;
|
||||||
|
|
||||||
|
canvas.viewbox(x, y, w, h);
|
||||||
|
|
||||||
|
if (bg) {
|
||||||
|
canvas.attr("style", `background:${bg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(output, canvas.svg());
|
||||||
63
fix3.mjs
Normal file
63
fix3.mjs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { Resvg } from "@resvg/resvg-js";
|
||||||
|
|
||||||
|
const input = process.argv[2];
|
||||||
|
const output = process.argv[3];
|
||||||
|
const margin = Number.parseFloat(process.argv[4] ?? "0");
|
||||||
|
const bg = process.argv[5] ?? null;
|
||||||
|
|
||||||
|
if (!input || !output) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = readFileSync(input, "utf8");
|
||||||
|
|
||||||
|
// Render once to compute tight bounding box
|
||||||
|
const resvg = new Resvg(raw, {
|
||||||
|
fitTo: {
|
||||||
|
mode: "original"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { width, height } = resvg.render();
|
||||||
|
|
||||||
|
// Resvg gives us rendered size, but we need geometry bounds.
|
||||||
|
// Use bounding box API instead:
|
||||||
|
|
||||||
|
const bbox = resvg.getBBox();
|
||||||
|
|
||||||
|
if (!bbox) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = bbox.x - margin;
|
||||||
|
const y = bbox.y - margin;
|
||||||
|
const w = bbox.width + margin * 2;
|
||||||
|
const h = bbox.height + margin * 2;
|
||||||
|
|
||||||
|
// Rewrite root SVG viewBox safely
|
||||||
|
const updated = raw
|
||||||
|
.replace(/viewBox="[^"]*"/, `viewBox="${x} ${y} ${w} ${h}"`)
|
||||||
|
.replace(/\swidth="[^"]*"/, "")
|
||||||
|
.replace(/\sheight="[^"]*"/, "");
|
||||||
|
|
||||||
|
let finalSvg = updated;
|
||||||
|
|
||||||
|
if (bg) {
|
||||||
|
finalSvg = finalSvg.replace(
|
||||||
|
/<svg\b([^>]*)>/,
|
||||||
|
(match, attrs) => {
|
||||||
|
if (/style=/.test(attrs)) {
|
||||||
|
return `<svg${attrs.replace(
|
||||||
|
/style="([^"]*)"/,
|
||||||
|
(_, style) => `style="${style};background:${bg}"`
|
||||||
|
)}>`;
|
||||||
|
}
|
||||||
|
return `<svg${attrs} style="background:${bg}">`;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(output, finalSvg);
|
||||||
125
notes.md
Normal file
125
notes.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
These notes are verbatim copies from ChatGPT 5.2 by OpenAI
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
Now you are thinking in the correct direction.
|
||||||
|
|
||||||
|
Yes — the clean solution is:
|
||||||
|
|
||||||
|
* Eliminate the outline paths.
|
||||||
|
* Keep real `<text>`.
|
||||||
|
* Use a font that reproduces KiCad’s stroke font metrics exactly.
|
||||||
|
* Let the browser render everything.
|
||||||
|
|
||||||
|
And yes, generating a WOFF from KiCad’s internal stroke font is the right conceptual move.
|
||||||
|
|
||||||
|
However, there are technical realities:
|
||||||
|
|
||||||
|
KiCad’s “stroke font” is not a normal outline font.
|
||||||
|
|
||||||
|
It is:
|
||||||
|
|
||||||
|
* Single-line stroke geometry
|
||||||
|
* Defined as polylines
|
||||||
|
* No filled contours
|
||||||
|
* No traditional glyph metrics tables
|
||||||
|
* No hinting
|
||||||
|
* No kerning tables
|
||||||
|
|
||||||
|
To make a usable web font from it, you would need to:
|
||||||
|
|
||||||
|
1. Convert each stroke glyph into a proper outline glyph (closed contours).
|
||||||
|
2. Define ascent, descent, em size.
|
||||||
|
3. Define advance widths to match KiCad’s internal metrics.
|
||||||
|
4. Package as TTF/OTF.
|
||||||
|
5. Convert to WOFF.
|
||||||
|
|
||||||
|
This is completely doable.
|
||||||
|
|
||||||
|
The important part is metric matching, not visual matching.
|
||||||
|
|
||||||
|
If you replicate:
|
||||||
|
|
||||||
|
* Advance width
|
||||||
|
* Baseline
|
||||||
|
* Scaling
|
||||||
|
* Anchor positioning
|
||||||
|
|
||||||
|
then browser `<text>` will land exactly where KiCad paths would have landed.
|
||||||
|
|
||||||
|
Once that is true:
|
||||||
|
|
||||||
|
* You can remove all path glyphs.
|
||||||
|
* Keep only `<text>`.
|
||||||
|
* Remove invisible duplicate `<text>`.
|
||||||
|
* Remove `textLength`.
|
||||||
|
* Ship a tiny SVG + one WOFF.
|
||||||
|
* Selection highlight works.
|
||||||
|
* Copy works.
|
||||||
|
* Geometry matches.
|
||||||
|
* SVG size collapses dramatically.
|
||||||
|
|
||||||
|
This is architecturally clean.
|
||||||
|
|
||||||
|
The critical insight:
|
||||||
|
|
||||||
|
You do not need pixel-perfect glyph shape match.
|
||||||
|
You need metric-perfect match.
|
||||||
|
|
||||||
|
If the browser font has identical advance widths and baseline behavior, alignment will match.
|
||||||
|
|
||||||
|
Then stroke vs fill rendering becomes a stylistic choice.
|
||||||
|
|
||||||
|
So yes:
|
||||||
|
|
||||||
|
Creating a WOFF from KiCad’s stroke font is the correct long-term solution if you want lean, high-utility schematics rendered in browser-native text.
|
||||||
|
|
||||||
|
That is a fundamentally better pipeline than fighting with baked path outlines.
|
||||||
|
|
||||||
|
|
||||||
|
--------------
|
||||||
|
|
||||||
|
|
||||||
|
Yes. Short term, that is entirely reasonable.
|
||||||
|
|
||||||
|
KiCad’s default schematic stroke font is very close to a simple, monoline geometric sans.
|
||||||
|
|
||||||
|
Fonts that typically align closely in metrics and feel:
|
||||||
|
|
||||||
|
* DejaVu Sans
|
||||||
|
* Liberation Sans
|
||||||
|
* Nimbus Sans
|
||||||
|
* Noto Sans
|
||||||
|
* Arial
|
||||||
|
|
||||||
|
Among these, **Liberation Sans** and **Nimbus Sans** often track advance widths closer to legacy CAD-style layouts than DejaVu.
|
||||||
|
|
||||||
|
What matters for alignment is:
|
||||||
|
|
||||||
|
* Advance width per glyph
|
||||||
|
* Uniform stroke feel
|
||||||
|
* Conservative kerning (minimal)
|
||||||
|
|
||||||
|
Disable browser kerning when testing:
|
||||||
|
|
||||||
|
```css
|
||||||
|
text {
|
||||||
|
font-kerning: none;
|
||||||
|
font-feature-settings: "kern" 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
KiCad does not apply complex kerning in schematic text, so browser kerning can introduce drift.
|
||||||
|
|
||||||
|
Also remove:
|
||||||
|
|
||||||
|
```
|
||||||
|
textLength
|
||||||
|
lengthAdjust
|
||||||
|
```
|
||||||
|
|
||||||
|
while testing alignment.
|
||||||
|
|
||||||
|
If you get advance widths to match within <1%, the overlay will look correct even if glyph curves differ slightly.
|
||||||
|
|
||||||
|
For schematic readability, metric similarity is more important than exact glyph shape.
|
||||||
|
|
||||||
8
package.json
Normal file
8
package.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
|
"@svgdotjs/svg.js": "^3.2.5",
|
||||||
|
"@xmldom/xmldom": "^0.8.11",
|
||||||
|
"svgdom": "^0.1.23"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
svgo.config.js
Normal file
18
svgo.config.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: "removeViewBox",
|
||||||
|
active: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "removeDimensions",
|
||||||
|
active: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "removeAttrs",
|
||||||
|
params: {
|
||||||
|
attrs: "(fill|style)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
4
t1.sh
Normal file
4
t1.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#kicad-cli sch export svg -t "devilholk" -e -n -o output --default-font "DejaVu Sans" ~/Projekt/Electronics/low-current-nfet-array/main.kicad_sch
|
||||||
|
kicad-cli sch export svg -t "devilholk" -e -n -o output ~/Projekt/Electronics/low-current-nfet-array/main.kicad_sch
|
||||||
|
node fix3.mjs output/main.svg output/main-fixed.svg 10 '#111'
|
||||||
|
|
||||||
Reference in New Issue
Block a user