#!/usr/bin/env python3 """Turns Lucide SVGs into Android vector drawables (res/drawable/lucide_*.xml). Lucide draws 24x24 strokes 2 wide with round caps and joins; the drawables keep that, in black, and Compose tints them. Usage: lucide.py name... """ import os import sys import xml.etree.ElementTree as ET def num(value, default=0.0): return float(value) if value is not None else default def fmt(value): text = f"{value:.3f}".rstrip("0").rstrip(".") return text or "0" def rect(el): x, y = num(el.get("x")), num(el.get("y")) w, h = num(el.get("width")), num(el.get("height")) rx = el.get("rx") ry = el.get("ry") rx = num(rx if rx is not None else ry) ry = num(ry if ry is not None else rx) rx, ry = min(rx, w / 2), min(ry, h / 2) if rx == 0: return f"M{fmt(x)},{fmt(y)}h{fmt(w)}v{fmt(h)}h{fmt(-w)}Z" return ( f"M{fmt(x + rx)},{fmt(y)}h{fmt(w - 2 * rx)}a{fmt(rx)},{fmt(ry)} 0 0 1 {fmt(rx)},{fmt(ry)}" f"v{fmt(h - 2 * ry)}a{fmt(rx)},{fmt(ry)} 0 0 1 {fmt(-rx)},{fmt(ry)}" f"h{fmt(-(w - 2 * rx))}a{fmt(rx)},{fmt(ry)} 0 0 1 {fmt(-rx)},{fmt(-ry)}" f"v{fmt(-(h - 2 * ry))}a{fmt(rx)},{fmt(ry)} 0 0 1 {fmt(rx)},{fmt(-ry)}Z" ) def ellipse(cx, cy, rx, ry): return ( f"M{fmt(cx - rx)},{fmt(cy)}a{fmt(rx)},{fmt(ry)} 0 1 0 {fmt(2 * rx)},0" f"a{fmt(rx)},{fmt(ry)} 0 1 0 {fmt(-2 * rx)},0Z" ) def points(el, close): values = el.get("points").replace(",", " ").split() pairs = [(values[i], values[i + 1]) for i in range(0, len(values) - 1, 2)] data = "M" + " L".join(f"{x},{y}" for x, y in pairs) return data + ("Z" if close else "") def path_data(el): tag = el.tag.split("}")[-1] if tag == "path": return el.get("d") if tag == "rect": return rect(el) if tag == "circle": r = num(el.get("r")) return ellipse(num(el.get("cx")), num(el.get("cy")), r, r) if tag == "ellipse": return ellipse(num(el.get("cx")), num(el.get("cy")), num(el.get("rx")), num(el.get("ry"))) if tag == "line": return f"M{el.get('x1')},{el.get('y1')} L{el.get('x2')},{el.get('y2')}" if tag == "polyline": return points(el, False) if tag == "polygon": return points(el, True) return None def convert(svg_path): root = ET.parse(svg_path).getroot() paths = [] for el in root.iter(): data = path_data(el) if data: fill = el.get("fill") paths.append((data, fill not in (None, "none"))) out = [ '', '', ] for data, filled in paths: fill = ' android:fillColor="#FF000000"' if filled else "" out.append( f' ' ) out.append("") return "\n".join(out) + "\n" def main(): source, target, names = sys.argv[1], sys.argv[2], sys.argv[3:] os.makedirs(target, exist_ok=True) for name in names: svg = os.path.join(source, f"{name}.svg") if not os.path.exists(svg): print(f"missing: {name}", file=sys.stderr) continue out = os.path.join(target, "lucide_" + name.replace("-", "_") + ".xml") with open(out, "w") as handle: handle.write(convert(svg)) if __name__ == "__main__": main()