PKTGa"UAUApdfimpose/__init__.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright Louis Paternault 2014-2015 # # 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 3 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, see . 1 """Perform imposition of a PDF file. Basic example ------------- The following example opens pdf file :file:`foo.pdf`, and writes as :file:`foo-impose.pdf` its imposed version, made to be folded vertically then horizontally. .. code-block:: python from pdfimpose import impose, VERTICAL, HORIZONTAL impose( inname="foo.pdf", outname="foo-impose.pdf", fold=[VERTICAL, HORIZONTAL], bind="left", last=0, ) High level manipulation ----------------------- This is the only function you will need to perform imposition. For more fine-grained manipulation, see :ref:`low-level`. .. autofunction:: impose Direction ^^^^^^^^^ Fold direction is set using constants :data:`VERTICAL` and :data:`HORIZONTAL`, instances of :class:`Direction`. .. autodata:: VERTICAL :annotation: .. autodata:: HORIZONTAL :annotation: .. autoclass:: Direction :members: .. _low-level: Low level manipulation ---------------------- These objects will usually not be manipulated directly by user. They are used internally may be manipulated if finer processing is performed. However, if you do want to have control over what is going on, you can use them. Orientation ^^^^^^^^^^^ :class:`Pages ` represented in the :class:`ImpositionMatrix` are oriented toward north or south. .. autodata:: NORTH .. autodata:: SOUTH .. autoclass:: Orientation :members: Imposition objects ^^^^^^^^^^^^^^^^^^ Befor performing imposition, the way input pages will be placed on output pages is computed and stored in the following objects. .. autoclass:: Coordinates .. autoclass:: ImpositionPage .. autoclass:: ImpositionMatrix Performing imposition ^^^^^^^^^^^^^^^^^^^^^ At last, imposition can be performed. .. autofunction:: pypdf_impose """ from PyPDF2.generic import NameObject, createStringObject try: from enum import Enum except ImportError: # pylint: disable=import-error from enum34 import Enum import PyPDF2 import logging import math VERSION = "0.1.1" __AUTHOR__ = "Louis Paternault (spalax@gresille.org)" __COPYRIGHT__ = "(C) 2014-2015 Louis Paternault. GNU GPL 3 or later." LOGGER = logging.getLogger(__name__) class Direction(Enum): """Direction (horizontal or vertical)""" # pylint: disable=too-few-public-methods vertical = False horizontal = True def __str__(self): return self.name[0].upper() @classmethod def from_char(cls, char): """Return :class:`Direction` object corresponding to `char`. Character can be one of ``h`` or ``v``, ignoring case. """ if char.lower() == 'h': return cls.horizontal elif char.lower() == 'v': return cls.vertical else: raise ValueError( "{}: Argument '{}' is not recognised as a direction.".format( cls.__name__, char ) ) #: Vertical direction VERTICAL = Direction.vertical #: Horizontal direction HORIZONTAL = Direction.horizontal class Orientation(Enum): """Two dimensions orientation""" # pylint: disable=too-few-public-methods north = 90 south = 270 def __str__(self): return self.name[0].upper() def fold(self, rotate): """Return the symmetrical orientation, according to an horizontal axe. :param bool rotate: If true, object is also applied a 180° rotation. >>> Orientation(90).fold(False) >>> Orientation(270).fold(False) >>> Orientation(90).fold(True) >>> Orientation(270).fold(True) """ if rotate: return Orientation((-self.value) % 360) else: return Orientation((180 - self.value) % 360) #: North orientation NORTH = Orientation.north #: South orientation SOUTH = Orientation.south _ORIENTATION_MATRIX = { NORTH.value: [1, 0, 0, 1], SOUTH.value: [-1, 0, 0, -1], } class Coordinates: """Two-dimensions coordinates.""" # pylint: disable=too-few-public-methods x = 0 y = 0 def __init__(self, x, y): self.x = x self.y = y def __add__(self, other): return Coordinates( self.x + other.x, self.y + other.y, ) def __sub__(self, other): return Coordinates( self.x - other.x, self.y - other.y, ) def __str__(self): return "({}, {})".format(self.x, self.y) def __repr__(self): return "{}({}, {})".format( self.__class__.__name__, self.x, self.y, ) class ImpositionPage: """Page, on an imposition matrix: a page number, and an orientation.""" # pylint: disable=too-few-public-methods def __init__(self, number, orientation): self.orientation = orientation self.number = number def __str__(self): return "{: 3}{}".format( self.number, self.orientation, ) def __eq__(self, other): return ( other.number == self.number and other.orientation == self.orientation ) def fold(self, page_max, rotate): """Return the symmetrical page to `self`. :arg bool orientation: Fold orientation. :arg int page_max: Maximum page number. """ return ImpositionPage( page_max - self.number, self.orientation.fold(rotate), ) class ImpositionMatrix: """Matrix of an imposition: array of numbered, oriented pages. :param list folds: Sorted list of folds, as a list of :class:`Direction` instances. :param str bind: One of ``top``, ``bottom``, ``left``, ``right``: edge on which the final book is to be folded. """ def __init__(self, folds, bind): size = Coordinates( 2**folds.count(HORIZONTAL), 2**folds.count(VERTICAL), ) # Initialisation self.folds = [] self.matrix = [ [ None for y in range(size.y) ] for x in range(2*size.x) ] self.bind = bind # First, fake, fold (corresponding to recto/verso) if bind in ["top", "right"]: self.matrix[0][0] = ImpositionPage(0, NORTH) else: # bind in ["bottom", "left"]: self.matrix[-1][-1] = ImpositionPage(0, NORTH) self.fold(HORIZONTAL, rotate=(bind in ["top", "bottom"])) # Actual folds for i in folds: self.fold(i) @property def vfolds(self): """Return the number of vertical folds""" return self.folds.count(VERTICAL) @property def hfolds(self): """Return the number of horizontal folds""" return self.folds.count(HORIZONTAL) @property def size(self): """Return the size of the matrix, as a :class:`Coordinates`.""" return Coordinates( len(self.matrix), len(self.matrix[0]), ) def fold(self, orientation, rotate=None): """Perform a fold according to `orientation`.""" if rotate is None: if orientation == HORIZONTAL: rotate = (self.bind in ["top", "bottom"]) else: rotate = (self.bind in ["left", "right"]) virtualpage_size = Coordinates( self.size.x//2**self.hfolds, self.size.y//2**self.vfolds, ) for x in range(0, self.size.x, virtualpage_size.x): for y in range(0, self.size.y, virtualpage_size.y): self._virtualpage_fold( Coordinates(x, y), virtualpage_size, orientation, rotate, ) self.folds.append(orientation) def __getitem__(self, item): if isinstance(item, Coordinates): return self.matrix[item.x][item.y] # pylint: disable=no-member elif isinstance(item, tuple) and len(item) == 2: if isinstance(item[0], int) and isinstance(item[1], int): return self.matrix[item[0]][item[1]] raise TypeError() def as_list(self): """Return ``self``, as a list of lists of :class:`ImpositionPage`.""" return [ [ self[(x, y)] for y in range(self.size.y) ] for x in range(self.size.x) ] def __setitem__(self, item, value): if len(item) == 1: item = item[0] if isinstance(item, Coordinates): self.matrix[item.x][item.y] = value return elif len(item) == 2: if isinstance(item[0], int) and isinstance(item[1], int): self.matrix[item[0]][item[1]] = value return raise TypeError() def _virtualpage_find_page(self, corner, size): """Find the actual page on a virtual page. A virtual page should contain only one actual page. Return the coorditanes of this page (relative to the matrix). :arg Coordinates corner: Coordinates of the low left corner of the virtual page. :arg Coordinates size: Size of the virtual page. """ for coordinates in [ corner, corner + Coordinates(size.x-1, 0), corner + Coordinates(0, size.y-1), corner + size - Coordinates(1, 1), ]: if self[coordinates] is not None: return coordinates def _virtualpage_fold(self, corner, size, orientation, rotate): """Fold a virtual page :arg Coordinates corner: Low left corner of the virtual page. :arg Coordinates size: Size of the virtual page. :arg bool orientation: Fold orientation. :arg bool rotate: Should pages be rotated? """ page = self._virtualpage_find_page(corner, size) if orientation == HORIZONTAL: self[ 2 * corner.x + size.x - page.x - 1, # Vertical symmetrical page.y, ] = self[page].fold(2**(len(self.folds)+1)-1, rotate) else: self[ page.x, 2 * corner.y + size.y - page.y - 1, # Horizontal symmetrical ] = self[page].fold(2**(len(self.folds)+1)-1, rotate) def __str__(self): return "\n".join([ " ".join([ str(page) for page in row ]) for row in reversed(list(zip(*self.matrix))) ]) @property def recto(self): """Return the recto of the matrix.""" return self.matrix[len(self.matrix)//2:] @property def verso(self): """Return the verso of the matrix.""" return self.matrix[:len(self.matrix)//2] def _get_input_pages(pdfsize, sectionsize, section_number, last): """Return the input pages, with `None` added to fit `sectionsize`.""" return ( [i for i in range(pdfsize - last)] + [None for i in range(pdfsize, section_number * sectionsize)] + [i for i in range(pdfsize - last, pdfsize)] ) def _get_pdf_size(page): """Return the size (width x height) of page.""" return ( page.mediaBox.lowerRight[0] - page.mediaBox.lowerLeft[0], page.mediaBox.upperRight[1] - page.mediaBox.lowerRight[1], ) def _set_metadata(inpdf, outpdf): """Copy and set metadata from inpdf to outpdf. """ #Source: # http://two.pairlist.net/pipermail/reportlab-users/2009-November/009033.html try: # pylint: disable=protected-access # Since we are accessing to a protected membre, which can no longer exist # in a future version of PyPDF2, we prevent errors. infodict = outpdf._info.getObject() infodict.update(inpdf.getDocumentInfo()) infodict.update({ NameObject('/Creator'): createStringObject( 'PdfImpose, using the PyPDF2 library — http://git.framasoft.org/spalax/pdfimpose' ) }) except AttributeError: LOGGER.warning("Could not copy metadata from source document.") def pypdf_impose(matrix, pdf, last, callback=None): """Return the pdf object corresponding to imposition of ``pdf``. :param ImpositionMatrix matrix: Imposition is performed according to this matrix. :param PyPDF2.PdfFileReader pdf: Input file, to be imposed. :param int last: Number of pages to keep as last pages (same meaning as same argument in :func:`impose`). :param function callback: Callback function (exactly the same meaning as same argument in :func:`impose`). :rtype: PyPDF2.PdfFileWriter """ # pylint: disable=too-many-locals if callback is None: callback = lambda x, y: None width, height = _get_pdf_size(pdf.getPage(0)) output = PyPDF2.PdfFileWriter() sectionsize = matrix.size.x * matrix.size.y section_number = int(math.ceil(pdf.numPages / sectionsize)) inputpages = _get_input_pages(pdf.numPages, sectionsize, section_number, last) rectoverso = [matrix.verso, matrix.recto] pagecount = 0 for outpagenumber in range(2 * section_number): currentoutputpage = output.addBlankPage( matrix.size.x * width // 2, matrix.size.y * height, ) for x in range(len(rectoverso[outpagenumber%2])): for y in range(len(rectoverso[outpagenumber%2][x])): pagenumber = ( (outpagenumber//2)*sectionsize + rectoverso[outpagenumber%2][x][y].number ) if inputpages[pagenumber] is not None: if rectoverso[outpagenumber%2][x][y].orientation == NORTH: currentoutputpage.mergeTransformedPage( pdf.getPage(inputpages[pagenumber]), _ORIENTATION_MATRIX[NORTH.value] + [x*width, y*height], ) else: currentoutputpage.mergeTransformedPage( pdf.getPage(inputpages[pagenumber]), _ORIENTATION_MATRIX[SOUTH.value] + [(x+1)*width, (y+1)*height], ) pagecount += 1 callback(pagecount, pdf.numPages) _set_metadata(pdf, output) return output def impose(inname, outname, fold, bind, last, callback=None): """Perform imposition on a pdf file. :param str inname: Name of input file. :param str outname: Name of output file. :param list fold: List of folds to perform, as a list of :class:`Direction` constants. :param int last: Number of pages to keep last. If necessary, blank pages are added at the end of the pdf file. If this argument is non zero, those blank pages are added, while keeping some pages at the end. This may be useful to keep the back-cover at the end of the file, for instance. :param function callback: Callback function, to provide user feedback. This functions is called each time one page (of the input file) has been processed, with the page number and total page numbers as arguments. It should return immediatly. This argument can be ``None`` to disable this. """ # pylint: disable=too-many-arguments pypdf_impose( matrix=ImpositionMatrix(fold, bind), pdf=PyPDF2.PdfFileReader(inname), last=last, callback=callback, ).write(outname) PKF; h99pdfimpose/errors.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright Louis Paternault 2011-2015 # # 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 3 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, see . 1 """Errors and exceptions""" class PdfImposeError(Exception): """Generic error""" pass class ArgumentError(PdfImposeError): """Error in command line arguments.""" def __init__(self, message): super().__init__() self.message = message def __str__(self): return self.message class IncompatibleBindSize(ArgumentError): """Bind and size are incompatible.""" def __init__(self, bind, size): super().__init__("Cannot bind on '{}' with size '{}x{}'".format( bind, size[0], size[1] )) class IncompatibleBindFold(ArgumentError): """Bind and fold are incompatible.""" def __init__(self, bind, fold): super().__init__("Cannot bind on '{}' with fold '{}'".format( bind, "".join([str(item) for item in fold]), )) PK9HsC$C$pdfimpose/options.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright Louis Paternault 2011-2015 # # 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 3 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, see . 1 """Manage options""" import argparse import logging import math import re import textwrap from pdfimpose import Direction, HORIZONTAL, VERTICAL from pdfimpose import VERSION from pdfimpose import errors import pdfimpose LOGGER = logging.getLogger(pdfimpose.__name__) def _positive_int(text): """Return ``True`` iff ``text`` represents a positive integer.""" try: if int(text) >= 0: return int(text) else: raise ValueError() except ValueError: raise argparse.ArgumentTypeError("Argument must be a positive integer.") SIZE_RE = r"^(?P\w+)x(?P\w+)$" def _is_power_of_two(number): """Return ``True`` iff `number` is a power of two.""" return math.trunc(math.log2(int(number))) == math.log2(int(number)) BIND = [ 'top', 'bottom', 'left', 'right', ] def _bind_type(text): """Check type of '--bind' argument.""" if not text: raise argparse.ArgumentTypeError( """Non-empty argument required.""" ) for bind in BIND: if bind.startswith(text): return bind raise argparse.ArgumentTypeError( """Argument must be one of {} (or one of their prefixes).""".format( ",".join(["'{}'".format(bind) for bind in BIND]) ) ) def _size_type(text): """Check type of '--size' argument.""" if text is None: return None if re.compile(SIZE_RE).match(text): match = re.compile(SIZE_RE).match(text).groupdict() if _is_power_of_two(match['width']) and _is_power_of_two(match['height']): if int(match['width']) != 1 or int(match['height']) != 1: return [match['width'], match['height']] raise argparse.ArgumentTypeError(textwrap.dedent(""" Argument must be "WIDTHxHEIGHT", where both WIDTH and HEIGHT are powers of two, and at least one of them is not 1. """)) def _fold_type(text): """Check type of '--fold' argument.""" if re.compile(r"^[vh]*$").match(text): return [ Direction.from_char(char) for char in text ] raise argparse.ArgumentTypeError(textwrap.dedent(""" Argument must be a sequence of letters 'v' and 'h'. """)) def _process_size_fold_bind(options): """Process arguments '--size', '--fold', '--bind'.""" # pylint: disable=too-many-branches processed = {} if options.size: width, height = [int(num) for num in options.size] if ( options.bind in ["left", "right"] and width == 1 ) or ( options.bind in ["top", "bottom"] and height == 1 ): raise errors.IncompatibleBindSize(options.bind, options.size) if options.bind is None: if width >= height: processed["bind"] = "left" else: processed["bind"] = "top" else: processed["bind"] = options.bind processed["fold"] = [] if processed["bind"] in ["left", "right"]: processed["fold"].append(HORIZONTAL) width //= 2 else: processed["fold"].append(VERTICAL) height //= 2 while width != 1 or height != 1: if width > height: processed["fold"].append(HORIZONTAL) width //= 2 else: processed["fold"].append(VERTICAL) height //= 2 processed["fold"].reverse() elif options.fold: processed["fold"] = options.fold if options.bind is None: if processed["fold"][-1] == VERTICAL: processed["bind"] = "top" else: processed["bind"] = "right" else: processed["bind"] = options.bind if ( processed["fold"][-1] == VERTICAL and options.bind not in ["top", "bottom"] ) or ( processed["fold"][-1] == HORIZONTAL and options.bind not in ["left", "right"] ): raise errors.IncompatibleBindFold(options.bind, options.fold) else: if options.bind is None: options.bind = "left" processed["bind"] = options.bind if processed["bind"] in ["top", "bottom"]: processed["fold"] = [HORIZONTAL, VERTICAL, HORIZONTAL, VERTICAL] else: processed["fold"] = [VERTICAL, HORIZONTAL, VERTICAL, HORIZONTAL] return processed def _process_output(text, source): """Process the `output` argument.""" if text is None: text = "{}-impose.pdf".format(".".join(source.split('.')[:-1])) return open(text, 'wb') def commandline_parser(): """Return a command line parser.""" parser = argparse.ArgumentParser( description=textwrap.dedent(""" Perform an imposition on the PDF file given in argument. """), formatter_class=argparse.RawTextHelpFormatter, epilog=textwrap.dedent(""" # Imposition Imposition consists in the arrangement of the printed product’s pages on the printer’s sheet, in order to obtain faster printing, simplify binding and reduce paper waste (source: http://en.wikipedia.org/wiki/Imposition). # How to ## Print The resulting document should be printed on both sides, binding left (or right). ## Fold Fold the document such that each page is placed against the previous one, beginning with the first page. More information on http://pdfimpose.readthedocs.org/en/latest/folding/ """), ) parser.add_argument( '--version', help='Show version', action='version', version='%(prog)s ' + VERSION ) parser.add_argument( '-v', '--verbose', help='Verbose mode.', action='store_true', ) parser.add_argument( 'file', metavar="FILE", help='PDF file to process', nargs=1, type=str, ) parser.add_argument( '--output', '-o', metavar='FILE', help=( 'Destination file. Default is "-impose" appended to first source file.' ), type=str, ) parser.add_argument( '--bind', '-b', help=textwrap.dedent(""" Binding edge. Default is left or top, depending on arguments '--fold' and '--size'. If neither '--size' nor '--fold' is set, default is 'left'. Note that any prefix of accepted choices is also accepted. """), metavar="{{{}}}".format(",".join(BIND)), default=None, type=_bind_type, ) parser.add_argument( '--last', '-l', metavar='N', help=textwrap.dedent(""" Number of pages to keep as last pages. Useful, for instance, to keep the back cover as a back cover. """), type=_positive_int, default=0, ) group = parser.add_mutually_exclusive_group() group.add_argument( '--fold', '-f', help=textwrap.dedent(""" Sequence of fold orientations, as letters 'v' and 'h'. Default is alternating, as much as possible, horizontal and vertical folds, to match the argument of '--size'. """), default=None, metavar='SEQUENCE', type=_fold_type, ) group.add_argument( '--size', '-s', metavar="WIDTHxHEIGHT", help=textwrap.dedent(""" Size of sections. Both width and height must be powers of two (1, 2, 4, 8, 16...). If neither this nor '--fold' is set, '--size' is '4x4'. """), type=_size_type, default=None, ) return parser def process_options(argv): """Make some more checks on options.""" processed = {} options = commandline_parser().parse_args(argv) if options.verbose: LOGGER.setLevel(logging.INFO) try: processed['last'] = options.last processed['output'] = _process_output(options.output, options.file[0]) processed["file"] = options.file[0] processed.update(_process_size_fold_bind(options)) except FileNotFoundError as error: raise errors.ArgumentError(str(error)) return processed PK9H7Updfimpose/main.py#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright Louis Paternault 2011-2015 # # 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 3 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, see . 1 """Main function for the command.""" import logging import sys from pdfimpose import errors, options import pdfimpose LOGGER = logging.getLogger(pdfimpose.__name__) LOGGER.addHandler(logging.StreamHandler()) def print_progress(progress, maximum): """Display progress to user""" LOGGER.info("{}/{}".format(progress, maximum)) def main(arguments=None): """Main function""" if arguments is None: arguments = sys.argv[1:] try: arguments = options.process_options(arguments) pdfimpose.impose( inname=arguments['file'], outname=arguments['output'], fold=arguments['fold'], bind=arguments['bind'], last=arguments['last'], callback=print_progress, ) except KeyboardInterrupt: sys.exit(1) except errors.PdfImposeError as error: LOGGER.error(error) sys.exit(1) sys.exit(0) if __name__ == "__main__": main() PKܳ9H2ny)PdfImpose-0.1.1.dist-info/DESCRIPTION.rstpdfimpose — Perform imposition of a PDF file ============================================ |sources| |pypi| |documentation| |license| Imposition consists in the arrangement of the printed product’s pages on the printer’s sheet, in order to obtain faster printing, simplify binding and reduce paper waste (source: http://en.wikipedia.org/wiki/Imposition). Examples -------- * `2015 calendar `_ (`source `__, see LaTeX source file in sources repository). * `64 pages file `_ (`source `__, generated using `dummypdf `_). What's new? ----------- See `changelog `_. Download and install -------------------- See the end of list for a (quick and dirty) Debian package. * From sources: * Download: https://pypi.python.org/pypi/pdfimpose * Install (in a `virtualenv`, if you do not want to mess with your distribution installation system):: python setup.py install * From pip:: pip install pdfimpose * Quick and dirty Debian (and Ubuntu?) package This requires `stdeb `_ to be installed:: python setup.py --command-packages=stdeb.command bdist_deb sudo dpkg -i deb_dist/python-pdfimpose__all.deb Documentation ------------- * The compiled documentation is available on `readthedocs `_ * To compile it from source, download and run:: cd doc && make html .. |documentation| image:: http://readthedocs.org/projects/pdfimpose/badge :target: http://pdfimpose.readthedocs.org .. |pypi| image:: https://img.shields.io/pypi/v/pdfimpose.svg :target: http://pypi.python.org/pypi/pdfimpose .. |license| image:: https://img.shields.io/pypi/l/pdfimpose.svg :target: http://www.gnu.org/licenses/gpl-3.0.html .. |sources| image:: https://img.shields.io/badge/sources-pdfimpose-brightgreen.svg :target: http://git.framasoft.org/spalax/pdfimpose PKճ9H@33*PdfImpose-0.1.1.dist-info/entry_points.txt[console_scripts] pdfimpose = pdfimpose.main:main PKܳ9HX'PdfImpose-0.1.1.dist-info/metadata.json{"classifiers": ["Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Manufacturing", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Printing", "Topic :: Software Development :: Libraries :: Python Modules"], "extensions": {"python.commands": {"wrap_console": {"pdfimpose": "pdfimpose.main:main"}}, "python.details": {"contacts": [{"email": "spalax@gresille.org", "name": "Louis Paternault", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://git.framasoft.org/spalax/pdfimpose"}}, "python.exports": {"console_scripts": {"pdfimpose": "pdfimpose.main:main"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "license": "GPLv3 or any later version", "metadata_version": "2.0", "name": "PdfImpose", "run_requires": [{"requires": ["PyPDF2", "enum34"]}], "summary": "Perform imposition of a PDF file.", "version": "0.1.1"}PKճ9H& 'PdfImpose-0.1.1.dist-info/top_level.txtpdfimpose PK"F2"PdfImpose-0.1.1.dist-info/zip-safe PKܳ9HndnnPdfImpose-0.1.1.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PKܳ9Hۙ9  "PdfImpose-0.1.1.dist-info/METADATAMetadata-Version: 2.0 Name: PdfImpose Version: 0.1.1 Summary: Perform imposition of a PDF file. Home-page: https://git.framasoft.org/spalax/pdfimpose Author: Louis Paternault Author-email: spalax@gresille.org License: GPLv3 or any later version Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Manufacturing Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Printing Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Dist: PyPDF2 Requires-Dist: enum34 pdfimpose — Perform imposition of a PDF file ============================================ |sources| |pypi| |documentation| |license| Imposition consists in the arrangement of the printed product’s pages on the printer’s sheet, in order to obtain faster printing, simplify binding and reduce paper waste (source: http://en.wikipedia.org/wiki/Imposition). Examples -------- * `2015 calendar `_ (`source `__, see LaTeX source file in sources repository). * `64 pages file `_ (`source `__, generated using `dummypdf `_). What's new? ----------- See `changelog `_. Download and install -------------------- See the end of list for a (quick and dirty) Debian package. * From sources: * Download: https://pypi.python.org/pypi/pdfimpose * Install (in a `virtualenv`, if you do not want to mess with your distribution installation system):: python setup.py install * From pip:: pip install pdfimpose * Quick and dirty Debian (and Ubuntu?) package This requires `stdeb `_ to be installed:: python setup.py --command-packages=stdeb.command bdist_deb sudo dpkg -i deb_dist/python-pdfimpose__all.deb Documentation ------------- * The compiled documentation is available on `readthedocs `_ * To compile it from source, download and run:: cd doc && make html .. |documentation| image:: http://readthedocs.org/projects/pdfimpose/badge :target: http://pdfimpose.readthedocs.org .. |pypi| image:: https://img.shields.io/pypi/v/pdfimpose.svg :target: http://pypi.python.org/pypi/pdfimpose .. |license| image:: https://img.shields.io/pypi/l/pdfimpose.svg :target: http://www.gnu.org/licenses/gpl-3.0.html .. |sources| image:: https://img.shields.io/badge/sources-pdfimpose-brightgreen.svg :target: http://git.framasoft.org/spalax/pdfimpose PKܳ9Hn+E PdfImpose-0.1.1.dist-info/RECORDPdfImpose-0.1.1.dist-info/DESCRIPTION.rst,sha256=7LMDYZfh39dcHsZ4JlcUjy6RHI43CuAiMsQeE6D0JJI,2296 PdfImpose-0.1.1.dist-info/METADATA,sha256=b_1RoVtG5twiT7E3WBwLvhz98nJcIdC8Q_Zn7LqPoQQ,3350 PdfImpose-0.1.1.dist-info/RECORD,, PdfImpose-0.1.1.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 PdfImpose-0.1.1.dist-info/entry_points.txt,sha256=AKL5ovI1ppdJVhiXz7tKCbBM87URWDVjLArBl-nJTH0,51 PdfImpose-0.1.1.dist-info/metadata.json,sha256=QiE9oO91s8zMdQDAcbFdwP5CTv5WfIqyw3fqKVxhScY,1298 PdfImpose-0.1.1.dist-info/top_level.txt,sha256=dFHxbMXGLad8gyirLSucZg38HYniFbhrpWu7b01uTmY,10 PdfImpose-0.1.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 pdfimpose/__init__.py,sha256=pVfryMvCU4x44lgmDUCH3T_LT9fYvm-bVAIBoB_puow,16725 pdfimpose/errors.py,sha256=UB2funYQ7K6mHvCK8jfsmaELJwvqP6XUe2ySdv8p6X4,1593 pdfimpose/main.py,sha256=BbZxQb1i2DAy9wYzqwDIaf7wTORgfk-eC9LFOrwXwZo,1699 pdfimpose/options.py,sha256=-vmUo3E9UMswCWlENZNSR-Nl7EzhPS-etAdAv0HNtXc,9283 PKTGa"UAUApdfimpose/__init__.pyPKF; h99Apdfimpose/errors.pyPK9HsC$C$Gpdfimpose/options.pyPK9H7Uglpdfimpose/main.pyPKܳ9H2ny)9sPdfImpose-0.1.1.dist-info/DESCRIPTION.rstPKճ9H@33*x|PdfImpose-0.1.1.dist-info/entry_points.txtPKܳ9HX'|PdfImpose-0.1.1.dist-info/metadata.jsonPKճ9H& 'JPdfImpose-0.1.1.dist-info/top_level.txtPK"F2"PdfImpose-0.1.1.dist-info/zip-safePKܳ9HndnnڂPdfImpose-0.1.1.dist-info/WHEELPKܳ9Hۙ9  "PdfImpose-0.1.1.dist-info/METADATAPKܳ9Hn+E ېPdfImpose-0.1.1.dist-info/RECORDPK