1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 05:37:52 +03:00
This commit is contained in:
Anton Afanasyeu
2026-05-27 22:53:49 +02:00
parent 43f802de5d
commit 2b7397edd2
16 changed files with 1843 additions and 262 deletions

View File

@@ -0,0 +1,50 @@
{
"source": "mercator-source.jpg",
"size": [
800,
519
],
"components": 8,
"layers": {
"vertical_snapshot": {
"translate": [
0.0,
238.0
],
"scale": [
1.2408477842003853,
1.2408477842003853
]
},
"inner": {
"translate": [
223.66088631984587,
238.0
],
"scale": [
1.2408477842003853,
1.2408477842003853
]
},
"outer": {
"scale": [
1.0,
1.0
]
},
"pivot": [
720.0,
560.0
]
},
"areas_top10": [
78439,
42423,
21849,
5520,
5428,
3434,
546,
540
]
}

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Extract red land polygons from Mercator reference image -> SVG paths."""
"""Extract red land polygons from Mercator reference image -> layered SVG paths."""
from __future__ import annotations
@@ -14,10 +14,17 @@ from PIL import Image
ROOT = Path(__file__).resolve().parent
SRC = ROOT / "mercator-source.jpg"
OUT_FLAT = ROOT / "hub-logo-continents-mercator-flat.svg"
OUT_GLOBE = ROOT / "hub-logo-continents-mercator-globe.svg"
OUT_GLOBE_SNAP_VERTICAL = ROOT / "hub-logo-continents-mercator-globe.snapshot-vertical.svg"
OUT_META = ROOT / "continents-meta.json"
# Source image is 800x519 Mercator-style plate.
W, H = 800, 519
# Globe framing in 1920x1080 logo scene.
GLOBE_CX = 720.0
GLOBE_CY = 560.0
GLOBE_R = 322.0
# Orthographic center (visible hemisphere).
CENTER_LAT = 18.0
CENTER_LON = 12.0
def red_mask(rgb: np.ndarray) -> np.ndarray:
@@ -26,7 +33,6 @@ def red_mask(rgb: np.ndarray) -> np.ndarray:
def dilate(mask: np.ndarray, radius: int = 3) -> np.ndarray:
"""Bridge thin white grid lines between red land cells."""
h, w = mask.shape
out = mask.copy()
ys, xs = np.where(mask)
@@ -61,18 +67,15 @@ def label_components(mask: np.ndarray) -> tuple[np.ndarray, int]:
def trace_boundary(labels: np.ndarray, label_id: int) -> list[tuple[int, int]]:
"""Moore-neighbor contour for one connected component."""
h, w = labels.shape
comp = labels == label_id
if not comp.any():
return []
# Start at topmost-leftmost pixel of component.
ys, xs = np.where(comp)
start = (int(xs[0]), int(ys[0]))
x, y = start
path = [start]
# Directions: E, SE, S, SW, W, NW, N, NE
dirs = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
dir_idx = 0
@@ -123,35 +126,30 @@ def path_d(points: list[tuple[float, float]]) -> str:
def mercator_y_to_lat(y: float, h: float) -> float:
# Map top->north, bottom->south; clamp to avoid infinities at poles.
t = 1.0 - (y / h)
t = min(max(t, 0.02), 0.98)
return math.degrees(math.atan(math.sinh(math.pi * (2 * t - 1))))
def lat_lon_to_globe(lat: float, lon: float, cx: float, cy: float, r: float) -> tuple[float, float]:
"""Simple orthographic-ish placement on visible globe disk."""
def lat_lon_to_globe(lat: float, lon: float) -> tuple[float, float]:
lat_r = math.radians(lat)
lon_r = math.radians(lon)
# Visible hemisphere centered around lon 20, lat 20 for this logo framing.
lon0 = math.radians(20.0)
lat0 = math.radians(15.0)
# Rotate lon relative to center.
lon0 = math.radians(CENTER_LON)
dlon = lon_r - lon0
x = cx + r * math.cos(lat_r) * math.sin(dlon)
y = cy - r * math.sin(lat_r)
x = GLOBE_CX + GLOBE_R * math.cos(lat_r) * math.sin(dlon)
y = GLOBE_CY - GLOBE_R * math.sin(lat_r)
return x, y
def map_to_globe(points: list[tuple[int, int]], w: int, h: int) -> list[tuple[float, float]]:
out: list[tuple[float, float]] = []
cx, cy, r = 720.0, 560.0, 318.0
def map_to_globe(points: list[tuple[int, int]], w: int, h: int) -> str:
mapped: list[tuple[float, float]] = []
for x, y in points:
lon = (x / w) * 360.0 - 180.0
lat = mercator_y_to_lat(float(y), float(h))
gx, gy = lat_lon_to_globe(lat, lon, cx, cy, r)
out.append((gx, gy))
return out
mapped.append(lat_lon_to_globe(lat, lon))
if len(mapped) < 3:
return ""
return path_d(mapped)
def main() -> None:
@@ -164,31 +162,34 @@ def main() -> None:
components: list[dict] = []
for label_id in range(1, n + 1):
area = int((labels == label_id).sum())
if area < 500: # drop tiny noise / watermark specks
if area < 500:
continue
boundary = trace_boundary(labels, label_id)
if len(boundary) < 20:
continue
boundary = simplify(boundary, min_dist=3.5)
flat = path_d([(float(x), float(y)) for x, y in boundary])
globe = map_to_globe(boundary, w, h)
if not flat:
continue
ys, xs = np.where(labels == label_id)
bbox = [int(xs.min()), int(ys.min()), int(xs.max()), int(ys.max())]
components.append(
{
"id": label_id,
"area": area,
"bbox": bbox,
"flat": path_d([(float(x), float(y)) for x, y in boundary]),
"globe": path_d(map_to_globe(boundary, w, h)),
"bbox": [int(xs.min()), int(ys.min()), int(xs.max()), int(ys.max())],
"flat": flat,
"globe": globe,
}
)
components.sort(key=lambda c: c["area"], reverse=True)
flat_paths = "\n ".join(
flat_paths = "\n ".join(
f'<path id="continent-{c["id"]}" data-area="{c["area"]}" d="{c["flat"]}"/>'
for c in components
)
globe_paths = "\n ".join(
globe_paths = "\n ".join(
f'<path id="continent-{c["id"]}" data-area="{c["area"]}" d="{c["globe"]}"/>'
for c in components
)
@@ -196,7 +197,7 @@ def main() -> None:
OUT_FLAT.write_text(
f"""<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" viewBox="0 0 {w} {h}" role="img" aria-label="Extracted continents (flat Mercator plate)">
<rect width="100%" height="100%" fill="#b8d4e8"/>
<g id="continents-extracted" fill="#c41e1a" fill-opacity="0.92" stroke="#8f1410" stroke-width="0.8">
<g id="continents-inner-layer" fill="#9ab3cc" fill-opacity="0.82" stroke="#dce8f6" stroke-opacity="0.78" stroke-width="1.1">
{flat_paths}
</g>
</svg>
@@ -204,17 +205,73 @@ def main() -> None:
encoding="utf-8",
)
globe_svg = ROOT.parent.parent / "examples" / "app_hub" / "assets" / "hub-logo-continents-mercator-globe.svg"
globe_svg.write_text(
f"""<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Extracted continents mapped to globe viewBox">
# Two-step alignment:
# 1) Vertical fit: Mercator north/south poles match globe top/bottom.
# 2) Horizontal fit: layer centers/pivots match at globe center.
globe_diameter = GLOBE_R * 2
inner_sy = globe_diameter / h
inner_sx = inner_sy
inner_ty = GLOBE_CY - GLOBE_R
inner_tx_vertical = 0.0
inner_tx = GLOBE_CX - (w * inner_sx) / 2
outer_sx = 1.0
outer_sy = 1.0
def globe_svg(
inner_tx_val: float,
inner_ty_val: float,
inner_sx_val: float,
inner_sy_val: float,
outer_sx_val: float,
outer_sy_val: float,
label: str,
) -> str:
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="{label}">
<defs>
<clipPath id="globeClip"><circle cx="720" cy="560" r="322"/></clipPath>
<clipPath id="globeClip"><circle cx="{GLOBE_CX:.0f}" cy="{GLOBE_CY:.0f}" r="{GLOBE_R:.0f}"/></clipPath>
<radialGradient id="continentRelief" cx="34%" cy="22%" r="78%">
<stop offset="0%" stop-color="#dbe7f6" stop-opacity="0.45"/>
<stop offset="58%" stop-color="#9ab3cc" stop-opacity="0.22"/>
<stop offset="100%" stop-color="#6a7f97" stop-opacity="0.18"/>
</radialGradient>
<filter id="continentDepth" x="-30%" y="-30%" width="160%" height="160%">
<feDropShadow dx="-4" dy="-6" stdDeviation="3" flood-color="#f4fbff" flood-opacity="0.2"/>
<feDropShadow dx="4" dy="6" stdDeviation="4" flood-color="#0a1220" flood-opacity="0.28"/>
</filter>
</defs>
<g id="continents-globe" clip-path="url(#globeClip)" fill="#7287a1" fill-opacity="0.88" stroke="#a8b8cc" stroke-opacity="0.45" stroke-width="1">
{globe_paths}
<g id="continents-outer-layer" clip-path="url(#globeClip)" transform="translate({GLOBE_CX:.0f} {GLOBE_CY:.0f}) scale({outer_sx_val:.3f} {outer_sy_val:.3f}) translate(-{GLOBE_CX:.0f} -{GLOBE_CY:.0f})">
<g id="continents-inner-layer" transform="translate({inner_tx_val:.2f} {inner_ty_val:.2f}) scale({inner_sx_val:.4f} {inner_sy_val:.4f})">
<g id="continents-flat" fill="url(#continentRelief)" fill-opacity="0.9" stroke="#dce8f6" stroke-opacity="0.78" stroke-width="1.2" filter="url(#continentDepth)">
{flat_paths}
</g>
</g>
</g>
</svg>
""",
"""
OUT_GLOBE_SNAP_VERTICAL.write_text(
globe_svg(
inner_tx_vertical,
inner_ty,
inner_sx,
inner_sy,
outer_sx,
outer_sy,
"Continents vertical-fit snapshot",
),
encoding="utf-8",
)
OUT_GLOBE.write_text(
globe_svg(
inner_tx,
inner_ty,
inner_sx,
inner_sy,
outer_sx,
outer_sy,
"Continents layered Mercator overlay",
),
encoding="utf-8",
)
@@ -224,6 +281,15 @@ def main() -> None:
"source": str(SRC.name),
"size": [w, h],
"components": len(components),
"layers": {
"vertical_snapshot": {
"translate": [inner_tx_vertical, inner_ty],
"scale": [inner_sx, inner_sy],
},
"inner": {"translate": [inner_tx, inner_ty], "scale": [inner_sx, inner_sy]},
"outer": {"scale": [outer_sx, outer_sy]},
"pivot": [GLOBE_CX, GLOBE_CY],
},
"areas_top10": [c["area"] for c in components[:10]],
},
indent=2,
@@ -233,7 +299,7 @@ def main() -> None:
print(f"components={len(components)}")
print(f"wrote {OUT_FLAT}")
print(f"wrote {globe_svg}")
print(f"wrote {OUT_GLOBE}")
if __name__ == "__main__":

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid slice" role="img" aria-label="Compass layer">
<g id="globe-compass" transform="translate(1050 820)">
<circle cx="0" cy="0" r="52" fill="#0a0e14" fill-opacity="0.42" stroke="#c7d3e1" stroke-opacity="0.52" stroke-width="2"/>
<circle cx="0" cy="0" r="40" fill="none" stroke="#dbe5f1" stroke-opacity="0.64" stroke-width="1.5"/>
<path d="M 0 -38 L 9 0 L 0 38 L -9 0 Z" fill="#e7ecf3" fill-opacity="0.9"/>
<path d="M -38 0 L 0 -9 L 38 0 L 0 9 Z" fill="#8ea1b9" fill-opacity="0.86"/>
<path d="M 0 -28 L 6 0 L 0 28 L -6 0 Z" fill="#223246" fill-opacity="0.9"/>
<circle cx="0" cy="0" r="4.2" fill="#e7ecf3" fill-opacity="0.92"/>
<text x="0" y="-56" text-anchor="middle" fill="#e7ecf3" fill-opacity="0.72" font-size="12" font-family="Segoe UI, sans-serif">N</text>
<text x="0" y="68" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">S</text>
<text x="-66" y="4" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">W</text>
<text x="66" y="4" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">E</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -31,18 +31,4 @@
<ellipse id="globe-south-pole" cx="720" cy="728" rx="260" ry="39" stroke="url(#gradSouthPole)" stroke-width="7.5"/>
</g>
<g id="globe-compass" transform="translate(1050 820)">
<circle cx="0" cy="0" r="52" fill="#0a0e14" fill-opacity="0.42" stroke="#c7d3e1" stroke-opacity="0.52" stroke-width="2"/>
<circle cx="0" cy="0" r="40" fill="none" stroke="#dbe5f1" stroke-opacity="0.64" stroke-width="1.5"/>
<path d="M 0 -38 L 9 0 L 0 38 L -9 0 Z" fill="#e7ecf3" fill-opacity="0.9"/>
<path d="M -38 0 L 0 -9 L 38 0 L 0 9 Z" fill="#8ea1b9" fill-opacity="0.86"/>
<path d="M 0 -28 L 6 0 L 0 28 L -6 0 Z" fill="#223246" fill-opacity="0.9"/>
<circle cx="0" cy="0" r="4.2" fill="#e7ecf3" fill-opacity="0.92"/>
<text x="0" y="-56" text-anchor="middle" fill="#e7ecf3" fill-opacity="0.72" font-size="12" font-family="Segoe UI, sans-serif">N</text>
<text x="0" y="68" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">S</text>
<text x="-66" y="4" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">W</text>
<text x="66" y="4" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">E</text>
</g>
<circle id="globe-border" cx="720" cy="560" r="330" fill="none" stroke="#e7ecf3" stroke-width="15"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -27,6 +27,14 @@
<stop offset="52%" stop-color="#d7e0ec" stop-opacity="0.78"/>
<stop offset="100%" stop-color="#4f6178" stop-opacity="0.92"/>
</linearGradient>
<linearGradient id="sunRim" x1="390" y1="560" x2="1050" y2="560" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f5f8ff" stop-opacity="0.98"/>
<stop offset="38%" stop-color="#dce7f5" stop-opacity="0.82"/>
<stop offset="100%" stop-color="#4a5c72" stop-opacity="0.7"/>
</linearGradient>
<filter id="sunRimGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3.2"/>
</filter>
<clipPath id="gridClip"><circle cx="720" cy="560" r="322"/></clipPath>
</defs>
@@ -45,18 +53,6 @@
<ellipse id="globe-south-pole" cx="720" cy="728" rx="260" ry="39" stroke="url(#gradSouthPole)" stroke-width="7.5"/>
</g>
<g id="globe-compass" transform="translate(1050 820)">
<circle cx="0" cy="0" r="52" fill="#0a0e14" fill-opacity="0.42" stroke="#c7d3e1" stroke-opacity="0.52" stroke-width="2"/>
<circle cx="0" cy="0" r="40" fill="none" stroke="#dbe5f1" stroke-opacity="0.64" stroke-width="1.5"/>
<path d="M 0 -38 L 9 0 L 0 38 L -9 0 Z" fill="#e7ecf3" fill-opacity="0.9"/>
<path d="M -38 0 L 0 -9 L 38 0 L 0 9 Z" fill="#8ea1b9" fill-opacity="0.86"/>
<path d="M 0 -28 L 6 0 L 0 28 L -6 0 Z" fill="#223246" fill-opacity="0.9"/>
<circle cx="0" cy="0" r="4.2" fill="#e7ecf3" fill-opacity="0.92"/>
<text x="0" y="-56" text-anchor="middle" fill="#e7ecf3" fill-opacity="0.72" font-size="12" font-family="Segoe UI, sans-serif">N</text>
<text x="0" y="68" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">S</text>
<text x="-66" y="4" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">W</text>
<text x="66" y="4" text-anchor="middle" fill="#a9bbce" fill-opacity="0.68" font-size="12" font-family="Segoe UI, sans-serif">E</text>
</g>
<circle id="globe-border" cx="720" cy="560" r="330" fill="none" stroke="#e7ecf3" stroke-width="15"/>
<circle id="globe-border-glow" cx="720" cy="560" r="330" fill="none" stroke="url(#sunRim)" stroke-opacity="0.75" stroke-width="20" filter="url(#sunRimGlow)"/>
<circle id="globe-border" cx="720" cy="560" r="330" fill="none" stroke="url(#sunRim)" stroke-width="14"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,22 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="340" height="46" viewBox="0 0 340 46" role="img" aria-label="Thunder schematic underline">
<defs>
<linearGradient id="thunderCore" x1="0%" y1="50%" x2="100%" y2="50%">
<stop offset="0%" stop-color="#7dd3fc" stop-opacity="0.15"/>
<stop offset="15%" stop-color="#bae6fd" stop-opacity="0.95"/>
<stop offset="48%" stop-color="#ffffff" stop-opacity="1"/>
<stop offset="82%" stop-color="#bae6fd" stop-opacity="0.95"/>
<stop offset="100%" stop-color="#7dd3fc" stop-opacity="0.15"/>
</linearGradient>
<linearGradient id="thunderEdge" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#9ddcff" stop-opacity="0"/>
<stop offset="24%" stop-color="#9ddcff" stop-opacity="0.8"/>
<stop offset="76%" stop-color="#9ddcff" stop-opacity="0.8"/>
<stop offset="100%" stop-color="#9ddcff" stop-opacity="0"/>
<linearGradient id="thunderFill" x1="0%" y1="50%" x2="100%" y2="50%">
<stop offset="0%" stop-color="#8ecfff" stop-opacity="0.22"/>
<stop offset="18%" stop-color="#c9ecff" stop-opacity="0.98"/>
<stop offset="50%" stop-color="#ffffff" stop-opacity="1"/>
<stop offset="82%" stop-color="#c9ecff" stop-opacity="0.98"/>
<stop offset="100%" stop-color="#8ecfff" stop-opacity="0.22"/>
</linearGradient>
</defs>
<path d="M 12 24 L 86 24 L 98 16 L 126 31 L 154 13 L 178 28 L 210 11 L 236 27 L 262 17 L 286 25 L 328 25"
fill="none" stroke="url(#thunderCore)" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 12 24 L 86 24 L 98 16 L 126 31 L 154 13 L 178 28 L 210 11 L 236 27 L 262 17 L 286 25 L 328 25"
fill="none" stroke="url(#thunderEdge)" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 10 9 L 196 9" fill="none" stroke="url(#thunderFill)" stroke-width="5.5" stroke-linecap="round"/>
<path d="M 196 9 L 248 31" fill="none" stroke="url(#thunderFill)" stroke-width="5.5" stroke-linecap="round"/>
<path d="M 248 31 L 330 31" fill="none" stroke="url(#thunderFill)" stroke-width="5.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 932 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 45.279 45.279" aria-hidden="true">
<symbol id="resizeArrow" viewBox="0 0 45.279 45.279">
<path d="M44.106,42.943H3.987L44.933,1.995c0.456-0.455,0.456-1.196,0-1.651c-0.452-0.455-1.195-0.455-1.651,0L2.336,41.292V1.168
C2.336,0.524,1.81,0,1.168,0C0.523,0,0,0.524,0,1.168v44.111h44.111c0.644,0,1.168-0.522,1.168-1.168
C45.279,43.469,44.755,42.943,44.106,42.943z"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<!-- Overtime 01 alphabet: 7-segment LCD glyphs for hub primary clock -->
<defs>
<symbol id="ot-0" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/></symbol>
<symbol id="ot-1" viewBox="0 0 20 32"><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/></symbol>
<symbol id="ot-2" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-3" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-4" viewBox="0 0 20 32"><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/></symbol>
<symbol id="ot-5" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-6" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-7" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/></symbol>
<symbol id="ot-8" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-9" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="3" y="15" width="14" height="3" rx="1"/><rect x="16" y="4" width="3" height="11" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/></symbol>
<symbol id="ot-colon" viewBox="0 0 12 32"><rect x="4" y="10" width="4" height="4" rx="1"/><rect x="4" y="20" width="4" height="4" rx="1"/></symbol>
<symbol id="ot-plus" viewBox="0 0 16 32"><rect x="6" y="8" width="4" height="16" rx="1"/><rect x="2" y="14" width="12" height="4" rx="1"/></symbol>
<symbol id="ot-minus" viewBox="0 0 16 32"><rect x="2" y="14" width="12" height="4" rx="1"/></symbol>
<symbol id="ot-space" viewBox="0 0 8 32"></symbol>
<symbol id="ot-G" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="1" y="4" width="3" height="11" rx="1"/><rect x="1" y="17" width="3" height="11" rx="1"/><rect x="3" y="28" width="14" height="3" rx="1"/><rect x="16" y="17" width="3" height="11" rx="1"/><rect x="10" y="15" width="9" height="3" rx="1"/></symbol>
<symbol id="ot-M" viewBox="0 0 24 32"><rect x="1" y="4" width="3" height="24" rx="1"/><rect x="20" y="4" width="3" height="24" rx="1"/><rect x="7.1" y="5" width="2.6" height="13.2" rx="1" transform="rotate(28 8.4 11.6)"/><rect x="14.3" y="5" width="2.6" height="13.2" rx="1" transform="rotate(-28 15.6 11.6)"/></symbol>
<symbol id="ot-T" viewBox="0 0 20 32"><rect x="3" y="1" width="14" height="3" rx="1"/><rect x="8" y="4" width="4" height="24" rx="1"/></symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB