Compare commits

...

2 Commits

Author SHA1 Message Date
mixeme 40b609a7d4 docs: add logo to README header
Theme-aware <picture> swaps the dark wordmark variant under
prefers-color-scheme: dark so it stays legible on GitHub's dark theme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 23:59:05 +03:00
mixeme 3a68c705c6 assets: add GoSentry wordmark logo (clock-dial variant)
Recommended variant from gosentry-logo.html: Space Grotesk SemiBold with
an amber "G", a clock-dial "o" (ring + hands), and petrol "Sentry". Ships
self-contained SVGs (glyphs outlined to paths, no font dependency) in
light/dark/mono plus transparent PNGs at 256-2048px, with a README and the
gen_logo.py/raster.py generators for reproducibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 23:51:38 +03:00
15 changed files with 295 additions and 0 deletions
+7
View File
@@ -1,3 +1,10 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/logo/gosentry-logo-dark.svg">
<img src="assets/logo/gosentry-logo.svg" alt="GoSentry" width="420">
</picture>
</p>
# GoSentry
GoSentry is a cross-platform desktop scheduler. It provides a native GUI for
+41
View File
@@ -0,0 +1,41 @@
# GoSentry logo
Recommended wordmark variant from [`../gosentry-logo.html`](../gosentry-logo.html):
**Space Grotesk SemiBold** with an amber **G**, a **clock-dial "o"** (ring + hands),
and a petrol **"Sentry"**. The dial sits exactly in the `o` slot, so the schedule /
"watch" theme lives inside the letters instead of a bolt-on icon.
## Colors
| token | hex | use |
|--------|-----------|-----------------------------|
| amber | `#F7A80C` | `G`, dial ring + hands |
| petrol | `#0A4A58` | `Sentry` (light background) |
| white | `#FFFFFF` | `Sentry` (dark background) |
## Files
Vector (self-contained — glyphs are outlined to paths, no font required):
- `gosentry-logo.svg` — transparent, petrol `Sentry` (for light backgrounds)
- `gosentry-logo-dark.svg` — transparent, white `Sentry` (for dark backgrounds)
- `gosentry-logo-mono.svg` — single-color petrol
Raster (transparent PNG, aspect ≈ 4326×1034 ≈ 4.18:1):
- `gosentry-logo-{256,512,1024,2048}.png` — petrol `Sentry`
- `gosentry-logo-dark-{256,512,1024,2048}.png` — white `Sentry`
## Regenerating
Requires `fonttools` and `matplotlib`, plus the Space Grotesk variable font
(SIL OFL) instanced to weight 600 as `SpaceGrotesk-600.ttf`:
```sh
python gen_logo.py # writes SVGs into ./out
python raster.py # writes PNGs into ./out
```
`gen_logo.py` (SVG) and `raster.py` (PNG) share the same layout + dial geometry,
so both outputs stay identical. Space Grotesk is licensed under the SIL Open Font
License; outlining its glyphs into a logo is permitted.
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Generate GoSentry wordmark logo assets (recommended variant:
Space Grotesk SemiBold + amber 'G' + clock-dial 'o' with hands + 'Sentry')."""
import os
from fontTools.ttLib import TTFont
from fontTools.pens.svgPathPen import SVGPathPen
from fontTools.pens.transformPen import TransformPen
from fontTools.pens.boundsPen import BoundsPen
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "out")
os.makedirs(OUT, exist_ok=True)
AMBER = "#F7A80C"
PETROL = "#0A4A58"
WHITE = "#FFFFFF"
LS = -30 # letter-spacing -0.03em at 1000 upm
f = TTFont(os.path.join(HERE, "SpaceGrotesk-600.ttf"))
cmap = f.getBestCmap()
hmtx = f["hmtx"]
gs = f.getGlyphSet()
def glyph_path(ch, dx):
"""Return SVG path 'd' for ch, shifted by dx in font units (y still up)."""
g = cmap[ord(ch)]
pen = SVGPathPen(gs)
tpen = TransformPen(pen, (1, 0, 0, 1, dx, 0))
gs[g].draw(tpen)
return pen.getCommands(), hmtx[g][0]
def o_metrics():
g = cmap[ord("o")]
bp = BoundsPen(gs); gs[g].draw(bp)
xmin,ymin,xmax,ymax = bp.bounds
return hmtx[g][0], xmin, ymin, xmax, ymax
# ---- layout the wordmark ---------------------------------------------------
x = 0.0
G_d, adv = glyph_path("G", x); x += adv + LS
# dial occupies the 'o' advance slot
o_adv, oxmin, oymin, oxmax, oymax = o_metrics()
o_left = x
cx = o_left + (oxmin + oxmax) / 2.0
cy = (oymin + oymax) / 2.0
R = ((oxmax - oxmin) + (oymax - oymin)) / 4.0 # avg radius, matches the 'o'
x += o_adv + LS
sentry_d = []
for ch in "Sentry":
d, adv = glyph_path(ch, x)
sentry_d.append(d)
x += adv + LS
x -= LS # no trailing letter-spacing
SENTRY_D = " ".join(sentry_d)
# ---- dial geometry (matched to the 'o', proportions from the HTML mock) ----
SW = R * 0.30 # ring stroke width
Rmid = R - SW / 2.0 # centreline radius of ring
HW = R * 0.21 # hand width
hour_len = R * 0.54 # 12 o'clock hand
min_len = R * 0.46 # ~4 o'clock hand
min_ang = 62 # degrees clockwise from 12
import math
mx = cx + min_len * math.sin(math.radians(min_ang))
my = cy + min_len * math.cos(math.radians(min_ang)) # font-up: +y is up
cap = R * 0.14
dial = f'''<circle cx="{cx:.1f}" cy="{cy:.1f}" r="{Rmid:.1f}" fill="none" stroke="{AMBER}" stroke-width="{SW:.1f}"/>
<path d="M{cx:.1f} {cy:.1f} L{cx:.1f} {cy+hour_len:.1f}" stroke="{AMBER}" stroke-width="{HW:.1f}" stroke-linecap="round"/>
<path d="M{cx:.1f} {cy:.1f} L{mx:.1f} {my:.1f}" stroke="{AMBER}" stroke-width="{HW:.1f}" stroke-linecap="round"/>
<circle cx="{cx:.1f}" cy="{cy:.1f}" r="{cap:.1f}" fill="{AMBER}"/>'''
# ---- overall bounds (font units, y up) -------------------------------------
bp = BoundsPen(gs)
xall = 0.0
gG = cmap[ord("G")]; gs[gG].draw(TransformPen(bp,(1,0,0,1,0,0)))
xall += hmtx[gG][0] + LS + o_adv + LS
for ch in "Sentry":
g = cmap[ord(ch)]
gs[g].draw(TransformPen(bp,(1,0,0,1,xall,0)))
xall += hmtx[g][0] + LS
bx0,by0,bx1,by1 = bp.bounds
# include the dial extents
bx0 = min(bx0, cx-R-SW/2); bx1 = max(bx1, cx+R+SW/2)
by0 = min(by0, cy-R-SW/2); by1 = max(by1, cy+R+SW/2)
PAD = 60
W = (bx1 - bx0) + 2*PAD
H = (by1 - by0) + 2*PAD
# transform: font(x,y up) -> screen: translate then flip y
tx = PAD - bx0
ty = PAD + by1
transform = f"matrix(1 0 0 -1 {tx:.2f} {ty:.2f})"
def svg(sentry_color, bg=None, name=""):
bgrect = f'<rect width="{W:.1f}" height="{H:.1f}" fill="{bg}"/>\n' if bg else ""
return f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W:.1f} {H:.1f}" role="img" aria-label="GoSentry">
{bgrect}<g transform="{transform}">
<path d="{G_d}" fill="{AMBER}"/>
<path d="{SENTRY_D}" fill="{sentry_color}"/>
{dial}
</g>
</svg>
'''
variants = {
"gosentry-logo.svg": svg(PETROL), # light bg, transparent
"gosentry-logo-dark.svg": svg(WHITE), # dark bg, transparent
"gosentry-logo-onlight.svg": svg(PETROL, bg="#FFFFFF"),
"gosentry-logo-ondark.svg": svg(WHITE, bg="#04262E"),
"gosentry-logo-mono.svg": svg(PETROL).replace(AMBER, PETROL), # single-colour petrol
}
for fn, data in variants.items():
with open(os.path.join(OUT, fn), "w", encoding="utf-8") as fh:
fh.write(data)
print("wrote", fn)
print("viewBox %.1f x %.1f" % (W, H))
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4326.0 1034.0" role="img" aria-label="GoSentry">
<g transform="matrix(1 0 0 -1 8.00 774.00)">
<path d="M312.0 -14Q238.0 -14 179.5 18.5Q121.0 51 86.5 113.5Q52.0 176 52.0 267V433Q52.0 569 128.0 641.5Q204.0 714 332.0 714Q460.0 714 529.0 645.5Q598.0 577 598.0 461V457H480.0V465Q480.0 505 464.0 537.0Q448.0 569 415.0 587.5Q382.0 606 332.0 606Q258.0 606 215.5 560.5Q173.0 515 173.0 435V265Q173.0 186 215.5 139.0Q258.0 92 334.0 92Q410.0 92 445.0 133.0Q480.0 174 480.0 238V250H303.0V352H598.0V0H488.0V69H471.0Q463.0 51 446.0 31.5Q429.0 12 397.5 -1.0Q366.0 -14 312.0 -14Z" fill="#F7A80C"/>
<path d="M1524.0 -14Q1444.0 -14 1382.5 14.5Q1321.0 43 1286.0 97.0Q1251.0 151 1251.0 229V255H1370.0V229Q1370.0 160 1412.0 126.0Q1454.0 92 1524.0 92Q1595.0 92 1631.0 121.0Q1667.0 150 1667.0 196Q1667.0 227 1650.0 246.5Q1633.0 266 1600.5 278.0Q1568.0 290 1522.0 301L1492.0 307Q1423.0 323 1372.5 347.5Q1322.0 372 1295.0 411.0Q1268.0 450 1268.0 513Q1268.0 576 1298.0 621.0Q1328.0 666 1383.0 690.0Q1438.0 714 1512.0 714Q1586.0 714 1644.0 689.0Q1702.0 664 1735.5 614.0Q1769.0 564 1769.0 489V456H1650.0V489Q1650.0 532 1633.0 558.0Q1616.0 584 1585.0 596.0Q1554.0 608 1512.0 608Q1450.0 608 1418.0 584.0Q1386.0 560 1386.0 516Q1386.0 488 1400.5 468.5Q1415.0 449 1443.5 436.5Q1472.0 424 1515.0 415L1545.0 408Q1617.0 392 1671.0 367.5Q1725.0 343 1755.5 303.0Q1786.0 263 1786.0 199Q1786.0 136 1753.5 88.0Q1721.0 40 1662.5 13.0Q1604.0 -14 1524.0 -14Z M2091.0 -14Q2017.0 -14 1960.5 17.5Q1904.0 49 1872.5 106.5Q1841.0 164 1841.0 241V253Q1841.0 331 1872.0 388.0Q1903.0 445 1959.0 476.5Q2015.0 508 2088.0 508Q2160.0 508 2214.0 476.5Q2268.0 445 2298.0 388.0Q2328.0 331 2328.0 255V214H1957.0Q1959.0 156 1998.0 121.0Q2037.0 86 2094.0 86Q2150.0 86 2177.0 110.5Q2204.0 135 2218.0 166L2313.0 117Q2299.0 90 2272.5 59.5Q2246.0 29 2202.0 7.5Q2158.0 -14 2091.0 -14ZM1958.0 301H2211.0Q2207.0 350 2173.5 379.0Q2140.0 408 2087.0 408Q2032.0 408 1999.0 379.0Q1966.0 350 1958.0 301Z M2416.0 0V494H2529.0V425H2546.0Q2559.0 453 2593.0 478.0Q2627.0 503 2696.0 503Q2753.0 503 2797.0 477.0Q2841.0 451 2865.5 405.0Q2890.0 359 2890.0 296V0H2775.0V287Q2775.0 347 2745.5 376.5Q2716.0 406 2662.0 406Q2601.0 406 2566.0 365.5Q2531.0 325 2531.0 250V0Z M3189.0 0Q3141.0 0 3112.5 28.5Q3084.0 57 3084.0 106V399H2955.0V494H3084.0V653H3199.0V494H3341.0V399H3199.0V125Q3199.0 95 3227.0 95H3326.0V0Z M3428.0 0V494H3541.0V437H3558.0Q3569.0 468 3595.0 482.0Q3621.0 496 3657.0 496H3717.0V394H3655.0Q3605.0 394 3574.0 367.5Q3543.0 341 3543.0 286V0Z M3844.0 -200V-100H4117.0Q4145.0 -100 4145.0 -70V68H4128.0Q4120.0 50 4102.0 32.5Q4084.0 15 4054.0 3.5Q4024.0 -8 3978.0 -8Q3921.0 -8 3877.0 17.5Q3833.0 43 3809.0 89.5Q3785.0 136 3785.0 198V494H3899.0V207Q3899.0 147 3928.5 118.0Q3958.0 89 4012.0 89Q4073.0 89 4108.5 129.0Q4144.0 169 4144.0 244V494H4258.0V-94Q4258.0 -143 4230.0 -171.5Q4202.0 -200 4154.0 -200Z" fill="#FFFFFF"/>
<circle cx="937.5" cy="247.0" r="220.4" fill="none" stroke="#F7A80C" stroke-width="77.8"/>
<path d="M937.5 247.0 L937.5 387.0" stroke="#F7A80C" stroke-width="54.4" stroke-linecap="round"/>
<path d="M937.5 247.0 L1042.8 303.0" stroke="#F7A80C" stroke-width="54.4" stroke-linecap="round"/>
<circle cx="937.5" cy="247.0" r="36.3" fill="#F7A80C"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4326.0 1034.0" role="img" aria-label="GoSentry">
<g transform="matrix(1 0 0 -1 8.00 774.00)">
<path d="M312.0 -14Q238.0 -14 179.5 18.5Q121.0 51 86.5 113.5Q52.0 176 52.0 267V433Q52.0 569 128.0 641.5Q204.0 714 332.0 714Q460.0 714 529.0 645.5Q598.0 577 598.0 461V457H480.0V465Q480.0 505 464.0 537.0Q448.0 569 415.0 587.5Q382.0 606 332.0 606Q258.0 606 215.5 560.5Q173.0 515 173.0 435V265Q173.0 186 215.5 139.0Q258.0 92 334.0 92Q410.0 92 445.0 133.0Q480.0 174 480.0 238V250H303.0V352H598.0V0H488.0V69H471.0Q463.0 51 446.0 31.5Q429.0 12 397.5 -1.0Q366.0 -14 312.0 -14Z" fill="#0A4A58"/>
<path d="M1524.0 -14Q1444.0 -14 1382.5 14.5Q1321.0 43 1286.0 97.0Q1251.0 151 1251.0 229V255H1370.0V229Q1370.0 160 1412.0 126.0Q1454.0 92 1524.0 92Q1595.0 92 1631.0 121.0Q1667.0 150 1667.0 196Q1667.0 227 1650.0 246.5Q1633.0 266 1600.5 278.0Q1568.0 290 1522.0 301L1492.0 307Q1423.0 323 1372.5 347.5Q1322.0 372 1295.0 411.0Q1268.0 450 1268.0 513Q1268.0 576 1298.0 621.0Q1328.0 666 1383.0 690.0Q1438.0 714 1512.0 714Q1586.0 714 1644.0 689.0Q1702.0 664 1735.5 614.0Q1769.0 564 1769.0 489V456H1650.0V489Q1650.0 532 1633.0 558.0Q1616.0 584 1585.0 596.0Q1554.0 608 1512.0 608Q1450.0 608 1418.0 584.0Q1386.0 560 1386.0 516Q1386.0 488 1400.5 468.5Q1415.0 449 1443.5 436.5Q1472.0 424 1515.0 415L1545.0 408Q1617.0 392 1671.0 367.5Q1725.0 343 1755.5 303.0Q1786.0 263 1786.0 199Q1786.0 136 1753.5 88.0Q1721.0 40 1662.5 13.0Q1604.0 -14 1524.0 -14Z M2091.0 -14Q2017.0 -14 1960.5 17.5Q1904.0 49 1872.5 106.5Q1841.0 164 1841.0 241V253Q1841.0 331 1872.0 388.0Q1903.0 445 1959.0 476.5Q2015.0 508 2088.0 508Q2160.0 508 2214.0 476.5Q2268.0 445 2298.0 388.0Q2328.0 331 2328.0 255V214H1957.0Q1959.0 156 1998.0 121.0Q2037.0 86 2094.0 86Q2150.0 86 2177.0 110.5Q2204.0 135 2218.0 166L2313.0 117Q2299.0 90 2272.5 59.5Q2246.0 29 2202.0 7.5Q2158.0 -14 2091.0 -14ZM1958.0 301H2211.0Q2207.0 350 2173.5 379.0Q2140.0 408 2087.0 408Q2032.0 408 1999.0 379.0Q1966.0 350 1958.0 301Z M2416.0 0V494H2529.0V425H2546.0Q2559.0 453 2593.0 478.0Q2627.0 503 2696.0 503Q2753.0 503 2797.0 477.0Q2841.0 451 2865.5 405.0Q2890.0 359 2890.0 296V0H2775.0V287Q2775.0 347 2745.5 376.5Q2716.0 406 2662.0 406Q2601.0 406 2566.0 365.5Q2531.0 325 2531.0 250V0Z M3189.0 0Q3141.0 0 3112.5 28.5Q3084.0 57 3084.0 106V399H2955.0V494H3084.0V653H3199.0V494H3341.0V399H3199.0V125Q3199.0 95 3227.0 95H3326.0V0Z M3428.0 0V494H3541.0V437H3558.0Q3569.0 468 3595.0 482.0Q3621.0 496 3657.0 496H3717.0V394H3655.0Q3605.0 394 3574.0 367.5Q3543.0 341 3543.0 286V0Z M3844.0 -200V-100H4117.0Q4145.0 -100 4145.0 -70V68H4128.0Q4120.0 50 4102.0 32.5Q4084.0 15 4054.0 3.5Q4024.0 -8 3978.0 -8Q3921.0 -8 3877.0 17.5Q3833.0 43 3809.0 89.5Q3785.0 136 3785.0 198V494H3899.0V207Q3899.0 147 3928.5 118.0Q3958.0 89 4012.0 89Q4073.0 89 4108.5 129.0Q4144.0 169 4144.0 244V494H4258.0V-94Q4258.0 -143 4230.0 -171.5Q4202.0 -200 4154.0 -200Z" fill="#0A4A58"/>
<circle cx="937.5" cy="247.0" r="220.4" fill="none" stroke="#0A4A58" stroke-width="77.8"/>
<path d="M937.5 247.0 L937.5 387.0" stroke="#0A4A58" stroke-width="54.4" stroke-linecap="round"/>
<path d="M937.5 247.0 L1042.8 303.0" stroke="#0A4A58" stroke-width="54.4" stroke-linecap="round"/>
<circle cx="937.5" cy="247.0" r="36.3" fill="#0A4A58"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4326.0 1034.0" role="img" aria-label="GoSentry">
<g transform="matrix(1 0 0 -1 8.00 774.00)">
<path d="M312.0 -14Q238.0 -14 179.5 18.5Q121.0 51 86.5 113.5Q52.0 176 52.0 267V433Q52.0 569 128.0 641.5Q204.0 714 332.0 714Q460.0 714 529.0 645.5Q598.0 577 598.0 461V457H480.0V465Q480.0 505 464.0 537.0Q448.0 569 415.0 587.5Q382.0 606 332.0 606Q258.0 606 215.5 560.5Q173.0 515 173.0 435V265Q173.0 186 215.5 139.0Q258.0 92 334.0 92Q410.0 92 445.0 133.0Q480.0 174 480.0 238V250H303.0V352H598.0V0H488.0V69H471.0Q463.0 51 446.0 31.5Q429.0 12 397.5 -1.0Q366.0 -14 312.0 -14Z" fill="#F7A80C"/>
<path d="M1524.0 -14Q1444.0 -14 1382.5 14.5Q1321.0 43 1286.0 97.0Q1251.0 151 1251.0 229V255H1370.0V229Q1370.0 160 1412.0 126.0Q1454.0 92 1524.0 92Q1595.0 92 1631.0 121.0Q1667.0 150 1667.0 196Q1667.0 227 1650.0 246.5Q1633.0 266 1600.5 278.0Q1568.0 290 1522.0 301L1492.0 307Q1423.0 323 1372.5 347.5Q1322.0 372 1295.0 411.0Q1268.0 450 1268.0 513Q1268.0 576 1298.0 621.0Q1328.0 666 1383.0 690.0Q1438.0 714 1512.0 714Q1586.0 714 1644.0 689.0Q1702.0 664 1735.5 614.0Q1769.0 564 1769.0 489V456H1650.0V489Q1650.0 532 1633.0 558.0Q1616.0 584 1585.0 596.0Q1554.0 608 1512.0 608Q1450.0 608 1418.0 584.0Q1386.0 560 1386.0 516Q1386.0 488 1400.5 468.5Q1415.0 449 1443.5 436.5Q1472.0 424 1515.0 415L1545.0 408Q1617.0 392 1671.0 367.5Q1725.0 343 1755.5 303.0Q1786.0 263 1786.0 199Q1786.0 136 1753.5 88.0Q1721.0 40 1662.5 13.0Q1604.0 -14 1524.0 -14Z M2091.0 -14Q2017.0 -14 1960.5 17.5Q1904.0 49 1872.5 106.5Q1841.0 164 1841.0 241V253Q1841.0 331 1872.0 388.0Q1903.0 445 1959.0 476.5Q2015.0 508 2088.0 508Q2160.0 508 2214.0 476.5Q2268.0 445 2298.0 388.0Q2328.0 331 2328.0 255V214H1957.0Q1959.0 156 1998.0 121.0Q2037.0 86 2094.0 86Q2150.0 86 2177.0 110.5Q2204.0 135 2218.0 166L2313.0 117Q2299.0 90 2272.5 59.5Q2246.0 29 2202.0 7.5Q2158.0 -14 2091.0 -14ZM1958.0 301H2211.0Q2207.0 350 2173.5 379.0Q2140.0 408 2087.0 408Q2032.0 408 1999.0 379.0Q1966.0 350 1958.0 301Z M2416.0 0V494H2529.0V425H2546.0Q2559.0 453 2593.0 478.0Q2627.0 503 2696.0 503Q2753.0 503 2797.0 477.0Q2841.0 451 2865.5 405.0Q2890.0 359 2890.0 296V0H2775.0V287Q2775.0 347 2745.5 376.5Q2716.0 406 2662.0 406Q2601.0 406 2566.0 365.5Q2531.0 325 2531.0 250V0Z M3189.0 0Q3141.0 0 3112.5 28.5Q3084.0 57 3084.0 106V399H2955.0V494H3084.0V653H3199.0V494H3341.0V399H3199.0V125Q3199.0 95 3227.0 95H3326.0V0Z M3428.0 0V494H3541.0V437H3558.0Q3569.0 468 3595.0 482.0Q3621.0 496 3657.0 496H3717.0V394H3655.0Q3605.0 394 3574.0 367.5Q3543.0 341 3543.0 286V0Z M3844.0 -200V-100H4117.0Q4145.0 -100 4145.0 -70V68H4128.0Q4120.0 50 4102.0 32.5Q4084.0 15 4054.0 3.5Q4024.0 -8 3978.0 -8Q3921.0 -8 3877.0 17.5Q3833.0 43 3809.0 89.5Q3785.0 136 3785.0 198V494H3899.0V207Q3899.0 147 3928.5 118.0Q3958.0 89 4012.0 89Q4073.0 89 4108.5 129.0Q4144.0 169 4144.0 244V494H4258.0V-94Q4258.0 -143 4230.0 -171.5Q4202.0 -200 4154.0 -200Z" fill="#0A4A58"/>
<circle cx="937.5" cy="247.0" r="220.4" fill="none" stroke="#F7A80C" stroke-width="77.8"/>
<path d="M937.5 247.0 L937.5 387.0" stroke="#F7A80C" stroke-width="54.4" stroke-linecap="round"/>
<path d="M937.5 247.0 L1042.8 303.0" stroke="#F7A80C" stroke-width="54.4" stroke-linecap="round"/>
<circle cx="937.5" cy="247.0" r="36.3" fill="#F7A80C"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Rasterize the GoSentry wordmark to PNG at several widths, reusing the
same font outlines + dial geometry as gen_logo.py (no SVG rasterizer needed)."""
import os, math
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch, Circle
from matplotlib.lines import Line2D
from fontTools.ttLib import TTFont
from fontTools.pens.basePen import BasePen
from fontTools.pens.boundsPen import BoundsPen
from fontTools.pens.transformPen import TransformPen
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "out"); os.makedirs(OUT, exist_ok=True)
AMBER, PETROL, WHITE = "#F7A80C", "#0A4A58", "#FFFFFF"
LS = -30
f = TTFont(os.path.join(HERE, "SpaceGrotesk-600.ttf"))
cmap, hmtx, gs = f.getBestCmap(), f["hmtx"], f.getGlyphSet()
class MplPen(BasePen):
def __init__(self, glyphSet):
super().__init__(glyphSet); self.v=[]; self.c=[]
def _moveTo(self,p): self.v.append(p); self.c.append(Path.MOVETO)
def _lineTo(self,p): self.v.append(p); self.c.append(Path.LINETO)
def _curveToOne(self,p1,p2,p3):
self.v += [p1,p2,p3]; self.c += [Path.CURVE4]*3
def _qCurveToOne(self,p1,p2):
self.v += [p1,p2]; self.c += [Path.CURVE3]*2
def _closePath(self):
self.v.append((0,0)); self.c.append(Path.CLOSEPOLY)
def glyph_mplpath(ch, dx):
pen = MplPen(gs)
gs[cmap[ord(ch)]].draw(TransformPen(pen,(1,0,0,1,dx,0)))
return Path(pen.v, pen.c), hmtx[cmap[ord(ch)]][0]
# layout
x=0.0
paths_amber=[]; paths_petrol=[]
p,adv = glyph_mplpath("G",x); paths_amber.append(p); x+=adv+LS
og=cmap[ord("o")]; bp=BoundsPen(gs); gs[og].draw(bp)
oxmin,oymin,oxmax,oymax=bp.bounds; o_adv=hmtx[og][0]
cx=x+(oxmin+oxmax)/2; cy=(oymin+oymax)/2
R=((oxmax-oxmin)+(oymax-oymin))/4
x+=o_adv+LS
for ch in "Sentry":
p,adv=glyph_mplpath(ch,x); paths_petrol.append(p); x+=adv+LS
x-=LS
SW=R*0.30; Rmid=R-SW/2; HW=R*0.21
hour_len=R*0.54; min_len=R*0.46; ang=math.radians(62)
mx=cx+min_len*math.sin(ang); my=cy+min_len*math.cos(ang); cap=R*0.14
# bounds
allb=BoundsPen(gs); gs[cmap[ord('G')]].draw(allb)
xx=hmtx[cmap[ord('G')]][0]+LS+o_adv+LS
for ch in "Sentry":
gs[cmap[ord(ch)]].draw(TransformPen(allb,(1,0,0,1,xx,0))); xx+=hmtx[cmap[ord(ch)]][0]+LS
bx0,by0,bx1,by1=allb.bounds
bx0=min(bx0,cx-R-SW/2); bx1=max(bx1,cx+R+SW/2)
by0=min(by0,cy-R-SW/2); by1=max(by1,cy+R+SW/2)
PAD=60
X0,X1=bx0-PAD,bx1+PAD; Y0,Y1=by0-PAD,by1+PAD
W=X1-X0; H=Y1-Y0
def render(path_png, width_px, sentry_color, bg=None, mono=False):
dpi=100
fw=width_px/dpi; fh=fw*H/W
fig=plt.figure(figsize=(fw,fh),dpi=dpi)
ax=fig.add_axes([0,0,1,1]); ax.set_xlim(X0,X1); ax.set_ylim(Y0,Y1)
ax.set_aspect('equal'); ax.axis('off')
if bg: fig.patch.set_facecolor(bg); ax.set_facecolor(bg)
else: fig.patch.set_alpha(0)
amberc = sentry_color if mono else AMBER
for p in paths_amber: ax.add_patch(PathPatch(p,facecolor=amberc,edgecolor='none',antialiased=True))
for p in paths_petrol: ax.add_patch(PathPatch(p,facecolor=sentry_color,edgecolor='none',antialiased=True))
pt_per_unit = fw/W*72
ax.add_patch(Circle((cx,cy),Rmid,fill=False,edgecolor=amberc,linewidth=SW*pt_per_unit))
for (ex,ey) in [(cx,cy+hour_len),(mx,my)]:
ax.add_line(Line2D([cx,ex],[cy,ey],color=amberc,linewidth=HW*pt_per_unit,
solid_capstyle='round'))
ax.add_patch(Circle((cx,cy),cap,facecolor=amberc,edgecolor='none'))
fig.savefig(path_png,dpi=dpi,transparent=(bg is None))
plt.close(fig)
print("wrote",os.path.basename(path_png))
for w in (256,512,1024,2048):
render(os.path.join(OUT,f"gosentry-logo-{w}.png"),w,PETROL)
render(os.path.join(OUT,f"gosentry-logo-dark-{w}.png"),w,WHITE)
render(os.path.join(OUT,"gosentry-logo-onlight-1024.png"),1024,PETROL,bg="#FFFFFF")
render(os.path.join(OUT,"gosentry-logo-ondark-1024.png"),1024,WHITE,bg="#04262E")
render(os.path.join(OUT,"gosentry-logo-mono-1024.png"),1024,PETROL,mono=True)
print(f"aspect {W:.0f}x{H:.0f}")