#! /usr/bin/python ''' Copyright (C) 2017 Artem Synytsyn a.synytsyn@gmail.com #TODO: Code cleaning and refactoring 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 inkex import sys from lxml import etree from math import * import re from typing import Any import wikipedia import openpyxl import math, decimal, datetime dec = decimal.Decimal # ['NAMESPACE', 'PARSER', 'TAG', 'WRAPPED_ATTRS', '__abstractmethods__', '__bool__', # '__class__', '__contains__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__', # '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', # '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__init__', # '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', # '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', # '__setitem__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', # '_abc_impl', '_ancestors', '_init', '_is_visible', 'add', 'add_unit', 'addnext', # 'addprevious', 'ancestors', 'append', 'attrib', 'backlinks', 'bake_transforms_recursively', # 'base', 'bounding_box', 'cascaded_style', 'clear', 'clip', 'composed_style', # 'composed_transform', 'copy', 'cssselect', 'delete', 'desc', 'descendants', # 'description', 'duplicate', 'effective_style', 'eid', 'extend', 'find', 'findall', # 'findone', 'findtext', 'get', 'get_id', 'get_line_height_uu', 'get_or_create', # 'get_path', 'getchildren', 'getiterator', 'getnext', 'getparent', 'getprevious', # 'getroottree', 'groupmode', 'href', 'index', 'insert', 'is_class_element', # 'is_sensitive', 'is_visible', 'items', 'iter', 'iterancestors', 'iterchildren', # 'iterdescendants', 'iterfind', 'itersiblings', 'itertext', 'keys', 'label', # 'makeelement', 'new', 'nsmap', 'path', 'pop', 'prefix', 'presentation_style', # 'remove', 'remove_all', 'replace', 'replace_with', 'root', 'set', 'set_id', # 'set_path', 'set_random_id', 'set_random_ids', 'set_sensitive', 'shape_box', # 'sourceline', 'specified_style', 'tag', 'tag_name', 'tail', 'text', 'title', # 'to_dimensional', 'to_dimensionless', 'to_path_element', 'tostring', 'typename', # 'unit', 'unit_to_viewport', 'unittouu', 'update', 'uutounit', 'values', # 'viewport_to_unit', 'wrapped_attrs', 'wrapped_props', 'xml_path', 'xpath'] # ___ _ _ _ _ _ # |_ _|_ __ | | _____ ___ __ _ _ __ ___| | | |_ __ _ __(_)_ __ | |_ # | || '_ \| |/ / __|/ __/ _` | '_ \ / _ \ | | | '_ \| '__| | '_ \| __| # | || | | | <\__ \ (_| (_| | |_) | __/ |_| | |_) | | | | | | | |_ # |___|_| |_|_|\_\___/\___\__,_| .__/ \___|\___/| .__/|_| |_|_| |_|\__| # |_| |_| class _Spacing(): size = 0. padding = 0. margin_top = 0. margin_bottom = 0. margin_left = 0. margin_right = 0. class InkscapeUprint(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) # General settings self.arg_parser.add_argument("--x", type=float, dest="x", default=0.0, help="Center X") self.arg_parser.add_argument("--y", type=float, dest="y", default=0.0, help="Center Y") self.arg_parser.add_argument("-u", "--units", type=str, dest="units", default="mm", help="units to measure size of knob") # Dummy self.arg_parser.add_argument("--tab") # # Label settings self.arg_parser.add_argument("--subst_file", type=str, dest="subst_file", default='', help="xlsx file to load substitutions") # self.arg_parser.add_argument("--font_family", # type=str, # dest="font_family", default="Apple Garamond", # help="Font family to use for days and labels") # self.arg_parser.add_argument("--year", # type=int, # dest="year", default=1970, # help="Calendar Year") # self.arg_parser.add_argument("-m", "--month", # type=str, # dest="month", default="Gennaio", # help="month to render") # self.arg_parser.add_argument("--dayfrom", # type=int, # dest="dayfrom", default=1, # help="Range to render day from") # self.arg_parser.add_argument("--dayto", # type=int, # dest="dayto", default=31, # help="Range to render day to") # self.arg_parser.add_argument("--day_text_size", # type=float, # dest="day_text_size", default=12, # help="Text size of day") # self.arg_parser.add_argument("--saint_text_size", # type=float, # dest="saint_text_size", default=8, # help="Text size of saint") # spacing # self.__elements = ['day', 'wday', 'saint', 'moon'] self.__element_spacing = { 'size' : 0, 'padding' : 0, 'margin-top' : 5, 'margin-bottom' : 5, 'margin-left' : 5, 'margin-right' : 5 } self.__page_spacing = { 'size' : 0, 'padding' : 0, 'margin-top' : 5, 'margin-bottom' : 5, 'margin-left' : 5, 'margin-right' : 5 } # for el in self.__elements: # for sk in self.__spacing.keys(): # sk = sk.replace('-','_') # self.arg_parser.add_argument("--" + el + "_" + sk, # type=float, # dest=el + "_" + sk, # help="Spacing " + el + " " + sk) self.pages = [] def svg_traverse_node(self, node, tag=None): def _tag(tag): return re.sub(r'\{.*?\}', r'', tag) l = [] if isinstance(node, etree._Element): if tag is None or _tag(node.tag) == tag: l.append(node) for child in node.getchildren(): l.extend(self.svg_traverse_node(child)) elif isinstance(node, etree._Comment): pass elif isinstance(node, list): for child in node: l.extend(self.svg_traverse_node(child)) return l def replace_text(self, selected, substs): import re for node in selected: tspans = [ n for n in self.svg_traverse_node(node, tag='tspan')] for k,v in substs.items(): patt = re.compile(r'\{\{' + re.escape(k) + r'\}\}') for t in tspans: if t.text is None: continue t.text = patt.sub(v, t.text) multilines = t.text.split('\n') if len(multilines) > 1: t.text = multilines[0] for i in range(1, len(multilines)): new_t = etree.Element('tspan', nsmap=t.nsmap) new_t.text = multilines[i] new_t.tail = t.tail new_t.attrib.update(t.attrib) # copy style p = t.getparent() p.insert(p.index(t) + 1, new_t) def duplicate_object(self, parent, selected): duplicated = [] for node in selected: duplicated.append(etree.Element(node.tag, node.attrib, nsmap=node.nsmap)) self.duplicate_object(duplicated[-1], node.getchildren()) duplicated[-1].text = node.text if 'transform' in node.attrib: duplicated[-1].set('transform', node.attrib['transform']) parent.append(duplicated[-1]) return duplicated def translate_object(self, obj, x, y): trans = 'translate(' + str(x) + ',' + str(y) + ')' for d in obj: if 'transform' in d.attrib: d.set('transform', d.get('transform') + ' ' + trans) else: d.set('transform', trans) def place_in_pages(self, elements = None): if elements is None: return first_page = self.svg.getElement("//inkscape:page") parent = first_page.getparent() pages = [ node for node in self.svg_traverse_node(parent, tag='inkscape:page')] rightmost_page = max(pages, key=lambda p: p.get('x')) # the reference page is always the first one pgbbox = {"width": first_page.get('width'), "height": first_page.get('height')} elbbox = self.svg.selected.bounding_box() # number of elements per page nx = int (( float(pgbbox['width']) - self.__page_spacing['margin-left'] - self.__page_spacing['margin-right'] ) / \ ( elbbox.width + self.__element_spacing['margin-left'] + self.__element_spacing['margin-right'] )) ny = int (( float(pgbbox['height']) - self.__page_spacing['margin-top'] - self.__page_spacing['margin-bottom'] ) / \ ( elbbox.height + self.__element_spacing['margin-top'] + self.__element_spacing['margin-bottom'] )) # remove model position correct_x = self.svg.selected.bounding_box().left correct_y = self.svg.selected.bounding_box().top # compute page position zero px = float(rightmost_page.get('x')) + float(rightmost_page.get('width')) py = float(rightmost_page.get('y')) # place elements for i, el in enumerate(elements): # create new page if needed if i % int(nx * ny) == 0: new_page = etree.Element('{http://www.inkscape.org/namespaces/inkscape}page', nsmap=first_page.nsmap) new_page.set('x', str(float(rightmost_page.get('x')) + float(rightmost_page.get('width')) + 20 )) new_page.set('y', str(float(rightmost_page.get('y')) + 0 )) new_page.set('width', str(float(first_page.get('width')))) new_page.set('height', str(float(first_page.get('height')))) parent.append(new_page) rightmost_page = new_page px = float(rightmost_page.get('x')) py = float(rightmost_page.get('y')) x = px + (i % nx) * ( elbbox.width + self.__element_spacing['margin-left'] + self.__element_spacing['margin-right'] ) + self.__page_spacing['margin-left'] y = py + (i // nx) * ( elbbox.height + self.__element_spacing['margin-top'] + self.__element_spacing['margin-bottom'] ) + self.__page_spacing['margin-top'] self.translate_object(el, x-correct_x, y-correct_y) # inkex.utils.errormsg("pages:" + repr(pages)) # for p in pages: # inkex.utils.errormsg("p:" + str(p)) pass def effect(self): # for el in self.options.element_spacing: # self.__element_spacing[el] = self.svg.unittouu(str(self.options.element_spacing[el]) + self.options.units) # for el in self.options.page_spacing: # self.__page_spacing[el] = self.svg.unittouu(str(self.options.page_spacing[el]) + self.options.units) parent = self.svg.get_current_layer() selected = self.svg.selected if selected: self.bbox = selected.bounding_box() self.x_offset = self.svg.unittouu(str(self.options.x) + self.options.units) self.y_offset = self.svg.unittouu(str(self.options.y) + self.options.units) else: inkex.utils.errormsg("Error - No object selected") # self.place_in_pages() return wb = openpyxl.load_workbook(self.options.subst_file) ws = wb.active headings = [cell.value for cell in ws[1]] substs = [] for row in ws.iter_rows(min_row=2, values_only=True): substs.append(dict(zip(headings, row))) new_objects = [] for i, subst in enumerate(substs): dup = self.duplicate_object(parent, selected) # self.translate_object(dup, (i+1) * (self.x_offset + self.bbox.width), self.y_offset) self.replace_text(dup, subst) new_objects.append(dup) # populating spacing with options self.place_in_pages(new_objects) if __name__ == '__main__': e = InkscapeUprint() e.run()