diff --git a/README.md b/README.md index 5954e09..7966918 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,30 @@ # inkscape_uprint Inkscape plugin to create multiple clones of the same object with pattern substitution + +## Install + +copy the source content into your inkscape extension directory: + + ${HOME}/.config/inkscape/extensions/inkscape_uprint + +or equivalently get it using git clone: + + cd ${HOME}/.config/inkscape/extensions/ + git clone[https://gitea.mildstone.org/YFinGames/inkscape_uprint.git]() + +reload inkscape + +find the extension in **Extensions -> Renderg -> InkscapeUprint** + +## INSTRUCTIONS: + +1. Create any shape with text wherever you like; + place text elements in a frame as much as possible ( multiline substitution works in framed text only ) +2. place {{tags}} where you like, each tag is eventually searched in the first line of the xlsx file ( the header ) and a set of replicas of the selection will be generatend in a separate page, one for each line of the xlsx. Any keyword can be used as a tag and it will be substituted unless wrapped between double curly braces. +3. apply any transform to the shape lou like +4. group anything together but do not apply any transform to the group afterwards. If you need to ungroup make what you like ( translate, rotate, scale ) and then regroup everything together. The changes applied to the toplevel group are affecting the position of the shape after the placement on the output pages. +5. Select the group and apply InkscapeUprint. The parameters are: + - padding: padding space among placed shapes + - page margins: From the page to the page collection + - The size of the output pages is copied from the model one diff --git a/example.svg b/example.svg new file mode 100644 index 0000000..ab9c204 --- /dev/null +++ b/example.svg @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + InkscapeUprint + + + INSTRUCTIONS: + +1. Create any shape with text whwere you like, put text in a frame as much as possible ( multiline substitution works in framed text only ) + +2. place {{tags}} where you like in the text + +3. apply any transform to the shape + +4. group anything together but do not apply any transform to the group afterwards. If you need to ungroup make what you like ( translate, rotate, scale ) and then regroup everything together. The changes applied to the toplevel group are affecting the position of the shape after the placement on the output pages. + +5. Select the group and apply InkscapeUprint. The parameters are: + padding: padding space among placed shapes + page margins: From the page to the page collection + The size of the output pages is copied from the model one + + + + + + + {{nome}}{{cognome}} + {{ruolo}} + + + {{affiliazione}} + + + + + {{nome}}{{cognome}} + {{ruolo}} + + + {{affiliazione}} + + + diff --git a/example.xlsx b/example.xlsx index b070051..5fa1ff9 100644 Binary files a/example.xlsx and b/example.xlsx differ diff --git a/inkscape_uprint.inx b/inkscape_uprint.inx index 75b09b8..b4884b2 100644 --- a/inkscape_uprint.inx +++ b/inkscape_uprint.inx @@ -7,79 +7,35 @@ - 0 - 0 + + 2 px mm - - - 14 - 0 - 0 - 0 - 0 - 0 + + + 10 + 0 + 0 + 0 + 0 - - - - 9 - 0 - 0 - 0 - 0 - 0 - - - - 21 - 0 - 0 - 0 - 0 - 0 - - --> diff --git a/inkscape_uprint.py b/inkscape_uprint.py index a934ee0..647fae1 100755 --- a/inkscape_uprint.py +++ b/inkscape_uprint.py @@ -37,6 +37,7 @@ dec = decimal.Decimal + # ['NAMESPACE', 'PARSER', 'TAG', 'WRAPPED_ATTRS', '__abstractmethods__', '__bool__', # '__class__', '__contains__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__', # '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', @@ -71,13 +72,16 @@ dec = decimal.Decimal # |_| |_| -class _Spacing(): - size = 0. - padding = 0. - margin_top = 0. - margin_bottom = 0. - margin_left = 0. - margin_right = 0. +# 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): @@ -105,79 +109,54 @@ class InkscapeUprint(inkex.Effect): 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 + 'margin-top' : 0, + 'margin-bottom' : 0, + 'margin-left' : 0, + 'margin-right' : 0 } self.__page_spacing = { 'size' : 0, 'padding' : 0, - 'margin-top' : 5, - 'margin-bottom' : 5, - 'margin-left' : 5, - 'margin-right' : 5 + 'margin-top' : 0, + 'margin-bottom' : 0, + 'margin-left' : 0, + 'margin-right' : 0 } - # 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 = [] + for sk in self.__element_spacing.keys(): + sk = sk.replace('-','_') + self.arg_parser.add_argument("--element_" + sk, + type=float, + dest="element_" + sk, + help="Spacing element" + " " + sk) + for sk in self.__page_spacing.keys(): + sk = sk.replace('-','_') + self.arg_parser.add_argument("--page_" + sk, + type=float, + dest="page_" + sk, + help="Spacing page" + " " + sk) + - def svg_traverse_node(self, node, tag=None): - def _tag(tag): - return re.sub(r'\{.*?\}', r'', tag) + def svg_traverse_node(self, node, tag=None, exclude=None): + def _tag(t): + return re.sub(r'\{.*?\}', r'', t) l = [] if isinstance(node, etree._Element): - if tag is None or _tag(node.tag) == tag: + if (tag is None or _tag(node.tag) == tag) and (exclude is None or _tag(node.tag) != exclude): l.append(node) for child in node.getchildren(): - l.extend(self.svg_traverse_node(child)) + l.extend(self.svg_traverse_node(child, tag=tag, exclude=exclude)) elif isinstance(node, etree._Comment): pass elif isinstance(node, list): for child in node: - l.extend(self.svg_traverse_node(child)) + l.extend(self.svg_traverse_node(child, tag=tag, exclude=exclude)) return l def replace_text(self, selected, substs): @@ -189,31 +168,34 @@ class InkscapeUprint(inkex.Effect): 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) + # 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 = [] + import copy 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']) + new_node = node.duplicate() + duplicated.append(new_node) + # duplicated.append(etree.Element(node.tag, node.attrib, nsmap=node.nsmap)) + # # # inkex.utils.debug(' node.text = ' + str(type(node.text))) + # duplicated[-1].tail = node.tail + # duplicated[-1].text = node.text + # self.duplicate_object(duplicated[-1], node.getchildren()) parent.append(duplicated[-1]) return duplicated def translate_object(self, obj, x, y): - trans = 'translate(' + str(x) + ',' + str(y) + ')' + trans = 'translate(' + str(float(x)) + ',' + str(float(y)) + ')' for d in obj: if 'transform' in d.attrib: d.set('transform', d.get('transform') + ' ' + trans) else: d.set('transform', trans) @@ -222,68 +204,92 @@ class InkscapeUprint(inkex.Effect): 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')) - + if first_page is not None: + page_width = float(first_page.get('width')) + page_height = float(first_page.get('height')) + view = first_page.getparent() + else: + view = self.svg.getElement("//sodipodi:namedview") + if view is None: + inkex.utils.errormsg('No view found') + return + page_width = self.svg.get_page_bbox().width + page_height = self.svg.get_page_bbox().height + + pages = [ node for node in self.svg_traverse_node(view, tag='inkscape:page')] + # 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'] )) + pgbbox = {"width": page_width, "height": page_height} + elbbox = self.bbox + nx = int (( float(pgbbox['width']) - self.__page_spacing['margin-left'] - self.__page_spacing['margin-right'] - self.__element_spacing['padding']) / \ + ( elbbox.width + self.__element_spacing['padding'] )) + ny = int (( float(pgbbox['height']) - self.__page_spacing['margin-top'] - self.__page_spacing['margin-bottom'] - self.__element_spacing['padding'] ) / \ + ( elbbox.height + self.__element_spacing['padding'] )) # remove model position - correct_x = self.svg.selected.bounding_box().left - correct_y = self.svg.selected.bounding_box().top + correct_x = self.bbox.left + correct_y = self.bbox.top # compute page position zero - px = float(rightmost_page.get('x')) + float(rightmost_page.get('width')) - py = float(rightmost_page.get('y')) + if len(pages) > 0: + rightmost_page = max(pages, key=lambda p: p.get('x')) + px = float(rightmost_page.get('x')) + py = float(rightmost_page.get('y')) + else: + px = 0. + py = 0. # place elements for i, el in enumerate(elements): # create new page if needed + # inkex.utils.debug('nx: '+str(nx) + ' ny: '+str(ny)) 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) + new_page = etree.Element('{http://www.inkscape.org/namespaces/inkscape}page', nsmap=view.nsmap) + new_page.set('x', str(px + ( page_width + self.__page_spacing['padding'] ))) + new_page.set('y', str(py) ) + new_page.set('width', str(page_width)) + new_page.set('height', str(page_height)) + view.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) + x = px + (i % nx) * ( elbbox.width + self.__element_spacing['padding'] ) \ + + self.__page_spacing['margin-left'] + self.__element_spacing['padding'] + + y = py + (i % (nx*ny) // nx) * ( elbbox.height + self.__element_spacing['padding'] ) \ + + self.__page_spacing['margin-top'] + self.__element_spacing['padding'] + + 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 compute_bounding_box(self, selected): + _tag = lambda t: re.sub(r'\{.*?\}', r'', t) + objs = [ n for n in self.svg_traverse_node(selected[0]) if _tag(n.tag) not in ['text', 'tspan','g'] ] + l = inkex.elements._selected.ElementList(self.svg, objs) + return l.bounding_box() + + 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) - + # inkex.utils.debug('inkex: ' + inkex.__file__) parent = self.svg.get_current_layer() selected = self.svg.selected + for k in self.__page_spacing.keys(): + self.__page_spacing[k] = self.svg.unittouu( str(self.options.__dict__['page_'+k.replace('-','_')]) + self.options.units ) + for k in self.__element_spacing.keys(): + self.__element_spacing[k] = self.svg.unittouu(str(self.options.__dict__['element_'+k.replace('-','_')]) + self.options.units) + 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) + self.bbox = self.compute_bounding_box(selected) + if self.bbox is None: + inkex.utils.errormsg('bbox is None') + # 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)