summaryrefslogtreecommitdiffstats
path: root/share/extensions/webslicer_export.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xshare/extensions/webslicer_export.py470
1 files changed, 470 insertions, 0 deletions
diff --git a/share/extensions/webslicer_export.py b/share/extensions/webslicer_export.py
new file mode 100755
index 0000000..0bc65d6
--- /dev/null
+++ b/share/extensions/webslicer_export.py
@@ -0,0 +1,470 @@
+#!/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(
+ "<html>\n<head>\n"
+ + " <title>Web Layout Testing</title>\n"
+ + ' <style type="text/css" media="screen">\n'
+ + ' @import url("style.css")\n'
+ + " </style>\n"
+ + "</head>\n<body>\n"
+ + self.html_code()
+ + ' <p style="position:absolute; bottom:10px">\n'
+ + " This HTML code is not done to the web. <br/>\n"
+ + " The automatic HTML and CSS code are only a helper."
+ + "</p>\n</body>\n</html>"
+ )
+ 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
+ + '<img id="'
+ + el["id"]
+ + '"'
+ + tag_class
+ + ' src="'
+ + self.img_name(el, self.get_el_conf(el))
+ + '"/>\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"] + '><!-- id="' + el["id"] + '" -->\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()