#!/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 für die Symbolleisten, wird von Thunderbird skaliert. PNG in 16, 32 und 64 Pixeln - das Manifest verlangt feste Größen, und für die Erweiterungsverwaltung wird 64 gebraucht. 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, 32, 64) 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 # Thunderbird braucht weniger Symbole als die LibreOffice-Fassung: Es gibt # nur zwei Schaltflächen (Ablegen im Nachrichtenfenster, Einfügen im # Verfassenfenster) und das Symbol der Erweiterung selbst. ICONS = { "paperless-logo": icon_logo, "paperless-store": icon_version, "paperless-insert": icon_open, "paperless-share": icon_share, } # ----------------------------------------------------------- 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()