Readable source
#!/usr/bin/env python3
"""Set SVG display dimensions while preserving its drawing viewBox. Original stays untouched."""
import argparse,re,xml.etree.ElementTree as ET
from pathlib import Path
def pixels(s):
m=re.fullmatch(r'\s*([0-9]+(?:\.[0-9]+)?)\s*(px|mm|cm|in|pt|pc)?\s*',s or '')
if not m:raise ValueError('Cannot infer a viewBox from relative or missing dimensions.')
return float(m[1])*{'px':1,'mm':96/25.4,'cm':96/2.54,'in':96,'pt':96/72,'pc':16}[m[2] or 'px']
def normalise(source,width,height):
if '<!doctype' in source.lower() or '<!entity' in source.lower():raise ValueError('DOCTYPE and entities are not accepted.')
root=ET.fromstring(source)
if root.tag.split('}')[-1]!='svg':raise ValueError('Root is not SVG.')
box=root.get('viewBox')
if not box:
w,h=pixels(root.get('width')),pixels(root.get('height'))
if w<=0 or h<=0:raise ValueError('Dimensions must be positive.')
root.set('viewBox',f'0 0 {w:g} {h:g}')
else:
values=re.split('[ ,]+',box.strip())
if len(values)!=4 or float(values[2])<=0 or float(values[3])<=0:raise ValueError('Invalid viewBox.')
root.set('width',f'{width:g}');root.set('height',f'{height:g}');root.set('preserveAspectRatio','xMidYMid meet')
ET.register_namespace('','http://www.w3.org/2000/svg');ET.register_namespace('xlink','http://www.w3.org/1999/xlink')
return ET.tostring(root,encoding='unicode')
def main():
p=argparse.ArgumentParser(description=__doc__);p.add_argument('input',type=Path);p.add_argument('output',type=Path);p.add_argument('--width',type=float,required=True);p.add_argument('--height',type=float,required=True);a=p.parse_args()
if not 0<a.width<=100000 or not 0<a.height<=100000:p.error('Dimensions must be positive and at most 100000 px.')
try:
if a.input.resolve()==a.output.resolve():raise ValueError('Output must differ from input.')
if a.output.exists():raise ValueError('Output exists. Choose a new filename.')
if a.input.stat().st_size>5*1024*1024:raise ValueError('File exceeds 5 MB.')
a.output.write_text(normalise(a.input.read_text(encoding='utf-8'),a.width,a.height),encoding='utf-8')
except (ValueError,OSError,ET.ParseError) as e:p.error(str(e))
if __name__=='__main__':main()