commit 55c07770e1147dbd95c22cca356ee1f54351add4 Author: Mikael Lövqvist Date: Wed Feb 18 03:39:50 2026 +0100 Initial commit diff --git a/fix-up.mjs b/fix-up.mjs new file mode 100644 index 0000000..8c58e5b --- /dev/null +++ b/fix-up.mjs @@ -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); diff --git a/fix2.mjs b/fix2.mjs new file mode 100644 index 0000000..bf4473f --- /dev/null +++ b/fix2.mjs @@ -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()); diff --git a/fix3.mjs b/fix3.mjs new file mode 100644 index 0000000..789e840 --- /dev/null +++ b/fix3.mjs @@ -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( + /]*)>/, + (match, attrs) => { + if (/style=/.test(attrs)) { + return ` `style="${style};background:${bg}"` + )}>`; + } + return ``; + } + ); +} + +writeFileSync(output, finalSvg); diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..d0b3cb9 --- /dev/null +++ b/notes.md @@ -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 ``. +* 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 `` will land exactly where KiCad paths would have landed. + +Once that is true: + +* You can remove all path glyphs. +* Keep only ``. +* Remove invisible duplicate ``. +* 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. + diff --git a/package.json b/package.json new file mode 100644 index 0000000..95ffdfa --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/svgo.config.js b/svgo.config.js new file mode 100644 index 0000000..ea2823c --- /dev/null +++ b/svgo.config.js @@ -0,0 +1,18 @@ +export default { + plugins: [ + { + name: "removeViewBox", + active: false + }, + { + name: "removeDimensions", + active: true + }, + { + name: "removeAttrs", + params: { + attrs: "(fill|style)" + } + } + ] +}; diff --git a/t1.sh b/t1.sh new file mode 100644 index 0000000..ea1387f --- /dev/null +++ b/t1.sh @@ -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' +