1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
import os
import sys
import subprocess
import tempfile
import wasmtime
import platform
import click
from pathlib import Path
import hashlib
import lzma
import appdirs
from importlib import resources as importlib_resources
try:
importlib_resources.files # py3.9+ stdlib
except AttributeError:
import importlib_resources # py3.8- shim
# ==============================
# Note on wasmtime path handling
# ==============================
#
# Hack: Right now, wasmtime's preopen_dir / --map functionality is completely borked. AFAICT only the first mapping is
# even considered, and preopening both / and . simply does not work: Either all paths open'ed by the executable must be
# absolute, or all paths must be relative. I spent some hours trying to track down where exactly this borkage originates
# from, but I found the code confusing and did not succeed.
#
# FOR NOW we work around this issue the dumb way: We simply have click parse enough of the command line to transform any
# paths given on the command line to absolute paths. The actual path resolution is done by click because of
# resolve_path=True.
#
def _run_wasm_app(wasm_filename, argv, cachedir="svg-flatten-wasi"):
module_binary = importlib_resources.read_binary(__package__, wasm_filename)
module_path_digest = hashlib.sha256(__file__.encode()).hexdigest()
module_digest = hashlib.sha256(module_binary).hexdigest()
cache_path = Path(os.getenv("SVG_FLATTEN_WASI_CACHE_DIR", appdirs.user_cache_dir(cachedir)))
cache_path.mkdir(parents=True, exist_ok=True)
cache_filename = (cache_path / f'{wasm_filename}-{module_path_digest[:8]}-{module_digest[:16]}')
wasi_cfg = wasmtime.WasiConfig()
wasi_cfg.argv = argv
wasi_cfg.preopen_dir('/', '/')
wasi_cfg.inherit_stdin()
wasi_cfg.inherit_stdout()
wasi_cfg.inherit_stderr()
engine = wasmtime.Engine()
import time
try:
with cache_filename.open("rb") as cache_file:
module = wasmtime.Module.deserialize(engine, lzma.decompress(cache_file.read()))
except:
print("Preparing to run {}. This might take a while...".format(argv[0]), file=sys.stderr)
module = wasmtime.Module(engine, module_binary)
with cache_filename.open("wb") as cache_file:
cache_file.write(lzma.compress(module.serialize(), preset=0))
linker = wasmtime.Linker(engine)
linker.define_wasi()
store = wasmtime.Store(engine)
store.set_wasi(wasi_cfg)
app = linker.instantiate(store, module)
linker.define_instance(store, "app", app)
try:
app.exports(store)["_start"](store)
return 0
except wasmtime.ExitTrap as trap:
return trap.code
def run_usvg(input_file, output_file, dpi=96):
args = ['--keep-named-groups', '--dpi', str(dpi), input_file, output_file]
# By default, try a number of options:
candidates = [
# somewhere in $PATH
'usvg',
'wasi-usvg',
# in user-local cargo installation
Path.home() / '.cargo' / 'bin' / 'usvg',
# wasi-usvg in user-local pip installation
Path.home() / '.local' / 'bin' / 'wasi-usvg',
# next to our current python interpreter (e.g. in virtualenv)
str(Path(sys.executable).parent / 'wasi-usvg')
]
# if USVG envvar is set, try that first.
if 'USVG' in os.environ:
exec_candidates = [os.environ['USVG'], *exec_candidates]
for candidate in candidates:
try:
res = subprocess.run([candidate, *args], check=True)
print('used usvg:', candidate)
break
except FileNotFoundError:
continue
else:
raise SystemError('usvg executable not found')
@click.command(context_settings={'ignore_unknown_options': True})
@click.option('--no-usvg', is_flag=True)
@click.option('--usvg-dpi', type=int, default=96)
@click.argument('other_args', nargs=-1, type=click.UNPROCESSED)
@click.argument('input_file', type=click.Path(resolve_path=True, dir_okay=False))
@click.argument('output_file', type=click.Path(resolve_path=True, dir_okay=False, writable=True))
def run_svg_flatten(input_file, output_file, other_args, usvg_dpi, no_usvg):
with tempfile.NamedTemporaryFile() as f:
if not no_usvg:
run_usvg(input_file, f.name, dpi=usvg_dpi)
input_file = f.name
cmdline = ['svg-flatten', '--force-svg', '--no-usvg', *other_args, input_file, output_file]
sys.exit(_run_wasm_app("svg-flatten.wasm", cmdline))
|