Skip to content

Instantly share code, notes, and snippets.

@jn0
Last active November 24, 2017 10:13
Show Gist options
  • Save jn0/d9fb13c548598bbab916b60a75c6e413 to your computer and use it in GitHub Desktop.
Save jn0/d9fb13c548598bbab916b60a75c6e413 to your computer and use it in GitHub Desktop.
Generated SVG optimizer

Fix SVG Utility

The fixsvg.py is intended to flatten an SVG generated by a converter (in my case - from Acme CAD Converter, which "Converts DWG, DXF, DWF to SVG", ran on an AutoCAD drawing).

My savings was 65%: 2MB of converted SVG versus 700KB of "fixed" one.

Converters often leave a lot of extraneous stuff in the SVG pathes like forced Move before any Line as well as single path splet over several tags.
This utility will squash these ops into a single command.

The utility may group togather elements found within radius of <R> (see the --radius=<R> switch).
It may --add-circle to that groups (with specified or default --fill=, --stroke=, --opacity=, etc) for some visualization.

I have used files stolen here for sampling.

Example

The run

jno@jno:~/src/UI$ ./fixsvg.py 065-02-Model.svg > z.svg
Performing SVG optimization (065-02-Model.svg -> <stdout>)...
	phase #0 -- loading...
		1998679 bytes loaded.
	phase #1 -- normalizing spaces...
		done, 1932275 bytes (3.3% saved).
	phase #2 -- removing single path groups (<g><path/></g> becomes <path/>)...
		done, 1839679 bytes (8.0% saved), 13228 groups removed.
	phase #3 -- grouping pathes on attributes, optimizing d...
	        done, 808367 bytes (59.6% saved).
	phase #4 -- grouping pathes using arithmetic mean in radius of 32.00, with circles...
	        done, 866133 bytes (56.7% saved).
	phase #5 -- fixing IDs, joining pathes (661 > P294)...
		done, 707942 bytes (64.6% saved).
661 groups found using radius of 32.00 and arithmetic mean in 1998679 lines for 10.78 seconds.
jno@jno:~/src/UI$ 

Converted SVG

<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" width="800.000" height="600.000" viewBox="0.0 0.0 800.000 600.000" version="1.1">
  <title>Produce by Acme CAD Converter</title>
  <desc>Produce by Acme CAD Converter</desc>
<g id="A-DETL-MBND" display="visible">
<g><path d="M170.93 257.51L170.38 256.56"
fill="none" stroke="blue" stroke-width="0.5"/>
</g>
<g><path d="M173.78 260.65L169.46 260.65"
fill="none" stroke="blue" stroke-width="0.5"/>
</g>
<g><path d="M173.78 256.32L173.78 260.65"
fill="none" stroke="blue" stroke-width="0.5"/>
</g>
<g><path d="M169.46 256.32L173.78 256.32"
fill="none" stroke="blue" stroke-width="0.5"/>
</g>
...

optimized, grouped and marked-up to

<?xml version="1.0" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" width="800.000" height="600.000" viewBox="0.0 0.0 800.000 600.000" version="1.1"><title>Produce by Acme CAD Converter</title><desc>Produce by Acme CAD Converter</desc><g id="A-DETL-MBND" display="visible"><g fill="none" stroke="blue" stroke-width="0.5"><!-- Radius=32.000000 -->
<style type="text/css">
<![CDATA[
circle.boxId {
        fill: none;
        opacity: 0.00;
        stroke: yellow
        stroke-width: 1;
        stroke-opacity: 0.70;
}
circle.boxShowId {
        fill: yellow;
        opacity: 0.50;
        stroke: yellow
        stroke-width: 1;
        stroke-opacity: 0.70;
}
circle.boxSelectId {
        fill: pink;
        opacity: 0.70;
        stroke: pink
        stroke-width: 1;
        stroke-opacity: 0.90;
}
]]>
</style>
<g id="P000"><circle cx="171" cy="258" r="32" class="boxId" id="C000" /><path d="M170.93 257.51L170.38 256.56 M173.78 260.65H169.46 M173.78 256.32V260.65 M169.46 256.32H173.78 M169.46 260.65V256.32" /></g>
<g id="P001"><circle cx="7" cy="372" r="32" class="boxId" id="C001" /><path d="M7.78 373.46V373.45 M7.78 374.38H7.14 M7.78 370.92V374.38 M4.82 370.92H7.78" /></g>
<g id="P002"><circle cx="61" cy="370" r="32" class="boxId" id="C002" /><path d="M62.58 370.49L62.5 370.63 M63.57 370.49V374.42 M59.24 370.49H63.57 M59.24 370.7V370.49" /></g>
<g id="P003"><circle cx="617" cy="166" r="32" class="boxId" id="C003" /><path d="M616.43 162.55V162.51 M618.16 161.36V183.95 M616.92 161.36H618.16 M616.12 162.55V162.51 M618.12 183.95H618.16" /></g>
<g id="P004"><circle cx="661" cy="341" r="32" class="boxId" id="C004" /><path d="M644.72 340.62H659.5 M661.15 340.62H667.97 M668.3 343H661.15 M659.5 343H644.72 M667.97 340.62V339.78 M668.3 339.78V343" /></g>
<g id="P005"><circle cx="108" cy="235" r="32" class="boxId" id="C005" /><path d="M103.31 224.68H92.93 M114.38 245.79V224.72" /></g>
...

Arguments

./fixsvg.py [options] <files...>

Options:
    -h, --help           -- this help

    -o, --output=<F>     -- write output to a file (=<stdout>)

    -r, --radius=<R>     -- radius to group pathes within (=32.00)
    -a, --average=<M>    -- algo to calc averages ('A' or 'S') (=arithmetic mean)

    -R, --add-circle     -- add <circle> to any idenitified <g>roup (=True)
        --fill=<X>           -- fill="none"
        --stroke=<X>         -- stroke="yellow"
        --stroke-width=<X>   -- stroke-width="1"
        --opacity=<X>        -- opacity="0"
        --stroke-opacity=<X> -- stroke-opacity="0.7"

    -O, --optimize      -- do/dont run SVG path optimization (=True)
    -J, --join-paths    -- do/dont join adjacent <path>s (=True)

Debug tools:
    -l, --label          -- add labels to groups (=False)
    -C, --font-color=<C> -- use this font color (=brown)
    -S, --font-size=<S>  -- use this font size (=6)
#!/usr/bin/python
import sys
import getopt
from time import time
from math import sqrt
from re import subn
from svgpath import parse, get_d
output = sys.stdout
message = sys.stderr
RADIUS = 32.0
label_it = False
font_size = 6
font_color = 'brown'
add_circle = True
optimize = True
join_paths = True
CIRCLE_CONFIG = {
'fill': 'none',
'stroke': 'yellow',
'stroke-width': 1,
'opacity': 0,
'stroke-opacity': 0.7,
'hl-fill': 'yellow',
'hl-stroke': 'yellow',
'hl-stroke-width': 1,
'hl-opacity': 0.5,
'hl-stroke-opacity': 0.7,
'sel-fill': 'pink',
'sel-stroke': 'pink',
'sel-stroke-width': 1,
'sel-opacity': 0.7,
'sel-stroke-opacity': 0.9,
}
def say(msg, *av, **kw):
'Write a message to <message> stream.'
if av:
o = msg % av
elif kw:
o = msg % kw
else:
o = msg
message.write(o)
_last_N = 0
def tick(N, NN, n=4096):
'Show some life on screen'
global _last_N
if NN == 0:
_last_N = 0
say('\r\t' + ' ' * 40 + '\r')
elif N - _last_N >= n:
_last_N = N
say('\r\t%8d / %8d (%s) ...', N, NN, per100(NN, NN - N))
def writer(obj):
'Return a method to "write" to the <obj>'
if hasattr(obj, 'write'):
return obj.write
return obj.append
def flush(cache, output):
if cache:
wr = writer(output)
while cache :
wr(cache.pop(0))
def fetch_args(d):
c = ''
while d and d[0] in '0123456789. ,':
c, d = c + d[0], d[1:]
return map(float, c.replace(',', ' ').split())
def dM(entry):
'Returns start coordinates of a path'
d = get_d(entry).strip()
if not d:
return None
closed = d.upper().endswith('Z') # not in use so far...
if d.upper().startswith('M'):
d = d[1:].strip()
if d[0] in '0123456789.':
x, y = fetch_args(d)[:2]
else:
x, y = 0.0, 0.0
return x, y, closed
def add_css(call):
'Insert CSS <style> section into the output.'
call(('<style type="text/css">\n<![CDATA[\ncircle.boxId {\n'
+ '\tfill: %(fill)s;\n\topacity: %(opacity).2f;\n'
+ '\tstroke: %(stroke)s\n\tstroke-width: %(stroke-width)d;\n'
+ '\tstroke-opacity: %(stroke-opacity).2f;\n'
+ '}\ncircle.boxShowId {\n'
+ '\tfill: %(hl-fill)s;\n\topacity: %(hl-opacity).2f;\n'
+ '\tstroke: %(hl-stroke)s\n\tstroke-width: %(hl-stroke-width)d;\n'
+ '\tstroke-opacity: %(hl-stroke-opacity).2f;\n'
+ '}\ncircle.boxSelectId {\n'
+ '\tfill: %(sel-fill)s;\n\topacity: %(sel-opacity).2f;\n'
+ '\tstroke: %(sel-stroke)s\n\tstroke-width: %(sel-stroke-width)d;\n'
+ '\tstroke-opacity: %(sel-stroke-opacity).2f;\n'
+ '}\n]]>\n</style>\n') % CIRCLE_CONFIG)
def circle(center, call):
'Output the <circle/>'
cx, cy = center
call(('<circle cx="%(cx)d" cy="%(cy)d" r="%(r).0f" class="boxId" id="%(id)s" />')
% {'cx': cx, 'cy': cy, 'r': RADIUS, 'id': 'C%08x' % p_serial})
def label(point, call):
'Output a label as <text/>'
x, y = point
call('<text stroke="none" x="%.2f" y="%.2f" font-size="%d" fill="%s">P.%x</text>'
% (x, y, font_size, font_color, p_serial))
def group(cache, output):
'Output the IDentified <g> (plus <circle/> and <text/>, if any)'
writer(output)('<g id="P%08x">' % (p_serial,))
if add_circle:
circle(cache_avg(cache), writer(output))
if label_it:
label(dM(cache[0])[:2], writer(output))
flush(cache, output)
writer(output)('</g>\n')
p_serial = 0
def make_group(cache, output):
'Output IDentified group, if any cached'
global p_serial
if not cache:
return
if len(cache) == 1:
return flush(cache, output)
if p_serial == 0:
writer(output)('<!-- Radius=%f -->\n' % (RADIUS,))
if add_circle:
add_css(writer(output))
group(cache, output)
p_serial += 1
def in_circle(x, y, cx, cy, r):
'Weather the <x,y> point is in <cx,cy,r> circle?'
dx, dy = x - cx, y - cy
return (dx * dx + dy * dy) <= (r * r)
def cache_rms(cache):
'root mean square'
cx, cy, N = 1.0, 1.0, 0
for entry in cache:
x, y, c = dM(entry)
cx += x * x
cy += y * y
N += 1
N = float(N)
return sqrt(cx / N), sqrt(cy / N)
def cache_am(cache):
'arithmetic mean'
cx, cy, N = 0.0, 0.0, 0
for entry in cache:
x, y, c = dM(entry)
cx += x
cy += y
N += 1
N = float(N)
return cx / N, cy / N
cache_avg = cache_am
def same_place(entry, cache, delta):
'Weather <entry> starts in about the same place as the rest of the cache do?'
if not cache:
return False
x, y, c = dM(entry)
cx, cy = cache_avg(cache)
return in_circle(x, y, cx, cy, float(delta))
def parse_first_entry(cache):
'Cut off and parse the 1st entry of the <cache> list.'
if not cache:
return None
entry = source = cache.pop(0)
assert entry.startswith('<') and entry.endswith('>')
entry = entry[1:-1]
closed = entry.endswith('/')
if closed:
entry = entry[:-1]
tmp = entry.split()
tag = tmp.pop(0)
attributes = {}
keep = []
while tmp:
word = tmp.pop(0)
if '="' in word:
if keep:
attr_name, attr_value = map(lambda x: x.strip('"'), (' '.join(keep)).split('=', 1))
attributes[attr_name] = attributes.get(attr_name, [])
attributes[attr_name].append(attr_value)
while keep:
keep.pop(0)
keep.append(word)
if keep:
attr_name, attr_value = map(lambda x: x.strip('"'), (' '.join(keep)).split('=', 1))
attributes[attr_name] = attributes.get(attr_name, [])
attributes[attr_name].append(attr_value)
return (tag, closed, attributes)
def make_tag(tag, attributes, closed=False):
'Reverse to the parse_first_entry() call -- returns the re-built entry.'
entry = tag
for attribute_name in sorted(attributes):
for attribute_value in attributes[attribute_name]:
if attribute_value is not None:
entry += ' ' + attribute_name + '="' + attribute_value + '"'
if closed:
entry += ' /'
return '<' + entry + '>'
SVG_D_ABS_COMMANDS = 'MZLHVCSQTA'
SVG_D_REL_COMMANDS = 'mzlhvcsqta'
SVG_D_COMMANDS = SVG_D_ABS_COMMANDS + SVG_D_REL_COMMANDS
SVG_D_WSP = ' \t\r\n'
SVG_D_WSP_COMMA = SVG_D_WSP + ','
SVG_D_WSPC_OR_COMMAND = SVG_D_WSP_COMMA + SVG_D_COMMANDS
SVG_D_DIGITS = '0123456789'
SVG_D_NUMERICS = SVG_D_DIGITS + '.+-' # don't want to handle exponents here
def simple_parse_d(ds):
'Parse the d="" string (ds) into the list of tuples [(cmd, args...), ...]'
if not ds:
return None
if ds[0] in SVG_D_NUMERICS:
ds = 'M' + ds
assert ds[0] in SVG_D_COMMANDS
c, ds = ds[0], ds[1:]
dl, n, tmp = [], '', [c]
while ds:
c, ds = ds[0], ds[1:]
if c in SVG_D_NUMERICS:
if c == '.':
assert '.' not in n
elif c in '+-':
assert tmp[0] in SVG_D_REL_COMMANDS
if n :
tmp.append(n)
n = ''
n += c
continue
if c in SVG_D_WSPC_OR_COMMAND:
if n:
tmp.append(n)
n = ''
if c in SVG_D_COMMANDS:
dl.append(tuple(tmp))
while tmp:
tmp.pop()
tmp.append(c)
continue
assert False, 'simple_parse_d @ ' + `c`
tmp.append(n)
dl.append(tuple(tmp))
return dl
def simple_merge_d(dl):
'reverse simple_parse_d() result'
# join every tuple without space between command and 1st argument
# then join the list tightly
return ''.join(map(lambda cmd: ' '.join(cmd).replace(' ', '', 1), dl))
def optimize_d_HV(ds):
'Replace (L)s with (H)s and (V)s respectively, track "current coordinate".'
# eliminate moves to the last visited point first
if not optimize:
return ds
ds = subn(r'(L(?P<x>[0-9.]+) (?P<y>[0-9.]+))M(?P=x) (?P=y)',r'\1',ds)[0].strip()
dl, x0, y0, x, y, X, Y = [], None, None, None, None, None, None
for entry in simple_parse_d(ds):
# say('optimize_d_HV: entry=%s\n', `entry`)
cmd, xyz = entry[0], entry[1:]
if cmd == 'M':
while xyz: # count anonymous (M)oves
try:
X, Y = xyz[:2]
xyz = xyz[2:]
except:
say('\n\nBad coordinates:\nds=%s\nxyz=%s\n\n', `ds`, `xyz`)
raise
if x0 is None:
x0, y0 = X, Y
if x != X or y != Y:
# skip (M)oves to already taken position
dl.append(('M', X, Y))
x, y = X, Y
elif cmd == 'L':
while True:
X, Y = xyz[:2]
xyz = xyz[2:]
if x0 is None:
x0, y0 = x, y
if x0 is None:
x0, y0 = X, Y
if x != X or y != Y: # ignore (L)ines to current point
if x == X:
dl.append(('V', Y))
elif y == Y:
dl.append(('H', X))
else:
dl.append(('L', X, Y))
x, y = X, Y
if not xyz:
break
elif cmd == 'H':
while True:
X = xyz[:1]
xyz = xyz[1:]
x = X # just update coordinates
if not xyz:
break
dl.append(entry)
elif cmd == 'V':
while True:
Y = xyz[:1]
xyz = xyz[1:]
y = Y # just update coordinates
if not xyz:
break
dl.append(entry)
elif cmd == 'C':
while True:
_X1, _Y1, _X2, _Y2, X, Y = xyz[:6]
xyz = xyz[6:]
x, y = X, Y # just update coordinates
if not xyz:
break
dl.append(entry)
elif cmd in 'SQ':
while True:
_X2, _Y2, X, Y = xyz[:4]
xyz = xyz[4:]
x, y = X, Y # just update coordinates
if not xyz:
break
dl.append(entry)
elif cmd == 'T':
while True:
X, Y = xyz[:2]
xyz = xyz[2:]
x, y = X, Y # just update coordinates
if not xyz:
break
dl.append(entry)
elif cmd == 'A':
while True:
_RX, _RY, _XR, _LAF, _SF, X, Y = xyz[:7]
xyz = xyz[7:]
x, y = X, Y # just update coordinates
if not xyz:
break
dl.append(entry)
elif cmd == 'Z':
dl.append(('Z',))
x, y = x0, y0
x0 = y0 = None
while xyz:
_ = xyz[:1]
xyz = xyz[1:]
# x, y = X, Y # just update coordinates
else:
say('Unhandled command %s', `cmd`)
x = y = None
dl.append(entry)
return simple_merge_d(dl)
def optimize_d(d):
'Optimizes a list of d-entries'
if d and d[0]:
# return optimize and map(optimize_d_HV, d) or d
return map(optimize_d_HV, d)
def per100(x, y):
'returns string represtntation of (x-y)/x in percents'
pc = (100.0 * float(x - y)) / float(x)
return '%.1f%%' % (pc,)
def load(filename):
'Returns the entier data from a file.'
with open(filename) as fp:
return fp.read()
def load_and_normalize(filename):
'Performs initial phases of SVG processing: load, normalize, degroup'
say('\tphase #0 -- loading...\n')
data = load(filename)
N0 = len(data)
say('\t\t%d bytes loaded.\n', N0)
def say_done(NX, N0=N0, extra=''):
say('\t\tdone, %d bytes (%s saved)%s.\n', NX, per100(N0, NX), extra)
say('\tphase #1 -- normalizing spaces...\n')
data = (' '.join(data.split())).replace('> <', '><')
N1 = len(data)
say_done(N1)
say('\tphase #2 -- removing single path groups (<g><path/></g> becomes <path/>)...\n')
count = 0
cnt = -1
while cnt:
data, cnt = subn(r'<g>(<path[^>]+>)</g>', r'\1', data)
count += cnt
N2 = len(data)
say_done(N2, extra=', %d groups removed' % count)
return N0, data
def regroup(cache):
'Gather <path>es to <g>roups on their attributes.'
o = ''
g = None
save, count = None, 0
while cache:
tag, closed, attributes = parse_first_entry(cache)
d = {'d': optimize_d(attributes.pop('d', [None]))}
xattr = attributes.copy()
xattr.update(d)
entry = make_tag(tag, xattr, closed)
fixed = make_tag(tag, d, closed)
if g == attributes:
# at least 2 adjacent tags found...
if save:
# <g> and the 1st one
o += make_tag('g', g) + save[0]
save = None
o += fixed # next one
count += 1
continue
if g:
# end of group (single entry groups have no <g></g> braces)
o += save and save[1] or '</g>'
g = attributes # new group suspection
save = fixed, entry, tag, closed, attributes, d
count = 1
if save:
o += save[1]
if g and count > 1:
o += '</g>'
return o
def group_path_entries(data):
'Grab <path>es and try to <g>roups them.'
N, NN = 0, len(data)
cache = []
R = ''
tick(0, 0)
for line in data.replace('>', '>\n').splitlines():
N += len(line)
tick(N, NN)
if line.startswith('<path'):
cache.append(line)
else:
R += regroup(cache) + line
tick(0, 0)
R += regroup(cache)
return len(R), R
def byte_size_of_list(data):
'Returns len(''.join(data))'
return reduce(lambda x, y: (x and ((type(x) == int) and x or len(x)) or 0) + len(y), data)
def squeeze_svg(data):
'Reduce size of the resulting SVG by removing extraneous chars'
zerz = '0' * (8 - len('%x' % (p_serial,)))
data, cnt = subn(r' id="([PC])(' + zerz + r')([0-9a-f]+)"', # remove extraneous zeroes in IDs
r' id="\1\3"', data)
# say('\t\t%d zeroes removed\n', len(zerz) * cnt)
if join_paths:
cnt = -1
while cnt: # join adjacent <path>s
data, cnt = subn(r'<path d="([^"]+)" /><path d="([^"]+)" />',
r'<path d="\1 \2" />', data)
# one may want to get rid of <path>es like that one:
# <path d="M376.99 373.27H383.31V377.95H376.99V373.27Z" fill="black" fill-rule="nonzero" />
cnt = -1
while cnt:
data, cnt = subn(r'<path d="M[0-9. ,LHV]+Z" fill="black" fill-rule="nonzero" />',
r'', data) # black spills over labels we don't want to see
cnt = -1
while cnt: # ungroup single path groups: <g a=x><path/></g> becomes <path a=x/>
data, cnt = subn(r'<g id="(P[^"]+)"><path d="([^"]+)" /></g>', # just NOP with <circle>s
r'<path id="\1" d="\2" />', data)
return len(data), data
def perform_svg_path_grouping(data):
'Move the pathes that starts in nearly same place (within RADIUS) to an IDentified group.'
cache = []
out = []
N, NN = 0, len(data)
tick(0, 0)
for line in data.replace('>', '>\n').splitlines():
N += len(line)
tick(N, NN)
if line.startswith('<path'):
if not same_place(line, cache, RADIUS):
make_group(cache, out)
cache.append(line)
else:
if cache:
make_group(cache, out)
out.append(line)
flush(cache, out)
tick(0, 0)
out = ''.join(out)
return len(out), out
def perform_svg_path_optimization(fname):
say('Performing SVG optimization (%s -> %s)...\n', fname, output.name)
N0, data = load_and_normalize(fname) # phases 0, 1, 2
def say_done(NX, N0=N0):
say('\t\tdone, %d bytes (%s saved).\n', NX, per100(N0, NX))
say('\tphase #3 -- grouping pathes on attributes%s...\n',
optimize and ', optimizing d' or '')
N3, data = group_path_entries(data)
say_done(N3)
say('\tphase #4 -- grouping pathes using %s in radius of %.2f, %s circles...\n',
cache_avg.__doc__, RADIUS, add_circle and 'with' or 'without')
N4, data = perform_svg_path_grouping(data)
say_done(N4)
say('\tphase #5 -- fixing IDs%s (%d > P%x)...\n',
join_paths and ', joining pathes' or '', p_serial, p_serial - 1)
N5, data = squeeze_svg(data)
say_done(N5)
output.write(data)
return N0
def handle_file(fname):
'Convert this one file.'
p0 = p_serial
t0 = time()
N = perform_svg_path_optimization(fname)
pn = p_serial - p0
t1 = time()
say('%s groups found using radius of %.2f and %s in %d lines for %.2f seconds.\n',
pn or 'No', RADIUS, cache_avg.__doc__, N, t1 - t0)
def help_values():
hv = {
'r': RADIUS,
'a': cache_avg.__doc__,
'l': label_it,
'C': font_color,
'S': font_size,
'R': add_circle,
'O': optimize,
'J': join_paths,
'o': output.name,
}
hv.update(CIRCLE_CONFIG)
return hv
HELP = '''[options] <files...>
Options:
-h, --help -- this help
-o, --output=<F> -- write output to a file (=%(o)s)
-r, --radius=<R> -- radius to group pathes within (=%(r).2f)
-a, --average=<M> -- algo to calc averages ('A' or 'S') (=%(a)s)
-R, --add-circle -- add <circle> to any idenitified <g>roup (=%(R)s)
--fill=<X> -- fill="%(fill)s"
--stroke=<X> -- stroke="%(stroke)s"
--stroke-width=<X> -- stroke-width="%(stroke-width)s"
--opacity=<X> -- opacity="%(opacity)s"
--stroke-opacity=<X> -- stroke-opacity="%(stroke-opacity)s"
-O, --optimize -- do/dont run SVG path optimization (=%(O)s)
-J, --join-paths -- do/dont join adjacent <path>s (=%(J)s)
Debug tools:
-l, --label -- add labels to groups (=%(l)s)
-C, --font-color=<C> -- use this font color (=%(C)s)
-S, --font-size=<S> -- use this font size (=%(S)d)
'''
def main():
try:
opts, args = getopt.getopt(
sys.argv[1:],
'hr:a:lS:C:ROJo:',
('help',
'radius=',
'average=',
'label',
'font-size=',
'font-color=',
'add-circle',
'fill=',
'stroke=',
'stroke-width=',
'opacity=',
'stroke-opacity=',
'optimize',
'join-paths',
'output=',
)
)
except getopt.error, why:
sys.stderr.write(`why`)
return 1
else:
for o, v in opts:
if o in ('-h', '--help'):
print sys.argv[0], HELP % help_values()
return 0
elif o in ('-o', '--output'):
global output
output = (v == '-') and sys.stdout or open(v, 'w')
elif o in ('-O', '--no-optimize'):
global optimize
optimize = not optimize
elif o in ('-S', '--font-size'):
global font_size
font_size = int(v)
elif o in ('-C', '--font-color'):
global font_color
font_color = v
elif o in ('-R', '--add-circle'):
global add_circle
add_circle = not add_circle
elif o in ('-l', '--label'):
global label_it
label_it = not label_it
elif o in ('-a', '--average'):
global cache_avg
if v.upper()[0] in ('A', 'M'):
cache_avg = cache_am
elif v.upper()[0] in ('R', 'S', 'Q'):
cache_avg = cache_rms
else:
sys.stderr.write('I dunno how to use "%s" method.\n' % (v,))
return 1
elif o in ('-r', '--radius'):
global RADIUS
RADIUS=float(v)
elif o in ('-J', '--join-paths'):
global join_paths
join_paths = not join_paths
elif o == '--fill':
CIRCLE_CONFIG['fill'] = v
elif o == '--stroke':
CIRCLE_CONFIG['stroke'] = v
elif o == '--stroke-width':
CIRCLE_CONFIG['stroke-width'] = int(v)
elif o == '--opacity':
CIRCLE_CONFIG['opacity'] = float(v)
elif o == '--stroke-opacity':
CIRCLE_CONFIG['stroke-opacity'] = float(v)
if label_it:
sys.stderr.write('Label with font size of %d and color of "%s".\n' % (font_size, font_color))
if not args:
print sys.argv[0], HELP % help_values()
return 1
for arg in args:
handle_file(arg)
return 0
if __name__ == '__main__':
sys.exit(main())
# EOF #
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment