@taloswork/ose-shared 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/package.json +3 -3
  2. package/skills/cozy-static-svg/SKILL.md +76 -0
  3. package/skills/cozy-static-svg/references/approved-preview.png +0 -0
  4. package/skills/cozy-static-svg/references/cross-platform.md +73 -0
  5. package/skills/cozy-static-svg/references/moon_keeps_watch.svg +229 -0
  6. package/skills/cozy-static-svg/references/style-anchor.md +271 -0
  7. package/skills/cozy-static-svg/scripts/artkit.py +321 -0
  8. package/skills/cozy-static-svg/scripts/to_png.py +333 -0
  9. package/skills/diagram-design/SKILL.md +568 -0
  10. package/skills/diagram-design/assets/example-architecture-dark.html +182 -0
  11. package/skills/diagram-design/assets/example-architecture-full.html +194 -0
  12. package/skills/diagram-design/assets/example-architecture.html +183 -0
  13. package/skills/diagram-design/assets/example-bar-dark.html +129 -0
  14. package/skills/diagram-design/assets/example-bar-full.html +105 -0
  15. package/skills/diagram-design/assets/example-bar.html +129 -0
  16. package/skills/diagram-design/assets/example-data-flow-dark.html +143 -0
  17. package/skills/diagram-design/assets/example-data-flow-full.html +185 -0
  18. package/skills/diagram-design/assets/example-data-flow.html +143 -0
  19. package/skills/diagram-design/assets/example-datalake-dark.html +243 -0
  20. package/skills/diagram-design/assets/example-datalake-full.html +248 -0
  21. package/skills/diagram-design/assets/example-datalake.html +269 -0
  22. package/skills/diagram-design/assets/example-dp-integration-dark.html +80 -0
  23. package/skills/diagram-design/assets/example-dp-integration-full.html +90 -0
  24. package/skills/diagram-design/assets/example-dp-integration.html +80 -0
  25. package/skills/diagram-design/assets/example-dp-security-matrix-dark.html +31 -0
  26. package/skills/diagram-design/assets/example-dp-security-matrix-full.html +32 -0
  27. package/skills/diagram-design/assets/example-dp-security-matrix.html +88 -0
  28. package/skills/diagram-design/assets/example-er-dark.html +202 -0
  29. package/skills/diagram-design/assets/example-er-full.html +205 -0
  30. package/skills/diagram-design/assets/example-er.html +202 -0
  31. package/skills/diagram-design/assets/example-flowchart-dark.html +156 -0
  32. package/skills/diagram-design/assets/example-flowchart-full.html +159 -0
  33. package/skills/diagram-design/assets/example-flowchart.html +156 -0
  34. package/skills/diagram-design/assets/example-gantt-dark.html +137 -0
  35. package/skills/diagram-design/assets/example-gantt-full.html +127 -0
  36. package/skills/diagram-design/assets/example-gantt.html +137 -0
  37. package/skills/diagram-design/assets/example-high-level-dark.html +251 -0
  38. package/skills/diagram-design/assets/example-high-level-full.html +247 -0
  39. package/skills/diagram-design/assets/example-high-level-vertical-dark.html +274 -0
  40. package/skills/diagram-design/assets/example-high-level-vertical-full.html +252 -0
  41. package/skills/diagram-design/assets/example-high-level-vertical.html +325 -0
  42. package/skills/diagram-design/assets/example-high-level.html +301 -0
  43. package/skills/diagram-design/assets/example-import-drawio.html +185 -0
  44. package/skills/diagram-design/assets/example-import-mermaid.html +70 -0
  45. package/skills/diagram-design/assets/example-it-state-dark.html +90 -0
  46. package/skills/diagram-design/assets/example-it-state-full.html +69 -0
  47. package/skills/diagram-design/assets/example-it-state.html +128 -0
  48. package/skills/diagram-design/assets/example-layers-dark.html +123 -0
  49. package/skills/diagram-design/assets/example-layers-full.html +126 -0
  50. package/skills/diagram-design/assets/example-layers.html +123 -0
  51. package/skills/diagram-design/assets/example-line-dark.html +111 -0
  52. package/skills/diagram-design/assets/example-line-full.html +105 -0
  53. package/skills/diagram-design/assets/example-line.html +111 -0
  54. package/skills/diagram-design/assets/example-loop-dark.html +100 -0
  55. package/skills/diagram-design/assets/example-loop-full.html +137 -0
  56. package/skills/diagram-design/assets/example-loop-terminal.html +347 -0
  57. package/skills/diagram-design/assets/example-loop.html +100 -0
  58. package/skills/diagram-design/assets/example-medallion-dark.html +159 -0
  59. package/skills/diagram-design/assets/example-medallion-full.html +201 -0
  60. package/skills/diagram-design/assets/example-medallion.html +159 -0
  61. package/skills/diagram-design/assets/example-nested-dark.html +132 -0
  62. package/skills/diagram-design/assets/example-nested-full.html +135 -0
  63. package/skills/diagram-design/assets/example-nested.html +136 -0
  64. package/skills/diagram-design/assets/example-org-chart-dark.html +45 -0
  65. package/skills/diagram-design/assets/example-org-chart-full.html +45 -0
  66. package/skills/diagram-design/assets/example-org-chart.html +45 -0
  67. package/skills/diagram-design/assets/example-paved-road-animated.html +546 -0
  68. package/skills/diagram-design/assets/example-policy-trace-animated.html +490 -0
  69. package/skills/diagram-design/assets/example-process-dark.html +64 -0
  70. package/skills/diagram-design/assets/example-process-full.html +74 -0
  71. package/skills/diagram-design/assets/example-process.html +64 -0
  72. package/skills/diagram-design/assets/example-pyramid-dark.html +119 -0
  73. package/skills/diagram-design/assets/example-pyramid-full.html +122 -0
  74. package/skills/diagram-design/assets/example-pyramid.html +119 -0
  75. package/skills/diagram-design/assets/example-quadrant-consultant.html +166 -0
  76. package/skills/diagram-design/assets/example-quadrant-dark.html +135 -0
  77. package/skills/diagram-design/assets/example-quadrant-full.html +138 -0
  78. package/skills/diagram-design/assets/example-quadrant.html +135 -0
  79. package/skills/diagram-design/assets/example-queue-animated.html +577 -0
  80. package/skills/diagram-design/assets/example-radar-dark.html +139 -0
  81. package/skills/diagram-design/assets/example-radar-full.html +135 -0
  82. package/skills/diagram-design/assets/example-radar.html +139 -0
  83. package/skills/diagram-design/assets/example-scatter-dark.html +141 -0
  84. package/skills/diagram-design/assets/example-scatter-full.html +116 -0
  85. package/skills/diagram-design/assets/example-scatter.html +141 -0
  86. package/skills/diagram-design/assets/example-sequence-dark.html +222 -0
  87. package/skills/diagram-design/assets/example-sequence-full.html +388 -0
  88. package/skills/diagram-design/assets/example-sequence-oauth-dark.html +188 -0
  89. package/skills/diagram-design/assets/example-sequence-oauth-full.html +350 -0
  90. package/skills/diagram-design/assets/example-sequence-oauth.html +227 -0
  91. package/skills/diagram-design/assets/example-sequence.html +222 -0
  92. package/skills/diagram-design/assets/example-state-dark.html +147 -0
  93. package/skills/diagram-design/assets/example-state-full.html +150 -0
  94. package/skills/diagram-design/assets/example-state.html +147 -0
  95. package/skills/diagram-design/assets/example-swimlane-dark.html +172 -0
  96. package/skills/diagram-design/assets/example-swimlane-full.html +175 -0
  97. package/skills/diagram-design/assets/example-swimlane.html +172 -0
  98. package/skills/diagram-design/assets/example-timeline-dark.html +135 -0
  99. package/skills/diagram-design/assets/example-timeline-full.html +138 -0
  100. package/skills/diagram-design/assets/example-timeline.html +135 -0
  101. package/skills/diagram-design/assets/example-tree-dark.html +173 -0
  102. package/skills/diagram-design/assets/example-tree-full.html +176 -0
  103. package/skills/diagram-design/assets/example-tree.html +173 -0
  104. package/skills/diagram-design/assets/example-venn-dark.html +132 -0
  105. package/skills/diagram-design/assets/example-venn-full.html +135 -0
  106. package/skills/diagram-design/assets/example-venn.html +110 -0
  107. package/skills/diagram-design/assets/icons.html +230 -0
  108. package/skills/diagram-design/assets/index.html +369 -0
  109. package/skills/diagram-design/assets/template-dark.html +85 -0
  110. package/skills/diagram-design/assets/template-full.html +413 -0
  111. package/skills/diagram-design/assets/template-motion.html +434 -0
  112. package/skills/diagram-design/assets/template-terminal.html +173 -0
  113. package/skills/diagram-design/assets/template.html +86 -0
  114. package/skills/diagram-design/references/animation.md +141 -0
  115. package/skills/diagram-design/references/export.md +136 -0
  116. package/skills/diagram-design/references/import-drawio.md +171 -0
  117. package/skills/diagram-design/references/import-mermaid.md +126 -0
  118. package/skills/diagram-design/references/onboarding.md +297 -0
  119. package/skills/diagram-design/references/output-spec.md +181 -0
  120. package/skills/diagram-design/references/primitive-annotation.md +36 -0
  121. package/skills/diagram-design/references/primitive-icons.md +827 -0
  122. package/skills/diagram-design/references/primitive-sketchy.md +43 -0
  123. package/skills/diagram-design/references/primitive-terminal.md +76 -0
  124. package/skills/diagram-design/references/semantic-patterns.md +122 -0
  125. package/skills/diagram-design/references/style-guide.md +139 -0
  126. package/skills/diagram-design/references/type-architecture.md +78 -0
  127. package/skills/diagram-design/references/type-bar.md +48 -0
  128. package/skills/diagram-design/references/type-data-flow.md +374 -0
  129. package/skills/diagram-design/references/type-dp-integration.md +410 -0
  130. package/skills/diagram-design/references/type-dp-security-matrix.md +379 -0
  131. package/skills/diagram-design/references/type-er.md +23 -0
  132. package/skills/diagram-design/references/type-flowchart.md +23 -0
  133. package/skills/diagram-design/references/type-gantt.md +45 -0
  134. package/skills/diagram-design/references/type-high-level.md +458 -0
  135. package/skills/diagram-design/references/type-it-state.md +470 -0
  136. package/skills/diagram-design/references/type-layers.md +26 -0
  137. package/skills/diagram-design/references/type-line.md +44 -0
  138. package/skills/diagram-design/references/type-loop.md +223 -0
  139. package/skills/diagram-design/references/type-medallion.md +356 -0
  140. package/skills/diagram-design/references/type-nested.md +22 -0
  141. package/skills/diagram-design/references/type-org-chart.md +44 -0
  142. package/skills/diagram-design/references/type-process.md +495 -0
  143. package/skills/diagram-design/references/type-pyramid.md +33 -0
  144. package/skills/diagram-design/references/type-quadrant.md +81 -0
  145. package/skills/diagram-design/references/type-radar.md +80 -0
  146. package/skills/diagram-design/references/type-scatter.md +39 -0
  147. package/skills/diagram-design/references/type-sequence.md +130 -0
  148. package/skills/diagram-design/references/type-state.md +21 -0
  149. package/skills/diagram-design/references/type-swimlane.md +20 -0
  150. package/skills/diagram-design/references/type-timeline.md +20 -0
  151. package/skills/diagram-design/references/type-tree.md +24 -0
  152. package/skills/diagram-design/references/type-venn.md +26 -0
  153. package/skills/diagram-design/scripts/drawio_extract.py +856 -0
  154. package/skills/diagram-design/scripts/mermaid_extract.py +1285 -0
  155. package/skills/diagram-design/scripts/self_check.py +389 -0
  156. package/skills/document/bin/doc-gen.cjs +3 -6
  157. package/skills/document/bin/html2pptx.cjs +17 -9
  158. package/skills/document/bin/pptx-clone.cjs +41 -30
  159. package/skills/document/bin/pptx-embed-fonts.cjs +6 -2
  160. package/skills/document/bin/pptx-slide.cjs +46 -50
  161. package/skills/document/content-pipeline.md +54 -5
  162. package/skills/document/layouts/README.md +8 -0
  163. package/skills/document/layouts/blocks-2x2.tpl +19 -9
  164. package/skills/document/layouts/cards-3.tpl +1 -1
  165. package/skills/document/layouts/org-tree.tpl +1 -1
  166. package/skills/document/layouts/steps-vertical.tpl +1 -1
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env python3
2
+ """SVG → PNG(跨平台:macOS / Windows / Linux 同一条命令,纯标准库 + 系统已有工具)。
3
+
4
+ 仅当用户明确要 PNG 时才用,默认工作流仍是「直出原生 SVG,不转 PNG」。
5
+
6
+ 用法(两个平台写法一致):
7
+ macOS / Linux python3 scripts/to_png.py 作品.svg
8
+ Windows py -3 scripts\\to_png.py 作品.svg
9
+ python3 scripts/to_png.py --scale 2 . # 目录下全部 SVG,2 倍图
10
+ py -3 scripts\\to_png.py -o out --width 2400 作品目录
11
+ python3 scripts/to_png.py --backend inkscape 作品.svg
12
+
13
+ 后端自动探测,找到哪个用哪个(也可用 --backend 指定):
14
+ 1. rsvg-convert 与手绘流程同源,最推荐
15
+ macOS: brew install librsvg | Windows: MSYS2 / scoop 自行安装
16
+ 2. inkscape Inkscape 1.x,装了图形界面就自带命令行
17
+ 3. cairosvg pip install cairosvg(纯 Python 依赖)
18
+ 4. chrome / edge 系统自带浏览器无头截图,零安装兜底
19
+
20
+ 其它参数:
21
+ -o DIR 输出目录(默认与源文件同目录)
22
+ --scale N 相对 SVG 原始尺寸的倍数(默认 1)
23
+ --width / --height 指定像素尺寸(只给一边则按比例算另一边)
24
+ --background RRGGBB 铺不透明底色(默认保留透明,圆角外仍是透明的)
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import argparse
29
+ import glob
30
+ import os
31
+ import re
32
+ import shutil
33
+ import subprocess
34
+ import sys
35
+ import tempfile
36
+ import xml.etree.ElementTree as ET
37
+ from pathlib import Path
38
+
39
+ if sys.version_info < (3, 9):
40
+ sys.exit(
41
+ "需要 Python 3.9 或更新版本(当前 %d.%d);"
42
+ "Windows 请用 py -3 运行,或到 python.org 安装新版。" % sys.version_info[:2]
43
+ )
44
+
45
+ if hasattr(sys.stdout, "reconfigure"): # Windows 控制台默认 GBK,强制 UTF-8 输出
46
+ try:
47
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
48
+ except (AttributeError, ValueError):
49
+ pass
50
+
51
+ CHROME_CANDIDATES = {
52
+ "darwin": [
53
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
54
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
55
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
56
+ ],
57
+ "win32": [
58
+ r"%PROGRAMFILES%\Google\Chrome\Application\chrome.exe",
59
+ r"%PROGRAMFILES(X86)%\Google\Chrome\Application\chrome.exe",
60
+ r"%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe",
61
+ r"%PROGRAMFILES(X86)%\Microsoft\Edge\Application\msedge.exe",
62
+ r"%PROGRAMFILES%\Microsoft\Edge\Application\msedge.exe",
63
+ ],
64
+ }
65
+ CHROME_ON_PATH = ["chrome", "google-chrome", "chromium", "chromium-browser", "msedge"]
66
+
67
+
68
+ # ---------- 输入展开(与 artkit.py 同规则:文件 / 目录 / 通配符) ----------
69
+
70
+ def collect_sources(items: list[str]) -> list[Path]:
71
+ found: list[Path] = []
72
+ for item in items:
73
+ text = str(item)
74
+ path = Path(text)
75
+ if path.is_dir():
76
+ found.extend(
77
+ sorted(
78
+ p
79
+ for p in path.rglob("*")
80
+ if p.is_file() and p.suffix.lower() == ".svg"
81
+ )
82
+ )
83
+ continue
84
+ if any(ch in text for ch in "*?["):
85
+ found.extend(
86
+ sorted(
87
+ Path(m)
88
+ for m in glob.glob(text, recursive=True)
89
+ if Path(m).is_file() and Path(m).suffix.lower() == ".svg"
90
+ )
91
+ )
92
+ continue
93
+ found.append(path)
94
+
95
+ unique: list[Path] = []
96
+ seen: set[str] = set()
97
+ for p in found:
98
+ try:
99
+ key = str(p.resolve())
100
+ except OSError:
101
+ key = str(p)
102
+ if key not in seen:
103
+ seen.add(key)
104
+ unique.append(p)
105
+ return unique
106
+
107
+
108
+ # ---------- 后端探测 ----------
109
+
110
+ def _which(*names: str) -> str | None:
111
+ for name in names:
112
+ hit = shutil.which(name)
113
+ if hit:
114
+ return hit
115
+ return None
116
+
117
+
118
+ def find_chrome() -> str | None:
119
+ for raw in CHROME_CANDIDATES.get(sys.platform, []):
120
+ path = os.path.expandvars(raw)
121
+ if Path(path).is_file():
122
+ return path
123
+ return _which(*CHROME_ON_PATH)
124
+
125
+
126
+ def find_backends() -> dict[str, str]:
127
+ """返回 {后端名: 可执行路径或说明};cairosvg 用 'python' 表示模块可用。"""
128
+ backends: dict[str, str] = {}
129
+ if (rsvg := _which("rsvg-convert")):
130
+ backends["rsvg"] = rsvg
131
+ if (ink := _which("inkscape")) or Path(
132
+ "/Applications/Inkscape.app/Contents/MacOS/inkscape"
133
+ ).is_file():
134
+ backends["inkscape"] = ink or "/Applications/Inkscape.app/Contents/MacOS/inkscape"
135
+ try:
136
+ import cairosvg # noqa: F401
137
+
138
+ backends["cairosvg"] = "python-module"
139
+ except ImportError:
140
+ pass
141
+ if (chrome := find_chrome()):
142
+ backends["chrome"] = chrome
143
+ return backends
144
+
145
+
146
+ # ---------- 尺寸 ----------
147
+
148
+ def svg_size(src: Path) -> tuple[float, float] | None:
149
+ try:
150
+ root = ET.parse(src).getroot()
151
+ except (ET.ParseError, OSError):
152
+ return None
153
+ size = re.compile(r"^\s*(\d+(?:\.\d+)?)")
154
+ out = []
155
+ for attr in ("width", "height"):
156
+ match = size.match(root.get(attr) or "")
157
+ if not match:
158
+ return None
159
+ out.append(float(match.group(1)))
160
+ return out[0], out[1]
161
+
162
+
163
+ def target_size(src: Path, scale: float, width: float | None, height: float | None):
164
+ base = svg_size(src) or (1200.0, 900.0)
165
+ w, h = base[0] * scale, base[1] * scale
166
+ if width and height:
167
+ return width, height
168
+ if width:
169
+ return width, round(w and h * width / w)
170
+ if height:
171
+ return round(w * height / h), height
172
+ return round(w), round(h)
173
+
174
+
175
+ # ---------- 各后端转换 ----------
176
+
177
+ def convert_rsvg(exe: str, src: Path, out: Path, size, background: str | None) -> None:
178
+ cmd = [exe, "-w", str(size[0]), "-h", str(size[1])]
179
+ if background:
180
+ cmd += ["-b", f"#{background.lstrip('#')}"]
181
+ cmd += [str(src), "-o", str(out)]
182
+ subprocess.run(cmd, check=True, capture_output=True)
183
+
184
+
185
+ def convert_inkscape(exe: str, src: Path, out: Path, size, background: str | None) -> None:
186
+ cmd = [
187
+ exe,
188
+ "--export-type=png",
189
+ f"--export-filename={out}",
190
+ f"--export-width={size[0]}",
191
+ f"--export-height={size[1]}",
192
+ ]
193
+ if background:
194
+ cmd += [f"--export-background=#{background.lstrip('#')}", "--export-background-opacity=255"]
195
+ cmd.append(str(src))
196
+ subprocess.run(cmd, check=True, capture_output=True)
197
+
198
+
199
+ def convert_cairosvg(src: Path, out: Path, size, background: str | None) -> None:
200
+ import cairosvg
201
+
202
+ cairosvg.svg2png(
203
+ url=str(src),
204
+ write_to=str(out),
205
+ output_width=size[0],
206
+ output_height=size[1],
207
+ background_color=f"#{background.lstrip('#')}" if background else None,
208
+ )
209
+
210
+
211
+ def convert_chrome(exe: str, src: Path, out: Path, size, background: str | None) -> None:
212
+ markup = src.read_text(encoding="utf-8")
213
+ markup = re.sub(r"<\?xml[^>]*\?>", "", markup, count=1).strip()
214
+ if background:
215
+ markup = markup.replace(
216
+ "<svg ", f'<svg style="background:#{background.lstrip("#")}" ', 1
217
+ )
218
+ html = (
219
+ "<!doctype html><html><head><meta charset=\"utf-8\"><style>"
220
+ "html,body{margin:0;padding:0;overflow:hidden;background:transparent}"
221
+ "svg{display:block;width:100vw;height:100vh}</style></head><body>"
222
+ + markup
223
+ + "</body></html>"
224
+ )
225
+ with tempfile.TemporaryDirectory() as tmp:
226
+ page = Path(tmp) / "page.html"
227
+ page.write_text(html, encoding="utf-8")
228
+ common = [
229
+ f"--screenshot={out}",
230
+ f"--window-size={size[0]},{size[1]}",
231
+ "--hide-scrollbars",
232
+ "--force-device-scale-factor=1",
233
+ "--virtual-time-budget=2000",
234
+ "--default-background-color=00000000",
235
+ page.resolve().as_uri(),
236
+ ]
237
+ for headless in ("--headless=new", "--headless"):
238
+ try:
239
+ subprocess.run(
240
+ [exe, headless, *common], check=True, capture_output=True, timeout=120
241
+ )
242
+ if out.is_file():
243
+ return
244
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
245
+ continue
246
+ raise RuntimeError("无头浏览器没有产出截图(可换 --backend inkscape 或 rsvg)")
247
+
248
+
249
+ # ---------- 主流程 ----------
250
+
251
+ def main() -> int:
252
+ parser = argparse.ArgumentParser(
253
+ description="SVG → PNG(跨平台,自动选择可用后端)",
254
+ epilog="参数可以是文件、目录或通配符。默认保留透明底,输出与源文件同目录。",
255
+ )
256
+ parser.add_argument("svgs", nargs="+", help="待转换的 SVG(文件 / 目录 / 通配符)")
257
+ parser.add_argument("-o", "--outdir", type=Path, help="输出目录")
258
+ parser.add_argument("--scale", type=float, default=1.0, help="相对原始尺寸的倍数(默认 1)")
259
+ parser.add_argument("--width", type=float, help="输出宽度(像素)")
260
+ parser.add_argument("--height", type=float, help="输出高度(像素)")
261
+ parser.add_argument("--background", help="铺底色,如 FFFFFF(默认透明)")
262
+ parser.add_argument(
263
+ "--backend",
264
+ choices=["auto", "rsvg", "inkscape", "cairosvg", "chrome"],
265
+ default="auto",
266
+ help="指定后端(默认 auto:rsvg → inkscape → cairosvg → chrome)",
267
+ )
268
+ args = parser.parse_args()
269
+
270
+ if args.outdir:
271
+ args.outdir.mkdir(parents=True, exist_ok=True)
272
+
273
+ sources = collect_sources(args.svgs)
274
+ if not sources:
275
+ print("没有找到任何 .svg —— 检查路径,或用目录 / 通配符批量指定。")
276
+ return 1
277
+
278
+ backends = find_backends()
279
+ order = ["rsvg", "inkscape", "cairosvg", "chrome"]
280
+ if args.backend != "auto":
281
+ if args.backend not in backends:
282
+ print(f"指定的后端 {args.backend} 在本机没找到。可用:{', '.join(backends) or '无'}")
283
+ print(_install_hint())
284
+ return 1
285
+ chosen = args.backend
286
+ else:
287
+ chosen = next((name for name in order if name in backends), None)
288
+ if not chosen:
289
+ print("没有找到任何可用的转换后端。")
290
+ print(_install_hint())
291
+ return 1
292
+
293
+ print(f"后端:{chosen}({backends[chosen]})")
294
+ failed = 0
295
+ for index, src in enumerate(sources, 1):
296
+ name = src.name
297
+ if not src.is_file():
298
+ print(f"[{index}/{len(sources)}] {name} ✗ 文件不存在")
299
+ failed += 1
300
+ continue
301
+ size = target_size(src, args.scale, args.width, args.height)
302
+ out = (args.outdir or src.parent) / (src.stem + ".png")
303
+ try:
304
+ if chosen == "rsvg":
305
+ convert_rsvg(backends["rsvg"], src, out, size, args.background)
306
+ elif chosen == "inkscape":
307
+ convert_inkscape(backends["inkscape"], src, out, size, args.background)
308
+ elif chosen == "cairosvg":
309
+ convert_cairosvg(src, out, size, args.background)
310
+ else:
311
+ convert_chrome(backends["chrome"], src, out, size, args.background)
312
+ print(f"[{index}/{len(sources)}] {name} → {out.name}({size[0]:g}×{size[1]:g})")
313
+ except (subprocess.CalledProcessError, RuntimeError, OSError, ValueError) as exc:
314
+ print(f"[{index}/{len(sources)}] {name} ✗ 转换失败:{exc}")
315
+ failed += 1
316
+
317
+ ok = len(sources) - failed
318
+ print(f"总计:{ok}/{len(sources)} 张转成 PNG。")
319
+ return 1 if failed else 0
320
+
321
+
322
+ def _install_hint() -> str:
323
+ return (
324
+ "装一个就行:\n"
325
+ " macOS brew install librsvg (或 brew install --cask inkscape)\n"
326
+ " Windows choco/scoop 装 librsvg,或装 Inkscape,或 pip install cairosvg\n"
327
+ " —— 其实系统自带的 Edge 就能当后端,装不了工具时用 --backend chrome\n"
328
+ " Linux apt install librsvg2-bin / dnf install librsvg2-tools"
329
+ )
330
+
331
+
332
+ if __name__ == "__main__":
333
+ raise SystemExit(main())