#!/usr/bin/env python3 """ make_icons.py - Erzeugt die Symbole der Erweiterung als SVG und PNG. Die Geometrie ist EINMAL beschrieben und wird von zwei Ausgaben gelesen. So können SVG und PNG nicht auseinanderlaufen. SVG wird von LibreOffice skaliert und passt sich damit der eingestellten Symbolgröße an (Extras → Optionen → Ansicht → Symbolgröße). PNG in 16, 26 und 32 Pixeln als Rückfallebene, falls die SVG-Unterstützung der eingesetzten Version nicht greift. Alle Symbole teilen dieselbe Grundform - ein Blatt mit umgeknickter Ecke in Paperless-Grün - damit die Leiste als zusammengehörige Gruppe erkennbar bleibt. Unterschieden wird über eine farbige Scheibe unten rechts. Aufruf: python3 make_icons.py (benötigt Pillow für die PNG-Ausgabe) """ import math import os OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icons") # Zeichenfläche in Einheiten. Die Formen reichen bewusst bis dicht an den # Rand, damit bei kleiner Darstellung möglichst viel Fläche trägt. BOX = 24.0 PNG_SIZES = (16, 26, 32) SCALE = 12 # Überabtastung für die PNG-Ausgabe GREEN = "#175420" GREEN_LIGHT = "#3a8a44" PAPER = "#fcfcfa" WHITE = "#ffffff" BLUE = "#1e60a8" # herunterladen ORANGE = "#c86a14" # hinauf VIOLET = "#703e98" # klassifizieren TEAL = "#117a7a" # teilen GREY = "#5c6064" # einstellungen # ----------------------------------------------------------- Grundformen def poly(points, fill=None, stroke=None, width=0.0): return ("poly", points, fill, stroke, width) def circle(cx, cy, r, fill=None, stroke=None, width=0.0): return ("circle", (cx, cy, r), fill, stroke, width) def line(p1, p2, stroke, width): return ("line", (p1, p2), None, stroke, width) def rrect(x0, y0, x1, y1, r, fill=None, stroke=None, width=0.0): return ("rrect", (x0, y0, x1, y1, r), fill, stroke, width) def sheet(colour=GREEN): """Blatt mit umgeknickter Ecke, füllt die Fläche links oben.""" x0, y0, x1, y1 = 0.6, 0.5, 18.4, 23.4 fold = 5.4 out = [ poly([(x0, y0), (x1 - fold, y0), (x1, y0 + fold), (x1, y1), (x0, y1)], fill=PAPER, stroke=colour, width=1.8), poly([(x1 - fold, y0), (x1, y0 + fold), (x1 - fold, y0 + fold)], fill=colour), ] for i, w in enumerate((10.4, 12.6, 8.4)): yy = y0 + 7.4 + i * 3.8 out.append(line((x0 + 2.2, yy), (x0 + 2.2 + w, yy), colour, 1.4)) return out def badge(colour): """Scheibe unten rechts, mit hellem Ring zur Abgrenzung vom Blatt.""" cx, cy, r = 16.9, 17.1, 6.7 return cx, cy, r, [ circle(cx, cy, r + 1.1, fill=WHITE), circle(cx, cy, r, fill=colour), ] def arrow(cx, cy, up=True, colour=PAPER): w, h = 2.9, 3.5 if up: return [poly([(cx, cy - h), (cx - w, cy - 0.3), (cx + w, cy - 0.3)], fill=colour), line((cx, cy - 0.6), (cx, cy + h * 0.85), colour, 2.0)] return [poly([(cx, cy + h), (cx - w, cy + 0.3), (cx + w, cy + 0.3)], fill=colour), line((cx, cy - h * 0.85), (cx, cy + 0.6), colour, 2.0)] # --------------------------------------------------------------- Symbole def icon_logo(): """Markensymbol für den Anfang der Leiste: gefüllte Fläche, weißes Blatt.""" out = [rrect(0.4, 0.4, 23.6, 23.6, 4.0, fill=GREEN)] x0, y0, x1, y1 = 5.0, 3.9, 19.0, 20.1 fold = 4.4 out += [ poly([(x0, y0), (x1 - fold, y0), (x1, y0 + fold), (x1, y1), (x0, y1)], fill=PAPER), poly([(x1 - fold, y0), (x1, y0 + fold), (x1 - fold, y0 + fold)], fill=GREEN_LIGHT), ] for i, w in enumerate((7.4, 9.2, 6.0)): yy = y0 + 6.0 + i * 3.4 out.append(line((x0 + 2.0, yy), (x0 + 2.0 + w, yy), GREEN, 1.5)) return out def icon_open(): out = sheet() cx, cy, _r, b = badge(BLUE) return out + b + arrow(cx, cy, up=False) def icon_version(): out = sheet() cx, cy, _r, b = badge(ORANGE) out += b + arrow(cx - 1.2, cy, up=True) px, py, a = cx + 3.4, cy - 2.6, 1.7 out += [line((px - a, py), (px + a, py), PAPER, 1.4), line((px, py - a), (px, py + a), PAPER, 1.4)] return out def icon_save_as(): # Zweites Blatt dahinter: es kann ein eigenständiges Dokument entstehen. out = [poly([(4.4, 0.5), (18.4, 0.5), (18.4, 4.6), (4.4, 4.6)], fill=PAPER, stroke=GREEN_LIGHT, width=1.5)] out += sheet() cx, cy, _r, b = badge(ORANGE) out += b + arrow(cx - 1.2, cy, up=True) sx, sy, a = cx + 3.4, cy - 2.6, 1.9 for dx, dy in ((a, 0), (0, a), (a * .7, a * .7), (-a * .7, a * .7)): out.append(line((sx - dx, sy - dy), (sx + dx, sy + dy), PAPER, 1.15)) return out def icon_classify(): out = sheet() cx, cy, _r, b = badge(VIOLET) out += b + [ poly([(cx - 3.6, cy - 0.5), (cx + 0.4, cy - 4.0), (cx + 3.7, cy - 0.6), (cx - 0.2, cy + 3.6)], fill=PAPER), circle(cx + 1.4, cy - 1.5, 0.9, fill=VIOLET), ] return out def icon_share(): out = sheet() cx, cy, _r, b = badge(TEAL) a, bb, c = (cx + 2.6, cy - 3.2), (cx + 2.6, cy + 3.2), (cx - 3.2, cy) out += b + [line(c, a, PAPER, 1.3), line(c, bb, PAPER, 1.3)] for p in (a, bb, c): out.append(circle(p[0], p[1], 1.65, fill=PAPER)) return out def icon_settings(): out = sheet() cx, cy, _r, b = badge(GREY) out += b outer, inner, hole = 5.0, 3.4, 1.6 for i in range(8): ang = math.pi * i / 4 dx, dy = math.cos(ang), math.sin(ang) out.append(line((cx + dx * inner * .7, cy + dy * inner * .7), (cx + dx * outer, cy + dy * outer), PAPER, 1.7)) out += [circle(cx, cy, inner, stroke=PAPER, width=1.5), circle(cx, cy, hole, fill=GREY)] return out ICONS = { "paperless-logo": icon_logo, "paperless-open": icon_open, "paperless-version": icon_version, "paperless-saveas": icon_save_as, "paperless-classify": icon_classify, "paperless-share": icon_share, "paperless-settings": icon_settings, } # ----------------------------------------------------------- Ausgabe SVG def to_svg(shapes): out = ['', '' % (BOX, BOX)] def attrs(fill, stroke, width): a = ' fill="%s"' % (fill or "none") if stroke: a += (' stroke="%s" stroke-width="%g" stroke-linecap="round"' ' stroke-linejoin="round"' % (stroke, width)) return a for kind, data, fill, stroke, width in shapes: if kind == "poly": pts = " ".join("%g,%g" % p for p in data) out.append(' ' % (pts, attrs(fill, stroke, width))) elif kind == "circle": cx, cy, r = data out.append(' ' % (cx, cy, r, attrs(fill, stroke, width))) elif kind == "line": (x1, y1), (x2, y2) = data out.append(' ' % (x1, y1, x2, y2, attrs(None, stroke, width))) elif kind == "rrect": x0, y0, x1, y1, r = data out.append(' ' % (x0, y0, x1 - x0, y1 - y0, r, attrs(fill, stroke, width))) out.append("") return "\n".join(out) + "\n" # ----------------------------------------------------------- Ausgabe PNG def to_png(shapes, size): from PIL import Image, ImageDraw def rgb(c): c = c.lstrip("#") return tuple(int(c[i:i + 2], 16) for i in (0, 2, 4)) + (255,) s = SCALE img = Image.new("RGBA", (int(BOX * s), int(BOX * s)), (0, 0, 0, 0)) d = ImageDraw.Draw(img) for kind, data, fill, stroke, width in shapes: f = rgb(fill) if fill else None st = rgb(stroke) if stroke else None w = max(1, int(width * s)) if kind == "poly": pts = [(x * s, y * s) for x, y in data] d.polygon(pts, fill=f, outline=st, width=w if st else 0) elif kind == "circle": cx, cy, r = data box = [(cx - r) * s, (cy - r) * s, (cx + r) * s, (cy + r) * s] if f: d.ellipse(box, fill=f) if st: d.ellipse(box, outline=st, width=w) elif kind == "line": (x1, y1), (x2, y2) = data d.line([(x1 * s, y1 * s), (x2 * s, y2 * s)], fill=st, width=w) elif kind == "rrect": x0, y0, x1, y1, r = data d.rounded_rectangle([x0 * s, y0 * s, x1 * s, y1 * s], radius=r * s, fill=f, outline=st, width=w if st else 0) return img.resize((size, size), Image.LANCZOS) def main(): os.makedirs(OUT, exist_ok=True) for name, fn in ICONS.items(): shapes = fn() with open(os.path.join(OUT, name + ".svg"), "w", encoding="utf-8") as fh: fh.write(to_svg(shapes)) for size in PNG_SIZES: to_png(shapes, size).save( os.path.join(OUT, "%s_%d.png" % (name, size)), "PNG", optimize=True) print("erzeugt: %s.svg und PNG in %s" % (name, ", ".join("%d" % z for z in PNG_SIZES))) if __name__ == "__main__": main()