From c8948cdfe1bdc2f08b7df2c4f51130ed7f4f1d18 Mon Sep 17 00:00:00 2001 From: heyzec Date: Wed, 31 Mar 2021 22:45:06 +0800 Subject: [PATCH 1/6] Partial solution to remove ImageMagick dependency The convert.py script will do the necessary scaling and changing of colors instead of ImageMagick. Inkscape is still required to do the final conversion of svg to png. Requires lxml python package but in theory, can modify it to just do the svg transformations using plain regex. --- convert.py | 43 +++++++++++++++++++++++++++++++++++++++++++ matter.py | 17 ++++++++++------- 2 files changed, 53 insertions(+), 7 deletions(-) create mode 100755 convert.py diff --git a/convert.py b/convert.py new file mode 100755 index 0000000..f1bcc04 --- /dev/null +++ b/convert.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +import os +import lxml.etree as ET + +def inkscape_export_svg2png(color, src_path, dst_path): + NS = {"svg" : 'http://www.w3.org/2000/svg'} + FRAC = 0.7 + TEMPFILE = 'temp.svg' + + dom = ET.parse(src_path) + root = dom.getroot() + width, height = int(root.attrib['width']), int(root.attrib['height']) + interval = (1-FRAC)*width/2 + + elements = root.findall('svg:path', namespaces=NS) + group = ET.SubElement(root, "g") + for element in elements: + element.attrib['style'] = f'fill:{color};fill-opacity:1' + group.append(element) + group.attrib['transform'] = f"matrix({FRAC},0,0,{FRAC},{interval},{interval})" + + et = ET.ElementTree(root) + et.write(TEMPFILE, pretty_print=True) + + cmd = f"inkscape --export-filename='{dst_path}' {TEMPFILE} -w 72 2>/dev/null" + exit_code = os.system(cmd) + os.remove(TEMPFILE) + return exit_code + +def magick_convert_svg2png(color, src_path, dst_path): + 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}" + ) + return os.system(command) + +if __name__ == '__main__': + 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') diff --git a/matter.py b/matter.py index 6c5b409..ab5b587 100755 --- a/matter.py +++ b/matter.py @@ -11,6 +11,8 @@ from subprocess import run, check_call, PIPE from shutil import which, rmtree, copytree, copyfile +from convert import inkscape_export_svg2png + # Configuration constants MIN_PYTHON_VERSION = (3, 6) # Mainly for f-strings @@ -248,13 +250,14 @@ 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) + # 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 exit_code != 0: error("Stop. The convert command returned an error") From bf3c6f235056184178a1ec20a72b045a87144cb1 Mon Sep 17 00:00:00 2001 From: heyzec Date: Sat, 3 Apr 2021 01:19:22 +0800 Subject: [PATCH 2/6] 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(): From cbb09f132aecd6094705b35717c209dedc1349d6 Mon Sep 17 00:00:00 2001 From: heyzec Date: Sat, 3 Apr 2021 01:27:32 +0800 Subject: [PATCH 3/6] Minor typos --- convert.py | 2 +- matter.py | 6 +++--- theme.txt.template | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/convert.py b/convert.py index 526fb41..3801305 100755 --- a/convert.py +++ b/convert.py @@ -44,7 +44,7 @@ def prettify(xml_string): 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 - # Group all elements that are child of while changing their 'style' attributes + # Group all elements that are children of while changing their 'style' attributes elements = root.findall('svg:*', namespaces={'svg': SVG_URI}) group = ET.SubElement(root, 'g') for element in elements: diff --git a/matter.py b/matter.py index 7d3f89a..f12364d 100755 --- a/matter.py +++ b/matter.py @@ -560,7 +560,7 @@ def do_uninstall(): clean_hookcheck() clean_install_dir() update_grub_cfg() - info(f"{THEME_NAME} succesfully uninstalled") + info(f"{THEME_NAME} successfully uninstalled") def do_list_grub_cfg_entries(): @@ -614,7 +614,7 @@ def do_patch_grub_cfg_icons(): with open(GRUB_CFG_PATH, "w") as f: f.write(new_grub_cfg) - info(f"{len(icons)} icons succesfully patched onto {GRUB_CFG_PATH}") + info(f"{len(icons)} icons successfully patched onto {GRUB_CFG_PATH}") def do_set_icons(): @@ -637,7 +637,7 @@ def do_set_icons(): f.write(new_grub_mkconfig) info( - f"{GRUB_MKCONFIG_PATH} succesfully patched, icons will now persist between grub updates." + f"{GRUB_MKCONFIG_PATH} successfully patched, icons will now persist between grub updates." ) diff --git a/theme.txt.template b/theme.txt.template index 1eb932d..fc0d6e4 100644 --- a/theme.txt.template +++ b/theme.txt.template @@ -2,7 +2,7 @@ # already parsed and the comments below could not make too much sense. # theme.txt.template represents a python string that gets format()-ed -# Note: for scaping literal curly braces, double them like so: {{ or }} +# Note: for escaping literal curly braces, double them like so: {{ or }} # {theme_name} Theme File # Designed for any resolution From c3b888fa604346a5d1aad512a87dbf545340b6c1 Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Sat, 3 Apr 2021 01:47:06 +0800 Subject: [PATCH 4/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6d9c64..b7b8c0d 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ the syntax: You can specify a background image with `--image/-im`, the supported image formats/extensions are PNG, JPG, JPEG, and TGA. This feature is considered *unfinished* because it does not yet work as well as it could *(see -[#57](https://github.com/mateosss/matter/pull/57))* +[#58](https://github.com/mateosss/matter/issues/58))* Here is an example of the syntax: From 9304b741dbaf7fdb7c8476bd131fa2142420c713 Mon Sep 17 00:00:00 2001 From: heyzec Date: Sat, 3 Apr 2021 02:00:22 +0800 Subject: [PATCH 5/6] Renamed for clarity --- matter.py | 2 +- convert.py => svg2png.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename convert.py => svg2png.py (100%) diff --git a/matter.py b/matter.py index f12364d..f8ec6b2 100755 --- a/matter.py +++ b/matter.py @@ -13,7 +13,7 @@ from shutil import which, rmtree, copytree, copyfile # Local Matter modules -from convert import inkscape_convert_svg2png, magick_convert_svg2png +from svg2png import inkscape_convert_svg2png, magick_convert_svg2png # Configuration constants diff --git a/convert.py b/svg2png.py similarity index 100% rename from convert.py rename to svg2png.py From c68ea0c2f8d0d0250100243239548164f8dcd660 Mon Sep 17 00:00:00 2001 From: heyzec Date: Sun, 4 Apr 2021 20:09:48 +0800 Subject: [PATCH 6/6] More fixes (mateosss#61) - Changed verbosity of inkscape command - Script now works with older versions of inkscape (Tested on 0.92.5) --- svg2png.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/svg2png.py b/svg2png.py index 3801305..25b8d85 100755 --- a/svg2png.py +++ b/svg2png.py @@ -2,6 +2,7 @@ import os import re +import subprocess import xml.etree.ElementTree as ET import xml.dom.minidom @@ -65,8 +66,26 @@ def 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) + + # Check inkscape version + version_string = subprocess.run('inkscape --version 2>/dev/null', + shell=True, capture_output=True).stdout.decode() + version = re.findall(r'(?<=Inkscape )[.\d]+', version_string)[0] + if version[0] == '1': + arguments = [f'--export-filename={dst_path}'] + elif version[0] == '0': + arguments = ['--without-gui', f'--export-png={dst_path}'] + arguments.extend([TEMPFILE, '-w', '72']) + + inkscape_proc = subprocess.Popen(['inkscape', *arguments], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + subprocess.run(['sed', 's/^/inkscape: /'], stdin=inkscape_proc.stdout) + inkscape_proc.wait() + exit_code = inkscape_proc.returncode + # Alternative - Using bash to pipe + # cmd = f"""/bin/bash -c 'inkscape {' '.join(arguments)} 2>&1 | sed "s/^/inkscape: /"; exit ${{PIPESTATUS[0]}}'""" + # exit_code = os.system(cmd) + os.remove(TEMPFILE) return exit_code