#!/usr/bin/env python # coding=utf-8 # # Copyright (C) 2007 Terry Brown, terry_n_brown@yahoo.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. # from __future__ import absolute_import, unicode_literals import inkex from inkex import Use, Rectangle from inkex.base import SvgOutputMixin from inkex.localization import inkex_gettext as _ class Nup(inkex.OutputExtension, SvgOutputMixin): """N-up Layout generator""" def add_arguments(self, pars): pars.add_argument("--unit", default="px") pars.add_argument("--rows", type=int, default=2) pars.add_argument("--cols", type=int, default=2) pars.add_argument("--paddingTop", type=float) pars.add_argument("--paddingBottom", type=float) pars.add_argument("--paddingLeft", type=float) pars.add_argument("--paddingRight", type=float) pars.add_argument("--marginTop", type=float) pars.add_argument("--marginBottom", type=float) pars.add_argument("--marginLeft", type=float) pars.add_argument("--marginRight", type=float) pars.add_argument("--pgMarginTop", type=float) pars.add_argument("--pgMarginBottom", type=float) pars.add_argument("--pgMarginLeft", type=float) pars.add_argument("--pgMarginRight", type=float) pars.add_argument("--pgSizeX", type=float) pars.add_argument("--pgSizeY", type=float) pars.add_argument("--sizeX", type=float) pars.add_argument("--sizeY", type=float) pars.add_argument("--calculateSize", type=inkex.Boolean, default=True) pars.add_argument("--showHolder", type=inkex.Boolean, default=True) pars.add_argument("--showCrosses", type=inkex.Boolean, default=True) pars.add_argument("--showInner", type=inkex.Boolean, default=True) pars.add_argument("--showOuter", type=inkex.Boolean, default=False) pars.add_argument("--showInnerBox", type=inkex.Boolean, default=False) pars.add_argument("--showOuterBox", type=inkex.Boolean, default=False) pars.add_argument("--tab") def save(self, stream): show_list = [] for i in [ "showHolder", "showCrosses", "showInner", "showOuter", "showInnerBox", "showOuterBox", ]: if getattr(self.options, i): show_list.append(i.lower().replace("show", "")) opt = self.options ret = self.generate_nup( unit=opt.unit, pgSize=(opt.pgSizeX, opt.pgSizeY), pgMargin=( opt.pgMarginTop, opt.pgMarginRight, opt.pgMarginBottom, opt.pgMarginLeft, ), num=(opt.rows, opt.cols), calculateSize=opt.calculateSize, size=(opt.sizeX, opt.sizeY), margin=(opt.marginTop, opt.marginRight, opt.marginBottom, opt.marginLeft), padding=( opt.paddingTop, opt.paddingRight, opt.paddingBottom, opt.paddingLeft, ), show=show_list, ) if ret: stream.write(ret) def expandTuple(self, unit, x, length=4): try: iter(x) except: return None if len(x) != length: x *= 2 if len(x) != length: raise Exception("expandTuple: requires 2 or 4 item tuple") try: return tuple( map( lambda ev: ( self.svg.unittouu(str(eval(str(ev))) + unit) / self.svg.unittouu("1px") ), x, ) ) except: return None def generate_nup( self, unit="px", pgSize=("8.5*96", "11*96"), pgMargin=(0, 0), pgPadding=(0, 0), num=(2, 2), calculateSize=True, size=None, margin=(0, 0), padding=(20, 20), show=["default"], ): """Generate the SVG. Inputs are run through 'eval(str(x))' so you can use '8.5*72' instead of 612. Margin / padding dimension tuples can be (top & bottom, left & right) or (top, right, bottom, left). Keyword arguments: pgSize -- page size, width x height pgMargin -- extra space around each page pgPadding -- added to pgMargin n -- rows x cols size -- override calculated size, width x height margin -- white space around each piece padding -- inner padding for each piece show -- list of keywords indicating what to show - 'crosses' - cutting guides - 'inner' - inner boundary - 'outer' - outer boundary """ if "default" in show: show = set(show).union(["inner", "innerbox", "holder", "crosses"]) pgMargin = self.expandTuple(unit, pgMargin) pgPadding = self.expandTuple(unit, pgPadding) margin = self.expandTuple(unit, margin) padding = self.expandTuple(unit, padding) pgSize = self.expandTuple(unit, pgSize, length=2) # num = tuple(map(lambda ev: eval(str(ev)), num)) if not pgMargin or not pgPadding: return inkex.errormsg(_("No padding or margin available.")) page_edge = list(map(sum, zip(pgMargin, pgPadding))) top, right, bottom, left = 0, 1, 2, 3 width, height = 0, 1 rows, cols = 0, 1 size = self.expandTuple(unit, size, length=2) if ( size is None or calculateSize or len(size) < 2 or size[0] == 0 or size[1] == 0 ): size = ( ( pgSize[width] - page_edge[left] - page_edge[right] - num[cols] * (margin[left] + margin[right]) ) / num[cols], ( pgSize[height] - page_edge[top] - page_edge[bottom] - num[rows] * (margin[top] + margin[bottom]) ) / num[rows], ) else: size = self.expandTuple(unit, size, length=2) # sep is separation between same points on pieces sep = ( size[width] + margin[right] + margin[left], size[height] + margin[top] + margin[bottom], ) style = "stroke:#000000;stroke-opacity:1;fill:none;fill-opacity:1;" padbox = Rectangle( x=str(page_edge[left] + margin[left] + padding[left]), y=str(page_edge[top] + margin[top] + padding[top]), width=str(size[width] - padding[left] - padding[right]), height=str(size[height] - padding[top] - padding[bottom]), style=style, ) margbox = Rectangle( x=str(page_edge[left] + margin[left]), y=str(page_edge[top] + margin[top]), width=str(size[width]), height=str(size[height]), style=style, ) doc = self.get_template(width=pgSize[width], height=pgSize[height]) svg = doc.getroot() def make_clones(under, to): for row in range(0, num[rows]): for col in range(0, num[cols]): if row == 0 and col == 0: continue use = under.add(Use()) use.set("xlink:href", "#" + to) use.transform.add_translate(col * sep[width], row * sep[height]) # guidelayer ##################################################### if {"inner", "outer"}.intersection(show): layer = svg.add(inkex.Layer.new("Guide Layer")) if "inner" in show: ibox = layer.add(padbox.copy()) ibox.style["stroke"] = "#8080ff" ibox.set("id", "innerguide") make_clones(layer, "innerguide") if "outer" in show: obox = layer.add(margbox.copy()) obox.style["stroke"] = "#8080ff" obox.set("id", "outerguide") make_clones(layer, "outerguide") # crosslayer ##################################################### if {"crosses"}.intersection(show): layer = svg.add(inkex.Layer.new("Cut Layer")) if "crosses" in show: crosslen = 12 group = layer.add(inkex.Group(id="cross")) x, y = 0, 0 path = "M%f %f" % ( x + page_edge[left] + margin[left], y + page_edge[top] + margin[top] - crosslen, ) path += " L%f %f" % ( x + page_edge[left] + margin[left], y + page_edge[top] + margin[top] + crosslen, ) path += " M%f %f" % ( x + page_edge[left] + margin[left] - crosslen, y + page_edge[top] + margin[top], ) path += " L%f %f" % ( x + page_edge[left] + margin[left] + crosslen, y + page_edge[top] + margin[top], ) group.add( inkex.PathElement( style=style + "stroke-width:0.05", d=path, id="crossmarker" ) ) for row in 0, 1: for col in 0, 1: if row or col: cln = group.add(Use()) cln.set("xlink:href", "#crossmarker") cln.transform.add_translate( col * size[width], row * size[height] ) make_clones(layer, "cross") # clonelayer ##################################################### layer = svg.add(inkex.Layer.new("Clone Layer")) make_clones(layer, "main") # mainlayer ###################################################### layer = svg.add(inkex.Layer.new("Main Layer")) group = layer.add(inkex.Group(id="main")) if "innerbox" in show: group.add(padbox) if "outerbox" in show: group.add(margbox) if "holder" in show: x, y = ( page_edge[left] + margin[left] + padding[left], page_edge[top] + margin[top] + padding[top], ) w, h = ( size[width] - padding[left] - padding[right], size[height] - padding[top] - padding[bottom], ) path = "M{:f} {:f}".format(x + w / 2.0, y) path += " L{:f} {:f}".format(x + w, y + h / 2.0) path += " L{:f} {:f}".format(x + w / 2.0, y + h) path += " L{:f} {:f}".format(x, y + h / 2.0) path += " Z" group.add(inkex.PathElement(style=style, d=path)) return svg.tostring() if __name__ == "__main__": Nup().run()