@taloswork/ose-shared 0.1.8 → 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.
- package/package.json +3 -3
- package/skills/cozy-static-svg/SKILL.md +76 -0
- package/skills/cozy-static-svg/references/approved-preview.png +0 -0
- package/skills/cozy-static-svg/references/cross-platform.md +73 -0
- package/skills/cozy-static-svg/references/moon_keeps_watch.svg +229 -0
- package/skills/cozy-static-svg/references/style-anchor.md +271 -0
- package/skills/cozy-static-svg/scripts/artkit.py +321 -0
- package/skills/cozy-static-svg/scripts/to_png.py +333 -0
- package/skills/diagram-design/SKILL.md +568 -0
- package/skills/diagram-design/assets/example-architecture-dark.html +182 -0
- package/skills/diagram-design/assets/example-architecture-full.html +194 -0
- package/skills/diagram-design/assets/example-architecture.html +183 -0
- package/skills/diagram-design/assets/example-bar-dark.html +129 -0
- package/skills/diagram-design/assets/example-bar-full.html +105 -0
- package/skills/diagram-design/assets/example-bar.html +129 -0
- package/skills/diagram-design/assets/example-data-flow-dark.html +143 -0
- package/skills/diagram-design/assets/example-data-flow-full.html +185 -0
- package/skills/diagram-design/assets/example-data-flow.html +143 -0
- package/skills/diagram-design/assets/example-datalake-dark.html +243 -0
- package/skills/diagram-design/assets/example-datalake-full.html +248 -0
- package/skills/diagram-design/assets/example-datalake.html +269 -0
- package/skills/diagram-design/assets/example-dp-integration-dark.html +80 -0
- package/skills/diagram-design/assets/example-dp-integration-full.html +90 -0
- package/skills/diagram-design/assets/example-dp-integration.html +80 -0
- package/skills/diagram-design/assets/example-dp-security-matrix-dark.html +31 -0
- package/skills/diagram-design/assets/example-dp-security-matrix-full.html +32 -0
- package/skills/diagram-design/assets/example-dp-security-matrix.html +88 -0
- package/skills/diagram-design/assets/example-er-dark.html +202 -0
- package/skills/diagram-design/assets/example-er-full.html +205 -0
- package/skills/diagram-design/assets/example-er.html +202 -0
- package/skills/diagram-design/assets/example-flowchart-dark.html +156 -0
- package/skills/diagram-design/assets/example-flowchart-full.html +159 -0
- package/skills/diagram-design/assets/example-flowchart.html +156 -0
- package/skills/diagram-design/assets/example-gantt-dark.html +137 -0
- package/skills/diagram-design/assets/example-gantt-full.html +127 -0
- package/skills/diagram-design/assets/example-gantt.html +137 -0
- package/skills/diagram-design/assets/example-high-level-dark.html +251 -0
- package/skills/diagram-design/assets/example-high-level-full.html +247 -0
- package/skills/diagram-design/assets/example-high-level-vertical-dark.html +274 -0
- package/skills/diagram-design/assets/example-high-level-vertical-full.html +252 -0
- package/skills/diagram-design/assets/example-high-level-vertical.html +325 -0
- package/skills/diagram-design/assets/example-high-level.html +301 -0
- package/skills/diagram-design/assets/example-import-drawio.html +185 -0
- package/skills/diagram-design/assets/example-import-mermaid.html +70 -0
- package/skills/diagram-design/assets/example-it-state-dark.html +90 -0
- package/skills/diagram-design/assets/example-it-state-full.html +69 -0
- package/skills/diagram-design/assets/example-it-state.html +128 -0
- package/skills/diagram-design/assets/example-layers-dark.html +123 -0
- package/skills/diagram-design/assets/example-layers-full.html +126 -0
- package/skills/diagram-design/assets/example-layers.html +123 -0
- package/skills/diagram-design/assets/example-line-dark.html +111 -0
- package/skills/diagram-design/assets/example-line-full.html +105 -0
- package/skills/diagram-design/assets/example-line.html +111 -0
- package/skills/diagram-design/assets/example-loop-dark.html +100 -0
- package/skills/diagram-design/assets/example-loop-full.html +137 -0
- package/skills/diagram-design/assets/example-loop-terminal.html +347 -0
- package/skills/diagram-design/assets/example-loop.html +100 -0
- package/skills/diagram-design/assets/example-medallion-dark.html +159 -0
- package/skills/diagram-design/assets/example-medallion-full.html +201 -0
- package/skills/diagram-design/assets/example-medallion.html +159 -0
- package/skills/diagram-design/assets/example-nested-dark.html +132 -0
- package/skills/diagram-design/assets/example-nested-full.html +135 -0
- package/skills/diagram-design/assets/example-nested.html +136 -0
- package/skills/diagram-design/assets/example-org-chart-dark.html +45 -0
- package/skills/diagram-design/assets/example-org-chart-full.html +45 -0
- package/skills/diagram-design/assets/example-org-chart.html +45 -0
- package/skills/diagram-design/assets/example-paved-road-animated.html +546 -0
- package/skills/diagram-design/assets/example-policy-trace-animated.html +490 -0
- package/skills/diagram-design/assets/example-process-dark.html +64 -0
- package/skills/diagram-design/assets/example-process-full.html +74 -0
- package/skills/diagram-design/assets/example-process.html +64 -0
- package/skills/diagram-design/assets/example-pyramid-dark.html +119 -0
- package/skills/diagram-design/assets/example-pyramid-full.html +122 -0
- package/skills/diagram-design/assets/example-pyramid.html +119 -0
- package/skills/diagram-design/assets/example-quadrant-consultant.html +166 -0
- package/skills/diagram-design/assets/example-quadrant-dark.html +135 -0
- package/skills/diagram-design/assets/example-quadrant-full.html +138 -0
- package/skills/diagram-design/assets/example-quadrant.html +135 -0
- package/skills/diagram-design/assets/example-queue-animated.html +577 -0
- package/skills/diagram-design/assets/example-radar-dark.html +139 -0
- package/skills/diagram-design/assets/example-radar-full.html +135 -0
- package/skills/diagram-design/assets/example-radar.html +139 -0
- package/skills/diagram-design/assets/example-scatter-dark.html +141 -0
- package/skills/diagram-design/assets/example-scatter-full.html +116 -0
- package/skills/diagram-design/assets/example-scatter.html +141 -0
- package/skills/diagram-design/assets/example-sequence-dark.html +222 -0
- package/skills/diagram-design/assets/example-sequence-full.html +388 -0
- package/skills/diagram-design/assets/example-sequence-oauth-dark.html +188 -0
- package/skills/diagram-design/assets/example-sequence-oauth-full.html +350 -0
- package/skills/diagram-design/assets/example-sequence-oauth.html +227 -0
- package/skills/diagram-design/assets/example-sequence.html +222 -0
- package/skills/diagram-design/assets/example-state-dark.html +147 -0
- package/skills/diagram-design/assets/example-state-full.html +150 -0
- package/skills/diagram-design/assets/example-state.html +147 -0
- package/skills/diagram-design/assets/example-swimlane-dark.html +172 -0
- package/skills/diagram-design/assets/example-swimlane-full.html +175 -0
- package/skills/diagram-design/assets/example-swimlane.html +172 -0
- package/skills/diagram-design/assets/example-timeline-dark.html +135 -0
- package/skills/diagram-design/assets/example-timeline-full.html +138 -0
- package/skills/diagram-design/assets/example-timeline.html +135 -0
- package/skills/diagram-design/assets/example-tree-dark.html +173 -0
- package/skills/diagram-design/assets/example-tree-full.html +176 -0
- package/skills/diagram-design/assets/example-tree.html +173 -0
- package/skills/diagram-design/assets/example-venn-dark.html +132 -0
- package/skills/diagram-design/assets/example-venn-full.html +135 -0
- package/skills/diagram-design/assets/example-venn.html +110 -0
- package/skills/diagram-design/assets/icons.html +230 -0
- package/skills/diagram-design/assets/index.html +369 -0
- package/skills/diagram-design/assets/template-dark.html +85 -0
- package/skills/diagram-design/assets/template-full.html +413 -0
- package/skills/diagram-design/assets/template-motion.html +434 -0
- package/skills/diagram-design/assets/template-terminal.html +173 -0
- package/skills/diagram-design/assets/template.html +86 -0
- package/skills/diagram-design/references/animation.md +141 -0
- package/skills/diagram-design/references/export.md +136 -0
- package/skills/diagram-design/references/import-drawio.md +171 -0
- package/skills/diagram-design/references/import-mermaid.md +126 -0
- package/skills/diagram-design/references/onboarding.md +297 -0
- package/skills/diagram-design/references/output-spec.md +181 -0
- package/skills/diagram-design/references/primitive-annotation.md +36 -0
- package/skills/diagram-design/references/primitive-icons.md +827 -0
- package/skills/diagram-design/references/primitive-sketchy.md +43 -0
- package/skills/diagram-design/references/primitive-terminal.md +76 -0
- package/skills/diagram-design/references/semantic-patterns.md +122 -0
- package/skills/diagram-design/references/style-guide.md +139 -0
- package/skills/diagram-design/references/type-architecture.md +78 -0
- package/skills/diagram-design/references/type-bar.md +48 -0
- package/skills/diagram-design/references/type-data-flow.md +374 -0
- package/skills/diagram-design/references/type-dp-integration.md +410 -0
- package/skills/diagram-design/references/type-dp-security-matrix.md +379 -0
- package/skills/diagram-design/references/type-er.md +23 -0
- package/skills/diagram-design/references/type-flowchart.md +23 -0
- package/skills/diagram-design/references/type-gantt.md +45 -0
- package/skills/diagram-design/references/type-high-level.md +458 -0
- package/skills/diagram-design/references/type-it-state.md +470 -0
- package/skills/diagram-design/references/type-layers.md +26 -0
- package/skills/diagram-design/references/type-line.md +44 -0
- package/skills/diagram-design/references/type-loop.md +223 -0
- package/skills/diagram-design/references/type-medallion.md +356 -0
- package/skills/diagram-design/references/type-nested.md +22 -0
- package/skills/diagram-design/references/type-org-chart.md +44 -0
- package/skills/diagram-design/references/type-process.md +495 -0
- package/skills/diagram-design/references/type-pyramid.md +33 -0
- package/skills/diagram-design/references/type-quadrant.md +81 -0
- package/skills/diagram-design/references/type-radar.md +80 -0
- package/skills/diagram-design/references/type-scatter.md +39 -0
- package/skills/diagram-design/references/type-sequence.md +130 -0
- package/skills/diagram-design/references/type-state.md +21 -0
- package/skills/diagram-design/references/type-swimlane.md +20 -0
- package/skills/diagram-design/references/type-timeline.md +20 -0
- package/skills/diagram-design/references/type-tree.md +24 -0
- package/skills/diagram-design/references/type-venn.md +26 -0
- package/skills/diagram-design/scripts/drawio_extract.py +856 -0
- package/skills/diagram-design/scripts/mermaid_extract.py +1285 -0
- package/skills/diagram-design/scripts/self_check.py +389 -0
|
@@ -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())
|