zcbot/skills/ppt/scripts/fetch_icon.py

178 lines
5.6 KiB
Python

"""fetch_icon.py: 从 Iconify CDN 拉个性化图标,按主题色染色,缓存本地。
Iconify 聚合了 150+ 免费开源图标集,无需账号、无 API key:
tabler -- 现代描边 (Apache 2.0) ⭐ 推荐
lucide -- 开源经典 (ISC)
heroicons -- Tailwind (MIT)
material-symbols -- Google (Apache 2.0)
carbon -- IBM (Apache 2.0)
fluent -- Microsoft (MIT)
mdi -- Material Design (Apache 2.0)
每个集都有数千图标,在 https://icon-sets.iconify.design/ 浏览找名字。
用法:
# 推荐: 染主色,导出 PNG (需 cairosvg 或 svglib)
python fetch_icon.py rocket --set tabler --color C00000 --size 128 \\
-o slides/rocket.png
# 只要 SVG (PowerPoint 2016+ 支持嵌入 SVG)
python fetch_icon.py target --set lucide --color FFC107 \\
-o slides/target.svg
# 默认值: set=tabler, color=C00000(主红), size=128
python fetch_icon.py chart-bar -o slides/chart_bar.png
环境:
PNG 转换依赖任一: `pip install cairosvg` (推荐) 或 `pip install svglib`
若都没有,会保存 .svg 到目标路径(扩展名自动改).
退出码:
0 = 成功 PNG/SVG
1 = SVG 有了但 PNG 转换失败 (已保存 SVG)
2 = 网络/图标名错误 (没拉到)
"""
from __future__ import annotations
import argparse
import io
import sys
import urllib.parse
import urllib.request
from pathlib import Path
ICONIFY_API = "https://api.iconify.design/{set}/{name}.svg"
def fetch_svg(name: str, icon_set: str, color: str, size: int) -> str:
"""从 Iconify 拉 SVG,带主题色和大小参数。"""
params: dict[str, str] = {}
if color:
params["color"] = "#" + color.lstrip("#")
if size:
params["height"] = str(size)
params["width"] = str(size)
url = ICONIFY_API.format(set=icon_set, name=name)
if params:
url += "?" + urllib.parse.urlencode(params)
req = urllib.request.Request(
url, headers={"User-Agent": "ppt-skill-fetch_icon/1.0"}
)
with urllib.request.urlopen(req, timeout=15) as resp:
body = resp.read().decode("utf-8")
return body
def svg_to_png(svg_text: str, out_path: Path, size: int) -> bool:
"""SVG → PNG,降级链:cairosvg → svglib+reportlab → 失败。"""
# 路径 1: cairosvg (推荐,质量最好)
try:
import cairosvg # type: ignore
cairosvg.svg2png(
bytestring=svg_text.encode("utf-8"),
write_to=str(out_path),
output_width=size,
output_height=size,
)
return True
except ImportError:
pass
except Exception as e:
print(f"[warn] cairosvg 渲染失败: {e}", file=sys.stderr)
# 路径 2: svglib + reportlab
try:
from svglib.svglib import svg2rlg # type: ignore
from reportlab.graphics import renderPM # type: ignore
drawing = svg2rlg(io.StringIO(svg_text))
if drawing is None:
return False
renderPM.drawToFile(drawing, str(out_path), fmt="PNG")
return True
except ImportError:
pass
except Exception as e:
print(f"[warn] svglib 渲染失败: {e}", file=sys.stderr)
return False
def main() -> int:
ap = argparse.ArgumentParser(
description="从 Iconify CDN 拉个性化 SVG/PNG 图标"
)
ap.add_argument("name", help="图标名,见 https://icon-sets.iconify.design/")
ap.add_argument(
"--set", default="tabler",
help="图标集 (默认 tabler;可选 lucide/heroicons/material-symbols/carbon/fluent/mdi)",
)
ap.add_argument(
"--color", default="C00000",
help="主题色 hex,无 # (默认 C00000 商务红主色)",
)
ap.add_argument(
"--size", type=int, default=128,
help="像素 (默认 128,适合 0.5-1.0 in PPT 图标)",
)
ap.add_argument(
"-o", "--out", required=True, type=Path,
help="输出路径 (.png 或 .svg)",
)
ap.add_argument(
"--svg-only", action="store_true",
help="只输出 SVG,跳过 PNG 转换",
)
args = ap.parse_args()
args.out.parent.mkdir(parents=True, exist_ok=True)
try:
svg = fetch_svg(args.name, args.set, args.color, args.size)
except urllib.error.HTTPError as e:
print(
f"[error] Iconify 返回 {e.code}: 图标 '{args.set}:{args.name}' "
f"可能不存在,在 https://icon-sets.iconify.design/{args.set}/ 搜",
file=sys.stderr,
)
return 2
except Exception as e:
print(f"[error] 拉取失败: {e}", file=sys.stderr)
return 2
if "<svg" not in svg:
print(
f"[error] 返回不是 SVG: 图标 '{args.set}:{args.name}' 不存在",
file=sys.stderr,
)
return 2
out: Path = args.out
want_svg = args.svg_only or out.suffix.lower() == ".svg"
if want_svg:
if out.suffix.lower() != ".svg":
out = out.with_suffix(".svg")
out.write_text(svg, encoding="utf-8")
print(f"[ok] SVG → {out}")
return 0
if svg_to_png(svg, out, args.size):
print(f"[ok] PNG → {out} ({args.set}:{args.name} #{args.color})")
return 0
# PNG 转换失败,保存 SVG 兜底
svg_alt = out.with_suffix(".svg")
svg_alt.write_text(svg, encoding="utf-8")
print(
f"[warn] PNG 转换不可用 (装 `pip install cairosvg` 或 `pip install svglib`)\n"
f" 已保存 SVG → {svg_alt}\n"
f" PowerPoint 2016+ 直接 add_picture(svg) 也可以",
file=sys.stderr,
)
return 1
if __name__ == "__main__":
sys.exit(main())