From bf3c6f235056184178a1ec20a72b045a87144cb1 Mon Sep 17 00:00:00 2001 From: heyzec Date: Sat, 3 Apr 2021 01:19:22 +0800 Subject: [PATCH] Changed requirement for lxml to xml. (#46) Other changes: - Edited main script to prioritise using inkscape, but will fallback to ImageMagick's convert if inkscape not found. --- convert.py | 76 +++++++++++++++++++++++++++++++++++++++++++----------- matter.py | 38 +++++++++++++++------------ 2 files changed, 83 insertions(+), 31 deletions(-) diff --git a/convert.py b/convert.py index f1bcc04..526fb41 100755 --- a/convert.py +++ b/convert.py @@ -1,26 +1,69 @@ #!/usr/bin/env python3 + import os -import lxml.etree as ET +import re +import xml.etree.ElementTree as ET +import xml.dom.minidom -def inkscape_export_svg2png(color, src_path, dst_path): - NS = {"svg" : 'http://www.w3.org/2000/svg'} +def inkscape_convert_svg2png(color, src_path, dst_path): + SVG_URI = 'http://www.w3.org/2000/svg' FRAC = 0.7 TEMPFILE = 'temp.svg' - dom = ET.parse(src_path) + def parse_with_map(source): + """Parses file, returns a tuple containing the parsed ElementTree and a namespace map (dict). + + The ElementTree object returned is the same as if parsed using xml.etree.ElementTree.parse. For + some reason, ElementTree objects by the xml package will not provide a namespace map, unlike the + lxml package. + """ + + root = None + ns_map = [] + for event, node in ET.iterparse(source, events=['start-ns', 'start']): + if event == 'start-ns': + ns_map.append(node) + elif event == 'start': + if root is None: + root = node + return (ET.ElementTree(root), dict(ns_map)) + + def int_ignore_units(s): + return int(''.join(ch for ch in s if ch.isdigit())) + + def prettify(xml_string): + return '\n'.join(line for line in xml.dom.minidom.parseString(xml_string).toprettyxml( + indent=' ').splitlines() if not line.isspace() and line != '') + + # Fixes undefined namespace tags in output xml (not a big issue) + dom, ns_map = parse_with_map(src_path) + for key, value in ns_map.items(): + ET.register_namespace(key, value) + root = dom.getroot() - width, height = int(root.attrib['width']), int(root.attrib['height']) - interval = (1-FRAC)*width/2 + width, height = int_ignore_units(root.attrib['width']), int_ignore_units(root.attrib['height']) + width_gap, height_gap = (1-FRAC)*width/2, (1-FRAC)*height/2 - elements = root.findall('svg:path', namespaces=NS) - group = ET.SubElement(root, "g") + # Group all elements that are child of while changing their 'style' attributes + elements = root.findall('svg:*', namespaces={'svg': SVG_URI}) + group = ET.SubElement(root, 'g') for element in elements: - element.attrib['style'] = f'fill:{color};fill-opacity:1' + if any(map(element.tag.endswith, ['defs', 'metadata'])): # Don't group these special tags + continue + root.remove(element) + for child in element.iter(): # Changes all decendents (.iter will also include itself) + if 'style' in child.attrib: + child.attrib['style'] = re.sub(r'(?<=fill:)\S+?(?=;)', color, child.attrib['style']) + else: + child.attrib['style'] = f'fill:{color};' group.append(element) - group.attrib['transform'] = f"matrix({FRAC},0,0,{FRAC},{interval},{interval})" + # Shrink the svg by a factor of FRAC for padding around icon + group.attrib['transform'] = f"matrix({FRAC},0,0,{FRAC},{width_gap},{height_gap})" - et = ET.ElementTree(root) - et.write(TEMPFILE, pretty_print=True) + xml_string = ET.tostring(root).decode() + xml_string = prettify(xml_string) + with open(TEMPFILE, 'w') as f: + f.write(xml_string) cmd = f"inkscape --export-filename='{dst_path}' {TEMPFILE} -w 72 2>/dev/null" exit_code = os.system(cmd) @@ -28,16 +71,19 @@ def inkscape_export_svg2png(color, src_path, dst_path): return exit_code def magick_convert_svg2png(color, src_path, dst_path): - command = ( + cmd = ( r"convert -trim -scale 36x36 -extent 72x72 -gravity center " r"-define png:color-type=6 -background none -colorspace sRGB -channel RGB " rf"-threshold -1 -density 300 -fill \{color} +opaque none " rf"{src_path} {dst_path}" ) - return os.system(command) + return os.system(cmd) +# For demostration purposes if __name__ == '__main__': + svg2png = inkscape_convert_svg2png + # svg2png = magick_convert_svg2png for file in os.listdir('./icons'): basename, ext = os.path.splitext(file) if ext == '.svg': - inkscape_export_svg2png('#FFFFFF', f'icons/{basename}.svg', f'icons/INK_{basename}.png') + svg2png('#FFFFFF', f'icons/{basename}.svg', f'icons/{basename}.png') diff --git a/matter.py b/matter.py index ab5b587..7d3f89a 100755 --- a/matter.py +++ b/matter.py @@ -1,5 +1,6 @@ #!/usr/bin/python3 +# Standard library modules import sys import os import re @@ -11,7 +12,8 @@ from subprocess import run, check_call, PIPE from shutil import which, rmtree, copytree, copyfile -from convert import inkscape_export_svg2png +# Local Matter modules +from convert import inkscape_convert_svg2png, magick_convert_svg2png # Configuration constants @@ -236,13 +238,17 @@ def is_icon_downloaded(icon_name): def convert_icon_svg2png(icon_name): - if not has_command("convert"): - error( - "Stop. The `convert` command from imagemagick was not found", - "Also consider installing `inkscape` for the best results", - ) if not has_command("inkscape"): - warning("Resulting icons could look a bit off, consider installing inkscape") + if not has_command("convert"): + error( + "Stop. Both `inkscape` and `convert` command from imagemagick was not found", + "Consider installing `inkscape` for the best results", + ) + else: + command = "convert" + else: + command = "inkscape" + color = ( parse_color(user_args.iconcolor) if user_args.iconcolor @@ -250,16 +256,16 @@ def convert_icon_svg2png(icon_name): ) src_path = ICON_SVG_PATHF.format(icon_name) dst_path = ICON_PNG_PATHF.format(icon_name) - # command = ( - # r"convert -trim -scale 36x36 -extent 72x72 -gravity center " - # r"-define png:color-type=6 -background none -colorspace sRGB -channel RGB " - # rf"-threshold -1 -density 300 -fill \{color} +opaque none " - # rf"{src_path} {dst_path}" - # ) - # exit_code = sh(command) - exit_code = inkscape_export_svg2png(color, src_path, dst_path) + + if command == "convert": + warning("Resulting icons could look a bit off, consider installing inkscape") + converter = magick_convert_svg2png + elif command == "inkscape": + converter = inkscape_convert_svg2png + + exit_code = converter(color, src_path, dst_path) if exit_code != 0: - error("Stop. The convert command returned an error") + error(f"Stop. The `{command}` command returned an error") def get_available_fonts():