Skip to content

Commit

Permalink
Merge pull request #61 from heyzec/svg2png
Browse files Browse the repository at this point in the history
Partial solution to remove ImageMagick dependency (#46)
  • Loading branch information
mateosss authored Apr 4, 2021
2 parents 3d83c23 + c68ea0c commit 7155e2e
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 19 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
43 changes: 26 additions & 17 deletions matter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/python3

# Standard library modules
import sys
import os
import re
Expand All @@ -11,6 +12,9 @@
from subprocess import run, check_call, PIPE
from shutil import which, rmtree, copytree, copyfile

# Local Matter modules
from svg2png import inkscape_convert_svg2png, magick_convert_svg2png

# Configuration constants

MIN_PYTHON_VERSION = (3, 6) # Mainly for f-strings
Expand Down Expand Up @@ -234,29 +238,34 @@ 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
else parse_color(user_args.foreground)
)
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)

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():
Expand Down Expand Up @@ -551,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():
Expand Down Expand Up @@ -605,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():
Expand All @@ -628,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."
)


Expand Down
108 changes: 108 additions & 0 deletions svg2png.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env python3

import os
import re
import subprocess
import xml.etree.ElementTree as ET
import xml.dom.minidom

def inkscape_convert_svg2png(color, src_path, dst_path):
SVG_URI = 'http://www.w3.org/2000/svg'
FRAC = 0.7
TEMPFILE = 'temp.svg'

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_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 children of <svg> while changing their 'style' attributes
elements = root.findall('svg:*', namespaces={'svg': SVG_URI})
group = ET.SubElement(root, 'g')
for element in elements:
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)
# 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})"

xml_string = ET.tostring(root).decode()
xml_string = prettify(xml_string)
with open(TEMPFILE, 'w') as f:
f.write(xml_string)


# 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

def magick_convert_svg2png(color, src_path, dst_path):
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(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':
svg2png('#FFFFFF', f'icons/{basename}.svg', f'icons/{basename}.png')
2 changes: 1 addition & 1 deletion theme.txt.template
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 7155e2e

Please sign in to comment.