#!/usr/bin/env python
#
# Copyright (C) 2010 Aurelio A. Heckert, aurium (a) gmail dot com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
import subprocess
import os
import sys
import tempfile
from lxml import etree
import inkex
from inkex.localization import inkex_gettext as _
from webslicer_effect import WebSlicerMixin, is_empty
class Export(WebSlicerMixin, inkex.OutputExtension):
def add_arguments(self, pars):
pars.add_argument("--tab")
pars.add_argument("--dir")
pars.add_argument("--create-dir", type=inkex.Boolean, dest="create_dir")
pars.add_argument("--with-code", type=inkex.Boolean, dest="with_code")
svgNS = "{http://www.w3.org/2000/svg}"
def validate_inputs(self):
# The user must supply a directory to export:
if is_empty(self.options.dir):
raise inkex.AbortExtension(
_("You must give a directory to export the slices.")
)
# No directory separator at the path end:
if self.options.dir[-1] == "/" or self.options.dir[-1] == "\\":
self.options.dir = self.options.dir[0:-1]
# Test if the directory exists:
if not os.path.exists(self.options.dir):
if self.options.create_dir:
# Try to create it:
try:
os.makedirs(self.options.dir)
except Exception as e:
raise inkex.AbortExtension(
_("Can't create '{}': {}.".format(self.options.dir, e))
)
else:
raise inkex.AbortExtension(
_("Dir doesn't exist '{}'.".format(self.options.dir))
)
# Check whether slicer layer exists (bug #1198826)
slicer_layer = self.get_slicer_layer()
if slicer_layer is None:
raise inkex.AbortExtension(_("No slicer layer found."))
else:
self.unique_html_id(slicer_layer)
return None
def get_cmd_output(self, cmd):
try:
pipe = subprocess.Popen(cmd)
except FileNotFoundError:
return 1, ""
stdout, _ = pipe.communicate()
sts = pipe.returncode
if sts is None:
sts = 0
if stdout is not None and stdout[-1:] == "\n":
stdout = stdout[:-1]
return sts, stdout
_html_ids = []
def unique_html_id(self, el):
for child in el.getchildren():
if child.tag in [
self.svgNS + "rect",
self.svgNS + "path",
self.svgNS + "circle",
self.svgNS + "g",
]:
conf = self.get_el_conf(child)
if conf["html-id"] in self._html_ids:
inkex.errormsg(
'You have more than one element with "{}" html-id.'.format(
conf["html-id"]
)
)
n = 2
while (conf["html-id"] + "-" + str(n)) in self._html_ids:
n += 1
conf["html-id"] += "-" + str(n)
self._html_ids.append(conf["html-id"])
self.save_conf(conf, child)
self.unique_html_id(child)
def test_if_has_imagemagick(self):
(status, output) = self.get_cmd_output("convert --version")
self.has_magick = status == 0 and "ImageMagick" in output
def save(self, stream):
self.test_if_has_imagemagick()
error = self.validate_inputs()
if error:
return error
# Register the basic CSS code:
self.reg_css("body", "text-align", "center")
# Create the temporary SVG with invisible Slicer layer to export image pieces
self.create_the_temporary_svg()
# Start what we really want!
self.export_chids_of(self.get_slicer_layer())
# Write the HTML and CSS files if asked for:
if self.options.with_code:
self.make_html_file()
self.make_css_file()
# Delete the temporary SVG with invisible Slicer layer
self.delete_the_temporary_svg()
# TODO: prevent inkex to return svg code to update Inkscape
def make_html_file(self):
f = open(os.path.join(self.options.dir, "layout.html"), "w")
f.write(
"\n
\n"
+ " Web Layout Testing\n"
+ ' \n"
+ "\n\n"
+ self.html_code()
+ ' \n'
+ " This HTML code is not done to the web.
\n"
+ " The automatic HTML and CSS code are only a helper."
+ "
\n\n"
)
f.close()
def make_css_file(self):
f = open(os.path.join(self.options.dir, "style.css"), "w")
f.write(
"/*\n"
+ "** This CSS code is not done to the web.\n"
+ "** The automatic HTML and CSS code are only a helper.\n"
+ "*/\n"
+ self.css_code()
)
f.close()
def create_the_temporary_svg(self):
(self.tmp_svg_ref, self.tmp_svg) = tempfile.mkstemp(".svg")
layer = self.get_slicer_layer()
current_style = layer.style
layer.style = "display:none"
self.document.write(self.tmp_svg)
layer.style = current_style
def delete_the_temporary_svg(self):
try:
os.close(self.tmp_svg_ref)
os.remove(self.tmp_svg)
except (IOError, OSError, PermissionError):
pass
noid_element_count = 0
def get_el_conf(self, el):
desc = el.find(self.svgNS + "desc")
conf = {}
if desc is None:
desc = etree.SubElement(el, "desc")
if desc.text is None:
desc.text = ""
for line in desc.text.split("\n"):
if line.find(":") > 0:
line = line.split(":")
conf[line[0].strip()] = line[1].strip()
if not "html-id" in conf:
if el == self.get_slicer_layer():
return {"html-id": "#body#"}
else:
self.noid_element_count += 1
conf["html-id"] = "element-" + str(self.noid_element_count)
desc.text += "\nhtml-id:" + conf["html-id"]
return conf
def save_conf(self, conf, el):
desc = el.find("{http://www.w3.org/2000/svg}desc")
if desc is not None:
conf_a = []
for k in conf:
conf_a.append(k + " : " + conf[k])
desc.text = "\n".join(conf_a)
def export_chids_of(self, parent):
parent_id = self.get_el_conf(parent)["html-id"]
for el in parent.getchildren():
el_conf = self.get_el_conf(el)
if el.tag == self.svgNS + "g":
if self.options.with_code:
self.register_group_code(el, el_conf)
else:
self.export_chids_of(el)
if el.tag in [
self.svgNS + "rect",
self.svgNS + "path",
self.svgNS + "circle",
]:
if self.options.with_code:
self.register_unity_code(el, el_conf, parent_id)
self.export_img(el, el_conf)
def register_group_code(self, group, conf):
self.reg_html("div", group)
selec = "#" + conf["html-id"]
self.reg_css(selec, "position", "absolute")
geometry = self.get_relative_el_geometry(group)
self.reg_css(selec, "top", str(int(geometry["y"])) + "px")
self.reg_css(selec, "left", str(int(geometry["x"])) + "px")
self.reg_css(selec, "width", str(int(geometry["w"])) + "px")
self.reg_css(selec, "height", str(int(geometry["h"])) + "px")
self.export_chids_of(group)
def __validate_slice_conf(self, conf):
if not "layout-disposition" in conf:
conf["layout-disposition"] = "bg-el-norepeat"
if not "layout-position-anchor" in conf:
conf["layout-position-anchor"] = "mc"
return conf
def register_unity_code(self, el, conf, parent_id):
conf = self.__validate_slice_conf(conf)
css_selector = "#" + conf["html-id"]
bg_repeat = "no-repeat"
img_name = self.img_name(el, conf)
if conf["layout-disposition"][0:2] == "bg":
if conf["layout-disposition"][0:9] == "bg-parent":
if parent_id == "#body#":
css_selector = "body"
else:
css_selector = "#" + parent_id
if conf["layout-disposition"] == "bg-parent-repeat":
bg_repeat = "repeat"
if conf["layout-disposition"] == "bg-parent-repeat-x":
bg_repeat = "repeat-x"
if conf["layout-disposition"] == "bg-parent-repeat-y":
bg_repeat = "repeat-y"
lay_anchor = conf["layout-position-anchor"]
if lay_anchor == "tl":
lay_anchor = "top left"
if lay_anchor == "tc":
lay_anchor = "top center"
if lay_anchor == "tr":
lay_anchor = "top right"
if lay_anchor == "ml":
lay_anchor = "middle left"
if lay_anchor == "mc":
lay_anchor = "middle center"
if lay_anchor == "mr":
lay_anchor = "middle right"
if lay_anchor == "bl":
lay_anchor = "bottom left"
if lay_anchor == "bc":
lay_anchor = "bottom center"
if lay_anchor == "br":
lay_anchor = "bottom right"
self.reg_css(
css_selector,
"background",
'url("{}") {} {}'.format(img_name, bg_repeat, lay_anchor),
)
else: # conf['layout-disposition'][0:9] == 'bg-el...'
self.reg_html("div", el)
self.reg_css(
css_selector,
"background",
'url("{}") {}'.format(img_name, "no-repeat"),
)
self.reg_css(css_selector, "position", "absolute")
geo = self.get_relative_el_geometry(el, True)
self.reg_css(css_selector, "top", geo["y"])
self.reg_css(css_selector, "left", geo["x"])
self.reg_css(css_selector, "width", geo["w"])
self.reg_css(css_selector, "height", geo["h"])
else: # conf['layout-disposition'] == 'img...'
self.reg_html("img", el)
if conf["layout-disposition"] == "img-pos":
self.reg_css(css_selector, "position", "absolute")
geo = self.get_relative_el_geometry(el)
self.reg_css(css_selector, "left", str(geo["x"]) + "px")
self.reg_css(css_selector, "top", str(geo["y"]) + "px")
if conf["layout-disposition"] == "img-float-left":
self.reg_css(css_selector, "float", "right")
if conf["layout-disposition"] == "img-float-right":
self.reg_css(css_selector, "float", "right")
el_geo = {}
def register_all_els_geometry(self):
ink_cmm = "inkscape --query-all " + self.tmp_svg
(status, output) = self.get_cmd_output(ink_cmm)
self.el_geo = {}
if status == 0:
for el in output.split("\n"):
el = el.split(",")
if len(el) == 5:
self.el_geo[el[0]] = {
"x": float(el[1]),
"y": float(el[2]),
"w": float(el[3]),
"h": float(el[4]),
}
doc_w = self.svg.unittouu(self.document.getroot().get("width"))
doc_h = self.svg.unittouu(self.document.getroot().get("height"))
self.el_geo["webslicer-layer"] = {"x": 0, "y": 0, "w": doc_w, "h": doc_h}
def get_relative_el_geometry(self, el, value_to_css=False):
# This method return a dictionary with x, y, w and h keys.
# All values are float, if value_to_css is False, otherwise
# that is a string ended with "px". The x and y values are
# relative to parent position.
if not self.el_geo:
self.register_all_els_geometry()
parent = el.getparent()
geometry = self.el_geo[el.attrib["id"]]
geometry["x"] -= self.el_geo[parent.attrib["id"]]["x"]
geometry["y"] -= self.el_geo[parent.attrib["id"]]["y"]
if value_to_css:
for k in geometry:
geometry[k] = str(int(geometry[k])) + "px"
return geometry
def img_name(self, el, conf):
return el.attrib["id"] + "." + conf["format"]
def export_img(self, el, conf):
if not self.has_magick:
inkex.errormsg(_("You must install the ImageMagick to get JPG and GIF."))
conf["format"] = "png"
img_name = os.path.join(self.options.dir, self.img_name(el, conf))
img_name_png = img_name
if conf["format"] != "png":
img_name_png = img_name + ".png"
opts = ""
if "bg-color" in conf:
opts += ' -b "' + conf["bg-color"] + '" -y 1'
if "dpi" in conf:
opts += " -d " + conf["dpi"]
if "dimension" in conf:
dim = conf["dimension"].split("x")
opts += " -w " + dim[0] + " -h " + dim[1]
(status, output) = self.get_cmd_output(
'inkscape {} -i "{}" -o "{}" "{}"'.format(
opts, el.attrib["id"], img_name_png, self.tmp_svg
)
)
if conf["format"] != "png":
opts = ""
if conf["format"] == "jpg":
opts += " -quality " + str(conf["quality"])
if conf["format"] == "gif":
if conf["gif-type"] == "grayscale":
opts += " -type Grayscale"
else:
opts += " -type Palette"
if conf["palette-size"] < 256:
opts += " -colors " + str(conf["palette-size"])
(status, output) = self.get_cmd_output(
'convert "{}" {} "{}"'.format(img_name_png, opts, img_name)
)
if status != 0:
inkex.errormsg("Upss... ImageMagick error: " + output)
os.remove(img_name_png)
_html = {}
def reg_html(self, el_tag, el):
parent = el.getparent()
parent_id = self.get_el_conf(parent)["html-id"]
if parent == self.get_slicer_layer():
parent_id = "body"
conf = self.get_el_conf(el)
el_id = conf["html-id"]
if "html-class" in conf:
el_class = conf["html-class"]
else:
el_class = ""
if not parent_id in self._html:
self._html[parent_id] = []
self._html[parent_id].append({"tag": el_tag, "id": el_id, "class": el_class})
def html_code(self, parent="body", ident=" "):
# inkex.errormsg( self._html )
if not parent in self._html:
return ""
code = ""
for el in self._html[parent]:
child_code = self.html_code(el["id"], ident + " ")
tag_class = ""
if el["class"] != "":
tag_class = ' class="' + el["class"] + '"'
if el["tag"] == "img":
code += (
ident
+ '\n'
)
else:
code += (
ident
+ "<"
+ el["tag"]
+ ' id="'
+ el["id"]
+ '"'
+ tag_class
+ ">\n"
)
if child_code:
code += child_code
else:
code += ident + " Element " + el["id"] + "\n"
code += ident + "" + el["tag"] + '>\n'
return code
_css = []
def reg_css(self, selector, att, val):
pos = i = -1
for s in self._css:
i += 1
if s["selector"] == selector:
pos = i
if pos == -1:
pos = i + 1
self._css.append({"selector": selector, "atts": {}})
if not att in self._css[pos]["atts"]:
self._css[pos]["atts"][att] = []
self._css[pos]["atts"][att].append(val)
def css_code(self):
code = ""
for s in self._css:
code += "\n" + s["selector"] + " {\n"
for att in s["atts"]:
val = s["atts"][att]
if att == "background" and len(val) > 1:
code += " /* the next attribute needs a CSS3 enabled browser */\n"
code += " " + att + ": " + (", ".join(val)) + ";\n"
code += "}\n"
return code
if __name__ == "__main__":
Export().run()