284 lines
9.4 KiB
Python
284 lines
9.4 KiB
Python
#!/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 = ['<?xml version="1.0" encoding="UTF-8"?>',
|
|
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" '
|
|
'viewBox="0 0 %g %g">' % (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(' <polygon points="%s"%s/>'
|
|
% (pts, attrs(fill, stroke, width)))
|
|
elif kind == "circle":
|
|
cx, cy, r = data
|
|
out.append(' <circle cx="%g" cy="%g" r="%g"%s/>'
|
|
% (cx, cy, r, attrs(fill, stroke, width)))
|
|
elif kind == "line":
|
|
(x1, y1), (x2, y2) = data
|
|
out.append(' <line x1="%g" y1="%g" x2="%g" y2="%g"%s/>'
|
|
% (x1, y1, x2, y2, attrs(None, stroke, width)))
|
|
elif kind == "rrect":
|
|
x0, y0, x1, y1, r = data
|
|
out.append(' <rect x="%g" y="%g" width="%g" height="%g" '
|
|
'rx="%g"%s/>'
|
|
% (x0, y0, x1 - x0, y1 - y0, r,
|
|
attrs(fill, stroke, width)))
|
|
out.append("</svg>")
|
|
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()
|