506 lines
16 KiB
Python
Executable File
506 lines
16 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate the launcher icon VectorDrawable files and a local SVG preview.
|
|
|
|
This keeps the icon tweakable without hand-editing dense Android vector XML.
|
|
Edit the constants below, run this script, then open build/launcher-icon-preview.svg.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from xml.sax.saxutils import escape
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
FOREGROUND = ROOT / "app/src/main/res/drawable/ic_launcher_foreground.xml"
|
|
MONOCHROME = ROOT / "app/src/main/res/drawable/ic_launcher_monochrome.xml"
|
|
BACKGROUND = ROOT / "app/src/main/res/drawable/ic_launcher_background.xml"
|
|
PREVIEW = ROOT / "build/launcher-icon-preview.svg"
|
|
|
|
VIEWPORT = 108
|
|
|
|
# Main geometry knobs. Coordinates are in Android/SVG viewport units.
|
|
# Open build/launcher-icon-preview.svg after regenerating to compare the
|
|
# square, rounded, and circular launcher masks.
|
|
# Statusbar panel. It fills the full top width.
|
|
STATUS_BOTTOM = 54
|
|
|
|
CUTOUT_CX = 54
|
|
CUTOUT_TOP = 0
|
|
CUTOUT_BOTTOM = 39
|
|
CUTOUT_STEM_HALF_WIDTH = 2.2
|
|
|
|
# Android adaptive icons use a 108x108 viewport, but only the central 72x72
|
|
# area is guaranteed to survive launcher masks. The preview uses this safe-zone
|
|
# circle, not the full viewport circle.
|
|
ADAPTIVE_SAFE_RADIUS = 36
|
|
|
|
# Row 1.
|
|
ENVELOPE_X = 39
|
|
ENVELOPE_Y = 22.5
|
|
ENVELOPE_W = 11
|
|
ENVELOPE_H = 8
|
|
ENVELOPE_STROKE = 1.3
|
|
|
|
BATTERY_ICON_X = 58
|
|
BATTERY_ICON_Y = 23
|
|
BATTERY_ICON_W = 13
|
|
BATTERY_ICON_H = 7.7
|
|
BATTERY_ICON_RADIUS = 1
|
|
BATTERY_ICON_PROTRUSION_W = 2
|
|
BATTERY_ICON_PROTRUSION_H = 3
|
|
BATTERY_ICON_LEVEL = 0.70
|
|
BATTERY_ICON_STROKE = 1.0
|
|
|
|
# Row 2.
|
|
DAY_TEXT_X = 31
|
|
DAY_TEXT_Y = 31.6
|
|
DAY_TEXT_H = 8
|
|
DAY_TEXT_STROKE = 1.1
|
|
DAY_TEXT_LETTER_SPACING = 2.0
|
|
DAY_TEXT_LOWERCASE_SCALE = 0.82
|
|
|
|
WIFI_X = 61
|
|
WIFI_Y = 36.5
|
|
WIFI_W = 10
|
|
WIFI_H = 5
|
|
WIFI_STROKE = 1.3
|
|
WIFI_DOT_R = 0.9
|
|
|
|
SIGNAL_X = 73
|
|
SIGNAL_BOTTOM = 42.5
|
|
SIGNAL_BAR_W = 1.8
|
|
SIGNAL_BAR_SPACING = 1.2
|
|
SIGNAL_BAR_HEIGHTS = (2.8, 5.6, 8.4)
|
|
|
|
# The divider between the statusbar illustration and the SBT wordmark.
|
|
BATTERY_BAR_LEFT = 0
|
|
BATTERY_BAR_RIGHT = 108
|
|
BATTERY_BAR_Y = STATUS_BOTTOM + 0.0
|
|
BATTERY_BAR_H = 6
|
|
BATTERY_BAR_LEVEL = 0.70
|
|
|
|
WORDMARK_X = 35
|
|
WORDMARK_Y = 62.8
|
|
WORDMARK_W = 50
|
|
WORDMARK_H = 20
|
|
WORDMARK_STROKE_OUTER = 5.0
|
|
WORDMARK_STROKE_INNER = 1.8
|
|
|
|
COLOR_STATUS = "#77746E"
|
|
COLOR_CUTOUT = "#050403"
|
|
COLOR_WHITE = "#FFFFFFFF"
|
|
COLOR_WARM_WHITE = "#FFF5DC"
|
|
COLOR_ORANGE = "#FF9F1A"
|
|
COLOR_GREEN = "#2FDB72"
|
|
COLOR_BAR_TRACK = "#2A2116"
|
|
COLOR_BG = "#0D0B08"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PathStyle:
|
|
fill: str = "#00000000"
|
|
stroke: str | None = None
|
|
stroke_width: float | None = None
|
|
line_cap: str | None = None
|
|
line_join: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class IconPath:
|
|
data: str
|
|
style: PathStyle
|
|
comment: str | None = None
|
|
|
|
|
|
def round_rect(x: float, y: float, w: float, h: float, r: float) -> str:
|
|
if r <= 0:
|
|
return rect(x, y, w, h)
|
|
r = min(r, w / 2, h / 2)
|
|
return (
|
|
f"M{x + r:g},{y:g} L{x + w - r:g},{y:g} "
|
|
f"A{r:g},{r:g} 0,0 1 {x + w:g},{y + r:g} "
|
|
f"L{x + w:g},{y + h - r:g} "
|
|
f"A{r:g},{r:g} 0,0 1 {x + w - r:g},{y + h:g} "
|
|
f"L{x + r:g},{y + h:g} "
|
|
f"A{r:g},{r:g} 0,0 1 {x:g},{y + h - r:g} "
|
|
f"L{x:g},{y + r:g} "
|
|
f"A{r:g},{r:g} 0,0 1 {x + r:g},{y:g} Z"
|
|
)
|
|
|
|
|
|
def rect(x: float, y: float, w: float, h: float) -> str:
|
|
return f"M{x:g},{y:g} L{x + w:g},{y:g} L{x + w:g},{y + h:g} L{x:g},{y + h:g} Z"
|
|
|
|
|
|
def circle(cx: float, cy: float, r: float) -> str:
|
|
return (
|
|
f"M{cx - r:g},{cy:g} "
|
|
f"A{r:g},{r:g} 0,1 0 {cx + r:g},{cy:g} "
|
|
f"A{r:g},{r:g} 0,1 0 {cx - r:g},{cy:g}"
|
|
)
|
|
|
|
|
|
def path(data: str, fill: str = "#00000000", *, stroke: str | None = None,
|
|
stroke_width: float | None = None, line_cap: str | None = None,
|
|
line_join: str | None = None, comment: str | None = None) -> IconPath:
|
|
return IconPath(data, PathStyle(fill, stroke, stroke_width, line_cap, line_join), comment)
|
|
|
|
|
|
def stroke_path(data: str, color: str, width: float, *, comment: str | None = None) -> IconPath:
|
|
return path(
|
|
data,
|
|
stroke=color,
|
|
stroke_width=width,
|
|
line_cap="round",
|
|
line_join="round",
|
|
comment=comment,
|
|
)
|
|
|
|
|
|
def seven_segment_digit(x: float, y: float, digit: str, scale: float) -> str:
|
|
segments = {
|
|
"0": "abcfed",
|
|
"1": "bc",
|
|
"2": "abged",
|
|
"3": "abgcd",
|
|
"4": "fgbc",
|
|
"5": "afgcd",
|
|
"6": "afgecd",
|
|
"7": "abc",
|
|
"8": "abcdefg",
|
|
"9": "abfgcd",
|
|
}[digit]
|
|
w = 5 * scale
|
|
h = 10 * scale
|
|
mid = y + h / 2
|
|
left = x
|
|
right = x + w
|
|
top = y
|
|
bottom = y + h
|
|
coords = {
|
|
"a": f"M{left:g},{top:g} L{right:g},{top:g}",
|
|
"b": f"M{right:g},{top:g} L{right:g},{mid:g}",
|
|
"c": f"M{right:g},{mid:g} L{right:g},{bottom:g}",
|
|
"d": f"M{left:g},{bottom:g} L{right:g},{bottom:g}",
|
|
"e": f"M{left:g},{mid:g} L{left:g},{bottom:g}",
|
|
"f": f"M{left:g},{top:g} L{left:g},{mid:g}",
|
|
"g": f"M{left:g},{mid:g} L{right:g},{mid:g}",
|
|
}
|
|
return " ".join(coords[s] for s in segments)
|
|
|
|
|
|
def colon(x: float, y: float, scale: float) -> str:
|
|
r = 1.1 * scale
|
|
return circle(x, y + 3 * scale, r) + " " + circle(x, y + 7 * scale, r)
|
|
|
|
|
|
def cutout_path() -> str:
|
|
cx = CUTOUT_CX
|
|
top = CUTOUT_TOP
|
|
bottom = CUTOUT_BOTTOM
|
|
stem_half = CUTOUT_STEM_HALF_WIDTH
|
|
return (
|
|
f"M{cx - stem_half:g},{top:g} "
|
|
f"L{cx + stem_half:g},{top:g} "
|
|
f"L{cx + stem_half:g},{bottom - stem_half:g} "
|
|
f"C{cx + stem_half:g},{bottom:g} {cx - stem_half:g},{bottom:g} {cx - stem_half:g},{bottom - stem_half:g} "
|
|
f"Z"
|
|
)
|
|
|
|
|
|
def envelope_path() -> str:
|
|
x = ENVELOPE_X
|
|
y = ENVELOPE_Y
|
|
w = ENVELOPE_W
|
|
h = ENVELOPE_H
|
|
return (
|
|
f"M{x:g},{y:g} L{x + w:g},{y:g} L{x + w:g},{y + h:g} L{x:g},{y + h:g} Z "
|
|
f"M{x:g},{y:g} L{x + w / 2:g},{y + h * 0.64:g} L{x + w:g},{y:g}"
|
|
)
|
|
|
|
|
|
def battery_icon_paths(fill: str, stroke: str) -> list[IconPath]:
|
|
x = BATTERY_ICON_X
|
|
y = BATTERY_ICON_Y
|
|
w = BATTERY_ICON_W
|
|
h = BATTERY_ICON_H
|
|
r = BATTERY_ICON_RADIUS
|
|
protrusion_w = BATTERY_ICON_PROTRUSION_W
|
|
protrusion_h = BATTERY_ICON_PROTRUSION_H
|
|
gauge_pad = max(1.0, BATTERY_ICON_STROKE + 0.4)
|
|
gauge_w = max(0.0, (w - gauge_pad * 2) * BATTERY_ICON_LEVEL)
|
|
protrusion_y = y + (h - protrusion_h) / 2
|
|
return [
|
|
stroke_path(
|
|
round_rect(x, y, w, h, r)
|
|
+ " "
|
|
+ rect(x + w, protrusion_y, protrusion_w, protrusion_h),
|
|
stroke,
|
|
BATTERY_ICON_STROKE,
|
|
comment="Battery outline",
|
|
),
|
|
path(rect(x + gauge_pad, y + gauge_pad, gauge_w, max(0.0, h - gauge_pad * 2)), fill,
|
|
comment="Battery fill"),
|
|
]
|
|
|
|
|
|
def day_text_path() -> str:
|
|
x = DAY_TEXT_X
|
|
y = DAY_TEXT_Y
|
|
h = DAY_TEXT_H
|
|
lower_h = h * DAY_TEXT_LOWERCASE_SCALE
|
|
lower_top = y + h - lower_h
|
|
spacing = DAY_TEXT_LETTER_SPACING
|
|
|
|
m_w = h * 0.62
|
|
o_w = lower_h * 0.58
|
|
n_w = lower_h * 0.58
|
|
o_x = x + m_w + spacing
|
|
n_x = o_x + o_w + spacing
|
|
return (
|
|
# M
|
|
f"M{x:g},{y + h:g} L{x:g},{y:g} L{x + m_w / 2:g},{y + h * 0.58:g} "
|
|
f"L{x + m_w:g},{y:g} L{x + m_w:g},{y + h:g} "
|
|
# o
|
|
f"M{o_x:g},{lower_top + lower_h / 2:g} "
|
|
f"A{o_w / 2:g},{lower_h / 2:g} 0,1 0 {o_x + o_w:g},{lower_top + lower_h / 2:g} "
|
|
f"A{o_w / 2:g},{lower_h / 2:g} 0,1 0 {o_x:g},{lower_top + lower_h / 2:g} "
|
|
# n
|
|
f"M{n_x:g},{y + h:g} L{n_x:g},{lower_top:g} "
|
|
f"C{n_x + n_w * 0.55:g},{lower_top:g} {n_x + n_w:g},{lower_top + lower_h * 0.2:g} "
|
|
f"{n_x + n_w:g},{y + h:g}"
|
|
)
|
|
|
|
|
|
def wifi_paths(color: str) -> list[IconPath]:
|
|
x = WIFI_X
|
|
y = WIFI_Y
|
|
w = WIFI_W
|
|
h = WIFI_H
|
|
return [
|
|
stroke_path(
|
|
f"M{x:g},{y:g} C{x + w * 0.28:g},{y - h * 0.75:g} "
|
|
f"{x + w * 0.72:g},{y - h * 0.75:g} {x + w:g},{y:g} "
|
|
f"M{x + w * 0.22:g},{y + h * 0.5:g} C{x + w * 0.4:g},{y + h * 0.1:g} "
|
|
f"{x + w * 0.6:g},{y + h * 0.1:g} {x + w * 0.78:g},{y + h * 0.5:g}",
|
|
color,
|
|
WIFI_STROKE,
|
|
comment="Wi-Fi",
|
|
),
|
|
path(circle(x + w / 2, y + h, WIFI_DOT_R), color, comment="Wi-Fi dot"),
|
|
]
|
|
|
|
|
|
def signal_path() -> str:
|
|
pieces = []
|
|
for index, height in enumerate(SIGNAL_BAR_HEIGHTS):
|
|
x = SIGNAL_X + index * (SIGNAL_BAR_W + SIGNAL_BAR_SPACING)
|
|
pieces.append(rect(x, SIGNAL_BOTTOM - height, SIGNAL_BAR_W, height))
|
|
return " ".join(pieces)
|
|
|
|
|
|
def clock_paths() -> list[IconPath]:
|
|
y = 42.5
|
|
x = 25
|
|
s = 1.0
|
|
small = 0.72
|
|
pieces = [
|
|
seven_segment_digit(x, y, "1", s),
|
|
seven_segment_digit(x + 8, y, "2", s),
|
|
seven_segment_digit(x + 20, y, "3", s),
|
|
seven_segment_digit(x + 28, y, "4", s),
|
|
]
|
|
small_x = x + 40
|
|
pieces.extend([
|
|
seven_segment_digit(small_x, y + 2.6, "5", small),
|
|
seven_segment_digit(small_x + 6, y + 2.6, "6", small),
|
|
])
|
|
return [
|
|
stroke_path(" ".join(pieces), COLOR_WHITE, 1.55, comment="Clock 12:34"),
|
|
path(colon(x + 16.5, y, s) + " " + colon(small_x - 3.5, y + 2.6, small), COLOR_WHITE,
|
|
comment="Clock colons"),
|
|
]
|
|
|
|
|
|
def statusbar_paths(monochrome: bool) -> list[IconPath]:
|
|
white = COLOR_WHITE
|
|
status_fill = COLOR_WHITE if monochrome else COLOR_STATUS
|
|
cutout_fill = COLOR_WHITE if monochrome else COLOR_CUTOUT
|
|
paths: list[IconPath] = []
|
|
paths.append(path(rect(0, 0, VIEWPORT, STATUS_BOTTOM), status_fill, comment="Statusbar panel"))
|
|
paths.append(path(cutout_path(), cutout_fill, comment="Camera cutout"))
|
|
|
|
# Row 1.
|
|
paths.append(stroke_path(envelope_path(), white, ENVELOPE_STROKE, comment="Envelope"))
|
|
paths.extend(battery_icon_paths(white, white))
|
|
|
|
# Row 2.
|
|
paths.append(stroke_path(day_text_path(), white, DAY_TEXT_STROKE, comment="Mon"))
|
|
paths.extend(wifi_paths(white))
|
|
paths.append(path(signal_path(), white, comment="Cellular bars"))
|
|
|
|
# Row 3.
|
|
paths.extend(clock_paths())
|
|
return paths
|
|
|
|
|
|
def battery_bar_paths(monochrome: bool) -> list[IconPath]:
|
|
green = COLOR_WHITE if monochrome else COLOR_GREEN
|
|
track = COLOR_WHITE if monochrome else COLOR_BAR_TRACK
|
|
track_w = max(0.0, BATTERY_BAR_RIGHT - BATTERY_BAR_LEFT)
|
|
fill_w = track_w * BATTERY_BAR_LEVEL
|
|
return [
|
|
path(round_rect(BATTERY_BAR_LEFT, BATTERY_BAR_Y, track_w, BATTERY_BAR_H, 3), track,
|
|
comment="Battery bar track"),
|
|
path(round_rect(BATTERY_BAR_LEFT + 1, BATTERY_BAR_Y + 1, max(0.0, fill_w - 2), BATTERY_BAR_H - 2, 2), green,
|
|
comment="Battery bar fill"),
|
|
]
|
|
|
|
|
|
def wordmark_paths(monochrome: bool) -> list[IconPath]:
|
|
x = WORDMARK_X
|
|
y = WORDMARK_Y
|
|
w = WORDMARK_W
|
|
h = WORDMARK_H
|
|
gap = w * 0.075
|
|
s_w = w * 0.22
|
|
b_w = w * 0.24
|
|
t_w = w * 0.26
|
|
s_x = x
|
|
b_x = s_x + s_w + gap
|
|
t_x = b_x + b_w + gap
|
|
mid = y + h * 0.50
|
|
cut = min(b_w * 0.28, h * 0.18)
|
|
data = (
|
|
f"M{s_x + s_w:g},{y:g} L{s_x:g},{y:g} L{s_x:g},{mid:g} L{s_x + s_w:g},{mid:g} "
|
|
f"L{s_x + s_w:g},{y + h:g} L{s_x:g},{y + h:g} "
|
|
f"M{b_x:g},{y:g} L{b_x:g},{y + h:g} "
|
|
f"M{b_x:g},{y:g} L{b_x + b_w - cut:g},{y:g} L{b_x + b_w:g},{y + cut:g} "
|
|
f"L{b_x + b_w:g},{mid - cut:g} L{b_x + b_w - cut:g},{mid:g} L{b_x:g},{mid:g} "
|
|
f"M{b_x:g},{mid:g} L{b_x + b_w - cut:g},{mid:g} L{b_x + b_w:g},{mid + cut:g} "
|
|
f"L{b_x + b_w:g},{y + h - cut:g} L{b_x + b_w - cut:g},{y + h:g} L{b_x:g},{y + h:g} "
|
|
f"M{t_x:g},{y:g} L{t_x + t_w:g},{y:g} M{t_x + t_w / 2:g},{y:g} L{t_x + t_w / 2:g},{y + h:g}"
|
|
)
|
|
if monochrome:
|
|
return [stroke_path(data, COLOR_WHITE, WORDMARK_STROKE_OUTER, comment="SBT wordmark")]
|
|
return [
|
|
stroke_path(data, COLOR_ORANGE, WORDMARK_STROKE_OUTER, comment="SBT wordmark outer"),
|
|
stroke_path(data, COLOR_WARM_WHITE, WORDMARK_STROKE_INNER, comment="SBT wordmark inner"),
|
|
]
|
|
|
|
|
|
def foreground_paths(monochrome: bool = False) -> list[IconPath]:
|
|
return statusbar_paths(monochrome) + battery_bar_paths(monochrome) + wordmark_paths(monochrome)
|
|
|
|
|
|
def background_paths() -> list[IconPath]:
|
|
return [
|
|
path(rect(0, 0, VIEWPORT, VIEWPORT), COLOR_BG),
|
|
]
|
|
|
|
|
|
def android_path_xml(icon_path: IconPath) -> str:
|
|
attrs = [
|
|
f'android:fillColor="{icon_path.style.fill}"',
|
|
f'android:pathData="{escape(icon_path.data)}"',
|
|
]
|
|
if icon_path.style.stroke:
|
|
attrs.append(f'android:strokeColor="{icon_path.style.stroke}"')
|
|
if icon_path.style.stroke_width is not None:
|
|
attrs.append(f'android:strokeWidth="{icon_path.style.stroke_width:g}"')
|
|
if icon_path.style.line_cap:
|
|
attrs.append(f'android:strokeLineCap="{icon_path.style.line_cap}"')
|
|
if icon_path.style.line_join:
|
|
attrs.append(f'android:strokeLineJoin="{icon_path.style.line_join}"')
|
|
return " <path\n " + "\n ".join(attrs) + " />"
|
|
|
|
|
|
def write_vector(destination: Path, paths: list[IconPath]) -> None:
|
|
body = "\n".join(
|
|
(f"\n <!-- {escape(p.comment)} -->\n" if p.comment else "\n") + android_path_xml(p)
|
|
for p in paths
|
|
)
|
|
destination.write_text(
|
|
'<?xml version="1.0" encoding="utf-8"?>\n'
|
|
'<vector xmlns:android="http://schemas.android.com/apk/res/android"\n'
|
|
' android:width="108dp"\n'
|
|
' android:height="108dp"\n'
|
|
' android:viewportWidth="108"\n'
|
|
' android:viewportHeight="108">'
|
|
f'{body}\n'
|
|
'</vector>\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def svg_path_xml(icon_path: IconPath) -> str:
|
|
style = [f'fill="{icon_path.style.fill}"']
|
|
if icon_path.style.stroke:
|
|
style.append(f'stroke="{icon_path.style.stroke}"')
|
|
if icon_path.style.stroke_width is not None:
|
|
style.append(f'stroke-width="{icon_path.style.stroke_width:g}"')
|
|
if icon_path.style.line_cap:
|
|
style.append(f'stroke-linecap="{icon_path.style.line_cap}"')
|
|
if icon_path.style.line_join:
|
|
style.append(f'stroke-linejoin="{icon_path.style.line_join}"')
|
|
return f'<path d="{escape(icon_path.data)}" {" ".join(style)} />'
|
|
|
|
|
|
def write_preview() -> None:
|
|
PREVIEW.parent.mkdir(parents=True, exist_ok=True)
|
|
paths = background_paths() + foreground_paths(False)
|
|
body = "\n ".join(svg_path_xml(p) for p in paths)
|
|
panel_bg = '<rect width="108" height="108" fill="#050403" />'
|
|
square = f'<g transform="translate(0,0)">\n {panel_bg}\n {body}\n</g>'
|
|
rounded = (
|
|
'<g clip-path="url(#rounded-mask)">\n'
|
|
' <g transform="translate(126,0)">\n'
|
|
f' {panel_bg}\n {body}\n'
|
|
' </g>\n'
|
|
'</g>\n'
|
|
'<rect x="126" y="0" width="108" height="108" rx="27" fill="none" '
|
|
'stroke="#66FFFFFF" stroke-width="0.8" />'
|
|
)
|
|
circle_preview = (
|
|
'<g clip-path="url(#circle-mask)">\n'
|
|
' <g transform="translate(252,0)">\n'
|
|
f' {panel_bg}\n {body}\n'
|
|
' </g>\n'
|
|
'</g>\n'
|
|
f'<circle cx="306" cy="54" r="{ADAPTIVE_SAFE_RADIUS}" fill="none" '
|
|
'stroke="#66FFFFFF" stroke-width="0.8" />'
|
|
)
|
|
PREVIEW.write_text(
|
|
'<svg xmlns="http://www.w3.org/2000/svg" width="1368" height="432" viewBox="0 0 360 114">\n'
|
|
' <defs>\n'
|
|
' <clipPath id="rounded-mask"><rect x="126" y="0" width="108" height="108" rx="27" /></clipPath>\n'
|
|
f' <clipPath id="circle-mask"><circle cx="306" cy="54" r="{ADAPTIVE_SAFE_RADIUS}" /></clipPath>\n'
|
|
' </defs>\n'
|
|
' <rect width="360" height="114" fill="#18130C" />\n'
|
|
f' {square}\n'
|
|
f' {rounded}\n'
|
|
f' {circle_preview}\n'
|
|
' <text x="54" y="113" fill="#DDD4C2" font-size="4" text-anchor="middle">square</text>\n'
|
|
' <text x="180" y="113" fill="#DDD4C2" font-size="4" text-anchor="middle">rounded</text>\n'
|
|
' <text x="306" y="113" fill="#DDD4C2" font-size="4" text-anchor="middle">circle</text>\n'
|
|
'</svg>\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
write_vector(BACKGROUND, background_paths())
|
|
write_vector(FOREGROUND, foreground_paths(False))
|
|
write_vector(MONOCHROME, foreground_paths(True))
|
|
write_preview()
|
|
print(f"Wrote {FOREGROUND.relative_to(ROOT)}")
|
|
print(f"Wrote {MONOCHROME.relative_to(ROOT)}")
|
|
print(f"Wrote {BACKGROUND.relative_to(ROOT)}")
|
|
print(f"Wrote {PREVIEW.relative_to(ROOT)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|