sdk: sb_icons 扩充动作图标 + TopAppBar 位图图标支持

TopAppBar 动作项此前用字体码点(⚙↻⌕)绘制,4k 字库无这些码点→豆腐。改走位图:
- sb_icons.h 追加 15 个 Material Symbols Rounded 动作位图(settings/refresh/search/
  play_arrow/pause/skip_next/skip_previous/arrow_back/close/add/check/lyrics/
  queue_music/download/heart),FILL=1 实心与状态栏一致;sb_icons[] 只追加(0-14 不变),
  动作索引 15-29。
- 生成脚本 tools/gen_action_icons.py(cairosvg svg→32x32 单通道 alpha,SVG 缓存
  icons_src/ 可复现、断网可跑)。
- meow_ui_gfx.c 抽取图标 blit 像素核心 icon_blit_px,新增 meow_gfx_blit_icon_ink
  (控件路径不标顶栏脏区);meow_gfx_blit_icon 状态栏路径行为不变。
- meow_ui_widget.c W_TOPAPPBAR 项支持位图模式:新 API meow_topappbar_set_item_icon
  (w, idx, icon_idx),绘制取主题 onSurface/按压向栏底色淡 12%,文本项行为不变;
  hit 用统一项宽。
- 公共头 widgets.h 加 MEOW_ICON_*(15-29) 枚举与 set_item_icon 声明;internal.h 声明
  meow_gfx_blit_icon_ink。
- 宿主测试(test_meow_ui_widgets)加 topbar 位图图标绘制/命中/按压变淡 + 状态栏 blit
  回归断言;test_umbrella 加 MEOW_ICON_* 编译期锁;MEOW_APP_SDK.md 补说明+索引表。
- 验证:宿主测试 ALL PASS(14项);交叉 zig 构建 0 warning;meow_ui.a 新 md5
  1d69cdae55ab5dde3a98f5e2124f3656。
This commit is contained in:
AskaEth committed 2026-09-02 03:24:46 +08:00
1 parent 41427fdcb3
commit 3611cbbbde
24 files changed
+1006 -22

No files matched your search

+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""gen_action_icons.py — 从 Material Symbols Rounded 官方 SVG 生成 sb_icons.h 位图动作图标。
背景:MeowOS SDK topbar(W_TOPAPPBAR)动作此前用字体码点(⚙↻⌕)绘制,4k 字库无这些码点→豆腐。
正确路线 = 位图图标:与状态栏同一张 sb_icons[](Material Symbols Rounded 32x32 单通道 alpha),
动作项经 meow_topappbar_set_item_icon(w, idx, MEOW_ICON_*) 指定索引后即画位图。
版权/来源:图标位图源自 google/material-design-icons 的 Material Symbols Rounded(Apache-2.0)。
本脚本只做下载(或读缓存)+ 栅格化成 32x32 单通道 alpha + 拼接 C 数组。
生成方式:svg(24px 网格,viewBox 0 -960 960 960)→ cairosvg 512x512 → LANCZOS 降到 32x32
取 alpha 通道(每像素 0..255 亮度/alpha,与状态栏同源——绘制颜色由调用方主题表提供)。
用 FILL=1(实心)以匹配现有状态栏图标的实心风格(WiFi/BT/BATT 均为实心)。
可复现性:SVG 缓存于本目录 icons_src/{symbol}_fill1_24px.svg(缺则联网下载,命中缓存即断网可跑);
栅格参数(512/32/LANCZOS/FILL=1)固定 → 重跑输出一致。
用法(在 patch_v2/sdk/ 下):
python3 tools/gen_action_icons.py # 用 venv 的 cairosvg 运行
依赖:Python3 + Pillow + cairosvg(isolated venv:python3 -m venv <v> && <v>/bin/pip install cairosvg)。
输出:重写 patch_v2/sdk/sb_icons.h——保留现有 15 个状态栏位图(0-14,原样),追加 15 个动作
图标(15-29),索引兼容(既有索引不变,只追加)。
"""
import io
import os
import sys
import time
import urllib.request
try:
import cairosvg
from PIL import Image
except Exception: # noqa
sys.exit("缺少 cairosvg / Pillow,请先安装:python3 -m venv <v> && <v>/bin/pip install cairosvg")
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
SDK_DIR = os.path.abspath(os.path.join(THIS_DIR, os.pardir))
SB_ICONS_H = os.path.join(SDK_DIR, "sb_icons.h")
SRC_DIR = os.path.join(THIS_DIR, "icons_src")
OUT_RES = 512 # cairosvg 栅格目标边长(先大后小 → 抗锯齿 alpha)
TARGET = 32 # 最终位图边长(= SB_ICON_SIZE)
FILL = 1 # Material Symbols FILL 轴:1=实心(与状态栏一致)
BASE_URL = "https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web/"
UA = {"User-Agent": "Mozilla/5.0"}
# 现有状态栏图标 C 数组名(0-14,按 sb_icons[] 现有顺序;勿改序)。
# 本脚本不改动它们(原样保留),仅在生成表格/枚举时按此顺序引用。
BASE_ICONS = [
"SB_ICON_WIFI", "SB_ICON_BT", "SB_ICON_BATT", "SB_ICON_VOL", "SB_ICON_BRIGHT",
"SB_ICON_WIFI_STRONG", "SB_ICON_WIFI_MEDIUM", "SB_ICON_WIFI_WEAK",
"SB_ICON_WIFI_NO", "SB_ICON_WIFI_OFF", "SB_ICON_BT_NO", "SB_ICON_BT_CONN",
"SB_ICON_BT_DIS", "SB_ICON_BT_SEARCH", "SB_ICON_BATT_CHG",
]
# 动作图标:c_array_name, enum_name, material_symbols 图标名。索引 15-29,勿改序。
ACTIONS = [
("SB_ICON_SETTINGS", "SB_I_SETTINGS", "settings"),
("SB_ICON_REFRESH", "SB_I_REFRESH", "refresh"),
("SB_ICON_SEARCH", "SB_I_SEARCH", "search"),
("SB_ICON_PLAY_ARROW", "SB_I_PLAY_ARROW", "play_arrow"),
("SB_ICON_PAUSE", "SB_I_PAUSE", "pause"),
("SB_ICON_SKIP_NEXT", "SB_I_SKIP_NEXT", "skip_next"),
("SB_ICON_SKIP_PREV", "SB_I_SKIP_PREV", "skip_previous"),
("SB_ICON_ARROW_BACK", "SB_I_ARROW_BACK", "arrow_back"),
("SB_ICON_CLOSE", "SB_I_CLOSE", "close"),
("SB_ICON_ADD", "SB_I_ADD", "add"),
("SB_ICON_CHECK", "SB_I_CHECK", "check"),
("SB_ICON_LYRICS", "SB_I_LYRICS", "lyrics"),
("SB_ICON_QUEUE_MUSIC", "SB_I_QUEUE_MUSIC", "queue_music"),
("SB_ICON_DOWNLOAD", "SB_I_DOWNLOAD", "download"),
("SB_ICON_HEART", "SB_I_HEART", "favorite"), # Material Symbols 用 "favorite" 表心形
]
HEADER_COMMENT = """/* sb_icons.h — 状态栏 + 动作位图图标(Material Symbols Rounded, MD3),单通道 alpha 位图 32x32。
* 生成自 material-symbols rounded(google/material-design-icons,Apache-2.0)SVG 栅格化:
* 图标族 = FILL=1 实心(与状态栏 WiFi/BT/BATT 现有实心风格一致);每像素 0..255(亮度/alpha)。
* ★ 为日/夜间预留:位图只存亮度;绘制颜色由调用方主题表提供,换主题仅改配色。
* 索引布局(兼容):状态栏 SB_I_WIFI=0..SB_I_BATT_CHG=14(原样保留);TopAppBar 动作
* MEOW_ICON_SETTINGS=15..MEOW_ICON_HEART=29(只追加,既有索引不变)。动作图标为 15-29,
* 经 meow_topappbar_set_item_icon(w, idx, MEOW_ICON_*) 使 W_TOPAPPBAR 动作项画位图。
* 重新生成:python3 tools/gen_action_icons.py(SVG 缓存 tools/icons_src/,可复现)。 */
#ifndef SB_ICONS_H
#define SB_ICONS_H
#define SB_ICON_SIZE 32
typedef struct { const unsigned char *data; } sb_icon_t;
"""
ACTION_BLOCK_MARKER = "/* ===== TopAppBar \u52a8\u4f5c\u56fe\u6807" # "/* ===== TopAppBar 动作图标"
def fetch(url, path, retries=6):
"""带重试的下载(网络不稳):命中缓存直接返回,失败退避重试。"""
if os.path.exists(path) and os.path.getsize(path) > 200:
return open(path, "rb").read()
for k in range(retries):
try:
req = urllib.request.Request(url, headers=UA)
data = urllib.request.urlopen(req, timeout=25).read()
if len(data) < 200:
raise ValueError("too small %d" % len(data))
open(path, "wb").write(data)
return data
except Exception as e: # noqa
sys.stderr.write(" retry %d %s: %s\n" % (k, path, repr(e)[:80]))
time.sleep(1.5 * (k + 1))
raise RuntimeError("download failed: " + url)
def svg_path_for(symbol):
return os.path.join(SRC_DIR, symbol + "_fill%d_24px.svg" % FILL)
def ensure_svg(symbol):
path = svg_path_for(symbol)
if not (os.path.exists(path) and os.path.getsize(path) > 200):
url = BASE_URL + symbol + "/materialsymbolsrounded/" + symbol + "_fill%d_24px.svg" % FILL
fetch(url, path)
return path
def rasterize_alpha32(symbol):
"""SVG → 32x32 单通道 alpha(0..255)。512 渲大图再 LANCZOS 降采样,得到平滑边缘。"""
svg = open(ensure_svg(symbol), "rb").read()
png = cairosvg.svg2png(bytestring=svg, output_width=OUT_RES, output_height=OUT_RES)
alpha = Image.open(io.BytesIO(png)).convert("RGBA").getchannel("A")
return alpha.resize((TARGET, TARGET), Image.LANCZOS)
def fmt_alpha_array(name, alpha):
"""把 32x32 alpha 排成 C 数组(每行 32 个像素,便于目检)。"""
lines = ["static const unsigned char %s[%d]={" % (name, TARGET * TARGET)]
for y in range(TARGET):
row = ",".join(str(alpha.getpixel((x, y))) for x in range(TARGET))
lines.append(row + ",")
lines.append("};")
return "\n".join(lines)
def build_action_block():
outs = [ACTION_BLOCK_MARKER + "(Material Symbols Rounded, MD3, Apache-2.0)====\n"]
outs.append(" * 复用状态栏同源 32x32 单通道 alpha;绘制颜色/按压态由 meow_ui_widget.c 主题表提供。 */\n")
for cname, _enum, symbol in ACTIONS:
a = rasterize_alpha32(symbol)
outs.append(fmt_alpha_array(cname, a))
return "\n".join(outs)
def build_table_and_enum():
table = ["static const sb_icon_t sb_icons[] = {"]
for name in BASE_ICONS:
table.append(" { %s }," % name)
for cname, _enum, _symbol in ACTIONS:
table.append(" { %s }," % cname)
table.append("};")
table = "\n".join(table)
enum = ["/* 枚举索引(与 sb_icons[] 对应)。状态栏 0-14(勿改序);动作 15-29(TopAppBar 用)。 */"]
enum.append("enum { SB_I_WIFI=0, SB_I_BT=1, SB_I_BATT=2, SB_I_VOL=3, SB_I_BRIGHT=4,")
enum.append(" SB_I_WIFI_STRONG=5, SB_I_WIFI_MEDIUM=6, SB_I_WIFI_WEAK=7,")
enum.append(" SB_I_WIFI_NO=8, SB_I_WIFI_OFF=9, SB_I_BT_NO=10, SB_I_BT_CONN=11,")
enum.append(" SB_I_BT_DIS=12, SB_I_BT_SEARCH=13, SB_I_BATT_CHG=14,")
# 动作枚举从 15 起,与 meow_ui_widgets.h 的 MEOW_ICON_* 同值(勿改序)
for i, (_c, e, _s) in enumerate(ACTIONS):
last = (i == len(ACTIONS) - 1)
enum.append(" %s=%d%s" % (e, 15 + i, " }; " if last else ","))
return table + "\n\n" + "\n".join(enum) + "\n\n#endif /* SB_ICONS_H */\n"
def regenerate_sb_icons_h():
old = open(SB_ICONS_H, encoding="utf-8").read()
# body = 现有状态栏数组(原样保留)。HEADER_COMMENT 已提供 ifndef/define/size/typedef,
# 故从首个状态栏数组起切;终点:优先动作块标记(幂等重跑),缺失则 sb_icons[] 表格(首跑)。
body_start = old.find("static const unsigned char " + BASE_ICONS[0] + "[1024]")
if body_start < 0:
sys.exit("sb_icons.h 缺少首个状态栏数组,拒绝重写")
pr = old.find(ACTION_BLOCK_MARKER)
if pr < 0:
pr = old.find("static const sb_icon_t sb_icons[]")
if pr < 0 or pr <= body_start:
sys.exit("sb_icons.h 缺少 sb_icons[] 表格,拒绝重写")
body = old[body_start:pr]
out = HEADER_COMMENT + body.rstrip("\n") + "\n\n"
out += build_action_block() + "\n\n"
out += build_table_and_enum()
open(SB_ICONS_H, "w", encoding="utf-8").write(out)
print("wrote %s (%d bytes)" % (SB_ICONS_H, len(out)))
def main():
os.makedirs(SRC_DIR, exist_ok=True)
regenerate_sb_icons_h()
if __name__ == "__main__":
main()
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-440H240q-17 0-28.5-11.5T200-480q0-17 11.5-28.5T240-520h200v-200q0-17 11.5-28.5T480-760q17 0 28.5 11.5T520-720v200h200q17 0 28.5 11.5T760-480q0 17-11.5 28.5T720-440H520v200q0 17-11.5 28.5T480-200q-17 0-28.5-11.5T440-240v-200Z"/></svg>

After

Width:  |  Height:  |  Size: 335 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m313-440 196 196q12 12 11.5 28T508-188q-12 11-28 11.5T452-188L188-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l264-264q11-11 27.5-11t28.5 11q12 12 12 28.5T508-715L313-520h447q17 0 28.5 11.5T800-480q0 17-11.5 28.5T760-440H313Z"/></svg>

After

Width:  |  Height:  |  Size: 328 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m382-354 339-339q12-12 28-12t28 12q12 12 12 28.5T777-636L410-268q-12 12-28 12t-28-12L182-440q-12-12-11.5-28.5T183-497q12-12 28.5-12t28.5 12l142 143Z"/></svg>

After

Width:  |  Height:  |  Size: 254 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-424 284-228q-11 11-28 11t-28-11q-11-11-11-28t11-28l196-196-196-196q-11-11-11-28t11-28q11-11 28-11t28 11l196 196 196-196q11-11 28-11t28 11q11 11 11 28t-11 28L536-480l196 196q11 11 11 28t-11 28q-11 11-28 11t-28-11L480-424Z"/></svg>

After

Width:  |  Height:  |  Size: 331 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-337q-8 0-15-2.5t-13-8.5L308-492q-12-12-11.5-28t11.5-28q12-12 28.5-12.5T365-549l75 75v-286q0-17 11.5-28.5T480-800q17 0 28.5 11.5T520-760v286l75-75q12-12 28.5-11.5T652-548q11 12 11.5 28T652-492L508-348q-6 6-13 8.5t-15 2.5ZM240-160q-33 0-56.5-23.5T160-240v-80q0-17 11.5-28.5T200-360q17 0 28.5 11.5T240-320v80h480v-80q0-17 11.5-28.5T760-360q17 0 28.5 11.5T800-320v80q0 33-23.5 56.5T720-160H240Z"/></svg>

After

Width:  |  Height:  |  Size: 501 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-147q-14 0-28.5-5T426-168l-69-63q-106-97-191.5-192.5T80-634q0-94 63-157t157-63q53 0 100 22.5t80 61.5q33-39 80-61.5T660-854q94 0 157 63t63 157q0 115-85 211T602-230l-68 62q-11 11-25.5 16t-28.5 5Z"/></svg>

After

Width:  |  Height:  |  Size: 303 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-400h80q17 0 28.5-11.5T400-440q0-17-11.5-28.5T360-480h-80q-17 0-28.5 11.5T240-440q0 17 11.5 28.5T280-400Zm480-80q-50 0-85-35t-35-85q0-50 35-85t85-35q11 0 21 2t19 5v-167q0-17 11.5-28.5T840-920h80q17 0 28.5 11.5T960-880q0 17-11.5 28.5T920-840h-40v240q0 50-35 85t-85 35Zm-480-40h200q17 0 28.5-11.5T520-560q0-17-11.5-28.5T480-600H280q-17 0-28.5 11.5T240-560q0 17 11.5 28.5T280-520Zm0-120h200q17 0 28.5-11.5T520-680q0-17-11.5-28.5T480-720H280q-17 0-28.5 11.5T240-680q0 17 11.5 28.5T280-640Zm-40 400-92 92q-19 19-43.5 8.5T80-177v-623q0-33 23.5-56.5T160-880h440q31 0 54.5 19t23.5 49q0 14-6.5 25T654-769q-44 27-69 72t-25 97q0 54 26.5 100t73.5 73q22 13 35 34.5t13 47.5q0 45-31.5 75T600-240H240Z"/></svg>

After

Width:  |  Height:  |  Size: 795 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M640-200q-33 0-56.5-23.5T560-280v-400q0-33 23.5-56.5T640-760q33 0 56.5 23.5T720-680v400q0 33-23.5 56.5T640-200Zm-320 0q-33 0-56.5-23.5T240-280v-400q0-33 23.5-56.5T320-760q33 0 56.5 23.5T400-680v400q0 33-23.5 56.5T320-200Z"/></svg>

After

Width:  |  Height:  |  Size: 327 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M320-273v-414q0-17 12-28.5t28-11.5q5 0 10.5 1.5T381-721l326 207q9 6 13.5 15t4.5 19q0 10-4.5 19T707-446L381-239q-5 3-10.5 4.5T360-233q-16 0-28-11.5T320-273Z"/></svg>

After

Width:  |  Height:  |  Size: 261 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M640-160q-50 0-85-35t-35-85q0-50 35-85t85-35q11 0 21 1.5t19 6.5v-288q0-17 11.5-28.5T720-720h120q17 0 28.5 11.5T880-680q0 17-11.5 28.5T840-640h-80v360q0 50-35 85t-85 35ZM160-320q-17 0-28.5-11.5T120-360q0-17 11.5-28.5T160-400h240q17 0 28.5 11.5T440-360q0 17-11.5 28.5T400-320H160Zm0-160q-17 0-28.5-11.5T120-520q0-17 11.5-28.5T160-560h400q17 0 28.5 11.5T600-520q0 17-11.5 28.5T560-480H160Zm0-160q-17 0-28.5-11.5T120-680q0-17 11.5-28.5T160-720h400q17 0 28.5 11.5T600-680q0 17-11.5 28.5T560-640H160Z"/></svg>

After

Width:  |  Height:  |  Size: 600 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-70q0-17 11.5-28.5T760-800q17 0 28.5 11.5T800-760v200q0 17-11.5 28.5T760-520H560q-17 0-28.5-11.5T520-560q0-17 11.5-28.5T560-600h128q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q68 0 124.5-34.5T692-367q8-14 22.5-19.5t29.5-.5q16 5 23 21t-1 30q-41 80-117 128t-169 48Z"/></svg>

After

Width:  |  Height:  |  Size: 463 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M380-320q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l224 224q11 11 11 28t-11 28q-11 11-28 11t-28-11L532-372q-30 24-69 38t-83 14Zm0-80q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>

After

Width:  |  Height:  |  Size: 387 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M433-80q-27 0-46.5-18T363-142l-9-66q-13-5-24.5-12T307-235l-62 26q-25 11-50 2t-39-32l-47-82q-14-23-8-49t27-43l53-40q-1-7-1-13.5v-27q0-6.5 1-13.5l-53-40q-21-17-27-43t8-49l47-82q14-23 39-32t50 2l62 26q11-8 23-15t24-12l9-66q4-26 23.5-44t46.5-18h94q27 0 46.5 18t23.5 44l9 66q13 5 24.5 12t22.5 15l62-26q25-11 50-2t39 32l47 82q14 23 8 49t-27 43l-53 40q1 7 1 13.5v27q0 6.5-2 13.5l53 40q21 17 27 43t-8 49l-48 82q-14 23-39 32t-50-2l-60-26q-11 8-23 15t-24 12l-9 66q-4 26-23.5 44T527-80h-94Zm49-260q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Z"/></svg>

After

Width:  |  Height:  |  Size: 671 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M660-280v-400q0-17 11.5-28.5T700-720q17 0 28.5 11.5T740-680v400q0 17-11.5 28.5T700-240q-17 0-28.5-11.5T660-280Zm-440-35v-330q0-18 12-29t28-11q5 0 11 1t11 5l248 166q9 6 13.5 14.5T548-480q0 10-4.5 18.5T530-447L282-281q-5 4-11 5t-11 1q-16 0-28-11t-12-29Z"/></svg>

After

Width:  |  Height:  |  Size: 357 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M220-280v-400q0-17 11.5-28.5T260-720q17 0 28.5 11.5T300-680v400q0 17-11.5 28.5T260-240q-17 0-28.5-11.5T220-280Zm458-1L430-447q-9-6-13.5-14.5T412-480q0-10 4.5-18.5T430-513l248-166q5-4 11-5t11-1q16 0 28 11t12 29v330q0 18-12 29t-28 11q-5 0-11-1t-11-5Z"/></svg>

After

Width:  |  Height:  |  Size: 354 B