diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /share/extensions/webslicer_export.py | |
parent | Initial commit. (diff) | |
download | inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip |
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'share/extensions/webslicer_export.py')
-rwxr-xr-x | share/extensions/webslicer_export.py | 470 |
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..c166caf --- /dev/null +++ b/share/extensions/webslicer_export.py @@ -0,0 +1,470 @@ +#!/usr/bin/env python3 +# +# 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() |