app
Adom Symbol
Public Made by Adomby adom
KiCad symbol creator with interactive viewer and delivery to KiCad/Fusion 360
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
/**
* SVG Theme Post-Processor
*
* Transforms KiCad CLI SVG export into the Adom branded dark theme.
* Replaces default KiCad colors with the Adom design system palette.
*
* KiCad default SVG uses:
* - White/transparent background
* - #840000 (dark red) strokes for body, group labels
* - #840000 text for pin names, numbers
* - Standard line widths
*
* Adom theme uses:
* - Dark canvas background (#0d1117)
* - Body: dark gradient fill, #30363d border, rounded corners
* - Pin wires: #4a5568 resting → #00e6dc hover
* - Pin names: #e6edf3 → #00e6dc hover
* - Pin numbers: #8b949e (discrete only; stripped from IC SVGs)
* - Group labels: #E6B450 (warm amber — visible on dark body)
*/
// KiCad SVG colors we want to replace
const KICAD_BODY_STROKE = '#840000'; // Body outlines, pin wires, geometric shapes
const KICAD_BODY_FILL = '#ffffc2'; // KiCad background fill for body
const KICAD_TEXT_STROKE = '#A90000'; // Stroked-text paths (pin names, numbers, group labels)
const KICAD_HIDDEN_TEXT = '#006464'; // Hidden property text (Reference, Footprint, etc.)
// Adom theme target colors
export const THEME_COLORS = {
canvasBg: '#0d1117',
bodyStroke: '#30363d',
bodyFill: '#1a2332',
bodyFillGrad: '#141c27',
pinWire: '#4a5568',
pinWireHover: '#00e6dc',
pinName: '#ffffff',
pinNameHover: '#00e6dc',
pinNumber: '#8b949e',
groupLabel: '#E6B450',
groupLabelHover: '#FFD180',
refText: '#484f58',
valueText: '#8b949e',
pinDot: '#4a5568',
pinDotHover: '#00e6dc',
};
/**
* Apply Adom dark theme to a KiCad-exported SVG string.
*
* Strategy: Inject a <style> block into the SVG that overrides KiCad's
* inline styles with Adom theme colors. Also inject CSS classes for
* hover effects that the viewer JS will trigger.
*
* @param {string} svg - Raw SVG string from KiCad CLI export
* @returns {string} Themed SVG string
*/
export function applyDarkTheme(svg) {
// Strip XML declaration and DOCTYPE
let themed = svg
.replace(/<\?xml[^?]*\?>/, '')
.replace(/<!DOCTYPE[^>]*>/, '')
.trim();
// Replace KiCad's default background rect (if present)
themed = themed.replace(
/(<rect[^>]*fill=")white("[^>]*>)/,
`$1${THEME_COLORS.canvasBg}$2`
);
// Inject a <defs> section with gradient for body rectangles
const defs = `
<defs>
<linearGradient id="adom-body-grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="${THEME_COLORS.bodyFill}"/>
<stop offset="100%" stop-color="${THEME_COLORS.bodyFillGrad}"/>
</linearGradient>
</defs>`;
// Inject <style> block for interactive hover effects (JS-toggled classes)
const style = `
<style>
/* Stroked text groups (KiCad renders text as paths in <g class="stroked-text">) */
g.stroked-text path {
stroke: ${THEME_COLORS.pinName} !important;
fill: ${THEME_COLORS.pinName} !important;
}
/* Hidden text elements (used for hit detection) */
text[opacity="0"] {
fill: ${THEME_COLORS.pinName} !important;
}
/* Pin highlight state (toggled by JS) */
.pin-highlight path {
stroke: ${THEME_COLORS.pinWireHover} !important;
}
.pin-highlight g.stroked-text path {
stroke: ${THEME_COLORS.pinNameHover} !important;
}
/* Group label highlight */
.group-highlight g.stroked-text path {
stroke: ${THEME_COLORS.groupLabelHover} !important;
}
</style>`;
// Insert defs and style right after the opening <svg> tag
themed = themed.replace(
/(<svg[^>]*>)/,
`$1\n${defs}\n${style}`
);
// ── Direct color replacement ──────────────────────────────────────
// KiCad CLI outputs colors in TWO forms:
// 1. Inline style: style="stroke:#840000; ..."
// 2. Attributes: stroke="#840000" fill="#ffffc2"
// We must handle both.
// Replace #840000 strokes (body, pins, wires) → theme pin wire color
// In style="..." attributes:
themed = themed.replace(/stroke:#840000/g, `stroke:${THEME_COLORS.pinWire}`);
// In stroke="..." attributes:
themed = themed.replace(/stroke="#840000"/g, `stroke="${THEME_COLORS.pinWire}"`);
// Replace #840000 fills (filled shapes like arrow triangles, dots)
// In style="..." attributes:
themed = themed.replace(/fill:#840000/g, `fill:${THEME_COLORS.pinDot}`);
// In fill="..." attributes:
themed = themed.replace(/fill="#840000"/g, `fill="${THEME_COLORS.pinDot}"`);
// Replace body fill #ffffc2 → gradient
themed = themed.replace(/fill:#ffffc2/gi, `fill:url(#adom-body-grad)`);
themed = themed.replace(/fill="#ffffc2"/gi, `fill="url(#adom-body-grad)"`);
themed = themed.replace(/fill="rgb\(255,\s*255,\s*194\)"/gi, `fill="url(#adom-body-grad)"`);
// Replace #000000 fills on the root <g> (KiCad's default group fill)
// This would make everything black on dark bg — make it transparent
themed = themed.replace(
/(<g\s+style="fill:#000000[^"]*")/,
(match) => match.replace('fill:#000000', 'fill:none')
);
// Replace #A90000 strokes (KiCad's stroked-text paths — pin names, numbers, group labels)
// These are rendered as thin stroke paths inside <g class="stroked-text"> elements
themed = themed.replace(/stroke:#A90000/g, `stroke:${THEME_COLORS.pinName}`);
themed = themed.replace(/stroke="#A90000"/g, `stroke="${THEME_COLORS.pinName}"`);
themed = themed.replace(/fill:#A90000/g, `fill:${THEME_COLORS.pinName}`);
themed = themed.replace(/fill="#A90000"/g, `fill="${THEME_COLORS.pinName}"`);
// Replace #006464 (KiCad's hidden text teal) with theme color
themed = themed.replace(/stroke:#006464/g, `stroke:${THEME_COLORS.refText}`);
themed = themed.replace(/fill:#006464/g, `fill:${THEME_COLORS.refText}`);
return themed;
}
/**
* Identify group label text elements in the SVG and re-color them.
* KiCad renders group labels as stroked-text groups with font-size ~1.016.
* We need to make them purple instead of the default pin color.
*
* @param {string} svg - SVG string (after applyDarkTheme)
* @param {string[]} groupNames - Array of group label text strings
* @returns {string} SVG with group labels colored
*/
export function colorGroupLabels(svg, groupNames) {
if (!groupNames || groupNames.length === 0) return svg;
// Group labels in KiCad SVG appear as:
// <text ...opacity="0"...>GROUP NAME</text>
// followed by <g class="stroked-text"><desc>GROUP NAME</desc>...paths...</g>
//
// We add a CSS class to the stroked-text group so we can color it purple
for (const name of groupNames) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Find the stroked-text group with this label
const re = new RegExp(
`(<g class="stroked-text"><desc>${escaped}</desc>)`,
'g'
);
svg = svg.replace(re, `<g class="stroked-text group-label-text"><desc>${name}</desc>`);
}
// Add CSS rule for group labels
svg = svg.replace(
'</style>',
` g.group-label-text path {
stroke: ${THEME_COLORS.groupLabel} !important;
fill: ${THEME_COLORS.groupLabel} !important;
}
g.group-label-text.group-highlight path {
stroke: ${THEME_COLORS.groupLabelHover} !important;
}
</style>`
);
return svg;
}
/**
* Strip Reference and Value property text from the SVG.
* KiCad CLI places these at arbitrary positions that overlap the body.
* The viewer toolbar and info bar already show this metadata.
*
* @param {string} svg - SVG string
* @param {string} reference - Reference designator (e.g., "U", "D", "Q")
* @param {string} value - Value text (part name)
* @returns {string} SVG with ref/value text removed
*/
export function stripRefValueText(svg, reference, value) {
for (const propText of [reference + '?', reference, value]) {
if (!propText) continue;
const esc = propText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Strip invisible <text> element (may have nested <g> wrapper before the stroked-text)
svg = svg.replace(
new RegExp('<text[^>]*opacity="0"[^>]*>' + esc + '</text>', 'g'),
''
);
// Strip the stroked-text group (the visible rendered text)
svg = svg.replace(
new RegExp('<g class="stroked-text"><desc>' + esc + '</desc>[\\s\\S]*?</g>', 'g'),
''
);
}
return svg;
}
/**
* Color pin number text in an IC symbol SVG to distinguish from pin names.
*
* KiCad renders pin names (inside body) and pin numbers (on wires) with the
* same color. This adds a CSS class to pin number stroked-text groups so they
* render in muted gray — visually subordinate to the white pin names.
*
* @param {string} svg - SVG string (after applyDarkTheme)
* @param {string[]} pinNumbers - Array of pin number strings
* @param {string[]} pinNames - Array of pin name strings (to avoid re-coloring names)
* @param {string[]} groupNames - Array of group label strings (to avoid re-coloring those)
* @returns {string} SVG with pin numbers colored distinctly
*/
export function colorPinNumbers(svg, pinNumbers, pinNames = [], groupNames = []) {
if (!pinNumbers || pinNumbers.length === 0) return svg;
// Build a set of labels we must NOT re-color (pin names + group labels)
const protect = new Set([...pinNames, ...groupNames]);
for (const num of pinNumbers) {
if (protect.has(num)) continue;
const esc = num.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Add pin-number-text class to the stroked-text group
svg = svg.replace(
new RegExp(`(<g class="stroked-text"><desc>${esc}</desc>)`, 'g'),
`<g class="stroked-text pin-number-text"><desc>${num}</desc>`
);
}
// Add CSS rule for pin numbers (muted gray, smaller visual weight)
svg = svg.replace(
'</style>',
` g.pin-number-text path {
stroke: ${THEME_COLORS.pinNumber} !important;
fill: ${THEME_COLORS.pinNumber} !important;
}
</style>`
);
return svg;
}