summaryrefslogtreecommitdiffstats
path: root/share/extensions/webslicer_export.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xshare/extensions/webslicer_export.py417
1 files changed, 417 insertions, 0 deletions
diff --git a/share/extensions/webslicer_export.py b/share/extensions/webslicer_export.py
new file mode 100755
index 0000000..2149177
--- /dev/null
+++ b/share/extensions/webslicer_export.py
@@ -0,0 +1,417 @@
+#!/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 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):
+ # This solution comes from Andrew Reedick <jr9445 at ATT.COM>
+ # http://mail.python.org/pipermail/python-win32/2008-January/006606.html
+ # This method replaces the commands.getstatusoutput() usage, with the
+ # hope to correct the windows exporting bug:
+ # https://bugs.launchpad.net/inkscape/+bug/563722
+ if sys.platform != "win32":
+ cmd = '{ ' + cmd + '; }'
+ pipe = os.popen(cmd + ' 2>&1', 'r')
+ text = pipe.read()
+ sts = pipe.close()
+ if sts is None:
+ sts = 0
+ if text[-1:] == '\n':
+ text = text[:-1]
+ return sts, text
+
+ _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):
+ (ref, self.tmp_svg) = tempfile.mkstemp('.svg')
+ layer = self.get_slicer_layer()
+ current_style = ('style' in layer.attrib) and layer.attrib['style'] or ''
+ layer.attrib['style'] = 'display:none'
+ self.document.write(self.tmp_svg)
+ layer.attrib['style'] = current_style
+
+ def delete_the_temporary_svg(self):
+ try:
+ 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 "{}" -e "{}" "{}"'.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()