PK TGa"UA UA pdfimpose/__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)
PK F; h9 9 pdfimpose/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]),
))
PK 9HsC$ 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
PK 9H7U pdfimpose/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@3 3 * 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 ܳ9Hndn n PdfImpose-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
PK TGa"UA UA pdfimpose/__init__.pyPK F; h9 9 A pdfimpose/errors.pyPK 9HsC$ C$ G pdfimpose/options.pyPK 9H7U gl pdfimpose/main.pyPK ܳ9H2ny ) 9s PdfImpose-0.1.1.dist-info/DESCRIPTION.rstPK ճ9H@3 3 * x| PdfImpose-0.1.1.dist-info/entry_points.txtPK ܳ9HX ' | PdfImpose-0.1.1.dist-info/metadata.jsonPK ճ9H&
' J PdfImpose-0.1.1.dist-info/top_level.txtPK "F2 " PdfImpose-0.1.1.dist-info/zip-safePK ܳ9Hndn n ڂ 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