feat(ui): add the OpenTimestamps logo as a custom glyph and use it in the OTS pill

Bake the official OpenTimestamps stamp logo (traced from
opentimestamps/logo vector.svg; monochrome outline, tinted at render
time like every other glyph) into the Material Symbols subset font at
U+F8F0. New tools/material-symbols-subset/add_custom_glyphs.py converts
the traced SVGs in custom/ into TrueType glyphs and is invoked by
subset.sh after pyftsubset, so font regenerations keep them.

With the logo identifying the pill, drop the verbose "OTS:" prefix:
the pill now reads icon + "2y" (or icon + "Pending", new
R.string.pending).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AgEpNtwnetPhETXQSdq4b5
This commit is contained in:
Claude
2026-07-14 12:59:30 +00:00
parent efae1dec22
commit 41ef1d89b3
8 changed files with 261 additions and 4 deletions
@@ -61,8 +61,8 @@ fun DisplayOts(
}
HeaderPill(
symbol = MaterialSymbols.CheckCircle,
text = stringRes(R.string.existed_since, timeStr),
symbol = MaterialSymbols.OpenTimestamps,
text = timeStr,
contentDescription = stringRes(R.string.ots_info_title),
onClick = {
accountViewModel.toastManager.toast(
@@ -75,8 +75,8 @@ fun DisplayOts(
},
whenPending = {
HeaderPill(
symbol = MaterialSymbols.Schedule,
text = stringRes(id = R.string.timestamp_pending_short),
symbol = MaterialSymbols.OpenTimestamps,
text = stringRes(id = R.string.pending),
contentDescription = stringRes(R.string.ots_info_title),
)
},
+1
View File
@@ -45,6 +45,7 @@
<string name="timestamp_it">Timestamp it</string>
<string name="timestamp_pending">Timestamp: Pending Confirmations</string>
<string name="timestamp_pending_short">OTS: Pending</string>
<string name="pending">Pending</string>
<string name="request_deletion">Request Deletion</string>
<string name="block_report">Block / Report</string>
<string name="block_hide_user"><![CDATA[Block & Hide User]]></string>
@@ -167,6 +167,12 @@ object MaterialSymbols {
val Numbers = MaterialSymbol("\uEAC7")
val OpenInBrowser = MaterialSymbol("\uE89D")
val OpenInFull = MaterialSymbol("\uF1CE")
// Custom glyph (not part of Google's Material Symbols): the OpenTimestamps
// stamp logo, baked into the subset font by
// tools/material-symbols-subset/add_custom_glyphs.py from custom/opentimestamps.svg.
val OpenTimestamps = MaterialSymbol("\uF8F0")
val Paid = MaterialSymbol("\uF041")
val PanTool = MaterialSymbol("\uE925")
val Pause = MaterialSymbol("\uE034")
+12
View File
@@ -54,3 +54,15 @@ Commit the regenerated `.ttf` alongside the `MaterialSymbols.kt` change.
glyph and OpenType feature not reachable from those codepoints, and rewrites
`name`, `cmap`, and `GSUB` tables accordingly. The Compose resource pipeline
treats the result as a normal TTF — no code changes needed.
## Custom glyphs
`custom/` holds traced SVG outlines for icons the upstream font will never
carry (third-party logos — currently the OpenTimestamps stamp).
`add_custom_glyphs.py` bakes them into the subset TTF at end-of-PUA
codepoints (U+F8F0 and up); `subset.sh` runs it automatically after
`pyftsubset`, so regenerating the subset keeps them. To add one: trace the
logo to a single-color SVG (potrace output works as-is), drop it in
`custom/`, register it in the `CUSTOM` map in `add_custom_glyphs.py`, add the
matching `MaterialSymbol("\uF8Fx")` to `MaterialSymbols.kt`, and rerun
`subset.sh` (or `add_custom_glyphs.py <ttf>` to patch the existing font).
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Append custom (non-Material) glyphs to the subset Material Symbols font.
Some icons Amethyst needs are third-party logos that Google's Material
Symbols font will never carry (e.g. the OpenTimestamps stamp). Their traced
SVG outlines live in `custom/` and this script bakes them into the subset
TTF at private-use codepoints, so `MaterialSymbols.kt` can reference them
exactly like any Material glyph.
subset.sh runs this automatically after pyftsubset; it can also be run
directly against an existing font:
python3 add_custom_glyphs.py <path-to-ttf>
The SVGs are potrace output: the raw path coordinates are y-up already (the
file's group transform re-flips them for SVG display), so they map into font
space with a plain uniform scale + translate.
"""
import os
import re
import sys
from fontTools.pens.boundsPen import BoundsPen
from fontTools.pens.cu2quPen import Cu2QuPen
from fontTools.pens.transformPen import TransformPen
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.svgLib.path import parse_path
from fontTools.ttLib import TTFont
HERE = os.path.dirname(os.path.abspath(__file__))
# codepoint -> (glyph name, traced SVG). Keep codepoints at the very end of
# the BMP private-use area (U+F8xx tail) to stay clear of the range Material
# Symbols actually allocates.
CUSTOM = {
0xF8F0: ("opentimestamps", os.path.join(HERE, "custom", "opentimestamps.svg")),
}
# Fit content into the same square Material Symbols draw in (its check_circle
# glyph spans 80..880 in a 960 upm font). Scaled to the font's real upm.
BOX_MIN_FRAC = 80 / 960
BOX_MAX_FRAC = 880 / 960
def svg_path_data(svg_file):
with open(svg_file, "r", encoding="utf-8") as f:
svg = f.read()
return re.findall(r'<path[^>]*\bd="([^"]+)"', svg)
def draw_svg(paths, pen):
for d in paths:
parse_path(d, pen)
def build_glyph(paths, upm):
# First pass: raw outline bounds.
bounds = BoundsPen(None)
draw_svg(paths, bounds)
x_min, y_min, x_max, y_max = bounds.bounds
# Uniform scale into the Material content box, centered on both axes.
box_min = BOX_MIN_FRAC * upm
box_max = BOX_MAX_FRAC * upm
box = box_max - box_min
scale = box / max(x_max - x_min, y_max - y_min)
tx = box_min + (box - (x_max - x_min) * scale) / 2 - x_min * scale
ty = box_min + (box - (y_max - y_min) * scale) / 2 - y_min * scale
tt_pen = TTGlyphPen(None)
quad_pen = Cu2QuPen(tt_pen, max_err=1.0)
draw_svg(paths, TransformPen(quad_pen, (scale, 0, 0, scale, tx, ty)))
return tt_pen.glyph()
def main(font_path):
font = TTFont(font_path)
upm = font["head"].unitsPerEm
order = font.getGlyphOrder()
for codepoint, (name, svg_file) in sorted(CUSTOM.items()):
glyph = build_glyph(svg_path_data(svg_file), upm)
if name not in order:
order.append(name)
font.setGlyphOrder(order)
font["glyf"][name] = glyph
glyph.recalcBounds(font["glyf"])
font["hmtx"][name] = (upm, glyph.xMin)
for table in font["cmap"].tables:
if table.isUnicode():
table.cmap[codepoint] = name
print(f"Added {name} at U+{codepoint:04X}")
font["maxp"].numGlyphs = len(order)
font.save(font_path)
print(f"Wrote {font_path}")
if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit(__doc__)
main(sys.argv[1])
@@ -0,0 +1,130 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="2000.000000pt" height="2056.000000pt" viewBox="0 0 2000.000000 2056.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,2056.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M5403 20550 c-99 -20 -200 -78 -273 -156 -106 -114 -129 -183 -141
-419 -10 -213 -34 -326 -102 -469 -56 -117 -111 -198 -194 -287 -92 -97 -180
-161 -308 -224 -166 -81 -320 -115 -535 -115 -159 0 -268 -41 -376 -142 -78
-73 -136 -181 -154 -285 -8 -48 -10 -196 -8 -474 4 -388 5 -406 26 -463 60
-161 165 -266 322 -324 55 -21 93 -26 245 -33 195 -9 235 -15 350 -54 362
-121 632 -425 711 -800 112 -530 -198 -1065 -719 -1240 -88 -29 -243 -55 -333
-55 -166 0 -316 -96 -386 -246 -121 -259 33 -553 317 -605 85 -16 309 1 472
36 670 143 1221 636 1433 1285 86 261 116 529 90 791 -69 681 -482 1270 -1096
1564 -185 88 -406 151 -629 177 -60 7 -94 14 -75 15 348 25 661 132 947 323
219 146 385 312 534 534 173 256 292 583 315 864 4 45 10 79 14 77 4 -2 10
-42 14 -87 4 -46 18 -134 32 -197 93 -417 318 -789 637 -1058 557 -467 1315
-588 1982 -317 448 183 817 534 1021 969 88 188 155 429 171 610 3 42 9 78 13
80 4 2 10 -31 13 -74 14 -185 88 -443 181 -635 228 -470 634 -826 1127 -990
338 -113 669 -132 1014 -61 582 121 1079 510 1340 1049 96 199 165 431 181
616 9 93 22 135 23 70 2 -133 61 -383 132 -560 210 -525 645 -937 1182 -1115
335 -111 667 -131 1011 -60 641 133 1174 588 1411 1205 59 152 100 329 121
525 l8 70 8 -90 c32 -356 177 -720 404 -1012 130 -166 328 -342 509 -453 248
-152 555 -254 830 -278 l90 -8 -104 -12 c-240 -28 -437 -85 -639 -182 -976
-471 -1377 -1637 -895 -2604 250 -503 706 -873 1243 -1011 112 -28 266 -55
330 -56 67 -1 19 -14 -105 -28 -192 -22 -420 -92 -607 -188 -1064 -541 -1392
-1883 -700 -2858 224 -315 571 -572 945 -700 128 -43 311 -84 419 -92 43 -3
75 -9 73 -13 -2 -4 -38 -10 -80 -13 -113 -10 -265 -43 -395 -87 -705 -234
-1207 -840 -1311 -1580 -7 -52 -13 -167 -13 -255 0 -88 6 -203 13 -255 104
-740 606 -1346 1311 -1580 130 -44 282 -77 395 -87 42 -3 78 -9 80 -13 2 -4
-31 -10 -74 -13 -43 -3 -125 -16 -182 -28 -621 -128 -1145 -556 -1392 -1139
-242 -570 -195 -1232 126 -1758 301 -494 825 -841 1377 -912 188 -24 185 -23
90 -31 -432 -39 -860 -241 -1174 -553 -336 -335 -534 -768 -569 -1246 -1 -19
-8 15 -15 75 -80 676 -478 1244 -1077 1536 -622 303 -1345 254 -1920 -129
-327 -219 -572 -518 -718 -877 -75 -184 -121 -368 -134 -534 -7 -101 -15 -86
-32 65 -71 623 -499 1208 -1089 1486 -819 387 -1795 154 -2354 -563 -217 -278
-370 -658 -395 -980 -3 -43 -9 -76 -13 -74 -4 2 -10 38 -13 80 -16 183 -87
434 -176 620 -225 472 -637 837 -1128 1001 -920 308 -1931 -112 -2353 -979
-100 -206 -171 -447 -186 -635 -4 -45 -10 -85 -14 -87 -4 -2 -10 31 -13 74
-14 175 -84 430 -169 611 -196 419 -545 761 -965 950 -186 83 -412 143 -589
157 -47 3 -83 8 -81 10 3 2 44 8 93 14 660 74 1233 478 1525 1074 215 439 256
942 114 1409 -183 603 -644 1077 -1243 1276 -224 75 -530 116 -672 92 -144
-25 -263 -112 -321 -236 -136 -290 71 -616 392 -616 213 0 455 -81 628 -210
389 -290 538 -785 372 -1239 -119 -326 -400 -580 -739 -667 -75 -20 -137 -27
-277 -34 -193 -8 -247 -20 -343 -76 -115 -67 -217 -214 -241 -348 -17 -93 -14
-804 3 -887 22 -100 69 -184 146 -260 109 -107 221 -149 400 -149 318 0 579
-101 792 -308 218 -211 328 -483 328 -811 0 -180 42 -293 150 -402 73 -74 155
-121 252 -144 91 -21 825 -21 916 0 191 45 342 196 387 387 8 35 15 112 15
173 0 325 112 591 337 806 430 409 1106 393 1513 -37 202 -213 300 -463 300
-768 0 -62 7 -138 15 -174 45 -191 196 -342 387 -387 91 -21 825 -21 916 0
150 36 283 141 347 277 43 90 55 152 55 279 0 308 100 564 303 776 305 318
774 417 1184 250 237 -96 457 -311 569 -552 61 -132 84 -245 94 -450 8 -199
20 -250 81 -350 66 -111 204 -207 337 -234 33 -7 199 -11 445 -11 439 0 469 4
588 66 125 66 229 204 258 342 6 29 11 99 11 155 0 454 230 827 624 1012 537
253 1174 20 1427 -521 66 -139 90 -250 99 -459 9 -193 21 -249 77 -344 64
-111 209 -213 341 -240 86 -18 821 -14 900 4 136 32 248 111 324 229 57 89 69
139 79 331 4 94 15 202 23 241 97 457 465 797 926 854 46 5 126 10 177 10 50
0 116 5 145 11 167 35 317 167 380 335 21 57 22 74 26 480 4 467 1 498 -59
616 -57 114 -187 220 -314 258 -22 6 -121 15 -220 20 -203 10 -295 29 -436 89
-160 69 -322 197 -431 341 -62 81 -151 261 -177 359 -69 252 -43 518 74 756
113 230 273 387 508 501 142 68 258 94 472 104 142 6 192 12 239 29 146 52
255 161 317 316 24 59 24 64 27 481 3 352 1 432 -12 487 -45 195 -196 346
-388 392 -36 8 -112 15 -174 15 -305 0 -558 100 -771 303 -438 420 -438 1124
0 1544 213 203 466 303 771 303 62 0 138 7 174 15 192 46 343 197 388 392 13
55 15 135 12 487 -3 417 -3 422 -27 481 -62 155 -171 264 -317 316 -47 17 -97
23 -239 29 -213 10 -324 35 -469 103 -165 78 -302 188 -407 326 -65 86 -153
265 -179 366 -48 186 -48 388 1 566 26 98 115 278 177 359 111 146 269 271
432 341 140 60 233 79 435 89 99 5 198 14 220 20 127 38 257 144 314 258 60
118 63 149 59 616 -4 406 -5 423 -26 480 -63 168 -213 300 -380 335 -29 6 -95
11 -145 11 -313 0 -574 97 -784 293 -394 367 -452 987 -133 1416 209 281 525
441 872 441 146 0 215 13 308 57 136 65 240 197 275 348 14 58 16 132 13 489
-4 406 -5 423 -26 480 -30 82 -72 143 -140 208 -102 98 -211 138 -372 138
-452 0 -812 204 -1009 571 -86 160 -118 293 -130 539 -6 118 -14 190 -25 220
-54 146 -175 267 -322 322 -57 21 -74 22 -480 26 -353 3 -432 1 -487 -12 -180
-42 -315 -164 -378 -342 -17 -47 -23 -96 -29 -239 -13 -270 -57 -419 -185
-610 -70 -106 -226 -255 -331 -316 -99 -58 -235 -112 -339 -135 -97 -21 -322
-24 -410 -5 -168 35 -325 104 -452 200 -284 214 -433 526 -433 908 0 50 -5
116 -11 145 -35 167 -167 317 -335 380 -57 21 -74 22 -480 26 -353 3 -432 1
-487 -12 -198 -46 -351 -201 -393 -395 -7 -36 -14 -116 -14 -177 0 -297 -99
-547 -300 -758 -272 -286 -665 -396 -1055 -296 -101 26 -280 114 -366 179
-190 144 -335 364 -395 596 -20 78 -27 140 -34 280 -6 142 -12 192 -29 239
-52 146 -161 255 -316 317 -59 23 -64 24 -477 27 -268 2 -438 -1 -475 -8 -95
-17 -196 -71 -268 -143 -111 -111 -155 -231 -155 -423 0 -361 -153 -675 -429
-885 -86 -65 -265 -153 -366 -179 -384 -99 -783 12 -1052 293 -203 213 -303
466 -303 771 0 192 -44 312 -155 423 -71 70 -173 126 -264 143 -64 12 -826 12
-888 0z M7805 16719 c-287 -54 -531 -250 -643 -521 -62 -148 -62 -154 -62
-956 0 -640 2 -735 16 -783 23 -76 56 -128 119 -188 99 -94 229 -135 356 -113
184 32 324 169 359 351 6 32 10 247 10 526 0 443 1 477 20 536 41 132 147 239
277 279 65 20 87 20 3378 20 3254 0 3314 0 3376 -19 136 -43 242 -152 283
-290 15 -50 16 -525 16 -5275 0 -3482 -4 -5236 -11 -5265 -35 -152 -168 -285
-320 -320 -62 -15 -6627 -14 -6689 0 -145 33 -266 146 -311 288 -18 58 -19 95
-19 536 0 279 -4 494 -10 526 -44 232 -263 390 -486 352 -166 -29 -297 -141
-346 -296 -17 -55 -18 -110 -18 -788 0 -804 0 -808 62 -958 58 -140 176 -286
303 -377 66 -47 172 -97 265 -126 l75 -23 3785 -3 c2643 -2 3806 1 3855 8 170
26 328 105 457 228 133 127 215 275 252 454 15 70 16 607 16 5757 0 6220 5
5741 -59 5907 -72 187 -237 368 -416 456 -63 32 -163 64 -239 77 -77 14 -7577
13 -7651 0z M11479 14127 c-155 -54 -259 -174 -288 -331 -8 -41 -11 -478 -11
-1416 l0 -1357 -57 -38 c-228 -151 -366 -418 -366 -705 0 -422 288 -766 708
-845 166 -32 343 -8 503 66 146 67 282 182 357 302 l30 47 722 0 c497 0 739 4
775 11 200 43 338 215 338 423 0 177 -104 327 -280 402 -43 18 -82 19 -800 22
l-755 3 -23 40 c-35 61 -165 192 -233 236 l-59 38 0 1381 c0 1349 0 1383 -20
1446 -37 120 -135 225 -253 270 -32 12 -85 22 -137 25 -71 3 -96 0 -151 -20z
M4944 12608 c-253 -16 -522 -104 -774 -251 -141 -83 -226 -144 -595 -433 -335
-261 -521 -350 -760 -361 -136 -7 -263 9 -377 47 -172 58 -299 136 -588 361
-377 294 -547 401 -790 494 -186 72 -522 141 -644 132 -131 -9 -244 -72 -329
-183 -130 -171 -109 -423 47 -580 98 -97 221 -141 371 -130 75 5 103 3 165
-14 175 -49 309 -131 650 -396 466 -363 725 -495 1105 -565 141 -26 397 -36
530 -20 403 48 706 190 1158 544 369 289 452 347 597 416 126 61 221 82 365
82 343 0 542 -88 990 -442 218 -171 310 -238 446 -320 227 -137 455 -223 707
-265 117 -19 322 -30 322 -17 0 6 18 13 41 17 110 18 235 119 291 235 27 57
33 80 36 161 3 78 0 106 -17 155 -43 122 -138 223 -253 267 -39 15 -90 22
-193 28 -123 6 -153 12 -250 44 -179 60 -289 129 -625 391 -343 268 -555 398
-802 489 -248 92 -543 133 -824 114z M4887 9810 c-266 -33 -522 -128 -777
-285 -94 -59 -281 -197 -520 -384 -400 -315 -636 -408 -940 -372 -236 29 -391
104 -665 325 -438 354 -600 458 -871 561 -114 44 -310 97 -404 110 -25 3 -90
15 -145 27 -88 18 -109 19 -175 9 -268 -41 -442 -299 -375 -556 58 -220 272
-366 492 -335 130 18 366 -64 563 -195 47 -32 184 -137 305 -234 318 -255 484
-362 700 -448 256 -102 573 -149 840 -124 415 39 746 192 1215 561 407 320
518 393 690 448 289 94 634 27 950 -183 38 -25 167 -122 287 -216 120 -94 279
-211 353 -261 241 -161 496 -270 746 -318 133 -25 373 -47 383 -33 3 5 26 15
51 22 127 36 228 122 283 239 30 63 32 74 32 178 0 107 -1 112 -37 185 -46 93
-113 158 -206 202 -61 29 -79 32 -202 38 -285 15 -446 92 -845 404 -369 288
-514 382 -740 480 -253 108 -460 153 -750 160 -93 2 -201 0 -238 -5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

+4
View File
@@ -59,4 +59,8 @@ pyftsubset "$SOURCE_TTF" \
--glyph-names \
--symbol-cmap
# Bake in the custom (non-Material) glyphs from custom/ — third-party logos
# like OpenTimestamps that the upstream font will never carry.
python3 "$REPO_ROOT/tools/material-symbols-subset/add_custom_glyphs.py" "$TARGET"
echo "Wrote $TARGET ($(du -h "$TARGET" | cut -f1))"