#!/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 + "\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()