commit 7438f7ae15535f599538a3f28b2d8cccc7b27eb0 Author: Kenneth Odle Date: Sun Jun 19 09:39:40 2022 -0400 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/book-g19ab6d77a_640.png b/book-g19ab6d77a_640.png new file mode 100644 index 0000000..162d465 Binary files /dev/null and b/book-g19ab6d77a_640.png differ diff --git a/pdfbooklet.xcf b/pdfbooklet.xcf new file mode 100644 index 0000000..ca28581 Binary files /dev/null and b/pdfbooklet.xcf differ diff --git a/pdfbooklet_3.1.2-3_all.deb b/pdfbooklet_3.1.2-3_all.deb new file mode 100644 index 0000000..ee838a9 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all.deb differ diff --git a/pdfbooklet_3.1.2-3_all/DEBIAN/control b/pdfbooklet_3.1.2-3_all/DEBIAN/control new file mode 100644 index 0000000..0f92786 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/DEBIAN/control @@ -0,0 +1,10 @@ +Package: pdfbooklet +Version: 3.1.2-3 +Architecture: all +Maintainer: Travis CI User +Installed-Size: 5214 +Depends: python3 (>= 3.4), python3-gi, python3-gi-cairo, python3-cairo, gir1.2-gtk-3.0, gir1.2-poppler-0.18 +Section: alien +Priority: extra +Description: Make booklets from pdfs. + Converted by ken (https://git.kjodle.net) diff --git a/pdfbooklet_3.1.2-3_all/md5sums b/pdfbooklet_3.1.2-3_all/md5sums new file mode 100644 index 0000000..bfb04f8 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/md5sums @@ -0,0 +1,56 @@ +ba56c5487eb08c12bb4a9b3e6a473ad5 postinst +52eff264dbe1c88e4cdae0e226a01f18 usr/bin/pdfbooklet +906b5b82769c2aa6670ad40ecde27913 usr/lib/python3/dist-packages/pdfbooklet-3.1.2-py3.5.egg-info +7c08f7a7b39e2ff0b9ec7ad59ab1b722 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__init__.py +bee924498685db3fa219dfc099933039 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/__init__.cpython-35.pyc +daec93ab0b71a886408c2271f569d69b usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/_version.cpython-35.pyc +399edd643a7ebe5705761d266f89a39e usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/filters.cpython-35.pyc +c341762928903c2ea146bab0a98222bc usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/generic.cpython-35.pyc +7bcd7e5070ea7cfb0b1a1fcbd2ba4a0c usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/merger.cpython-35.pyc +2ff2f3bbfe9960d891ee5297d99bc18f usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/pagerange.cpython-35.pyc +10eeae6883876bf6d1e494fb48480d78 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/pdf.cpython-35.pyc +60f85ad15bc9787fe4d9cd20dd9fcaab usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/utils.cpython-35.pyc +362b7ed03b0f0f02bb241cc3a5cab13e usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/xmp.cpython-35.pyc +7d0617c19300e959eced5ea961d7b603 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/_version.py +3d42cf9a67139de17946475432e93a34 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/filters.py +503d65931c283b5d3ccd52f88a365476 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/generic.py +38408394fbf7442d4ece6d17ff6433cf usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/merger.py +a74524899783ee4b56529d0287c3d5f6 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/pagerange.py +e6998315652813e37c44627569cd15b2 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/pdf.py +383614f6ba70f7d5cf9ed5496c201907 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/utils.py +e6379c525ae88b173e7b3a757e2c3063 usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/xmp.py +d41d8cd98f00b204e9800998ecf8427e usr/lib/python3/dist-packages/pdfbooklet/__init__.py +3b3d4b3ad68d0b42baa9405ffab42e37 usr/lib/python3/dist-packages/pdfbooklet/__pycache__/__init__.cpython-35.pyc +a427643aa93316c1407cbfd1327d146e usr/lib/python3/dist-packages/pdfbooklet/__pycache__/elib_intl3.cpython-35.pyc +b2a93dc73d6f8e522c9ef5aa410846ed usr/lib/python3/dist-packages/pdfbooklet/__pycache__/files_chooser.cpython-35.pyc +856537a63a5a9fd96af18dece31c782e usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfbooklet.cpython-35.pyc +7bdc8cd69d4848eb87801f4cd1c6d53d usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfshuffler_g3.cpython-35.pyc +f65a8a722149a8f678c3ef49a48444b3 usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfshuffler_iconview3.cpython-35.pyc +16d5f3a8ea1c233c81ef7b30127bc310 usr/lib/python3/dist-packages/pdfbooklet/elib_intl3.py +7262bce50b0bd5db96c298743f519b6f usr/lib/python3/dist-packages/pdfbooklet/files_chooser.py +4560e948c3a9c0ba561156c89222afe9 usr/lib/python3/dist-packages/pdfbooklet/pdfbooklet.py +85ff4690d95bcf0bc5dc88dc4518edee usr/lib/python3/dist-packages/pdfbooklet/pdfshuffler_g3.py +b18a190a171e1ae57118620f27773a26 usr/lib/python3/dist-packages/pdfbooklet/pdfshuffler_iconview3.py +f2a9808fe8ae4e932d6c471cf6326cb6 usr/share/applications/pdfbooklet.desktop +1e65a1a059a7ba375e144e57bafe0e03 usr/share/doc/pdfbooklet/changelog.Debian.gz +8c836ce2f8e3261dbabc7592a7cc9b6f usr/share/doc/pdfbooklet/copyright +5495e0fdf28e395bf3229069a7c5e1af usr/share/locale/fr/LC_MESSAGES/pdfbooklet.mo +b67b9bb1ea5b769203c3e73d9ecc4771 usr/share/locale/fr/LC_MESSAGES/pdfbooklet.po +97fc5e114afab69b2dda1d9ed27732d5 usr/share/locale/fr/LC_MESSAGES/pdfshuffler.mo +0bcfbf4a7a4e98c1dbd520fcc04ad342 usr/share/locale/fr/LC_MESSAGES/pdfshuffler.po +c3bc3ac07fa1d1c6f6c95e3e9d039908 usr/share/pdfbooklet/data/chooser_dialog.glade +84ff01d09458dc41a43edbc175896dd5 usr/share/pdfbooklet/data/nofile.pdf +f2a9808fe8ae4e932d6c471cf6326cb6 usr/share/pdfbooklet/data/pdfbooklet.desktop +7ba7a6336c65f32291fa2f4f1780dd44 usr/share/pdfbooklet/data/pdfbooklet.ico +ff8b8bd0473cff3b5f2e67f087674a95 usr/share/pdfbooklet/data/pdfbooklet.png +36aaf7de5c575a414bad40039dffbd5d usr/share/pdfbooklet/data/pdfbooklet.svg +7206b41ae6a12a3e60a66255f309d3a0 usr/share/pdfbooklet/data/pdfbooklet3.glade +22cc082abf6cc863fe0575fc574bac0f usr/share/pdfbooklet/data/pdfshuffler.ico +c101a33982b86a5e4deb7b49bd6a2252 usr/share/pdfbooklet/data/pdfshuffler64.ico +e08557a923bdc9dc3cb374ee36110a5d usr/share/pdfbooklet/data/pdfshuffler64.png +1d1da34644d20ead9325f6bbf6f2d9dd usr/share/pdfbooklet/data/pdfshuffler_g.glade +3242edc53175c4d9930b8f96529514c1 usr/share/pdfbooklet/documentation/Manuel_de_Pdf-Booklet.pdf +7c1c11d075a5ac583700a173ca40f589 usr/share/pdfbooklet/documentation/Note_for_Linux_users_3.0.6.html +09a1b8a9c361be1c84b5863d63344e3b usr/share/pdfbooklet/documentation/Pdf-Booklet_User's_Guide.pdf +36aaf7de5c575a414bad40039dffbd5d usr/share/pdfbooklet/icons/hicolor/scalable/pdfbooklet.svg +ff8b8bd0473cff3b5f2e67f087674a95 usr/share/pixmaps/pdfbooklet.png diff --git a/pdfbooklet_3.1.2-3_all/postinst b/pdfbooklet_3.1.2-3_all/postinst new file mode 100755 index 0000000..89f0967 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/postinst @@ -0,0 +1 @@ +chmod 777 ./usr/share/pdfbooklet/ diff --git a/pdfbooklet_3.1.2-3_all/usr/bin/pdfbooklet b/pdfbooklet_3.1.2-3_all/usr/bin/pdfbooklet new file mode 100755 index 0000000..c723c55 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/bin/pdfbooklet @@ -0,0 +1,33 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" + + PdfBooklet 2.2.2 - GTK+ based utility for creating booklets + and other layouts from PDF documents. + + + This file is part of PdfBooklet. + + PdfBooklet 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" + +try: + from pdfbooklet.pdfbooklet import main + main() +except ImportError as e: + print('Error: Could not import pdfbooklet') + print('Cause: %s' % e) diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet-3.1.2-py3.5.egg-info b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet-3.1.2-py3.5.egg-info new file mode 100644 index 0000000..b1b4df8 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet-3.1.2-py3.5.egg-info @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: pdfbooklet +Version: 3.1.2 +Summary: A simple application for creating booklets and other layouts from PDF files +Home-page: https://sourceforge.net/projects/pdfbooklet +Author: GAF Software +Author-email: Averell7 at sourceforge dot net +License: GNU GPL-3 +Description: UNKNOWN +Platform: UNKNOWN diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__init__.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__init__.py new file mode 100644 index 0000000..f458c0e --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__init__.py @@ -0,0 +1,5 @@ +from .pdf import PdfFileReader, PdfFileWriter +from .merger import PdfFileMerger +from .pagerange import PageRange, parse_filename_page_ranges +from ._version import __version__ +__all__ = ["pdf", "PdfFileMerger"] diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/__init__.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/__init__.cpython-35.pyc new file mode 100644 index 0000000..386fe49 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/__init__.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/_version.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/_version.cpython-35.pyc new file mode 100644 index 0000000..1f41950 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/_version.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/filters.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/filters.cpython-35.pyc new file mode 100644 index 0000000..3a89a9c Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/filters.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/generic.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/generic.cpython-35.pyc new file mode 100644 index 0000000..e1e9b47 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/generic.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/merger.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/merger.cpython-35.pyc new file mode 100644 index 0000000..6267b41 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/merger.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/pagerange.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/pagerange.cpython-35.pyc new file mode 100644 index 0000000..bfb49b0 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/pagerange.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/pdf.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/pdf.cpython-35.pyc new file mode 100644 index 0000000..42be278 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/pdf.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/utils.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/utils.cpython-35.pyc new file mode 100644 index 0000000..5d46dc3 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/utils.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/xmp.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/xmp.cpython-35.pyc new file mode 100644 index 0000000..7c25584 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/__pycache__/xmp.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/_version.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/_version.py new file mode 100644 index 0000000..760870c --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/_version.py @@ -0,0 +1 @@ +__version__ = '1.25.1' diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/filters.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/filters.py new file mode 100644 index 0000000..3717fd4 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/filters.py @@ -0,0 +1,362 @@ +# vim: sw=4:expandtab:foldmethod=marker +# +# Copyright (c) 2006, Mathieu Fenniak +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +""" +Implementation of stream filters for PDF. +""" +__author__ = "Mathieu Fenniak" +__author_email__ = "biziqe@mathieu.fenniak.net" + +from .utils import PdfReadError, ord_, chr_ +from sys import version_info +if version_info < ( 3, 0 ): + from cStringIO import StringIO +else: + from io import StringIO + import struct + +try: + import zlib + + def decompress(data): + return zlib.decompress(data) + + def compress(data): + return zlib.compress(data) + +except ImportError: + # Unable to import zlib. Attempt to use the System.IO.Compression + # library from the .NET framework. (IronPython only) + import System + from System import IO, Collections, Array + + def _string_to_bytearr(buf): + retval = Array.CreateInstance(System.Byte, len(buf)) + for i in range(len(buf)): + retval[i] = ord(buf[i]) + return retval + + def _bytearr_to_string(bytes): + retval = "" + for i in range(bytes.Length): + retval += chr(bytes[i]) + return retval + + def _read_bytes(stream): + ms = IO.MemoryStream() + buf = Array.CreateInstance(System.Byte, 2048) + while True: + bytes = stream.Read(buf, 0, buf.Length) + if bytes == 0: + break + else: + ms.Write(buf, 0, bytes) + retval = ms.ToArray() + ms.Close() + return retval + + def decompress(data): + bytes = _string_to_bytearr(data) + ms = IO.MemoryStream() + ms.Write(bytes, 0, bytes.Length) + ms.Position = 0 # fseek 0 + gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Decompress) + bytes = _read_bytes(gz) + retval = _bytearr_to_string(bytes) + gz.Close() + return retval + + def compress(data): + bytes = _string_to_bytearr(data) + ms = IO.MemoryStream() + gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Compress, True) + gz.Write(bytes, 0, bytes.Length) + gz.Close() + ms.Position = 0 # fseek 0 + bytes = ms.ToArray() + retval = _bytearr_to_string(bytes) + ms.Close() + return retval + + +class FlateDecode(object): + def decode(data, decodeParms): + data = decompress(data) + predictor = 1 + if decodeParms: + try: + predictor = decodeParms.get("/Predictor", 1) + except AttributeError: + pass # usually an array with a null object was read + + # predictor 1 == no predictor + if predictor != 1: + columns = decodeParms["/Columns"] + # PNG prediction: + if predictor >= 10 and predictor <= 15: + output = StringIO() + # PNG prediction can vary from row to row + rowlength = columns + 1 + assert len(data) % rowlength == 0 + prev_rowdata = (0,) * rowlength + for row in range(len(data) // rowlength): + rowdata = [ord_(x) for x in data[(row*rowlength):((row+1)*rowlength)]] + filterByte = rowdata[0] + if filterByte == 0: + pass + elif filterByte == 1: + for i in range(2, rowlength): + rowdata[i] = (rowdata[i] + rowdata[i-1]) % 256 + elif filterByte == 2: + for i in range(1, rowlength): + rowdata[i] = (rowdata[i] + prev_rowdata[i]) % 256 + else: + # unsupported PNG filter + raise PdfReadError("Unsupported PNG filter %r" % filterByte) + prev_rowdata = rowdata + output.write(''.join([chr(x) for x in rowdata[1:]])) + data = output.getvalue() + else: + # unsupported predictor + raise PdfReadError("Unsupported flatedecode predictor %r" % predictor) + return data + decode = staticmethod(decode) + + def encode(data): + return compress(data) + encode = staticmethod(encode) + + +class ASCIIHexDecode(object): + def decode(data, decodeParms=None): + retval = "" + char = "" + x = 0 + while True: + c = data[x] + if c == ">": + break + elif c.isspace(): + x += 1 + continue + char += c + if len(char) == 2: + retval += chr(int(char, base=16)) + char = "" + x += 1 + assert char == "" + return retval + decode = staticmethod(decode) + + +class LZWDecode(object): + """Taken from: + http://www.java2s.com/Open-Source/Java-Document/PDF/PDF-Renderer/com/sun/pdfview/decode/LZWDecode.java.htm + """ + class decoder(object): + def __init__(self, data): + self.STOP=257 + self.CLEARDICT=256 + self.data=data + self.bytepos=0 + self.bitpos=0 + self.dict=[""]*4096 + for i in range(256): + self.dict[i]=chr(i) + self.resetDict() + + def resetDict(self): + self.dictlen=258 + self.bitspercode=9 + + def nextCode(self): + fillbits=self.bitspercode + value=0 + while fillbits>0 : + if self.bytepos >= len(self.data): + return -1 + nextbits=ord(self.data[self.bytepos]) + bitsfromhere=8-self.bitpos + if bitsfromhere>fillbits: + bitsfromhere=fillbits + value |= (((nextbits >> (8-self.bitpos-bitsfromhere)) & + (0xff >> (8-bitsfromhere))) << + (fillbits-bitsfromhere)) + fillbits -= bitsfromhere + self.bitpos += bitsfromhere + if self.bitpos >=8: + self.bitpos=0 + self.bytepos = self.bytepos+1 + return value + + def decode(self): + """ algorithm derived from: + http://www.rasip.fer.hr/research/compress/algorithms/fund/lz/lzw.html + and the PDFReference + """ + cW = self.CLEARDICT; + baos="" + while True: + pW = cW; + cW = self.nextCode(); + if cW == -1: + raise PdfReadError("Missed the stop code in LZWDecode!") + if cW == self.STOP: + break; + elif cW == self.CLEARDICT: + self.resetDict(); + elif pW == self.CLEARDICT: + baos+=self.dict[cW] + else: + if cW < self.dictlen: + baos += self.dict[cW] + p=self.dict[pW]+self.dict[cW][0] + self.dict[self.dictlen]=p + self.dictlen+=1 + else: + p=self.dict[pW]+self.dict[pW][0] + baos+=p + self.dict[self.dictlen] = p; + self.dictlen+=1 + if (self.dictlen >= (1 << self.bitspercode) - 1 and + self.bitspercode < 12): + self.bitspercode+=1 + return baos + + @staticmethod + def decode(data,decodeParams=None): + return LZWDecode.decoder(data).decode() + + +class ASCII85Decode(object): + def decode(data, decodeParms=None): + if version_info < ( 3, 0 ): + retval = "" + group = [] + x = 0 + hitEod = False + # remove all whitespace from data + data = [y for y in data if not (y in ' \n\r\t')] + while not hitEod: + c = data[x] + if len(retval) == 0 and c == "<" and data[x+1] == "~": + x += 2 + continue + #elif c.isspace(): + # x += 1 + # continue + elif c == 'z': + assert len(group) == 0 + retval += '\x00\x00\x00\x00' + x += 1 + continue + elif c == "~" and data[x+1] == ">": + if len(group) != 0: + # cannot have a final group of just 1 char + assert len(group) > 1 + cnt = len(group) - 1 + group += [ 85, 85, 85 ] + hitEod = cnt + else: + break + else: + c = ord(c) - 33 + assert c >= 0 and c < 85 + group += [ c ] + if len(group) >= 5: + b = group[0] * (85**4) + \ + group[1] * (85**3) + \ + group[2] * (85**2) + \ + group[3] * 85 + \ + group[4] + assert b < (2**32 - 1) + c4 = chr((b >> 0) % 256) + c3 = chr((b >> 8) % 256) + c2 = chr((b >> 16) % 256) + c1 = chr(b >> 24) + retval += (c1 + c2 + c3 + c4) + if hitEod: + retval = retval[:-4+hitEod] + group = [] + x += 1 + return retval + else: + if isinstance(data, str): + data = data.encode('ascii') + n = b = 0 + out = bytearray() + for c in data: + if ord('!') <= c and c <= ord('u'): + n += 1 + b = b*85+(c-33) + if n == 5: + out += struct.pack(b'>L',b) + n = b = 0 + elif c == ord('z'): + assert n == 0 + out += b'\0\0\0\0' + elif c == ord('~'): + if n: + for _ in range(5-n): + b = b*85+84 + out += struct.pack(b'>L',b)[:n-1] + break + return bytes(out) + decode = staticmethod(decode) + + +def decodeStreamData(stream): + from .generic import NameObject + filters = stream.get("/Filter", ()) + if len(filters) and not isinstance(filters[0], NameObject): + # we have a single filter instance + filters = (filters,) + data = stream._data + # If there is not data to decode we should not try to decode the data. + if data: + for filterType in filters: + if filterType == "/FlateDecode" or filterType == "/Fl": + data = FlateDecode.decode(data, stream.get("/DecodeParms")) + elif filterType == "/ASCIIHexDecode" or filterType == "/AHx": + data = ASCIIHexDecode.decode(data) + elif filterType == "/LZWDecode" or filterType == "/LZW": + data = LZWDecode.decode(data, stream.get("/DecodeParms")) + elif filterType == "/ASCII85Decode" or filterType == "/A85": + data = ASCII85Decode.decode(data) + elif filterType == "/Crypt": + decodeParams = stream.get("/DecodeParams", {}) + if "/Name" not in decodeParams and "/Type" not in decodeParams: + pass + else: + raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet") + else: + # unsupported filter + raise NotImplementedError("unsupported filter %s" % filterType) + return data diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/generic.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/generic.py new file mode 100644 index 0000000..df1e028 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/generic.py @@ -0,0 +1,1222 @@ +# vim: sw=4:expandtab:foldmethod=marker +# +# Copyright (c) 2006, Mathieu Fenniak +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +""" +Implementation of generic PDF objects (dictionary, number, string, and so on) +""" +__author__ = "Mathieu Fenniak" +__author_email__ = "biziqe@mathieu.fenniak.net" + +import re +from .utils import readNonWhitespace, RC4_encrypt, skipOverComment +from .utils import b_, u_, chr_, ord_ +from .utils import PdfStreamError +import warnings +from . import filters +from . import utils +import decimal +import codecs +import sys +#import debugging + +ObjectPrefix = b_('/<[tf(n%') +NumberSigns = b_('+-') +IndirectPattern = re.compile(b_(r"(\d+)\s+(\d+)\s+R[^a-zA-Z]")) + + +def readObject(stream, pdf): + tok = stream.read(1) + stream.seek(-1, 1) # reset to start + idx = ObjectPrefix.find(tok) + if idx == 0: + # name object + return NameObject.readFromStream(stream, pdf) + elif idx == 1: + # hexadecimal string OR dictionary + peek = stream.read(2) + stream.seek(-2, 1) # reset to start + if peek == b_('<<'): + return DictionaryObject.readFromStream(stream, pdf) + else: + return readHexStringFromStream(stream) + elif idx == 2: + # array object + return ArrayObject.readFromStream(stream, pdf) + elif idx == 3 or idx == 4: + # boolean object + return BooleanObject.readFromStream(stream) + elif idx == 5: + # string object + return readStringFromStream(stream) + elif idx == 6: + # null object + return NullObject.readFromStream(stream) + elif idx == 7: + # comment + while tok not in (b_('\r'), b_('\n')): + tok = stream.read(1) + tok = readNonWhitespace(stream) + stream.seek(-1, 1) + return readObject(stream, pdf) + else: + # number object OR indirect reference + if tok in NumberSigns: + # number + return NumberObject.readFromStream(stream) + peek = stream.read(20) + stream.seek(-len(peek), 1) # reset to start + if IndirectPattern.match(peek) != None: + return IndirectObject.readFromStream(stream, pdf) + else: + return NumberObject.readFromStream(stream) + + +class PdfObject(object): + def getObject(self): + """Resolves indirect references.""" + return self + + +class NullObject(PdfObject): + def writeToStream(self, stream, encryption_key): + stream.write(b_("null")) + + def readFromStream(stream): + nulltxt = stream.read(4) + if nulltxt != b_("null"): + raise utils.PdfReadError("Could not read Null object") + return NullObject() + readFromStream = staticmethod(readFromStream) + + +class BooleanObject(PdfObject): + def __init__(self, value): + self.value = value + + def writeToStream(self, stream, encryption_key): + if self.value: + stream.write(b_("true")) + else: + stream.write(b_("false")) + + def readFromStream(stream): + word = stream.read(4) + if word == b_("true"): + return BooleanObject(True) + elif word == b_("fals"): + stream.read(1) + return BooleanObject(False) + else: + raise utils.PdfReadError('Could not read Boolean object') + readFromStream = staticmethod(readFromStream) + + +class ArrayObject(list, PdfObject): + def writeToStream(self, stream, encryption_key): + stream.write(b_("[")) + for data in self: + stream.write(b_(" ")) + data.writeToStream(stream, encryption_key) + stream.write(b_(" ]")) + + def readFromStream(stream, pdf): + arr = ArrayObject() + tmp = stream.read(1) + if tmp != b_("["): + raise utils.PdfReadError("Could not read array") + while True: + # skip leading whitespace + tok = stream.read(1) + while tok.isspace(): + tok = stream.read(1) + stream.seek(-1, 1) + # check for array ending + peekahead = stream.read(1) + if peekahead == b_("]"): + break + stream.seek(-1, 1) + # read and append obj + arr.append(readObject(stream, pdf)) + return arr + readFromStream = staticmethod(readFromStream) + + +class IndirectObject(PdfObject): + def __init__(self, idnum, generation, pdf): + self.idnum = idnum + self.generation = generation + self.pdf = pdf + + def getObject(self): + return self.pdf.getObject(self).getObject() + + def __repr__(self): + return "IndirectObject(%r, %r)" % (self.idnum, self.generation) + + def __eq__(self, other): + return ( + other != None and + isinstance(other, IndirectObject) and + self.idnum == other.idnum and + self.generation == other.generation and + self.pdf is other.pdf + ) + + def __ne__(self, other): + return not self.__eq__(other) + + def writeToStream(self, stream, encryption_key): + stream.write(b_("%s %s R" % (self.idnum, self.generation))) + + def readFromStream(stream, pdf): + idnum = b_("") + while True: + tok = stream.read(1) + if not tok: + # stream has truncated prematurely + raise PdfStreamError("Stream has ended unexpectedly") + if tok.isspace(): + break + idnum += tok + generation = b_("") + while True: + tok = stream.read(1) + if not tok: + # stream has truncated prematurely + raise PdfStreamError("Stream has ended unexpectedly") + if tok.isspace(): + if not generation: + continue + break + generation += tok + r = readNonWhitespace(stream) + if r != b_("R"): + raise utils.PdfReadError("Error reading indirect object reference at byte %s" % utils.hexStr(stream.tell())) + return IndirectObject(int(idnum), int(generation), pdf) + readFromStream = staticmethod(readFromStream) + + +class FloatObject(decimal.Decimal, PdfObject): + def __new__(cls, value="0", context=None): + try: + return decimal.Decimal.__new__(cls, utils.str_(value), context) + except: + return decimal.Decimal.__new__(cls, str(value)) + + def __repr__(self): + if self == self.to_integral(): + return str(self.quantize(decimal.Decimal(1))) + else: + # XXX: this adds useless extraneous zeros. + return "%.5f" % self + + def as_numeric(self): + return float(b_(repr(self))) + + def writeToStream(self, stream, encryption_key): + stream.write(b_(repr(self))) + + +class NumberObject(int, PdfObject): + NumberPattern = re.compile(b_('[^+-.0-9]')) + ByteDot = b_(".") + + def __new__(cls, value): + val = int(value) + try: + return int.__new__(cls, val) + except OverflowError: + return int.__new__(cls, 0) + + def as_numeric(self): + return int(b_(repr(self))) + + def writeToStream(self, stream, encryption_key): + stream.write(b_(repr(self))) + + def readFromStream(stream): + num = utils.readUntilRegex(stream, NumberObject.NumberPattern) + if num.find(NumberObject.ByteDot) != -1: + return FloatObject(num) + else: + return NumberObject(num) + readFromStream = staticmethod(readFromStream) + + +## +# Given a string (either a "str" or "unicode"), create a ByteStringObject or a +# TextStringObject to represent the string. +def createStringObject(string): + if isinstance(string, utils.string_type): + return TextStringObject(string) + elif isinstance(string, utils.bytes_type): + try: + if string.startswith(codecs.BOM_UTF16_BE): + retval = TextStringObject(string.decode("utf-16")) + retval.autodetect_utf16 = True + return retval + else: + # This is probably a big performance hit here, but we need to + # convert string objects into the text/unicode-aware version if + # possible... and the only way to check if that's possible is + # to try. Some strings are strings, some are just byte arrays. + retval = TextStringObject(decode_pdfdocencoding(string)) + retval.autodetect_pdfdocencoding = True + return retval + except UnicodeDecodeError: + return ByteStringObject(string) + else: + raise TypeError("createStringObject should have str or unicode arg") + + +def readHexStringFromStream(stream): + stream.read(1) + txt = "" + x = b_("") + while True: + tok = readNonWhitespace(stream) + if not tok: + # stream has truncated prematurely + raise PdfStreamError("Stream has ended unexpectedly") + if tok == b_(">"): + break + x += tok + if len(x) == 2: + txt += chr(int(x, base=16)) + x = b_("") + if len(x) == 1: + x += b_("0") + if len(x) == 2: + txt += chr(int(x, base=16)) + return createStringObject(b_(txt)) + + +def readStringFromStream(stream): + tok = stream.read(1) + parens = 1 + txt = b_("") + while True: + tok = stream.read(1) + if not tok: + # stream has truncated prematurely + raise PdfStreamError("Stream has ended unexpectedly") + if tok == b_("("): + parens += 1 + elif tok == b_(")"): + parens -= 1 + if parens == 0: + break + elif tok == b_("\\"): + tok = stream.read(1) + if tok == b_("n"): + tok = b_("\n") + elif tok == b_("r"): + tok = b_("\r") + elif tok == b_("t"): + tok = b_("\t") + elif tok == b_("b"): + tok = b_("\b") + elif tok == b_("f"): + tok = b_("\f") + elif tok == b_("c"): + tok = b_("\c") + elif tok == b_("("): + tok = b_("(") + elif tok == b_(")"): + tok = b_(")") + elif tok == b_("/"): + tok = b_("/") + elif tok == b_("\\"): + tok = b_("\\") + elif tok in (b_(" "), b_("/"), b_("%"), b_("<"), b_(">"), b_("["), + b_("]"), b_("#"), b_("_"), b_("&"), b_('$')): + # odd/unnessecary escape sequences we have encountered + tok = b_(tok) + elif tok.isdigit(): + # "The number ddd may consist of one, two, or three + # octal digits; high-order overflow shall be ignored. + # Three octal digits shall be used, with leading zeros + # as needed, if the next character of the string is also + # a digit." (PDF reference 7.3.4.2, p 16) + for i in range(2): + ntok = stream.read(1) + if ntok.isdigit(): + tok += ntok + else: + break + tok = b_(chr(int(tok, base=8))) + elif tok in b_("\n\r"): + # This case is hit when a backslash followed by a line + # break occurs. If it's a multi-char EOL, consume the + # second character: + tok = stream.read(1) + if not tok in b_("\n\r"): + stream.seek(-1, 1) + # Then don't add anything to the actual string, since this + # line break was escaped: + tok = b_('') + else: + raise utils.PdfReadError(r"Unexpected escaped string: %s" % tok) + txt += tok + return createStringObject(txt) + + +## +# Represents a string object where the text encoding could not be determined. +# This occurs quite often, as the PDF spec doesn't provide an alternate way to +# represent strings -- for example, the encryption data stored in files (like +# /O) is clearly not text, but is still stored in a "String" object. +class ByteStringObject(utils.bytes_type, PdfObject): + + ## + # For compatibility with TextStringObject.original_bytes. This method + # returns self. + original_bytes = property(lambda self: self) + + def writeToStream(self, stream, encryption_key): + bytearr = self + if encryption_key: + bytearr = RC4_encrypt(encryption_key, bytearr) + stream.write(b_("<")) + stream.write(utils.hexencode(bytearr)) + stream.write(b_(">")) + + +## +# Represents a string object that has been decoded into a real unicode string. +# If read from a PDF document, this string appeared to match the +# PDFDocEncoding, or contained a UTF-16BE BOM mark to cause UTF-16 decoding to +# occur. +class TextStringObject(utils.string_type, PdfObject): + autodetect_pdfdocencoding = False + autodetect_utf16 = False + + ## + # It is occasionally possible that a text string object gets created where + # a byte string object was expected due to the autodetection mechanism -- + # if that occurs, this "original_bytes" property can be used to + # back-calculate what the original encoded bytes were. + original_bytes = property(lambda self: self.get_original_bytes()) + + def get_original_bytes(self): + # We're a text string object, but the library is trying to get our raw + # bytes. This can happen if we auto-detected this string as text, but + # we were wrong. It's pretty common. Return the original bytes that + # would have been used to create this object, based upon the autodetect + # method. + if self.autodetect_utf16: + return codecs.BOM_UTF16_BE + self.encode("utf-16be") + elif self.autodetect_pdfdocencoding: + return encode_pdfdocencoding(self) + else: + raise Exception("no information about original bytes") + + def writeToStream(self, stream, encryption_key): + # Try to write the string out as a PDFDocEncoding encoded string. It's + # nicer to look at in the PDF file. Sadly, we take a performance hit + # here for trying... + try: + bytearr = encode_pdfdocencoding(self) + except UnicodeEncodeError: + bytearr = codecs.BOM_UTF16_BE + self.encode("utf-16be") + if encryption_key: + bytearr = RC4_encrypt(encryption_key, bytearr) + obj = ByteStringObject(bytearr) + obj.writeToStream(stream, None) + else: + stream.write(b_("(")) + for c in bytearr: + if not chr_(c).isalnum() and c != b_(' '): + stream.write(b_("\\%03o" % ord_(c))) + else: + stream.write(b_(chr_(c))) + stream.write(b_(")")) + + +class NameObject(str, PdfObject): + delimiterPattern = re.compile(b_(r"\s+|[\(\)<>\[\]{}/%]")) + surfix = b_("/") + + def writeToStream(self, stream, encryption_key): + stream.write(b_(self)) + + def readFromStream(stream, pdf): + debug = False + if debug: print((stream.tell())) + name = stream.read(1) + if name != NameObject.surfix: + raise utils.PdfReadError("name read error") + name += utils.readUntilRegex(stream, NameObject.delimiterPattern, + ignore_eof=True) + if debug: print(name) + try: + return NameObject(name.decode('utf-8')) + except (UnicodeEncodeError, UnicodeDecodeError) as e: + # Name objects should represent irregular characters + # with a '#' followed by the symbol's hex number + if not pdf.strict: + warnings.warn("Illegal character in Name Object", utils.PdfReadWarning) + return NameObject(name) + else: + raise utils.PdfReadError("Illegal character in Name Object") + + readFromStream = staticmethod(readFromStream) + + +class DictionaryObject(dict, PdfObject): + def raw_get(self, key): + return dict.__getitem__(self, key) + + def __setitem__(self, key, value): + if not isinstance(key, PdfObject): + raise ValueError("key must be PdfObject") + if not isinstance(value, PdfObject): + raise ValueError("value must be PdfObject") + return dict.__setitem__(self, key, value) + + def setdefault(self, key, value=None): + if not isinstance(key, PdfObject): + raise ValueError("key must be PdfObject") + if not isinstance(value, PdfObject): + raise ValueError("value must be PdfObject") + return dict.setdefault(self, key, value) + + def __getitem__(self, key): + return dict.__getitem__(self, key).getObject() + + ## + # Retrieves XMP (Extensible Metadata Platform) data relevant to the + # this object, if available. + #

+ # Stability: Added in v1.12, will exist for all future v1.x releases. + # @return Returns a {@link #xmp.XmpInformation XmlInformation} instance + # that can be used to access XMP metadata from the document. Can also + # return None if no metadata was found on the document root. + def getXmpMetadata(self): + metadata = self.get("/Metadata", None) + if metadata == None: + return None + metadata = metadata.getObject() + from . import xmp + if not isinstance(metadata, xmp.XmpInformation): + metadata = xmp.XmpInformation(metadata) + self[NameObject("/Metadata")] = metadata + return metadata + + ## + # Read-only property that accesses the {@link + # #DictionaryObject.getXmpData getXmpData} function. + #

+ # Stability: Added in v1.12, will exist for all future v1.x releases. + xmpMetadata = property(lambda self: self.getXmpMetadata(), None, None) + + def writeToStream(self, stream, encryption_key): + stream.write(b_("<<\n")) + for key, value in list(self.items()): + key.writeToStream(stream, encryption_key) + stream.write(b_(" ")) + value.writeToStream(stream, encryption_key) + stream.write(b_("\n")) + stream.write(b_(">>")) + + def readFromStream(stream, pdf): + debug = False + tmp = stream.read(2) + if tmp != b_("<<"): + raise utils.PdfReadError("Dictionary read error at byte %s: stream must begin with '<<'" % utils.hexStr(stream.tell())) + data = {} + while True: + tok = readNonWhitespace(stream) + if tok == b_('\x00'): + continue + elif tok == b_('%'): + stream.seek(-1, 1) + skipOverComment(stream) + continue + if not tok: + # stream has truncated prematurely + raise PdfStreamError("Stream has ended unexpectedly") + + if debug: print(("Tok:", tok)) + if tok == b_(">"): + stream.read(1) + break + stream.seek(-1, 1) + key = readObject(stream, pdf) + tok = readNonWhitespace(stream) + stream.seek(-1, 1) + value = readObject(stream, pdf) + if not data.get(key): + data[key] = value + elif pdf.strict: + # multiple definitions of key not permitted + raise utils.PdfReadError("Multiple definitions in dictionary at byte %s for key %s" \ + % (utils.hexStr(stream.tell()), key)) + else: + warnings.warn("Multiple definitions in dictionary at byte %s for key %s" \ + % (utils.hexStr(stream.tell()), key), utils.PdfReadWarning) + + pos = stream.tell() + s = readNonWhitespace(stream) + if s == b_('s') and stream.read(5) == b_('tream'): + eol = stream.read(1) + # odd PDF file output has spaces after 'stream' keyword but before EOL. + # patch provided by Danial Sandler + while eol == b_(' '): + eol = stream.read(1) + assert eol in (b_("\n"), b_("\r")) + if eol == b_("\r"): + # read \n after + if stream.read(1) != b_('\n'): + stream.seek(-1, 1) + # this is a stream object, not a dictionary + assert "/Length" in data + length = data["/Length"] + if debug: print(data) + if isinstance(length, IndirectObject): + t = stream.tell() + length = pdf.getObject(length) + stream.seek(t, 0) + data["__streamdata__"] = stream.read(length) + if debug: print("here") + #if debug: print(binascii.hexlify(data["__streamdata__"])) + e = readNonWhitespace(stream) + ndstream = stream.read(8) + if (e + ndstream) != b_("endstream"): + # (sigh) - the odd PDF file has a length that is too long, so + # we need to read backwards to find the "endstream" ending. + # ReportLab (unknown version) generates files with this bug, + # and Python users into PDF files tend to be our audience. + # we need to do this to correct the streamdata and chop off + # an extra character. + pos = stream.tell() + stream.seek(-10, 1) + end = stream.read(9) + if end == b_("endstream"): + # we found it by looking back one character further. + data["__streamdata__"] = data["__streamdata__"][:-1] + else: + if debug: print(("E", e, ndstream, debugging.toHex(end))) + stream.seek(pos, 0) + raise utils.PdfReadError("Unable to find 'endstream' marker after stream at byte %s." % utils.hexStr(stream.tell())) + else: + stream.seek(pos, 0) + if "__streamdata__" in data: + return StreamObject.initializeFromDictionary(data) + else: + retval = DictionaryObject() + retval.update(data) + return retval + readFromStream = staticmethod(readFromStream) + + +class TreeObject(DictionaryObject): + def __init__(self): + DictionaryObject.__init__(self) + + def hasChildren(self): + return '/First' in self + + def __iter__(self): + return self.children() + + def children(self): + if not self.hasChildren(): + raise StopIteration + + child = self['/First'] + while True: + yield child + if child == self['/Last']: + raise StopIteration + child = child['/Next'] + + def addChild(self, child, pdf): + childObj = child.getObject() + child = pdf.getReference(childObj) + assert isinstance(child, IndirectObject) + + if '/First' not in self: + self[NameObject('/First')] = child + self[NameObject('/Count')] = NumberObject(0) + prev = None + else: + prev = self['/Last'] + + self[NameObject('/Last')] = child + self[NameObject('/Count')] = NumberObject(self[NameObject('/Count')] + 1) + + if prev: + prevRef = pdf.getReference(prev) + assert isinstance(prevRef, IndirectObject) + childObj[NameObject('/Prev')] = prevRef + prev[NameObject('/Next')] = child + + parentRef = pdf.getReference(self) + assert isinstance(parentRef, IndirectObject) + childObj[NameObject('/Parent')] = parentRef + + def removeChild(self, child): + childObj = child.getObject() + + if NameObject('/Parent') not in childObj: + raise ValueError("Removed child does not appear to be a tree item") + elif childObj[NameObject('/Parent')] != self: + raise ValueError("Removed child is not a member of this tree") + + found = False + prevRef = None + prev = None + curRef = self[NameObject('/First')] + cur = curRef.getObject() + lastRef = self[NameObject('/Last')] + last = lastRef.getObject() + while cur != None: + if cur == childObj: + if prev == None: + if NameObject('/Next') in cur: + # Removing first tree node + nextRef = cur[NameObject('/Next')] + next = nextRef.getObject() + del next[NameObject('/Prev')] + self[NameObject('/First')] = nextRef + self[NameObject('/Count')] = self[NameObject('/Count')] - 1 + + else: + # Removing only tree node + assert self[NameObject('/Count')] == 1 + del self[NameObject('/Count')] + del self[NameObject('/First')] + if NameObject('/Last') in self: + del self[NameObject('/Last')] + else: + if NameObject('/Next') in cur: + # Removing middle tree node + nextRef = cur[NameObject('/Next')] + next = nextRef.getObject() + next[NameObject('/Prev')] = prevRef + prev[NameObject('/Next')] = nextRef + self[NameObject('/Count')] = self[NameObject('/Count')] - 1 + else: + # Removing last tree node + assert cur == last + del prev[NameObject('/Next')] + self[NameObject('/Last')] = prevRef + self[NameObject('/Count')] = self[NameObject('/Count')] - 1 + found = True + break + + prevRef = curRef + prev = cur + if NameObject('/Next') in cur: + curRef = cur[NameObject('/Next')] + cur = curRef.getObject() + else: + curRef = None + cur = None + + if not found: + raise ValueError("Removal couldn't find item in tree") + + del childObj[NameObject('/Parent')] + if NameObject('/Next') in childObj: + del childObj[NameObject('/Next')] + if NameObject('/Prev') in childObj: + del childObj[NameObject('/Prev')] + + def emptyTree(self): + for child in self: + childObj = child.getObject() + del childObj[NameObject('/Parent')] + if NameObject('/Next') in childObj: + del childObj[NameObject('/Next')] + if NameObject('/Prev') in childObj: + del childObj[NameObject('/Prev')] + + if NameObject('/Count') in self: + del self[NameObject('/Count')] + if NameObject('/First') in self: + del self[NameObject('/First')] + if NameObject('/Last') in self: + del self[NameObject('/Last')] + + +class StreamObject(DictionaryObject): + def __init__(self): + self._data = None + self.decodedSelf = None + + def writeToStream(self, stream, encryption_key): + self[NameObject("/Length")] = NumberObject(len(self._data)) + DictionaryObject.writeToStream(self, stream, encryption_key) + del self["/Length"] + stream.write(b_("\nstream\n")) + data = self._data + if encryption_key: + data = RC4_encrypt(encryption_key, data) + stream.write(data) + stream.write(b_("\nendstream")) + + def initializeFromDictionary(data): + if "/Filter" in data: + retval = EncodedStreamObject() + else: + retval = DecodedStreamObject() + retval._data = data["__streamdata__"] + del data["__streamdata__"] + del data["/Length"] + retval.update(data) + return retval + initializeFromDictionary = staticmethod(initializeFromDictionary) + + def flateEncode(self): + if "/Filter" in self: + f = self["/Filter"] + if isinstance(f, ArrayObject): + f.insert(0, NameObject("/FlateDecode")) + else: + newf = ArrayObject() + newf.append(NameObject("/FlateDecode")) + newf.append(f) + f = newf + else: + f = NameObject("/FlateDecode") + retval = EncodedStreamObject() + retval[NameObject("/Filter")] = f + retval._data = filters.FlateDecode.encode(self._data) + return retval + + +class DecodedStreamObject(StreamObject): + def getData(self): + return self._data + + def setData(self, data): + self._data = data + + +class EncodedStreamObject(StreamObject): + def __init__(self): + self.decodedSelf = None + + def getData(self): + if self.decodedSelf: + # cached version of decoded object + return self.decodedSelf.getData() + else: + # create decoded object + decoded = DecodedStreamObject() + + decoded._data = filters.decodeStreamData(self) + for key, value in list(self.items()): + if not key in ("/Length", "/Filter", "/DecodeParms"): + decoded[key] = value + self.decodedSelf = decoded + return decoded._data + + def setData(self, data): + raise utils.PdfReadError("Creating EncodedStreamObject is not currently supported") + + +class RectangleObject(ArrayObject): + """ + This class is used to represent *page boxes* in PyPDF2. These boxes include: + + * :attr:`artBox ` + * :attr:`bleedBox ` + * :attr:`cropBox ` + * :attr:`mediaBox ` + * :attr:`trimBox ` + """ + def __init__(self, arr): + # must have four points + assert len(arr) == 4 + # automatically convert arr[x] into NumberObject(arr[x]) if necessary + ArrayObject.__init__(self, [self.ensureIsNumber(x) for x in arr]) + + def ensureIsNumber(self, value): + if not isinstance(value, (NumberObject, FloatObject)): + value = FloatObject(value) + return value + + def __repr__(self): + return "RectangleObject(%s)" % repr(list(self)) + + def getLowerLeft_x(self): + return self[0] + + def getLowerLeft_y(self): + return self[1] + + def getUpperRight_x(self): + return self[2] + + def getUpperRight_y(self): + return self[3] + + def getUpperLeft_x(self): + return self.getLowerLeft_x() + + def getUpperLeft_y(self): + return self.getUpperRight_y() + + def getLowerRight_x(self): + return self.getUpperRight_x() + + def getLowerRight_y(self): + return self.getLowerLeft_y() + + def getLowerLeft(self): + return self.getLowerLeft_x(), self.getLowerLeft_y() + + def getLowerRight(self): + return self.getLowerRight_x(), self.getLowerRight_y() + + def getUpperLeft(self): + return self.getUpperLeft_x(), self.getUpperLeft_y() + + def getUpperRight(self): + return self.getUpperRight_x(), self.getUpperRight_y() + + def setLowerLeft(self, value): + self[0], self[1] = [self.ensureIsNumber(x) for x in value] + + def setLowerRight(self, value): + self[2], self[1] = [self.ensureIsNumber(x) for x in value] + + def setUpperLeft(self, value): + self[0], self[3] = [self.ensureIsNumber(x) for x in value] + + def setUpperRight(self, value): + self[2], self[3] = [self.ensureIsNumber(x) for x in value] + + def getWidth(self): + return self.getUpperRight_x() - self.getLowerLeft_x() + + def getHeight(self): + return self.getUpperRight_y() - self.getLowerLeft_y() + + lowerLeft = property(getLowerLeft, setLowerLeft, None, None) + """ + Property to read and modify the lower left coordinate of this box + in (x,y) form. + """ + lowerRight = property(getLowerRight, setLowerRight, None, None) + """ + Property to read and modify the lower right coordinate of this box + in (x,y) form. + """ + upperLeft = property(getUpperLeft, setUpperLeft, None, None) + """ + Property to read and modify the upper left coordinate of this box + in (x,y) form. + """ + upperRight = property(getUpperRight, setUpperRight, None, None) + """ + Property to read and modify the upper right coordinate of this box + in (x,y) form. + """ + + +class Field(TreeObject): + """ + A class representing a field dictionary. This class is accessed through + :meth:`getFields()` + """ + def __init__(self, data): + DictionaryObject.__init__(self) + attributes = ("/FT", "/Parent", "/Kids", "/T", "/TU", "/TM", "/Ff", + "/V", "/DV", "/AA") + for attr in attributes: + try: + self[NameObject(attr)] = data[attr] + except KeyError: + pass + + fieldType = property(lambda self: self.get("/FT")) + """ + Read-only property accessing the type of this field. + """ + + parent = property(lambda self: self.get("/Parent")) + """ + Read-only property accessing the parent of this field. + """ + + kids = property(lambda self: self.get("/Kids")) + """ + Read-only property accessing the kids of this field. + """ + + name = property(lambda self: self.get("/T")) + """ + Read-only property accessing the name of this field. + """ + + altName = property(lambda self: self.get("/TU")) + """ + Read-only property accessing the alternate name of this field. + """ + + mappingName = property(lambda self: self.get("/TM")) + """ + Read-only property accessing the mapping name of this field. This + name is used by PyPDF2 as a key in the dictionary returned by + :meth:`getFields()` + """ + + flags = property(lambda self: self.get("/Ff")) + """ + Read-only property accessing the field flags, specifying various + characteristics of the field (see Table 8.70 of the PDF 1.7 reference). + """ + + value = property(lambda self: self.get("/V")) + """ + Read-only property accessing the value of this field. Format + varies based on field type. + """ + + defaultValue = property(lambda self: self.get("/DV")) + """ + Read-only property accessing the default value of this field. + """ + + additionalActions = property(lambda self: self.get("/AA")) + """ + Read-only property accessing the additional actions dictionary. + This dictionary defines the field's behavior in response to trigger events. + See Section 8.5.2 of the PDF 1.7 reference. + """ + + +class Destination(TreeObject): + """ + A class representing a destination within a PDF file. + See section 8.2.1 of the PDF 1.6 reference. + + :param str title: Title of this destination. + :param int page: Page number of this destination. + :param str typ: How the destination is displayed. + :param args: Additional arguments may be necessary depending on the type. + :raises PdfReadError: If destination type is invalid. + + Valid ``typ`` arguments (see PDF spec for details): + /Fit No additional arguments + /XYZ [left] [top] [zoomFactor] + /FitH [top] + /FitV [left] + /FitR [left] [bottom] [right] [top] + /FitB No additional arguments + /FitBH [top] + /FitBV [left] + """ + def __init__(self, title, page, typ, *args): + DictionaryObject.__init__(self) + self[NameObject("/Title")] = title + self[NameObject("/Page")] = page + self[NameObject("/Type")] = typ + + # from table 8.2 of the PDF 1.7 reference. + if typ == "/XYZ": + (self[NameObject("/Left")], self[NameObject("/Top")], + self[NameObject("/Zoom")]) = args + elif typ == "/FitR": + (self[NameObject("/Left")], self[NameObject("/Bottom")], + self[NameObject("/Right")], self[NameObject("/Top")]) = args + elif typ in ["/FitH", "/FitBH"]: + self[NameObject("/Top")], = args + elif typ in ["/FitV", "/FitBV"]: + self[NameObject("/Left")], = args + elif typ in ["/Fit", "/FitB"]: + pass + else: + raise utils.PdfReadError("Unknown Destination Type: %r" % typ) + + def getDestArray(self): + return ArrayObject([self.raw_get('/Page'), self['/Type']] + [self[x] for x in ['/Left', '/Bottom', '/Right', '/Top', '/Zoom'] if x in self]) + + def writeToStream(self, stream, encryption_key): + stream.write(b_("<<\n")) + key = NameObject('/D') + key.writeToStream(stream, encryption_key) + stream.write(b_(" ")) + value = self.getDestArray() + value.writeToStream(stream, encryption_key) + + key = NameObject("/S") + key.writeToStream(stream, encryption_key) + stream.write(b_(" ")) + value = NameObject("/GoTo") + value.writeToStream(stream, encryption_key) + + stream.write(b_("\n")) + stream.write(b_(">>")) + + title = property(lambda self: self.get("/Title")) + """ + Read-only property accessing the destination title. + + :rtype: str + """ + + page = property(lambda self: self.get("/Page")) + """ + Read-only property accessing the destination page number. + + :rtype: int + """ + + typ = property(lambda self: self.get("/Type")) + """ + Read-only property accessing the destination type. + + :rtype: str + """ + + zoom = property(lambda self: self.get("/Zoom", None)) + """ + Read-only property accessing the zoom factor. + + :rtype: int, or ``None`` if not available. + """ + + left = property(lambda self: self.get("/Left", None)) + """ + Read-only property accessing the left horizontal coordinate. + + :rtype: int, or ``None`` if not available. + """ + + right = property(lambda self: self.get("/Right", None)) + """ + Read-only property accessing the right horizontal coordinate. + + :rtype: int, or ``None`` if not available. + """ + + top = property(lambda self: self.get("/Top", None)) + """ + Read-only property accessing the top vertical coordinate. + + :rtype: int, or ``None`` if not available. + """ + + bottom = property(lambda self: self.get("/Bottom", None)) + """ + Read-only property accessing the bottom vertical coordinate. + + :rtype: int, or ``None`` if not available. + """ + + +class Bookmark(Destination): + def writeToStream(self, stream, encryption_key): + stream.write(b_("<<\n")) + for key in [NameObject(x) for x in ['/Title', '/Parent', '/First', '/Last', '/Next', '/Prev'] if x in self]: + key.writeToStream(stream, encryption_key) + stream.write(b_(" ")) + value = self.raw_get(key) + value.writeToStream(stream, encryption_key) + stream.write(b_("\n")) + key = NameObject('/Dest') + key.writeToStream(stream, encryption_key) + stream.write(b_(" ")) + value = self.getDestArray() + value.writeToStream(stream, encryption_key) + stream.write(b_("\n")) + stream.write(b_(">>")) + + +def encode_pdfdocencoding(unicode_string): + retval = b_('') + for c in unicode_string: + try: + retval += b_(chr(_pdfDocEncoding_rev[c])) + except KeyError: + raise UnicodeEncodeError("pdfdocencoding", c, -1, -1, + "does not exist in translation table") + return retval + + +def decode_pdfdocencoding(byte_array): + retval = u_('') + for b in byte_array: + c = _pdfDocEncoding[ord_(b)] + if c == u_('\u0000'): + raise UnicodeDecodeError("pdfdocencoding", utils.barray(b), -1, -1, + "does not exist in translation table") + retval += c + return retval + +_pdfDocEncoding = ( + u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), + u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), + u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), u_('\u0000'), + u_('\u02d8'), u_('\u02c7'), u_('\u02c6'), u_('\u02d9'), u_('\u02dd'), u_('\u02db'), u_('\u02da'), u_('\u02dc'), + u_('\u0020'), u_('\u0021'), u_('\u0022'), u_('\u0023'), u_('\u0024'), u_('\u0025'), u_('\u0026'), u_('\u0027'), + u_('\u0028'), u_('\u0029'), u_('\u002a'), u_('\u002b'), u_('\u002c'), u_('\u002d'), u_('\u002e'), u_('\u002f'), + u_('\u0030'), u_('\u0031'), u_('\u0032'), u_('\u0033'), u_('\u0034'), u_('\u0035'), u_('\u0036'), u_('\u0037'), + u_('\u0038'), u_('\u0039'), u_('\u003a'), u_('\u003b'), u_('\u003c'), u_('\u003d'), u_('\u003e'), u_('\u003f'), + u_('\u0040'), u_('\u0041'), u_('\u0042'), u_('\u0043'), u_('\u0044'), u_('\u0045'), u_('\u0046'), u_('\u0047'), + u_('\u0048'), u_('\u0049'), u_('\u004a'), u_('\u004b'), u_('\u004c'), u_('\u004d'), u_('\u004e'), u_('\u004f'), + u_('\u0050'), u_('\u0051'), u_('\u0052'), u_('\u0053'), u_('\u0054'), u_('\u0055'), u_('\u0056'), u_('\u0057'), + u_('\u0058'), u_('\u0059'), u_('\u005a'), u_('\u005b'), u_('\u005c'), u_('\u005d'), u_('\u005e'), u_('\u005f'), + u_('\u0060'), u_('\u0061'), u_('\u0062'), u_('\u0063'), u_('\u0064'), u_('\u0065'), u_('\u0066'), u_('\u0067'), + u_('\u0068'), u_('\u0069'), u_('\u006a'), u_('\u006b'), u_('\u006c'), u_('\u006d'), u_('\u006e'), u_('\u006f'), + u_('\u0070'), u_('\u0071'), u_('\u0072'), u_('\u0073'), u_('\u0074'), u_('\u0075'), u_('\u0076'), u_('\u0077'), + u_('\u0078'), u_('\u0079'), u_('\u007a'), u_('\u007b'), u_('\u007c'), u_('\u007d'), u_('\u007e'), u_('\u0000'), + u_('\u2022'), u_('\u2020'), u_('\u2021'), u_('\u2026'), u_('\u2014'), u_('\u2013'), u_('\u0192'), u_('\u2044'), + u_('\u2039'), u_('\u203a'), u_('\u2212'), u_('\u2030'), u_('\u201e'), u_('\u201c'), u_('\u201d'), u_('\u2018'), + u_('\u2019'), u_('\u201a'), u_('\u2122'), u_('\ufb01'), u_('\ufb02'), u_('\u0141'), u_('\u0152'), u_('\u0160'), + u_('\u0178'), u_('\u017d'), u_('\u0131'), u_('\u0142'), u_('\u0153'), u_('\u0161'), u_('\u017e'), u_('\u0000'), + u_('\u20ac'), u_('\u00a1'), u_('\u00a2'), u_('\u00a3'), u_('\u00a4'), u_('\u00a5'), u_('\u00a6'), u_('\u00a7'), + u_('\u00a8'), u_('\u00a9'), u_('\u00aa'), u_('\u00ab'), u_('\u00ac'), u_('\u0000'), u_('\u00ae'), u_('\u00af'), + u_('\u00b0'), u_('\u00b1'), u_('\u00b2'), u_('\u00b3'), u_('\u00b4'), u_('\u00b5'), u_('\u00b6'), u_('\u00b7'), + u_('\u00b8'), u_('\u00b9'), u_('\u00ba'), u_('\u00bb'), u_('\u00bc'), u_('\u00bd'), u_('\u00be'), u_('\u00bf'), + u_('\u00c0'), u_('\u00c1'), u_('\u00c2'), u_('\u00c3'), u_('\u00c4'), u_('\u00c5'), u_('\u00c6'), u_('\u00c7'), + u_('\u00c8'), u_('\u00c9'), u_('\u00ca'), u_('\u00cb'), u_('\u00cc'), u_('\u00cd'), u_('\u00ce'), u_('\u00cf'), + u_('\u00d0'), u_('\u00d1'), u_('\u00d2'), u_('\u00d3'), u_('\u00d4'), u_('\u00d5'), u_('\u00d6'), u_('\u00d7'), + u_('\u00d8'), u_('\u00d9'), u_('\u00da'), u_('\u00db'), u_('\u00dc'), u_('\u00dd'), u_('\u00de'), u_('\u00df'), + u_('\u00e0'), u_('\u00e1'), u_('\u00e2'), u_('\u00e3'), u_('\u00e4'), u_('\u00e5'), u_('\u00e6'), u_('\u00e7'), + u_('\u00e8'), u_('\u00e9'), u_('\u00ea'), u_('\u00eb'), u_('\u00ec'), u_('\u00ed'), u_('\u00ee'), u_('\u00ef'), + u_('\u00f0'), u_('\u00f1'), u_('\u00f2'), u_('\u00f3'), u_('\u00f4'), u_('\u00f5'), u_('\u00f6'), u_('\u00f7'), + u_('\u00f8'), u_('\u00f9'), u_('\u00fa'), u_('\u00fb'), u_('\u00fc'), u_('\u00fd'), u_('\u00fe'), u_('\u00ff') +) + +assert len(_pdfDocEncoding) == 256 + +_pdfDocEncoding_rev = {} +for i in range(256): + char = _pdfDocEncoding[i] + if char == u_("\u0000"): + continue + assert char not in _pdfDocEncoding_rev + _pdfDocEncoding_rev[char] = i diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/merger.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/merger.py new file mode 100644 index 0000000..27702ad --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/merger.py @@ -0,0 +1,553 @@ +# vim: sw=4:expandtab:foldmethod=marker +# +# Copyright (c) 2006, Mathieu Fenniak +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from .generic import * +from .utils import isString, str_ +from .pdf import PdfFileReader, PdfFileWriter +from .pagerange import PageRange +from sys import version_info +if version_info < ( 3, 0 ): + from cStringIO import StringIO + StreamIO = StringIO +else: + from io import BytesIO + from io import FileIO as file + StreamIO = BytesIO + + +class _MergedPage(object): + """ + _MergedPage is used internally by PdfFileMerger to collect necessary + information on each page that is being merged. + """ + def __init__(self, pagedata, src, id): + self.src = src + self.pagedata = pagedata + self.out_pagedata = None + self.id = id + + +class PdfFileMerger(object): + """ + Initializes a PdfFileMerger object. PdfFileMerger merges multiple PDFs + into a single PDF. It can concatenate, slice, insert, or any combination + of the above. + + See the functions :meth:`merge()` (or :meth:`append()`) + and :meth:`write()` for usage information. + + :param bool strict: Determines whether user should be warned of all + problems and also causes some correctable problems to be fatal. + Defaults to ``True``. + """ + + def __init__(self, strict=True): + self.inputs = [] + self.pages = [] + self.output = PdfFileWriter() + self.bookmarks = [] + self.named_dests = [] + self.id_count = 0 + self.strict = strict + + def merge(self, position, fileobj, bookmark=None, pages=None, import_bookmarks=True): + """ + Merges the pages from the given file into the output file at the + specified page number. + + :param int position: The *page number* to insert this file. File will + be inserted after the given number. + + :param fileobj: A File Object or an object that supports the standard read + and seek methods similar to a File Object. Could also be a + string representing a path to a PDF file. + + :param str bookmark: Optionally, you may specify a bookmark to be applied at + the beginning of the included file by supplying the text of the bookmark. + + :param pages: can be a :ref:`Page Range ` or a ``(start, stop[, step])`` tuple + to merge only the specified range of pages from the source + document into the output document. + + :param bool import_bookmarks: You may prevent the source document's bookmarks + from being imported by specifying this as ``False``. + """ + + # This parameter is passed to self.inputs.append and means + # that the stream used was created in this method. + my_file = False + + # If the fileobj parameter is a string, assume it is a path + # and create a file object at that location. If it is a file, + # copy the file's contents into a BytesIO (or StreamIO) stream object; if + # it is a PdfFileReader, copy that reader's stream into a + # BytesIO (or StreamIO) stream. + # If fileobj is none of the above types, it is not modified + decryption_key = None + if isString(fileobj): + fileobj = file(fileobj, 'rb') + my_file = True + elif isinstance(fileobj, file): + fileobj.seek(0) + filecontent = fileobj.read() + fileobj = StreamIO(filecontent) + my_file = True + elif isinstance(fileobj, PdfFileReader): + orig_tell = fileobj.stream.tell() + fileobj.stream.seek(0) + filecontent = StreamIO(fileobj.stream.read()) + fileobj.stream.seek(orig_tell) # reset the stream to its original location + fileobj = filecontent + if hasattr(fileobj, '_decryption_key'): + decryption_key = fileobj._decryption_key + my_file = True + + # Create a new PdfFileReader instance using the stream + # (either file or BytesIO or StringIO) created above + pdfr = PdfFileReader(fileobj, strict=self.strict) + if decryption_key is not None: + pdfr._decryption_key = decryption_key + + # Find the range of pages to merge. + if pages == None: + pages = (0, pdfr.getNumPages()) + elif isinstance(pages, PageRange): + pages = pages.indices(pdfr.getNumPages()) + elif not isinstance(pages, tuple): + raise TypeError('"pages" must be a tuple of (start, stop[, step])') + + srcpages = [] + if bookmark: + bookmark = Bookmark(TextStringObject(bookmark), NumberObject(self.id_count), NameObject('/Fit')) + + outline = [] + if import_bookmarks: + outline = pdfr.getOutlines() + outline = self._trim_outline(pdfr, outline, pages) + + if bookmark: + self.bookmarks += [bookmark, outline] + else: + self.bookmarks += outline + + dests = pdfr.namedDestinations + dests = self._trim_dests(pdfr, dests, pages) + self.named_dests += dests + + # Gather all the pages that are going to be merged + for i in range(*pages): + pg = pdfr.getPage(i) + + id = self.id_count + self.id_count += 1 + + mp = _MergedPage(pg, pdfr, id) + + srcpages.append(mp) + + self._associate_dests_to_pages(srcpages) + self._associate_bookmarks_to_pages(srcpages) + + # Slice to insert the pages at the specified position + self.pages[position:position] = srcpages + + # Keep track of our input files so we can close them later + self.inputs.append((fileobj, pdfr, my_file)) + + def append(self, fileobj, bookmark=None, pages=None, import_bookmarks=True): + """ + Identical to the :meth:`merge()` method, but assumes you want to concatenate + all pages onto the end of the file instead of specifying a position. + + :param fileobj: A File Object or an object that supports the standard read + and seek methods similar to a File Object. Could also be a + string representing a path to a PDF file. + + :param str bookmark: Optionally, you may specify a bookmark to be applied at + the beginning of the included file by supplying the text of the bookmark. + + :param pages: can be a :ref:`Page Range ` or a ``(start, stop[, step])`` tuple + to merge only the specified range of pages from the source + document into the output document. + + :param bool import_bookmarks: You may prevent the source document's bookmarks + from being imported by specifying this as ``False``. + """ + + self.merge(len(self.pages), fileobj, bookmark, pages, import_bookmarks) + + def write(self, fileobj): + """ + Writes all data that has been merged to the given output file. + + :param fileobj: Output file. Can be a filename or any kind of + file-like object. + """ + my_file = False + if isString(fileobj): + fileobj = file(fileobj, 'wb') + my_file = True + + # Add pages to the PdfFileWriter + # The commented out line below was replaced with the two lines below it to allow PdfFileMerger to work with PyPdf 1.13 + for page in self.pages: + self.output.addPage(page.pagedata) + page.out_pagedata = self.output.getReference(self.output._pages.getObject()["/Kids"][-1].getObject()) + #idnum = self.output._objects.index(self.output._pages.getObject()["/Kids"][-1].getObject()) + 1 + #page.out_pagedata = IndirectObject(idnum, 0, self.output) + + # Once all pages are added, create bookmarks to point at those pages + self._write_dests() + self._write_bookmarks() + + # Write the output to the file + self.output.write(fileobj) + + if my_file: + fileobj.close() + + def close(self): + """ + Shuts all file descriptors (input and output) and clears all memory + usage. + """ + self.pages = [] + for fo, pdfr, mine in self.inputs: + if mine: + fo.close() + + self.inputs = [] + self.output = None + + def addMetadata(self, infos): + """ + Add custom metadata to the output. + + :param dict infos: a Python dictionary where each key is a field + and each value is your new metadata. + Example: ``{u'/Title': u'My title'}`` + """ + self.output.addMetadata(infos) + + def setPageLayout(self, layout): + """ + Set the page layout + + :param str layout: The page layout to be used + + Valid layouts are: + /NoLayout Layout explicitly not specified + /SinglePage Show one page at a time + /OneColumn Show one column at a time + /TwoColumnLeft Show pages in two columns, odd-numbered pages on the left + /TwoColumnRight Show pages in two columns, odd-numbered pages on the right + /TwoPageLeft Show two pages at a time, odd-numbered pages on the left + /TwoPageRight Show two pages at a time, odd-numbered pages on the right + """ + self.output.setPageLayout(layout) + + def setPageMode(self, mode): + """ + Set the page mode. + + :param str mode: The page mode to use. + + Valid modes are: + /UseNone Do not show outlines or thumbnails panels + /UseOutlines Show outlines (aka bookmarks) panel + /UseThumbs Show page thumbnails panel + /FullScreen Fullscreen view + /UseOC Show Optional Content Group (OCG) panel + /UseAttachments Show attachments panel + """ + self.output.setPageMode(mode) + + def _trim_dests(self, pdf, dests, pages): + """ + Removes any named destinations that are not a part of the specified + page set. + """ + new_dests = [] + prev_header_added = True + for k, o in list(dests.items()): + for j in range(*pages): + if pdf.getPage(j).getObject() == o['/Page'].getObject(): + o[NameObject('/Page')] = o['/Page'].getObject() + assert str_(k) == str_(o['/Title']) + new_dests.append(o) + break + return new_dests + + def _trim_outline(self, pdf, outline, pages): + """ + Removes any outline/bookmark entries that are not a part of the + specified page set. + """ + new_outline = [] + prev_header_added = True + for i, o in enumerate(outline): + if isinstance(o, list): + sub = self._trim_outline(pdf, o, pages) + if sub: + if not prev_header_added: + new_outline.append(outline[i-1]) + new_outline.append(sub) + else: + prev_header_added = False + for j in range(*pages): + if pdf.getPage(j).getObject() == o['/Page'].getObject(): + o[NameObject('/Page')] = o['/Page'].getObject() + new_outline.append(o) + prev_header_added = True + break + return new_outline + + def _write_dests(self): + dests = self.named_dests + + for v in dests: + pageno = None + pdf = None + if '/Page' in v: + for i, p in enumerate(self.pages): + if p.id == v['/Page']: + v[NameObject('/Page')] = p.out_pagedata + pageno = i + pdf = p.src + break + if pageno != None: + self.output.addNamedDestinationObject(v) + + def _write_bookmarks(self, bookmarks=None, parent=None): + + if bookmarks == None: + bookmarks = self.bookmarks + + last_added = None + for b in bookmarks: + if isinstance(b, list): + self._write_bookmarks(b, last_added) + continue + + pageno = None + pdf = None + if '/Page' in b: + for i, p in enumerate(self.pages): + if p.id == b['/Page']: + #b[NameObject('/Page')] = p.out_pagedata + args = [NumberObject(p.id), NameObject(b['/Type'])] + #nothing more to add + #if b['/Type'] == '/Fit' or b['/Type'] == '/FitB' + if b['/Type'] == '/FitH' or b['/Type'] == '/FitBH': + if '/Top' in b and not isinstance(b['/Top'], NullObject): + args.append(FloatObject(b['/Top'])) + else: + args.append(FloatObject(0)) + del b['/Top'] + elif b['/Type'] == '/FitV' or b['/Type'] == '/FitBV': + if '/Left' in b and not isinstance(b['/Left'], NullObject): + args.append(FloatObject(b['/Left'])) + else: + args.append(FloatObject(0)) + del b['/Left'] + elif b['/Type'] == '/XYZ': + if '/Left' in b and not isinstance(b['/Left'], NullObject): + args.append(FloatObject(b['/Left'])) + else: + args.append(FloatObject(0)) + if '/Top' in b and not isinstance(b['/Top'], NullObject): + args.append(FloatObject(b['/Top'])) + else: + args.append(FloatObject(0)) + if '/Zoom' in b and not isinstance(b['/Zoom'], NullObject): + args.append(FloatObject(b['/Zoom'])) + else: + args.append(FloatObject(0)) + del b['/Top'], b['/Zoom'], b['/Left'] + elif b['/Type'] == '/FitR': + if '/Left' in b and not isinstance(b['/Left'], NullObject): + args.append(FloatObject(b['/Left'])) + else: + args.append(FloatObject(0)) + if '/Bottom' in b and not isinstance(b['/Bottom'], NullObject): + args.append(FloatObject(b['/Bottom'])) + else: + args.append(FloatObject(0)) + if '/Right' in b and not isinstance(b['/Right'], NullObject): + args.append(FloatObject(b['/Right'])) + else: + args.append(FloatObject(0)) + if '/Top' in b and not isinstance(b['/Top'], NullObject): + args.append(FloatObject(b['/Top'])) + else: + args.append(FloatObject(0)) + del b['/Left'], b['/Right'], b['/Bottom'], b['/Top'] + + b[NameObject('/A')] = DictionaryObject({NameObject('/S'): NameObject('/GoTo'), NameObject('/D'): ArrayObject(args)}) + + pageno = i + pdf = p.src + break + if pageno != None: + del b['/Page'], b['/Type'] + last_added = self.output.addBookmarkDict(b, parent) + + def _associate_dests_to_pages(self, pages): + for nd in self.named_dests: + pageno = None + np = nd['/Page'] + + if isinstance(np, NumberObject): + continue + + for p in pages: + if np.getObject() == p.pagedata.getObject(): + pageno = p.id + + if pageno != None: + nd[NameObject('/Page')] = NumberObject(pageno) + else: + raise ValueError("Unresolved named destination '%s'" % (nd['/Title'],)) + + def _associate_bookmarks_to_pages(self, pages, bookmarks=None): + if bookmarks == None: + bookmarks = self.bookmarks + + for b in bookmarks: + if isinstance(b, list): + self._associate_bookmarks_to_pages(pages, b) + continue + + pageno = None + bp = b['/Page'] + + if isinstance(bp, NumberObject): + continue + + for p in pages: + if bp.getObject() == p.pagedata.getObject(): + pageno = p.id + + if pageno != None: + b[NameObject('/Page')] = NumberObject(pageno) + else: + raise ValueError("Unresolved bookmark '%s'" % (b['/Title'],)) + + def findBookmark(self, bookmark, root=None): + if root == None: + root = self.bookmarks + + for i, b in enumerate(root): + if isinstance(b, list): + res = self.findBookmark(bookmark, b) + if res: + return [i] + res + elif b == bookmark or b['/Title'] == bookmark: + return [i] + + return None + + def addBookmark(self, title, pagenum, parent=None): + """ + Add a bookmark to this PDF file. + + :param str title: Title to use for this bookmark. + :param int pagenum: Page number this bookmark will point to. + :param parent: A reference to a parent bookmark to create nested + bookmarks. + """ + if parent == None: + iloc = [len(self.bookmarks)-1] + elif isinstance(parent, list): + iloc = parent + else: + iloc = self.findBookmark(parent) + + dest = Bookmark(TextStringObject(title), NumberObject(pagenum), NameObject('/FitH'), NumberObject(826)) + + if parent == None: + self.bookmarks.append(dest) + else: + bmparent = self.bookmarks + for i in iloc[:-1]: + bmparent = bmparent[i] + npos = iloc[-1]+1 + if npos < len(bmparent) and isinstance(bmparent[npos], list): + bmparent[npos].append(dest) + else: + bmparent.insert(npos, [dest]) + return dest + + def addNamedDestination(self, title, pagenum): + """ + Add a destination to the output. + + :param str title: Title to use + :param int pagenum: Page number this destination points at. + """ + + dest = Destination(TextStringObject(title), NumberObject(pagenum), NameObject('/FitH'), NumberObject(826)) + self.named_dests.append(dest) + + +class OutlinesObject(list): + def __init__(self, pdf, tree, parent=None): + list.__init__(self) + self.tree = tree + self.pdf = pdf + self.parent = parent + + def remove(self, index): + obj = self[index] + del self[index] + self.tree.removeChild(obj) + + def add(self, title, pagenum): + pageRef = self.pdf.getObject(self.pdf._pages)['/Kids'][pagenum] + action = DictionaryObject() + action.update({ + NameObject('/D') : ArrayObject([pageRef, NameObject('/FitH'), NumberObject(826)]), + NameObject('/S') : NameObject('/GoTo') + }) + actionRef = self.pdf._addObject(action) + bookmark = TreeObject() + + bookmark.update({ + NameObject('/A'): actionRef, + NameObject('/Title'): createStringObject(title), + }) + + self.pdf._addObject(bookmark) + + self.tree.addChild(bookmark) + + def removeAll(self): + for child in [x for x in self.tree.children()]: + self.tree.removeChild(child) + self.pop() diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/pagerange.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/pagerange.py new file mode 100644 index 0000000..ce96ec5 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/pagerange.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +""" +Representation and utils for ranges of PDF file pages. + +Copyright (c) 2014, Steve Witham . +All rights reserved. This software is available under a BSD license; +see https://github.com/mstamy2/PyPDF2/blob/master/LICENSE +""" + +import re +from .utils import isString + +_INT_RE = r"(0|-?[1-9]\d*)" # A decimal int, don't allow "-0". +PAGE_RANGE_RE = "^({int}|({int}?(:{int}?(:{int}?)?)))$".format(int=_INT_RE) +# groups: 12 34 5 6 7 8 + + +class ParseError(Exception): + pass + + +PAGE_RANGE_HELP = """Remember, page indices start with zero. + Page range expression examples: + : all pages. -1 last page. + 22 just the 23rd page. :-1 all but the last page. + 0:3 the first three pages. -2 second-to-last page. + :3 the first three pages. -2: last two pages. + 5: from the sixth page onward. -3:-1 third & second to last. + The third, "stride" or "step" number is also recognized. + ::2 0 2 4 ... to the end. 3:0:-1 3 2 1 but not 0. + 1:10:2 1 3 5 7 9 2::-1 2 1 0. + ::-1 all pages in reverse order. +""" + + +class PageRange(object): + """ + A slice-like representation of a range of page indices, + i.e. page numbers, only starting at zero. + The syntax is like what you would put between brackets [ ]. + The slice is one of the few Python types that can't be subclassed, + but this class converts to and from slices, and allows similar use. + o PageRange(str) parses a string representing a page range. + o PageRange(slice) directly "imports" a slice. + o to_slice() gives the equivalent slice. + o str() and repr() allow printing. + o indices(n) is like slice.indices(n). + """ + + def __init__(self, arg): + """ + Initialize with either a slice -- giving the equivalent page range, + or a PageRange object -- making a copy, + or a string like + "int", "[int]:[int]" or "[int]:[int]:[int]", + where the brackets indicate optional ints. + {page_range_help} + Note the difference between this notation and arguments to slice(): + slice(3) means the first three pages; + PageRange("3") means the range of only the fourth page. + However PageRange(slice(3)) means the first three pages. + """ + if isinstance(arg, slice): + self._slice = arg + return + + if isinstance(arg, PageRange): + self._slice = arg.to_slice() + return + + m = isString(arg) and re.match(PAGE_RANGE_RE, arg) + if not m: + raise ParseError(arg) + elif m.group(2): + # Special case: just an int means a range of one page. + start = int(m.group(2)) + stop = start + 1 if start != -1 else None + self._slice = slice(start, stop) + else: + self._slice = slice(*[int(g) if g else None + for g in m.group(4, 6, 8)]) + + # Just formatting this when there is __doc__ for __init__ + if __init__.__doc__: + __init__.__doc__ = __init__.__doc__.format(page_range_help=PAGE_RANGE_HELP) + + @staticmethod + def valid(input): + """ True if input is a valid initializer for a PageRange. """ + return isinstance(input, slice) or \ + isinstance(input, PageRange) or \ + (isString(input) + and bool(re.match(PAGE_RANGE_RE, input))) + + def to_slice(self): + """ Return the slice equivalent of this page range. """ + return self._slice + + def __str__(self): + """ A string like "1:2:3". """ + s = self._slice + if s.step == None: + if s.start != None and s.stop == s.start + 1: + return str(s.start) + + indices = s.start, s.stop + else: + indices = s.start, s.stop, s.step + return ':'.join("" if i == None else str(i) for i in indices) + + def __repr__(self): + """ A string like "PageRange('1:2:3')". """ + return "PageRange(" + repr(str(self)) + ")" + + def indices(self, n): + """ + n is the length of the list of pages to choose from. + Returns arguments for range(). See help(slice.indices). + """ + return self._slice.indices(n) + + +PAGE_RANGE_ALL = PageRange(":") # The range of all pages. + + +def parse_filename_page_ranges(args): + """ + Given a list of filenames and page ranges, return a list of + (filename, page_range) pairs. + First arg must be a filename; other ags are filenames, page-range + expressions, slice objects, or PageRange objects. + A filename not followed by a page range indicates all pages of the file. + """ + pairs = [] + pdf_filename = None + did_page_range = False + for arg in args + [None]: + if PageRange.valid(arg): + if not pdf_filename: + raise ValueError("The first argument must be a filename, " \ + "not a page range.") + + pairs.append( (pdf_filename, PageRange(arg)) ) + did_page_range = True + else: + # New filename or end of list--do all of the previous file? + if pdf_filename and not did_page_range: + pairs.append( (pdf_filename, PAGE_RANGE_ALL) ) + + pdf_filename = arg + did_page_range = False + return pairs diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/pdf.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/pdf.py new file mode 100644 index 0000000..6378df7 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/pdf.py @@ -0,0 +1,3099 @@ +# -*- coding: utf-8 -*- +# +# vim: sw=4:expandtab:foldmethod=marker +# +# Copyright (c) 2006, Mathieu Fenniak +# Copyright (c) 2007, Ashish Kulkarni +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +""" +A pure-Python PDF library with an increasing number of capabilities. +See README for links to FAQ, documentation, homepage, etc. +""" + +__author__ = "Mathieu Fenniak" +__author_email__ = "biziqe@mathieu.fenniak.net" + +__maintainer__ = "Phaseit, Inc." +__maintainer_email = "PyPDF2@phaseit.net" + +import string +import math +import struct +import sys +import uuid +from sys import version_info +if version_info < ( 3, 0 ): + from cStringIO import StringIO +else: + from io import StringIO + +if version_info < ( 3, 0 ): + BytesIO = StringIO +else: + from io import BytesIO + +from . import filters +from . import utils +import warnings +import codecs +from .generic import * +from .utils import readNonWhitespace, readUntilWhitespace, ConvertFunctionsToVirtualList +from .utils import isString, b_, u_, ord_, chr_, str_, formatWarning + +if version_info < ( 2, 4 ): + from sets import ImmutableSet as frozenset + +if version_info < ( 2, 5 ): + from md5 import md5 +else: + from hashlib import md5 +import uuid + + +class PdfFileWriter(object): + """ + This class supports writing PDF files out, given pages produced by another + class (typically :class:`PdfFileReader`). + """ + def __init__(self): + self._header = b_("%PDF-1.3") + self._objects = [] # array of indirect objects + + # The root of our page tree node. + pages = DictionaryObject() + pages.update({ + NameObject("/Type"): NameObject("/Pages"), + NameObject("/Count"): NumberObject(0), + NameObject("/Kids"): ArrayObject(), + }) + self._pages = self._addObject(pages) + + # info object + info = DictionaryObject() + info.update({ + NameObject("/Producer"): createStringObject(codecs.BOM_UTF16_BE + u_("PyPDF2").encode('utf-16be')) + }) + self._info = self._addObject(info) + + # root object + root = DictionaryObject() + root.update({ + NameObject("/Type"): NameObject("/Catalog"), + NameObject("/Pages"): self._pages, + }) + self._root = None + self._root_object = root + + def _addObject(self, obj): + self._objects.append(obj) + return IndirectObject(len(self._objects), 0, self) + + def getObject(self, ido): + if ido.pdf != self: + raise ValueError("pdf must be self") + return self._objects[ido.idnum - 1] + + def _addPage(self, page, action): + assert page["/Type"] == "/Page" + page[NameObject("/Parent")] = self._pages + page = self._addObject(page) + pages = self.getObject(self._pages) + action(pages["/Kids"], page) + pages[NameObject("/Count")] = NumberObject(pages["/Count"] + 1) + + def addPage(self, page): + """ + Adds a page to this PDF file. The page is usually acquired from a + :class:`PdfFileReader` instance. + + :param PageObject page: The page to add to the document. Should be + an instance of :class:`PageObject` + """ + self._addPage(page, list.append) + + def insertPage(self, page, index=0): + """ + Insert a page in this PDF file. The page is usually acquired from a + :class:`PdfFileReader` instance. + + :param PageObject page: The page to add to the document. This + argument should be an instance of :class:`PageObject`. + :param int index: Position at which the page will be inserted. + """ + self._addPage(page, lambda l, p: l.insert(index, p)) + + def getPage(self, pageNumber): + """ + Retrieves a page by number from this PDF file. + + :param int pageNumber: The page number to retrieve + (pages begin at zero) + :return: the page at the index given by *pageNumber* + :rtype: :class:`PageObject` + """ + pages = self.getObject(self._pages) + # XXX: crude hack + return pages["/Kids"][pageNumber].getObject() + + def getNumPages(self): + """ + :return: the number of pages. + :rtype: int + """ + pages = self.getObject(self._pages) + return int(pages[NameObject("/Count")]) + + def addBlankPage(self, width=None, height=None): + """ + Appends a blank page to this PDF file and returns it. If no page size + is specified, use the size of the last page. + + :param float width: The width of the new page expressed in default user + space units. + :param float height: The height of the new page expressed in default + user space units. + :return: the newly appended page + :rtype: :class:`PageObject` + :raises PageSizeNotDefinedError: if width and height are not defined + and previous page does not exist. + """ + page = PageObject.createBlankPage(self, width, height) + self.addPage(page) + return page + + def insertBlankPage(self, width=None, height=None, index=0): + """ + Inserts a blank page to this PDF file and returns it. If no page size + is specified, use the size of the last page. + + :param float width: The width of the new page expressed in default user + space units. + :param float height: The height of the new page expressed in default + user space units. + :param int index: Position to add the page. + :return: the newly appended page + :rtype: :class:`PageObject` + :raises PageSizeNotDefinedError: if width and height are not defined + and previous page does not exist. + """ + if width is None or height is None and \ + (self.getNumPages() - 1) >= index: + oldpage = self.getPage(index) + width = oldpage.mediaBox.getWidth() + height = oldpage.mediaBox.getHeight() + page = PageObject.createBlankPage(self, width, height) + self.insertPage(page, index) + return page + + def addJS(self, javascript): + """ + Add Javascript which will launch upon opening this PDF. + + :param str javascript: Your Javascript. + + >>> output.addJS("this.print({bUI:true,bSilent:false,bShrinkToFit:true});") + # Example: This will launch the print window when the PDF is opened. + """ + js = DictionaryObject() + js.update({ + NameObject("/Type"): NameObject("/Action"), + NameObject("/S"): NameObject("/JavaScript"), + NameObject("/JS"): NameObject("(%s)" % javascript) + }) + js_indirect_object = self._addObject(js) + + # We need a name for parameterized javascript in the pdf file, but it can be anything. + js_string_name = str(uuid.uuid4()) + + js_name_tree = DictionaryObject() + js_name_tree.update({ + NameObject("/JavaScript"): DictionaryObject({ + NameObject("/Names"): ArrayObject([createStringObject(js_string_name), js_indirect_object]) + }) + }) + self._addObject(js_name_tree) + + self._root_object.update({ + NameObject("/OpenAction"): js_indirect_object, + NameObject("/Names"): js_name_tree + }) + + def addAttachment(self, fname, fdata): + """ + Embed a file inside the PDF. + + :param str fname: The filename to display. + :param str fdata: The data in the file. + + Reference: + https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf + Section 7.11.3 + """ + + # We need 3 entries: + # * The file's data + # * The /Filespec entry + # * The file's name, which goes in the Catalog + + + # The entry for the file + """ Sample: + 8 0 obj + << + /Length 12 + /Type /EmbeddedFile + >> + stream + Hello world! + endstream + endobj + """ + file_entry = DecodedStreamObject() + file_entry.setData(fdata) + file_entry.update({ + NameObject("/Type"): NameObject("/EmbeddedFile") + }) + + # The Filespec entry + """ Sample: + 7 0 obj + << + /Type /Filespec + /F (hello.txt) + /EF << /F 8 0 R >> + >> + """ + efEntry = DictionaryObject() + efEntry.update({ NameObject("/F"):file_entry }) + + filespec = DictionaryObject() + filespec.update({ + NameObject("/Type"): NameObject("/Filespec"), + NameObject("/F"): createStringObject(fname), # Perhaps also try TextStringObject + NameObject("/EF"): efEntry + }) + + # Then create the entry for the root, as it needs a reference to the Filespec + """ Sample: + 1 0 obj + << + /Type /Catalog + /Outlines 2 0 R + /Pages 3 0 R + /Names << /EmbeddedFiles << /Names [(hello.txt) 7 0 R] >> >> + >> + endobj + + """ + embeddedFilesNamesDictionary = DictionaryObject() + embeddedFilesNamesDictionary.update({ + NameObject("/Names"): ArrayObject([createStringObject(fname), filespec]) + }) + + embeddedFilesDictionary = DictionaryObject() + embeddedFilesDictionary.update({ + NameObject("/EmbeddedFiles"): embeddedFilesNamesDictionary + }) + # Update the root + self._root_object.update({ + NameObject("/Names"): embeddedFilesDictionary + }) + + def appendPagesFromReader(self, reader, after_page_append=None): + """ + Copy pages from reader to writer. Includes an optional callback parameter + which is invoked after pages are appended to the writer. + + :param reader: a PdfFileReader object from which to copy page + annotations to this writer object. The writer's annots + will then be updated + :callback after_page_append (function): Callback function that is invoked after + each page is appended to the writer. Callback signature: + + :param writer_pageref (PDF page reference): Reference to the page + appended to the writer. + """ + # Get page count from writer and reader + reader_num_pages = reader.getNumPages() + writer_num_pages = self.getNumPages() + + # Copy pages from reader to writer + for rpagenum in range(0, reader_num_pages): + reader_page = reader.getPage(rpagenum) + self.addPage(reader_page) + writer_page = self.getPage(writer_num_pages+rpagenum) + # Trigger callback, pass writer page as parameter + if callable(after_page_append): after_page_append(writer_page) + + def updatePageFormFieldValues(self, page, fields): + ''' + Update the form field values for a given page from a fields dictionary. + Copy field texts and values from fields to page. + + :param page: Page reference from PDF writer where the annotations + and field data will be updated. + :param fields: a Python dictionary of field names (/T) and text + values (/V) + ''' + # Iterate through pages, update field values + for j in range(0, len(page['/Annots'])): + writer_annot = page['/Annots'][j].getObject() + for field in fields: + if writer_annot.get('/T') == field: + writer_annot.update({ + NameObject("/V"): TextStringObject(fields[field]) + }) + + def cloneReaderDocumentRoot(self, reader): + ''' + Copy the reader document root to the writer. + + :param reader: PdfFileReader from the document root should be copied. + :callback after_page_append + ''' + self._root_object = reader.trailer['/Root'] + + def cloneDocumentFromReader(self, reader, after_page_append=None): + ''' + Create a copy (clone) of a document from a PDF file reader + + :param reader: PDF file reader instance from which the clone + should be created. + :callback after_page_append (function): Callback function that is invoked after + each page is appended to the writer. Signature includes a reference to the + appended page (delegates to appendPagesFromReader). Callback signature: + + :param writer_pageref (PDF page reference): Reference to the page just + appended to the document. + ''' + self.cloneReaderDocumentRoot(reader) + self.appendPagesFromReader(reader, after_page_append) + + def encrypt(self, user_pwd, owner_pwd = None, use_128bit = True): + """ + Encrypt this PDF file with the PDF Standard encryption handler. + + :param str user_pwd: The "user password", which allows for opening + and reading the PDF file with the restrictions provided. + :param str owner_pwd: The "owner password", which allows for + opening the PDF files without any restrictions. By default, + the owner password is the same as the user password. + :param bool use_128bit: flag as to whether to use 128bit + encryption. When false, 40bit encryption will be used. By default, + this flag is on. + """ + import time, random + if owner_pwd == None: + owner_pwd = user_pwd + if use_128bit: + V = 2 + rev = 3 + keylen = int(128 / 8) + else: + V = 1 + rev = 2 + keylen = int(40 / 8) + # permit everything: + P = -1 + O = ByteStringObject(_alg33(owner_pwd, user_pwd, rev, keylen)) + ID_1 = ByteStringObject(md5(b_(repr(time.time()))).digest()) + ID_2 = ByteStringObject(md5(b_(repr(random.random()))).digest()) + self._ID = ArrayObject((ID_1, ID_2)) + if rev == 2: + U, key = _alg34(user_pwd, O, P, ID_1) + else: + assert rev == 3 + U, key = _alg35(user_pwd, rev, keylen, O, P, ID_1, False) + encrypt = DictionaryObject() + encrypt[NameObject("/Filter")] = NameObject("/Standard") + encrypt[NameObject("/V")] = NumberObject(V) + if V == 2: + encrypt[NameObject("/Length")] = NumberObject(keylen * 8) + encrypt[NameObject("/R")] = NumberObject(rev) + encrypt[NameObject("/O")] = ByteStringObject(O) + encrypt[NameObject("/U")] = ByteStringObject(U) + encrypt[NameObject("/P")] = NumberObject(P) + self._encrypt = self._addObject(encrypt) + self._encrypt_key = key + + def write(self, stream): + """ + Writes the collection of pages added to this object out as a PDF file. + + :param stream: An object to write the file to. The object must support + the write method and the tell method, similar to a file object. + """ + if hasattr(stream, 'mode') and 'b' not in stream.mode: + warnings.warn("File <%s> to write to is not in binary mode. It may not be written to correctly." % stream.name) + debug = False + import struct + + if not self._root: + self._root = self._addObject(self._root_object) + + externalReferenceMap = {} + + # PDF objects sometimes have circular references to their /Page objects + # inside their object tree (for example, annotations). Those will be + # indirect references to objects that we've recreated in this PDF. To + # address this problem, PageObject's store their original object + # reference number, and we add it to the external reference map before + # we sweep for indirect references. This forces self-page-referencing + # trees to reference the correct new object location, rather than + # copying in a new copy of the page object. + for objIndex in range(len(self._objects)): + obj = self._objects[objIndex] + if isinstance(obj, PageObject) and obj.indirectRef != None: + data = obj.indirectRef + if data.pdf not in externalReferenceMap: + externalReferenceMap[data.pdf] = {} + if data.generation not in externalReferenceMap[data.pdf]: + externalReferenceMap[data.pdf][data.generation] = {} + externalReferenceMap[data.pdf][data.generation][data.idnum] = IndirectObject(objIndex + 1, 0, self) + + self.stack = [] + if debug: print(("ERM:", externalReferenceMap, "root:", self._root)) + self._sweepIndirectReferences(externalReferenceMap, self._root) + del self.stack + + # Begin writing: + object_positions = [] + stream.write(self._header + b_("\n")) + for i in range(len(self._objects)): + idnum = (i + 1) + obj = self._objects[i] + object_positions.append(stream.tell()) + stream.write(b_(str(idnum) + " 0 obj\n")) + key = None + if hasattr(self, "_encrypt") and idnum != self._encrypt.idnum: + pack1 = struct.pack("` for details. + """ + pageRef = self.getObject(self._pages)['/Kids'][pagenum] + action = DictionaryObject() + zoomArgs = [] + for a in args: + if a is not None: + zoomArgs.append(NumberObject(a)) + else: + zoomArgs.append(NullObject()) + dest = Destination(NameObject("/"+title + " bookmark"), pageRef, NameObject(fit), *zoomArgs) + destArray = dest.getDestArray() + action.update({ + NameObject('/D') : destArray, + NameObject('/S') : NameObject('/GoTo') + }) + actionRef = self._addObject(action) + + outlineRef = self.getOutlineRoot() + + if parent == None: + parent = outlineRef + + bookmark = TreeObject() + + bookmark.update({ + NameObject('/A'): actionRef, + NameObject('/Title'): createStringObject(title), + }) + + if color is not None: + bookmark.update({NameObject('/C'): ArrayObject([FloatObject(c) for c in color])}) + + format = 0 + if italic: + format += 1 + if bold: + format += 2 + if format: + bookmark.update({NameObject('/F'): NumberObject(format)}) + + bookmarkRef = self._addObject(bookmark) + + parent = parent.getObject() + parent.addChild(bookmarkRef, self) + + return bookmarkRef + + def addNamedDestinationObject(self, dest): + destRef = self._addObject(dest) + + nd = self.getNamedDestRoot() + nd.extend([dest['/Title'], destRef]) + + return destRef + + def addNamedDestination(self, title, pagenum): + pageRef = self.getObject(self._pages)['/Kids'][pagenum] + dest = DictionaryObject() + dest.update({ + NameObject('/D') : ArrayObject([pageRef, NameObject('/FitH'), NumberObject(826)]), + NameObject('/S') : NameObject('/GoTo') + }) + + destRef = self._addObject(dest) + nd = self.getNamedDestRoot() + + nd.extend([title, destRef]) + + return destRef + + def removeLinks(self): + """ + Removes links and annotations from this output. + """ + pages = self.getObject(self._pages)['/Kids'] + for page in pages: + pageRef = self.getObject(page) + if "/Annots" in pageRef: + del pageRef['/Annots'] + + def removeImages(self, ignoreByteStringObject=False): + """ + Removes images from this output. + + :param bool ignoreByteStringObject: optional parameter + to ignore ByteString Objects. + """ + pages = self.getObject(self._pages)['/Kids'] + for j in range(len(pages)): + page = pages[j] + pageRef = self.getObject(page) + content = pageRef['/Contents'].getObject() + if not isinstance(content, ContentStream): + content = ContentStream(content, pageRef) + + _operations = [] + seq_graphics = False + for operands, operator in content.operations: + if operator == b_('Tj'): + text = operands[0] + if ignoreByteStringObject: + if not isinstance(text, TextStringObject): + operands[0] = TextStringObject() + elif operator == b_("'"): + text = operands[0] + if ignoreByteStringObject: + if not isinstance(text, TextStringObject): + operands[0] = TextStringObject() + elif operator == b_('"'): + text = operands[2] + if ignoreByteStringObject: + if not isinstance(text, TextStringObject): + operands[2] = TextStringObject() + elif operator == b_("TJ"): + for i in range(len(operands[0])): + if ignoreByteStringObject: + if not isinstance(operands[0][i], TextStringObject): + operands[0][i] = TextStringObject() + + if operator == b_('q'): + seq_graphics = True + if operator == b_('Q'): + seq_graphics = False + if seq_graphics: + if operator in [b_('cm'), b_('w'), b_('J'), b_('j'), b_('M'), b_('d'), b_('ri'), b_('i'), + b_('gs'), b_('W'), b_('b'), b_('s'), b_('S'), b_('f'), b_('F'), b_('n'), b_('m'), b_('l'), + b_('c'), b_('v'), b_('y'), b_('h'), b_('B'), b_('Do'), b_('sh')]: + continue + if operator == b_('re'): + continue + _operations.append((operands, operator)) + + content.operations = _operations + pageRef.__setitem__(NameObject('/Contents'), content) + + def removeText(self, ignoreByteStringObject=False): + """ + Removes images from this output. + + :param bool ignoreByteStringObject: optional parameter + to ignore ByteString Objects. + """ + pages = self.getObject(self._pages)['/Kids'] + for j in range(len(pages)): + page = pages[j] + pageRef = self.getObject(page) + content = pageRef['/Contents'].getObject() + if not isinstance(content, ContentStream): + content = ContentStream(content, pageRef) + for operands,operator in content.operations: + if operator == b_('Tj'): + text = operands[0] + if not ignoreByteStringObject: + if isinstance(text, TextStringObject): + operands[0] = TextStringObject() + else: + if isinstance(text, TextStringObject) or \ + isinstance(text, ByteStringObject): + operands[0] = TextStringObject() + elif operator == b_("'"): + text = operands[0] + if not ignoreByteStringObject: + if isinstance(text, TextStringObject): + operands[0] = TextStringObject() + else: + if isinstance(text, TextStringObject) or \ + isinstance(text, ByteStringObject): + operands[0] = TextStringObject() + elif operator == b_('"'): + text = operands[2] + if not ignoreByteStringObject: + if isinstance(text, TextStringObject): + operands[2] = TextStringObject() + else: + if isinstance(text, TextStringObject) or \ + isinstance(text, ByteStringObject): + operands[2] = TextStringObject() + elif operator == b_("TJ"): + for i in range(len(operands[0])): + if not ignoreByteStringObject: + if isinstance(operands[0][i], TextStringObject): + operands[0][i] = TextStringObject() + else: + if isinstance(operands[0][i], TextStringObject) or \ + isinstance(operands[0][i], ByteStringObject): + operands[0][i] = TextStringObject() + + pageRef.__setitem__(NameObject('/Contents'), content) + + def addLink(self, pagenum, pagedest, rect, border=None, fit='/Fit', *args): + """ + Add an internal link from a rectangular area to the specified page. + + :param int pagenum: index of the page on which to place the link. + :param int pagedest: index of the page to which the link should go. + :param rect: :class:`RectangleObject` or array of four + integers specifying the clickable rectangular area + ``[xLL, yLL, xUR, yUR]``, or string in the form ``"[ xLL yLL xUR yUR ]"``. + :param border: if provided, an array describing border-drawing + properties. See the PDF spec for details. No border will be + drawn if this argument is omitted. + :param str fit: Page fit or 'zoom' option (see below). Additional arguments may need + to be supplied. Passing ``None`` will be read as a null value for that coordinate. + + Valid zoom arguments (see Table 8.2 of the PDF 1.7 reference for details): + /Fit No additional arguments + /XYZ [left] [top] [zoomFactor] + /FitH [top] + /FitV [left] + /FitR [left] [bottom] [right] [top] + /FitB No additional arguments + /FitBH [top] + /FitBV [left] + """ + + pageLink = self.getObject(self._pages)['/Kids'][pagenum] + pageDest = self.getObject(self._pages)['/Kids'][pagedest] #TODO: switch for external link + pageRef = self.getObject(pageLink) + + if border is not None: + borderArr = [NameObject(n) for n in border[:3]] + if len(border) == 4: + dashPattern = ArrayObject([NameObject(n) for n in border[3]]) + borderArr.append(dashPattern) + else: + borderArr = [NumberObject(0)] * 3 + + if isString(rect): + rect = NameObject(rect) + elif isinstance(rect, RectangleObject): + pass + else: + rect = RectangleObject(rect) + + zoomArgs = [] + for a in args: + if a is not None: + zoomArgs.append(NumberObject(a)) + else: + zoomArgs.append(NullObject()) + dest = Destination(NameObject("/LinkName"), pageDest, NameObject(fit), *zoomArgs) #TODO: create a better name for the link + destArray = dest.getDestArray() + + lnk = DictionaryObject() + lnk.update({ + NameObject('/Type'): NameObject('/Annot'), + NameObject('/Subtype'): NameObject('/Link'), + NameObject('/P'): pageLink, + NameObject('/Rect'): rect, + NameObject('/Border'): ArrayObject(borderArr), + NameObject('/Dest'): destArray + }) + lnkRef = self._addObject(lnk) + + if "/Annots" in pageRef: + pageRef['/Annots'].append(lnkRef) + else: + pageRef[NameObject('/Annots')] = ArrayObject([lnkRef]) + + _valid_layouts = ['/NoLayout', '/SinglePage', '/OneColumn', '/TwoColumnLeft', '/TwoColumnRight', '/TwoPageLeft', '/TwoPageRight'] + + def getPageLayout(self): + """ + Get the page layout. + See :meth:`setPageLayout()` for a description of valid layouts. + + :return: Page layout currently being used. + :rtype: str, None if not specified + """ + try: + return self._root_object['/PageLayout'] + except KeyError: + return None + + def setPageLayout(self, layout): + """ + Set the page layout + + :param str layout: The page layout to be used + + Valid layouts are: + /NoLayout Layout explicitly not specified + /SinglePage Show one page at a time + /OneColumn Show one column at a time + /TwoColumnLeft Show pages in two columns, odd-numbered pages on the left + /TwoColumnRight Show pages in two columns, odd-numbered pages on the right + /TwoPageLeft Show two pages at a time, odd-numbered pages on the left + /TwoPageRight Show two pages at a time, odd-numbered pages on the right + """ + if not isinstance(layout, NameObject): + if layout not in self._valid_layouts: + warnings.warn("Layout should be one of: {}".format(', '.join(self._valid_layouts))) + layout = NameObject(layout) + self._root_object.update({NameObject('/PageLayout'): layout}) + + pageLayout = property(getPageLayout, setPageLayout) + """Read and write property accessing the :meth:`getPageLayout()` + and :meth:`setPageLayout()` methods.""" + + _valid_modes = ['/UseNone', '/UseOutlines', '/UseThumbs', '/FullScreen', '/UseOC', '/UseAttachments'] + + def getPageMode(self): + """ + Get the page mode. + See :meth:`setPageMode()` for a description + of valid modes. + + :return: Page mode currently being used. + :rtype: str, None if not specified + """ + try: + return self._root_object['/PageMode'] + except KeyError: + return None + + def setPageMode(self, mode): + """ + Set the page mode. + + :param str mode: The page mode to use. + + Valid modes are: + /UseNone Do not show outlines or thumbnails panels + /UseOutlines Show outlines (aka bookmarks) panel + /UseThumbs Show page thumbnails panel + /FullScreen Fullscreen view + /UseOC Show Optional Content Group (OCG) panel + /UseAttachments Show attachments panel + """ + if not isinstance(mode, NameObject): + if mode not in self._valid_modes: + warnings.warn("Mode should be one of: {}".format(', '.join(self._valid_modes))) + mode = NameObject(mode) + self._root_object.update({NameObject('/PageMode'): mode}) + + pageMode = property(getPageMode, setPageMode) + """Read and write property accessing the :meth:`getPageMode()` + and :meth:`setPageMode()` methods.""" + + +class PdfFileReader(object): + """ + Initializes a PdfFileReader object. This operation can take some time, as + the PDF stream's cross-reference tables are read into memory. + + :param stream: A File object or an object that supports the standard read + and seek methods similar to a File object. Could also be a + string representing a path to a PDF file. + :param bool strict: Determines whether user should be warned of all + problems and also causes some correctable problems to be fatal. + Defaults to ``True``. + :param warndest: Destination for logging warnings (defaults to + ``sys.stderr``). + :param bool overwriteWarnings: Determines whether to override Python's + ``warnings.py`` module with a custom implementation (defaults to + ``True``). + """ + def __init__(self, stream, strict=True, warndest = None, overwriteWarnings = True): + if overwriteWarnings: + # have to dynamically override the default showwarning since there are no + # public methods that specify the 'file' parameter + def _showwarning(message, category, filename, lineno, file=warndest, line=None): + if file is None: + file = sys.stderr + try: + file.write(formatWarning(message, category, filename, lineno, line)) + except IOError: + pass + warnings.showwarning = _showwarning + self.strict = strict + self.flattenedPages = None + self.resolvedObjects = {} + self.xrefIndex = 0 + self._pageId2Num = None # map page IndirectRef number to Page Number + if hasattr(stream, 'mode') and 'b' not in stream.mode: + warnings.warn("PdfFileReader stream/file object is not in binary mode. It may not be read correctly.", utils.PdfReadWarning) + if isString(stream): + fileobj = open(stream, 'rb') + stream = BytesIO(b_(fileobj.read())) + fileobj.close() + self.read(stream) + self.stream = stream + + self._override_encryption = False + + def getDocumentInfo(self): + """ + Retrieves the PDF file's document information dictionary, if it exists. + Note that some PDF files use metadata streams instead of docinfo + dictionaries, and these metadata streams will not be accessed by this + function. + + :return: the document information of this PDF file + :rtype: :class:`DocumentInformation` or ``None`` if none exists. + """ + if "/Info" not in self.trailer: + return None + obj = self.trailer['/Info'] + retval = DocumentInformation() + retval.update(obj) + return retval + + documentInfo = property(lambda self: self.getDocumentInfo(), None, None) + """Read-only property that accesses the :meth:`getDocumentInfo()` function.""" + + def getXmpMetadata(self): + """ + Retrieves XMP (Extensible Metadata Platform) data from the PDF document + root. + + :return: a :class:`XmpInformation` + instance that can be used to access XMP metadata from the document. + :rtype: :class:`XmpInformation` or + ``None`` if no metadata was found on the document root. + """ + try: + self._override_encryption = True + return self.trailer["/Root"].getXmpMetadata() + finally: + self._override_encryption = False + + xmpMetadata = property(lambda self: self.getXmpMetadata(), None, None) + """ + Read-only property that accesses the + :meth:`getXmpMetadata()` function. + """ + + def getNumPages(self): + """ + Calculates the number of pages in this PDF file. + + :return: number of pages + :rtype: int + :raises PdfReadError: if file is encrypted and restrictions prevent + this action. + """ + + # Flattened pages will not work on an Encrypted PDF; + # the PDF file's page count is used in this case. Otherwise, + # the original method (flattened page count) is used. + if self.isEncrypted: + try: + self._override_encryption = True + self.decrypt('') + return self.trailer["/Root"]["/Pages"]["/Count"] + except: + raise utils.PdfReadError("File has not been decrypted") + finally: + self._override_encryption = False + else: + if self.flattenedPages == None: + self._flatten() + return len(self.flattenedPages) + + numPages = property(lambda self: self.getNumPages(), None, None) + """ + Read-only property that accesses the + :meth:`getNumPages()` function. + """ + + def getPage(self, pageNumber): + """ + Retrieves a page by number from this PDF file. + + :param int pageNumber: The page number to retrieve + (pages begin at zero) + :return: a :class:`PageObject` instance. + :rtype: :class:`PageObject` + """ + ## ensure that we're not trying to access an encrypted PDF + #assert not self.trailer.has_key("/Encrypt") + if self.flattenedPages == None: + self._flatten() + return self.flattenedPages[pageNumber] + + namedDestinations = property(lambda self: + self.getNamedDestinations(), None, None) + """ + Read-only property that accesses the + :meth:`getNamedDestinations()` function. + """ + + # A select group of relevant field attributes. For the complete list, + # see section 8.6.2 of the PDF 1.7 reference. + + def getFields(self, tree = None, retval = None, fileobj = None): + """ + Extracts field data if this PDF contains interactive form fields. + The *tree* and *retval* parameters are for recursive use. + + :param fileobj: A file object (usually a text file) to write + a report to on all interactive form fields found. + :return: A dictionary where each key is a field name, and each + value is a :class:`Field` object. By + default, the mapping name is used for keys. + :rtype: dict, or ``None`` if form data could not be located. + """ + fieldAttributes = {"/FT" : "Field Type", "/Parent" : "Parent", + "/T" : "Field Name", "/TU" : "Alternate Field Name", + "/TM" : "Mapping Name", "/Ff" : "Field Flags", + "/V" : "Value", "/DV" : "Default Value"} + if retval == None: + retval = {} + catalog = self.trailer["/Root"] + # get the AcroForm tree + if "/AcroForm" in catalog: + tree = catalog["/AcroForm"] + else: + return None + if tree == None: + return retval + + self._checkKids(tree, retval, fileobj) + for attr in fieldAttributes: + if attr in tree: + # Tree is a field + self._buildField(tree, retval, fileobj, fieldAttributes) + break + + if "/Fields" in tree: + fields = tree["/Fields"] + for f in fields: + field = f.getObject() + self._buildField(field, retval, fileobj, fieldAttributes) + + return retval + + def _buildField(self, field, retval, fileobj, fieldAttributes): + self._checkKids(field, retval, fileobj) + try: + key = field["/TM"] + except KeyError: + try: + key = field["/T"] + except KeyError: + # Ignore no-name field for now + return + if fileobj: + self._writeField(fileobj, field, fieldAttributes) + fileobj.write("\n") + retval[key] = Field(field) + + def _checkKids(self, tree, retval, fileobj): + if "/Kids" in tree: + # recurse down the tree + for kid in tree["/Kids"]: + self.getFields(kid.getObject(), retval, fileobj) + + def _writeField(self, fileobj, field, fieldAttributes): + order = ["/TM", "/T", "/FT", "/Parent", "/TU", "/Ff", "/V", "/DV"] + for attr in order: + attrName = fieldAttributes[attr] + try: + if attr == "/FT": + # Make the field type value more clear + types = {"/Btn":"Button", "/Tx":"Text", "/Ch": "Choice", + "/Sig":"Signature"} + if field[attr] in types: + fileobj.write(attrName + ": " + types[field[attr]] + "\n") + elif attr == "/Parent": + # Let's just write the name of the parent + try: + name = field["/Parent"]["/TM"] + except KeyError: + name = field["/Parent"]["/T"] + fileobj.write(attrName + ": " + name + "\n") + else: + fileobj.write(attrName + ": " + str(field[attr]) + "\n") + except KeyError: + # Field attribute is N/A or unknown, so don't write anything + pass + + def getNamedDestinations(self, tree=None, retval=None): + """ + Retrieves the named destinations present in the document. + + :return: a dictionary which maps names to + :class:`Destinations`. + :rtype: dict + """ + if retval == None: + retval = {} + catalog = self.trailer["/Root"] + + # get the name tree + if "/Dests" in catalog: + tree = catalog["/Dests"] + elif "/Names" in catalog: + names = catalog['/Names'] + if "/Dests" in names: + tree = names['/Dests'] + + if tree == None: + return retval + + if "/Kids" in tree: + # recurse down the tree + for kid in tree["/Kids"]: + self.getNamedDestinations(kid.getObject(), retval) + + if "/Names" in tree: + names = tree["/Names"] + for i in range(0, len(names), 2): + key = names[i].getObject() + val = names[i+1].getObject() + if isinstance(val, DictionaryObject) and '/D' in val: + val = val['/D'] + dest = self._buildDestination(key, val) + if dest != None: + retval[key] = dest + + return retval + + outlines = property(lambda self: self.getOutlines(), None, None) + """ + Read-only property that accesses the + :meth:`getOutlines()` function. + """ + + def getOutlines(self, node=None, outlines=None): + """ + Retrieves the document outline present in the document. + + :return: a nested list of :class:`Destinations`. + """ + if outlines == None: + outlines = [] + catalog = self.trailer["/Root"] + + # get the outline dictionary and named destinations + if "/Outlines" in catalog: + try: + lines = catalog["/Outlines"] + except utils.PdfReadError: + # this occurs if the /Outlines object reference is incorrect + # for an example of such a file, see https://unglueit-files.s3.amazonaws.com/ebf/7552c42e9280b4476e59e77acc0bc812.pdf + # so continue to load the file without the Bookmarks + return outlines + + if "/First" in lines: + node = lines["/First"] + self._namedDests = self.getNamedDestinations() + + if node == None: + return outlines + + # see if there are any more outlines + while True: + outline = self._buildOutline(node) + if outline: + outlines.append(outline) + + # check for sub-outlines + if "/First" in node: + subOutlines = [] + self.getOutlines(node["/First"], subOutlines) + if subOutlines: + outlines.append(subOutlines) + + if "/Next" not in node: + break + node = node["/Next"] + + return outlines + + def _getPageNumberByIndirect(self, indirectRef): + """Generate _pageId2Num""" + if self._pageId2Num is None: + id2num = {} + for i, x in enumerate(self.pages): + id2num[x.indirectRef.idnum] = i + self._pageId2Num = id2num + + if isinstance(indirectRef, int): + idnum = indirectRef + else: + idnum = indirectRef.idnum + + ret = self._pageId2Num.get(idnum, -1) + return ret + + def getPageNumber(self, page): + """ + Retrieve page number of a given PageObject + + :param PageObject page: The page to get page number. Should be + an instance of :class:`PageObject` + :return: the page number or -1 if page not found + :rtype: int + """ + indirectRef = page.indirectRef + ret = self._getPageNumberByIndirect(indirectRef) + return ret + + def getDestinationPageNumber(self, destination): + """ + Retrieve page number of a given Destination object + + :param Destination destination: The destination to get page number. + Should be an instance of + :class:`Destination` + :return: the page number or -1 if page not found + :rtype: int + """ + indirectRef = destination.page + ret = self._getPageNumberByIndirect(indirectRef) + return ret + + def _buildDestination(self, title, array): + page, typ = array[0:2] + array = array[2:] + return Destination(title, page, typ, *array) + + def _buildOutline(self, node): + dest, title, outline = None, None, None + + if "/A" in node and "/Title" in node: + # Action, section 8.5 (only type GoTo supported) + title = node["/Title"] + action = node["/A"] + if action["/S"] == "/GoTo": + dest = action["/D"] + elif "/Dest" in node and "/Title" in node: + # Destination, section 8.2.1 + title = node["/Title"] + dest = node["/Dest"] + + # if destination found, then create outline + if dest: + if isinstance(dest, ArrayObject): + outline = self._buildDestination(title, dest) + elif isString(dest) and dest in self._namedDests: + outline = self._namedDests[dest] + outline[NameObject("/Title")] = title + else: + raise utils.PdfReadError("Unexpected destination %r" % dest) + return outline + + pages = property(lambda self: ConvertFunctionsToVirtualList(self.getNumPages, self.getPage), + None, None) + """ + Read-only property that emulates a list based upon the + :meth:`getNumPages()` and + :meth:`getPage()` methods. + """ + + def getPageLayout(self): + """ + Get the page layout. + See :meth:`setPageLayout()` + for a description of valid layouts. + + :return: Page layout currently being used. + :rtype: ``str``, ``None`` if not specified + """ + try: + return self.trailer['/Root']['/PageLayout'] + except KeyError: + return None + + pageLayout = property(getPageLayout) + """Read-only property accessing the + :meth:`getPageLayout()` method.""" + + def getPageMode(self): + """ + Get the page mode. + See :meth:`setPageMode()` + for a description of valid modes. + + :return: Page mode currently being used. + :rtype: ``str``, ``None`` if not specified + """ + try: + return self.trailer['/Root']['/PageMode'] + except KeyError: + return None + + pageMode = property(getPageMode) + """Read-only property accessing the + :meth:`getPageMode()` method.""" + + def _flatten(self, pages=None, inherit=None, indirectRef=None): + inheritablePageAttributes = ( + NameObject("/Resources"), NameObject("/MediaBox"), + NameObject("/CropBox"), NameObject("/Rotate") + ) + if inherit == None: + inherit = dict() + if pages == None: + self.flattenedPages = [] + catalog = self.trailer["/Root"].getObject() + pages = catalog["/Pages"].getObject() + + t = "/Pages" + if "/Type" in pages: + t = pages["/Type"] + + if t == "/Pages": + for attr in inheritablePageAttributes: + if attr in pages: + inherit[attr] = pages[attr] + for page in pages["/Kids"]: + addt = {} + if isinstance(page, IndirectObject): + addt["indirectRef"] = page + self._flatten(page.getObject(), inherit, **addt) + elif t == "/Page": + for attr, value in list(inherit.items()): + # if the page has it's own value, it does not inherit the + # parent's value: + if attr not in pages: + pages[attr] = value + pageObj = PageObject(self, indirectRef) + pageObj.update(pages) + self.flattenedPages.append(pageObj) + + def _getObjectFromStream(self, indirectReference): + # indirect reference to object in object stream + # read the entire object stream into memory + debug = False + stmnum, idx = self.xref_objStm[indirectReference.idnum] + if debug: print(("Here1: %s %s"%(stmnum, idx))) + objStm = IndirectObject(stmnum, 0, self).getObject() + if debug: print(("Here2: objStm=%s.. stmnum=%s data=%s"%(objStm, stmnum, objStm.getData()))) + # This is an xref to a stream, so its type better be a stream + assert objStm['/Type'] == '/ObjStm' + # /N is the number of indirect objects in the stream + assert idx < objStm['/N'] + streamData = BytesIO(b_(objStm.getData())) + for i in range(objStm['/N']): + readNonWhitespace(streamData) + streamData.seek(-1, 1) + objnum = NumberObject.readFromStream(streamData) + readNonWhitespace(streamData) + streamData.seek(-1, 1) + offset = NumberObject.readFromStream(streamData) + readNonWhitespace(streamData) + streamData.seek(-1, 1) + if objnum != indirectReference.idnum: + # We're only interested in one object + continue + if self.strict and idx != i: + raise utils.PdfReadError("Object is in wrong index.") + streamData.seek(objStm['/First']+offset, 0) + if debug: + pos = streamData.tell() + streamData.seek(0, 0) + lines = streamData.readlines() + for i in range(0, len(lines)): + print((lines[i])) + streamData.seek(pos, 0) + try: + obj = readObject(streamData, self) + except utils.PdfStreamError as e: + # Stream object cannot be read. Normally, a critical error, but + # Adobe Reader doesn't complain, so continue (in strict mode?) + e = sys.exc_info()[1] + warnings.warn("Invalid stream (index %d) within object %d %d: %s" % \ + (i, indirectReference.idnum, indirectReference.generation, e), utils.PdfReadWarning) + + if self.strict: + raise utils.PdfReadError("Can't read object stream: %s"%e) + # Replace with null. Hopefully it's nothing important. + obj = NullObject() + return obj + + if self.strict: raise utils.PdfReadError("This is a fatal error in strict mode.") + return NullObject() + + def getObject(self, indirectReference): + debug = False + if debug: print(("looking at:", indirectReference.idnum, indirectReference.generation)) + retval = self.cacheGetIndirectObject(indirectReference.generation, + indirectReference.idnum) + if retval != None: + return retval + if indirectReference.generation == 0 and \ + indirectReference.idnum in self.xref_objStm: + retval = self._getObjectFromStream(indirectReference) + elif indirectReference.generation in self.xref and \ + indirectReference.idnum in self.xref[indirectReference.generation]: + start = self.xref[indirectReference.generation][indirectReference.idnum] + if debug: print((" Uncompressed Object", indirectReference.idnum, indirectReference.generation, ":", start)) + self.stream.seek(start, 0) + idnum, generation = self.readObjectHeader(self.stream) + if idnum != indirectReference.idnum and self.xrefIndex: + # Xref table probably had bad indexes due to not being zero-indexed + if self.strict: + raise utils.PdfReadError("Expected object ID (%d %d) does not match actual (%d %d); xref table not zero-indexed." \ + % (indirectReference.idnum, indirectReference.generation, idnum, generation)) + else: pass # xref table is corrected in non-strict mode + elif idnum != indirectReference.idnum: + # some other problem + raise utils.PdfReadError("Expected object ID (%d %d) does not match actual (%d %d)." \ + % (indirectReference.idnum, indirectReference.generation, idnum, generation)) + assert generation == indirectReference.generation + retval = readObject(self.stream, self) + + # override encryption is used for the /Encrypt dictionary + if not self._override_encryption and self.isEncrypted: + # if we don't have the encryption key: + if not hasattr(self, '_decryption_key'): + raise utils.PdfReadError("file has not been decrypted") + # otherwise, decrypt here... + import struct + pack1 = struct.pack(">read", stream) + # start at the end: + stream.seek(-1, 2) + if not stream.tell(): + raise utils.PdfReadError('Cannot read an empty file') + last1K = stream.tell() - 1024 + 1 # offset of last 1024 bytes of stream + line = b_('') + while line[:5] != b_("%%EOF"): + if stream.tell() < last1K: + raise utils.PdfReadError("EOF marker not found") + line = self.readNextEndLine(stream) + if debug: print(" line:",line) + + # find startxref entry - the location of the xref table + line = self.readNextEndLine(stream) + try: + startxref = int(line) + except ValueError: + # 'startxref' may be on the same line as the location + if not line.startswith(b_("startxref")): + raise utils.PdfReadError("startxref not found") + startxref = int(line[9:].strip()) + warnings.warn("startxref on same line as offset") + else: + line = self.readNextEndLine(stream) + if line[:9] != b_("startxref"): + raise utils.PdfReadError("startxref not found") + + # read all cross reference tables and their trailers + self.xref = {} + self.xref_objStm = {} + self.trailer = DictionaryObject() + while True: + # load the xref table + stream.seek(startxref, 0) + x = stream.read(1) + if x == b_("x"): + # standard cross-reference table + ref = stream.read(4) + if ref[:3] != b_("ref"): + raise utils.PdfReadError("xref table read error") + readNonWhitespace(stream) + stream.seek(-1, 1) + firsttime = True; # check if the first time looking at the xref table + while True: + num = readObject(stream, self) + if firsttime and num != 0: + self.xrefIndex = num + warnings.warn("Xref table not zero-indexed. ID numbers for objects will %sbe corrected." % \ + ("" if not self.strict else "not "), utils.PdfReadWarning) + #if table not zero indexed, could be due to error from when PDF was created + #which will lead to mismatched indices later on + firsttime = False + readNonWhitespace(stream) + stream.seek(-1, 1) + size = readObject(stream, self) + readNonWhitespace(stream) + stream.seek(-1, 1) + cnt = 0 + while cnt < size: + line = stream.read(20) + + # It's very clear in section 3.4.3 of the PDF spec + # that all cross-reference table lines are a fixed + # 20 bytes (as of PDF 1.7). However, some files have + # 21-byte entries (or more) due to the use of \r\n + # (CRLF) EOL's. Detect that case, and adjust the line + # until it does not begin with a \r (CR) or \n (LF). + while line[0] in b_("\x0D\x0A"): + stream.seek(-20 + 1, 1) + line = stream.read(20) + + # On the other hand, some malformed PDF files + # use a single character EOL without a preceeding + # space. Detect that case, and seek the stream + # back one character. (0-9 means we've bled into + # the next xref entry, t means we've bled into the + # text "trailer"): + if line[-1] in b_("0123456789t"): + stream.seek(-1, 1) + + offset, generation = line[:16].split(b_(" ")) + offset, generation = int(offset), int(generation) + if generation not in self.xref: + self.xref[generation] = {} + if num in self.xref[generation]: + # It really seems like we should allow the last + # xref table in the file to override previous + # ones. Since we read the file backwards, assume + # any existing key is already set correctly. + pass + else: + self.xref[generation][num] = offset + cnt += 1 + num += 1 + readNonWhitespace(stream) + stream.seek(-1, 1) + trailertag = stream.read(7) + if trailertag != b_("trailer"): + # more xrefs! + stream.seek(-7, 1) + else: + break + readNonWhitespace(stream) + stream.seek(-1, 1) + newTrailer = readObject(stream, self) + for key, value in list(newTrailer.items()): + if key not in self.trailer: + self.trailer[key] = value + if "/Prev" in newTrailer: + startxref = newTrailer["/Prev"] + else: + break + elif x.isdigit(): + # PDF 1.5+ Cross-Reference Stream + stream.seek(-1, 1) + idnum, generation = self.readObjectHeader(stream) + xrefstream = readObject(stream, self) + assert xrefstream["/Type"] == "/XRef" + self.cacheIndirectObject(generation, idnum, xrefstream) + streamData = BytesIO(b_(xrefstream.getData())) + # Index pairs specify the subsections in the dictionary. If + # none create one subsection that spans everything. + idx_pairs = xrefstream.get("/Index", [0, xrefstream.get("/Size")]) + if debug: print(("read idx_pairs=%s"%list(self._pairs(idx_pairs)))) + entrySizes = xrefstream.get("/W") + assert len(entrySizes) >= 3 + if self.strict and len(entrySizes) > 3: + raise utils.PdfReadError("Too many entry sizes: %s" %entrySizes) + + def getEntry(i): + # Reads the correct number of bytes for each entry. See the + # discussion of the W parameter in PDF spec table 17. + if entrySizes[i] > 0: + d = streamData.read(entrySizes[i]) + return convertToInt(d, entrySizes[i]) + + # PDF Spec Table 17: A value of zero for an element in the + # W array indicates...the default value shall be used + if i == 0: return 1 # First value defaults to 1 + else: return 0 + + def used_before(num, generation): + # We move backwards through the xrefs, don't replace any. + return num in self.xref.get(generation, []) or \ + num in self.xref_objStm + + # Iterate through each subsection + last_end = 0 + for start, size in self._pairs(idx_pairs): + # The subsections must increase + assert start >= last_end + last_end = start + size + for num in range(start, start+size): + # The first entry is the type + xref_type = getEntry(0) + # The rest of the elements depend on the xref_type + if xref_type == 0: + # linked list of free objects + next_free_object = getEntry(1) + next_generation = getEntry(2) + elif xref_type == 1: + # objects that are in use but are not compressed + byte_offset = getEntry(1) + generation = getEntry(2) + if generation not in self.xref: + self.xref[generation] = {} + if not used_before(num, generation): + self.xref[generation][num] = byte_offset + if debug: print(("XREF Uncompressed: %s %s"%( + num, generation))) + elif xref_type == 2: + # compressed objects + objstr_num = getEntry(1) + obstr_idx = getEntry(2) + generation = 0 # PDF spec table 18, generation is 0 + if not used_before(num, generation): + if debug: print(("XREF Compressed: %s %s %s"%( + num, objstr_num, obstr_idx))) + self.xref_objStm[num] = (objstr_num, obstr_idx) + elif self.strict: + raise utils.PdfReadError("Unknown xref type: %s"% + xref_type) + + trailerKeys = "/Root", "/Encrypt", "/Info", "/ID" + for key in trailerKeys: + if key in xrefstream and key not in self.trailer: + self.trailer[NameObject(key)] = xrefstream.raw_get(key) + if "/Prev" in xrefstream: + startxref = xrefstream["/Prev"] + else: + break + else: + # bad xref character at startxref. Let's see if we can find + # the xref table nearby, as we've observed this error with an + # off-by-one before. + stream.seek(-11, 1) + tmp = stream.read(20) + xref_loc = tmp.find(b_("xref")) + if xref_loc != -1: + startxref -= (10 - xref_loc) + continue + # No explicit xref table, try finding a cross-reference stream. + stream.seek(startxref, 0) + found = False + for look in range(5): + if stream.read(1).isdigit(): + # This is not a standard PDF, consider adding a warning + startxref += look + found = True + break + if found: + continue + # no xref table found at specified location + raise utils.PdfReadError("Could not find xref table at specified location") + #if not zero-indexed, verify that the table is correct; change it if necessary + if self.xrefIndex and not self.strict: + loc = stream.tell() + for gen in self.xref: + if gen == 65535: continue + for id in self.xref[gen]: + stream.seek(self.xref[gen][id], 0) + try: + pid, pgen = self.readObjectHeader(stream) + except ValueError: + break + if pid == id - self.xrefIndex: + self._zeroXref(gen) + break + #if not, then either it's just plain wrong, or the non-zero-index is actually correct + stream.seek(loc, 0) #return to where it was + + def _zeroXref(self, generation): + self.xref[generation] = dict( (k-self.xrefIndex, v) for (k, v) in list(self.xref[generation].items()) ) + + def _pairs(self, array): + i = 0 + while True: + yield array[i], array[i+1] + i += 2 + if (i+1) >= len(array): + break + + def readNextEndLine(self, stream): + debug = False + if debug: print(">>readNextEndLine") + line = b_("") + while True: + # Prevent infinite loops in malformed PDFs + if stream.tell() == 0: + raise utils.PdfReadError("Could not read malformed PDF file") + x = stream.read(1) + if debug: print((" x:", x, "%x"%ord(x))) + if stream.tell() < 2: + raise utils.PdfReadError("EOL marker not found") + stream.seek(-2, 1) + if x == b_('\n') or x == b_('\r'): ## \n = LF; \r = CR + crlf = False + while x == b_('\n') or x == b_('\r'): + if debug: + if ord(x) == 0x0D: print(" x is CR 0D") + elif ord(x) == 0x0A: print(" x is LF 0A") + x = stream.read(1) + if x == b_('\n') or x == b_('\r'): # account for CR+LF + stream.seek(-1, 1) + crlf = True + if stream.tell() < 2: + raise utils.PdfReadError("EOL marker not found") + stream.seek(-2, 1) + stream.seek(2 if crlf else 1, 1) #if using CR+LF, go back 2 bytes, else 1 + break + else: + if debug: print(" x is neither") + line = x + line + if debug: print((" RNEL line:", line)) + if debug: print("leaving RNEL") + return line + + def decrypt(self, password): + """ + When using an encrypted / secured PDF file with the PDF Standard + encryption handler, this function will allow the file to be decrypted. + It checks the given password against the document's user password and + owner password, and then stores the resulting decryption key if either + password is correct. + + It does not matter which password was matched. Both passwords provide + the correct decryption key that will allow the document to be used with + this library. + + :param str password: The password to match. + :return: ``0`` if the password failed, ``1`` if the password matched the user + password, and ``2`` if the password matched the owner password. + :rtype: int + :raises NotImplementedError: if document uses an unsupported encryption + method. + """ + + self._override_encryption = True + try: + return self._decrypt(password) + finally: + self._override_encryption = False + + def _decrypt(self, password): + encrypt = self.trailer['/Encrypt'].getObject() + if encrypt['/Filter'] != '/Standard': + raise NotImplementedError("only Standard PDF encryption handler is available") + if not (encrypt['/V'] in (1, 2)): + raise NotImplementedError("only algorithm code 1 and 2 are supported") + user_password, key = self._authenticateUserPassword(password) + if user_password: + self._decryption_key = key + return 1 + else: + rev = encrypt['/R'].getObject() + if rev == 2: + keylen = 5 + else: + keylen = encrypt['/Length'].getObject() // 8 + key = _alg33_1(password, rev, keylen) + real_O = encrypt["/O"].getObject() + if rev == 2: + userpass = utils.RC4_encrypt(key, real_O) + else: + val = real_O + for i in range(19, -1, -1): + new_key = b_('') + for l in range(len(key)): + new_key += b_(chr(utils.ord_(key[l]) ^ i)) + val = utils.RC4_encrypt(new_key, val) + userpass = val + owner_password, key = self._authenticateUserPassword(userpass) + if owner_password: + self._decryption_key = key + return 2 + return 0 + + def _authenticateUserPassword(self, password): + encrypt = self.trailer['/Encrypt'].getObject() + rev = encrypt['/R'].getObject() + owner_entry = encrypt['/O'].getObject() + p_entry = encrypt['/P'].getObject() + id_entry = self.trailer['/ID'].getObject() + id1_entry = id_entry[0].getObject() + real_U = encrypt['/U'].getObject().original_bytes + if rev == 2: + U, key = _alg34(password, owner_entry, p_entry, id1_entry) + elif rev >= 3: + U, key = _alg35(password, rev, + encrypt["/Length"].getObject() // 8, owner_entry, + p_entry, id1_entry, + encrypt.get("/EncryptMetadata", BooleanObject(False)).getObject()) + U, real_U = U[:16], real_U[:16] + return U == real_U, key + + def getIsEncrypted(self): + return "/Encrypt" in self.trailer + + isEncrypted = property(lambda self: self.getIsEncrypted(), None, None) + """ + Read-only boolean property showing whether this PDF file is encrypted. + Note that this property, if true, will remain true even after the + :meth:`decrypt()` method is called. + """ + + # returns a tuple indicating if the file has an owner and a user password, and the permissions + def getPermissions(self) : + if "/Encrypt" in self.trailer : + encrypt = self.trailer['/Encrypt'].getObject() + return (encrypt['/O'].getObject(), + encrypt['/U'].getObject(), + encrypt['/P'].getObject()) + + + +def getRectangle(self, name, defaults): + retval = self.get(name) + if isinstance(retval, RectangleObject): + return retval + if retval == None: + for d in defaults: + retval = self.get(d) + if retval != None: + break + if isinstance(retval, IndirectObject): + retval = self.pdf.getObject(retval) + retval = RectangleObject(retval) + setRectangle(self, name, retval) + return retval + + +def setRectangle(self, name, value): + if not isinstance(name, NameObject): + name = NameObject(name) + self[name] = value + + +def deleteRectangle(self, name): + del self[name] + + +def createRectangleAccessor(name, fallback): + return \ + property( + lambda self: getRectangle(self, name, fallback), + lambda self, value: setRectangle(self, name, value), + lambda self: deleteRectangle(self, name) + ) + + +class PageObject(DictionaryObject): + """ + This class represents a single page within a PDF file. Typically this + object will be created by accessing the + :meth:`getPage()` method of the + :class:`PdfFileReader` class, but it is + also possible to create an empty page with the + :meth:`createBlankPage()` static method. + + :param pdf: PDF file the page belongs to. + :param indirectRef: Stores the original indirect reference to + this object in its source PDF + """ + def __init__(self, pdf=None, indirectRef=None): + DictionaryObject.__init__(self) + self.pdf = pdf + self.indirectRef = indirectRef + + def createBlankPage(pdf=None, width=None, height=None): + """ + Returns a new blank page. + If ``width`` or ``height`` is ``None``, try to get the page size + from the last page of *pdf*. + + :param pdf: PDF file the page belongs to + :param float width: The width of the new page expressed in default user + space units. + :param float height: The height of the new page expressed in default user + space units. + :return: the new blank page: + :rtype: :class:`PageObject` + :raises PageSizeNotDefinedError: if ``pdf`` is ``None`` or contains + no page + """ + page = PageObject(pdf) + + # Creates a new page (cf PDF Reference 7.7.3.3) + page.__setitem__(NameObject('/Type'), NameObject('/Page')) + page.__setitem__(NameObject('/Parent'), NullObject()) + page.__setitem__(NameObject('/Resources'), DictionaryObject()) + if width is None or height is None: + if pdf is not None and pdf.getNumPages() > 0: + lastpage = pdf.getPage(pdf.getNumPages() - 1) + width = lastpage.mediaBox.getWidth() + height = lastpage.mediaBox.getHeight() + else: + raise utils.PageSizeNotDefinedError() + page.__setitem__(NameObject('/MediaBox'), + RectangleObject([0, 0, width, height])) + + return page + createBlankPage = staticmethod(createBlankPage) + + def rotateClockwise(self, angle): + """ + Rotates a page clockwise by increments of 90 degrees. + + :param int angle: Angle to rotate the page. Must be an increment + of 90 deg. + """ + assert angle % 90 == 0 + self._rotate(angle) + return self + + def rotateCounterClockwise(self, angle): + """ + Rotates a page counter-clockwise by increments of 90 degrees. + + :param int angle: Angle to rotate the page. Must be an increment + of 90 deg. + """ + assert angle % 90 == 0 + self._rotate(-angle) + return self + + def _rotate(self, angle): + currentAngle = self.get("/Rotate", 0) + self[NameObject("/Rotate")] = NumberObject(currentAngle + angle) + + def _mergeResources(res1, res2, resource): + newRes = DictionaryObject() + newRes.update(res1.get(resource, DictionaryObject()).getObject()) + page2Res = res2.get(resource, DictionaryObject()).getObject() + renameRes = {} + for key in list(page2Res.keys()): + if key in newRes and newRes.raw_get(key) != page2Res.raw_get(key): + newname = NameObject(key + str(uuid.uuid4())) + renameRes[key] = newname + newRes[newname] = page2Res[key] + elif key not in newRes: + newRes[key] = page2Res.raw_get(key) + return newRes, renameRes + _mergeResources = staticmethod(_mergeResources) + + def _contentStreamRename(stream, rename, pdf): + if not rename: + return stream + stream = ContentStream(stream, pdf) + for operands, operator in stream.operations: + for i in range(len(operands)): + op = operands[i] + if isinstance(op, NameObject): + operands[i] = rename.get(op,op) + return stream + _contentStreamRename = staticmethod(_contentStreamRename) + + def _pushPopGS(contents, pdf): + # adds a graphics state "push" and "pop" to the beginning and end + # of a content stream. This isolates it from changes such as + # transformation matricies. + stream = ContentStream(contents, pdf) + stream.operations.insert(0, [[], "q"]) + stream.operations.append([[], "Q"]) + return stream + _pushPopGS = staticmethod(_pushPopGS) + + def _addCode(contents, pdf, code, endCode = ""): + + stream = ContentStream(contents, pdf) + stream.operations.insert(0, [[], code]) + stream.operations.append([[], endCode]) + return stream + _addCode = staticmethod(_addCode) + + def _addTransformationMatrix(contents, pdf, ctm): + # adds transformation matrix at the beginning of the given + # contents stream. + a, b, c, d, e, f = ctm + contents = ContentStream(contents, pdf) + contents.operations.insert(0, [[FloatObject(a), FloatObject(b), + FloatObject(c), FloatObject(d), FloatObject(e), + FloatObject(f)], " cm"]) + return contents + _addTransformationMatrix = staticmethod(_addTransformationMatrix) + + def getContents(self): + """ + Accesses the page contents. + + :return: the ``/Contents`` object, or ``None`` if it doesn't exist. + ``/Contents`` is optional, as described in PDF Reference 7.7.3.3 + """ + if "/Contents" in self: + return self["/Contents"].getObject() + else: + return None + + def mergePage(self, page2): + """ + Merges the content streams of two pages into one. Resource references + (i.e. fonts) are maintained from both pages. The mediabox/cropbox/etc + of this page are not altered. The parameter page's content stream will + be added to the end of this page's content stream, meaning that it will + be drawn after, or "on top" of this page. + + :param PageObject page2: The page to be merged into this one. Should be + an instance of :class:`PageObject`. + """ + self._mergePage(page2) + + def _mergePage(self, page2, page2transformation=None, ctm=None, expand=False): + # First we work on merging the resource dictionaries. This allows us + # to find out what symbols in the content streams we might need to + # rename. + + newResources = DictionaryObject() + rename = {} + originalResources = self["/Resources"].getObject() + page2Resources = page2["/Resources"].getObject() + newAnnots = ArrayObject() + + for page in (self, page2): + if "/Annots" in page: + annots = page["/Annots"] + if isinstance(annots, ArrayObject): + for ref in annots: + newAnnots.append(ref) + + for res in "/ExtGState", "/Font", "/XObject", "/ColorSpace", "/Pattern", "/Shading", "/Properties": + new, newrename = PageObject._mergeResources(originalResources, page2Resources, res) + if new: + newResources[NameObject(res)] = new + rename.update(newrename) + + # Combine /ProcSet sets. + newResources[NameObject("/ProcSet")] = ArrayObject( + frozenset(originalResources.get("/ProcSet", ArrayObject()).getObject()).union( + frozenset(page2Resources.get("/ProcSet", ArrayObject()).getObject()) + ) + ) + + newContentArray = ArrayObject() + + originalContent = self.getContents() + if originalContent is not None: + newContentArray.append(PageObject._pushPopGS( + originalContent, self.pdf)) + + page2Content = page2.getContents() + if page2Content is not None: + if page2transformation is not None: + page2Content = page2transformation(page2Content) + page2Content = PageObject._contentStreamRename( + page2Content, rename, self.pdf) + page2Content = PageObject._pushPopGS(page2Content, self.pdf) + newContentArray.append(page2Content) + + # if expanding the page to fit a new page, calculate the new media box size + if expand: + corners1 = [self.mediaBox.getLowerLeft_x().as_numeric(), self.mediaBox.getLowerLeft_y().as_numeric(), + self.mediaBox.getUpperRight_x().as_numeric(), self.mediaBox.getUpperRight_y().as_numeric()] + corners2 = [page2.mediaBox.getLowerLeft_x().as_numeric(), page2.mediaBox.getLowerLeft_y().as_numeric(), + page2.mediaBox.getUpperLeft_x().as_numeric(), page2.mediaBox.getUpperLeft_y().as_numeric(), + page2.mediaBox.getUpperRight_x().as_numeric(), page2.mediaBox.getUpperRight_y().as_numeric(), + page2.mediaBox.getLowerRight_x().as_numeric(), page2.mediaBox.getLowerRight_y().as_numeric()] + if ctm is not None: + ctm = [float(x) for x in ctm] + new_x = [ctm[0]*corners2[i] + ctm[2]*corners2[i+1] + ctm[4] for i in range(0, 8, 2)] + new_y = [ctm[1]*corners2[i] + ctm[3]*corners2[i+1] + ctm[5] for i in range(0, 8, 2)] + else: + new_x = corners2[0:8:2] + new_y = corners2[1:8:2] + lowerleft = [min(new_x), min(new_y)] + upperright = [max(new_x), max(new_y)] + lowerleft = [min(corners1[0], lowerleft[0]), min(corners1[1], lowerleft[1])] + upperright = [max(corners1[2], upperright[0]), max(corners1[3], upperright[1])] + + self.mediaBox.setLowerLeft(lowerleft) + self.mediaBox.setUpperRight(upperright) + + self[NameObject('/Contents')] = ContentStream(newContentArray, self.pdf) + self[NameObject('/Resources')] = newResources + self[NameObject('/Annots')] = newAnnots + + + # Variant of the mergePage function. + # Merges the content streams of several pages and code strings into one page. + # Resource references (i.e. fonts) are maintained from all pages. + # The parameter ar_data is an array containing code strings and PageObjects. + # ContentStream is called only if necessary because it calls ParseContentStream + # which is slox. Otherwise the Content is directly extracted and added to the code. + + def mergePage3(self, ar_data ): + + newResources = DictionaryObject() + rename = {} + originalResources = self["/Resources"].getObject() + code_s = b"" + + if isinstance(ar_data, PageObject) : + ar_data = [ar_data] + for data in ar_data : + + # modified by Dysmas, march 2017 + if sys.version[0:1] == "2" : # when used in python3, this code raises the error : unicode is not defined + if isinstance(data, unicode) : + data = data.encode("utf8") + elif isinstance(data, str) : + data = bytes(data, "utf-8") + + if isinstance(data, PageObject) : + + # Now we work on merging the resource dictionaries. This allows us + # to find out what symbols in the content streams we might need to + # rename. + pagexResources = data["/Resources"].getObject() + + for res in "/ExtGState", "/Font", "/XObject", "/ColorSpace", "/Pattern", "/Shading": + new, newrename = PageObject._mergeResources(originalResources, pagexResources, res) + if new: + newResources[NameObject(res)] = new + rename.update(newrename) + + # Combine /Resources sets. + originalResources.update(newResources) + + # Combine /ProcSet sets. + newResources[NameObject("/ProcSet")] = ArrayObject( + frozenset(originalResources.get("/ProcSet", ArrayObject()).getObject()).union( + frozenset(pagexResources.get("/ProcSet", ArrayObject()).getObject()) + ) + ) + + if len(rename) > 0 : + pagexContent = data['/Contents'].getObject() + pagexContent = PageObject._contentStreamRename(pagexContent, rename, self.pdf) + code_s += bytes(pagexContent.getData()) + b"\n" + else : + page_keys = data.keys() + if "/Contents" in page_keys : # if page is not blank + data1 = bytes(self.extractContent(data["/Contents"])) + code_s += data1 + b"\n" + + + + else : + code_s += data + b"\n" + + + originalContent = self["/Contents"].getObject() + outputContent = PageObject._addCode(originalContent, self.pdf, code_s) + + self[NameObject('/Contents')] = outputContent + self[NameObject('/Resources')] = originalResources + + + def extractContent(self,data) : + code_s = b"" + pageContent = data.getObject() + if isinstance(pageContent, ArrayObject) : + for data2 in pageContent : + code_s += self.extractContent(data2) + else : + if isinstance(data, TextStringObject) : + code_s += data + else : + try : + decodedData = filters.decodeStreamData(pageContent) + code_s += decodedData + except : + print ("le code n'a pas pu etre extrait") + + return code_s + + def mergeModifiedPage(self, page2, code, endCode = ""): + self._mergePage(page2, lambda page2Content: + PageObject._addCode(page2Content, page2.pdf, code, endCode), code) + + + def mergeTransformedPage(self, page2, ctm, expand=False): + """ + This is similar to mergePage, but a transformation matrix is + applied to the merged stream. + + :param PageObject page2: The page to be merged into this one. Should be + an instance of :class:`PageObject`. + :param tuple ctm: a 6-element tuple containing the operands of the + transformation matrix + :param bool expand: Whether the page should be expanded to fit the dimensions + of the page to be merged. + """ + self._mergePage(page2, lambda page2Content: + PageObject._addTransformationMatrix(page2Content, page2.pdf, ctm), ctm, expand) + + def mergeScaledPage(self, page2, scale, expand=False): + """ + This is similar to mergePage, but the stream to be merged is scaled + by appling a transformation matrix. + + :param PageObject page2: The page to be merged into this one. Should be + an instance of :class:`PageObject`. + :param float scale: The scaling factor + :param bool expand: Whether the page should be expanded to fit the + dimensions of the page to be merged. + """ + # CTM to scale : [ sx 0 0 sy 0 0 ] + return self.mergeTransformedPage(page2, [scale, 0, + 0, scale, + 0, 0], expand) + + def mergeRotatedPage(self, page2, rotation, expand=False): + """ + This is similar to mergePage, but the stream to be merged is rotated + by appling a transformation matrix. + + :param PageObject page2: the page to be merged into this one. Should be + an instance of :class:`PageObject`. + :param float rotation: The angle of the rotation, in degrees + :param bool expand: Whether the page should be expanded to fit the + dimensions of the page to be merged. + """ + rotation = math.radians(rotation) + return self.mergeTransformedPage(page2, + [math.cos(rotation), math.sin(rotation), + -math.sin(rotation), math.cos(rotation), + 0, 0], expand) + + def mergeTranslatedPage(self, page2, tx, ty, expand=False): + """ + This is similar to mergePage, but the stream to be merged is translated + by appling a transformation matrix. + + :param PageObject page2: the page to be merged into this one. Should be + an instance of :class:`PageObject`. + :param float tx: The translation on X axis + :param float ty: The translation on Y axis + :param bool expand: Whether the page should be expanded to fit the + dimensions of the page to be merged. + """ + return self.mergeTransformedPage(page2, [1, 0, + 0, 1, + tx, ty], expand) + + def mergeRotatedTranslatedPage(self, page2, rotation, tx, ty, expand=False): + """ + This is similar to mergePage, but the stream to be merged is rotated + and translated by appling a transformation matrix. + + :param PageObject page2: the page to be merged into this one. Should be + an instance of :class:`PageObject`. + :param float tx: The translation on X axis + :param float ty: The translation on Y axis + :param float rotation: The angle of the rotation, in degrees + :param bool expand: Whether the page should be expanded to fit the + dimensions of the page to be merged. + """ + + translation = [[1, 0, 0], + [0, 1, 0], + [-tx, -ty, 1]] + rotation = math.radians(rotation) + rotating = [[math.cos(rotation), math.sin(rotation), 0], + [-math.sin(rotation), math.cos(rotation), 0], + [0, 0, 1]] + rtranslation = [[1, 0, 0], + [0, 1, 0], + [tx, ty, 1]] + ctm = utils.matrixMultiply(translation, rotating) + ctm = utils.matrixMultiply(ctm, rtranslation) + + return self.mergeTransformedPage(page2, [ctm[0][0], ctm[0][1], + ctm[1][0], ctm[1][1], + ctm[2][0], ctm[2][1]], expand) + + def mergeRotatedScaledPage(self, page2, rotation, scale, expand=False): + """ + This is similar to mergePage, but the stream to be merged is rotated + and scaled by appling a transformation matrix. + + :param PageObject page2: the page to be merged into this one. Should be + an instance of :class:`PageObject`. + :param float rotation: The angle of the rotation, in degrees + :param float scale: The scaling factor + :param bool expand: Whether the page should be expanded to fit the + dimensions of the page to be merged. + """ + rotation = math.radians(rotation) + rotating = [[math.cos(rotation), math.sin(rotation), 0], + [-math.sin(rotation), math.cos(rotation), 0], + [0, 0, 1]] + scaling = [[scale, 0, 0], + [0, scale, 0], + [0, 0, 1]] + ctm = utils.matrixMultiply(rotating, scaling) + + return self.mergeTransformedPage(page2, + [ctm[0][0], ctm[0][1], + ctm[1][0], ctm[1][1], + ctm[2][0], ctm[2][1]], expand) + + def mergeScaledTranslatedPage(self, page2, scale, tx, ty, expand=False): + """ + This is similar to mergePage, but the stream to be merged is translated + and scaled by appling a transformation matrix. + + :param PageObject page2: the page to be merged into this one. Should be + an instance of :class:`PageObject`. + :param float scale: The scaling factor + :param float tx: The translation on X axis + :param float ty: The translation on Y axis + :param bool expand: Whether the page should be expanded to fit the + dimensions of the page to be merged. + """ + + translation = [[1, 0, 0], + [0, 1, 0], + [tx, ty, 1]] + scaling = [[scale, 0, 0], + [0, scale, 0], + [0, 0, 1]] + ctm = utils.matrixMultiply(scaling, translation) + + return self.mergeTransformedPage(page2, [ctm[0][0], ctm[0][1], + ctm[1][0], ctm[1][1], + ctm[2][0], ctm[2][1]], expand) + + def mergeRotatedScaledTranslatedPage(self, page2, rotation, scale, tx, ty, expand=False): + """ + This is similar to mergePage, but the stream to be merged is translated, + rotated and scaled by appling a transformation matrix. + + :param PageObject page2: the page to be merged into this one. Should be + an instance of :class:`PageObject`. + :param float tx: The translation on X axis + :param float ty: The translation on Y axis + :param float rotation: The angle of the rotation, in degrees + :param float scale: The scaling factor + :param bool expand: Whether the page should be expanded to fit the + dimensions of the page to be merged. + """ + translation = [[1, 0, 0], + [0, 1, 0], + [tx, ty, 1]] + rotation = math.radians(rotation) + rotating = [[math.cos(rotation), math.sin(rotation), 0], + [-math.sin(rotation), math.cos(rotation), 0], + [0, 0, 1]] + scaling = [[scale, 0, 0], + [0, scale, 0], + [0, 0, 1]] + ctm = utils.matrixMultiply(rotating, scaling) + ctm = utils.matrixMultiply(ctm, translation) + + return self.mergeTransformedPage(page2, [ctm[0][0], ctm[0][1], + ctm[1][0], ctm[1][1], + ctm[2][0], ctm[2][1]], expand) + + ## + # Applys a transformation matrix the page. + # + # @param ctm A 6 elements tuple containing the operands of the + # transformation matrix + def addTransformation(self, ctm): + """ + Applies a transformation matrix to the page. + + :param tuple ctm: A 6-element tuple containing the operands of the + transformation matrix. + """ + originalContent = self.getContents() + if originalContent is not None: + newContent = PageObject._addTransformationMatrix( + originalContent, self.pdf, ctm) + newContent = PageObject._pushPopGS(newContent, self.pdf) + self[NameObject('/Contents')] = newContent + + def scale(self, sx, sy): + """ + Scales a page by the given factors by appling a transformation + matrix to its content and updating the page size. + + :param float sx: The scaling factor on horizontal axis. + :param float sy: The scaling factor on vertical axis. + """ + self.addTransformation([sx, 0, + 0, sy, + 0, 0]) + self.mediaBox = RectangleObject([ + float(self.mediaBox.getLowerLeft_x()) * sx, + float(self.mediaBox.getLowerLeft_y()) * sy, + float(self.mediaBox.getUpperRight_x()) * sx, + float(self.mediaBox.getUpperRight_y()) * sy]) + if "/VP" in self: + viewport = self["/VP"] + if isinstance(viewport, ArrayObject): + bbox = viewport[0]["/BBox"] + else: + bbox = viewport["/BBox"] + scaled_bbox = RectangleObject([ + float(bbox[0]) * sx, + float(bbox[1]) * sy, + float(bbox[2]) * sx, + float(bbox[3]) * sy]) + if isinstance(viewport, ArrayObject): + self[NameObject("/VP")][NumberObject(0)][NameObject("/BBox")] = scaled_bbox + else: + self[NameObject("/VP")][NameObject("/BBox")] = scaled_bbox + + def scaleBy(self, factor): + """ + Scales a page by the given factor by appling a transformation + matrix to its content and updating the page size. + + :param float factor: The scaling factor (for both X and Y axis). + """ + self.scale(factor, factor) + + def scaleTo(self, width, height): + """ + Scales a page to the specified dimentions by appling a + transformation matrix to its content and updating the page size. + + :param float width: The new width. + :param float height: The new heigth. + """ + sx = width / float(self.mediaBox.getUpperRight_x() - + self.mediaBox.getLowerLeft_x ()) + sy = height / float(self.mediaBox.getUpperRight_y() - + self.mediaBox.getLowerLeft_y ()) + self.scale(sx, sy) + + def compressContentStreams(self): + """ + Compresses the size of this page by joining all content streams and + applying a FlateDecode filter. + + However, it is possible that this function will perform no action if + content stream compression becomes "automatic" for some reason. + """ + content = self.getContents() + if content is not None: + if not isinstance(content, ContentStream): + content = ContentStream(content, self.pdf) + self[NameObject("/Contents")] = content.flateEncode() + + def extractText(self): + """ + Locate all text drawing commands, in the order they are provided in the + content stream, and extract the text. This works well for some PDF + files, but poorly for others, depending on the generator used. This will + be refined in the future. Do not rely on the order of text coming out of + this function, as it will change if this function is made more + sophisticated. + + :return: a unicode string object. + """ + text = u_("") + content = self["/Contents"].getObject() + if not isinstance(content, ContentStream): + content = ContentStream(content, self.pdf) + # Note: we check all strings are TextStringObjects. ByteStringObjects + # are strings where the byte->string encoding was unknown, so adding + # them to the text here would be gibberish. + for operands, operator in content.operations: + if operator == b_("Tj"): + _text = operands[0] + if isinstance(_text, TextStringObject): + text += _text + elif operator == b_("T*"): + text += "\n" + elif operator == b_("'"): + text += "\n" + _text = operands[0] + if isinstance(_text, TextStringObject): + text += operands[0] + elif operator == b_('"'): + _text = operands[2] + if isinstance(_text, TextStringObject): + text += "\n" + text += _text + elif operator == b_("TJ"): + for i in operands[0]: + if isinstance(i, TextStringObject): + text += i + text += "\n" + return text + + mediaBox = createRectangleAccessor("/MediaBox", ()) + """ + A :class:`RectangleObject`, expressed in default user space units, + defining the boundaries of the physical medium on which the page is + intended to be displayed or printed. + """ + + cropBox = createRectangleAccessor("/CropBox", ("/MediaBox",)) + """ + A :class:`RectangleObject`, expressed in default user space units, + defining the visible region of default user space. When the page is + displayed or printed, its contents are to be clipped (cropped) to this + rectangle and then imposed on the output medium in some + implementation-defined manner. Default value: same as :attr:`mediaBox`. + """ + + bleedBox = createRectangleAccessor("/BleedBox", ("/CropBox", "/MediaBox")) + """ + A :class:`RectangleObject`, expressed in default user space units, + defining the region to which the contents of the page should be clipped + when output in a production enviroment. + """ + + trimBox = createRectangleAccessor("/TrimBox", ("/CropBox", "/MediaBox")) + """ + A :class:`RectangleObject`, expressed in default user space units, + defining the intended dimensions of the finished page after trimming. + """ + + artBox = createRectangleAccessor("/ArtBox", ("/CropBox", "/MediaBox")) + """ + A :class:`RectangleObject`, expressed in default user space units, + defining the extent of the page's meaningful content as intended by the + page's creator. + """ + + +class ContentStream(DecodedStreamObject): + def __init__(self, stream, pdf): + self.pdf = pdf + self.operations = [] + # stream may be a StreamObject or an ArrayObject containing + # multiple StreamObjects to be cat'd together. + stream = stream.getObject() + if isinstance(stream, ArrayObject): + data = b_("") + for s in stream: + data += s.getObject().getData() + stream = BytesIO(b_(data)) + else: + stream = BytesIO(b_(stream.getData())) + self.__parseContentStream(stream) + + def __parseContentStream(self, stream): + # file("f:\\tmp.txt", "w").write(stream.read()) + stream.seek(0, 0) + operands = [] + while True: + peek = readNonWhitespace(stream) + if peek == b_('') or ord_(peek) == 0: + break + stream.seek(-1, 1) + if peek.isalpha() or peek == b_("'") or peek == b_('"'): + operator = utils.readUntilRegex(stream, + NameObject.delimiterPattern, True) + if operator == b_("BI"): + # begin inline image - a completely different parsing + # mechanism is required, of course... thanks buddy... + assert operands == [] + ii = self._readInlineImage(stream) + self.operations.append((ii, b_("INLINE IMAGE"))) + else: + self.operations.append((operands, operator)) + operands = [] + elif peek == b_('%'): + # If we encounter a comment in the content stream, we have to + # handle it here. Typically, readObject will handle + # encountering a comment -- but readObject assumes that + # following the comment must be the object we're trying to + # read. In this case, it could be an operator instead. + while peek not in (b_('\r'), b_('\n')): + peek = stream.read(1) + else: + operands.append(readObject(stream, None)) + + def _readInlineImage(self, stream): + # begin reading just after the "BI" - begin image + # first read the dictionary of settings. + settings = DictionaryObject() + while True: + tok = readNonWhitespace(stream) + stream.seek(-1, 1) + if tok == b_("I"): + # "ID" - begin of image data + break + key = readObject(stream, self.pdf) + tok = readNonWhitespace(stream) + stream.seek(-1, 1) + value = readObject(stream, self.pdf) + settings[key] = value + # left at beginning of ID + tmp = stream.read(3) + assert tmp[:2] == b_("ID") + data = b_("") + while True: + # Read the inline image, while checking for EI (End Image) operator. + tok = stream.read(1) + if tok == b_("E"): + # Check for End Image + tok2 = stream.read(1) + if tok2 == b_("I"): + # Sometimes that data will contain EI, so check for the Q operator. + tok3 = stream.read(1) + info = tok + tok2 + while tok3 in utils.WHITESPACES: + info += tok3 + tok3 = stream.read(1) + if tok3 == b_("Q"): + stream.seek(-1, 1) + break + else: + stream.seek(-1,1) + data += info + else: + stream.seek(-1, 1) + data += tok + else: + data += tok + return {"settings": settings, "data": data} + + def _getData(self): + newdata = BytesIO() + for operands, operator in self.operations: + if operator == "INLINE IMAGE": + newdata.write("BI") + dicttext = StringIO() + operands["settings"].writeToStream(dicttext, None) + newdata.write(dicttext.getvalue()[2:-2]) + newdata.write("ID ") + newdata.write(operands["data"]) + newdata.write("EI") + else: + for op in operands: + op.writeToStream(newdata, None) + newdata.write(b_(" ")) + newdata.write(b_(operator)) + newdata.write(b_("\n")) + return newdata.getvalue() + + def _setData(self, value): + self.__parseContentStream(BytesIO(b_(value))) + + _data = property(_getData, _setData) + + +class DocumentInformation(DictionaryObject): + """ + A class representing the basic document metadata provided in a PDF File. + This class is accessible through + :meth:`getDocumentInfo()` + + All text properties of the document metadata have + *two* properties, eg. author and author_raw. The non-raw property will + always return a ``TextStringObject``, making it ideal for a case where + the metadata is being displayed. The raw property can sometimes return + a ``ByteStringObject``, if PyPDF2 was unable to decode the string's + text encoding; this requires additional safety in the caller and + therefore is not as commonly accessed. + """ + + def __init__(self): + DictionaryObject.__init__(self) + + def getText(self, key): + retval = self.get(key, None) + if isinstance(retval, TextStringObject): + return retval + return None + + title = property(lambda self: self.getText("/Title")) + """Read-only property accessing the document's **title**. + Returns a unicode string (``TextStringObject``) or ``None`` + if the title is not specified.""" + title_raw = property(lambda self: self.get("/Title")) + """The "raw" version of title; can return a ``ByteStringObject``.""" + + author = property(lambda self: self.getText("/Author")) + """Read-only property accessing the document's **author**. + Returns a unicode string (``TextStringObject``) or ``None`` + if the author is not specified.""" + author_raw = property(lambda self: self.get("/Author")) + """The "raw" version of author; can return a ``ByteStringObject``.""" + + subject = property(lambda self: self.getText("/Subject")) + """Read-only property accessing the document's **subject**. + Returns a unicode string (``TextStringObject``) or ``None`` + if the subject is not specified.""" + subject_raw = property(lambda self: self.get("/Subject")) + """The "raw" version of subject; can return a ``ByteStringObject``.""" + + creator = property(lambda self: self.getText("/Creator")) + """Read-only property accessing the document's **creator**. If the + document was converted to PDF from another format, this is the name of the + application (e.g. OpenOffice) that created the original document from + which it was converted. Returns a unicode string (``TextStringObject``) + or ``None`` if the creator is not specified.""" + creator_raw = property(lambda self: self.get("/Creator")) + """The "raw" version of creator; can return a ``ByteStringObject``.""" + + producer = property(lambda self: self.getText("/Producer")) + """Read-only property accessing the document's **producer**. + If the document was converted to PDF from another format, this is + the name of the application (for example, OSX Quartz) that converted + it to PDF. Returns a unicode string (``TextStringObject``) + or ``None`` if the producer is not specified.""" + producer_raw = property(lambda self: self.get("/Producer")) + """The "raw" version of producer; can return a ``ByteStringObject``.""" + + +def convertToInt(d, size): + if size > 8: + raise utils.PdfReadError("invalid size in convertToInt") + d = b_("\x00\x00\x00\x00\x00\x00\x00\x00") + b_(d) + d = d[-8:] + return struct.unpack(">q", d)[0] + +# ref: pdf1.8 spec section 3.5.2 algorithm 3.2 +_encryption_padding = b_('\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56') + \ + b_('\xff\xfa\x01\x08\x2e\x2e\x00\xb6\xd0\x68\x3e\x80\x2f\x0c') + \ + b_('\xa9\xfe\x64\x53\x69\x7a') + + +# Implementation of algorithm 3.2 of the PDF standard security handler, +# section 3.5.2 of the PDF 1.6 reference. +def _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encrypt=True): + # 1. Pad or truncate the password string to exactly 32 bytes. If the + # password string is more than 32 bytes long, use only its first 32 bytes; + # if it is less than 32 bytes long, pad it by appending the required number + # of additional bytes from the beginning of the padding string + # (_encryption_padding). + password = b_((str_(password) + str_(_encryption_padding))[:32]) + # 2. Initialize the MD5 hash function and pass the result of step 1 as + # input to this function. + import struct + m = md5(password) + # 3. Pass the value of the encryption dictionary's /O entry to the MD5 hash + # function. + m.update(owner_entry.original_bytes) + # 4. Treat the value of the /P entry as an unsigned 4-byte integer and pass + # these bytes to the MD5 hash function, low-order byte first. + p_entry = struct.pack('= 3 and not metadata_encrypt: + m.update(b_("\xff\xff\xff\xff")) + # 7. Finish the hash. + md5_hash = m.digest() + # 8. (Revision 3 or greater) Do the following 50 times: Take the output + # from the previous MD5 hash and pass the first n bytes of the output as + # input into a new MD5 hash, where n is the number of bytes of the + # encryption key as defined by the value of the encryption dictionary's + # /Length entry. + if rev >= 3: + for i in range(50): + md5_hash = md5(md5_hash[:keylen]).digest() + # 9. Set the encryption key to the first n bytes of the output from the + # final MD5 hash, where n is always 5 for revision 2 but, for revision 3 or + # greater, depends on the value of the encryption dictionary's /Length + # entry. + return md5_hash[:keylen] + + +# Implementation of algorithm 3.3 of the PDF standard security handler, +# section 3.5.2 of the PDF 1.6 reference. +def _alg33(owner_pwd, user_pwd, rev, keylen): + # steps 1 - 4 + key = _alg33_1(owner_pwd, rev, keylen) + # 5. Pad or truncate the user password string as described in step 1 of + # algorithm 3.2. + user_pwd = b_((user_pwd + str_(_encryption_padding))[:32]) + # 6. Encrypt the result of step 5, using an RC4 encryption function with + # the encryption key obtained in step 4. + val = utils.RC4_encrypt(key, user_pwd) + # 7. (Revision 3 or greater) Do the following 19 times: Take the output + # from the previous invocation of the RC4 function and pass it as input to + # a new invocation of the function; use an encryption key generated by + # taking each byte of the encryption key obtained in step 4 and performing + # an XOR operation between that byte and the single-byte value of the + # iteration counter (from 1 to 19). + if rev >= 3: + for i in range(1, 20): + new_key = '' + for l in range(len(key)): + new_key += chr(ord_(key[l]) ^ i) + val = utils.RC4_encrypt(new_key, val) + # 8. Store the output from the final invocation of the RC4 as the value of + # the /O entry in the encryption dictionary. + return val + + +# Steps 1-4 of algorithm 3.3 +def _alg33_1(password, rev, keylen): + # 1. Pad or truncate the owner password string as described in step 1 of + # algorithm 3.2. If there is no owner password, use the user password + # instead. + password = b_((password + str_(_encryption_padding))[:32]) + # 2. Initialize the MD5 hash function and pass the result of step 1 as + # input to this function. + m = md5(password) + # 3. (Revision 3 or greater) Do the following 50 times: Take the output + # from the previous MD5 hash and pass it as input into a new MD5 hash. + md5_hash = m.digest() + if rev >= 3: + for i in range(50): + md5_hash = md5(md5_hash).digest() + # 4. Create an RC4 encryption key using the first n bytes of the output + # from the final MD5 hash, where n is always 5 for revision 2 but, for + # revision 3 or greater, depends on the value of the encryption + # dictionary's /Length entry. + key = md5_hash[:keylen] + return key + + +# Implementation of algorithm 3.4 of the PDF standard security handler, +# section 3.5.2 of the PDF 1.6 reference. +def _alg34(password, owner_entry, p_entry, id1_entry): + # 1. Create an encryption key based on the user password string, as + # described in algorithm 3.2. + key = _alg32(password, 2, 5, owner_entry, p_entry, id1_entry) + # 2. Encrypt the 32-byte padding string shown in step 1 of algorithm 3.2, + # using an RC4 encryption function with the encryption key from the + # preceding step. + U = utils.RC4_encrypt(key, _encryption_padding) + # 3. Store the result of step 2 as the value of the /U entry in the + # encryption dictionary. + return U, key + + +# Implementation of algorithm 3.4 of the PDF standard security handler, +# section 3.5.2 of the PDF 1.6 reference. +def _alg35(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encrypt): + # 1. Create an encryption key based on the user password string, as + # described in Algorithm 3.2. + key = _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry) + # 2. Initialize the MD5 hash function and pass the 32-byte padding string + # shown in step 1 of Algorithm 3.2 as input to this function. + m = md5() + m.update(_encryption_padding) + # 3. Pass the first element of the file's file identifier array (the value + # of the ID entry in the document's trailer dictionary; see Table 3.13 on + # page 73) to the hash function and finish the hash. (See implementation + # note 25 in Appendix H.) + m.update(id1_entry.original_bytes) + md5_hash = m.digest() + # 4. Encrypt the 16-byte result of the hash, using an RC4 encryption + # function with the encryption key from step 1. + val = utils.RC4_encrypt(key, md5_hash) + # 5. Do the following 19 times: Take the output from the previous + # invocation of the RC4 function and pass it as input to a new invocation + # of the function; use an encryption key generated by taking each byte of + # the original encryption key (obtained in step 2) and performing an XOR + # operation between that byte and the single-byte value of the iteration + # counter (from 1 to 19). + for i in range(1, 20): + new_key = b_('') + for l in range(len(key)): + new_key += b_(chr(ord_(key[l]) ^ i)) + val = utils.RC4_encrypt(new_key, val) + # 6. Append 16 bytes of arbitrary padding to the output from the final + # invocation of the RC4 function and store the 32-byte result as the value + # of the U entry in the encryption dictionary. + # (implementator note: I don't know what "arbitrary padding" is supposed to + # mean, so I have used null bytes. This seems to match a few other + # people's implementations) + return val + (b_('\x00') * 16), key diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/utils.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/utils.py new file mode 100644 index 0000000..718a875 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/utils.py @@ -0,0 +1,295 @@ +# Copyright (c) 2006, Mathieu Fenniak +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +""" +Utility functions for PDF library. +""" +__author__ = "Mathieu Fenniak" +__author_email__ = "biziqe@mathieu.fenniak.net" + + +import sys + +try: + import __builtin__ as builtins +except ImportError: # Py3 + import builtins + + +xrange_fn = getattr(builtins, "xrange", range) +_basestring = getattr(builtins, "basestring", str) + +bytes_type = type(bytes()) # Works the same in Python 2.X and 3.X +string_type = getattr(builtins, "unicode", str) +int_types = (int, long) if sys.version_info[0] < 3 else (int,) + + +# Make basic type tests more consistent +def isString(s): + """Test if arg is a string. Compatible with Python 2 and 3.""" + return isinstance(s, _basestring) + + +def isInt(n): + """Test if arg is an int. Compatible with Python 2 and 3.""" + return isinstance(n, int_types) + + +def isBytes(b): + """Test if arg is a bytes instance. Compatible with Python 2 and 3.""" + return isinstance(b, bytes_type) + + +#custom implementation of warnings.formatwarning +def formatWarning(message, category, filename, lineno, line=None): + file = filename.replace("/", "\\").rsplit("\\", 1)[1] # find the file name + return "%s: %s [%s:%s]\n" % (category.__name__, message, file, lineno) + + +def readUntilWhitespace(stream, maxchars=None): + """ + Reads non-whitespace characters and returns them. + Stops upon encountering whitespace or when maxchars is reached. + """ + txt = b_("") + while True: + tok = stream.read(1) + if tok.isspace() or not tok: + break + txt += tok + if len(txt) == maxchars: + break + return txt + + +def readNonWhitespace(stream): + """ + Finds and reads the next non-whitespace character (ignores whitespace). + """ + tok = WHITESPACES[0] + while tok in WHITESPACES: + tok = stream.read(1) + return tok + + +def skipOverWhitespace(stream): + """ + Similar to readNonWhitespace, but returns a Boolean if more than + one whitespace character was read. + """ + tok = WHITESPACES[0] + cnt = 0; + while tok in WHITESPACES: + tok = stream.read(1) + cnt+=1 + return (cnt > 1) + + +def skipOverComment(stream): + tok = stream.read(1) + stream.seek(-1, 1) + if tok == b_('%'): + while tok not in (b_('\n'), b_('\r')): + tok = stream.read(1) + + +def readUntilRegex(stream, regex, ignore_eof=False): + """ + Reads until the regular expression pattern matched (ignore the match) + Raise PdfStreamError on premature end-of-file. + :param bool ignore_eof: If true, ignore end-of-line and return immediately + """ + name = b_('') + while True: + tok = stream.read(16) + if not tok: + # stream has truncated prematurely + if ignore_eof == True: + return name + else: + raise PdfStreamError("Stream has ended unexpectedly") + m = regex.search(tok) + if m is not None: + name += tok[:m.start()] + stream.seek(m.start()-len(tok), 1) + break + name += tok + return name + + +class ConvertFunctionsToVirtualList(object): + def __init__(self, lengthFunction, getFunction): + self.lengthFunction = lengthFunction + self.getFunction = getFunction + + def __len__(self): + return self.lengthFunction() + + def __getitem__(self, index): + if isinstance(index, slice): + indices = xrange_fn(*index.indices(len(self))) + cls = type(self) + return cls(indices.__len__, lambda idx: self[indices[idx]]) + if not isInt(index): + raise TypeError("sequence indices must be integers") + len_self = len(self) + if index < 0: + # support negative indexes + index = len_self + index + if index < 0 or index >= len_self: + raise IndexError("sequence index out of range") + return self.getFunction(index) + + +def RC4_encrypt(key, plaintext): + S = [i for i in range(256)] + j = 0 + for i in range(256): + j = (j + S[i] + ord_(key[i % len(key)])) % 256 + S[i], S[j] = S[j], S[i] + i, j = 0, 0 + retval = b_("") + for x in range(len(plaintext)): + i = (i + 1) % 256 + j = (j + S[i]) % 256 + S[i], S[j] = S[j], S[i] + t = S[(S[i] + S[j]) % 256] + retval += b_(chr(ord_(plaintext[x]) ^ t)) + return retval + + +def matrixMultiply(a, b): + return [[sum([float(i)*float(j) + for i, j in zip(row, col)] + ) for col in zip(*b)] + for row in a] + + +def markLocation(stream): + """Creates text file showing current location in context.""" + # Mainly for debugging + RADIUS = 5000 + stream.seek(-RADIUS, 1) + outputDoc = open('PyPDF2_pdfLocation.txt', 'w') + outputDoc.write(stream.read(RADIUS)) + outputDoc.write('HERE') + outputDoc.write(stream.read(RADIUS)) + outputDoc.close() + stream.seek(-RADIUS, 1) + + +class PyPdfError(Exception): + pass + + +class PdfReadError(PyPdfError): + pass + + +class PageSizeNotDefinedError(PyPdfError): + pass + + +class PdfReadWarning(UserWarning): + pass + + +class PdfStreamError(PdfReadError): + pass + + +if sys.version_info[0] < 3: + def b_(s): + return s +else: + B_CACHE = {} + + def b_(s): + bc = B_CACHE + if s in bc: + return bc[s] + if type(s) == bytes: + return s + else: + r = s.encode('latin-1') + if len(s) < 2: + bc[s] = r + return r + + +def u_(s): + if sys.version_info[0] < 3: + return unicode(s, 'unicode_escape') + else: + return s + + +def str_(b): + if sys.version_info[0] < 3: + return b + else: + if type(b) == bytes: + return b.decode('latin-1') + else: + return b + + +def ord_(b): + if sys.version_info[0] < 3 or type(b) == str: + return ord(b) + else: + return b + + +def chr_(c): + if sys.version_info[0] < 3: + return c + else: + return chr(c) + + +def barray(b): + if sys.version_info[0] < 3: + return b + else: + return bytearray(b) + + +def hexencode(b): + if sys.version_info[0] < 3: + return b.encode('hex') + else: + import codecs + coder = codecs.getencoder('hex_codec') + return coder(b)[0] + + +def hexStr(num): + return hex(num).replace('L', '') + + +WHITESPACES = [b_(x) for x in [' ', '\n', '\r', '\t', '\x00']] diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/xmp.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/xmp.py new file mode 100644 index 0000000..7ba62f0 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/PyPDF2_G/xmp.py @@ -0,0 +1,358 @@ +import re +import datetime +import decimal +from .generic import PdfObject +from xml.dom import getDOMImplementation +from xml.dom.minidom import parseString +from .utils import u_ + +RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +DC_NAMESPACE = "http://purl.org/dc/elements/1.1/" +XMP_NAMESPACE = "http://ns.adobe.com/xap/1.0/" +PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/" +XMPMM_NAMESPACE = "http://ns.adobe.com/xap/1.0/mm/" + +# What is the PDFX namespace, you might ask? I might ask that too. It's +# a completely undocumented namespace used to place "custom metadata" +# properties, which are arbitrary metadata properties with no semantic or +# documented meaning. Elements in the namespace are key/value-style storage, +# where the element name is the key and the content is the value. The keys +# are transformed into valid XML identifiers by substituting an invalid +# identifier character with \u2182 followed by the unicode hex ID of the +# original character. A key like "my car" is therefore "my\u21820020car". +# +# \u2182, in case you're wondering, is the unicode character +# \u{ROMAN NUMERAL TEN THOUSAND}, a straightforward and obvious choice for +# escaping characters. +# +# Intentional users of the pdfx namespace should be shot on sight. A +# custom data schema and sensical XML elements could be used instead, as is +# suggested by Adobe's own documentation on XMP (under "Extensibility of +# Schemas"). +# +# Information presented here on the /pdfx/ schema is a result of limited +# reverse engineering, and does not constitute a full specification. +PDFX_NAMESPACE = "http://ns.adobe.com/pdfx/1.3/" + +iso8601 = re.compile(""" + (?P[0-9]{4}) + (- + (?P[0-9]{2}) + (- + (?P[0-9]+) + (T + (?P[0-9]{2}): + (?P[0-9]{2}) + (:(?P[0-9]{2}(.[0-9]+)?))? + (?PZ|[-+][0-9]{2}:[0-9]{2}) + )? + )? + )? + """, re.VERBOSE) + + +class XmpInformation(PdfObject): + """ + An object that represents Adobe XMP metadata. + Usually accessed by :meth:`getXmpMetadata()` + """ + + def __init__(self, stream): + self.stream = stream + docRoot = parseString(self.stream.getData()) + self.rdfRoot = docRoot.getElementsByTagNameNS(RDF_NAMESPACE, "RDF")[0] + self.cache = {} + + def writeToStream(self, stream, encryption_key): + self.stream.writeToStream(stream, encryption_key) + + def getElement(self, aboutUri, namespace, name): + for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"): + if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri: + attr = desc.getAttributeNodeNS(namespace, name) + if attr != None: + yield attr + for element in desc.getElementsByTagNameNS(namespace, name): + yield element + + def getNodesInNamespace(self, aboutUri, namespace): + for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"): + if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri: + for i in range(desc.attributes.length): + attr = desc.attributes.item(i) + if attr.namespaceURI == namespace: + yield attr + for child in desc.childNodes: + if child.namespaceURI == namespace: + yield child + + def _getText(self, element): + text = "" + for child in element.childNodes: + if child.nodeType == child.TEXT_NODE: + text += child.data + return text + + def _converter_string(value): + return value + + def _converter_date(value): + m = iso8601.match(value) + year = int(m.group("year")) + month = int(m.group("month") or "1") + day = int(m.group("day") or "1") + hour = int(m.group("hour") or "0") + minute = int(m.group("minute") or "0") + second = decimal.Decimal(m.group("second") or "0") + seconds = second.to_integral(decimal.ROUND_FLOOR) + milliseconds = (second - seconds) * 1000000 + tzd = m.group("tzd") or "Z" + dt = datetime.datetime(year, month, day, hour, minute, seconds, milliseconds) + if tzd != "Z": + tzd_hours, tzd_minutes = [int(x) for x in tzd.split(":")] + tzd_hours *= -1 + if tzd_hours < 0: + tzd_minutes *= -1 + dt = dt + datetime.timedelta(hours=tzd_hours, minutes=tzd_minutes) + return dt + _test_converter_date = staticmethod(_converter_date) + + def _getter_bag(namespace, name, converter): + def get(self): + cached = self.cache.get(namespace, {}).get(name) + if cached: + return cached + retval = [] + for element in self.getElement("", namespace, name): + bags = element.getElementsByTagNameNS(RDF_NAMESPACE, "Bag") + if len(bags): + for bag in bags: + for item in bag.getElementsByTagNameNS(RDF_NAMESPACE, "li"): + value = self._getText(item) + value = converter(value) + retval.append(value) + ns_cache = self.cache.setdefault(namespace, {}) + ns_cache[name] = retval + return retval + return get + + def _getter_seq(namespace, name, converter): + def get(self): + cached = self.cache.get(namespace, {}).get(name) + if cached: + return cached + retval = [] + for element in self.getElement("", namespace, name): + seqs = element.getElementsByTagNameNS(RDF_NAMESPACE, "Seq") + if len(seqs): + for seq in seqs: + for item in seq.getElementsByTagNameNS(RDF_NAMESPACE, "li"): + value = self._getText(item) + value = converter(value) + retval.append(value) + else: + value = converter(self._getText(element)) + retval.append(value) + ns_cache = self.cache.setdefault(namespace, {}) + ns_cache[name] = retval + return retval + return get + + def _getter_langalt(namespace, name, converter): + def get(self): + cached = self.cache.get(namespace, {}).get(name) + if cached: + return cached + retval = {} + for element in self.getElement("", namespace, name): + alts = element.getElementsByTagNameNS(RDF_NAMESPACE, "Alt") + if len(alts): + for alt in alts: + for item in alt.getElementsByTagNameNS(RDF_NAMESPACE, "li"): + value = self._getText(item) + value = converter(value) + retval[item.getAttribute("xml:lang")] = value + else: + retval["x-default"] = converter(self._getText(element)) + ns_cache = self.cache.setdefault(namespace, {}) + ns_cache[name] = retval + return retval + return get + + def _getter_single(namespace, name, converter): + def get(self): + cached = self.cache.get(namespace, {}).get(name) + if cached: + return cached + value = None + for element in self.getElement("", namespace, name): + if element.nodeType == element.ATTRIBUTE_NODE: + value = element.nodeValue + else: + value = self._getText(element) + break + if value != None: + value = converter(value) + ns_cache = self.cache.setdefault(namespace, {}) + ns_cache[name] = value + return value + return get + + dc_contributor = property(_getter_bag(DC_NAMESPACE, "contributor", _converter_string)) + """ + Contributors to the resource (other than the authors). An unsorted + array of names. + """ + + dc_coverage = property(_getter_single(DC_NAMESPACE, "coverage", _converter_string)) + """ + Text describing the extent or scope of the resource. + """ + + dc_creator = property(_getter_seq(DC_NAMESPACE, "creator", _converter_string)) + """ + A sorted array of names of the authors of the resource, listed in order + of precedence. + """ + + dc_date = property(_getter_seq(DC_NAMESPACE, "date", _converter_date)) + """ + A sorted array of dates (datetime.datetime instances) of signifigance to + the resource. The dates and times are in UTC. + """ + + dc_description = property(_getter_langalt(DC_NAMESPACE, "description", _converter_string)) + """ + A language-keyed dictionary of textual descriptions of the content of the + resource. + """ + + dc_format = property(_getter_single(DC_NAMESPACE, "format", _converter_string)) + """ + The mime-type of the resource. + """ + + dc_identifier = property(_getter_single(DC_NAMESPACE, "identifier", _converter_string)) + """ + Unique identifier of the resource. + """ + + dc_language = property(_getter_bag(DC_NAMESPACE, "language", _converter_string)) + """ + An unordered array specifying the languages used in the resource. + """ + + dc_publisher = property(_getter_bag(DC_NAMESPACE, "publisher", _converter_string)) + """ + An unordered array of publisher names. + """ + + dc_relation = property(_getter_bag(DC_NAMESPACE, "relation", _converter_string)) + """ + An unordered array of text descriptions of relationships to other + documents. + """ + + dc_rights = property(_getter_langalt(DC_NAMESPACE, "rights", _converter_string)) + """ + A language-keyed dictionary of textual descriptions of the rights the + user has to this resource. + """ + + dc_source = property(_getter_single(DC_NAMESPACE, "source", _converter_string)) + """ + Unique identifier of the work from which this resource was derived. + """ + + dc_subject = property(_getter_bag(DC_NAMESPACE, "subject", _converter_string)) + """ + An unordered array of descriptive phrases or keywrods that specify the + topic of the content of the resource. + """ + + dc_title = property(_getter_langalt(DC_NAMESPACE, "title", _converter_string)) + """ + A language-keyed dictionary of the title of the resource. + """ + + dc_type = property(_getter_bag(DC_NAMESPACE, "type", _converter_string)) + """ + An unordered array of textual descriptions of the document type. + """ + + pdf_keywords = property(_getter_single(PDF_NAMESPACE, "Keywords", _converter_string)) + """ + An unformatted text string representing document keywords. + """ + + pdf_pdfversion = property(_getter_single(PDF_NAMESPACE, "PDFVersion", _converter_string)) + """ + The PDF file version, for example 1.0, 1.3. + """ + + pdf_producer = property(_getter_single(PDF_NAMESPACE, "Producer", _converter_string)) + """ + The name of the tool that created the PDF document. + """ + + xmp_createDate = property(_getter_single(XMP_NAMESPACE, "CreateDate", _converter_date)) + """ + The date and time the resource was originally created. The date and + time are returned as a UTC datetime.datetime object. + """ + + xmp_modifyDate = property(_getter_single(XMP_NAMESPACE, "ModifyDate", _converter_date)) + """ + The date and time the resource was last modified. The date and time + are returned as a UTC datetime.datetime object. + """ + + xmp_metadataDate = property(_getter_single(XMP_NAMESPACE, "MetadataDate", _converter_date)) + """ + The date and time that any metadata for this resource was last + changed. The date and time are returned as a UTC datetime.datetime + object. + """ + + xmp_creatorTool = property(_getter_single(XMP_NAMESPACE, "CreatorTool", _converter_string)) + """ + The name of the first known tool used to create the resource. + """ + + xmpmm_documentId = property(_getter_single(XMPMM_NAMESPACE, "DocumentID", _converter_string)) + """ + The common identifier for all versions and renditions of this resource. + """ + + xmpmm_instanceId = property(_getter_single(XMPMM_NAMESPACE, "InstanceID", _converter_string)) + """ + An identifier for a specific incarnation of a document, updated each + time a file is saved. + """ + + def custom_properties(self): + if not hasattr(self, "_custom_properties"): + self._custom_properties = {} + for node in self.getNodesInNamespace("", PDFX_NAMESPACE): + key = node.localName + while True: + # see documentation about PDFX_NAMESPACE earlier in file + idx = key.find(u_("\u2182")) + if idx == -1: + break + key = key[:idx] + chr(int(key[idx+1:idx+5], base=16)) + key[idx+5:] + if node.nodeType == node.ATTRIBUTE_NODE: + value = node.nodeValue + else: + value = self._getText(node) + self._custom_properties[key] = value + return self._custom_properties + + custom_properties = property(custom_properties) + """ + Retrieves custom metadata properties defined in the undocumented pdfx + metadata schema. + + :return: a dictionary of key/value items for custom metadata properties. + :rtype: dict + """ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__init__.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/__init__.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/__init__.cpython-35.pyc new file mode 100644 index 0000000..0986fa4 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/__init__.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/elib_intl3.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/elib_intl3.cpython-35.pyc new file mode 100644 index 0000000..94fd7f8 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/elib_intl3.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/files_chooser.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/files_chooser.cpython-35.pyc new file mode 100644 index 0000000..0baeafb Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/files_chooser.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfbooklet.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfbooklet.cpython-35.pyc new file mode 100644 index 0000000..84e2368 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfbooklet.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfshuffler_g3.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfshuffler_g3.cpython-35.pyc new file mode 100644 index 0000000..9392967 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfshuffler_g3.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfshuffler_iconview3.cpython-35.pyc b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfshuffler_iconview3.cpython-35.pyc new file mode 100644 index 0000000..44d4d44 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/__pycache__/pdfshuffler_iconview3.cpython-35.pyc differ diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/elib_intl3.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/elib_intl3.py new file mode 100644 index 0000000..4e1ce3f --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/elib_intl3.py @@ -0,0 +1,527 @@ +# -*- coding: utf-8 -*- + +from __future__ import print_function +from __future__ import unicode_literals + +# +# Copyright � 2007-2010 Dieter Verfaillie +# +# This file is part of elib.intl. +# +# elib.intl is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# elib.intl 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with elib.intl. If not, see . + +# Update by Dysmas : added the option to explicitly define the language : +# parameter "code" in the function : install , line 453 + + + +''' +The elib.intl module provides enhanced internationalization (I18N) services for +your Python modules and applications. + +elib.intl wraps Python's :func:`gettext` functionality and adds the following on +Microsoft Windows systems: + + - automatic detection of the current screen language (not necessarily the same + as the installation language) provided by MUI packs, + - makes sure internationalized C libraries which internally invoke gettext() or + dcgettext() can properly locate their message catalogs. This fixes a known + limitation in gettext's Windows support when using eg. gtk.builder or gtk.glade. + +See http://www.gnu.org/software/gettext/FAQ.html#windows_setenv for more +information. + +The elib.intl module defines the following functions: +''' + + +__all__ = ['install', 'install_module'] +__version__ = '0.0.3' +__docformat__ = 'restructuredtext' + + +import os +import sys +import locale +import gettext + +from logging import getLogger + + +logger = getLogger('elib.intl') + + +def _isofromlcid(lcid): + ''' + :param lcid: Microsoft Windows LCID + :returns: the ISO 639-1 language code for a given lcid. If there is no + ISO 639-1 language code assigned to the language specified by lcid, + the ISO 639-2 language code is returned. If the language specified + by lcid is unknown in the ISO 639-x database, None is returned. + + More information can be found on the following websites: + - List of ISO 639-1 and ISO 639-2 language codes: http://www.loc.gov/standards/iso639-2/ + - List of known lcid's: http://www.microsoft.com/globaldev/reference/lcid-all.mspx + - List of known MUI packs: http://www.microsoft.com/globaldev/reference/win2k/setup/Langid.mspx + ''' + mapping = {1078: 'af', #Afrikaans - South Africa + 1052: 'sq', #Albanian - Albania + 1118: 'am', #Amharic - Ethiopia + 1025: 'ar', #Arabic - Saudi Arabia + 5121: 'ar', #Arabic - Algeria + 15361: 'ar', #Arabic - Bahrain + 3073: 'ar', #Arabic - Egypt + 2049: 'ar', #Arabic - Iraq + 11265: 'ar', #Arabic - Jordan + 13313: 'ar', #Arabic - Kuwait + 12289: 'ar', #Arabic - Lebanon + 4097: 'ar', #Arabic - Libya + 6145: 'ar', #Arabic - Morocco + 8193: 'ar', #Arabic - Oman + 16385: 'ar', #Arabic - Qatar + 10241: 'ar', #Arabic - Syria + 7169: 'ar', #Arabic - Tunisia + 14337: 'ar', #Arabic - U.A.E. + 9217: 'ar', #Arabic - Yemen + 1067: 'hy', #Armenian - Armenia + 1101: 'as', #Assamese + 2092: 'az', #Azeri (Cyrillic) + 1068: 'az', #Azeri (Latin) + 1069: 'eu', #Basque + 1059: 'be', #Belarusian + 1093: 'bn', #Bengali (India) + 2117: 'bn', #Bengali (Bangladesh) + 5146: 'bs', #Bosnian (Bosnia/Herzegovina) + 1026: 'bg', #Bulgarian + 1109: 'my', #Burmese + 1027: 'ca', #Catalan + 1116: 'chr', #Cherokee - United States + 2052: 'zh', #Chinese - People's Republic of China + 4100: 'zh', #Chinese - Singapore + 1028: 'zh', #Chinese - Taiwan + 3076: 'zh', #Chinese - Hong Kong SAR + 5124: 'zh', #Chinese - Macao SAR + 1050: 'hr', #Croatian + 4122: 'hr', #Croatian (Bosnia/Herzegovina) + 1029: 'cs', #Czech + 1030: 'da', #Danish + 1125: 'dv', #Divehi + 1043: 'nl', #Dutch - Netherlands + 2067: 'nl', #Dutch - Belgium + 1126: 'bin', #Edo + 1033: 'en', #English - United States + 2057: 'en', #English - United Kingdom + 3081: 'en', #English - Australia + 10249: 'en', #English - Belize + 4105: 'en', #English - Canada + 9225: 'en', #English - Caribbean + 15369: 'en', #English - Hong Kong SAR + 16393: 'en', #English - India + 14345: 'en', #English - Indonesia + 6153: 'en', #English - Ireland + 8201: 'en', #English - Jamaica + 17417: 'en', #English - Malaysia + 5129: 'en', #English - New Zealand + 13321: 'en', #English - Philippines + 18441: 'en', #English - Singapore + 7177: 'en', #English - South Africa + 11273: 'en', #English - Trinidad + 12297: 'en', #English - Zimbabwe + 1061: 'et', #Estonian + 1080: 'fo', #Faroese + 1065: None, #TODO: Farsi + 1124: 'fil', #Filipino + 1035: 'fi', #Finnish + 1036: 'fr', #French - France + 2060: 'fr', #French - Belgium + 11276: 'fr', #French - Cameroon + 3084: 'fr', #French - Canada + 9228: 'fr', #French - Democratic Rep. of Congo + 12300: 'fr', #French - Cote d'Ivoire + 15372: 'fr', #French - Haiti + 5132: 'fr', #French - Luxembourg + 13324: 'fr', #French - Mali + 6156: 'fr', #French - Monaco + 14348: 'fr', #French - Morocco + 58380: 'fr', #French - North Africa + 8204: 'fr', #French - Reunion + 10252: 'fr', #French - Senegal + 4108: 'fr', #French - Switzerland + 7180: 'fr', #French - West Indies + 1122: 'fy', #Frisian - Netherlands + 1127: None, #TODO: Fulfulde - Nigeria + 1071: 'mk', #FYRO Macedonian + 2108: 'ga', #Gaelic (Ireland) + 1084: 'gd', #Gaelic (Scotland) + 1110: 'gl', #Galician + 1079: 'ka', #Georgian + 1031: 'de', #German - Germany + 3079: 'de', #German - Austria + 5127: 'de', #German - Liechtenstein + 4103: 'de', #German - Luxembourg + 2055: 'de', #German - Switzerland + 1032: 'el', #Greek + 1140: 'gn', #Guarani - Paraguay + 1095: 'gu', #Gujarati + 1128: 'ha', #Hausa - Nigeria + 1141: 'haw', #Hawaiian - United States + 1037: 'he', #Hebrew + 1081: 'hi', #Hindi + 1038: 'hu', #Hungarian + 1129: None, #TODO: Ibibio - Nigeria + 1039: 'is', #Icelandic + 1136: 'ig', #Igbo - Nigeria + 1057: 'id', #Indonesian + 1117: 'iu', #Inuktitut + 1040: 'it', #Italian - Italy + 2064: 'it', #Italian - Switzerland + 1041: 'ja', #Japanese + 1099: 'kn', #Kannada + 1137: 'kr', #Kanuri - Nigeria + 2144: 'ks', #Kashmiri + 1120: 'ks', #Kashmiri (Arabic) + 1087: 'kk', #Kazakh + 1107: 'km', #Khmer + 1111: 'kok', #Konkani + 1042: 'ko', #Korean + 1088: 'ky', #Kyrgyz (Cyrillic) + 1108: 'lo', #Lao + 1142: 'la', #Latin + 1062: 'lv', #Latvian + 1063: 'lt', #Lithuanian + 1086: 'ms', #Malay - Malaysia + 2110: 'ms', #Malay - Brunei Darussalam + 1100: 'ml', #Malayalam + 1082: 'mt', #Maltese + 1112: 'mni', #Manipuri + 1153: 'mi', #Maori - New Zealand + 1102: 'mr', #Marathi + 1104: 'mn', #Mongolian (Cyrillic) + 2128: 'mn', #Mongolian (Mongolian) + 1121: 'ne', #Nepali + 2145: 'ne', #Nepali - India + 1044: 'no', #Norwegian (Bokm??l) + 2068: 'no', #Norwegian (Nynorsk) + 1096: 'or', #Oriya + 1138: 'om', #Oromo + 1145: 'pap', #Papiamentu + 1123: 'ps', #Pashto + 1045: 'pl', #Polish + 1046: 'pt', #Portuguese - Brazil + 2070: 'pt', #Portuguese - Portugal + 1094: 'pa', #Punjabi + 2118: 'pa', #Punjabi (Pakistan) + 1131: 'qu', #Quecha - Bolivia + 2155: 'qu', #Quecha - Ecuador + 3179: 'qu', #Quecha - Peru + 1047: 'rm', #Rhaeto-Romanic + 1048: 'ro', #Romanian + 2072: 'ro', #Romanian - Moldava + 1049: 'ru', #Russian + 2073: 'ru', #Russian - Moldava + 1083: 'se', #Sami (Lappish) + 1103: 'sa', #Sanskrit + 1132: 'nso', #Sepedi + 3098: 'sr', #Serbian (Cyrillic) + 2074: 'sr', #Serbian (Latin) + 1113: 'sd', #Sindhi - India + 2137: 'sd', #Sindhi - Pakistan + 1115: 'si', #Sinhalese - Sri Lanka + 1051: 'sk', #Slovak + 1060: 'sl', #Slovenian + 1143: 'so', #Somali + 1070: 'wen', #Sorbian + 3082: 'es', #Spanish - Spain (Modern Sort) + 1034: 'es', #Spanish - Spain (Traditional Sort) + 11274: 'es', #Spanish - Argentina + 16394: 'es', #Spanish - Bolivia + 13322: 'es', #Spanish - Chile + 9226: 'es', #Spanish - Colombia + 5130: 'es', #Spanish - Costa Rica + 7178: 'es', #Spanish - Dominican Republic + 12298: 'es', #Spanish - Ecuador + 17418: 'es', #Spanish - El Salvador + 4106: 'es', #Spanish - Guatemala + 18442: 'es', #Spanish - Honduras + 58378: 'es', #Spanish - Latin America + 2058: 'es', #Spanish - Mexico + 19466: 'es', #Spanish - Nicaragua + 6154: 'es', #Spanish - Panama + 15370: 'es', #Spanish - Paraguay + 10250: 'es', #Spanish - Peru + 20490: 'es', #Spanish - Puerto Rico + 21514: 'es', #Spanish - United States + 14346: 'es', #Spanish - Uruguay + 8202: 'es', #Spanish - Venezuela + 1072: None, #TODO: Sutu + 1089: 'sw', #Swahili + 1053: 'sv', #Swedish + 2077: 'sv', #Swedish - Finland + 1114: 'syr', #Syriac + 1064: 'tg', #Tajik + 1119: None, #TODO: Tamazight (Arabic) + 2143: None, #TODO: Tamazight (Latin) + 1097: 'ta', #Tamil + 1092: 'tt', #Tatar + 1098: 'te', #Telugu + 1054: 'th', #Thai + 2129: 'bo', #Tibetan - Bhutan + 1105: 'bo', #Tibetan - People's Republic of China + 2163: 'ti', #Tigrigna - Eritrea + 1139: 'ti', #Tigrigna - Ethiopia + 1073: 'ts', #Tsonga + 1074: 'tn', #Tswana + 1055: 'tr', #Turkish + 1090: 'tk', #Turkmen + 1152: 'ug', #Uighur - China + 1058: 'uk', #Ukrainian + 1056: 'ur', #Urdu + 2080: 'ur', #Urdu - India + 2115: 'uz', #Uzbek (Cyrillic) + 1091: 'uz', #Uzbek (Latin) + 1075: 've', #Venda + 1066: 'vi', #Vietnamese + 1106: 'cy', #Welsh + 1076: 'xh', #Xhosa + 1144: 'ii', #Yi + 1085: 'yi', #Yiddish + 1130: 'yo', #Yoruba + 1077: 'zu'} #Zulu + + return mapping[lcid] + +def _getscreenlanguage(): + global language_code + ''' + :returns: the ISO 639-x language code for this session. + + If the LANGUAGE environment variable is set, it's value overrides the + screen language detection. Otherwise the screen language is determined by + the currently selected Microsoft Windows MUI language pack or the Microsoft + Windows installation language. + + Works on Microsoft Windows 2000 and up. + ''' + if sys.platform == 'win32' or sys.platform == 'nt': + # Start with nothing + lang = None + + # Check the LANGUAGE environment variable + lang = os.getenv('LANGUAGE') + if language_code != "" : + lang = language_code + if lang is None: + # Start with nothing + lcid = None + + try: + from ctypes import windll + lcid = windll.kernel32.GetUserDefaultUILanguage() + except: + logger.debug('Failed to get current screen language with \'GetUserDefaultUILanguage\'') + finally: + if lcid is None: + lang = 'C' + else: + lang = _isofromlcid(lcid) + + logger.debug('Windows screen language is \'%s\' (lcid %s)' % (lang, lcid)) + + return lang + +def _putenv(name, value): + ''' + :param name: environment variable name + :param value: environment variable value + + This function ensures that changes to an environment variable are applied + to each copy of the environment variables used by a process. Starting from + Python 2.4, os.environ changes only apply to the copy Python keeps (os.environ) + and are no longer automatically applied to the other copies for the process. + + On Microsoft Windows, each process has multiple copies of the environment + variables, one managed by the OS and one managed by the C library. We also + need to take care of the fact that the C library used by Python is not + necessarily the same as the C library used by pygtk and friends. This because + the latest releases of pygtk and friends are built with mingw32 and are thus + linked against msvcrt.dll. The official gtk+ binaries have always been built + in this way. + ''' + + if sys.platform == 'win32' or sys.platform == 'nt': + from ctypes import windll + from ctypes import cdll + from ctypes.util import find_msvcrt + + # Update Python's copy of the environment variables + os.environ[name] = value + + # Update the copy maintained by Windows (so SysInternals Process Explorer sees it) + try: + result = windll.kernel32.SetEnvironmentVariableW(name, value) + if result == 0: raise Warning + except Exception: + logger.debug('Failed to set environment variable \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % name) + else: + logger.debug('Set environment variable \'%s\' to \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % (name, value)) + + # Update the copy maintained by msvcrt (used by gtk+ runtime) + try: + result = cdll.msvcrt._putenv('%s=%s' % (name, value)) + if result !=0: raise Warning + except Exception: + logger.debug('Failed to set environment variable \'%s\' (\'msvcrt._putenv\')' % name) + else: + logger.debug('Set environment variable \'%s\' to \'%s\' (\'msvcrt._putenv\')' % (name, value)) + + # Update the copy maintained by whatever c runtime is used by Python + try: + msvcrt = find_msvcrt() + msvcrtname = str(msvcrt).split('.')[0] if '.' in msvcrt else str(msvcrt) + result = cdll.LoadLibrary(msvcrt)._putenv('%s=%s' % (name, value)) + if result != 0: raise Warning + except Exception: + logger.debug('Failed to set environment variable \'%s\' (\'%s._putenv\')' % (name, msvcrtname)) + else: + logger.debug('Set environment variable \'%s\' to \'%s\' (\'%s._putenv\')' % (name, value, msvcrtname)) + +def _dugettext(domain, message): + ''' + :param domain: translation domain + :param message: message to translate + :returns: the translated message + + Unicode version of :func:`gettext.dgettext`. + ''' + try: + t = gettext.translation(domain, gettext._localedirs.get(domain, None), + codeset=gettext._localecodesets.get(domain)) + except IOError: + return message + else: + return t.ugettext(message) + +def _install(domain, localedir, asglobal=False): + ''' + :param domain: translation domain + :param localedir: locale directory + :param asglobal: if True, installs the function _() in Python�s builtin namespace. Default is False + + Private function doing all the work for the :func:`elib.intl.install` and + :func:`elib.intl.install_module` functions. + ''' + # prep locale system + if asglobal: + locale.setlocale(locale.LC_ALL, '') + + # on windows systems, set the LANGUAGE environment variable + if sys.platform == 'win32' or sys.platform == 'nt': + _putenv('LANGUAGE', _getscreenlanguage()) + #print "=========2>", os.getenv('LANGUAGE') + #print ("=========2>",_getscreenlanguage()) + + + # The locale module on Max OS X lacks bindtextdomain so we specifically + # test on linux2 here. See commit 4ae8b26fd569382ab66a9e844daa0e01de409ceb + if sys.platform == 'linux2': + locale.bindtextdomain(domain, localedir) + locale.bind_textdomain_codeset(domain, 'UTF-8') + locale.textdomain(domain) + + # initialize Python's gettext interface + gettext.bindtextdomain(domain, localedir) + gettext.bind_textdomain_codeset(domain, 'UTF-8') + + if asglobal: + gettext.textdomain(domain) + + # on windows systems, initialize libintl + domain = domain.encode("cp1252") + localedir = localedir.encode("cp1252") + if sys.platform == 'win32' or sys.platform == 'nt': + from ctypes import cdll + libintl = cdll.LoadLibrary("libintl-8.dll") + libintl.bindtextdomain(domain, localedir) + libintl.bind_textdomain_codeset(domain, 'UTF-8') + + if asglobal: + libintl.textdomain(domain) + + del libintl + +def install(domain, localedir, code = ""): + ''' + :param domain: translation domain + :param localedir: locale directory + :param code: Language code required. Default means autodetect. + + Installs the function _() in Python�s builtin namespace, based on + domain and localedir. Codeset is always UTF-8. + + As seen below, you usually mark the strings in your application that are + candidates for translation, by wrapping them in a call to the _() function, + like this: + + .. sourcecode:: python + + import elib.intl + elib.intl.install('myapplication', '/path/to/usr/share/locale') + print _('This string will be translated.') + + Note that this is only one way, albeit the most convenient way, + to make the _() function available to your application. Because it affects + the entire application globally, and specifically Python�s built-in + namespace, localized modules should never install _(). Instead, you should + use :func:`elib.intl.install_module` to make _() available to your module. + ''' + + global language_code + language_code = code + + _install(domain, localedir, True) + gettext.install(domain, localedir) + +def install_module(domain, localedir): + ''' + :param domain: translation domain + :param localedir: locale directory + :returns: an anonymous function object, based on domain and localedir. + Codeset is always UTF-8. + + You may find this function usefull when writing localized modules. + Use this code to make _() available to your module: + + .. sourcecode:: python + + import elib.intl + _ = elib.intl.install_module('mymodule', '/path/to/usr/share/locale') + print _('This string will be translated.') + + When writing a package, you can usually do this in the package's __init__.py + file and import the _() function from the package namespace as needed. + ''' + _install(domain, localedir, False) + return lambda message: _dugettext(domain, message) + + +def main() : + language = "" + if sys.platform == 'win32' or sys.platform == 'nt': + install("pdfbooklet", "share/locale", language) + print (_getscreenlanguage()) + else : + install("pdfbooklet", "/usr/share/locale", language) + print (_("There is no selection")) +if __name__ == '__main__' : + main() \ No newline at end of file diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/files_chooser.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/files_chooser.py new file mode 100644 index 0000000..918aeb2 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/files_chooser.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# coding: utf-8 -*- + + +# version 3.0.3 + + +import os, sys + + +from gi.repository import Gtk, Gio +from pdfbooklet.PyPDF2_G import PdfFileReader + + +def alert(message, type = 0) : + + dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, + Gtk.ButtonsType.CLOSE , message) + dialog.run() + dialog.destroy() + +class Chooser: + def __init__(self, + inputFiles_a = None, + share_path_u = "", + mru_dir = ""): + + self.inputFiles_a = inputFiles_a + self.chooser1 = Gtk.Builder() + self.chooser1.add_from_file(os.path.join(share_path_u, 'data/chooser_dialog.glade')) + self.chooser1.connect_signals(self) + self.chooser = self.chooser1.get_object("filechooserdialog1") + + # treeview + self.treeview1 = self.chooser1.get_object("treeview1") + + # create a TreeStore with one string column to use as the model + #self.treestore = self.treeview1.get_model() + self.treestore = Gtk.ListStore(str,int) + self.cell = Gtk.CellRendererText() + + # set the model for TreeView + self.treeview1.set_model(self.treestore) + self.tvcolumn = Gtk.TreeViewColumn(_('Filename')) + self.treeview1.append_column(self.tvcolumn) + + # add the cell to the tvcolumn and allow it to expand + self.tvcolumn.pack_start(self.cell, True) + + # set the cell "text" attribute to column 0 - retrieve text + # from that column in treestore + self.tvcolumn.add_attribute(self.cell, 'text', 0) + +## self.tvcolumn = Gtk.TreeViewColumn(_('Pages')) +## self.treeview1.append_column(self.tvcolumn) +## self.tvcolumn.pack_start(self.cell, True) +## self.tvcolumn.add_attribute(self.cell, 'text', 1) + + # Allow drag and drop reordering of rows + self.treeview1.set_reorderable(True) + + # load files in parameter list + for key in self.inputFiles_a : + self.treestore.append([self.inputFiles_a[key], 0]) + + + old_dir = "" + old_name = "" + + + chooser = self.chooser1.get_object("filechooserdialog1") + chooser.set_current_folder(mru_dir) + chooser.set_select_multiple(True) + + filter_all = Gtk.FileFilter() + filter_all.set_name(_('All files')) + filter_all.add_pattern('*') + chooser.add_filter(filter_all) + + filter_pdf = Gtk.FileFilter() + filter_pdf.set_name(_('PDF files')) + filter_pdf.add_mime_type('application/pdf') + filter_pdf.add_pattern('*.pdf') + chooser.add_filter(filter_pdf) + chooser.set_filter(filter_pdf) + + response = chooser.run() + + + def chooserClose(self, widget) : + self.chooser.destroy() + + def chooserOK(self, widget) : + self.treestore.clear() + self.genFilesArray() + self.chooser.destroy() + + def pdf_remove(self, widget) : + selection = self.treeview1.get_selection() + #sel = selection.get_selected_rows() + model, iter0 = selection.get_selected() + model.remove(iter0) + + def pdf_up(self, widget) : + selection = self.treeview1.get_selection() + model, iter0 = selection.get_selected() + string = model.get_string_from_iter(iter0) + newpos = int(string) - 1 + if newpos < 0 : newpos = 0 + newpos = model.get_iter_from_string(str(newpos)) + model.move_before(iter0, newpos) + + def pdf_down(self, widget) : + selection = self.treeview1.get_selection() + model, iter0 = selection.get_selected() + model.move_after(iter0, model.iter_next(iter0)) + + # clears the list and open a file + def pdf_open(self,widget) : + self.treestore.clear() + self.add_file("") + + def add_file(self, widget): + + for filename in self.chooser.get_filenames(): + if os.path.isfile(filename): + # FIXME +## f = Gio.File(filename) +## f_info = f.query_info('standard::content-type') +## mime_type = f_info.get_content_type() + mime_type = ".pdf" + if mime_type == 'application/pdf' or mime_type == '.pdf': + self.loadPdfFile(filename) + else : + print(_('File type not supported!')) + else: + print(_('File %s does not exist') % filename) + + + def loadPdfFile(self,filename) : + + pdfFile = PdfFileReader(open(filename, "rb")) + numpages = pdfFile.getNumPages() + self.treestore.append([filename, numpages]) + + # regenerate the array of files (easier to use than the treestore) + def genFilesArray(self, dummy = "") : + inputFiles_a = {} + selectedFiles1 = self.chooser.get_filenames() + + # eliminate directories + selectedFiles = [] + for file_u in selectedFiles1 : + if os.path.isdir(file_u) : + alert(_("You have chosen a directory, it is not supported")) + else : + # FIXME +## f = gio.File(filename) +## f_info = f.query_info('standard::content-type') +## mime_type = f_info.get_content_type() +## if mime_type == 'application/pdf' or mime_type == '.pdf': +## self.loadPdfFile(filename) +## else : +## print(_('File type not supported!')) + selectedFiles.append(file_u) + + if len(selectedFiles) == 0 : + print(_('Closed, no files selected')) + return + + size_i = len(self.treestore) + + if size_i == 0 : # nothing in the list + for i in range(len(selectedFiles)) : + inputFiles_a[i + 1] = selectedFiles[i] + else : # use the list + for i in range(size_i) : + iter0 = self.treestore.get_iter(i) + filename_s = self.treestore.get_value(iter0,0) + inputFiles_a[i + 1] = filename_s + self.inputFiles_a = inputFiles_a + + diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfbooklet.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfbooklet.py new file mode 100644 index 0000000..3682a15 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfbooklet.py @@ -0,0 +1,5076 @@ +#!/usr/bin/python3 +# -*- coding: utf8 -*- + +from __future__ import print_function +from __future__ import unicode_literals + + +# version 3.1.2 : bug fix : autoscale didn't work for a mix of portrait / landscape pages. +# version 3.1.1 : workaround for a bug appeared in Ubuntu 19 (see the OnDraw function) +# version 3.1 +# fix a serious bug which prevented auto-scale to work. + +# version 3.0.6, 09/05/2018 +# Bug fixes +# Better Linux support +# No longer uses a temporary file, preview data now in memory + +# version 3.0.5, 30 / 07 / 2017 +# German Translation added + +# version 3.0.4 +# New feature : add page numbers (still experimental) +# Gui : dotted line in the middle of a booklet - Still to be improved +# No longer uses the tempfiles/preview.pdf temporary file. +# this is now handled in Memory. Bugs to fix on that feature. +# To understand the reason for which we have used new_from_bytes and not new_from_data, see here : +# https://stackoverflow.com/questions/45838863/gio-memoryinputstream-does-not-free-memory-when-closed +# Fix bug for display of red rectangles when the output page is rotated 90° or 270° + +PB_version = "3.1.2" + + +""" + +website : pdfbooklet.sourceforge.net + +This software is a computer program whose purpose is to manipulate pdf files. + +This software is governed by the CeCILL license under French law and +abiding by the rules of distribution of free software. You can use, +modify and/ or redistribute the software under the terms of the CeCILL +license as circulated by CEA, CNRS and INRIA at the following URL +"http://www.cecill.info". + +As a counterpart to the access to the source code and rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors have only limited +liability. + +In this respect, the user's attention is drawn to the risks associated +with loading, using, modifying and/or developing or reproducing the +software by the user in light of its specific status of free software, +that may mean that it is complicated to manipulate, and that also +therefore means that it is reserved for developers and experienced +professionals having in-depth computer knowledge. Users are therefore +encouraged to load and test the software's suitability as regards their +requirements in conditions enabling the security of their systems and/or +data to be ensured and, more generally, to use and operate it in the +same conditions as regards security. + +The fact that you are presently reading this means that you have had +knowledge of the CeCILL license and that you accept its terms. + +========================================================================== +""" + + +""" +TODO : enregistrer un projet dans un répertoire avec caractères unicode +vérifier menuAdd + +selection_s : L'usage de cette variable serait à vérifier. Est-ce que cela ne crée pas de la confusion ? +Pourquoi ne pas utiliser directement config["options"]["pageSelection"] qu'elle remplace ? Un peu plus long, mais plus facile à déboguer. +Problème surtout à mettre au point : + Supposons une liste de fichiers ouverts dans lesquels on a fait une sélection. + Si on ouvre le gestionnaire de fichier a ajoute un fichier à la liste, la sélection est remise à zéro. + Ce n'est pas bon, il faudrait seulement ajouter les pages du nouveau fichier. + C'est assez compliqué à gérer si des fichiers ont été supprimés. Une routine de comparaison + avant et après avoir ouvert le gesionnaire devrait faire le travail. + +TODO : + +Autoscale : Distinguer les options : pour les pages et global ? Pas sûr que ce soit utile. +Quand un répertoire est sélectionné, avertir +quand on ouvre un fichier, et puis ensuite un projet qui a plusieurs fichiers, pdfshuffler n'est pas bien mis à jour +popumenu rotate : les valeurs de la fenêtre transformations ne sont pas mises à jour. + +bugs +fichier ini : ouvrir un fichier, ouvrir le fichier ini correspondant. Ne rien changer, fermer, +le fichier ini est mis à jour à un moment quelconque et souvent toutes les transformations sont remises à zéro. +Dans la même manipulation , quand on ouvre, il arrive que les modifications d'une page soient conservées +et pas celle de l'autre page (en cahier) + +slow mode : si la première feuille est faite entièrement de pages blanches générées (par exemple 2 pour + pages blanches au début dans une configuraiton à deux pages), + line 4285, in createNewPdf + pages[i - 1].append(dataz) + IndexError: list index out of range + +petits défauts + +quand on clique sur les boutons de global rotation, double update du preview (problème des boutons radio) + +améliorations +Le tooltip pour le nom de fichier pourrait afficher les valeurs réelles que donneront les différents paramètres + +""" + + + +""" + EXPLANATIONS OF THE WORKFLOW + + The structure of the program is easy to understand. + Everything runs around the "config" dictionary which defines how the source pdf files + must be placed in the output file. + The content of this dictionary may be viewd at any time by the command "Save project" + which builds an ini file from this dictionary. + + The program has two parts : + 1) The PdfRenderer class : It receives the config dictionary, + and from its content builds the output file, applying the necessary + transormations to the source pages. These transformations, in Pdf, + are always handled byt transformation matrices. See Pdf specifications + for details. + 2) The gui, whose only purpose is to build the config dictionary in an easy way. + + Workflow : + 1) Normal process is : - the gui creates and updates the config dict. + - When the Go button is pressed, the config dictionary + is sent to PdfRenderer which builds the output file + 2) Preview process : To create the preview, a similar process is used. + - the config dictionary is sent to PdfRenderer, with an + additional parameter which indicates a page number + - PdfRenderer creates a pdf file in memory which contains a single page. + - This page is displayed in the gui by Poppler. + + Inside config, the pages are named in two different ways : + - Absolute : 2:25 designates a single page, page 25 of the second file. + - Positional : 2,1 (line, column) designates any page which is placed + on line 2, column 1 + + What renders things complicated is that pdf counts pages from the bottom + left, starting by 0, which is not user friendly. So the program has to convert + data in a readable format. + + Another complication is that Pdf defines the center of rotation at the lower left corner, + which is not user friendly. The rotate function handles this and shifts the image to create + a centered rotation. + + Transformations + + 1) When the user clicks on the preview, the selectPage function is launched. + a) From the mouse coordinates, it determines the page clicked, and builds a page identifier + which is a list of six values : + - row and column (Pdf format) + - file number and page number + - row and column if the output page is rotated + then it updates the selected pages list. + b) it launchs area_expose to update the display + c) it extracts from config the transformations already defined for this page + and fills in the gtkEntry widgets which contains the transformations + + 2) When the user changes a value in these widgets, the transformationApply function is launched. + It reads the values in the gui and updates the config dictionary + Then it launchs the preview function which will update the preview + + + + HOWTO + + to add a parameter, three steps are necessary : + 1) add the code which will use the parameter + 2) add a control in Glade + 3) add a line in makeinifile to write the parameter in the project file + 4) add a line in setupGui to setup the gui from the ini file. + + +""" + +""" +Ubuntu 2016 - dépendences + +python3-gi +python3-gi-cairo +gir1.2-gtk-3.0 +gir1.2-poppler-0.18 + +Installation de pyinstaller + +sudo pip3 install pyinstaller +le paquet python-dev est aussi nécessaire (mais pip le trouve) + + +""" + +import time, math, string, os, sys, re, shutil, site +#print(sys.version) +sys.path.append(os.path.abspath(os.path.dirname(__file__))) + +try : + import configparser # Python 3 + from configparser import ConfigParser, RawConfigParser +except : + from ConfigParser import ConfigParser, RawConfigParser +import io +from collections import defaultdict, OrderedDict +import subprocess +from subprocess import Popen, PIPE +from ctypes import * +import threading +import tempfile, io +import copy +##import urllib +##from urllib.parse import urlparse +##from urllib.request import urljoin + + +from optparse import OptionParser +import traceback + + +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Poppler', '0.18') + +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Poppler +from gi.repository import Pango +from gi.repository import Gio, GLib +from gi.repository import cairo + + +Gtk.rc_parse("./gtkrc") + +from pdfbooklet.PyPDF2_G import PdfFileReader, PdfFileWriter +import pdfbooklet.PyPDF2_G.generic as generic + +# from pdfbooklet import * +from pdfbooklet.files_chooser import Chooser + +import locale #for multilanguage support +import gettext +import pdfbooklet.elib_intl3 as elib_intl3 +elib_intl3.install("pdfbooklet", "share/locale") + +debug_b = 0 + + + +def join_list(my_list, separator) : + mydata = "" + if isinstance(my_list, list) : + for s in my_list : + mydata += s + separator + elif isinstance(my_list, dict) : + for s in my_list : + try : + item1 = unicode(my_list[s], "utf-8") + except : + item1 = my_list[s] + mydata += item1 + separator + crop = len(separator) * -1 + mydata = mydata[0:crop] + return mydata + +def get_value(dictionary, key, default = 0) : + + if not key in dictionary : + dictionary[key] = default + return default + else : + return dictionary[key] + + +def unicode2(string, dummy = "") : + + if sys.version_info[0] == 2 : + if isinstance(string,unicode) : + return string + + try : + return unicode(string,"utf_8") + except : + try : +# print string, " est ecrit en cp1252" + return unicode(string,"cp1252") + except : + return string # Is this the good option ? Return False or an empty string ? + #return "inconnu" + +def printExcept() : + a,b,c = sys.exc_info() + for d in traceback.format_exception(a,b,c) : + print(d, end=' ') + +def bool_test(value) : + if isinstance(value, str) : + try : + value = int(value) + except : + if value.strip().lower() == "true" : + return True + else : + return False + + return bool(value) + +def alert(message, type = 0) : + + dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, + Gtk.ButtonsType.CLOSE , message) + dialog.run() + dialog.destroy() + +def showwarning(title, message) : + """ + GTK_MESSAGE_INFO, + GTK_MESSAGE_WARNING, + GTK_MESSAGE_QUESTION, + GTK_MESSAGE_ERROR, + GTK_MESSAGE_OTHER + """ + + resetTransform_b = False + + dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL , Gtk.MessageType.WARNING, + Gtk.ButtonsType.CLOSE , title) + + dialog.format_secondary_text(message) + if "transformWindow" in app.arw : + app.arw["transformWindow"].set_keep_above(False) + resetTransform_b = True + dialog.set_keep_above(True) + dialog.run() + dialog.destroy() + if resetTransform_b == True : + app.arw["transformWindow"].set_keep_above(True) + + +def askyesno(title, string) : + + + dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL , Gtk.MessageType.QUESTION, + Gtk.ButtonsType.NONE, title) + dialog.add_button(Gtk.STOCK_YES, True) + dialog.add_button(Gtk.STOCK_NO, False) + dialog.format_secondary_text(string) + dialog.set_keep_above(True) + rep = dialog.run() + dialog.destroy() + return rep + +def ask_text(parent, message, default=''): + """ + Display a dialog with a text entry. + Returns the text, or None if canceled. + """ + d = Gtk.MessageDialog(parent, + Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, + Gtk.MessageType.QUESTION, + Gtk.ButtonsType.OK_CANCEL, + message) + entry = Gtk.Entry() + entry.set_text(default) + entry.show() + d.vbox.pack_end(entry, True, True, 0) + entry.connect('activate', lambda _: d.response(Gtk.ResponseType.OK)) + d.set_default_response(Gtk.ResponseType.OK) + + r = d.run() + text = entry.get_text() + if sys.version_info[0] == 2 : + text = text.decode('utf8') + d.destroy() + if r == Gtk.ResponseType.OK: + return text + else: + return None + + + + +class myConfigParser() : + def __init__(self) : + pass + + def read(self, + iniFile_s, + encoding = "utf8", + comments = False) : + # ---- + # read ini file and creates a dictionary + # @param iniFile_s : ini file path + # @param comments : if True, comments are included in the config. Otherwise they are skipped + # @return : True if successful, False otherwise + + + myconfig = OrderedDict() + if not os.path.isfile(iniFile_s) : + return False + + try : # if pdfbooklet.cfg is invalid, don't block the program + if sys.version_info[0] == 3 : + fileIni = open(iniFile_s, "r", encoding = "utf8") + else : + fileIni = open(iniFile_s, "r") + # If BOM present, skip the first three bytes + isBOM_s = fileIni.read(3) + if isBOM_s == chr(239) + chr(187) + chr(191) : # There is a BOM, skips it + pass + else : + fileIni.seek(0) # No BOM, come back to beginning of file + + except : + myconfig = OrderedDict() + myconfig["mru"] = OrderedDict() + myconfig["mru2"] = OrderedDict() + myconfig["options"] = OrderedDict() + return myconfig + + section_s = "" + while True : + record_s = fileIni.readline() + if record_s == "" : # end of file + break + # format line : strip and replace possible \ by / + record_s = record_s.strip() + if sys.version_info[0] == 2 : + record_s = record_s.decode("utf8") + record_s = record_s.replace("\\", "/") # TODO : or better : formatPath() + # If the line is a section + if record_s[0:1] == "[" and record_s[-1:] == "]" : # section + section_s = record_s[1:-1] + myconfig[section_s] = OrderedDict() + else : + # Skip useless lines + if section_s == "" : # comment in the beginning of the file + continue + if len(record_s) == 0 : # empty line + continue + if record_s[0:1] == "#" : # comment + comment_b = True + else : + comment_b = False + if comments == False : # Skip comments + if comment_b == True : + continue + + + # otherwise, store data in section + # TODO : comments + record_data = record_s.split("=") + if len(record_data) > 1 : + key = record_data[0].strip() + linedata = record_data[1].strip() + if linedata == "False" : + linedata = False + if linedata == "True" : + linedata = True + myconfig[section_s][key] = linedata + return myconfig + + def write(self, myconfig, filename) : + if sys.version_info[0] == 3 : + iniFile = open(filename, "w", encoding = "utf8") + else : + iniFile = open(filename, "w") + for a in myconfig : + iniFile.write("[" + a + "]\n") + for b in myconfig[a] : + value = myconfig[a][b] + if value == True : + value = '1' + elif value == False : + value = '0' + data1 = (b + " = " + value + "\n").encode("utf8") # En python 3 cette ligne convertit en bytes !!! + data1 = (b + " = " + value + "\n") + iniFile.write(data1) + iniFile.write("\n") + iniFile.close() + return True + +class TxtOnly : + def __init__(self, + render, + pdfList = None, + pageSelection = None): + + global config, rows_i, columns_i, step_i, sections, output, input1, adobe_l, inputFiles_a, inputFile_a + global numfolio, prependPages, appendPages, ref_page, selection, PSSelection + global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l + global ouputFile, optionsDict, selectedIndex_a, selected_page, deletedIndex_a, app + global arw +## elib_intl.install("pdfbooklet", "share/locale") + + if None != pdfList : + inputFiles_a = pdfList + self.loadPdfFiles() + else : + inputFiles_a = {} + inputFile_a = {} + self.permissions_i = -1 # all permissions + self.password_s = "" + rows_i = 1 + columns_i = 2 + urx_i = 200 + ury_i = 200 + optionsDict = {} + adobe_l = 0.3527 + + self.radioSize = 1 + self.radioDisp = 1 + self.repeat = 0 + self.booklet = 1 + self.righttoleft = 0 + self.delete_rectangle = [] + + def openProject2(self, filename_u) : + # Called by OpenProject and OpenMru (in case the selected item was a project) + global config, openedProject_u, preview_b, project_b + + if os.path.isfile(filename_u): + openedProject_u = filename_u +## self.arw["window1"].set_title(u"Pdf-Booklet [ " + PB_version + " ] - " + filename_u) + preview_b = False + project_b = True + self.parseIniFile(filename_u) + preview_b = True + project_b = False + return True + + + def readNumEntry(self, entry, widget_s = "") : + + if isinstance(entry, int) : + return float(entry) + elif isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + value = value.replace(",", ".") + if value == "" : value = 0 + try : + value = float(value) + except : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + + return value + + + def readmmEntry(self, entry, widget_s = "", default = 0) : + global adobe_l + + value = "" + if isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + value = value.replace(",", ".") + if (value == "") : + value = default + else : + try : + value = float(value) / adobe_l + except : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + return value + + def readPercentEntry(self, entry, widget_s = "") : + + value = "" + if sys.version_info[0] == 2 and isinstance(entry, unicode) : + value = entry + elif isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + value = value.replace(",", ".") + if (value == "") : + value = 100 + else : + value.replace("%", "") + try : + value = float(value) / 100 + if value < 0 : + showwarning(_("Invalid data"), _("Invalid data for %s - must be > 0. Aborting \n") % widget_s) + return None + except : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + return value + + def readIntEntry(self, entry, widget_s = "", type_i = 0, default = 0) : + # type = 0 : accepts all values >= 0 + # type = 1 : accepts all values > 0 + # type = -1 : accepts any integer, positive or negative + # type = 2 : optional. Don't warn if missing, but warn if invalid (not integer) + + value = "" + if isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + value = value.replace(",", ".") + + + try : + value = int(value) + if type_i == 0 : + if value < 0 : + showwarning(_("Invalid data"), _("Invalid data for %s - must be >= 0. Aborting \n") % widget_s) + return None + elif type_i == 1 : + if value < 1 : + showwarning(_("Invalid data"), _("Invalid data for %s - must be > 0. Aborting \n") % widget_s) + return None + except : + if value == "" : + if type_i == 2 : + pass + else : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + else : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + if value == "" : + return default + elif value == 0 and default > 0 : + return default + return value + + def readBoolean(self, entry) : + + value = "" + + if isinstance(entry, str) : + if entry.strip().lower() == "true" : + value = True + else : + value = False + elif isinstance(entry, bool) : + return entry + else : + value = entry.get_text() + try : + if int(value) < 1 : + return False + else : + return True + except : + showwarning(_("Invalid data"), _("Invalid data for %s - must be 0 or 1. Aborting \n") % widget_s) + + + def parseIniFile(self, inifile = "") : + + global config, rows_i, columns_i, step_i, cells_i, input1, adobe_l + global numfolio, prependPages, appendPages, ref_page, selection + global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, inputFile, inputFiles_a + global startup_b + + + config = parser.read(inifile) + + + # migration to 2.4 format : copy xxxx1 values to xxxx and delete xxxx1 + for section in config : + for option in config[section] : + if option in["htranslate1", "vtranslate1", "scale1", "rotate1", "xscale1", "yscale1"] : # Clean no longer used data + if option[:-1] in config[section] : + config[section][option[:-1]] = config[section][option] + del config[section][option] + + + + # store in dictionary + self.pagesTr = config + if not "options" in config : + config["options"] = OrderedDict() + + for section in config : + if not section in self.pagesTr : + self.pagesTr[section] = OrderedDict() + + # inputs + + if startup_b == 0 : + if "options" in config: + if "inputs" in config["options"] : + + + temp1 = config["options"]["inputs"] + inputFiles_a = {} + for filename in temp1.split("|") : + if os.path.isfile(filename) : + pdfFile = PdfFileReader(open(filename, "rb")) + numpages = pdfFile.getNumPages() + path, shortFileName = os.path.split(filename) + i = len(inputFiles_a) + inputFiles_a[i + 1] = filename + + self.loadPdfFiles() + if "pageselection" in config["options"] : + self.selection_s = config["options"]["pageselection"] + + + + # variables + if "options" in config: + if "booklet" in config["options"] : + self.booklet = int(config["options"]["booklet"]) + + + # multi-line entries + + if "options" in config: + if "userLayout" in config["options"] : + layout_s = config["options"]["userLayout"] + config["options"]["userLayout"] = layout_s.replace("/", "\n") + (a,b,c,d) = self.parse_user_layout(layout_s) + self.imposition = a + + + + def setOption(self, option, default = "") : + if option in config["options"] : + result = config["options"][option] + else : + result = default + if isinstance(default, int) : + try : + return int(result) + except : + return default + def parse_user_layout(self, layout_s) : + + if layout_s.strip() == "" : + return ([], 0 , 0 , []) + + layout_s = layout_s.replace("/", "\n") + lines = layout_s.split("\n") + imposition = [] + lines2 = [] + for line in lines : + if line.strip() == "" : # correct errors : ignore blank lines + continue + if line[0:1] == "#" : # ignore comments + continue + if line[0:4] == "====" : # New sheet + imposition.append(lines2) + lines2 = [] + else : + lines2.append(line) + if len(lines2) > 0 : + imposition.append(lines2) + + numrows = len(lines2) + cols = lines2[0].split(",") + numcols = 0 + for a in cols : + if a.strip() != "" : + numcols += 1 + + imposition2 = [] + for lines2 in imposition : + pages = [] + for line in lines2 : + line = line.split(",") + for a in line : + if a.strip() != "" : # correct errors : ignore trailing comma + pages.append(a.strip()) + imposition2.append(pages) + self.imposition = imposition2 + + return (imposition2, numrows, numcols, pages) + + def loadPdfFiles(self) : + global inputFile_a, inputFiles_a, pagesIndex_a, refPageSize_a + + i = 1 + inputFile_a = {} + inputFile_details = {} + for key in inputFiles_a : + val = inputFiles_a[key] + if os.path.isfile(val) : + inputFile_a[val] = PdfFileReader(open(val, "rb")) + inputFile_details[val] = {} + if inputFile_a[val].getIsEncrypted() : + inputFile_details[val]["encrypt"] = True + if not hasattr(inputFile_a[val], "_decryption_key") : # if not already decrypted + password = get_text(None, _("Please, enter the password for this file")) + if password != None : + password = password.encode("utf8") + inputFile_a[val].decrypt(password) # Encrypted file + if key == 1 : # we get permissions and password from the first file + (a,b,self.permissions_i) = inputFile_a[val].getPermissions() + self.password_s = password + inputFile_details[val]["password"] = password + selectedIndex_a = {} + deletedIndex_a = {} + + i += 1 + + + + def output_page_size(self, radiosize, ref_file = 1, ref_page = 0, logdata = 1) : + + + global config, rows_i, columns_i, sections, output, adobe_l, inputFiles_a, inputFile_a + global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l, outputScale, refPageSize_a + + # Ouput page size + if ref_file in inputFiles_a : + fileName = inputFiles_a[ref_file] + fileName = unicode2(fileName) + try : + page0 = inputFile_a[fileName].getPage(ref_page) + except : + print(_("The reference page is invalid. We use the first page")) + page0 = inputFile_a[fileName].getPage(0) + + llx_i=page0.mediaBox.getLowerLeft_x() + lly_i=page0.mediaBox.getLowerLeft_y() + urx_i=page0.mediaBox.getUpperRight_x() + ury_i=page0.mediaBox.getUpperRight_y() + + urx_i=float(urx_i) - float(llx_i) + ury_i=float(ury_i) - float(lly_i) + + refPageSize_a = [urx_i, ury_i] + else : + alert(_("Reference page invalid, there is no file n°" + str(ref_file))) + return False + + + #££self.print2 (_("Size of source file = %s mm x %s mm ") % (int(urx_i * adobe_l), int(ury_i * adobe_l)), 1) + + oWidth_i = urx_i * columns_i + oHeight_i = ury_i * rows_i + + if radiosize == 1 : + mediabox_l = [oWidth_i, oHeight_i] + elif radiosize == 2 : # size = no change + if oWidth_i < oHeight_i : # set orientation + mediabox_l = [urx_i, ury_i] + else : + mediabox_l = [ury_i, urx_i] + + # calculate the scale factor + deltaW = mediabox_l[0] / oWidth_i + deltaH = mediabox_l[1] / oHeight_i + if deltaW < deltaH : + outputScale = deltaW + else : + outputScale = deltaH + + + elif radiosize == 3 : # user defined + + customX = self.readNumEntry(app.arw["outputWidth"], _("Width")) + if customX == None : return False + customY = self.readNumEntry(app.arw["outputHeight"], _("Height")) + if customY == None : return False + + + mediabox_l = [ customX * (1 / adobe_l), customY * (1 / adobe_l)] + + + # calculate the scale factor + deltaW = mediabox_l[0] / oWidth_i + deltaH = mediabox_l[1] / oHeight_i + if deltaW < deltaH : + outputScale = deltaW + else : + outputScale = deltaH + + + outputUrx_i = mediabox_l[0] + outputUry_i = mediabox_l[1] + + app.arw["info_fichier_sortie"].set_text(_("%s mm x %s mm ") % (int(outputUrx_i * adobe_l), int(outputUry_i * adobe_l))) + + + +class dummy: + def __init__(self) : + + self.pagesTr = {} + self.arw = {} + + +class gtkGui: + # parameters : + # render is an instance of pdfRenderer + # pdfList is a dictionary of path of pdf files : { 1:"...", 2:"...", ... } + # pageSelection is a list of pages in the form : ["w:x", ... , "y:z"] + def __init__(self, + render, + pdfList = None, + pageSelection = None): + + global config, rows_i, columns_i, step_i, sections, output, input1, adobe_l, inputFiles_a, inputFile_a + global numfolio, prependPages, appendPages, ref_page, selection, PSSelection + global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l + global ouputFile, optionsDict, selectedIndex_a, selected_page, selected_pages_a, selectedeletedIndex_a, app + + elib_intl3.install("pdfbooklet", "share/locale") + + if None != pdfList : + inputFiles_a = pdfList + ini.loadPdfFiles() + else : + inputFiles_a = {} + inputFile_a = {} + self.permissions_i = -1 # all permissions + self.password_s = "" + rows_i = 1 + columns_i = 2 + step_i = 1 + urx_i = 200 + ury_i = 200 + optionsDict = {} + adobe_l = 0.3527 + + + areaAllocationH_i = 400 + areaAllocationW_i = 400 + self.freeze_b = False + self.preview_scale = 1 + self.dev1 = "" # for development needs + + selectedIndex_a = {} + selected_page = None + selected_pages_a = [] + deletedIndex_a = {} + + + app = self + self.render = render + self.ar_pages = [] + self.ar_layout = [] + self.previewPage = 0 + self.clipboard= {} + self.shuffler = None + self.imposition = [] + + self.initdrag = [] + self.enddrag = [] + + self.backup = [] + self.backup_index = 0 + self.backup_command = True + + + self.widgets = Gtk.Builder() + #self.widgets.set_translation_domain('pdfbooklet') + self.widgets.add_from_file(sfp2('data/pdfbooklet3.glade')) + arWidgets = self.widgets.get_objects() + self.arw = {} + for z in arWidgets : + try : + name = Gtk.Buildable.get_name(z) + self.arw[name]= z + z.set_name(name) + except : + pass + + + #autoconnect signals for self functions + self.widgets.connect_signals(self) + self.arw["drawingarea1"].connect('draw', self.OnDraw) + + self.autoscale = self.arw["autoscale"] + self.area = self.arw["drawingarea1"] + self.settings = self.arw["settings"] + self.overwrite = self.arw["overwrite"] + self.noCompress = self.arw["noCompress"] + self.slowmode = self.arw["slowMode"] + self.righttoleft = self.arw["righttoleft"] + self.status = self.arw["status"] + + + self.window1 = self.arw["window1"] + self.window1.show_all() + self.window1.set_title("Pdf-Booklet [ " + PB_version + " ]") +## self.window1.connect("destroy", lambda w: Gtk.main_quit()) + self.window1.connect("destroy", self.close_application) + + """ + To change the cursor : + watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) + self.window1.get_window().set_cursor(watch_cursor) + +## display = self.window1.get_display() +## watch_cursor = Gdk.Cursor.new_from_name(display, "default") + + watch_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) + watch_cursor = Gdk.Cursor(Gdk.CursorType.CROSS) + self.window1.get_window().set_cursor(watch_cursor) + """ + + self.mru_items = {} + self.menuAdd() + + self.selection_s = "" + + # Global transformations + self.Vtranslate1 = self.arw["vtranslate1"] + self.scale1 = self.arw["scale1"] + self.rotation1 = self.arw["rotation1"] + self.thispage = self.arw["thispage"] + self.evenpages = self.arw["evenpages"] + self.oddpages = self.arw["oddpages"] + + + + self.area.show() + self.pagesTr = {} + +# ############ Setup drag motion for drawingarea1 ############## + + + # setup drag +## targets = Gtk.TargetList.new([]) +## targets.add_text_targets(0) +## +## self.area.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], +## Gdk.DragAction.COPY) +## self.area.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) +## self.area.drag_source_set_target_list(targets) +## self.area.drag_dest_set_target_list(targets) + + # self.area is connected to drag_motion in glade + + + + +# ##################### Themes ################################# + + + menu_themes = Gtk.Menu() + mem_menu_name = "" + # Get the list of gtkrc-xxx files in "data", extract the name, and add items to menu + themes_dict = {} + + themes_dir = os.path.join(prog_path_u, "share/themes") + if os.path.isdir(themes_dir) : + + + for a in os.listdir(themes_dir) : # TODO ££ + rcpath = os.path.join(prog_path_u,a) + themes_dict[a] = rcpath + themes_list = themes_dict.keys() + ## themes_list.sort() + + # Extract similar short names to build submenus + mem_short_name = "" + submenus = [] + for menu_name in themes_list : + short_name = menu_name.split("-")[0] + if short_name == mem_short_name : + if short_name not in submenus : + submenus.append(short_name) + mem_short_name = short_name + + # Build first level menu + sub_dict = {} + to_del = [] + keys = themes_dict.keys() + ## keys.sort() + for menu_name in submenus : + if len(menu_name.strip()) == 0 : + continue + sub_dict[menu_name] = Gtk.MenuItem(menu_name) + menu_themes.append(sub_dict[menu_name]) + sub_dict[menu_name].show() + + + submenu = Gtk.Menu() + submenu.show() + for key in keys : + rcpath = themes_dict[key] + short_name = key.split("-")[0] + if short_name == menu_name : + commandes = Gtk.MenuItem(key) + submenu.append(commandes) + commandes.connect("activate", self.change_theme, rcpath, key) + commandes.show() + to_del.append(key) + + sub_dict[menu_name].set_submenu(submenu) + + # delete used keys and add the remaining to main menu + + for key in to_del : + del themes_dict[key] + keys = themes_dict.keys() + ## keys.sort() + + for menu_name in keys : + if len(menu_name.strip()) == 0 : + continue + rcpath = themes_dict[menu_name] + commandes = Gtk.MenuItem(menu_name) + menu_themes.append(commandes) + commandes.connect("activate", self.change_theme, rcpath, menu_name) + commandes.show() + + self.arw["themes"].set_submenu(menu_themes) + aaa = 1 + + + def change_theme(self, widget, path, theme) : + + try: + settings_location = os.path.join(site.getsitepackages()[1], "gnome/etc/gtk-3.0/settings.ini") + except : + settings_location = os.path.join(prog_path_u, "etc/gtk-3.0/settings.ini") + f1 = open(settings_location, "w") + f1.write("[Settings]\n") + f1.write("gtk-theme-name = " + theme) + f1.close() + alert(_("You must restart the program to apply the new theme.")) + + + + + + + # this small function returns the type of a widget + def widget_type(self, widget) : + try : + z = widget.class_path() + z2 = z.split(".")[-1] + return z2 + except: + return False + + + def gtk_delete(self, source=None, event=None): + Gtk.main_quit() + + def close_application(self, widget, event=None, mydata=None): + """Termination""" + if self.shuffler != None : + self.shuffler.close_application("") + self.shuffler = None + + if Gtk.main_level(): + self.arw["window1"].destroy() + Gtk.main_quit() + Gdk.threads_leave() + + #os._exit(0) + return False + + def file_manager(self,widget): + global inputFiles_a + mrudir = self.read_mru2() + if mrudir == "" : + mrudir = prog_path_u + self.chooser = Chooser(inputFiles_a, share_path_u, mrudir) + inputFiles_a = self.chooser.inputFiles_a + if len(inputFiles_a) == 0 : + return + # add file(s) to most recently used + self.mru(inputFiles_a) + self.chooser.chooser.destroy() + self.chooser = None + if self.shuffler: + self.shuffler.model.clear() + self.shuffler.pdfqueue = [] + self.shuffler.nfile = 0 + for key in inputFiles_a : + self.shuffler.add_pdf_pages(inputFiles_a[key]) + # TODO : N'est à faire que si la liste des fichiers a changé + self.shuffler.rendering_thread.pdfqueue = self.shuffler.pdfqueue + + ini.loadPdfFiles() + app.selection_s = "" + + self.previewUpdate() + + + def FormatPath ( + self, + path, + typePath = 0) : + # Replaces // and \\ by /, but preserves the initial // necessary in urls on a network + + if path[0:2] == "//" or path[0:2] == ["\\"] : + prefix_s = "//" + path = path[2:] + else : + prefix_s = "" + + if typePath == 1 : + path = path.replace(":", "") + prefix_s = "" + path = path.replace("\\", "/") + path = path.replace("//", "/") + return(prefix_s + path) + + + + + def openProject(self, widget, name = "") : + global config, openedProject_u, preview_b, project_b + + + + old_dir = self.read_mru2() + + gtk_chooser = Gtk.FileChooserDialog(title=_('Import...'), + action=Gtk.FileChooserAction.OPEN, + buttons=(Gtk.STOCK_CANCEL, + Gtk.ResponseType.CANCEL, + Gtk.STOCK_OPEN, + Gtk.ResponseType.OK)) + gtk_chooser.set_current_folder(old_dir) + gtk_chooser.set_select_multiple(False) + + filter_all = Gtk.FileFilter() + filter_all.set_name(_('All files')) + filter_all.add_pattern('*') + gtk_chooser.add_filter(filter_all) + + filter_ini = Gtk.FileFilter() + filter_ini.set_name(_('INI files')) + filter_ini.add_pattern('*.ini') + gtk_chooser.add_filter(filter_ini) + gtk_chooser.set_filter(filter_ini) + + response = gtk_chooser.run() + if response == Gtk.ResponseType.OK: + filename = gtk_chooser.get_filename() + filename_u = unicode2(filename, "utf-8") + self.mru(filename) + ini.openProject2(filename_u) + ini.loadPdfFiles() + self.setupGui() + self.arw["previewEntry"].set_text("1") + self.previewUpdate() + self.write_mru2(filename_u) # write the location of the opened directory in the cfg file + + + +## elif response == Gtk.RESPONSE_CANCEL: +## print(_('Closed, no files selected')) + gtk_chooser.destroy() + + def openMru(self, widget) : + global config, openedProject_u, preview_b, project_b + global inputFiles_a + + widget_name = widget.get_name() + filenames_list_s = self.mru_items[widget_name][1] + + # are we opening a project file ? + filename_u = unicode2(filenames_list_s[0], "utf-8") + extension_s = os.path.splitext(filename_u)[1] + if extension_s == ".ini" : + ini.openProject2(filename_u) + self.selection_s = config["options"]["pageSelection"] + ini.loadPdfFiles() + self.setupGui() + self.arw["previewEntry"].set_text("1") + self.previewUpdate() + return + else : + ini.parseIniFile(sfp3("pdfbooklet.cfg")) # reset transformations + self.setupGui(sfp3("pdfbooklet.cfg")) + + inputFiles_a = {} + + for filename_s in filenames_list_s : + filename_u = unicode2(filename_s, "utf-8") + extension_s = os.path.splitext(filename_u)[1] + i = len(inputFiles_a) + inputFiles_a[i + 1] = filename_u + + + ini.loadPdfFiles() + app.selection_s = "" + self.previewUpdate() + + + if self.shuffler: + self.shuffler.model.clear() + self.shuffler.pdfqueue = [] + self.shuffler.nfile = 0 + self.shuffler.npage = 0 + for key in inputFiles_a : + self.shuffler.add_pdf_pages(inputFiles_a[key]) + # TODO : N'est à faire que si la liste des fichiers a changé + self.shuffler.rendering_thread.pdfqueue = self.shuffler.pdfqueue + #for row in self.shuffler.model: + # row[6] = False + + + + + + + def saveProject(self, widget) : + global openedProject_u + + if openedProject_u : + self.saveProjectAs("", openedProject_u) + else : + self.saveProjectAs("") + + + def saveProjectAs(self, widget, filename_u = "") : + global config, openedProject_u + + if filename_u == "" : + + old_dir = self.read_mru2() + + gtk_chooser = Gtk.FileChooserDialog(title=_('Save project...'), + action=Gtk.FileChooserAction.SAVE, + buttons=(Gtk.STOCK_CANCEL, + Gtk.ResponseType.CANCEL, + Gtk.STOCK_SAVE, + Gtk.ResponseType.ACCEPT)) + gtk_chooser.set_do_overwrite_confirmation(True) + gtk_chooser.set_current_folder(old_dir) + gtk_chooser.set_current_name("untitled document") + # or chooser.set_filename("untitled document") + + response = gtk_chooser.run() + + if response == Gtk.ResponseType.CANCEL: +## print(_('Closed, no files selected')) + gtk_chooser.destroy() + return + + elif response == Gtk.ResponseType.ACCEPT: + filename = gtk_chooser.get_filename() + filename_u = unicode2(filename, "utf-8") + if filename_u[-4:] != ".ini" : + filename_u += ".ini" + gtk_chooser.destroy() + self.mru(filename_u) + + + + openedProject_u = filename_u + + for section in self.pagesTr : + if not section in config : + config[section] = self.pagesTr[section] + + + + # update with last selections in the gui + # perhaps we could also update first pagesTr + self.makeIniFile() +## for section in out_a : +## config[section] = out_a[section] + + + #config.write(iniFile) + self.write_ordered_config(filename_u) + self.write_mru2(filename_u) # write the location of the opened directory in the cfg file + + def write_ordered_config(self, filename_u) : + global config + + if sys.version_info[0] == 3 : + iniFile = open(filename_u, "w", encoding = "utf8") + else : + iniFile = open(filename_u, "w") + + # store data in an ordered dictionary + out_a = OrderedDict() + sections_list = list(config.keys()) + for section1 in["options", "mru", "mru2", "output"] : + if section1 in config : + out_a[section1] = OrderedDict() + for option in config[section1] : + value = config[section1][option] + if value == 'False' : + value = False + elif value == 'True' : + value = True + + out_a[section1][option] = value + sections_list.remove(section1) + + sections_list.sort() + for section1 in sections_list : + out_a[section1] = OrderedDict() + for option in config[section1] : + value = config[section1][option] + if value == 'False' : + value = False + elif value == 'True' : + value = True + out_a[section1][option] = value + + # write data + for section2 in out_a : + iniFile.write("[" + section2 + "]\n") + for option2 in out_a[section2] : + iniFile.write(option2 + " = " + str(out_a[section2][option2]) + "\n") + + iniFile.close() + + + + + def mru(self, filenames_a) : + + mrudir = "" + if isinstance(filenames_a, dict) : + filenames = join_list(filenames_a, "|") + if 1 in filenames_a : + mrudir = os.path.split(filenames_a[1])[0] + else : + filenames = filenames_a + mrudir = os.path.split(filenames_a)[0] + + + configtemp = parser.read(sfp3("pdfbooklet.cfg")) + +#### if configtemp.has_section(section) == False : +#### configtemp.add_section(section) +#### configtemp.set(section,option,value) +## +## + if not "mru" in configtemp : + configtemp["mru"] = {} + if not "mru2" in configtemp : + configtemp["mru2"] = {} + + # cancel if already present + temp_a = [] + for index in ["mru1", "mru2", "mru3", "mru4"] : + if index in configtemp["mru"] : + if filenames == configtemp["mru"][index] : + return + + + # shift mru + if "mru3" in configtemp["mru"] : + configtemp["mru"]["mru4"] = configtemp["mru"]["mru3"] + if "mru2" in configtemp["mru"] : + configtemp["mru"]["mru3"] = configtemp["mru"]["mru2"] + if "mru1" in configtemp["mru"] : + configtemp["mru"]["mru2"] = configtemp["mru"]["mru1"] + # set the new value + configtemp["mru"]["mru1"] = filenames + configtemp["mru2"]["mru1"] = mrudir +## f = open(sfp3("pdfbooklet.cfg"),"w") +## configtemp.write(f) +## f.close() + parser.write(configtemp, sfp3("pdfbooklet.cfg")) + configtemp = None + self.menuAdd() + + + def mru_python2(self, filenames_a) : + + mrudir = "" + if isinstance(filenames_a, dict) : # several files selected + filenames = join_list(filenames_a, "|") + if 1 in filenames_a : # At least one file + mrudir = os.path.split(filenames_a[1])[0] + else : + filenames = filenames_a + mrudir = os.path.split(filenames_a)[0] + + #filenames = filenames.encode('utf-8') + + configtemp = parser.read(sfp3("pdfbooklet.cfg")) + + if configtemp.has_section("mru") == False : + configtemp.add_section("mru") + if configtemp.has_section("mru2") == False : + configtemp.add_section("mru2") + + # cancel if already present + temp_a = [] + for index in ["mru1", "mru2", "mru3", "mru4"] : + if configtemp.has_option("mru",index) : + if filenames == configtemp.get("mru",index) : + return + + try : + # shift mru + if configtemp.has_option("mru","mru3") : + configtemp.set("mru","mru4",configtemp.get("mru","mru3")) + if configtemp.has_option("mru","mru2") : + configtemp.set("mru","mru3",configtemp.get("mru","mru2")) + if configtemp.has_option("mru","mru1") : + configtemp.set("mru","mru2",configtemp.get("mru","mru1")) + # set the new value +## filenames_s = filenames.encode("utf-8") +## mrudir_s = mrudir.encode("utf-8") + + configtemp.set("mru","mru1",filenames) + configtemp.set("mru2","mru2",mrudir) + f = open(sfp3("pdfbooklet.cfg"),"w", encoding = "utf8") + configtemp.write(f) + f.close() + configtemp = None + self.menuAdd() + except : + printExcept() + alert("problem in mru (line 700)") + + + + def read_mru2(self) : + + if os.path.isfile(sfp3("pdfbooklet.cfg")) : + configtemp = parser.read(sfp3("pdfbooklet.cfg")) + + mru_dir = "" + if "mru2" in configtemp : + if "mru2" in configtemp["mru2"] : + mru_dir = configtemp["mru2"]["mru2"] + + configtemp = None + return mru_dir + + def read_mru2_python2(self) : + + if os.path.isfile(sfp3("pdfbooklet.cfg")) : + configtemp = parser.read(sfp3("pdfbooklet.cfg")) + + try : + mru_dir = configtemp.get("mru2","mru2") + except : + mru_dir = "" + + configtemp = None + return mru_dir + + def write_mru2(self, filename_u) : + + if os.path.isfile(sfp3("pdfbooklet.cfg")) : + configtemp = parser.read(sfp3("pdfbooklet.cfg")) + + if not "mru2" in configtemp : + configtemp["mru2"] = OrderedDict() + + (path_u, file_u) = os.path.split(filename_u) + configtemp["mru2"]["mru2"] = path_u + parser.write(configtemp, sfp3("pdfbooklet.cfg")) + + + def menuAdd(self) : + # Called by function mru, adds an entry to the menu + + + configtemp = parser.read(sfp3("pdfbooklet.cfg")) + if configtemp == False : + return + + if "mru" in configtemp : + for item in ["mru1", "mru2", "mru3", "mru4"] : + if item in configtemp["mru"] : + filepath_list_s = configtemp["mru"][item] + filepath_list = filepath_list_s.split("|") + temp1 = [] + for filepath_s in filepath_list : + filepath_s = self.FormatPath(filepath_s) + path_s,filename_s = os.path.split(filepath_s) + temp1 += [filename_s] + menu_entry_s = join_list(temp1, ", ") + if len(menu_entry_s) > 40 : + menu_entry_s = menu_entry_s[0:40] + "..." + + self.mru_items[item] = [menu_entry_s, filepath_list] # contains real path, used to open files + self.arw[item].set_label(menu_entry_s) # displayed menu text + + + + + + def pdfBooklet_doc(self, widget) : + + userGuide_s = "documentation/" + _("Pdf-Booklet_User's_Guide.pdf") + if 'linux' in sys.platform : + subprocess.call(["xdg-open", userGuide_s]) + else: + os.startfile(sfp(userGuide_s)) + + + def popup_rotate(self, widget): + # Called by popup menu (right clic on preview) + global selected_page, rows_i + + + # We get the value from the widget name (the 3 menu options use the same function) + wname = widget.get_name() + match = re.match("rotate(\d*)", wname) + value = match.group(1) + + # Code below is just an adaptation of function "transormationsApply" + if selected_page == None : + showwarning(_("No selection"), _("There is no selected page. \nPlease select a page first. ")) + return + # selected_page 4 and 5 contain the correct page reference, including the global rotation. + humanReadableRow_i = rows_i - selected_page[4] + Id = str(str(humanReadableRow_i) + "," + str(selected_page[5] + 1)) + + pageId = str(selected_page[2]) + ":" + str(selected_page[3]) + +## # if transformation is for this page only, use page ref instead of position ref +## if self.thispage.get_active() == 1 : +## Id = pageId + + if (Id in config) == False : + config[Id] = {} + config[Id]["htranslate"] = '0' + config[Id]["vtranslate"] = '0' + config[Id]["scale"] = '100' + config[Id]["xscale"] = '100' + config[Id]["yscale"] = '100' + config[Id]["vflip"] = False + config[Id]["hflip"] = False + + + + + + config[Id]["rotate"] = str(value) + + self.preview(self.previewPage, 0) + + + + def __________________INI_FILE() : + pass + + + def saveDefaults(self, dummy) : + + out_a = self.makeIniFile() + iniFile = open(sfp3("pdfbooklet.cfg"), "w") + for a in out_a : + if not a in ["mru", "mru2", "options"] : + continue + iniFile.write("[" + a + "]\n") + for b in out_a[a] : + value = out_a[a][b] + if value == True : + value = '1' + elif value == False : + value = '0' + iniFile.write(b + " = " + value + "\n") + iniFile.write("\n") + iniFile.close() + + def readNumEntry(self, entry, widget_s = "") : + + if sys.version_info[0] == 2 and isinstance(entry, unicode) : + value = entry + elif isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + value = value.replace(",", ".") + if value == "" : value = 0 + try : + value = float(value) + except : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + + return value + + + def readmmEntry(self, entry, widget_s = "", default = 0) : + global adobe_l + + value = "" + if sys.version_info[0] == 2 and isinstance(entry, unicode) : + value = entry + elif isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + value = value.replace(",", ".") + if (value == "") : + value = default + else : + try : + value = float(value) / adobe_l + except : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + return value + + def readPercentEntry(self, entry, widget_s = "") : + + value = "" + if sys.version_info[0] == 2 and isinstance(entry, unicode) : + value = entry + elif isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + value = value.replace(",", ".") + if (value == "") : + value = 100 + else : + value.replace("%", "") + try : + value = float(value) / 100 + if value < 0 : + showwarning(_("Invalid data"), _("Invalid data for %s - must be > 0. Aborting \n") % widget_s) + return None + except : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + return value + + def readIntEntry(self, entry, widget_s = "", type_i = 0, default = "") : + # type = 0 : accepts all values >= 0 + # type = 1 : accepts all values > 0 + # type = -1 : accepts any integer, positive or negative + # type = 2 : optional. Don't warn if missing, but warn if invalid (not integer) + + value = "" + if isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + value = value.replace(",", ".") + + + try : + value = int(value) + if type_i == 0 : + if value < 0 : + showwarning(_("Invalid data"), _("Invalid data for %s - must be >= 0. Aborting \n") % widget_s) + return None + elif type_i == 1 : + if value < 1 : + showwarning(_("Invalid data"), _("Invalid data for %s - must be > 0. Aborting \n") % widget_s) + return None + except : + if value == "" : + if type_i == 2 : + pass + else : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + else : + showwarning(_("Invalid data"), _("Invalid data for %s - must be numeric. Aborting \n") % widget_s) + return None + if value == "" : + return default + return value + + def readBoolean(self, entry) : + + value = "" + if sys.version_info[0] == 2 : + if isinstance(entry, unicode) : + value = entry + elif isinstance(entry, str) : + value = entry + else : + value = entry.get_text() + try : + if int(value) < 1 : + return False + else : + return True + except : + showwarning(_("Invalid data"), _("Invalid data for %s - must be 0 or 1. Aborting \n") % widget_s) + + + + + + + def readGui(self, logdata = 1) : + + global config, rows_i, columns_i, step_i, sections, output, input1, input2, adobe_l, inputFiles_a, inputFile_a + global numfolio, prependPages, appendPages, ref_page, selection + global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l, outputScale, refPageSize_a + + if self.freeze_b == True : + return + + # Read the gui and update the config dictionary + self.makeIniFile() + + outputFile = config["options"]["output"] + outputScale = 1 + + rows_i = self.readIntEntry(self.arw["entry11"], _("rows"), 1) + #if rows_i == None : return False + columns_i = self.readIntEntry(self.arw["entry12"], _("columns"), 1) + #if columns_i == None : return False + if (rows_i < 1) : + rows_i = 1 + if (columns_i < 1) : + columns_i = 1 + + + if ini.repeat == 1 : + step_i = self.readIntEntry(self.arw["entry15"], _("step"), 1) + if step_i == None : return False + if (step_i < 1) : + step_i = 1 + else : + step_i = rows_i * columns_i + + + + numfolio = self.readIntEntry(self.arw["entry13"], _("folios")) + prependPages = self.readIntEntry(self.arw["entry32"], _("Leading blank pages")) + if prependPages == None : return False + appendPages = self.readIntEntry(self.arw["entry33"], _("Trailing blank pages")) + if appendPages == None : return False + selection = self.selection_s + + + if self.arw["radiosize1"].get_active() == 1 : + radiosize = 1 + elif self.arw["radiosize2"].get_active() == 1 : + radiosize = 2 + if self.arw["radiosize3"].get_active() == 1 : + radiosize = 3 + + referencePage = config["options"]["referencePage"] + if referencePage.strip() == "" or referencePage == "0" : + referencePage = "1" + temp1 = referencePage.split(":") + try : + if len(temp1) == 2 : + ref_file = int(temp1[0]) + ref_page = int(temp1[1]) + else : + ref_file = 1 + ref_page = int(temp1[0]) + except : + alert(_("Invalid value for reference page, please correct and try again")) + return False + if ref_page > 0 : + system_ref_page = ref_page - 1 # system numbering start from 0 and not 1 + ini.output_page_size(radiosize, ref_file, system_ref_page, logdata) + + return True + + + + def setOption(self, option, widget, section = "options") : + global config + + if section in config : + if option in config[section] : + value = config[section][option] + z = widget.class_path()[1] + z2 = z.split(".") + z3 = z2[-1] + if z3 == "GtkSpinButton" : + mydata = value + mydata = mydata.replace(",",".") + if mydata.strip() != "" : + widget.set_value(float(mydata)) + elif z3 == "GtkTextView" : + widget.get_buffer().set_text(value) + elif z3 == "GtkCheckButton" : + try : + value = int(value) + except : + pass + if bool_test(value) == True : + widget.set_active(True) + + else : + widget.set_text(value) + + + + + def setupGui(self, inifile = "") : + + global config, rows_i, columns_i, step_i, cells_i, input1, adobe_l + global numfolio, prependPages, appendPages, ref_page, selection + global numPages, pagesSel, llx_i, lly_i, urx_i, ury_i, inputFile, inputFiles_a + global startup_b + + + self.freeze_b = True # prevent update of the display, which would trigger readGui and corrupt the data + + # set radio buttons + + if "presets" in config["options"] : + temp1 = config["options"]["presets"] + self.arw[temp1].set_active(True) + + if "size" in config["options"] : + temp1 = config["options"]["size"] + self.arw[temp1].set_active(True) + + if "presetOrientation" in config["options"] : + temp1 = config["options"]["presetOrientation"] + self.arw[temp1].set_active(True) + + if "globalRotation" in config["options"] : + temp1 = config["options"]["globalRotation"] + self.arw[temp1].set_active(True) + + + self.setOption("rows", self.arw["entry11"]) + self.setOption("columns", self.arw["entry12"]) + self.setOption("step", self.arw["entry15"]) + self.setOption("numfolio", self.arw["entry13"]) + self.setOption("prependPages", self.arw["entry32"]) + self.setOption("appendPages", self.arw["entry33"]) + self.setOption("referencePage", self.arw["entry31"]) + self.setOption("creep", self.arw["creep"]) + self.setOption("output", self.arw["entry2"]) + self.setOption("width", self.arw["outputWidth"]) + self.setOption("height", self.arw["outputHeight"]) + self.setOption("userLayout", self.arw["user_layout"]) + + self.setOption("htranslate", self.arw["htranslate2"], "output") + self.setOption("vtranslate", self.arw["vtranslate2"], "output") + self.setOption("scale", self.arw["scale2"], "output") + self.setOption("rotate", self.arw["rotation2"], "output") + self.setOption("xscale", self.arw["xscale2"], "output") + self.setOption("yscale", self.arw["yscale2"], "output") + self.setOption("vflip", self.arw["vflip2"], "output") + self.setOption("hflip", self.arw["hflip2"], "output") + + self.setOption("font_size", self.arw["numbers_font_size"], "page_numbers") + self.setOption("start_from", self.arw["numbers_start_from"], "page_numbers") + self.setOption("bottom_margin", self.arw["numbers_bottom_margin"], "page_numbers") + + + # set check boxes + + if "advanced" in config["options"] : + if config.getint("options", "advanced") == 1 : self.advanced.set_active(1) + else : self.advanced.set_active(0) + self.guiAdvanced() + if "autoscale" in config["options"] : + if bool_test(config["options"]["autoscale"]) == True : + self.autoscale.set_active(1) + else : + self.autoscale.set_active(0) + if "autoRotate" in config["options"] : + if int(config["options"]["autoRotate"]) == 1 : self.autorotate.set_active(1) + else : self.autorotate.set_active(0) + + if "showPdf" in config["options"] : + if bool_test(config["options"]["showPdf"]) == True : self.arw["show"].set_active(1) + else : self.arw["show"].set_active(0) + if "saveSettings" in config["options"] : + if bool_test(config["options"]["saveSettings"]) == True : self.settings.set_active(1) + else : self.settings.set_active(0) + + if "noCompress" in config["options"] : + if bool_test(config["options"]["noCompress"]) == True : self.noCompress.set_active(1) + else : self.noCompress.set_active(0) + if "overwrite" in config["options"] : + if bool_test(config["options"]["overwrite"]) == True : self.overwrite.set_active(1) + else : self.overwrite.set_active(0) + + if "righttoleft" in config["options"] : + if bool_test(config["options"]["righttoleft"]) == True : self.righttoleft.set_active(1) + else : self.righttoleft.set_active(0) + if "slowmode" in config["options"] : + if bool_test(config["options"]["slowmode"]) == True : self.slowmode.set_active(1) + else : self.slowmode.set_active(0) + + if "page_numbers" in config : + if "page_numbers" in config["page_numbers"] : + if bool_test(config["page_numbers"]["page_numbers"]) == True : + self.arw["page_numbers"].set_active(1) + else : + self.arw["page_numbers"].set_active(0) + + # set TextView + + buf = self.arw["textview1"].get_buffer() + if "userLayout" in config["options"] : + buf.set_text(config["options"]["userLayout"]) + + + self.freeze_b = False + + + def makeIniFile(self, inifile = "") : + # Reads the gui and updates config dictionary + + global config, rows_i, columns_i, step_i, cells_i, input1, adobe_l + global numfolio, prependPages, appendPages, ref_page, selection + global numPages, pagesSel, inputFile + global out_a + + out_a = config + + config["options"]["rows"] = self.arw["entry11"].get_text() + config["options"]["columns"] = self.arw["entry12"].get_text() + config["options"]["booklet"] = str(ini.booklet) + config["options"]["step"] = self.arw["entry15"].get_text() + config["options"]["numfolio"] = self.arw["entry13"].get_text() + config["options"]["prependPages"] = self.arw["entry32"].get_text() + config["options"]["appendPages"] = self.arw["entry33"].get_text() + config["options"]["referencePage"] = self.arw["entry31"].get_text() + config["options"]["creep"] = self.arw["creep"].get_text() + config["options"]["pageSelection"] = self.selection_s # TODO : is this a good idea ? + buf = self.arw["user_layout"].get_buffer() + start, end = buf.get_bounds() + layout_s = buf.get_text(start, end, True) + config["options"]["userLayout"] = layout_s.replace("\n", "/") + + temp1 = "" + for key in inputFiles_a : + val = inputFiles_a[key] + temp1 += val + '|' + + config["options"]["inputs"] = temp1 + config["options"]["output"] = self.arw["entry2"].get_text() + config["options"]["repeat"] = str(self.arw["entry15"].get_text()) + config["options"]["showPdf"] = str(self.arw["show"].get_active()) + config["options"]["saveSettings"] = str(self.settings.get_active()) + config["options"]["autoscale"] = str(self.autoscale.get_active()) + config["options"]["width"] = str(self.arw["outputWidth"].get_text()) + config["options"]["height"] = str(self.arw["outputHeight"].get_text()) + + config["options"]["noCompress"] = str(self.noCompress.get_active()) + config["options"]["righttoleft"] = str(self.righttoleft.get_active()) + config["options"]["overwrite"] = str(self.overwrite.get_active()) + config["options"]["slowmode"] = str(self.slowmode.get_active()) + + if not "output" in config : + config["output"] = OrderedDict() + config["output"]["htranslate"] = self.arw["htranslate2"].get_text() + config["output"]["vtranslate"] = self.arw["vtranslate2"].get_text() + config["output"]["scale"] = self.arw["scale2"].get_text() + config["output"]["rotate"] = self.arw["rotation2"].get_text() + config["output"]["xscale"] = self.arw["xscale2"].get_text() + config["output"]["yscale"] = self.arw["yscale2"].get_text() + config["output"]["vflip"] = self.arw["vflip2"].get_active() + config["output"]["hflip"] = self.arw["hflip2"].get_active() + + if not "page_numbers" in config : + config["page_numbers"] = OrderedDict() + config["page_numbers"]["page_numbers"] = self.arw["page_numbers"].get_active() + config["page_numbers"]["font_size"] = self.arw["numbers_font_size"].get_text() + config["page_numbers"]["start_from"] = self.arw["numbers_start_from"].get_text() + config["page_numbers"]["bottom_margin"] = self.arw["numbers_bottom_margin"].get_text() +## config["output"]["yscale"] = self.arw["yscale2"].get_text() + + # radio buttons + + group = self.arw["radiopreset1"].get_group() + for a in group : + if a.get_active() == True : + config["options"]["presets"] = a.get_name() + + group = self.arw["radiosize1"].get_group() + for a in group : + if a.get_active() == True : + config["options"]["size"] = a.get_name() + + group = self.arw["presetOrientation1"].get_group() + for a in group : + if a.get_active() == True : + config["options"]["presetOrientation"] = a.get_name() + + group = self.arw["globalRotation0"].get_group() + for a in group : + if a.get_active() == True : + config["options"]["globalRotation"] = a.get_name() + + # most recently used + + # TODO : if file exists (ici et ailleurs) + configtemp = parser.read(sfp3("pdfbooklet.cfg")) + + if "mru" in configtemp : + for option in configtemp["mru"] : + if "mru" in config : + config["mru"][option] = configtemp["mru"][option] + + + + + + return config + + + def _______________________PRESETS() : + pass + + + def guiPresets(self, radiobutton = 0, event = None) : + + global startup_b, project_b, preview_b + + if radiobutton != 0 : + if radiobutton.get_active() == 0 : # signal is sent twice, ignore one of them + return + if project_b == True : # Don't change values if we are loading a project + return + + preview_b = False # prevent multiple preview commands due to signals emitted by controls + presetOrientation_i = self.arw["presetOrientation1"].get_active() + + if self.arw["radiopreset1"].get_active() == 1 : # single booklet + if presetOrientation_i == 1 : + self.presetBooklet(0,0) + else : + self.presetBooklet(0,1) + self.guiPresetsShow("booklet") + + + elif self.arw["radiopreset2"].get_active() == 1 : # Multiple booklets + if presetOrientation_i == 1 : + self.presetBooklet(5,0) + else : + self.presetBooklet(5,1) + self.guiPresetsShow("booklet") + + elif self.arw["radiopreset3"].get_active() == 1 : # 2-up + if presetOrientation_i == 1 : + self.presetUp(1,2) + else : + self.presetUp(2,1) + self.guiPresetsShow("copies") + + elif self.arw["radiopreset4"].get_active() == 1 : # 4-up in lines + if presetOrientation_i == 1 : + self.presetUp(2,2,1) + else : + self.presetUp(2,2,1) + self.guiPresetsShow("") + + elif self.arw["radiopreset5"].get_active() == 1 : # 4-up in columns + if presetOrientation_i == 1 : + self.presetUp(2,2,2) + else : + self.presetUp(2,2,2) + self.guiPresetsShow("") + + elif self.arw["radiopreset6"].get_active() == 1 : # x copies + if presetOrientation_i == 1 : + self.presetCopies(1,2) + else : + self.presetCopies(2,1) + self.guiPresetsShow("copies") + + elif self.arw["radiopreset7"].get_active() == 1 : # 1 page + if presetOrientation_i == 1 : + self.presetMerge() + else : + self.presetMerge() + self.guiPresetsShow("copies") + + elif self.arw["radiopreset8"].get_active() == 1 : # User defined + return # This button launchs the function "user_defined" which will handle the request + + + preview_b = True + if radiobutton != 0 and startup_b == False : + self.preview(self.previewPage) + + def user_defined(self, widget) : + # Called by the "user defined" option button in the main window + # Gets data from the dialog where user enters the user defined layout + # Process the text from the TextView and sets controls and variables + # then update the preview. + # @widget : if this parameter is False, the dialog is not shown (used by OpenProject) + + global startup_b, project_b, preview_b + + preview_b = False # prevent multiple preview commands due to signals emitted by controls + if widget == False : + response = 1 + else : + dialog = self.arw["dialog2"] + response = dialog.run() + dialog.hide() + + if response == 0 : + return + if response == 1 : + buf = self.arw["user_layout"].get_buffer() + start, end = buf.get_bounds() + layout_s = buf.get_text(start, end, False) + + imposition2 = ini.parse_user_layout(layout_s) + + self.userpages = imposition2[0] + (self.imposition, numrows, numcols, pages) = imposition2 + + # set step + if self.arw["step_defined"].get_active() == True : + step_s = self.arw["step_value"].get_text() + self.presetCopies(numrows,numcols,step_s) + self.guiPresetsShow("copies") + else : + self.presetUp(numrows,numcols) + self.guiPresetsShow("") + + total_pages = numrows * numcols + if len(pages) != total_pages : + message =_("Expected page number was : %d. Only %d found. \nThere is an error in your layout, please correct") % (total_pages, len(pages)) + alert(message) + + + + # Update gui before updating preview, since preview takes its data from the gui + while Gtk.events_pending(): + Gtk.main_iteration() + + preview_b = True + if startup_b == False : + self.preview(self.previewPage) + + def select_step_value(self, widget, event) : + # launched when user types something in "step_value" entry + # Selects the appropriate radio button + self.arw["step_defined"].set_active(True) + + def guiPresetsShow(self, action_s) : + + StepWidgets = [self.arw["label15"], self.arw["entry15"]] + LeafsWidgets = [self.arw["label13"], self.arw["entry13"]] + OrientationWidgets = [self.arw["label11"], self.arw["presetOrientation1"], + self.arw["label12"], self.arw["presetOrientation2"]] + + + for a in StepWidgets + LeafsWidgets + OrientationWidgets : + a.hide() + + if action_s == "booklet" : + for a in LeafsWidgets + OrientationWidgets : + a.show() + + if action_s == "copies" : + for a in StepWidgets + OrientationWidgets : + a.show() + + + def presetBooklet(self, leafs_i, orientation) : + if orientation == 0 : + self.arw["entry11"].set_value(1) # rows + self.arw["entry12"].set_value(2) # columns + else : + self.arw["entry11"].set_value(2) # rows + self.arw["entry12"].set_value(1) # columns + self.arw["entry13"].set_text(str(leafs_i)) # leafs in booklet + ini.repeat = 0 + ini.booklet = 1 + + def presetUp(self, r,c,l=1) : + self.arw["entry11"].set_value(r) # rows + self.arw["entry12"].set_value(c) # columns + ini.booklet = 0 # checkerboard + ini.radioDisp = l # lines / columns + ini.repeat = 0 + + def presetCopies(self, r,c, step="1") : + global step_i + self.arw["entry11"].set_value(r) # rows + self.arw["entry12"].set_value(c) # columns + ini.booklet = 0 # checkerboard + ini.repeat = 1 + self.arw["entry15"].set_text(step) # step + + def presetCopies2(self, r,c,l=1) : + self.arw["entry11"].set_value(r) # rows + self.arw["entry12"].set_value(c) # columns + ini.booklet = 0 # checkerboard + ini.radioDisp = l # lines / columns + ini.repeat = 0 + + + def presetMerge(self) : # One page + global outputFile, inputFile_a + + self.arw["entry11"].set_value(1) # rows + self.arw["entry12"].set_value(1) # columns + ini.booklet = 0 # checkerboard + ini.repeat = 0 + + def _________________________PREVIEW() : + pass + + def OnDraw(self, area, cr): + global page + global areaAllocationW_i, areaAllocationH_i + global previewColPos_a, previewRowPos_a, pageContent_a + global refPageSize_a, numPages + global outputStream, outputStream_mem + + # Was the size changed ? + + if (areaAllocationW_i == self.area.get_allocated_width() + and areaAllocationH_i == self.area.get_allocated_height()) : + nochange_b = True + else : + nochange_b = False + areaAllocationW_i = self.area.get_allocated_width() + areaAllocationH_i = self.area.get_allocated_height() + + + # If nothing has not yet been loaded, show nofile.pdf + try : + data1 = outputStream.getvalue() + except : + f1 = open(sfp2("data/nofile.pdf"),"rb") + data1 = f1.read() + f1.close() + + + # TODO : When two calls are too close, it may create a strange stack resulting in an empty OutputStream + # This has to be redesigned. + if len(data1) == 0 : + #print("......... OutputStream is empty") + return + + # If the preview has not changed, don't render + # TODO : This must be redesigned, because with the image in memory, it may be necessary to render again with the same data +## if data1 == outputStream_mem : +## return +## else : +## outputStream_mem = data1 + + try : + bytes_data = GLib.Bytes(data1) + input_stream = Gio.MemoryInputStream.new_from_bytes(bytes_data) + # Take care that you need to call .close() on the Gio.MemoryInputStream once you're done with your pdf document. + document = Poppler.Document.new_from_stream(input_stream, -1, None, None) + self.document= document + + except : + # bug workaround + """ + Hi there, I installed the deb on Ubuntu 19.04. It installed fine but gives this message: + + Traceback (most recent call last): + File "/usr/local/lib/python3.7/dist-packages/pdfbooklet/pdfbooklet.py", line 2342, in OnDraw + document = Poppler.Document.new_from_stream(input_stream, -1, None, None) + gi.repository.GLib.GError: poppler-quark: Failed to load document (0) + + The preview panel remains blank but when I press the Go button the .bklt file is produced correctly. I get the same if I download the tar and install manually. Also the same with many different input pdf files. + """ + try: + filepath = os.path.join(tempfile.gettempdir(), "preview.pdf") + with open(filepath, "wb") as f1: + f1.write(data1) + document = Poppler.Document.new_from_file("file:///" + filepath, None) + self.document= document + except: + print("Error rendering document in poppler") + printExcept() + return False + + page = document.get_page(0) + self.page = page + + x = document.get_n_pages() + if x > 1 : + self.page1 = document.get_page(1) + else : + self.page1 = None + + # calculate the preview size + + pix_w, pix_h = page.get_size() + + A = int((areaAllocationH_i * pix_w) / pix_h) # width of preview if full height + B = int((areaAllocationW_i * pix_h) / pix_w) # height of preview if full width + + + if A < areaAllocationW_i : # if full height is OK + heightPoints = areaAllocationH_i + widthPoints = A + scale = areaAllocationH_i / pix_h + else : + widthPoints = areaAllocationW_i + heightPoints = B + scale = areaAllocationW_i / pix_w + + self.preview_scale = scale # this will be needed for drag (see the end_drag function) + Hoffset_i = int((areaAllocationW_i - widthPoints) /2) + Voffset_i = int((areaAllocationH_i - heightPoints)/2) + + # set background. + cr.set_source_rgb(0.7, 0.6, 0.5) + cr.paint() + + # set page background + cr.set_source_rgb(1, 1, 1) + cr.rectangle(Hoffset_i, Voffset_i, widthPoints, heightPoints) + + cr.fill() + + # render page + + cr.save() + cr.translate(Hoffset_i, Voffset_i) + cr.scale(scale,scale) + self.page.render(cr) + cr.restore() + + # If there are two pages in the preview, the second will be printed over the other + # to help fine tuning the margins for duplex printing + + if self.page1 : + cr.save() + cr.translate(Hoffset_i, Voffset_i) + cr.scale(scale,scale) + self.page1.render(cr) + cr.restore() + + # clear memory + input_stream.close() + + + # draw middle line + + if ((ini.booklet > 0 and len(inputFiles_a) > 0) + or self.arw["draw_middle_line"].get_active() == True) : + cr.save() + cr.set_line_width(1) + cr.set_dash((10,8)) + cr.set_source_rgb(0.5, 0.5, 1) + line_position = (areaAllocationW_i/2) + 0.5 # The reason for + 0.5 is explained here : https://www.cairographics.org/FAQ/#sharp_lines + # under the title : How do I draw a sharp, single-pixel-wide line? + cr.move_to(line_position, Voffset_i) + cr.line_to(line_position, (areaAllocationH_i - Voffset_i )) + cr.stroke() + cr.restore() + + + # if the output is turned, swap the numbers of rows and columns + if app.arw["globalRotation90"].get_active() == 1 \ + or app.arw["globalRotation270"].get_active() == 1: + preview_cols = rows_i + preview_rows = columns_i + else : + preview_cols = columns_i + preview_rows = rows_i + + + columnWidth = widthPoints / preview_cols + rowHeight = heightPoints / preview_rows + + #store position of columns and rows + # previewColPos_a will contain the position of the left of all columns (from left to right) + # previewRowPos_a will contain the position of the top of all rows (from bottom to top) + previewColPos_a = [] + previewRowPos_a = [] + previewPagePos_a = {} + + + for a1 in range(preview_cols) : + #left of the column + previewColPos_a += [int((a1 * columnWidth) + Hoffset_i)] + for a2 in range(preview_rows) : + #top of the row + # rows count starts from bottom => areaAllocationH_i - ... + previewRowPos_a += [areaAllocationH_i - (int((a2 * rowHeight) + Voffset_i ))] + # add the right pos of the last col + previewColPos_a += [int(previewColPos_a[a1] + columnWidth)] + previewRowPos_a += [int(previewRowPos_a[a2] - rowHeight)] + + + # show page numbers + i = 0 + pageContent_a = {} + + + for a in self.rotate_layout() : # returns the layout, rotated if necessary + + # human readable page number + pageRef_s = self.ar_pages[0][i] + file_number, page_number = pageRef_s.split(":") + page_number = int(page_number) + 1 + if file_number == "1" : + pageNumber = str(page_number) + else : + pageNumber = str(file_number) + ":" + str(page_number) + + + fontsize_i = int(columnWidth / 4) + if fontsize_i < 10 : + fontsize_i = 10 + colpos_i = previewColPos_a[a[1]] + (columnWidth / 2) + rowpos_i = previewRowPos_a[a[0]] - (rowHeight / 2) + x1,x2 = colpos_i,rowpos_i + + # draw page number + if self.arw["hide_numbers"].get_active() == 0 : + # TODO cr.select_font_face("Georgia", + ## cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) + ## AttributeError: 'gi.repository.cairo' object has no attribute 'FONT_SLANT_NORMAL' + ## Remplacé par 0 et 1 ci-dessous + cr.select_font_face("Georgia", 0, 1) + cr.set_font_size(fontsize_i) + x_bearing, y_bearing, txtWidth, txtHeight = cr.text_extents(pageNumber)[:4] + + colpos_i -= int(txtWidth / 4) + rowpos_i -= int(txtHeight / 4) + + + cr.move_to(colpos_i, rowpos_i - y_bearing) + cr.set_source_rgba(1, 0, 0, 0.6) + cr.show_text(pageNumber) + + pageId = str(a[0]) + ":" + str(a[1]) + pageContent_a[pageId] = [file_number, page_number] + + # page position + bottom = previewRowPos_a[a[0]] + top = previewRowPos_a[a[0]] - rowHeight + left = previewColPos_a[a[1]] + right = previewColPos_a[a[1]] + columnWidth + + + + # draw rectangle if selected + humanReadableRow_i = (preview_rows - a[0]) + pagePosition_a = [humanReadableRow_i, a[1] + 1] + pagePosition_s = self.rotate_position(pagePosition_a) + pageNumber_s = str(file_number) + ":" + str(page_number) + draw_page_b = False + type_s = "" + + # is this page odd or even ? + if self.arw["evenpages"].get_active() == 1 : + if int(pageNumber) % 2 == 0 : + cr.set_source_rgb(0, 0, 1) + draw_page_b = True + type_s = "even" + + elif self.arw["oddpages"].get_active() == 1 : + if int(pageNumber) % 2 == 1 : + cr.set_source_rgb(0, 0, 1) + draw_page_b = True + type_s = "odd" + + # if page is selected + elif pagePosition_s in selectedIndex_a : + # Select the rectangle color + cr.set_source_rgb(1, 0, 0) + draw_page_b = True + type_s = "select" + + + elif pageNumber_s in selectedIndex_a : + # Select the rectangle color + cr.set_source_rgb(0, 0.7, 0) + draw_page_b = True + type_s = "select" + + + + + # draw rectangle + if draw_page_b == True : + + file_number, page_number = self.myselectedpage.split(":") + selected_ref = file_number + ":" + str(int(page_number) - 1) + + + coord = [left, top] + cr.set_line_width(3) + cr.rectangle(coord[0], coord[1], columnWidth, rowHeight) + cr.stroke() + + + i += 1 + + + # Show page size and total pages number +## cr.select_font_face("Arial", 0, 1) + # TODO Anciennement cr.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) + # mais les attributs ne marche plus. +## cr.set_font_size(12) + try : + message = str(round(refPageSize_a[0] * adobe_l)) + message += " x " + str(round(refPageSize_a[1] * adobe_l)) + message += " - " + str(numPages) + " pages" +## x_bearing, y_bearing, txtWidth, txtHeight = cr.text_extents(message)[:4] +## cr.move_to(20,20) +## cr.set_source_rgb(0, 0.6, 0) +## cr.show_text(message) + self.arw["info_fichier"].set_text(message) + except : + pass # if refPageSize is not defined, error + + + + def rotate_layout(self) : + rotated_layout= [] + for i in range (len(self.ar_layout)) : + r, c = self.ar_layout[i] + + # Flip columns or rows if vertical or horizontal flip is selected + if app.arw["vflip2"].get_active() == 1 : + r = (rows_i - 1) - r + if app.arw["hflip2"].get_active() == 1 : + c = (columns_i - 1) - c + + # if output page is rotated (global rotation) + if app.arw["globalRotation270"].get_active() == 1 : + # invert row; We use columns_i because when rotated 90°, + # the numbers of rows of the preview is the number of columns of the page + r = (rows_i - 1) - r + r,c = c,r # swap + + elif app.arw["globalRotation90"].get_active() == 1 : + # invert column; ; We use rows_i because when rotated 90°, + # the numbers of columns of the preview is the number of rows of the page + c = (columns_i - 1) - c + r,c = c,r # swap + + elif app.arw["globalRotation180"].get_active() == 1 : + # invert row and column + r = (rows_i - 1) - r + c = (columns_i - 1) - c + + + rotated_layout.append([r,c]) + return rotated_layout + + + + def rotate_position(self, position) : + +## print( "source : ", position) + r, c = position + # if output page is rotated (global rotation) + if app.arw["globalRotation270"].get_active() == 1 : + # invert row; We use columns_i because when rotated 90°, + # the numbers of rows of the preview is the number of columns of the page + r = (columns_i + 1) - r + r,c = c,r # swap + + elif app.arw["globalRotation90"].get_active() == 1 : + # invert column; ; We use rows_i because when rotated 90°, + # the numbers of columns of the preview is the number of rows of the page + c = (rows_i + 1) - c + r,c = c,r # swap + + elif app.arw["globalRotation180"].get_active() == 1 : + # invert row and column + r = (rows_i + 1) - r + c = (columns_i +1) - c + +## print ("dest : ", str(r) + "," + str(c)) + + return str(r) + "," + str(c) + + + + def selectPage (self, widget, event=None): + # Called by a click on the preview or on the radio buttons, this function will : + # - Select the appropriate Id + # - launch area_expose to update the display + # - fill in the gtkEntry widgets which contains the transformations + # + # The selected_page list contains a list of six values : + # - row and column (Pdf format) + # - file number and page number + # - row and column if the output page is rotated + + global previewColPos_a, previewRowPos_a, canvasId20, pageContent_a + global selectedIndex_a, selected_page, selected_pages_a, pageId + + + if event == None : # Click on a radio button + if self.thispage.get_active() == 1 : + pageId = str(selected_page[2]) + ":" + str(selected_page[3]) + Id = pageId + elif self.evenpages.get_active() == 1 : + Id = "even" + elif self.oddpages.get_active() == 1 : + Id = "odd" + else : # first button, pages in this position + humanReadableRow_i = rows_i - selected_page[4] + Id = str(str(humanReadableRow_i) + "," + str(selected_page[5] + 1)) + Id = str(str(selected_page[5] + 1) + "," + str(humanReadableRow_i)) + + elif event.type == Gdk.EventType.BUTTON_PRESS: + if event.button == 3 : # right click, runs the context menu + self.arw["contextmenu1"].popup(None, None, None, None, event.button, event.time) + return + + else : + # get preview area + left_limit = previewColPos_a[0] + bottom_limit = previewRowPos_a[-1] + right_limit = previewColPos_a[-1] + top_limit = previewRowPos_a[0] + + xpos = event.x + ypos = event.y + + self.initdrag = [xpos, ypos] # Will be used for moving page with the mouse + self.preview_limits = [left_limit, right_limit, top_limit, bottom_limit] + + # check if click is inside preview + if (xpos < left_limit + or xpos > right_limit + or ypos < bottom_limit + or ypos > top_limit) : + + return + + # find the row and column + for c in range(len(previewColPos_a)) : + if xpos > previewColPos_a[c] and xpos < previewColPos_a[c + 1]: + leftPos_i = previewColPos_a[c] + rightPos_i = previewColPos_a[c + 1] + break + + + for r in range(len(previewRowPos_a)) : + if ypos < previewRowPos_a[r] and ypos > previewRowPos_a[r + 1]: + bottomPos_i = previewRowPos_a[r] + topPos_i = previewRowPos_a[r + 1] + break + + r1 = r + c1 = c + + # it should be possible to use rotate_layout + + # Flip columns or rows if vertical or horizontal flip is selected +## if app.arw["vflip2"].get_active() == 1 : +## r1 = (rows_i - 1) - r +## if app.arw["hflip2"].get_active() == 1 : +## c1 = (columns_i - 1) - c + + # if output page is rotated (global rotation) + if app.arw["globalRotation90"].get_active() == 1 : + # invert row; We use columns_i because when rotated 90°, + # the numbers of rows of the preview is the number of columns of the page + r1 = (columns_i - 1) - r1 + r1,c1 = c1,r1 # swap + + elif app.arw["globalRotation270"].get_active() == 1 : + # invert column; ; We use rows_i because when rotated 90°, + # the numbers of columns of the preview is the number of rows of the page + c1 = (rows_i - 1) - c1 + r1,c1 = c1,r1 # swap + + elif app.arw["globalRotation180"].get_active() == 1 : + # invert row and column + r1 = (rows_i - 1) - r1 + c1 = (columns_i - 1) - c1 + + selected_page = [r, c] + selected_page += pageContent_a[str(r) + ":" + str(c)] + selected_page += [r1, c1] + + + # Selection information + self.myselectedpage = str(selected_page[2]) + ":" + str(selected_page[3]) + if self.thispage.get_active() == 1 : # This page + Id = self.myselectedpage + elif self.evenpages.get_active() == 1 : # even pages + Id = "even" + elif self.oddpages.get_active() == 1 : # odd pages + Id = "odd" + else : # first button, pages in this position + humanReadableRow_i = rows_i - selected_page[4] + Id = str(str(humanReadableRow_i) + "," + str(selected_page[5] + 1)) + + + + if event.state != Gdk.ModifierType.CONTROL_MASK : # If Control not pressed, delete previous selection + selectedIndex_a = {} + selected_pages_a = [] + + # position reference + selectedIndex_a[Id] = 1 + selected_pages_a.append(selected_page) + + + # force the "draw" signal to update the display + self.arw["drawingarea1"].hide() + self.arw["drawingarea1"].show() + + + + + + + else : # unsupported event + return + + # Load settings in transformation dialogs + if event.state != Gdk.ModifierType.CONTROL_MASK : # but only if CONTROL is not pressed + self.load_settings_in_dialog(Id) + + + def load_settings_in_dialog(self, Id) : + + + # defaults + self.arw["htranslate1"].set_text("0") + self.Vtranslate1.set_text("0") + self.scale1.set_text("100") + self.rotation1.set_text("0") + self.arw["xscale1"].set_text("100") + self.arw["yscale1"].set_text("100") + self.arw["vflip1"].set_active(False) + self.arw["hflip1"].set_active(False) + + + if Id in config : + if "htranslate" in config[Id] : + self.arw["htranslate1"].set_text(str(config[Id]["htranslate"])) + if "vtranslate" in config[Id] : + self.Vtranslate1.set_text(str(config[Id]["vtranslate"])) + if "scale" in config[Id] : + self.scale1.set_text(str(config[Id]["scale"])) + if "rotate" in config[Id] : + self.rotation1.set_text(str(config[Id]["rotate"])) + if "xscale" in config[Id] : + self.arw["xscale1"].set_text(str(config[Id]["xscale"])) + if "yscale" in config[Id] : + self.arw["yscale1"].set_text(str(config[Id]["yscale"])) + + if "vflip" in config[Id] : + bool_b = config[Id]["vflip"] + if bool_b == "True" or bool_b == True or bool_b == "1" : # when parameter comes from the ini file, it is a string + bool_b = 1 + elif bool_b == "False" or bool_b == False or bool_b == "0" : + bool_b = 0 + self.arw["vflip1"].set_active(bool_b) + if "hflip" in config[Id] : + bool_b = config[Id]["hflip"] + if bool_b == "True" or bool_b == True or bool_b == "1" : # when parameter comes from the ini file, it is a string + bool_b = 1 + elif bool_b == "False" or bool_b == False or bool_b == "0" : + bool_b = 0 + + self.arw["hflip1"].set_active(bool_b) + + + def drag_motion(self, widget, cr, x, y, time) : + # unused + # To activate, connect the signal "drag-motion" of then eventbox parent of drawingarea1 + # to this function + print (x) + cr.set_line_width(1) + cr.rectangle(x, y, 2, 2) + cr.stroke() + return False + + + def set_even_odd_settings(self, widget) : # launched by a clic on the domain buttons + global selected_page, selectedIndex_a + #print ("set even odd") + if self.evenpages.get_active() == 1 : + Id = "even" + selected_page = Id + self.load_settings_in_dialog(Id) + elif self.oddpages.get_active() == 1 : + Id = "odd" + selected_page = Id + self.load_settings_in_dialog(Id) + else : + #print ("reset selection") + selected_page = None + selectedIndex_a = {} + self.previewUpdate() + + + + def end_drag(self, widget, event) : + global outputScale + + # Thisfunction will move the page when the mouse button is released + x = event.x + y = event.y + + # scaling factor + if self.arw["autoscale"].get_active() == 1 : + scaling_factor = self.preview_scale * outputScale + else : + scaling_factor = self.preview_scale + + + scaling_factor2 = scaling_factor / adobe_l + + hmove = ((x - self.initdrag[0]) / scaling_factor2) + vmove = ((y - self.initdrag[1]) / scaling_factor2) + + if self.arw["move_with_mouse"].get_active() == 1 : + + # Calculate the scaling factor + + temp = self.arw["htranslate1"].get_text() + temp = temp.replace(",", ".") + temp = float(temp) + temp += hmove + temp = str(temp).split(".") + temp = temp[0] + "." + temp[1][0:1] + self.arw["htranslate1"].set_text(temp) + self.transformationsApply("") + + temp = self.arw["vtranslate1"].get_text() + temp = temp.replace(",", ".") + temp = float(temp) + temp -= vmove + temp = str(temp).split(".") + temp = temp[0] + "." + temp[1][0:1] + self.arw["vtranslate1"].set_text(temp) + self.transformationsApply("") + + elif self.arw["delete_rectangle"].get_active() == 1 : + # preview_limits gives : left x, right x, bottom y, top y, in pixels. + # + # init_drag gives : x, y + # x = horizontal position + left = self.preview_limits[0] # left margin + top = self.preview_limits[3] # top margin + + + x1 = (self.initdrag[0] - left) # start drag + y1 = self.initdrag[1] + y1 = self.preview_limits[2] - y1 # invert the vertical position, because Pdf counts from bottom + # This corrects also the top margin because bottom y = page height + margin + x2 = x - left # end drag + y2 = y + y2 = self.preview_limits[2] - y2 + width = x2 - x1 + height = y2 - y1 + x1 = x1 / scaling_factor + y1 = y1 / scaling_factor + width = width / scaling_factor + height = height / scaling_factor + ini.delete_rectangle = [x1,y1,width,height] + + elif self.arw["divide"].get_active() == 1 : + # preview_limits gives : left x, right x, bottom y, top y, in pixels. + # + # init_drag gives : x, y + # x = horizontal position + left = self.preview_limits[0] # left margin + top = self.preview_limits[3] # top margin + + + x1 = (self.initdrag[0] - left) # start drag + y1 = self.initdrag[1] + y1 = self.preview_limits[2] - y1 # invert the vertical position, because Pdf counts from bottom + # This corrects also the top margin because bottom y = page height + margin + x2 = x - left # end drag + y2 = y + y2 = self.preview_limits[2] - y2 + width = x2 - x1 + height = y2 - y1 + x1 = x1 / scaling_factor + y1 = y1 / scaling_factor + width = width / scaling_factor + height = height / scaling_factor + ini.delete_rectangle = [x1,y1,width,height] + + + + def createSelection(self) : + global inputFiles_a + i = 1 + x = [] + for f in inputFiles_a : + fileName = inputFiles_a[f] + numPages = inputFile_a[fileName].getNumPages() + for z in range(numPages) : + pageRef_s = str(i) + ":" + str(z) + if pageRef_s + "deleted" in deletedIndex_a : + if deletedIndex_a[pageRef_s + "deleted"] == 1 : + pass + else : + x += [pageRef_s] + else : + x += [pageRef_s] + i += 1 + self.selection_s = self.compressSelection(x) + + def compressSelection(self, x) : + i = 0 + temp = {} + out = "" + for a in x : + b = a.split(":") + if len(b) == 1 : + npage = b[0] + nfile = 1 + else : + npage = b[1] + nfile = b[0] + + if i == 0 : + temp["file"] = nfile + temp["first"] = npage + temp["last"] = npage + else : + if nfile == temp["file"] and int(npage) == int(temp["last"]) + 1 : + temp["last"] = npage # on continue + else : # sinon on écrit + if temp["first"] == "-1": + out += "b" + else : + out += str(temp["file"]) + ":" + str(temp["first"]) + if temp["last"] != temp["first"] : + out += "-" + str(temp["last"]) + out += "; " + # et on mémorise + temp["file"] = nfile + temp["first"] = npage + temp["last"] = npage + i += 1 + + if temp["first"] == "-1": + out += "b" + else : + out += str(temp["file"]) + ":" + str(temp["first"]) + if temp["last"] != temp["first"] : + out += "-" + str(temp["last"]) + + # compress blank pages + temp1 = out.split(";") + out = "" + blank_count = 0 + for a in temp1 : + if a.strip() == "b" : + blank_count += 1 + else : + if blank_count > 0 : + out += str(blank_count) + "b;" + blank_count = 0 + out += a + ";" + if blank_count > 0 : + out += str(blank_count) + "b" + + return out + + def edit_selection(self, widget) : + dialog = self.arw["dialog1"] + TextBuffer = self.arw["textview1"].get_buffer() + selection1 = self.selection_s.replace(";","\n") + TextBuffer.set_text(selection1) + choice = dialog.run() + if choice == 1 : + start_iter = TextBuffer.get_start_iter() + end_iter = TextBuffer.get_end_iter() + selection2 = TextBuffer.get_text(start_iter, end_iter, False) + selection2 = selection2.replace("\n",";") + self.selection_s = selection2 + if self.shuffler: + self.shuffler.model.clear() + self.shuffler.pdfqueue = [] + self.shuffler.nfile = 0 + self.loadShuffler() + # TODO : N'est à faire que si la liste des fichiers a changé + self.shuffler.rendering_thread.pdfqueue = self.shuffler.pdfqueue + dialog.hide() + self.previewUpdate() + + + def compare_files_selection(self,widget) : + # Create a selection to display two or more files side by side + + # Number of files and pages + files_data = {} + index = 1 + maxPages = 0 + numFiles = len(inputFiles_a) + for f in inputFiles_a : + fileName = inputFiles_a[f] + numPages = inputFile_a[fileName].getNumPages() + if numPages > maxPages : + maxPages = numPages + files_data[index] = numPages + index += 1 + + # create the selection + index = 1 + text = "" + for i in range(maxPages) : + temp = [] + for j in range(1, numFiles + 1) : + if files_data[j] > i : # if the file is shorter, insert blank pages + temp.append(str(j) + ":" + str(i)) + else : + temp.append("b") + text += ",".join(temp) + "\n" + + TextBuffer = self.arw["textview1"].get_buffer() + TextBuffer.set_text(text) + + + def preview(self, previewPage, delete = 1) : + + + global mediabox_l0, columns_i, rows_i, step_i, urx_i, ury_i, mediabox_l + global outputScale, pageContent_a + global selectedIndex_a, deletedIndex_a + global areaAllocationW_i, areaAllocationH_i + global preview_b + + + if preview_b == False : + return + + + + if self.readGui(0) : + self.manage_backup() + if self.render.parsePageSelection() : + #self.readConditions() + ar_pages, ar_layout, ar_cahiers = self.render.createPageLayout(0) + if ar_pages != None : + if previewPage > len(ar_pages) - 1 : + previewPage = len(ar_pages) - 1 + self.previewPage = previewPage + self.arw["previewEntry"].set_text(str(previewPage + 1)) + mem = ar_pages[previewPage] + try : + mem1 = ar_pages[previewPage + 1] + except : # If we are arrived at the last page + pass + ar_pages = {} + ar_pages[0] = mem + + # If previewing of two pages one over the other is requested + if 1 == 2 : + ar_pages[1] = mem1 + + self.ar_pages = ar_pages + self.ar_layout = ar_layout + + if self.render.createNewPdf(ar_pages, ar_layout, ar_cahiers, "", previewPage) : + + # force the "draw" signal to update the display + self.arw["drawingarea1"].hide() + self.arw["drawingarea1"].show() + + + + def preview_keys(self, widget, event = None) : + print ( "==========>>", event) + + def previewNext(self, dummy, event=None) : + global selected_page, selectedIndex_a + if event.state != Gdk.ModifierType.CONTROL_MASK : + selected_page = None + selectedIndex_a = {} + self.previewPage += 1 + self.arw["previewEntry"].set_text(str(self.previewPage + 1)) + if self.arw["automaticUpdate"].get_active() == 0 : # If automatic update is not disabled + self.preview(self.previewPage) # update the preview + + def previewPrevious(self, dummy, event = None) : + global selected_page, selectedIndex_a + if event.state != Gdk.ModifierType.CONTROL_MASK : + selected_page = None + selectedIndex_a = {} + self.previewPage -= 1 + if self.previewPage < 0 : + self.previewPage = 0 + self.arw["previewEntry"].set_text(str(self.previewPage + 1)) + if self.arw["automaticUpdate"].get_active() == 0 : # If automatic update is not disabled + self.preview(self.previewPage) # update the preview + + def previewFirst(self, widget) : + global selected_page, selectedIndex_a + selected_page = None + selectedIndex_a = {} + self.previewPage = 0 + self.arw["previewEntry"].set_text(str(self.previewPage + 1)) + if self.arw["automaticUpdate"].get_active() == 0 : # If automatic update is not disabled + self.preview(self.previewPage) # update the preview + + def previewLast(self, widget) : + global selected_page, selectedIndex_a + selected_page = None + selectedIndex_a = {} + self.previewPage = 1000000 # CreatePageLayout will substitute the right number + self.arw["previewEntry"].set_text(str(self.previewPage + 1)) + if self.arw["automaticUpdate"].get_active() == 0 : # If automatic update is not disabled + self.preview(self.previewPage) # update the preview + + def previewUpdate(self, Event = None, mydata = None) : + global inputFiles_a + + if len(inputFiles_a) == 0 : + #showwarning(_("No file loaded"), _("Please select a file first")) + return False + if Event != None : + value_s = self.arw["entry11"].get_text() + value2_s = self.arw["entry12"].get_text() + if value_s != "" and value2_s != "" : + previewPage = self.arw["previewEntry"].get_text() + if previewPage != "" : + self.preview(int(previewPage) - 1) + return + else : + return +## self.preview(self.previewPage) + if self.arw["automaticUpdate"].get_active() == 0 : # If automatic update is not disabled + self.preview(self.previewPage) # update the preview + + + + + def previewDelayedUpdate(self, event) : # Unused : does not work + return + print("previewUpdateDelayed") + try : + self.t1.cancel() + except : + pass + self.t1 = threading.Timer(1, self.previewUpdate, [event]) + self.t1.start() + print("timer démarré") + + def preview2(self, widget) : + global selected_page + selected_page = None + previewPage = int(widget.get_text()) + self.previewPage = previewPage - 1 + self.arw["previewEntry"].set_text(str(self.previewPage + 1)) + if self.arw["automaticUpdate"].get_active() == 0 : # If automatic update is not disabled + self.preview(self.previewPage) # update the preview + + def manage_backup(self) : + return + if self.backup_command == False : + self.backup_command = True + return + if len(self.backup) == 0 : + self.backup.append(copy.deepcopy(config)) + self.backup_index = len(self.backup) + else : + last = self.backup[-1] + a = repr(config) + b = repr(last) + if a == b : + return + else : + self.backup.append(copy.deepcopy(config)) + #print (len(self.backup)) + self.backup_index = len(self.backup) + + def go_back(self, widget) : + config = copy.deepcopy(self.backup[self.backup_index - 2]) + self.setupGui() + self.backup_command = False + + def _____________________SHUFFLER() : + pass + + + def runPS(self, widget=None) : + global inputFiles_a, pagesSel + + if len(inputFiles_a) == 0 : + showwarning(_("No selection"), _("There is no selected file. \nPlease select a file first. ")) + return + if self.shuffler == None : + self.shuffler = PdfShuffler() + self.shuffler_window = self.shuffler.uiXML.get_object('main_window') + self.shuffler.uiXML.get_object('menubar').hide() + self.shuffler.uiXML.get_object('toolbar1').hide() + #self.shuffler.uiXML.get_object('menu1_RR').hide() + #self.shuffler.uiXML.get_object('menu1_RL').hide() + self.shuffler.uiXML.get_object('menu1_crop').hide() + self.shuffler.window.set_deletable(False) + shufflerBB = self.arw['shufflerbuttonbox'] + shufflerBB.unparent() + vbox = self.shuffler.uiXML.get_object('vbox1') + vbox.pack_start(shufflerBB, False, True, 0) + + self.loadShuffler() + + else : + self.shuffler_window.show() + + + def loadShuffler(self) : + render.parsePageSelection("", 0) + + for key in inputFiles_a : + pdfdoc = PDF_Doc(inputFiles_a[key], self.shuffler.nfile, self.shuffler.tmp_dir) + if pdfdoc.nfile != 0 and pdfdoc != []: + self.shuffler.nfile = pdfdoc.nfile + self.shuffler.pdfqueue.append(pdfdoc) + + angle=0 + crop=[0.,0.,0.,0.] + for page in pagesSel : + file1, page1 = page.split(":") + npage = int(page1) + 1 + filenumber = int(file1) - 1 + pdfdoc = self.shuffler.pdfqueue[filenumber] + if npage > 0 : + docPage = pdfdoc.document.get_page(npage-1) + else : + docPage = pdfdoc.document.get_page(0) + w, h = docPage.get_size() + + # blank page + if npage == 0 : + descriptor = 'Blank' + width = self.shuffler.iv_col_width + row =(descriptor, # 0 + None, # 1 + 1, # 2 + -1, # 3 + self.zoom_scale, # 4 + "", # 5 + 0, # 6 + 0,0, # 7-8 + 0,0, # 9-10 + w,h, # 11-12 + 2. ) # 13 FIXME + + self.shuffler.model.append(row) + else : + + + + + + descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)]) + iter = self.shuffler.model.append((descriptor, # 0 + None, # 1 + pdfdoc.nfile, # 2 + npage, # 3 + self.shuffler.zoom_scale, # 4 + pdfdoc.filename, # 5 + angle, # 6 + crop[0],crop[1], # 7-8 + crop[2],crop[3], # 9-10 + w,h, # 11-12 + 2. )) # 13 FIXME + self.shuffler.update_geometry(iter) + res = True + + self.shuffler.reset_iv_width() + if res: + self.shuffler.render() + return res + + + + def closePS(self) : + if self.shuffler : + self.shuffler.rendering_thread.quit = True + Gdk.threads_enter() + # TODO ££ +## if self.shuffler.rendering_thread.paused == True: +## self.shuffler.rendering_thread.evnt.set() +## self.shuffler.rendering_thread.evnt.clear() + self.shuffler_window.destroy() + self.shuffler= None + self.shuffler + self.runPS() + + def getShufflerSel(self, widget): + + selection = [] + for row in self.shuffler.model: + Id = str(row[2]) + ":" + str(row[3]) + selection += [Id] + angle = row[7] + + if angle != 0 : + if angle == 90 : # In Pdf format, global rotation rotates clockwise, + angle = 270 # fine rotation (used by PdfBooklet) rotates counterclockwise. + + elif angle == -90 : + angle = 90 + + + if not Id in config and angle != 0 : + config[Id] = {} + # defaults + config[Id]["htranslate"] = 0 + config[Id]["vtranslate"] = 0 + config[Id]["scale"] = 1 + config[Id]["rotate"] = 0 + + if angle != 0 : + config[Id]["shuffler_rotate"] = angle +## if angle == 270 : +## config[Id]["vtranslate"] = pix_w +## elif angle == 90 : +## config[Id]["htranslate"] = pix_h + + + + + + self.selection_s = self.compressSelection(selection) + self.shuffler_window.hide() + self.previewUpdate() + + + def closeShuffler(self, widget) : + self.shuffler_window.hide() + + + def ________________TRANSFORMATIONS() : + pass + + + def ta2(self, widget, event = "") : + print("ta2", event) + self.transformationsApply("") + + def transformationsApply(self, widget, event="", force_update = False) : + # Reads the values in the gui and updates the config dictionary + + global selected_page, selected_pages_a, rows_i + + for this_selected_page in selected_pages_a : + + if this_selected_page == None : + showwarning(_("No selection"), _("There is no selected page. \nPlease select a page first. ")) + return + # this_selected_page 4 and 5 contain the correct page reference, including the global rotation. + humanReadableRow_i = rows_i - this_selected_page[4] + Id = str(str(humanReadableRow_i) + "," + str(this_selected_page[5] + 1)) + + pageId = str(this_selected_page[2]) + ":" + str(this_selected_page[3]) + + # if transformation is for this page only, use page ref instead of position ref + if self.thispage.get_active() == 1 : + Id = pageId + if self.evenpages.get_active() == 1 : + Id = "even" + if self.oddpages.get_active() == 1 : + Id = "odd" + + config[Id] = {} + + config[Id]["htranslate"] = self.arw["htranslate1"].get_text() # data from the gui unmodified + config[Id]["vtranslate"] = self.Vtranslate1.get_text() + config[Id]["scale"] = self.scale1.get_text() + config[Id]["rotate"] = self.rotation1.get_text() + config[Id]["xscale"] = self.arw["xscale1"].get_text() + config[Id]["yscale"] = self.arw["yscale1"].get_text() + config[Id]["vflip"] = self.arw["vflip1"].get_active() + config[Id]["hflip"] = self.arw["hflip1"].get_active() + #if not force_update : + # prevent useless update. If no change, return. + # TODO : à débuguer +## a = repr(config[Id]) +## if not "memory1" in self : +## memory1 = {} +## b = self.memory1["memory1"] +## if a == b : +## return +## else : +## self.memory1["memory2"] = 0 +## self.memory1["memory1"] = a +## self.memory1["memory2"] += 1 + + if self.arw["automaticUpdate"].get_active() == 0 : # If automatic update is not disabled + self.preview(self.previewPage) # update the preview + + + def resetTransformations(self, event = 0) : + self.arw["htranslate1"].set_value(0) + self.arw["vtranslate1"].set_value(0) + self.arw["scale1"].set_value(100) + self.arw["xscale1"].set_value(100) + self.arw["yscale1"].set_value(100) + self.arw["rotation1"].set_value(0) + self.arw["vflip1"].set_active(False) + self.arw["hflip1"].set_active(False) + + if event != 0 : self.transformationsApply("dummy", force_update = True) + pass + + def resetTransformations2(self, event = 0) : + # reset default values for global transformations + self.arw["htranslate2"].set_value(0) + self.arw["vtranslate2"].set_value(0) + self.arw["scale2"].set_value(100) + self.arw["xscale2"].set_value(100) + self.arw["yscale2"].set_value(100) + self.arw["rotation2"].set_value(0) + self.arw["vflip2"].set_active(False) + self.arw["hflip2"].set_active(False) + if event != 0 : + self.arw["globalRotation0"].set_active(1) + self.previewUpdate() + pass + + def copy_transformations(self, event) : # called by context menu + self.clipboard["htranslate1"] = self.arw["htranslate1"].get_text() # data from the gui unmodified + self.clipboard["vtranslate1"] = self.Vtranslate1.get_text() + self.clipboard["scale1"] = self.scale1.get_text() + self.clipboard["rotate1"] = self.rotation1.get_text() + self.clipboard["this_page"] = self.thispage.get_active() + self.clipboard["xscale1"] = self.arw["xscale1"].get_text() + self.clipboard["yscale1"] = self.arw["yscale1"].get_text() + self.clipboard["vflip1"] = self.arw["vflip1"].get_active() + self.clipboard["hflip1"] = self.arw["hflip1"].get_active() + + def paste_transformations(self, event) : # called by context menu + self.arw["htranslate1"].set_text(self.clipboard["htranslate1"]) + self.Vtranslate1.set_text(self.clipboard["vtranslate1"]) + self.scale1.set_text(self.clipboard["scale1"]) + self.rotation1.set_text(self.clipboard["rotate1"]) + self.thispage.set_active(self.clipboard["this_page"]) + self.arw["xscale1"].set_text(self.clipboard["xscale1"]) + self.arw["yscale1"].set_text(self.clipboard["yscale1"]) + self.arw["vflip1"].set_active(self.clipboard["vflip1"]) + self.arw["hflip1"].set_active(self.clipboard["hflip1"]) + + self.transformationsApply("", force_update = True) + + + + def version21(self, widget) : + showwarning("Not yet implemented", "This feature will be implemented in version 2.2") + + def aboutPdfbooklet(self, widget) : + self.arw["Pdf-Booklet"].show() + + def aboutdialog1_close(self, widget,event) : + self.arw["Pdf-Booklet"].hide() + + def print2(self, string, cr=0) : # no longer used + global editIniFile + + return + editIniFile = 0 + enditer = self.text1.get_end_iter() + self.text1.insert(enditer, string) + if cr == 1 : + self.text1.insert(enditer, chr(10)) + iter0 = self.text1.get_end_iter() + self.arw["text1"].scroll_to_iter(iter0,0) + # TODO : bug + # This command hangs the program (the interface remains blank) when : + # a file has been loaded + # The page selector has been used + # a second file is loaded. + #while Gtk.events_pending(): + # Gtk.main_iteration() + + def test(self,event, dummy = None) : + print("test") + + def destroyWindow() : + pass # commande encore présente dans Glade, à supprimer + + def go(self, button, preview = -1) : + + if self.readGui() : + if self.render.parsePageSelection() : + self.readConditions() + ar_pages, ar_layout, ar_cahiers = self.render.createPageLayout() + if ar_pages != None : + # Verify that the output file may be written to + + if app.arw["entry2"].get_text() == "" : + inputFile = inputFiles_a[1] + inputFile_name = os.path.splitext(inputFile)[0] + outputFile = inputFile_name + "-bklt.pdf" + else : + outputFile = app.arw["entry2"].get_text() + inputFile = inputFiles_a[1] + inputFile_wo_ext = os.path.splitext(inputFile)[0] + (inputFile_path, inputFile_name) = os.path.split(inputFile) + (inputFile_basename, inputFile_ext) = os.path.splitext(inputFile_name) + inputFile_path += "/" + inputFile_ext = inputFile_ext[1:] + + + outputFile = outputFile.replace("%F", inputFile_wo_ext) + outputFile = outputFile.replace("%N", inputFile_name) + outputFile = outputFile.replace("%B", inputFile_basename) + outputFile = outputFile.replace("%P", inputFile_path) + outputFile = outputFile.replace("%E", inputFile_ext) + + if preview == -1 : + if os.path.isfile(outputFile) : + if app.overwrite.get_active() == 0 : + answer_b = askyesno(_("File existing"), _("The outputfile already exists \n" \ + "overWrite ? " )) + if False == answer_b : + return False + + + if self.render.createNewPdf(ar_pages, ar_layout, ar_cahiers, outputFile, preview) : + if self.arw["show"].get_active() == 1 : + + if 'linux' in sys.platform : + subprocess.call(["xdg-open", outputFile]) + else: + os.startfile(outputFile) + + return [ar_pages, ar_layout, ar_cahiers] + + + + + def readConditions(self) : + global optionsDict, config + # Used by the go function above + return + if app.arw["entry3"].get() == "" : + return + else : + inifile = app.arw["entry3"].get() + config = parser.read(inifile) + optionsDict = {} + optionsDict["pages"] = {} + optionsDict["conditions"] = {} + + if config.has_section("pages") : + for a in config.options("pages") : + optionsDict["pages"][a] = config["pages"][a] + + if config.has_section("conditions") : + for a in config.options("conditions") : + optionsDict["conditions"][a] = config["conditions"][a] + + + def test1(self, widget) : + print("input") + def test2(self, widget) : + print("output") + def test3(self, widget) : + print("value_changed") + def test4(self, widget) : + print("change value") + def test5(self, widget) : + print("test5") + + + + +class pdfRender(): + + def transform(self, row, column, page_number, output_page_number, file_number) : + global optionsDict, config, rows_i + + # init variables + + V_offset = row * ury_i + H_offset = column * urx_i + cos_l = 1 + sin_l = 0 + + transform_s = " %s %s %s %s %s %s cm \n" % (cos_l , sin_l, -sin_l, cos_l , H_offset , V_offset) + transformations = [] + + # Transformations defined in gui + + # Transformations for page in position (row, col) + section_s = str(rows_i - row) + "," + str(column + 1) + if section_s in config : + transform_s += self.transform2(section_s) + + # Transformations for page #:# + pageId = str(file_number) + ":" + str(page_number + 1) + if pageId in config : + transform_s += self.transform2(pageId) + + # Debug !!! + """ Je ne comprends pas le sens de ce bloc, qui refait une deuxième fois la transformation, + si bien que quand, par exemple, on demande une transformation de 45, on obtient 90 ! + ht = ini.readmmEntry(config[pageId]["htranslate"]) + vt = ini.readmmEntry(config[pageId]["vtranslate"]) + sc = ini.readPercentEntry(config[pageId]["scale"]) + ro = ini.readNumEntry(config[pageId]["rotate"]) + try : + pdfrotate = ini.readNumEntry(config[pageId]["pdfrotate"]) + except : + pdfrotate = 0 + xs = ini.readPercentEntry(config[pageId]["xscale"]) + ys = ini.readPercentEntry(config[pageId]["yscale"]) + + + + matrix_s = self.calcMatrix2(ht, vt, + cScale = sc, + xscale = xs, + yscale = ys, + cRotate = ro, + Rotate = pdfrotate, + vflip = config[pageId]["vflip"], + hflip = config[pageId]["hflip"]) + + + +## if "shuffler_rotate" in config[pageId] : +## # we need the page size +## pdfDoc = app.shuffler.pdfqueue[file_number-1] +## page = pdfDoc.document.get_page(page_number) +## pix_w, pix_h = page.get_size() +## +## angle = int(config[pageId]["shuffler_rotate"]) +## pdfrotate += angle +## if angle == 270 : +## vtranslate = float(vtranslate) + float(pix_w) +## elif angle == 90 : +## htranslate = float(htranslate) + float(pix_h) +## elif angle == 180 : +## htranslate = float(htranslate) + float(pix_w) +## vtranslate = float(vtranslate) + float(pix_h) + + + transform_s += self.calcMatrix2(ht, vt, + cScale = sc, + cRotate = ro, + Rotate = pdfrotate) + """ + # Transformations for even and odd pages + if page_number % 2 == 1 : + transform_s += self.transform2("even") + if page_number % 2 == 0 : + transform_s += self.transform2("odd") + + + + + # Transformations defined in ini file + + if "pages" in config : + pages_a = config["pages"].keys() + if section_s in pages_a : # If the layout page presently treated is referenced in [pages] + temp1 = config["pages"][section_s] + transformations += temp1.split(", ") + + if str(page_number + 1) in pages_a : # If the page presently treated is referenced in [pages] + temp1 = config["pages"][str(page_number + 1)] + transformations += temp1.split(", ") + + + if "conditions" in config : + conditions_a = config["conditions"].keys() + for line1 in conditions_a : + condition_s = config["conditions"][line1] + command_s, filters_s = condition_s.split("=>") + if (eval(command_s)) : + transformations += filters_s.split(", ") + + for a in transformations : + + transform_s += self.calcMatrix(a) + return (transform_s) + + def transform2(self, Id) : + # Calculates matrix for a given section + matrix_s = "" + if Id in config : + if not "pdfRotate" in config[Id] : + config[Id]["pdfRotate"] = 0 + if not "rotate" in config[Id] : + config[Id]["rotate"] = 0 + + if "shuffler_rotate" in config[Id] : + # we need the page size + pdfDoc = app.shuffler.pdfqueue[file_number-1] + page = pdfDoc.document.get_page(page_number) + pix_w, pix_h = page.get_size() + + angle = int(config[Id]["shuffler_rotate"]) + pdfrotate += angle + if angle == 270 : + vtranslate = float(vtranslate) + float(pix_w) + elif angle == 90 : + htranslate = float(htranslate) + float(pix_h) + elif angle == 180 : + htranslate = float(htranslate) + float(pix_w) + vtranslate = float(vtranslate) + float(pix_h) + + ht = ini.readmmEntry(config[Id]["htranslate"]) + vt = ini.readmmEntry(config[Id]["vtranslate"]) + sc = ini.readPercentEntry(config[Id]["scale"]) + ro = ini.readNumEntry(config[Id]["rotate"]) + xs = ini.readPercentEntry(config[Id]["xscale"]) + ys = ini.readPercentEntry(config[Id]["yscale"]) + + + + matrix_s = self.calcMatrix2(ht, vt, + cScale = sc, + xscale = xs, + yscale = ys, + cRotate = ro, + Rotate = ini.readNumEntry(config[Id]["pdfRotate"]), + vflip = ini.readBoolean(config[Id]["vflip"]), + hflip = ini.readBoolean(config[Id]["hflip"])) + + + + return matrix_s + + + def calcMatrix(self, mydata, myrows_i = 1, mycolumns_i = 1) : + # Calculate matrix for transformations defined in the configuration + global config + + trans = mydata.strip() + cos_l = 1 + cos2_l = 1 + sin_l = 0 + Htranslate = 0 + Vtranslate = 0 + + + if config.has_option(trans, "PdfRotate") : + Rotate = config.getint(trans, "PdfRotate") + sin_l = math.sin(math.radians(Rotate)) + cos_l = math.cos(math.radians(Rotate)) + cos2_l = cos_l + + if config.has_option(trans, "Rotate") : + try : + Rotate = config.getfloat(trans, "Rotate") + except : + pass + + sin_l, cos_l, HCorr, VCorr = self.centeredRotation(Rotate, myrows_i, mycolumns_i) + cos2_l = cos_l + + Htranslate += HCorr + Vtranslate += VCorr + + + if config.has_option(trans, "Scale") : + Scale_f = ini.readPercentEntry(config[trans]["Scale"]) + cos_l = cos_l * Scale_f + cos2_l = cos_l + + HCorr = (urx_i * mycolumns_i * (Scale_f - 1)) / 2 + VCorr = (ury_i * myrows_i * (Scale_f - 1)) / 2 + Htranslate -= HCorr + Vtranslate -= VCorr + + if config.has_option(trans, "xscale") : + Scale_f = ini.readPercentEntry(config[trans]["xscale"]) + cos_l = cos_l * Scale_f + + HCorr = (urx_i * mycolumns_i * (Scale_f - 1)) / 2 + Htranslate -= HCorr + + if config.has_option(trans, "yScale") : + Scale_f = ini.readPercentEntry(config[trans]["yScale"]) + cos2_l = cos2_l * Scale_f + + VCorr = (ury_i * myrows_i * (Scale_f - 1)) / 2 + Vtranslate -= VCorr + + # Vertical flip : 1 0 0 -1 0 + if config.has_option(trans, "vflip") : + value = config[trans]["vflip"].lower() + if value == "true" or value == "1" : + cos2_l = cos2_l * (-1) + Vtranslate += ury_i * myrows_i + + # Horizontal flip : -1 0 0 1 0 + if config.has_option(trans, "hflip") : + value = config[trans]["hflip"].lower() + if value == "true" or value == "1" : + cos_l = cos_l * (-1) + Htranslate += urx_i * mycolumns_i + + + if config.has_option(trans, "htranslate") : + ht = ini.readNumEntry(config[trans]["htranslate"]) + Htranslate += ht / adobe_l + + if config.has_option(trans, "vtranslate") : + vt = ini.readNumEntry(config[trans]["vtranslate"]) + Vtranslate += vt / adobe_l + + + + if abs(sin_l) < 0.00001 : sin_l = 0 # contournement d'un petit problème : sin 180 ne renvoie pas 0 mais 1.22460635382e-16 + if abs(cos_l) < 0.00001 : cos_l = 0 + + + + transform_s = " %s %s %s %s %s %s cm \n" % (cos_l , sin_l, -sin_l, cos2_l , Htranslate , Vtranslate) + + + if "Matrix" in config[trans] : + Matrix = config[trans]["Matrix"] + transform_s = " %s cm \n" % (Matrix) + + + return transform_s + + + def calcMatrix2(self, Htranslate, Vtranslate, + cScale = 1, Scale = 1, + Rotate = 0, cRotate = 0, + vflip = 0, hflip = 0, + xscale = 1, yscale = 1, + global_b = False) : + # calculate matrix for transformations defined in parameters + + Htranslate = float(Htranslate) + Vtranslate = float(Vtranslate) + cos_l = 1 + sin_l = 0 + + if global_b == True : # for global transformations, reference for centered scale, rotation and flip is the output page + myrows_i = rows_i + mycolumns_i = columns_i + else : # for page transformations, reference is the active source page + myrows_i = 1 + mycolumns_i = 1 + + if Scale != 1: + Scale_f = float(Scale) + elif cScale != 1 : + Scale_f = float(cScale) + else : + Scale_f = 1 + + if Rotate != 0 : + sin_l = math.sin(math.radians(float(Rotate))) + cos_l = math.cos(math.radians(float(Rotate))) + # TODO Rotate and cRotate are not compatible. + elif cRotate != 0 : + sin_l, cos_l, HCorr, VCorr = self.centeredRotation(float(cRotate), myrows_i, mycolumns_i) + Htranslate += (HCorr * Scale_f) + Vtranslate += (VCorr * Scale_f) + + if Scale != 1 : + sin_l = sin_l * Scale_f + cos_l = cos_l * Scale_f + HCorr = (urx_i * (Scale_f - 1)) / 2 + VCorr = (ury_i * (Scale_f - 1)) / 2 + + if cScale != 1 : + sin_l = sin_l* Scale_f + cos_l = cos_l * Scale_f + HCorr = (urx_i * mycolumns_i * (Scale_f - 1)) / 2 + VCorr = (ury_i * myrows_i * (Scale_f - 1)) / 2 + + Htranslate -= HCorr + Vtranslate -= VCorr + + + if abs(sin_l) < 0.00001 : sin_l = 0 # contournement d'un petit problème : sin 180 ne renvoie pas 0 mais 1.22460635382e-16 + if abs(cos_l) < 0.00001 : cos_l = 0 + + transform_s = " %s %s %s %s %s %s cm \n" % (cos_l , sin_l, -sin_l, cos_l , Htranslate , Vtranslate) + + + Htranslate = 0 + Vtranslate = 0 + cos_l = 1 + cos2_l = 1 + sin_l = 0 + + + if xscale != '1' and xscale != 1 : + xscale = float(xscale) + cos_l = cos_l * xscale + + HCorr = (urx_i * mycolumns_i * (xscale - 1)) / 2 + Htranslate -= HCorr + + if yscale != '1' and yscale != 1: + yscale = float(yscale) + cos2_l = cos2_l * yscale + + VCorr = (ury_i * myrows_i * (yscale - 1)) / 2 + Vtranslate -= VCorr + + if abs(sin_l) < 0.00001 : sin_l = 0 # contournement d'un petit problème : sin 180 ne renvoie pas 0 mais 1.22460635382e-16 + if abs(cos_l) < 0.00001 : cos_l = 0 + + transform_s += " %s %s %s %s %s %s cm \n" % (cos_l , sin_l, -sin_l, cos2_l , Htranslate , Vtranslate) + + + Htranslate = 0 + Vtranslate = 0 + cos_l = 1 + cos2_l = 1 + sin_l = 0 + + # Vertical flip : 1 0 0 -1 0 + if vflip != 0 and vflip != False : + cos2_l = cos2_l * (-1) + Vtranslate += ury_i * myrows_i + + # Horizontal flip : -1 0 0 1 0 + if hflip != 0 and hflip != False : + cos_l = cos_l * (-1) + Htranslate += urx_i * mycolumns_i + + + + if abs(sin_l) < 0.00001 : sin_l = 0 # contournement d'un petit problème : sin 180 ne renvoie pas 0 mais 1.22460635382e-16 + if abs(cos_l) < 0.00001 : cos_l = 0 + if abs(cos2_l) < 0.00001 : cos2_l = 0 + + transform_s += " %s %s %s %s %s %s cm \n" % (cos_l , sin_l, -sin_l, cos2_l , Htranslate , Vtranslate) + return transform_s + + def centeredRotation_old(self, Rotate) : + + Rotate = math.radians(Rotate) + sin_l = math.sin(Rotate) + cos_l = math.cos(Rotate) + + # If a is the angle of the diagonale, and R the rotation angle, the center of the rectangle moves like this : + # Horizontal move = sin(a + R) - sin(a) + # Vertical move = cos(a + R) - cos(a) + #Hence, corrections are sin(a) - sin(a+R) and cos(a) - cos(a-R) + + diag = math.pow((urx_i * urx_i) + (ury_i * ury_i), 0.5) + alpha = math.atan2(ury_i, urx_i) + + S1 = math.sin(alpha) + S2 = math.sin(alpha + Rotate) + + C1 = math.cos(alpha) + C2 = math.cos(alpha + Rotate) + + Vcorr = (S1 - S2) * diag / 2 + Hcorr = (C1 - C2) * diag / 2 + + return (sin_l, cos_l, Hcorr, Vcorr) + + def centeredRotation(self, Rotate, myrows_i = 1, mycolumns_i = 1) : + + Rotate = math.radians(Rotate) + sin_l = math.sin(Rotate) + cos_l = math.cos(Rotate) + + # If a is the angle of the diagonale, and R the rotation angle, the center of the rectangle moves like this : + # Horizontal move = sin(a + R) - sin(a) + # Vertical move = cos(a + R) - cos(a) + #Hence, corrections are sin(a) - sin(a+R) and cos(a) - cos(a-R) + + oWidth_i = urx_i * mycolumns_i + oHeight_i = ury_i * myrows_i + + diag = math.pow((oWidth_i * oWidth_i) + (oHeight_i * oHeight_i), 0.5) + alpha = math.atan2(oHeight_i, oWidth_i) + + S1 = math.sin(alpha) + S2 = math.sin(alpha + Rotate) + + C1 = math.cos(alpha) + C2 = math.cos(alpha + Rotate) + + Vcorr = (S1 - S2) * diag / 2 + Hcorr = (C1 - C2) * diag / 2 + + return (sin_l, cos_l, Hcorr, Vcorr) + + def CalcAutoScale(self, fileNum, page) : + global inputFiles_a, inputFile_a, refPageSize_a + + fileName = inputFiles_a[fileNum] + page0 = inputFile_a[fileName].getPage(page) + llx_i=page0.mediaBox.getLowerLeft_x() + lly_i=page0.mediaBox.getLowerLeft_y() + urx_i=page0.mediaBox.getUpperRight_x() + ury_i=page0.mediaBox.getUpperRight_y() + + page_width = float(urx_i) - float(llx_i) + page_height = float(ury_i) - float(lly_i) + + (ref_width, ref_height) = refPageSize_a + + delta1 = ref_height / page_height + delta2 = ref_width / page_width + Vdiff = ref_height - (page_height * delta2) + Hdiff = ref_width - (page_width * delta1) + + # Choose the lower factor, and calculate the translation for centering the image + if delta1 < delta2 : + Scale = delta1 + Vtranslate = 0 + Htranslate = Hdiff/2 + else: + Scale = delta2 + Vtranslate = Vdiff/2 + Htranslate = 0 + + return (Scale, Htranslate, Vtranslate) + + + def parsePageSelection(self, selection = "", append_prepend = 1) : + global pagesSel, totalPages, pgcount, input1, numPages, step_i, blankPages + global inputFiles_a, inputFile_a + + if len(inputFiles_a) == 0 : + showwarning(_("No file loaded"), _("Please select a file first")) + return False + + if selection == "" : + selection = app.selection_s + + if selection.strip() == "" : # if there is no selection, then we create the default selection + # which contains all pages of all documents + i = 1 + for f in inputFiles_a : + fileName = inputFiles_a[f] + numPages = inputFile_a[fileName].getNumPages() + selection += str(i) + ":1-%s;" % (numPages) + i += 1 + app.selection_s = selection + + if selection == "" : # This should not happen... + showwarning(_("No selection"), _("There is no selection")) + return False + + selection = re.sub("[;,]{2,}", ";", selection) # we replace successive ; or , by a single ; + syntax_s = re.sub("[0-9,;:b\-\s]*", "", selection) # To verify all characters are valid, we remove all them and there should remain nothing + syntax_s = syntax_s.strip() + if syntax_s != "" : # If something remains, it is not valid + showwarning(_("Invalid data"), _("Invalid data for Selection : %s. Aborting \n") % syntax_s) + return False + + if append_prepend == 1 : + pagesSel = prependPages * ["1:-1"] + else : + pagesSel = [] + + + selection = selection.replace(";", ",") + selection = selection.strip() + if selection[-1:] == "," : # remove the trailing , + selection = selection[0:-1] + list1 = selection.split(",") + + for a in list1 : + a = a.strip() + b = a.split(":") + if (len(b) == 1) : + docId_s = "1:" + else : + docId_s = b[0] + ":" + a = b[1] + + if a.count("-") > 0: + list2 = a.split("-") + serie = list(range(int(list2[0]) - 1, int(list2[1]))) + for x in serie : + page_s = docId_s + str(x) + pagesSel = pagesSel + [page_s] + elif a[-1:] == "b" : + if a[0:-1].strip() == "" : + blank_pages_i = 1 + else : + blank_pages_i = int(a[0:-1]) + pagesSel = pagesSel + (["1:-1"] * blank_pages_i) + else : + try : + a = str(int(a) - 1) + pagesSel = pagesSel + [docId_s + a] + except : + alert("Invalid selection ", a) + if append_prepend == 1 : + pagesSel = pagesSel + appendPages * ["1:-1"] + + if ini.booklet > 0 : + step_i = 2 + blankPages = (len(pagesSel) % -4) * -1 + #app.print2(_("Blank pages to be added : %s") % (blankPages) , 1) + + else : + if step_i < 1 : + step_i = 1 + blankPages = (len(pagesSel) % (step_i * -1)) * -1 + + pagesSel += ["1:-1"] * blankPages + + + + totalPages = len(pagesSel) + pgcount = totalPages + + return True + + + def createPageLayout(self, logdata = 1) : + global config, rows_i, columns_i, cells_i, step_i, sections, output, input1, adobe_l + global numfolio,prependPages, appendPages, ref_page, selection + global numPages, blankPages, pagesSel, llx_i, lly_i, urx_i, ury_i, mediabox_l, pgcount + + ar_pages = {} + ar_cahiers = {} + index=0 + last=0 + cells_i = columns_i * rows_i + + + # Create booklets + if ini.booklet > 0 : + + multiple_bkt = int(cells_i / 2) + ini.radioDisp= 1 + + + folios = pgcount / 4 + if numfolio == 0 : # single booklet + numcahiers = 1 + else : # multiple booklets + numcahiers = int(folios / numfolio) + if (folios % numfolio > 0) : + numcahiers = numcahiers + 1 + + # equilibrate booklets size + minfolios = int(folios / numcahiers) + restefolios = folios - (minfolios * numcahiers) + + for k in range(numcahiers) : + if (k < restefolios) : # number of folios in each booklet + ar_cahiers[k] = minfolios + 1 + else : + ar_cahiers[k] = minfolios + + first = last + 1 # num of first page of the booklet + last = first + (ar_cahiers[k] * 4) - 1 # num of the last page of the booklet + + if logdata == 1 : + #app.print2(_( "Booklet %s : pages %s - %s") % (k + 1, first, last), 1) + pass + + bkltPages = (last - first) + 1 # number of pages in the booklet + for i in range (bkltPages // 2) : # page nums for each sheet + if ((i % 2) == 0) : # Page paire à gauche + pg2 = (i + first) + pg1 = (last - i) + else : + pg1 = (i + first) + pg2 = (last - i) + + ar_pages[index] = [pagesSel[pg1 - 1], pagesSel[pg2 - 1]] * multiple_bkt + index += 1 + + else : + while index < (pgcount / step_i) : + start = last + last = last + step_i + pages = [] + for a in range(start, start + cells_i) : + PageX = start + (a % step_i) + if PageX > totalPages : + pages = pages + [-1] + else : + pages = pages + [pagesSel[PageX]] + ar_pages[index] = pages + index += 1 + + + # create layout + ar_layout = [] + if ini.radioDisp == 1 : + for r in range(rows_i) : + r2 = rows_i - (r + 1) # rows are counted from bottom because reference is lower left in pdf so we must invert + for c in range (columns_i) : + ar_layout += [[r2, c]] + elif ini.radioDisp == 2 : + for c in range(columns_i) : + for r in range(rows_i) : + r2 = rows_i - (r + 1) # rows are counted from bottom so we must invert + ar_layout += [[r2, c]] + + # If option "Right to left" has been selected creates inverted layout (which will overwrite the previous one) + + if bool_test(config["options"]["righttoleft"]) == True : + + # create inverted layout + ar_layout = [] + if ini.radioDisp == 1 : + for r in range(rows_i) : + r2 = rows_i - (r + 1) # rows are counted from bottom because reference is lower left in pdf so we must invert + for c in range (columns_i) : + c2 = columns_i - (c + 1) # Invert for right to left option + ar_layout += [[r2, c2]] + elif ini.radioDisp == 2 : + for c in range(columns_i) : + c2 = columns_i - (c + 1) # Invert for right to left optionInvert for right to left option Invert for right to left option + for r in range(rows_i) : + r2 = rows_i - (r + 1) # rows are counted from bottom so we must invert + ar_layout += [[r2, c2]] + + # End of inverted layout + + + # User defined layout + if "radiopreset8" in app.arw and app.arw["radiopreset8"].get_active() == 1 : + + # number of sheets of this layout + sheets = len(ini.imposition) + + # create blank pages if necessary + rest = len(ar_pages) % sheets + + if rest > 0 : + blank = [] + for i in range(len(ar_pages[0])) : + blank.append(["1:-1"]) + for i in range(rest) : + ar_pages[len(ar_pages) + i] = blank + + + # create a copy of ar_pages + + ar_pages1 = {} + for key in ar_pages : + pages1 = ar_pages[key] + temp = [] + for a in pages1 : + temp.append(a) + ar_pages1[key] = temp + + + if sheets == 1 : + userpages = ini.imposition[0] + + for key in ar_pages : + pages = ar_pages[key] + pages1 = ar_pages1[key] + + i = 0 + # we reorder the pages following the order given in userpages + for value in userpages : + if value == "b" : + pages[i] = "1:-1" + else : + row = int(value) - 1 + pages[i] = pages1[row] + i += 1 + ar_pages[key] = pages + + elif sheets == 2 : # more complicated... + + userpagesA = app.imposition[0] + userpagesB = app.imposition[1] + step = len(userpagesA) + + for key in range(0,len(ar_pages),2) : + + pagesA = ar_pages[key] + pages1A = ar_pages1[key] + pagesB = ar_pages[key + 1] + pages1B = ar_pages1[key + 1] + + + i = 0 + # we reorder the pages following the order given in userpages + for value in userpagesA : + if value == "b" : + pagesA[i] = "1:-1" + else : + row = int(value) - 1 + if row < step : + index = pages1A[row] + else : + rowB = row % step + index = pages1B[rowB] + pagesA[i] = index + i += 1 + i = 0 + for value in userpagesB : + if value == "b" : + pagesB[i] = "1:-1" + else : + row = int(value) - 1 + if row < step : + index = pages1A[row] + else : + rowB = row % step + index = pages1B[rowB] + pagesB[i] = index + i += 1 + ar_pages[key] = pagesA + ar_pages[key + 1] = pagesB + + return (ar_pages, ar_layout, ar_cahiers) + + + + def createNewPdf(self, ar_pages, ar_layout, ar_cahiers, outputFile, preview = -1) : + global debug_b, inputFile_a, inputFiles_a, result + global mediabox_l + global outputStream + + + if debug_b == 1 : + logfile_f = open ("log.txt", "wb") + # status + statusTotal_i = len(ar_pages) + statusValue_i = 1 + + + time_s=time.time() + output = PdfFileWriter() + + + + if preview >= 0 : # if this is a preview + outputStream = io.BytesIO() + + else : + try : + outputStream = open(outputFile, "wb") + except : + showwarning(_("File already open"), _("The output file is already opened \n" \ + "probably in Adobe Reader. \n" \ + "Close the file and start again")) + return + + if preview >= 0 : # if this is a preview + output_page_number = preview + 1 + else : + output_page_number = 1 +## # encryption +## if app.permissions_i != -1 and app.password_s != "" : # if permissions or password were present in the file +## output.encrypt("", app.password_s, P = app.permissions_i) # TODO : there may be two passwords (user and owner) + for a in ar_pages : + # create the output sheet + page2 = output.addBlankPage(100,100) + newSheet = page2.getObject() + newSheet[generic.NameObject("/Contents")] = generic.ArrayObject([]) + newSheet.mediaBox.upperRight = mediabox_l # output page size + newSheet.cropBox.upperRight = mediabox_l + # global rotation + if ("options" in config) and ("globalRotation" in config["options"]) : + gr = config["options"]["globalRotation"] + gr_s = gr.strip()[14:] + gr_i = int(gr_s) + if gr_i > 0 : + newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(gr_i) + + i = 0 + ar_data = [] + + + if outputScale != 1 and app.autoscale.get_active() == 1 : + temp1 = "%s 0 0 %s 0 0 cm \n" % (str(outputScale), str(outputScale)) + ar_data.append([temp1]) + + + #Output page transformations + if "output" in config : + OHShift = ini.readmmEntry(config["output"]["htranslate"]) + OVShift = ini.readmmEntry(config["output"]["vtranslate"]) + OScale = ini.readPercentEntry(config["output"]["scale"]) + ORotate = ini.readNumEntry(config["output"]["rotate"]) + + Ovflip = ini.readBoolean(config["output"]["vflip"]) + Ohflip = ini.readBoolean(config["output"]["hflip"]) + + Oxscale = ini.readPercentEntry(config["output"]["xscale"]) + if Oxscale == None : return False + + Oyscale = ini.readPercentEntry(config["output"]["yscale"]) + if Oyscale == None : return False + + + + temp1 = self.calcMatrix2(OHShift, OVShift, + cScale = OScale, + cRotate = ORotate, + vflip = Ovflip, + hflip = Ohflip, + xscale = Oxscale, + yscale = Oyscale, + global_b = True) + ar_data.append([temp1]) + + + + # Transformations defined in ini file + + + if "pages" in config : + pages_a = config["pages"].keys() + if "@" + str(output_page_number) in pages_a : # If the output page presently treated is referenced in [pages] + temp1 = config["pages"]["@" + str(output_page_number)] + transformations = temp1.split(", ") + for name_s in transformations : + if config.has_option(name_s, "globalRotation") : + gr_s = config[name_s.strip()]["globalRotation"] + gr_i = int(gr_s) + if gr_i == 90 : + newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(90) + elif gr_i == 180 : + newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(180) + elif gr_i == 270 : + newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(270) + else : + transform_s = self.calcMatrix(name_s, rows_i, columns_i) + ar_data.append([transform_s]) + + + + + if "output_conditions" in config : + conditions_a = config["output_conditions"].keys() + for line1 in conditions_a : + condition_s = config["output_conditions"][line1] + command_s, filters_s = condition_s.split("=>") + if (eval(command_s)) : + transformations = filters_s.split(", ") + for name_s in transformations : + if "globalRotation" in config[name_s.strip()] : + gr_s = config[name_s.strip()]["globalRotation"] + gr_i = int(gr_s) + if gr_i == 90 : + newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(90) + elif gr_i == 180 : + newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(180) + elif gr_i == 270 : + newSheet[generic.NameObject("/Rotate")] = generic.NumberObject(270) + else : + transform_s = self.calcMatrix(name_s, rows_i, columns_i) + ar_data.append([transform_s]) + + + oString = "" + + # Ready to create the page + for r, c in ar_layout : # We are creating the page in row r and column c + data_x = [] + if ar_pages[0] == [] : # system not yet initialised + return + file_number, page_number = ar_pages[a][i].split(":") + file_number = int(file_number) + page_number = int(page_number) + if (page_number < 0) : + i += 1 + continue # blank page + + + data_x.append("q\n") + # Create the transformation matrix for the page + matrix_s = self.transform(r, c, page_number, output_page_number, file_number) + if matrix_s == False : + return False + data_x.append(matrix_s) + + # Booklets : option "creep" + if ini.booklet > 0 : # This option makes sense only for booklets + creep_f1 = app.readmmEntry(app.arw["creep"]) + if creep_f1 > 0 : + # calculate creep for each booklet (number of folios may vary) + for key, value in ar_cahiers.items() : + creep_f = creep_f1 / value + + # reset to 0 for each booklet + # Starting page of each booklet + start_pages = [0] + mem = 0 + for key, value in ar_cahiers.items() : + page_value = value * 2 # ar_cahiers gives the number of folios, not pages + start_pages.append(page_value + mem) + mem += page_value + + # Calculate creep for a given page + + for start_page in start_pages : + if output_page_number <= start_page : + page_in_booklet = output_page_number - start_page # page number inside a given booklet + Htrans = creep_f * (int(page_in_booklet/2) - (page_in_booklet % 2)) # increment every two pages. + # It is a bit difficult to explain... + # We must get this : + # page -5 => -3 External page + # page -4 => -2 + # page -3 => -2 + # page -2 => -1 + # page -1 => -1 + # page 0 => 0 Internal page + + if c == 0 : + data_x.append(self.calcMatrix2(Htrans , 0)) # shift left + else : + data_x.append(self.calcMatrix2((Htrans * -1), 0)) # shift right + break + + + # scale the page to fit the output sheet, if required + if"autoscale" in config["options"]: + if ini.readBoolean(config["options"]["autoscale"]) == True : + (scaleFactor_f, Htranslate, Vtranslate) = self.CalcAutoScale(file_number, page_number) + matrix1_s = self.calcMatrix2(Htranslate, Vtranslate, Scale = scaleFactor_f) + data_x.append(matrix1_s) + + file_name = inputFiles_a[file_number] + newPage = inputFile_a[file_name].getPage(page_number) + + data_x.append(newPage) + + # Add page number if required + # Choose font + try: + temp1 = newPage['/Resources'].getObject() + if isinstance(temp1, dict) : + temp2 = temp1['/Font'].getObject() + temp3 = temp2.keys() + if '/F1' in temp3 : + font = '/F1' + else : # we get the first font in the list + for k in temp3 : + font = k + break + except : + pass # Not critical. Default font will be used, but it does not show in the preview, that's why it is better to have an available font number. + + + if app.arw["page_numbers"].get_active() == True : + font_size = ini.readIntEntry(app.arw["numbers_font_size"], default = 18) + bottom_margin = ini.readIntEntry(app.arw["numbers_bottom_margin"], default = 20) + start_from = ini.readIntEntry(app.arw["numbers_start_from"]) + position = urx_i / 2 + if start_from <= page_number + 1 : + data_x.append(" q BT %s %d Tf 1 0 0 1 %d %d Tm (%d) Tj ET Q\n" % (font, font_size, position, bottom_margin, page_number + 1)) + data_x.append("Q\n") + if len(ini.delete_rectangle) > 0 : + (x1,y1,w1,h1) = ini.delete_rectangle + data_x.append("q n 1 1 1 rg \n") # Couleur RGB ; 1 1 1 = white; 0,0,0 = black + data_x.append(" n %d %d %d %d re f* Q\n" % (x1,y1,w1,h1)) # rectangle : x, y, width, height + ar_data.append(data_x) + + i += 1 + + aa = urx_i + bb = ury_i + + + datay = [] + for datax in ar_data : + datay += datax + ["\n"] + +## datay += ["q n 10 10 m 10 122 l S \n"] +## datay += [" n 10 10 m 72 10 l S \n"] +## datay += ["Q\n"] + if preview > 0 : + newSheet.mergePage3(datay) # never use slow mode for preview + elif ("slowMode" in app.arw + and app.arw["slowMode"].get_active() == 0) : # normal mode + newSheet.mergePage3(datay) + + else : # slow mode (uses mergePage instead of mergePage3) + + dataz = "" + pages = [] + end_code = "" + for data2 in datay : + if not isinstance(data2, str) : + + pages.append([data2, dataz]) + dataz = "" + else : + dataz += data2 + if not (dataz == "" or len(pages) == 0) : # skip blank pages + + i = len(pages) + pages[i - 1].append(dataz) + + for content in pages : + if len(content) == 3 : + end_code == content[2] + else : + end_code = "" + newSheet.mergeModifiedPage(content[0], content[1], end_code) + + + + + if ( "noCompress" in app.arw + and app.arw["noCompress"].get_active() == 0) : + newSheet.compressContentStreams() + + + if preview == -1 : # if we are creating a real file (not a preview) + message_s = _("Assembling pages: %s ") % (ar_pages[a]) + app.print2( message_s , 1) + app.status.set_text("page " + str(statusValue_i) + " / " + str(statusTotal_i)) + statusValue_i += 1 + output_page_number += 1 + while Gtk.events_pending(): + Gtk.main_iteration() + + time_e=time.time() + + #app.print2(_("Total length : %s ") % (time_e - time_s), 1) + + output.write(outputStream) + if preview == -1 : # if we are creating a real file (not a preview) + outputStream.close() # We must close the file, otherwise it is not possible to see it in the Reader + # TODO : after closing the reader, preview should be automatically updated. + + del output + + if debug_b == 1 : + logfile_f.close() + + # TODO + """ + if preview == -1 : # if we are creating a real file (not a preview) + if app.settings.get_active() == 1 : + app.saveProjectAs("",inputFile + ".ini") + """ + return True + + + + +def printTree(curPage,out) : + + #curPage = source.getPage(page) + keys_a = list(curPage.keys()) + #temp1 = curPage["/Parent"].getObject() + for j in keys_a : + #if j in ["/Parent", "/Rotate", "/MediaBox", "/Type", "/Annots", "/Contents"] : + if j in ["/Parent"] : + continue + temp1 = curPage[j].getObject() + print("======> page " + str(page) + " " + j, file=out) + print(temp1, file=out) + if isinstance(temp1, dict) : + for k in temp1 : + temp2 = temp1[k].getObject() + print(str(k) + " : ", end=' ', file=out) + print(temp2, file=out) + if isinstance(temp2, dict) : + for l in temp2 : + temp3 = temp2[l].getObject() + print(str(l) + " : ", end=' ', file=out) + print(temp3, file=out) + if isinstance(temp3, dict) : + for m in temp3 : + temp4 = temp3[m].getObject() + print(str(m) + " : ", end=' ', file=out) + print(temp4, file=out) + if isinstance(temp4, dict) : + for n in temp4 : + temp5 = temp4[n].getObject() + print(str(n) + " : ", end=' ', file=out) + print(temp5, file=out) + + #out.close() + + +def parseOptions() : + global arg_a + parser = OptionParser() + parser.add_option("-f", "--file", dest="filename", + help="write report to FILE", metavar="FILE") + parser.add_option("-r", "--rows", dest="rows_i", + help="write report to FILE", metavar="FILE") + parser.add_option("-c", "--columns", dest="columns_i", + help="write report to FILE", metavar="FILE") + parser.add_option("-n", "--numfolio", dest="numfolio", + help="write report to FILE", metavar="FILE") + parser.add_option("-b", "--booklet", dest="booklet", + help="write report to FILE", metavar="FILE") + parser.add_option("-a", "--appendPages", dest="appendPages", + help="write report to FILE", metavar="FILE") + parser.add_option("-p", "--prependPages", dest="prependPages", + help="write report to FILE", metavar="FILE") + parser.add_option("-s", "--selection", dest="selection", + help="write report to FILE", metavar="FILE") + parser.add_option("-o", "--output", dest="outputFile", + help="write report to FILE", metavar="FILE") + parser.add_option("-i", "--iniFile", dest="iniFile", + help="write report to FILE", metavar="FILE") + parser.add_option("-e", "--referencePage", dest="referencePage", + help="write report to FILE", metavar="FILE") + + parser.add_option("-q", "--quiet", + action="store_false", dest="verbose", default=True, + help="don't print status messages to stdout") + + (option_v, arg_a) = parser.parse_args() + + +## if None != option_v.iniFile : +## ini_s = option_v.iniFile +## parseIniFile(ini_s) + + +def extractBase() : + """ + extract absolute path to script + @return prog_s : absolute program path + @return pwd_s : current working dir + @return base_s : dirname of prog_s + """ + + # read current dir + prog_s = sys.argv[0] + pwd_s = os.path.abspath(".") + name_s = os.path.basename(prog_s) + _sep_s = '\\' + + # extract program path + # if path starts with \ or x:\ absolute path + if _sep_s == prog_s[0] or \ + (2 < len(prog_s) and \ + ":" == prog_s[1] and + _sep_s == prog_s[2]) : + base_s = os.path.dirname(prog_s) + # if it starts with ./ , relative path + elif 1 < len(prog_s) and \ + "." == prog_s[0] and \ + _sep_s == prog_s[1] : + path_s = os.path.abspath(prog_s) + base_s = os.path.dirname(path_s) + # if it is in the active directory + elif os.path.exists(os.path.join(pwd_s, prog_s)) or \ + os.path.exists(os.path.join(pwd_s, prog_s) + ".exe"): # Necessary if the user starts the program without the extension (maggy, without .exe) + path_s = os.path.join(pwd_s, prog_s) + base_s = os.path.dirname(path_s) + else : + tab_a = os.environ["PATH"].split(":") + limit = len(tab_a) + found = False + for scan in range(limit) : + path_s = os.path.join(tab_a[scan], prog_s) + if os.path.exists(path_s) : + base_s = os.path.dirname(path_s) + found = True + break + if not found : + raise ScriptRt("path to program is undefined") + + # application base import + return(name_s, pwd_s, base_s) + + +def sfp(path) : + # sfp = set full path + return os.path.join(prog_path_u, path) + +def sfp2(file1) : + # sfp2 = set full path, used for temporary directory + try: + return os.path.join(share_path_u, file1) + except : + time.sleep(1) # Sometimes there was a sort of conflict with another thread + return os.path.join(share_path_u, file1) + +def sfp3(file1) : + # sfp3 = set full path, used for config directory + try: + return os.path.join(cfg_path_u, file1) + except : + time.sleep(1) # Sometimes there was a sort of conflict with another thread + +def close_applicationx(self, widget, event=None, mydata=None): + + if Gtk.main_level(): + app.arw["window1"].destroy() + Gtk.main_quit() + else: + sys.exit(0) + + return False + +########################################################################### +# MAIN #################################################################### +########################################################################### + +def main() : + + global PdfShuffler, PDF_Doc + + from pdfbooklet.pdfshuffler_g3 import PdfShuffler, PDF_Doc + + global isExcept + global startup_b + global preview_b + global project_b + global openedProject_u + global areaAllocationW_i + global areaAllocationH_i + + global base_a + global prog_path_u + global temp_path_u + global cfg_path_u + global share_path_u + + global rows_i + global columns_i + global step_i + global outputScale + global outputStream_mem + global mem + + isExcept = False + startup_b = True + preview_b = True + project_b = False + openedProject_u = "" + areaAllocationW_i = 1 + areaAllocationH_i = 1 + rows_i = 1 + columns_i = 2 + step_i = 1 + outputScale = 1 + outputStream_mem = "" + mem = {} + mem["update"] = time.time() + + base_a = extractBase() + prog_path_u = unicode2(base_a[2]) + + + errorLog = sys.argv[0] + ".log" + argv_a = sys.argv + sys.argv = [sys.argv[0]] # remove any parameter because they are not supported by PdfShuffler + if os.path.exists(sfp(errorLog)) : + try : # Sometimes the file may be locked + os.remove(sfp(errorLog)) + except : + pass + + # set directories for Linux and Windows + + if 'linux' in sys.platform : + cfg_path_u = os.path.join(os.environ['HOME'], ".config", "pdfbooklet") + if os.path.isdir(cfg_path_u) == False : + os.mkdir(cfg_path_u) + + share_path_u = "/usr/share/pdfbooklet" + if os.path.isdir(share_path_u) == False : + os.mkdir(share_path_u) + + else: + #TODO : this is not the recommended situation. + cfg_path_u = prog_path_u + share_path_u = prog_path_u + + + + #parseOptions() + + try: + + + global render, app, parser, ini + global inputFiles_a + + render = pdfRender() + parser = myConfigParser() + ini = TxtOnly(render) + + + # command line processing + if len(argv_a) > 2 and argv_a[2].strip() != "" : + arg1 = argv_a[1] + (name,ext) = os.path.splitext(arg1) # determine file type from the extension + # TODO : determine from mimetype for Linux + if ext == ".ini" : + startup_b = False + app = dummy() + ini.openProject2(arg1) + ini.output_page_size(1) + app.pagesTr = copy.deepcopy(config) + if render.parsePageSelection("1-20", 0) : + +## self.readConditions() + ar_pages, ar_layout, ar_cahiers = render.createPageLayout() + if ar_pages != None : + render.createNewPdf(ar_pages, ar_layout, ar_cahiers, "test.pdf", -1) + + return True + + settings = Gtk.Settings.get_default() + settings.props.gtk_button_images = True + app = gtkGui(render) + + app.guiPresetsShow("booklet") + app.resetTransformations(0) + app.resetTransformations2(0) + app.guiPresets(0) + + + + if os.path.isfile(sfp3("pdfbooklet.cfg")) == False : + f1 = open(sfp3("pdfbooklet.cfg"), "w") + f1.close() + ini.parseIniFile(sfp3("pdfbooklet.cfg")) + + + if len(argv_a) > 1 : # a filename has been added in the command line + if len(argv_a[1]) > 0 : # this is not an empty string + arg1 = argv_a[1] + (name,ext) = os.path.splitext(arg1) # determine file type from the extension + # TODO : determine from mimetype for Linux + if ext == ".ini" : # If it is a project + startup_b = False + if not ini.openProject2(arg1) : # if file not found + print ("Unknown file on the command line : " + arg1) + else : + app.arw["previewEntry"].set_text("1") + while Gtk.events_pending(): + Gtk.main_iteration() + app.previewUpdate() + + if len(argv_a) > 2 : + if argv_a[2].strip() == "-r" : + app.go("") # TODO : should be go("", 0) but this creates errors + app.close_application("") + + elif ext == ".pdf" : + inputFiles_a = {} + inputFiles_a[1] = argv_a[1] + ini.loadPdfFiles() + app.selection_s = "" + app.arw["previewEntry"].set_text("1") + app.previewUpdate() + else : + print("Unknown file type in the command line") + + + startup_b = False + +## Gdk.threads_init() +## Gdk.threads_enter() + Gtk.main() +## Gdk.threads_leave() + +## os._exit(0) # required because pdf-shuffler does not close correctly + + + + except : + isExcept = True + excMsg_s = "unexpected exception" + (excType, excValue, excTb) = sys.exc_info() + tb_a = traceback.format_exception(excType, excValue, excTb) + for a in tb_a : + print(a) + + # handle eventual exception + if isExcept : + +## if app.shuffler != None : +## app.shuffler.window.destroy() + if Gtk.main_level(): +## Gtk.gdk.threads_enter() + Gtk.main_quit() +## Gtk.gdk.threads_leave() + #os._exit(0) + sys.exit(1) + else: + sys.exit(1) + + + + +if __name__ == '__main__' : + main() diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfshuffler_g3.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfshuffler_g3.py new file mode 100644 index 0000000..bdf3435 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfshuffler_g3.py @@ -0,0 +1,1315 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from __future__ import print_function +from __future__ import unicode_literals + +# PdfShuffler 0.6.0 Rev 82, modified for Windows compatibility +# See the Class PdfShuffler_Windows_cod" / class PdfShuffler_Linux_code : + +# updated for python 3 and Gtk 3 + +# Version inside pdfBooklet : 3.0.2 + + +""" + + PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and + modification of PDF documents. + Copyright (C) 2008-2012 Konstantinos Poulios + + + This file is part of PdfShuffler. + + PdfShuffler 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" + +import os +import shutil # for file operations like whole directory deletion +import sys # for proccessing of command line args +import stat +import urllib # for parsing filename information passed by DnD +import threading +import tempfile +import glob +from copy import copy +import locale #for multilanguage support +import gettext +import pdfbooklet.elib_intl3 as elib_intl3 +# elib_intl does not work if the strings are unicode +domain = "pdfshuffler" # these lines have no effect in python 3 +locale = "share/locale" + +elib_intl3.install(domain, locale) + +APPNAME = 'PdfShuffler' # PDF-Shuffler, PDFShuffler, pdfshuffler +VERSION = '0.6.0' +WEBSITE = 'http://pdfshuffler.sourceforge.net/' +LICENSE = 'GNU General Public License (GPL) Version 3.' + +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Poppler', '0.18') +from gi.repository import Gtk, Gdk, Poppler +from gi.repository import Pango # for adjusting the text alignment in CellRendererText +from gi.repository import Gio # for inquiring mime types information +from gi.repository import GObject # for using custom signals +##from gi.repository import cairo # Raises the error : 'gi.repository.cairo' object has no attribute 'ImageSurface' +import cairo + +from pdfbooklet.PyPDF2_G.pdf import PdfFileWriter, PdfFileReader + +from pdfbooklet.pdfshuffler_iconview3 import CellRendererImage +GObject.type_register(CellRendererImage) + + +import time + +Gtk.rc_parse("./gtkrc") + +class PdfShuffler: + prefs = { + 'window width': min(700, Gdk.Screen.get_default().get_width() / 2), + 'window height': min(600, Gdk.Screen.get_default().get_height() - 50), + + 'window x': 0, + 'window y': 0, + 'initial thumbnail size': 800, + 'initial zoom level': -3, + } + + MODEL_ROW_INTERN = 1001 + MODEL_ROW_EXTERN = 1002 + TEXT_URI_LIST = 1003 + MODEL_ROW_MOTION = 1004 + TARGETS_IV = [Gtk.TargetEntry.new('MODEL_ROW_INTERN', Gtk.TargetFlags.SAME_WIDGET, MODEL_ROW_INTERN), + Gtk.TargetEntry.new('MODEL_ROW_EXTERN', Gtk.TargetFlags.OTHER_APP, MODEL_ROW_EXTERN), + Gtk.TargetEntry.new('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)] + TARGETS_SW = [Gtk.TargetEntry.new('text/uri-list', 0, TEXT_URI_LIST), + Gtk.TargetEntry.new('MODEL_ROW_EXTERN', Gtk.TargetFlags.OTHER_APP, MODEL_ROW_EXTERN)] + + + + def __init__(self): + + if os.name == "nt" : + self.winux = PdfShuffler_Windows_code() + else : + self.winux = PdfShuffler_Linux_code() + + + # Create the temporary directory + self.tmp_dir = tempfile.mkdtemp("pdfshuffler") + self.selection_start = 0 + os.chmod(self.tmp_dir, stat.S_IRWXO) # TODO il y avait 0700. RWXO est plutôt 777 ? + icon_theme = Gtk.IconTheme.get_default() + # TODO : icontheme +## try: +## Gtk.window_set_default_icon(icon_theme.load_icon("pdfshuffler", 64, 0)) +## except: +## print(_("Can't load icon. Application is not installed correctly.")) + + # Import the user interface file, trying different possible locations + ui_path = '/usr/share/pdfbooklet/data/pdfshuffler_g.glade' + if not os.path.exists(ui_path): + ui_path = '/usr/local/share/pdfbooklet/data/pdfshuffler_g.glade' + + # Windows standard path + if not os.path.exists(ui_path): + if getattr( sys, 'frozen', False ) : # running in a bundle (pyinstaller) + ui_path = os.path.join(sys._MEIPASS, "data/pdfshuffler_g.glade") + else : # running live + ui_path = './data/pdfshuffler_g.glade' + + if not os.path.exists(ui_path): + parent_dir = os.path.dirname( \ + os.path.dirname(os.path.realpath(__file__))) + ui_path = os.path.join(parent_dir, 'data', 'pdfshuffler_g.glade') + + if not os.path.exists(ui_path): + head, tail = os.path.split(parent_dir) + while tail != 'lib' and tail != '': + head, tail = os.path.split(head) + if tail == 'lib': + ui_path = os.path.join(head, 'share', 'pdfbooklet', \ + 'data/pdfshuffler_g.glade') + + self.uiXML = Gtk.Builder() + self.uiXML.add_from_file(ui_path) + self.uiXML.connect_signals(self) + + # Create the main window, and attach delete_event signal to terminating + # the application + self.window = self.uiXML.get_object('main_window') + self.window.set_title(APPNAME) + self.window.set_border_width(0) + self.window.move(self.prefs['window x'], self.prefs['window y']) + self.window.set_default_size(self.prefs['window width'], + self.prefs['window height']) + self.window.connect('delete_event', self.close_application) + + # Create a scrolled window to hold the thumbnails-container + self.sw = self.uiXML.get_object('scrolledwindow') + self.sw.drag_dest_set(Gtk.DestDefaults.MOTION | + Gtk.DestDefaults.HIGHLIGHT | + Gtk.DestDefaults.DROP | + Gtk.DestDefaults.MOTION, + self.TARGETS_SW, + Gdk.DragAction.COPY | + Gdk.DragAction.MOVE) + self.sw.connect('drag_data_received', self.sw_dnd_received_data) + self.sw.connect('button_press_event', self.sw_button_press_event) + self.sw.connect('scroll_event', self.sw_scroll_event) + + # Create an alignment to keep the thumbnails center-aligned + align = Gtk.Alignment.new(0.5, 0.5, 0, 0) # python 3 + self.sw.add_with_viewport(align) + + # Create ListStore model and IconView + self.model = Gtk.ListStore(str, # 0.Text descriptor + GObject.TYPE_PYOBJECT, + # 1.Cached page image + int, # 2.Document number + int, # 3.Page number + float, # 4.Scale + str, # 5.Document filename + int, # 6.Rotation angle + float, # 7.Crop left + float, # 8.Crop right + float, # 9.Crop top + float, # 10.Crop bottom + int, # 11.Page width + int, # 12.Page height + float) # 13.Resampling factor + + self.zoom_set(self.prefs['initial zoom level']) + bar = self.uiXML.get_object('hscale1') + bar.set_value(self.prefs['initial zoom level']) + self.iv_col_width = self.prefs['initial thumbnail size'] + + + + self.iconview = Gtk.IconView(self.model) + self.iconview.set_item_width(self.iv_col_width + 12) + + self.cellthmb = CellRendererImage() +## self.cellthmb = IDRenderer() + self.iconview.pack_start(self.cellthmb, False) + self.iconview.add_attribute(self.cellthmb, "image", 1) + self.iconview.add_attribute(self.cellthmb, "scale", 4) + self.iconview.add_attribute(self.cellthmb, "rotation", 6) + self.iconview.add_attribute(self.cellthmb, "cropL", 7) + self.iconview.add_attribute(self.cellthmb, "cropR", 8) + self.iconview.add_attribute(self.cellthmb, "cropT", 9) + self.iconview.add_attribute(self.cellthmb, "cropB", 10) + self.iconview.add_attribute(self.cellthmb, "width", 11) + self.iconview.add_attribute(self.cellthmb, "height", 12) + self.iconview.add_attribute(self.cellthmb, "resample", 13) + + self.celltxt = Gtk.CellRendererText() + self.celltxt.set_property('width', self.iv_col_width) + self.celltxt.set_property('wrap-width', self.iv_col_width) + self.celltxt.set_property('alignment', Pango.Alignment.CENTER) + self.iconview.pack_start(self.celltxt, False) + self.iconview.set_properties(self.celltxt, text_column=0) + + self.iconview.set_selection_mode(Gtk.SelectionMode.MULTIPLE) + self.iconview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, + self.TARGETS_IV, + Gdk.DragAction.COPY | + Gdk.DragAction.MOVE) + self.iconview.enable_model_drag_dest(self.TARGETS_IV, + Gdk.DragAction.DEFAULT) + self.iconview.connect('drag_begin', self.iv_drag_begin) + self.iconview.connect('drag_data_get', self.iv_dnd_get_data) + self.iconview.connect('drag_data_received', self.iv_dnd_received_data) + self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete) + self.iconview.connect('drag_motion', self.iv_dnd_motion) + self.iconview.connect('drag_leave', self.iv_dnd_leave_end) + self.iconview.connect('drag_end', self.iv_dnd_leave_end) + self.iconview.connect('button_press_event', self.iv_button_press_event) + + align.add(self.iconview) + + # Progress bar + self.progress_bar = self.uiXML.get_object('progressbar') + self.progress_bar_timeout_id = 0 + + # Define window callback function and show window + self.window.connect('size_allocate', self.on_window_size_request) # resize + self.window.connect('key_press_event', self.on_keypress_event ) # keypress + self.window.show_all() + self.progress_bar.hide() + + # Change iconview color background +## style = self.sw.get_style().copy() +## for state in (Gtk.StateType.NORMAL, Gtk.StateType.PRELIGHT, Gtk.StateType.ACTIVE): +## style.base[state] = style.bg[Gtk.StateType.NORMAL] +## self.iconview.set_style(style) + + # Creating the popup menu +## self.popup = Gtk.Menu() +## popup_rotate_right = Gtk.ImageMenuItem(_('_Rotate Right')) +## popup_rotate_left = Gtk.ImageMenuItem(_('Rotate _Left')) +## popup_crop = Gtk.MenuItem(_('C_rop...')) +## popup_delete = Gtk.ImageMenuItem(Gtk.STOCK_DELETE) +## popup_rotate_right.connect('activate', self.rotate_page_right) +## popup_rotate_left.connect('activate', self.rotate_page_left) +## popup_crop.connect('activate', self.crop_page_dialog) +## popup_delete.connect('activate', self.clear_selected) +## popup_rotate_right.show() +## popup_rotate_left.show() +## popup_crop.show() +## popup_delete.show() +## self.popup.append(popup_rotate_right) +## self.popup.append(popup_rotate_left) +## self.popup.append(popup_crop) +## self.popup.append(popup_delete) + + self.popup = self.uiXML.get_object('contextmenu1') + + # Initializing variables + self.export_directory = self.winux.home_dir() + + self.import_directory = self.export_directory + self.nfile = 0 + self.iv_auto_scroll_direction = 0 + self.iv_auto_scroll_timer = None + self.pdfqueue = [] + + GObject.type_register(PDF_Renderer) + GObject.signal_new('update_thumbnail', PDF_Renderer, + GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, + [GObject.TYPE_INT, GObject.TYPE_PYOBJECT, + GObject.TYPE_FLOAT]) + self.rendering_thread = 0 + + self.set_unsaved(False) + + # Importing documents passed as command line arguments + for filename in sys.argv[1:]: + self.add_pdf_pages(filename) + + + + + def render(self): + if self.rendering_thread: + self.rendering_thread.quit = True + self.rendering_thread.join() + #FIXME: the resample=2. factor has to be dynamic when lazy rendering + # is implemented + self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue, 2) + self.rendering_thread.connect('update_thumbnail', self.update_thumbnail) + self.rendering_thread.start() + + if self.progress_bar_timeout_id: + GObject.source_remove(self.progress_bar_timeout_id) + self.progress_bar_timout_id = \ + GObject.timeout_add(50, self.progress_bar_timeout) + + def set_unsaved(self, flag): + self.is_unsaved = flag + GObject.idle_add(self.retitle) + + def retitle(self): + title = '' + if len(self.pdfqueue) == 1: + title += self.pdfqueue[0].filename + elif len(self.pdfqueue) == 0: + title += _("No document") + else: + title += _("Several documents") + if self.is_unsaved: + title += '*' + title += ' - ' + APPNAME + self.window.set_title(title) + + def progress_bar_timeout(self): + cnt_finished = 0 + cnt_all = 0 + for row in self.model: + cnt_all += 1 + if row[1]: + cnt_finished += 1 + fraction = float(cnt_finished)/float(cnt_all) + + self.progress_bar.set_fraction(fraction) + self.progress_bar.set_text(_('Rendering thumbnails... [%(i1)s/%(i2)s]') + % {'i1' : cnt_finished, 'i2' : cnt_all}) + if fraction >= 0.999: + self.progress_bar.hide() + return False +## elif not self.progress_bar.flags() & Gtk.VISIBLE: python 3 : flags unknown + else : + self.progress_bar.show_all() + + + + return True + + def update_thumbnail(self, object, num, thumbnail, resample): + row = self.model[num] + Gdk.threads_enter() + row[13] = resample + row[4] = self.zoom_scale + row[1] = thumbnail + Gdk.threads_leave() + + def on_window_size_request(self, window, event): + """Main Window resize - workaround for autosetting of + iconview cols no.""" + + #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152 + col_num = 9 * window.get_size()[0] \ + / (10 * (self.iv_col_width + self.iconview.get_column_spacing() * 2)) + self.iconview.set_columns(col_num) + + def update_geometry(self, iter): + """Recomputes the width and height of the rotated page and saves + the result in the ListStore""" + + if not self.model.iter_is_valid(iter): + return + + nfile, npage, rotation = self.model.get(iter, 2, 3, 6) + crop = self.model.get(iter, 7, 8, 9, 10) + page = self.pdfqueue[nfile-1].document.get_page(npage-1) + w0, h0 = page.get_size() + + rotation = int(rotation) % 360 + rotation = ((rotation + 45) / 90) * 90 + if rotation == 90 or rotation == 270: + w1, h1 = h0, w0 + else: + w1, h1 = w0, h0 + + self.model.set(iter, 11, w1, 12, h1) + + def reset_iv_width(self, renderer=None): + """Reconfigures the width of the iconview columns""" + + if not self.model.get_iter_first(): #just checking if model is empty + return + + max_w = 10 + int(max(row[4]*row[11]*(1.-row[7]-row[8]) \ + for row in self.model)) + if max_w != self.iv_col_width: + self.iv_col_width = max_w + self.celltxt.set_property('width', self.iv_col_width) + self.celltxt.set_property('wrap-width', self.iv_col_width) + self.iconview.set_item_width(self.iv_col_width + 12) #-1) + self.on_window_size_request(self.window, None) + + def on_keypress_event(self, widget, event): + """Keypress events in Main Window""" + + #keyname = Gdk.keyval_name(event.keyval) + if event.keyval == 65535: # Delete keystroke + self.clear_selected() + + def close_application(self, widget, event=None, data=None): + """Termination""" + + try : + if self.rendering_thread: + self.rendering_thread.quit = True + self.rendering_thread.join() + except : + pass # PdfShuffler may be already closed + + if os.path.isdir(self.tmp_dir): + self.winux.remove_temp_dir(self.tmp_dir) + + if Gtk.main_level(): + Gtk.main_quit() + else: + sys.exit(0) + return False + + def add_pdf_pages(self, filename, + firstpage=None, lastpage=None, + angle=0, crop=[0.,0.,0.,0.]): + """Add pages of a pdf document to the model""" + + res = False + # Check if the document has already been loaded + pdfdoc = None + for it_pdfdoc in self.pdfqueue: + if self.winux.check_same_file(filename, it_pdfdoc) == True : + pdfdoc = it_pdfdoc + break + + if not pdfdoc: + pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir) + self.import_directory = os.path.split(filename)[0] + self.export_directory = self.import_directory + if pdfdoc.nfile != 0 and pdfdoc != []: + self.nfile = pdfdoc.nfile + self.pdfqueue.append(pdfdoc) + else: + return res + + n_start = 1 + n_end = pdfdoc.npage + if firstpage: + n_start = min(n_end, max(1, firstpage)) + if lastpage: + n_end = max(n_start, min(n_end, lastpage)) + + for npage in range(n_start, n_end + 1): + descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)]) + page = pdfdoc.document.get_page(npage-1) + w, h = page.get_size() + iter = self.model.append((descriptor, # 0 + None, # 1 + pdfdoc.nfile, # 2 + npage, # 3 + self.zoom_scale, # 4 + pdfdoc.filename, # 5 + angle, # 6 + crop[0],crop[1], # 7-8 + crop[2],crop[3], # 9-10 + w,h, # 11-12 + 2. )) # 13 FIXME + self.update_geometry(iter) + res = True + + self.reset_iv_width() + GObject.idle_add(self.retitle) + if res: + GObject.idle_add(self.render) + return res + + def choose_export_pdf_name(self, widget=None, only_selected=False): + """Handles choosing a name for exporting """ + + chooser = Gtk.FileChooserDialog(title=_('Export ...'), + action=Gtk.FileChooserAction.SAVE, + buttons=(Gtk.STOCK_CANCEL, + Gtk.ResponseType.CANCEL, + Gtk.STOCK_SAVE, + Gtk.ResponseType.OK)) + chooser.set_do_overwrite_confirmation(True) + chooser.set_current_folder(self.export_directory) + filter_pdf = Gtk.FileFilter() + filter_pdf.set_name(_('PDF files')) + filter_pdf.add_mime_type('application/pdf') + filter_pdf.add_pattern('*.pdf') + chooser.add_filter(filter_pdf) + + filter_all = Gtk.FileFilter() + filter_all.set_name(_('All files')) + filter_all.add_pattern('*') + chooser.add_filter(filter_all) + + while True: + response = chooser.run() + if response == Gtk.ResponseType.OK: + file_out = chooser.get_filename() + (path, shortname) = os.path.split(file_out) + (shortname, ext) = os.path.splitext(shortname) + if ext.lower() != '.pdf': + file_out = file_out + '.pdf' + try: + self.export_to_file(file_out, only_selected) + self.export_directory = path + self.set_unsaved(False) + except Exception as e: + chooser.destroy() + error_msg_dlg = Gtk.MessageDialog(None, + Gtk.DialogFlags.MODAL, + Gtk.MessageType.WARNING, + Gtk.ButtonsType.CLOSE, + str(e)) + response = error_msg_dlg.run() + if response == Gtk.ResponseType.OK: + error_msg_dlg.destroy() + return + break + chooser.destroy() + + def export_to_file(self, file_out, only_selected=False): + """Export to file""" + + selection = self.iconview.get_selected_items() + pdf_output = PdfFileWriter() + pdf_input = [] + for pdfdoc in self.pdfqueue: + pdfdoc_inp = PdfFileReader(open(pdfdoc.copyname, 'rb')) + if pdfdoc_inp.getIsEncrypted(): + try: # Workaround for lp:#355479 + stat = pdfdoc_inp.decrypt('') + except: + stat = 0 + if (stat!=1): + errmsg = _('File %s is encrypted.\n' + 'Support for encrypted files has not been implemented yet.\n' + 'File export failed.') % pdfdoc.filename + raise Exception(errmsg) + #FIXME + #else + # ask for password and decrypt file + pdf_input.append(pdfdoc_inp) + + for row in self.model: + + if only_selected and row.path not in selection: + continue + + # add pages from input to output document + nfile = row[2] + npage = row[3] + if npage == -1 : + pdf_output.addBlankPage() + continue + current_page = copy(pdf_input[nfile-1].getPage(npage-1)) + angle = row[6] + angle0 = current_page.get("/Rotate",0) + crop = [row[7],row[8],row[9],row[10]] + if angle != 0: + current_page.rotateClockwise(angle) + if crop != [0.,0.,0.,0.]: + rotate_times = (((angle + angle0) % 360 + 45) / 90) % 4 + crop_init = crop + if rotate_times != 0: + perm = [0,2,1,3] + for it in range(rotate_times): + perm.append(perm.pop(0)) + perm.insert(1,perm.pop(2)) + crop = [crop_init[perm[side]] for side in range(4)] + #(x1, y1) = current_page.cropBox.lowerLeft + #(x2, y2) = current_page.cropBox.upperRight + (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft] + (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight] + x1_new = int(x1 + (x2-x1) * crop[0]) + x2_new = int(x2 - (x2-x1) * crop[1]) + y1_new = int(y1 + (y2-y1) * crop[3]) + y2_new = int(y2 - (y2-y1) * crop[2]) + #current_page.cropBox.lowerLeft = (x1_new, y1_new) + #current_page.cropBox.upperRight = (x2_new, y2_new) + current_page.mediaBox.lowerLeft = (x1_new, y1_new) + current_page.mediaBox.upperRight = (x2_new, y2_new) + + pdf_output.addPage(current_page) + + # finally, write "output" to document-output.pdf + pdf_output.write(open(file_out, 'wb')) + + def on_action_add_doc_activate(self, widget, data=None): + """Import doc""" + + chooser = Gtk.FileChooserDialog(title=_('Import...'), + action=Gtk.FileChooserAction.OPEN, + buttons=(Gtk.STOCK_CANCEL, + Gtk.ResponseType.CANCEL, + Gtk.STOCK_OPEN, + Gtk.ResponseType.OK)) + if self.import_directory : + chooser.set_current_folder(self.import_directory) + chooser.set_select_multiple(True) + + filter_all = Gtk.FileFilter() + filter_all.set_name(_('All files')) + filter_all.add_pattern('*') + chooser.add_filter(filter_all) + + filter_pdf = Gtk.FileFilter() + filter_pdf.set_name(_('PDF files')) + filter_pdf.add_mime_type('application/pdf') + filter_pdf.add_pattern('*.pdf') + chooser.add_filter(filter_pdf) + chooser.set_filter(filter_pdf) + + response = chooser.run() + if response == Gtk.ResponseType.OK: + for filename in chooser.get_filenames(): + #filename = str(filename,"utf-8") Unsupported in python 3 # convert utf-8 to unicode for internal use + if os.path.isfile(filename): + # FIXME +## f = Gio.File(filename) # ££ python 3 +## f_info = f.query_info('standard::content-type') +## mime_type = f_info.get_content_type() +## expected_mime_type = pdf_mime_type +## +## if mime_type == expected_mime_type : + self.add_pdf_pages(filename) +## elif mime_type[:34] == 'application/vnd.oasis.opendocument': +## print((_('OpenDocument not supported yet!'))) +## elif mime_type[:5] == 'image': +## print((_('Image file not supported yet!'))) +## else: +## print((_('File type not supported!'))) + else: + print((_('File %s does not exist') % filename)) + elif response == Gtk.RESPONSE_CANCEL: + print((_('Closed, no files selected'))) + chooser.destroy() + GObject.idle_add(self.retitle) + + def clear_selected(self, button=None): + """Removes the selected elements in the IconView""" + + model = self.iconview.get_model() + selection = self.iconview.get_selected_items() + if selection: + selection.sort(reverse=True) + self.set_unsaved(True) + for path in selection: + iter = model.get_iter(path) + model.remove(iter) + path = selection[-1] + self.iconview.select_path(path) + if not self.iconview.path_is_selected(path): + if len(model) > 0: # select the last row + row = model[-1] + path = row.path + self.iconview.select_path(path) + self.iconview.grab_focus() + + def add_blank_page(self, menu=None, num_blank_pages=1): + action = "" + if menu != None : + name = Gtk.Buildable.get_name(menu) + num_blank_pages = int(name[-1]) + action = name[0:5] + model = self.iconview.get_model() + selection = self.iconview.get_selected_items() + if selection: + selection.sort(reverse=True) + self.set_unsaved(True) + path = selection[0] + iter = model.get_iter(path) + descriptor = 'Blank' + w, h = self.model.get(iter, 11, 12) + + row =(descriptor, # 0 + None, # 1 + 1, # 2 + -1, # 3 + self.zoom_scale, # 4 + "", # 5 + 0, # 6 + 0,0, # 7-8 + 0,0, # 9-10 + w,h, # 11-12 + 2. ) # 13 FIXME + for i in range(num_blank_pages) : + if action == "befor" : + self.model.insert_before(iter, row) + else : + self.model.insert_after(iter, row) + + + def iv_drag_begin(self, iconview, context): + """Sets custom icon on drag begin for multiple items selected""" + + global shuffler_selection_a + #if len(iconview.get_selected_items()) > 1: + if len(shuffler_selection_a) > 0 : + iconview.stop_emission('drag_begin') +## context.set_icon_stock(Gtk.STOCK_DND_MULTIPLE, 0, 0) + for a in shuffler_selection_a : + iconview.select_path(a) + + def iv_dnd_get_data(self, iconview, context, + selection_data, target_id, etime): + """Handles requests for data by drag and drop in iconview""" + + global shuffler_selection_a + model = iconview.get_model() + if len(shuffler_selection_a) > 1 : + selection = shuffler_selection_a + else : + selection = self.iconview.get_selected_items() + selection.sort(key=lambda x: x[0]) + data = [] + target_s = str(selection_data.get_target()) + for path in selection: + if target_s == 'MODEL_ROW_INTERN': + data.append(str(path[0])) + elif target_s == 'MODEL_ROW_EXTERN': + iter = model.get_iter(path) + nfile, npage, angle = model.get(iter, 2, 3, 6) + crop = model.get(iter, 7, 8, 9, 10) + pdfdoc = self.pdfqueue[nfile - 1] + data.append('\n'.join([pdfdoc.filename, + str(npage), + str(angle)] + + [str(side) for side in crop])) + if data: + data = bytes('\n;\n'.join(data), "utf-8") + selection_data.set(selection_data.get_target(), 8, data) + + def iv_dnd_received_data(self, iconview, context, x, y, + selection_data, target_id, etime): + """Handles received data by drag and drop in iconview""" + + model = iconview.get_model() + data = selection_data.get_data().decode("utf-8") + if data: + data = data.split('\n;\n') + drop_info = iconview.get_dest_item_at_pos(x, y) + iter_to = None + if drop_info: + path, position = drop_info + ref_to = Gtk.TreeRowReference.new(model,path) + else: + position = Gtk.IconViewDropPosition.DROP_RIGHT + if len(model) > 0: #find the iterator of the last row + row = model[-1] + path = row.path + ref_to = Gtk.TreeRowReference(model,path) + if ref_to: + before = (position == Gtk.IconViewDropPosition.DROP_LEFT + or position == Gtk.IconViewDropPosition.DROP_ABOVE) + #if target_id == self.MODEL_ROW_INTERN: + if str(selection_data.get_target()) == 'MODEL_ROW_INTERN': + if before: + data.sort(key=int) + else: + data.sort(key=int,reverse=True) + data2 = [] + for data1 in data : + data2.append(Gtk.TreePath.new_from_string(data1)) + ref_from_list = [Gtk.TreeRowReference.new(model,path) + for path in data2] + for ref_from in ref_from_list: + path = ref_to.get_path() + iter_to = model.get_iter(path) + path = ref_from.get_path() + iter_from = model.get_iter(path) + row = model[iter_from] + row_data = [] + for a in row : + row_data.append(a) + + if before: + iter_new = model.insert_before(iter_to, row_data) + else: + iter_new = model.insert_after(iter_to, row_data) + + + if context.get_selected_action() == Gdk.DragAction.MOVE: + for ref_from in ref_from_list: + path = ref_from.get_path() + iter_from = model.get_iter(path) + model.remove(iter_from) + + + + #elif target_id == self.MODEL_ROW_EXTERN: + elif selection_data.target == 'MODEL_ROW_EXTERN': + if not before: + data.reverse() + while data: + tmp = data.pop(0).split('\n') + filename = tmp[0] + npage, angle = [int(k) for k in tmp[1:3]] + crop = [float(side) for side in tmp[3:7]] + if self.add_pdf_pages(filename, npage, npage, + angle, crop): + if len(model) > 0: + path = ref_to.get_path() + iter_to = model.get_iter(path) + row = model[-1] #the last row + path = row.path + iter_from = model.get_iter(path) + if before: + model.move_before(iter_from, iter_to) + else: + model.move_after(iter_from, iter_to) + if context.action == Gdk.ACTION_MOVE: + context.finish(True, True, etime) + + def iv_dnd_data_delete(self, widget, context): + """Deletes dnd items after a successful move operation""" + + model = self.iconview.get_model() + selection = self.iconview.get_selected_items() + ref_del_list = [Gtk.TreeRowReference(model,path) for path in selection] + for ref_del in ref_del_list: + path = ref_del.get_path() + iter = model.get_iter(path) + model.remove(iter) + + def iv_dnd_motion(self, iconview, context, x, y, etime): + """Handles the drag-motion signal in order to auto-scroll the view""" + + autoscroll_area = 40 + sw_vadj = self.sw.get_vadjustment() + sw_height = self.sw.get_allocation().height + if y -sw_vadj.get_value() < autoscroll_area: + if not self.iv_auto_scroll_timer: + self.iv_auto_scroll_direction = Gtk.DIR_UP + self.iv_auto_scroll_timer = GObject.timeout_add(150, + self.iv_auto_scroll) + elif y -sw_vadj.get_value() > sw_height - autoscroll_area: + if not self.iv_auto_scroll_timer: + self.iv_auto_scroll_direction = Gtk.DIR_DOWN + self.iv_auto_scroll_timer = GObject.timeout_add(150, + self.iv_auto_scroll) + elif self.iv_auto_scroll_timer: + GObject.source_remove(self.iv_auto_scroll_timer) + self.iv_auto_scroll_timer = None + + def iv_dnd_leave_end(self, widget, context, ignored=None): + """Ends the auto-scroll during DND""" + + if self.iv_auto_scroll_timer: + GObject.source_remove(self.iv_auto_scroll_timer) + self.iv_auto_scroll_timer = None + + def iv_auto_scroll(self): + """Timeout routine for auto-scroll""" + + sw_vadj = self.sw.get_vadjustment() + sw_vpos = sw_vadj.get_value() + if self.iv_auto_scroll_direction == Gtk.DIR_UP: + sw_vpos -= sw_vadj.step_increment + sw_vadj.set_value(max(sw_vpos, sw_vadj.lower)) + elif self.iv_auto_scroll_direction == Gtk.DIR_DOWN: + sw_vpos += sw_vadj.step_increment + sw_vadj.set_value(min(sw_vpos, sw_vadj.upper - sw_vadj.page_size)) + return True #call me again + + def iv_button_press_event(self, iconview, event): + """Manages mouse clicks on the iconview""" + + x = int(event.x) + y = int(event.y) + path = iconview.get_path_at_pos(x, y) + if path == None : + return + #print event.button + if event.button == 1: # Left button + global shuffler_selection_a + shuffler_selection_a = [] + time = event.time + selection = iconview.get_selected_items() + if path: + if path in selection: + # Record the selection + shuffler_selection_a = selection + + + if event.button == 3: + time = event.time + selection = iconview.get_selected_items() + if path: + if path not in selection: + iconview.unselect_all() + iconview.select_path(path) + iconview.grab_focus() + self.popup.popup(None, None, None, None, event.button, time) + return 1 + elif event.state & Gdk.ModifierType.SHIFT_MASK : + first_selection = self.selection_start + last_selection = path[0] + if last_selection > first_selection : + step = 1 + last_selection += 1 + else : + step = -1 + last_selection -= 1 + for a in range(first_selection,last_selection, step) : + iconview.select_path(Gtk.TreePath(a)) + + + return True + else : + self.selection_start = path[0] + + + + + + def sw_dnd_received_data(self, scrolledwindow, context, x, y, + selection_data, target_id, etime): + """Handles received data by drag and drop in scrolledwindow""" + + data = selection_data.data + if target_id == self.MODEL_ROW_EXTERN: + self.model + if data: + data = data.split('\n;\n') + while data: + tmp = data.pop(0).split('\n') + filename = tmp[0] + npage, angle = [int(k) for k in tmp[1:3]] + crop = [float(side) for side in tmp[3:7]] + if self.add_pdf_pages(filename, npage, npage, angle, crop): + if context.action == Gdk.ACTION_MOVE: + context.finish(True, True, etime) + elif target_id == self.TEXT_URI_LIST: + uri = data.strip() + uri_splitted = uri.split() # we may have more than one file dropped + for uri in uri_splitted: + filename = self.get_file_path_from_dnd_dropped_uri(uri) + if os.path.isfile(filename): # is it file? + self.add_pdf_pages(filename) + + def sw_button_press_event(self, scrolledwindow, event): + """Unselects all items in iconview on mouse click in scrolledwindow""" + + if event.button == 1: + self.iconview.unselect_all() + + def sw_scroll_event(self, scrolledwindow, event): + """Manages mouse scroll events in scrolledwindow""" + + if event.state & Gdk.CONTROL_MASK: + if event.direction == Gdk.SCROLL_UP: + self.zoom_change(1) + return 1 + elif event.direction == Gdk.SCROLL_DOWN: + self.zoom_change(-1) + return 1 + + def zoom_set(self, level): + """Sets the zoom level""" + self.zoom_level = max(min(level, 5), -24) + self.zoom_scale = 1.1 ** self.zoom_level + for row in self.model: + row[4] = self.zoom_scale + self.reset_iv_width() + + def zoom_change(self, step=5): + """Modifies the zoom level""" + bar = self.uiXML.get_object('hscale1') + bar.set_value(self.zoom_level + step) + self.zoom_set(self.zoom_level + step) + + def zoom_in(self, widget=None): + """Increases the zoom level by 5 steps""" + self.zoom_change(5) + + def zoom_out(self, widget=None, step=5): + """Reduces the zoom level by 5 steps""" + self.zoom_change(-5) + + def zoom_bar(self,widget,a=None, b=None): + """Modifies the zoom level with the slider""" + zoom_scale = widget.get_value() + self.zoom_set(zoom_scale) + + + + def get_file_path_from_dnd_dropped_uri(self, uri): + """Extracts the path from an uri""" + + path = urllib.request.url2pathname(uri) # escape special chars + path = path.strip('\r\n\x00') # remove \r\n and NULL + + # get the path to file + if path.startswith('file:\\\\\\'): # windows + path = path[8:] # 8 is len('file:///') + elif path.startswith('file://'): # nautilus, rox + path = path[7:] # 7 is len('file://') + elif path.startswith('file:'): # xffm + path = path[5:] # 5 is len('file:') + return path + + def rotate_page_right(self, widget, data=None): + self.rotate_page(90) + + def rotate_page_left(self, widget, data=None): + self.rotate_page(-90) + + def rotate_page(self, angle): + """Rotates the selected page in the IconView""" + + model = self.iconview.get_model() + selection = self.iconview.get_selected_items() + if len(selection) > 0: + self.set_unsaved(True) + rotate_times = (((-angle) % 360 + 45) / 90) % 4 + if rotate_times is not 0: + for path in selection: + iter = model.get_iter(path) + nfile = model.get_value(iter, 2) + npage = model.get_value(iter, 3) + + crop = [0.,0.,0.,0.] + perm = [0,2,1,3] + for it in range(int(rotate_times)): + perm.append(perm.pop(0)) + perm.insert(1,perm.pop(2)) + crop = [model.get_value(iter, 7 + perm[side]) for side in range(4)] + for side in range(4): + model.set_value(iter, 7 + side, crop[side]) + + new_angle = model.get_value(iter, 6) + int(angle) + new_angle = new_angle % 360 + model.set_value(iter, 6, new_angle) + self.update_geometry(iter) + self.reset_iv_width() + + def crop_page_dialog(self, widget): + """Opens a dialog box to define margins for page cropping""" + + sides = ('L', 'R', 'T', 'B') + side_names = {'L':_('Left'), 'R':_('Right'), + 'T':_('Top'), 'B':_('Bottom') } + opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' } + + def set_crop_value(spinbutton, side): + opp_side = opposite_sides[side] + pos = sides.index(opp_side) + adj = spin_list[pos].get_adjustment() + adj.set_upper(99.0 - spinbutton.get_value()) + + model = self.iconview.get_model() + selection = self.iconview.get_selected_items() + + crop = [0.,0.,0.,0.] + if selection: + path = selection[0] + pos = model.get_iter(path) + crop = [model.get_value(pos, 7 + side) for side in range(4)] + + + dialog = Gtk.Dialog(title=(_('Crop Selected Pages')), + parent=self.window, + flags=Gtk.DialogFlags.MODAL, + buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OK, Gtk.ResponseType.OK)) + dialog.set_size_request(340, 250) + dialog.set_default_response(Gtk.ResponseType.OK) + + frame = Gtk.Frame() + frame.set_label(_('Crop Margins')) + dialog.vbox.pack_start(frame, False, False, 20) + + vbox = Gtk.VBox(False, 0) + frame.add(vbox) + + spin_list = [] + units = 2 * [_('% of width')] + 2 * [_('% of height')] + for side in sides: + hbox = Gtk.HBox(True, 0) + vbox.pack_start(hbox, False, False, 5) + + label = Gtk.Label(side_names[side]) + label.set_alignment(0, 0.0) + hbox.pack_start(label, True, True, 20) + + adj = Gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0) +## spin = Gtk.SpinButton(adj, 0, 1) + spin = Gtk.SpinButton() + spin.set_adjustment(adj) + spin.set_activates_default(True) + spin.connect('value-changed', set_crop_value, side) + spin_list.append(spin) + hbox.pack_start(spin, False, False, 30) + + label = Gtk.Label(units.pop(0)) + label.set_alignment(0, 0.0) + hbox.pack_start(label, True, True, 0) + + dialog.show_all() + result = dialog.run() + + if result == Gtk.ResponseType.OK: + modified = False + crop = [spin.get_value()/100. for spin in spin_list] + for path in selection: + pos = model.get_iter(path) + for it in range(4): + old_val = model.get_value(pos, 7 + it) + model.set_value(pos, 7 + it, crop[it]) + if crop[it] != old_val: + modified = True + self.update_geometry(pos) + if modified: + self.set_unsaved(True) + self.reset_iv_width() + elif result == Gtk.ResponseType.CANCEL: + print((_('Dialog closed'))) + dialog.destroy() + + def about_dialog(self, widget, data=None): + about_dialog = Gtk.AboutDialog() + try: + about_dialog.set_transient_for(self.window) + about_dialog.set_modal(True) + except: + pass + # FIXME + about_dialog.set_name(APPNAME) + about_dialog.set_version(VERSION) + about_dialog.set_comments(_( + '%s is a tool for rearranging and modifying PDF files. ' \ + 'Developed using GTK+ and Python') % APPNAME) + about_dialog.set_authors(['Konstantinos Poulios',]) + about_dialog.set_website_label(WEBSITE) + about_dialog.set_logo_icon_name('pdfshuffler') + about_dialog.set_license(LICENSE) + about_dialog.connect('response', lambda w, *args: w.destroy()) + about_dialog.connect('delete_event', lambda w, *args: w.destroy()) + about_dialog.show_all() + + + +class PDF_Doc: + """Class handling PDF documents""" + + def __init__(self, filename, nfile, tmp_dir): + + self.filename = os.path.abspath(filename) + (self.path, self.shortname) = os.path.split(self.filename) + (self.shortname, self.ext) = os.path.splitext(self.shortname) +## f = Gio.File +## mime_type = f.query_info('standard::content-type').get_content_type() +## expected_mime_type = pdf_mime_type +## +## if mime_type == expected_mime_type: + if 1 == 1 : + self.nfile = nfile + 1 + self.mtime = os.path.getmtime(filename) + self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile + + self.shortname + '.pdf') + shutil.copy(self.filename, self.copyname) + self.document = Poppler.Document.new_from_file (file_prefix + self.copyname, None) + self.npage = self.document.get_n_pages() + else: + self.nfile = 0 + self.npage = 0 + + + +class PDF_Renderer(threading.Thread, GObject.GObject): + + def __init__(self, model, pdfqueue, resample=1.): + + threading.Thread.__init__(self) + GObject.GObject.__init__(self) + self.model = model + self.pdfqueue = pdfqueue + self.resample = resample + self.quit = False + + def run(self): + for idx, row in enumerate(self.model): + if self.quit: + return + if not row[1]: + try: + nfile = row[2] + npage = row[3] + pdfdoc = self.pdfqueue[nfile - 1] + page = pdfdoc.document.get_page(npage-1) + w, h = page.get_size() + thumbnail = cairo.ImageSurface(cairo.FORMAT_ARGB32, + int(w/self.resample), + int(h/self.resample)) +## thumbnail1 = cairo.image_surface_create() +## thumbnail = thumbnail1(1, +## int(w/self.resample), +## int(h/self.resample)) + + + cr = cairo.Context(thumbnail) + if self.resample != 1.: + cr.scale(1./self.resample, 1./self.resample) + page.render(cr) + time.sleep(0.003) + GObject.idle_add(self.emit,'update_thumbnail', + idx, thumbnail, self.resample, + priority=GObject.PRIORITY_LOW) + except Exception as e: + print(e) + + +class PdfShuffler_Linux_code : + def __init__(self): + + global pdf_mime_type, file_prefix + pdf_mime_type = "application/pdf" + file_prefix = 'file://' + + def home_dir(self): + return os.getenv('HOME') + + + def remove_temp_dir(self, tmp_dir): + shutil.rmtree(tmp_dir) + + def check_same_file(self, filename, it_pdfdoc): + + if os.path.isfile(it_pdfdoc.filename) and \ + os.path.samefile(filename, it_pdfdoc.filename) and \ + os.path.getmtime(filename) is it_pdfdoc.mtime: + return True + else : + return False + + +class PdfShuffler_Windows_code : + def __init__(self): + global _winreg, pdf_mime_type, file_prefix + +## import winreg python 27 + pdf_mime_type = ".pdf" + file_prefix = 'file:///' + + + + def home_dir(self) : + return "" # python 3 + global _winreg + regKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, + 'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders') + myDocuments = _winreg.QueryValueEx(regKey, 'Personal')[0] + return myDocuments + + def remove_temp_dir(self, tmp_dir): + + # ============= Python-poppler for Windows bug workaround ============ + # python-poppler does not "release" the file and only the files of previous sessions can be deleted + # Get the list of all pdf-shuffler temporary dirs + temp_dir_root = os.path.split(tmp_dir)[0] + shuffler_dirs = glob.glob(temp_dir_root + "/tmp??????pdfshuffler") + # delete if possible + for directory in shuffler_dirs : + try : + shutil.rmtree(directory) + except : + pass + + def check_same_file(self, filename, it_pdfdoc) : + # The samefile method does not exist in Windows versions of Python + if os.path.isfile(it_pdfdoc.filename) and \ + filename == it_pdfdoc.filename and \ + os.path.getmtime(filename) is it_pdfdoc.mtime: + return True + else : + return False + +def main(): + """This function starts PdfShuffler""" + #Gdk.threads_init() # This line hangs the program when the user tries to move the window + GObject.threads_init() + #Gdk.threads_enter() # This line was is necessary in Windows. Does not seem to be now + PdfShuffler() + Gtk.init() + Gtk.main() + + +if __name__ == '__main__': + + main() + diff --git a/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfshuffler_iconview3.py b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfshuffler_iconview3.py new file mode 100644 index 0000000..cebe0dc --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/lib/python3/dist-packages/pdfbooklet/pdfshuffler_iconview3.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" + + PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and + modification of PDF documents. + Copyright (C) 2008-2012 Konstantinos Poulios + + + This file is part of PdfShuffler. + + PdfShuffler 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +""" + +from gi.repository import Gtk, GObject +from gi.repository import cairo + +from math import pi as M_PI + +class CellRendererImage(Gtk.CellRenderer): + __gproperties__ = { + "image": (GObject.TYPE_PYOBJECT, "Image", "Image", + GObject.PARAM_READWRITE), + "width": (GObject.TYPE_FLOAT, "Width", "Width", + 0., 1.e4, 0., GObject.PARAM_READWRITE), + "height": (GObject.TYPE_FLOAT, "Height", "Height", + 0., 1.e4, 0., GObject.PARAM_READWRITE), + "rotation": (GObject.TYPE_INT, "Rotation", "Rotation", + 0, 360, 0, GObject.PARAM_READWRITE), + "scale": (GObject.TYPE_FLOAT, "Scale", "Scale", + 0.01, 100., 1., GObject.PARAM_READWRITE), + "resample": (GObject.TYPE_FLOAT, + "Resample", "Resample Coefficient", + 1., 100., 1., GObject.PARAM_READWRITE), + "cropL": (GObject.TYPE_FLOAT, "CropL", "CropL", + 0., 1., 0., GObject.PARAM_READWRITE), + "cropR": (GObject.TYPE_FLOAT, "CropR", "CropR", + 0., 1., 0., GObject.PARAM_READWRITE), + "cropT": (GObject.TYPE_FLOAT, "CropT", "CropT", + 0., 1., 0., GObject.PARAM_READWRITE), + "cropB": (GObject.TYPE_FLOAT, "CropB", "CropB", + 0., 1., 0., GObject.PARAM_READWRITE), + } + + def __init__(self): +## self.__GObject_init__() Original code modified in the line below + Gtk.CellRendererText.__init__(self) + GObject.GObject.__init__(self) + self.th1 = 2. # border thickness + self.th2 = 3. # shadow thickness + + def get_geometry(self): + + rotation = int(self.rotation) % 360 + rotation = ((rotation) / 90) * 90 + if not self.image: + w0 = w1 = self.width / self.resample + h0 = h1 = self.height / self.resample + else: + w0 = self.image.get_width() + h0 = self.image.get_height() + if rotation == 90 or rotation == 270: + w1, h1 = h0, w0 + else: + w1, h1 = w0, h0 + + x = self.cropL * w1 + y = self.cropT * h1 + + scale = self.resample * self.scale + w2 = int(scale * (1. - self.cropL - self.cropR) * w1) + h2 = int(scale * (1. - self.cropT - self.cropB) * h1) + + return w0,h0,w1,h1,w2,h2,rotation + + def do_set_property(self, pspec, value): + setattr(self, pspec.name, value) + + def do_get_property(self, pspec): + return getattr(self, pspec.name) + + def do_render(self, window, widget, cell_area, \ + expose_area, flags): + if not self.image: + return + + w0,h0,w1,h1,w2,h2,rotation = self.get_geometry() + th = int(2*self.th1+self.th2) + w = w2 + th + h = h2 + th + + x = cell_area.x + y = cell_area.y + if cell_area and w > 0 and h > 0: + x += self.get_property('xalign') * \ + (cell_area.width - w - self.get_property('xpad')) + y += self.get_property('yalign') * \ + (cell_area.height - h - self.get_property('ypad')) + + cr = window # cr = window.cairo_create() + cr.translate(x,y) + + x = self.cropL * w1 + y = self.cropT * h1 + + #shadow + cr.set_source_rgb(0.5, 0.5, 1) + cr.rectangle(th, th, w2, h2) + cr.fill() + + #border + cr.set_source_rgb(0, 0, 0) + cr.rectangle(0, 0, w2+2*self.th1, h2+2*self.th1) + cr.fill() + + #image + cr.set_source_rgb(1, 1, 1) + cr.rectangle(self.th1, self.th1, w2, h2) + cr.fill_preserve() + cr.clip() + + cr.translate(self.th1,self.th1) + scale = self.resample * self.scale + cr.scale(scale, scale) + cr.translate(-x,-y) + if rotation > 0: + cr.translate(w1/2,h1/2) + cr.rotate(rotation * M_PI / 180) + cr.translate(-w0/2,-h0/2) + + cr.set_source_surface(self.image) + cr.paint() + + def do_get_size(self, widget, cell_area=None): + x = y = 0 + w0,h0,w1,h1,w2,h2,rotation = self.get_geometry() + th = int(2*self.th1+self.th2) + w = w2 + th + h = h2 + th + + if cell_area and w > 0 and h > 0: + x = self.get_property('xalign') * \ + (cell_area.width - w - self.get_property('xpad')) + y = self.get_property('yalign') * \ + (cell_area.height - h - self.get_property('ypad')) + w += 2 * self.get_property('xpad') + h += 2 * self.get_property('ypad') + return int(x), int(y), w, h + diff --git a/pdfbooklet_3.1.2-3_all/usr/share/applications/pdfbooklet.desktop b/pdfbooklet_3.1.2-3_all/usr/share/applications/pdfbooklet.desktop new file mode 100644 index 0000000..b92fb57 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/applications/pdfbooklet.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=2.0 +Name=PDF-Booklet +Comment=PDF Merging, Rearranging, Splitting, Rotating and Cropping +Type=Application +Exec=pdfbooklet +Icon=pdfbooklet +Categories=Office; +Terminal=false +StartupNotify=false diff --git a/pdfbooklet_3.1.2-3_all/usr/share/doc/pdfbooklet/changelog.Debian.gz b/pdfbooklet_3.1.2-3_all/usr/share/doc/pdfbooklet/changelog.Debian.gz new file mode 100644 index 0000000..b7eb20f Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/doc/pdfbooklet/changelog.Debian.gz differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/doc/pdfbooklet/copyright b/pdfbooklet_3.1.2-3_all/usr/share/doc/pdfbooklet/copyright new file mode 100644 index 0000000..d70e493 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/doc/pdfbooklet/copyright @@ -0,0 +1,8 @@ +This package was debianized by the alien program by converting +a binary .tgz package on Fri, 05 Jul 2019 11:09:47 +0000 + +Copyright: unknown + +Information from the binary package: +-rw-rw-r-- 1 travis travis 5324800 Jul 5 11:09 ./pdfbooklet-3.1.2-all_64_corr.tar.gz + diff --git a/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfbooklet.mo b/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfbooklet.mo new file mode 100644 index 0000000..655b216 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfbooklet.mo differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfbooklet.po b/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfbooklet.po new file mode 100644 index 0000000..98d9534 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfbooklet.po @@ -0,0 +1,1286 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Pdf-Booklet 2.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-05-12 18:45+0200\n" +"PO-Revision-Date: 2018-05-12 18:57+0100\n" +"Last-Translator: Averell \n" +"Language-Team: Averell \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Poedit-Language: French\n" +"X-Poedit-Country: FRANCE\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: pdfbooklet304.py:532 +#: pdfbooklet304.py:553 +#: pdfbooklet304.py:574 +#: pdfbooklet304.py:577 +#: pdfbooklet304.py:599 +#: pdfbooklet304.py:603 +#: pdfbooklet304.py:610 +#: pdfbooklet304.py:613 +#: pdfbooklet304.py:640 +#: pdfbooklet304.py:1662 +#: pdfbooklet304.py:1685 +#: pdfbooklet304.py:1706 +#: pdfbooklet304.py:1709 +#: pdfbooklet304.py:1731 +#: pdfbooklet304.py:1735 +#: pdfbooklet304.py:1742 +#: pdfbooklet304.py:1745 +#: pdfbooklet304.py:1767 +#: pdfbooklet304.py:4118 +msgid "Invalid data" +msgstr "Entrée invalide" + +#: pdfbooklet304.py:532 +#: pdfbooklet304.py:553 +#: pdfbooklet304.py:577 +#: pdfbooklet304.py:610 +#: pdfbooklet304.py:613 +#: pdfbooklet304.py:1662 +#: pdfbooklet304.py:1685 +#: pdfbooklet304.py:1709 +#: pdfbooklet304.py:1742 +#: pdfbooklet304.py:1745 +#, python-format +msgid "Invalid data for %s - must be numeric. Aborting \n" +msgstr "Valeur invalide pour %s, doit être numérique. Abandon.\n" + +#: pdfbooklet304.py:574 +#: pdfbooklet304.py:603 +#: pdfbooklet304.py:1706 +#: pdfbooklet304.py:1735 +#, python-format +msgid "Invalid data for %s - must be > 0. Aborting \n" +msgstr "Valeur invalide pour %s, doit être > 0. Abandon.\n" + +#: pdfbooklet304.py:599 +#: pdfbooklet304.py:1731 +#, python-format +msgid "Invalid data for %s - must be >= 0. Aborting \n" +msgstr "Valeur invalide pour %s, doit être >= 0. Abandon.\n" + +#: pdfbooklet304.py:640 +#: pdfbooklet304.py:1767 +#, python-format +msgid "Invalid data for %s - must be 0 or 1. Aborting \n" +msgstr "Valeur invalide pour %s, doit être 0 ou 1. Abandon.\n" + +#: pdfbooklet304.py:779 +msgid "Please, enter the password for this file" +msgstr "Entrez le mot de passe pour ce fichier svp" + +#: pdfbooklet304.py:807 +msgid "The reference page is invalid. We use the first page" +msgstr "La page de référence est invalide, nous utilisons la première page. " + +#: pdfbooklet304.py:820 +msgid "Reference page invalid, there is no file n°" +msgstr "La page de référence est invalide, il n'y a pas de fichier n°" + +#: pdfbooklet304.py:848 +msgid "Width" +msgstr "Largeur" + +#: pdfbooklet304.py:850 +msgid "Height" +msgstr "Hauteur" + +#: pdfbooklet304.py:869 +#, python-format +msgid "%s mm x %s mm " +msgstr "%s mm x %s mm " + +#: pdfbooklet304.py:1113 +msgid "You must restart the program to apply the new theme." +msgstr "Vous devez redémarrer le programme pour afficher le nouveau thème." + +#: pdfbooklet304.py:1204 +#: pdfbooklet/pdfshuffler_g3.py:615 +msgid "Import..." +msgstr "Importer ..." + +#: pdfbooklet304.py:1214 +#: pdfbooklet/files_chooser.py:77 +#: pdfbooklet/pdfshuffler_g3.py:515 +#: pdfbooklet/pdfshuffler_g3.py:626 +msgid "All files" +msgstr "Tous les fichiers" + +#: pdfbooklet304.py:1219 +msgid "INI files" +msgstr "fichiers INI" + +#: pdfbooklet304.py:1311 +msgid "Save project..." +msgstr "Enregistrer le projet..." + +#: pdfbooklet304.py:1576 +msgid "Pdf-Booklet_User's_Guide.pdf" +msgstr "Manuel_de_Pdf-Booklet.pdf" + +#: pdfbooklet304.py:1595 +#: pdfbooklet304.py:3242 +#: pdfbooklet304.py:3404 +#: pdfbooklet304.py:4111 +msgid "No selection" +msgstr "Il n'y a pas de sélection" + +#: pdfbooklet304.py:1595 +#: pdfbooklet304.py:3404 +msgid "" +"There is no selected page. \n" +"Please select a page first. " +msgstr "Vous devez d'abord sélectionner une page" + +#: pdfbooklet304.py:1789 +msgid "rows" +msgstr "lignes" + +#: pdfbooklet304.py:1791 +msgid "columns" +msgstr "colonnes" + +#: pdfbooklet304.py:1800 +msgid "step" +msgstr "Pas" + +#: pdfbooklet304.py:1809 +msgid "folios" +msgstr "folios" + +#: pdfbooklet304.py:1810 +#: data/pdfbooklet3.glade:2547 +msgid "Leading blank pages" +msgstr "Pages blanches au début" + +#: pdfbooklet304.py:1812 +#: data/pdfbooklet3.glade:2502 +msgid "Trailing blank pages" +msgstr "Pages blanches à la fin" + +#: pdfbooklet304.py:1836 +msgid "Invalid value for reference page, please correct and try again" +msgstr "La valeur de la page de référence est invalide. Veuillez corriger et réessayer." + +#: pdfbooklet304.py:2202 +#, python-format +msgid "" +"Expected page number was : %d. Only %d found. \n" +"There is an error in your layout, please correct" +msgstr "" +"Le nombre de pages attendues était: %d. %d seulement trouvées. \n" +"Il y a une erreur dans votre disposition, merci de corriger" + +#: pdfbooklet304.py:3242 +msgid "" +"There is no selected file. \n" +"Please select a file first. " +msgstr "Vous devez d'abord sélectionner un fichier" + +#: pdfbooklet304.py:3309 +#: pdfbooklet/pdfshuffler_g3.py:474 +msgid "page" +msgstr "pages" + +#: pdfbooklet304.py:3568 +msgid "File existing" +msgstr "Fichier existant" + +#: pdfbooklet304.py:3568 +msgid "" +"The outputfile already exists \n" +"overWrite ? " +msgstr "" +"Le fichier de sortie existe déjà. \n" +"Écraser ? " + +#: pdfbooklet304.py:4094 +msgid "No file loaded" +msgstr "Auncun fichier chargé" + +#: pdfbooklet304.py:4094 +msgid "Please select a file first" +msgstr "Vous devez d'abord sélectionner un fichier" + +#: pdfbooklet304.py:4111 +msgid "There is no selection" +msgstr "Il n'y a pas de sélection" + +#: pdfbooklet304.py:4118 +#, python-format +msgid "Invalid data for Selection : %s. Aborting \n" +msgstr "Valeur invalide pour %s, Abandon.\n" + +#: pdfbooklet304.py:4409 +msgid "File already open" +msgstr "Fichier déjà ouvert" + +#: pdfbooklet304.py:4409 +msgid "" +"The output file is already opened \n" +"probably in Adobe Reader. \n" +"Close the file and start again" +msgstr "" +"Le fichier de sortie est déjà ouvert \n" +"probablement dans Adobe Reader. \n" +"Fermez le fichier et recommencez." + +#: pdfbooklet304.py:4678 +#, python-format +msgid "Assembling pages: %s " +msgstr "Assemblage des pages: %s" + +#: pdfbooklet/files_chooser.py:45 +msgid "Filename" +msgstr "Fichier" + +#: pdfbooklet/files_chooser.py:82 +#: pdfbooklet/pdfshuffler_g3.py:509 +#: pdfbooklet/pdfshuffler_g3.py:631 +msgid "PDF files" +msgstr "Fichiers PDF" + +#: pdfbooklet/files_chooser.py:136 +msgid "File type not supported!" +msgstr "Type de ficher pas encore supporté !" + +#: pdfbooklet/files_chooser.py:138 +#: pdfbooklet/pdfshuffler_g3.py:657 +#, python-format +msgid "File %s does not exist" +msgstr "Le fichier %s n'existe pas" + +#: pdfbooklet/files_chooser.py:156 +msgid "You have chosen a directory, it is not supported" +msgstr "Vous avez choisi un répertoire, ce n'est pas supporté. " + +#: pdfbooklet/files_chooser.py:169 +#: pdfbooklet/pdfshuffler_g3.py:659 +msgid "Closed, no files selected" +msgstr "Fermé, aucun fichier sélectionné" + +#: pdfbooklet/pdfshuffler_g3.py:333 +msgid "No document" +msgstr "Pas de document" + +#: pdfbooklet/pdfshuffler_g3.py:335 +msgid "Several documents" +msgstr "Plusieurs documents" + +#: pdfbooklet/pdfshuffler_g3.py:351 +#, python-format +msgid "Rendering thumbnails... [%(i1)s/%(i2)s]" +msgstr "Rendu des miniatures... [%(i1)s/%(i2)s]" + +#: pdfbooklet/pdfshuffler_g3.py:500 +msgid "Export ..." +msgstr "Exporter ..." + +#: pdfbooklet/pdfshuffler_g3.py:559 +#, python-format +msgid "" +"File %s is encrypted.\n" +"Support for encrypted files has not been implemented yet.\n" +"File export failed." +msgstr "" +"Le fichier %s est crypté.\n" +"Le support des fichiers encryptés n'est pas encore implémenté.\n" +"L'exportation du fichier a échoué." + +#: pdfbooklet/pdfshuffler_g3.py:1065 +msgid "Left" +msgstr "Gauche" + +#: pdfbooklet/pdfshuffler_g3.py:1065 +msgid "Right" +msgstr "Droite" + +#: pdfbooklet/pdfshuffler_g3.py:1066 +msgid "Top" +msgstr "Haut" + +#: pdfbooklet/pdfshuffler_g3.py:1066 +msgid "Bottom" +msgstr "Bas" + +#: pdfbooklet/pdfshuffler_g3.py:1085 +msgid "Crop Selected Pages" +msgstr "Récupération des pages sélectionnés" + +#: pdfbooklet/pdfshuffler_g3.py:1094 +msgid "Crop Margins" +msgstr "Récupération des marges" + +#: pdfbooklet/pdfshuffler_g3.py:1101 +#, python-format +msgid "% of width" +msgstr "% de la largeur" + +#: pdfbooklet/pdfshuffler_g3.py:1101 +#, python-format +msgid "% of height" +msgstr "% de la hauteur" + +#: pdfbooklet/pdfshuffler_g3.py:1141 +msgid "Dialog closed" +msgstr "Fenêtre de dialogue fermée" + +#: pdfbooklet/pdfshuffler_g3.py:1155 +#, python-format +msgid "%s is a tool for rearranging and modifying PDF files. Developed using GTK+ and Python" +msgstr "%s est un outil pour réorganiser et modifier les fichiers PDF. Développé avec GTK+ et Python" + +#: data/pdfbooklet3.glade:17 +msgid "" +"Pdf-Shuffler is delivered under the GNU General Public Licence (GPL) Version 3.\n" +"Pdf-Booklet is delivered under the CeCill Licence. " +msgstr "" +"Pdf Shuffler est distribué sous la licence GPL (GNU General Public Licence) Version 3.\n" +"Pdf-Booklet est distribué sous la licence CeCill" + +#: data/pdfbooklet3.glade:173 +msgid "Copy transformations" +msgstr "Copie transformations" + +#: data/pdfbooklet3.glade:182 +msgid "Paste Transformations" +msgstr "Dépose transformations" + +#: data/pdfbooklet3.glade:191 +msgid "Standard Transformations" +msgstr "Transformations standards" + +#: data/pdfbooklet3.glade:201 +msgid "Rotate 0" +msgstr "Tourner de 0°" + +#: data/pdfbooklet3.glade:210 +msgid "Rotate 90" +msgstr "Tourner de 90°" + +#: data/pdfbooklet3.glade:219 +msgid "Rotate 180" +msgstr "Tourner de 180°" + +#: data/pdfbooklet3.glade:228 +msgid "Rotate 270" +msgstr "Tourner de 270°" + +#: data/pdfbooklet3.glade:302 +msgid "" +"Syntax : \n" +"separate items by commas, \n" +"or on different lines. \n" +"\n" +"## \t: single page\n" +"[## - ##] : page range\n" +"b \t: blank page\n" +"#b \t: repeated blank pages. \n" +"\n" +"Examples : \n" +"10, 25, 43\n" +"[10-20]\n" +"5b\n" +"\n" +"For files other than the first, \n" +"indicate the number followed by a colon. \n" +"\n" +"Examples :\n" +"2:10, 3:25, 1:43\n" +"2:[10-20]\n" +"\n" +msgstr "" +"Syntaxe : \n" +"séparer les objets par des virgules, \n" +"ou les placer sur des lignes différentes. \n" +"\n" +"## \t: page unique\n" +"[## - ##] : plage de pages\n" +"b \t: page blanche\n" +"#b \t: nombre de pages blanches. \n" +"\n" +"Exemples : \n" +"10, 25, 43\n" + +#: data/pdfbooklet3.glade:362 +msgid "compare files" +msgstr "Comparer des fichiers" + +#: data/pdfbooklet3.glade:394 +msgid "" +"This is an advanced feature. Please read documentation to use it properly.\n" +"Here is a short summary :\n" +"\n" +"Enter page numbers separated by commas on a separate line for each row\n" +"Blank pages can be indicated by a b. \n" +"\n" +"Example : \n" +"\n" +"8,1,2,7\n" +"3,6,5,4\n" +msgstr "" +"C'est une fonctionnalité avancée. Lire la documentation pour l'utiliser correctement.\n" +"Voici un bref résumé :\n" +"\n" +"Entrer les numéros de pages séparés par des virgules sur une ligne séparée pour chaque rangée\n" +"Les pages blanches peuvent être signalées par un b. \n" +"\n" +"Exemple : \n" +"\n" +"8,1,2,7\n" +"3,6,5,4\n" + +#: data/pdfbooklet3.glade:473 +#: data/pdfbooklet3.glade:1127 +msgid "Step :" +msgstr "Pas :" + +#: data/pdfbooklet3.glade:484 +#: data/pdfbooklet3.glade:1318 +msgid "Automatic" +msgstr "Automatique" + +#: data/pdfbooklet3.glade:501 +msgid "=" +msgstr "=" + +#: data/pdfbooklet3.glade:634 +#: data/pdfbooklet3.glade:642 +msgid "_Files" +msgstr "Fichiers" + +#: data/pdfbooklet3.glade:660 +msgid "_Open Project" +msgstr "_Ouvrir le projet..." + +#: data/pdfbooklet3.glade:672 +msgid "_Save project" +msgstr "Enregistrer le projet..." + +#: data/pdfbooklet3.glade:684 +msgid "Save project _as ..." +msgstr "Enregister le projet sous..." + +#: data/pdfbooklet3.glade:702 +msgid "Save settings as _default" +msgstr "Enregister les _paramètres" + +#: data/pdfbooklet3.glade:720 +#: data/pdfshuffler_g.glade:499 +msgid "_Quit" +msgstr "_Quitter" + +#: data/pdfbooklet3.glade:776 +msgid "_Pages" +msgstr "Pages" + +#: data/pdfbooklet3.glade:784 +msgid "_Edit pages list" +msgstr "Éditer la liste des pages" + +#: data/pdfbooklet3.glade:796 +msgid "_Pages selector" +msgstr "Sélecteur de pages" + +#: data/pdfbooklet3.glade:814 +msgid "_Display" +msgstr "_Affichage" + +#: data/pdfbooklet3.glade:824 +msgid "_Hide Numbers" +msgstr "_Cacher les numéros" + +#: data/pdfbooklet3.glade:832 +msgid "Disable Automatic _Update" +msgstr "Désactiver la mise à jour automatique de l'aperçu" + +#: data/pdfbooklet3.glade:844 +#: data/pdfshuffler_g.glade:720 +msgid "_Help" +msgstr "_Aide" + +#: data/pdfbooklet3.glade:852 +msgid "User's Guide" +msgstr "Manuel de l'utilisateur" + +#: data/pdfbooklet3.glade:864 +#: data/pdfshuffler_g.glade:728 +msgid "_About" +msgstr "À propos" + +#: data/pdfbooklet3.glade:882 +msgid "Themes" +msgstr "Thèmes" + +#: data/pdfbooklet3.glade:926 +msgid "Single Booklet" +msgstr "Cahier unique" + +#: data/pdfbooklet3.glade:943 +msgid "Multiple Booklets" +msgstr "Cahiers multiples" + +#: data/pdfbooklet3.glade:960 +msgid "2 pages" +msgstr " 2 pages" + +#: data/pdfbooklet3.glade:977 +msgid "x pages in line" +msgstr "x pages en ligne" + +#: data/pdfbooklet3.glade:994 +msgid "x pages in columns" +msgstr "x pages en colonnes" + +#: data/pdfbooklet3.glade:1011 +msgid "x copies" +msgstr "x copies" + +#: data/pdfbooklet3.glade:1028 +msgid "One Page" +msgstr "Une page" + +#: data/pdfbooklet3.glade:1045 +msgid "User defined" +msgstr "défini par l'utilisateur" + +#: data/pdfbooklet3.glade:1140 +msgid "Type : Calendar" +msgstr "Type : calendrier" + +#: data/pdfbooklet3.glade:1153 +msgid "Type : Book" +msgstr "Type : livre" + +#: data/pdfbooklet3.glade:1166 +msgid "Leafs in a booklet :" +msgstr "Feuilles par cahier : " + +#: data/pdfbooklet3.glade:1179 +msgid "columns :" +msgstr "colonnes : " + +#: data/pdfbooklet3.glade:1192 +msgid "rows :" +msgstr "lignes :" + +#: data/pdfbooklet3.glade:1279 +msgid " Nothing loaded" +msgstr "Auncun fichier chargé" + +#: data/pdfbooklet3.glade:1343 +msgid " " +msgstr " " + +#: data/pdfbooklet3.glade:1362 +msgid "No change" +msgstr "Pas de changement" + +#: data/pdfbooklet3.glade:1382 +msgid "user defined (W / H)" +msgstr "Personnalisé (L / H) : " + +#: data/pdfbooklet3.glade:1443 +#: data/pdfbooklet3.glade:2697 +msgid "mm" +msgstr "mm" + +#: data/pdfbooklet3.glade:1460 +msgid "Auto scale" +msgstr "Mise à l'échelle auto." + +#: data/pdfbooklet3.glade:1484 +msgid "Output size" +msgstr "Format de page du fichier se sortie" + +#: data/pdfbooklet3.glade:1500 +msgid " " +msgstr " " + +#: data/pdfbooklet3.glade:1517 +msgid "Disposition" +msgstr "Disposition" + +#: data/pdfbooklet3.glade:1527 +msgid "Layout" +msgstr "Disposition" + +#: data/pdfbooklet3.glade:1553 +msgid "All pages in this position" +msgstr "Toutes les pages dans cette position" + +#: data/pdfbooklet3.glade:1557 +msgid "" +"The tranformations will be applied to all pages which have the same position in the layout.\n" +"Example : all top left pages, or all bottom right pages. " +msgstr "" +"La transformation sera appliquée à toutes les pages qui ont la même position dans l'agencement.\n" +"Exemple: toutes les pages en haut à gauche, ou toutes les pages en bas à droite." + +#: data/pdfbooklet3.glade:1572 +msgid "This page only" +msgstr "Seulement cette page" + +#: data/pdfbooklet3.glade:1576 +msgid "Transformations are applied only to the selected page. " +msgstr "Les transformations sont appliquées seulement sur la page choisie." + +#: data/pdfbooklet3.glade:1590 +msgid "Even pages" +msgstr "Pages paires" + +#: data/pdfbooklet3.glade:1594 +msgid "Transformations are applied to all even pages. " +msgstr "Les transformations sont appliquées sur toutes les pages paires.." + +#: data/pdfbooklet3.glade:1608 +msgid "Odd pages" +msgstr "Pages impaires" + +#: data/pdfbooklet3.glade:1612 +msgid "Transformations are applied to all odd pages. " +msgstr "Les transformations sont appliquées sur toutes les pages impaires.." + +#: data/pdfbooklet3.glade:1630 +msgid "Domain" +msgstr "Domaine" + +#: data/pdfbooklet3.glade:1645 +msgid "" +"Choose the domain \n" +"before\n" +"applying\n" +"tranformations. \n" +msgstr "" +"Choisissez le domaine \n" +"avant\n" +"d'appliquer\n" +"des transformations. \n" + +#: data/pdfbooklet3.glade:1694 +#: data/pdfbooklet3.glade:2063 +msgid "Vertical Shift (mm)" +msgstr "Déplacement vertical (mm)" + +#: data/pdfbooklet3.glade:1706 +#: data/pdfbooklet3.glade:2075 +msgid "Scale (%)" +msgstr "Echelle (%)" + +#: data/pdfbooklet3.glade:1718 +#: data/pdfbooklet3.glade:2087 +msgid "Rotation (degrees)" +msgstr "Rotation (degrés)" + +#: data/pdfbooklet3.glade:1730 +#: data/pdfbooklet3.glade:2122 +msgid "Horizontal Shift (mm)" +msgstr "Déplacement horizontal (mm)" + +#: data/pdfbooklet3.glade:1818 +#: data/pdfbooklet3.glade:2187 +msgid "Vertical Flip" +msgstr "Retournement vertical" + +#: data/pdfbooklet3.glade:1830 +#: data/pdfbooklet3.glade:2199 +msgid "Horizontal Flip" +msgstr "Retournement horizontal" + +#: data/pdfbooklet3.glade:1842 +#: data/pdfbooklet3.glade:2211 +msgid "Scale Horizontally" +msgstr "Mettrre à l'échelle horizontallement" + +#: data/pdfbooklet3.glade:1854 +#: data/pdfbooklet3.glade:2223 +msgid "Scale Vertically" +msgstr "Mettre à l'échelle verticalement" + +#: data/pdfbooklet3.glade:1901 +#: data/pdfbooklet3.glade:1919 +msgid " " +msgstr " " + +#: data/pdfbooklet3.glade:1940 +msgid "" +"Activate this button to enable\n" +"moving the pages with the mouse." +msgstr "" +"Activez ce bouton\n" +"pour déplacer la page avec la souris" + +#: data/pdfbooklet3.glade:1988 +msgid "Apply" +msgstr "Appliquer" + +#: data/pdfbooklet3.glade:2002 +msgid "Reset" +msgstr "Défauts" + +#: data/pdfbooklet3.glade:2034 +msgid "" +"Pages\n" +"Transformations" +msgstr "" +"Transformations\n" +"de pages" + +#: data/pdfbooklet3.glade:2268 +#: data/pdfbooklet3.glade:2286 +#: data/pdfbooklet3.glade:2573 +#: data/pdfbooklet3.glade:2584 +msgid " " +msgstr " " + +#: data/pdfbooklet3.glade:2308 +msgid "Output page transformations" +msgstr "Transformations de la page finale" + +#: data/pdfbooklet3.glade:2411 +msgid "Global rotation" +msgstr " Rotation globale" + +#: data/pdfbooklet3.glade:2431 +msgid "Reset All" +msgstr "Réinitialise tout" + +#: data/pdfbooklet3.glade:2464 +msgid "" +"Global\n" +"Transformations" +msgstr "" +"Transformations\n" +"globales" + +#: data/pdfbooklet3.glade:2533 +msgid "0" +msgstr "0" + +#: data/pdfbooklet3.glade:2556 +msgid "Right to left" +msgstr "De droite à gauche" + +#: data/pdfbooklet3.glade:2599 +msgid "Page setup" +msgstr "Mise en page" + +#: data/pdfbooklet3.glade:2642 +msgid "Reference page" +msgstr "Page de référence" + +#: data/pdfbooklet3.glade:2654 +msgid "Creep adjustment" +msgstr "Ajustement de la gouttière" + +#: data/pdfbooklet3.glade:2717 +msgid "Advanced" +msgstr "Avancé" + +#: data/pdfbooklet3.glade:2734 +msgid "Page setup" +msgstr "Mise en page" + +#: data/pdfbooklet3.glade:2766 +msgid "Output file" +msgstr "Fichier de sortie" + +#: data/pdfbooklet3.glade:2789 +msgid "..." +msgstr "..." + +#: data/pdfbooklet3.glade:2836 +msgid "Show Pdf" +msgstr "Montrer Pdf" + +#: data/pdfbooklet3.glade:2847 +msgid "Overwrite output files" +msgstr "Écraser le fichier de sortie" + +#: data/pdfbooklet3.glade:2861 +msgid "Save settings with pdf" +msgstr "Enregister les paramètres avec le fichier pdf" + +#: data/pdfbooklet3.glade:2875 +msgid "Don't compress output" +msgstr "Ne pas compresser le fichier de sortie" + +#: data/pdfbooklet3.glade:2889 +msgid "Safe mode (slow)" +msgstr "Mode sûr (lent)" + +#: data/pdfbooklet3.glade:2916 +msgid "Output" +msgstr "Fichier de sortie " + +#: data/pdfbooklet3.glade:2942 +msgid "Output" +msgstr "Fichier de sortie" + +#: data/pdfbooklet3.glade:2957 +msgid "" +"New features under development\n" +"May work or not..." +msgstr "" +"Fonctions nouvelles en cours de développement.\n" +"Cela peut marcher ou non..." + +# Inutilisé +#: data/pdfbooklet3.glade:2978 +msgid "Delete rectangle" +msgstr "Effacer une zone" + +#: data/pdfbooklet3.glade:2991 +msgid "Preview" +msgstr "Aperçu" + +#: data/pdfbooklet3.glade:3017 +msgid "Diviser un cahier" +msgstr "" + +#: data/pdfbooklet3.glade:3031 +msgid "middle line" +msgstr "Ligne médiane" + +#: data/pdfbooklet3.glade:3114 +msgid "Bottom margin" +msgstr "Marge du bas" + +#: data/pdfbooklet3.glade:3169 +msgid "Start from page :" +msgstr "Commencer page :" + +#: data/pdfbooklet3.glade:3188 +msgid "Add page numbers" +msgstr "Ajouter des numéros de page" + +#: data/pdfbooklet3.glade:3203 +msgid "Font size" +msgstr "Taille de police" + +#: data/pdfbooklet3.glade:3240 +msgid "Page numbers (Experimental)" +msgstr "Numéros de page (expérimental)" + +#: data/pdfbooklet3.glade:3261 +msgid " Experimental" +msgstr "Expérimental" + +#: data/pdfbooklet3.glade:3277 +msgid "Options" +msgstr "Options" + +#: data/pdfbooklet3.glade:3393 +msgid "Update" +msgstr "Mettre à jour" + +#: data/pdfbooklet3.glade:3410 +msgid "Go" +msgstr "Lancer" + +#: data/pdfbooklet3.glade:3427 +msgid "---" +msgstr "---" + +#: data/chooser_dialog.glade:33 +msgid "Open Selected File(s)" +msgstr "Ouvrir les fichiers sélectionnés" + +#: data/chooser_dialog.glade:227 +msgid "Input files" +msgstr "Fichiers source" + +#: data/chooser_dialog.glade:247 +msgid "Open File(s) in List" +msgstr "Ouvrir les fichiers de la liste" + +#: data/pdfshuffler_g.glade:34 +#: data/pdfshuffler_g.glade:47 +msgid "button" +msgstr "bouton" + +#: data/pdfshuffler_g.glade:269 +#: data/pdfshuffler_g.glade:535 +msgid "Rotate _Right" +msgstr "Tourner à droite" + +#: data/pdfshuffler_g.glade:280 +#: data/pdfshuffler_g.glade:524 +msgid "Rotate _Left" +msgstr "Tourner à gauche" + +#: data/pdfshuffler_g.glade:291 +msgid "C_rop" +msgstr "_Couper les mages" + +#: data/pdfshuffler_g.glade:312 +msgid "Add _Blank Page Before" +msgstr "Ajouter une page_ blanche avant" + +#: data/pdfshuffler_g.glade:366 +msgid "Add Blank Page _After" +msgstr "_Ajouter une page blanche après" + +#: data/pdfshuffler_g.glade:452 +msgid "_File" +msgstr "_Fichier" + +#: data/pdfshuffler_g.glade:460 +msgid "_Open" +msgstr "_Ouvrir" + +#: data/pdfshuffler_g.glade:471 +msgid "_Save" +msgstr "_Enregistrer" + +#: data/pdfshuffler_g.glade:482 +msgid "Save _As..." +msgstr "Enregistrer _sous..." + +#: data/pdfshuffler_g.glade:516 +msgid "_Edit" +msgstr "É_diter" + +#: data/pdfshuffler_g.glade:546 +msgid "Add blank page _before" +msgstr "Ajouter une page _blanche avant" + +#: data/pdfshuffler_g.glade:598 +msgid "Add blank page _after" +msgstr "Ajouter une page blanche _après" + +#: data/pdfshuffler_g.glade:656 +msgid "_Delete" +msgstr "_Effacer" + +#: data/pdfshuffler_g.glade:667 +msgid "_Crop" +msgstr "_Recouper" + +#: data/pdfshuffler_g.glade:684 +msgid "_View" +msgstr "_Voir" + +#: data/pdfshuffler_g.glade:692 +msgid "Zoom _in" +msgstr "_Zoom avant" + +#: data/pdfshuffler_g.glade:703 +msgid "Zoom _out" +msgstr "Zoom _arrière" + +#: data/pdfshuffler_g.glade:763 +msgid "Open" +msgstr "Ouvrir" + +#: data/pdfshuffler_g.glade:777 +msgid "Save" +msgstr "Enregistrer" + +#: data/pdfshuffler_g.glade:791 +msgid "Save as" +msgstr "Enregistrer sous..." + +#: data/pdfshuffler_g.glade:815 +msgid "Zoom in" +msgstr "Agrandir" + +#: data/pdfshuffler_g.glade:829 +msgid "Zoom out" +msgstr "Réduire" + +#: data/pdfshuffler_g.glade:853 +msgid "Rotate left" +msgstr "Rotation à gauche" + +#: data/pdfshuffler_g.glade:867 +msgid "Rotate right" +msgstr "Tourner à droite" + +#: data/pdfshuffler_g.glade:881 +msgid "Crop" +msgstr "Couper les marges" + +#: data/pdfshuffler_g.glade:895 +msgid "Delete" +msgstr "Effacer" + +#~ msgid "Custom Transformations" +#~ msgstr "Transformations" + +#~ msgid "Load" +#~ msgstr "Charger" + +#~ msgid "" +#~ "To apply transformations to particular Output pages : \n" +#~ " - Create your pdf file. \n" +#~ " - Open the created file in PdfBooklet\n" +#~ " - Select the \"One page\" layout\n" +#~ " - Use the \"Transformations\" tab\n" +#~ " to modify the pages you want" +#~ msgstr "" +#~ "Pour appliquer des transformations sur des pages particulières de " +#~ "Sortie: \n" +#~ " - Créez votre fichier pdf. \n" +#~ " - Ouvrez le fichier créé dans PdfBooklet \n" +#~ " - Choisissez l'agencement \"Une page\" \n" +#~ " - Utilisez l'onglet \"Transformations\" pour modifier les pages que " +#~ "vous voulez" + +#~ msgid "" +#~ "%F : Full file path without extension\n" +#~ "%P : Path\n" +#~ "%N : Name\n" +#~ "%B : Basename\n" +#~ "%E : Extension" +#~ msgstr "" +#~ "%F : Chemin complet sans extension\n" +#~ "%P : Chemin\n" +#~ "%N : Nom\n" +#~ "%B : Non de la base\n" +#~ "%E : Extension" + +#~ msgid "Move file up" +#~ msgstr "Déplacer vers le haut" + +#~ msgid "Move file down" +#~ msgstr "Déplacer vers le bas" + +#~ msgid "Slow mode" +#~ msgstr "Mode lent" + +#~ msgid "Can't load icon. Application is not installed correctly." +#~ msgstr "Icône manquante. L'application n'est pas installée correctement" + +#~ msgid "Cancel" +#~ msgstr "Annuler" + +#~ msgid "Creep" +#~ msgstr "Décaler la marge intérieure" + +#~ msgid "Size of output file = %s mm x %s mm " +#~ msgstr "Taille du fichier de sortie = %s mm x %s mm" + +#~ msgid "Blank pages to be added : %s" +#~ msgstr "Pages blanches à ajouter : %s" + +#~ msgid "Booklet %s : pages %s - %s" +#~ msgstr "Cahier %s : pages %s - %s" + +#~ msgid "Output page Horizontal Shift" +#~ msgstr "Déplacement horizontal de la page finale" + +#~ msgid "Output page Vertical Shift" +#~ msgstr "Déplacement vertical de la page finale" + +#~ msgid "Output page Scale" +#~ msgstr "Mise à l'échelle de la page finale" + +#~ msgid "Output page Rotation" +#~ msgstr "Rotation de la page finale" + +#~ msgid "Output page scale horizontally" +#~ msgstr "Mise à l'échelle horizontale de la page finale" + +#~ msgid "Output page scale vertically" +#~ msgstr "Mise à l'échelle verticale de la page finale" + +#~ msgid "Total length : %s " +#~ msgstr "Durée totale : %s" + +#~ msgid "OpenDocument not supported yet!" +#~ msgstr "Document pas encore supporté !" + +#~ msgid "Image file not supported yet!" +#~ msgstr "Type d'image pas encore supporté !" + +#~ msgid "_File manager" +#~ msgstr "Gestionnaire de _fichiers" + +#~ msgid "Auto" +#~ msgstr "Auto" + +#~ msgid "Disposition" +#~ msgstr "Disposition" + +#~ msgid "Transformations" +#~ msgstr "Transformations" + +#~ msgid "" +#~ "To add transformations for individual pages \n" +#~ "select a page in the preview \n" +#~ "and use the Transformations tab. " +#~ msgstr "" +#~ "Pour appliquer des transformations aux pages individuellement \n" +#~ "sélectionner une page dans l'aperçu \n" +#~ "et cliquer sur l'onglet \"Transformations\"" + +#~ msgid "page 5" +#~ msgstr "page 5" + +#~ msgid "Close" +#~ msgstr "Fermer" + +#~ msgid "Log" +#~ msgstr "Log" + +#~ msgid "Log" +#~ msgstr "Log" + +#~ msgid "Create a new list" +#~ msgstr "Nouvelle liste" + +#~ msgid "Add file(s) to existing list" +#~ msgstr "Ajouter le(s) fichier(s) à la liste existante" + +#~ msgid "Remove file from list" +#~ msgstr "Enlever le fichier de la liste" + +#~ msgid "Add blank page after" +#~ msgstr "Ajouter une page blanche après" + +#~ msgid "label" +#~ msgstr " " + +# Debug +#~ msgid "destroy" +#~ msgstr "destroy" +#~ msgid "Pdf-Booklet on SourceForge" +#~ msgstr "Pdf-Booklet sur SourceForge" +# A développer +#~ msgid "Licence CeCill" +#~ msgstr "Licence CeCill" +#, fuzzy +#~ msgid "%F-bklt.pdf" +#~ msgstr "Montrer Pdf" + +#~ msgid "<<" +#~ msgstr "<<" + +#~ msgid "<" +#~ msgstr "<" + +#~ msgid ">" +#~ msgstr ">" + +#~ msgid ">>" +#~ msgstr ">>" + +#~ msgid "180" +#~ msgstr "180" + +#~ msgid "270" +#~ msgstr "270" + +#~ msgid "Pdf-Booklet [ beta 0.99 ]" +#~ msgstr "Pdf-Booklet [ 0.9.9 ]" + +#~ msgid " Input file 2" +#~ msgstr "Fichier d'entrée 2" + +#~ msgid " ini file" +#~ msgstr "Fichier ini" + +#~ msgid "" +#~ " Syntax : x-y = range; b = blank; Example : b, " +#~ "1-10, b, b, 15, 19, 25-50" +#~ msgstr "" +#~ " Syntaxe : x-y = série; b = blanche; Exemple : b, " +#~ "1-10, b, b, 15, 19, 25-50" + +#~ msgid " Leading blank pages" +#~ msgstr "Pages blanches au début" + +#~ msgid " Trailing blank pages" +#~ msgstr "Pages blanches à la fin" + +#~ msgid " Checkerboard" +#~ msgstr "Damier" + +#~ msgid " rows" +#~ msgstr "lignes" + +#~ msgid " columns" +#~ msgstr " colonnes " + +#~ msgid " lines" +#~ msgstr "lignes" + +#~ msgid " Right page" +#~ msgstr " Page droite" + +#~ msgid "" +#~ "Positive values \n" +#~ " shift right or up \n" +#~ " or rotate counter-clockwise" +#~ msgstr "" +#~ "Des valeurs positives\n" +#~ " déplacent vers la droite ou vers le haut\n" +#~ " ou font une rotation dans le sens \n" +#~ " contraire des aiguilles d'une montre" + +#~ msgid "" +#~ "Negative values \n" +#~ " shift left or down \n" +#~ " or rotate clockwise " +#~ msgstr "" +#~ "Des valeurs négatives\n" +#~ " déplacent vers la gauche ou vers le bas\n" +#~ " ou font une rotation dans le sens \n" +#~ " des aiguilles d'une montre" + +#~ msgid " Copy -> " +#~ msgstr "Copier ->" + +#~ msgid "<- Mirror ->" +#~ msgstr "<- Miroir ->" + +#~ msgid " Size : " +#~ msgstr "Taille :" + +#~ msgid " Auto rotate" +#~ msgstr "Rotation auto." + +#~ msgid " Reference page" +#~ msgstr " Page de référence" + +#~ msgid " Fast mode" +#~ msgstr "Mode rapide" + +#~ msgid " Start " +#~ msgstr "Lancer" + +#~ msgid "QUIT" +#~ msgstr "Quitter" + +#~ msgid "Save Defaults" +#~ msgstr "Sauver param." + +#~ msgid "Ini file found. Load ?\n" +#~ msgstr "" +#~ "Un fichier Ini a été trouvé. \n" +#~ " Le charger avec le fichier pdf ?" + +#~ msgid "Save, Close and Reload" +#~ msgstr "Enregistrer, Fermer et recharger" + +#~ msgid "No value !" +#~ msgstr "Pas de valeur !" + +#~ msgid "repeat" +#~ msgstr "Répéter" + +#~ msgid "Size of source file = %s mm x %s mm " +#~ msgstr "Taille du fichier source = %s mm x %s mm" + +#~ msgid "%s has %s pages." +#~ msgstr "%s a %s pages" diff --git a/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfshuffler.mo b/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfshuffler.mo new file mode 100644 index 0000000..7486982 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfshuffler.mo differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfshuffler.po b/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfshuffler.po new file mode 100644 index 0000000..76ec26d --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/locale/fr/LC_MESSAGES/pdfshuffler.po @@ -0,0 +1,281 @@ +msgid "" +msgstr "" +"Project-Id-Version: PDF-Shuffler 0.5\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-08-18 08:12+0200\n" +"PO-Revision-Date: 2015-08-18 08:26+0100\n" +"Last-Translator: DP \n" +"Language-Team: Emmanuel \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: french\n" +"X-Poedit-Country: FRANCE\n" + +#: ..\..\..\pdfshuffler_g.py:127 +msgid "Can't load icon. Application is not installed correctly." +msgstr "Impossible de charger l'icône. L'application n'est pas installée correctement." + +#: ..\..\..\pdfshuffler_g.py:318 +msgid "No document" +msgstr "Pas de document" + +#: ..\..\..\pdfshuffler_g.py:320 +msgid "Several documents" +msgstr "Plusieurs documents" + +#: ..\..\..\pdfshuffler_g.py:336 +#, python-format +msgid "Rendering thumbnails... [%(i1)s/%(i2)s]" +msgstr "Rendu miniatures... [%(i1)s/%(i2)s]" + +#: ..\..\..\pdfshuffler_g.py:453 +msgid "page" +msgstr "page" + +#: ..\..\..\pdfshuffler_g.py:479 +msgid "Export ..." +msgstr "Exporter ..." + +#: ..\..\..\pdfshuffler_g.py:488 +#: ..\..\..\pdfshuffler_g.py:610 +msgid "PDF files" +msgstr "Fichier PDF" + +#: ..\..\..\pdfshuffler_g.py:494 +#: ..\..\..\pdfshuffler_g.py:605 +msgid "All files" +msgstr "Tous les fichiers" + +#: ..\..\..\pdfshuffler_g.py:538 +#, python-format +msgid "" +"File %s is encrypted.\n" +"Support for encrypted files has not been implemented yet.\n" +"File export failed." +msgstr "" +"Le fichier %s est crypté.\n" +"Le support pour ce type de fichier n'a pas encore été implémenté.\n" +"L'exportation du fichier a échoué" + +#: ..\..\..\pdfshuffler_g.py:594 +msgid "Import..." +msgstr "Importer ..." + +#: ..\..\..\pdfshuffler_g.py:630 +msgid "OpenDocument not supported yet!" +msgstr "OpenDocument n'est pas encore supporté !" + +#: ..\..\..\pdfshuffler_g.py:632 +msgid "Image file not supported yet!" +msgstr "Le format image n'est pas encore supporté !" + +#: ..\..\..\pdfshuffler_g.py:634 +msgid "File type not supported!" +msgstr "Type de ficher pas encore supporté !" + +#: ..\..\..\pdfshuffler_g.py:636 +#, python-format +msgid "File %s does not exist" +msgstr "Le fichier %s n'existe pas" + +#: ..\..\..\pdfshuffler_g.py:638 +msgid "Closed, no files selected" +msgstr "Fermé, aucun fichier sélectionné" + +#: ..\..\..\pdfshuffler_g.py:1030 +msgid "Left" +msgstr "Gauche" + +#: ..\..\..\pdfshuffler_g.py:1030 +msgid "Right" +msgstr "Droite" + +#: ..\..\..\pdfshuffler_g.py:1031 +msgid "Top" +msgstr "Dessus" + +#: ..\..\..\pdfshuffler_g.py:1031 +msgid "Bottom" +msgstr "Bas" + +#: ..\..\..\pdfshuffler_g.py:1049 +msgid "Crop Selected Pages" +msgstr "Couper la(les) page(s) sélectionné(es)" + +#: ..\..\..\pdfshuffler_g.py:1057 +msgid "Crop Margins" +msgstr "Couper les marges" + +#: ..\..\..\pdfshuffler_g.py:1064 +#, python-format +msgid "% of width" +msgstr " \\%de la largeur" + +#: ..\..\..\pdfshuffler_g.py:1064 +#, python-format +msgid "% of height" +msgstr " \\%de la hauteur" + +#: ..\..\..\pdfshuffler_g.py:1102 +msgid "Dialog closed" +msgstr "Fenêtre de dialogue fermée" + +#: ..\..\..\pdfshuffler_g.py:1116 +#, python-format +msgid "%s is a tool for rearranging and modifying PDF files. Developed using GTK+ and Python" +msgstr "%s est un outil destiné à réarranger et modifier les pages de fichiers PDF. Développé avec GTK+ et Python" + +#: ..\..\..\data\pdfshuffler_g.glade:83 +#: ..\..\..\data\pdfshuffler_g.glade:370 +msgid "Rotate _Right" +msgstr "Tourner à droite" + +#: ..\..\..\data\pdfshuffler_g.glade:95 +#: ..\..\..\data\pdfshuffler_g.glade:358 +msgid "Rotate _Left" +msgstr "Tourner à gauche" + +#: ..\..\..\data\pdfshuffler_g.glade:107 +msgid "C_rop" +msgstr "_Couper les mages" + +#: ..\..\..\data\pdfshuffler_g.glade:130 +msgid "Add _Blank Page Before" +msgstr "Ajouter une page_ blanche avant" + +#: ..\..\..\data\pdfshuffler_g.glade:190 +msgid "Add Blank Page _After" +msgstr "_Ajouter une page blanche après" + +#: ..\..\..\data\pdfshuffler_g.glade:281 +msgid "_File" +msgstr "_Fichier" + +#: ..\..\..\data\pdfshuffler_g.glade:289 +msgid "_Open" +msgstr "_Ouvrir" + +#: ..\..\..\data\pdfshuffler_g.glade:301 +msgid "_Save" +msgstr "_Enregistrer" + +#: ..\..\..\data\pdfshuffler_g.glade:313 +msgid "Save _As..." +msgstr "Enregistrer _sous..." + +#: ..\..\..\data\pdfshuffler_g.glade:331 +msgid "_Quit" +msgstr "_Quitter" + +#: ..\..\..\data\pdfshuffler_g.glade:350 +msgid "_Edit" +msgstr "É_diter" + +#: ..\..\..\data\pdfshuffler_g.glade:382 +msgid "Add blank page _before" +msgstr "Ajouter une page _blanche avant" + +#: ..\..\..\data\pdfshuffler_g.glade:439 +msgid "Add blank page _after" +msgstr "Ajouter une page blanche _après" + +#: ..\..\..\data\pdfshuffler_g.glade:502 +msgid "_Delete" +msgstr "_Effacer" + +#: ..\..\..\data\pdfshuffler_g.glade:514 +msgid "_Crop" +msgstr "_Recouper" + +#: ..\..\..\data\pdfshuffler_g.glade:533 +msgid "_View" +msgstr "_Voir" + +#: ..\..\..\data\pdfshuffler_g.glade:541 +msgid "Zoom _in" +msgstr "_Zoom avant" + +#: ..\..\..\data\pdfshuffler_g.glade:553 +msgid "Zoom _out" +msgstr "Zoom _arrière" + +#: ..\..\..\data\pdfshuffler_g.glade:572 +msgid "_Help" +msgstr "_Aide" + +#: ..\..\..\data\pdfshuffler_g.glade:580 +msgid "_About" +msgstr "A propos" + +#: ..\..\..\data\pdfshuffler_g.glade:617 +msgid "Open" +msgstr "Ouvrir" + +#: ..\..\..\data\pdfshuffler_g.glade:632 +msgid "Save" +msgstr "Enregistrer" + +#: ..\..\..\data\pdfshuffler_g.glade:647 +msgid "Save as" +msgstr "Enregistrer sous..." + +#: ..\..\..\data\pdfshuffler_g.glade:672 +msgid "Zoom in" +msgstr "Agrandir" + +#: ..\..\..\data\pdfshuffler_g.glade:687 +msgid "Zoom out" +msgstr "Réduire" + +#: ..\..\..\data\pdfshuffler_g.glade:712 +msgid "Rotate left" +msgstr "Tourner à gauche" + +#: ..\..\..\data\pdfshuffler_g.glade:727 +msgid "Rotate right" +msgstr "Tourner à droite" + +#: ..\..\..\data\pdfshuffler_g.glade:742 +msgid "Crop" +msgstr "Couper les marges" + +#: ..\..\..\data\pdfshuffler_g.glade:757 +msgid "Delete" +msgstr "Effacer" + +#~ msgid "Error writing file: %s" +#~ msgstr "Une erreur s'est produite pendant l'écriture du fichier : %s" + +#~ msgid "File %s is encrypted." +#~ msgstr "Le fichier %s est codé." + +#~ msgid "File export failed." +#~ msgstr "Exportation des fichiers échouée." + +#~ msgid "exporting to:" +#~ msgstr "exporter en :" + +#~ msgid "Import pdf" +#~ msgstr "Importer PDF" + +#~ msgid "Export pdf" +#~ msgstr "Exporter PDF" + +#~ msgid "Rotate Page(s) Clockwise" +#~ msgstr "Rotation de la(les) page(s) en sens horaire" + +#~ msgid "Rotate Page(s) Counterclockwise" +#~ msgstr "Rotation de la(les) page(s) en sens anti-horaire" + +#~ msgid "Crop Page(s)" +#~ msgstr "Couper la(les) page(s)" + +#~ msgid "" +#~ "PDF-Shuffler is a simple pyGTK utility which lets you merge, split and " +#~ "rearrange PDF documents. You can also rotate and crop individual pages of " +#~ "a pdf document." +#~ msgstr "" +#~ "PDF-Shuffler est un simple outil pyGTK qui vous permet de réunir, couper " +#~ "et réarrangez des documents PDF. Vous pouvez aussi pivoter et couper des " +#~ "pages individuelles d'un document pdf." diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/chooser_dialog.glade b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/chooser_dialog.glade new file mode 100644 index 0000000..b48d2e2 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/chooser_dialog.glade @@ -0,0 +1,299 @@ + + + + + + 800 + 600 + 5 + True + True + normal + True + True + + + True + vertical + 2 + + + True + vertical + + + True + 7 + end + + + + + + Open Selected File(s) + True + True + True + image1 + True + + + + False + False + 1 + + + + + gtk-cancel + True + True + True + True + + + + False + False + 2 + + + + + False + False + 0 + + + + + 150 + True + 0 + + + True + + + True + True + automatic + automatic + + + True + True + 2 + True + False + False + horizontal + + + + + 0 + + + + + True + vertical + + + + + + + + + + + + True + True + True + + + + True + gtk-go-up + 2 + + + + + 3 + + + + + True + True + True + + + + True + gtk-go-down + 2 + + + + + False + False + 4 + + + + + False + False + 1 + + + + + True + vertical + + + True + True + True + + + + True + gtk-new + 2 + + + + + 0 + + + + + True + True + True + + + + True + gtk-add + 2 + + + + + 1 + + + + + True + True + True + + + + True + gtk-remove + 2 + + + + + 2 + + + + + False + False + 2 + + + + + + + True + <b>Input files</b> + True + + + + + 1 + + + + + 2 + + + + + True + end + + + Open File(s) in List + True + True + True + image2 + True + + + + False + False + 0 + + + + + gtk-cancel + True + True + True + True + + + + False + False + 1 + + + + + False + False + end + 0 + + + + + + button21 + button2 + + + + True + gtk-ok + + + True + gtk-ok + + diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/nofile.pdf b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/nofile.pdf new file mode 100644 index 0000000..288cd1d Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/nofile.pdf differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.desktop b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.desktop new file mode 100644 index 0000000..b92fb57 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=2.0 +Name=PDF-Booklet +Comment=PDF Merging, Rearranging, Splitting, Rotating and Cropping +Type=Application +Exec=pdfbooklet +Icon=pdfbooklet +Categories=Office; +Terminal=false +StartupNotify=false diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.ico b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.ico new file mode 100644 index 0000000..5291466 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.ico differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.png b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.png new file mode 100644 index 0000000..cac673c Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.png differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.svg b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.svg new file mode 100644 index 0000000..da24e38 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet.svg @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + PDF + PDF + PDF-Shuffler + + diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet3.glade b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet3.glade new file mode 100644 index 0000000..667ba71 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfbooklet3.glade @@ -0,0 +1,3519 @@ + + + + + False + 5 + False + True + dialog + True + False + window1 + Pdf-Booklet + 3.0.6 + http://pdfbooklet.sourceforge.net/ + http://pdfbooklet.sourceforge.net/ + Pdf-Shuffler is delivered under the GNU General Public Licence (GPL) Version 3. +Pdf-Booklet is delivered under the CeCill Licence. + Mathieu Fenniak for the pyPdf library +Konstantinos Poulios for pdfShuffler +Averell 7 for Pdf-Booklet + pdfbooklet.ico + + + + + True + False + 2 + + + True + False + end + + + False + True + end + 0 + + + + + + + + + + + -360 + 360 + 1 + 10 + + + 1 + 100 + 1 + 1 + 10 + + + 10000 + 100 + 1 + 10 + + + 10000 + 100 + 1 + 10 + + + 10000 + 100 + 1 + 10 + + + 10000 + 100 + 1 + 10 + 10 + + + -10000 + 10000 + 1 + 10 + + + -10000 + 10000 + 1 + 10 + + + -10000 + 10000 + 1 + 10 + + + -10000 + 10000 + 1 + 10 + + + -360 + 360 + 1 + 10 + + + 1 + 100 + 2 + 1 + 10 + + + 1 + 1 + 10 + 10 + + + 0.01 + 100 + 1 + 10 + + + 10000 + 100 + 1 + 10 + + + 0.01 + 100 + 1 + 10 + + + 1 + 1 + 10 + 10 + + + 10000 + 100 + 1 + 10 + + + 10000 + 1 + 10 + + + True + False + + + True + False + Copy transformations + True + + + + + + True + False + Paste Transformations + True + + + + + + True + False + Standard Transformations + True + + + True + False + + + True + False + Rotate 0 + True + + + + + + True + False + Rotate 90 + True + + + + + + True + False + Rotate 180 + True + + + + + + True + False + Rotate 270 + True + + + + + + + + + + False + 5 + dialog + + + True + False + 2 + + + True + False + end + + + gtk-ok + True + True + True + True + + + False + False + 0 + + + + + gtk-cancel + True + True + True + True + + + False + False + 1 + + + + + + + + + + + False + True + end + 0 + + + + + True + False + + + True + False + <b>Syntax : </b> +separate items by commas, +or on different lines. + +## : single page +[## - ##] : page range +b : blank page +#b : repeated blank pages. + +<b>Examples : </b> +10, 25, 43 +[10-20] +5b + +<b>For files other than the first, </b> +indicate the number followed by a colon. + +Examples : +2:10, 3:25, 1:43 +2:[10-20] + + + True + + + False + False + 11 + 0 + + + + + 200 + True + True + in + + + 300 + True + True + + + + + True + True + 1 + + + + + False + True + 2 + + + + + compare files + True + True + True + + + + False + False + 3 + + + + + + button2001 + button2010 + + + + False + 5 + dialog + + + True + False + 2 + + + True + False + This is an advanced feature. Please read documentation to use it properly. +Here is a short summary : + +Enter page numbers separated by commas on a separate line for each row +Blank pages can be indicated by a b. + +Example : + +8,1,2,7 +3,6,5,4 + + + + False + True + 0 + + + + + True + False + end + + + gtk-ok + True + True + True + True + + + False + False + 0 + + + + + gtk-cancel + True + True + True + True + + + False + False + 1 + + + + + False + True + end + 1 + + + + + 100 + True + True + + + False + True + 2 + + + + + True + False + + + True + False + Step : + + + False + False + 5 + 0 + + + + + Automatic + True + True + False + 0.5 + True + True + + + False + False + 5 + 1 + + + + + = + True + True + False + 0.5 + True + True + step_auto + + + False + False + 5 + 2 + + + + + 80 + True + True + + + + + False + False + 3 + + + + + False + True + 3 + + + + + + + + + button11 + button12 + + + + True + False + gtk-preferences + 3 + + + True + False + gtk-quit + + + True + False + gtk-help + + + True + False + gtk-open + + + True + False + gtk-save + + + True + False + gtk-save-as + + + True + False + gtk-new + + + True + False + gtk-open + 3 + + + True + False + gtk-edit + 3 + + + True + False + pdfshuffler64.png + + + True + False + gtk-about + + + + + + + + + + + + + False + True + + + + + + True + False + + + True + False + + + True + False + _Files + True + + + True + False + + + _Files + True + False + True + image5 + False + True + + + + + + True + False + + + + + _Open Project + True + False + True + image6 + False + True + + + + + + _Save project + True + False + True + image3 + False + True + + + + + + Save project _as ... + True + False + True + image4 + False + True + + + + + + True + False + + + + + Save settings as _default + True + False + True + image1 + False + True + + + + + + True + False + + + + + _Quit + True + False + True + image10 + False + True + + + + + + True + False + + + + + True + False + True + + + + + + True + False + True + + + + + + True + False + True + + + + + + True + False + True + + + + + + + + + + True + False + _Pages + True + + + True + False + + + _Edit pages list + True + False + True + image7 + False + True + + + + + + _Pages selector + True + False + True + image8 + False + True + + + + + + + + + + True + False + _Display + True + + + True + False + + + True + False + _Hide Numbers + True + + + + + True + False + Disable Automatic _Update + True + + + + + + + + + True + False + _Help + True + + + True + False + + + User's Guide + True + False + True + image11 + False + True + + + + + + _About + True + False + True + image9 + False + accelgroup1 + True + + + + + + + + + + False + Themes + True + + + + + False + True + 0 + + + + + True + True + + + True + True + + + True + False + 0 + + + True + False + + + True + False + + + True + False + 0 + etched-out + + + True + False + + + Single Booklet + True + True + False + 0.5 + True + True + + + + True + True + 0 + + + + + Multiple Booklets + True + True + False + 0.5 + True + radiopreset1 + + + + True + True + 1 + + + + + 2 pages + True + True + False + 0.5 + True + radiopreset1 + + + + True + True + 2 + + + + + x pages in line + True + True + False + 0.5 + True + radiopreset1 + + + + True + True + 3 + + + + + x pages in columns + True + True + False + 0.5 + True + radiopreset1 + + + + True + True + 4 + + + + + x copies + True + True + False + 0.5 + True + radiopreset1 + + + + True + True + 5 + + + + + One Page + True + True + False + 0.5 + True + radiopreset1 + + + + True + True + 6 + + + + + User defined + True + True + False + 0.5 + True + True + radiopreset1 + + + + True + True + 7 + + + + + + + False + False + 0 + + + + + True + False + + + True + False + 0 + etched-out + 0 + + + True + False + 6 + 2 + + + True + True + False + 0.5 + True + presetOrientation1 + + + + 1 + 2 + 4 + 5 + + + + + True + True + False + 0.5 + True + True + + + + 1 + 2 + 3 + 4 + + + + + True + False + 1 + 5 + Step : + + + 5 + 6 + + + + + True + False + 1 + 5 + Type : Calendar + + + 4 + 5 + + + + + True + False + 1 + 5 + Type : Book + + + 3 + 4 + + + + + True + False + 1 + 5 + Leafs in a booklet : + + + 2 + 3 + + + + + True + False + 1 + 5 + columns : + + + 1 + 2 + + + + + True + False + 1 + 5 + rows : + + + + + 70 + True + True + + adjustment10 + + + + 1 + 2 + + + + + + 70 + True + True + + adjustment9 + + + + 1 + 2 + 1 + 2 + GTK_EXPAND + + + + + 70 + True + True + + adjustment_leafs + + + 1 + 2 + 2 + 3 + GTK_EXPAND + + + + + 70 + True + True + + adjustment_step + + + 1 + 2 + 5 + 6 + GTK_EXPAND + + + + + + + False + False + 0 + + + + + True + False + 0 + etched-out + 1 + + + True + False + Nothing loaded + + + + + True + True + 1 + + + + + False + False + 1 + + + + + True + True + 0 + + + + + True + False + 0 + + + True + False + + + True + False + + + Automatic + True + True + False + 0.5 + True + True + + + False + True + 0 + + + + + True + False + 0 + etched-out + 1 + + + True + False + + + + + + True + True + 1 + + + + + False + True + 0 + + + + + No change + True + True + False + 0.5 + True + radiosize1 + + + False + True + 1 + + + + + True + False + + + user defined (W / H) + True + True + False + 0.5 + True + radiosize1 + + + False + True + 0 + + + + + True + True + 5 + + 5 + + + False + False + 8 + 1 + + + + + True + False + x + + + False + False + 2 + + + + + True + True + 5 + + 5 + + + False + False + 8 + 3 + + + + + True + False + 0.08999999612569809 + mm + + + False + False + 4 + + + + + False + False + 2 + + + + + Auto scale + True + True + False + 0.5 + right + True + True + + + False + True + 3 + + + + + + + + + + True + False + <b>Output size</b> + True + + + + + False + False + 9 + 1 + + + + + True + False + + + + True + True + 2 + + + + + + + + + + True + False + <b>Disposition</b> + True + + + + + + + True + False + Layout + + + False + + + + + True + False + + + True + False + + + True + False + 0 + in + + + True + False + + + All pages in this position + True + True + False + The tranformations will be applied to all pages which have the same position in the layout. +Example : all top left pages, or all bottom right pages. + 0.5 + True + True + + + + True + True + 0 + + + + + This page only + True + True + False + Transformations are applied only to the selected page. + 0.5 + True + thisposition + + + + True + True + 1 + + + + + Even pages + True + True + False + Transformations are applied to all even pages. + 0.5 + True + thisposition + + + + True + True + 2 + + + + + Odd pages + True + True + False + Transformations are applied to all odd pages. + 0.5 + True + thisposition + + + + True + True + 3 + + + + + + + True + False + <b>Domain</b> + True + + + + + True + True + 0 + + + + + True + False + Choose the domain +<b><span foreground="blue" size="large">before</span></b> +applying +tranformations. + + True + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + 8 + 3 + + + + + + + + + + + + + + + + + + + + + True + False + 1 + Vertical Shift (mm) + + + 1 + 2 + + + + + True + False + 1 + Scale (%) + + + 2 + 3 + + + + + True + False + 1 + Rotation (degrees) + + + 3 + 4 + + + + + True + False + 1 + Horizontal Shift (mm) + + + + + 70 + True + True + + 5 + adjustment4 + 0.01 + 1 + + + + + + 1 + 2 + GTK_EXPAND + + + + + 70 + True + True + + 5 + adjustment3 + 1 + + + + + + 1 + 2 + 1 + 2 + GTK_EXPAND + + + + + 70 + True + True + + 5 + adjustment_scale + + + + + + 1 + 2 + 2 + 3 + GTK_EXPAND + + + + + 70 + True + True + + 5 + adjustment1 + + + + + 1 + 2 + 3 + 4 + GTK_EXPAND + + + + + True + False + 1 + Vertical Flip + + + 4 + 5 + + + + + True + False + 1 + Horizontal Flip + + + 5 + 6 + + + + + True + False + 1 + Scale Horizontally + + + 6 + 7 + + + + + True + False + 1 + Scale Vertically + + + 7 + 8 + + + + + 70 + True + True + + 5 + adjustment_xscale1 + + + + + 1 + 2 + 6 + 7 + GTK_EXPAND + + + + + 70 + True + True + + 5 + adjustment_yscale1 + + + + + 1 + 2 + 7 + 8 + GTK_EXPAND + + + + + + True + True + False + 0.5 + True + + + + 1 + 2 + 4 + 5 + GTK_EXPAND + + + + + + True + True + False + 0.5 + True + + + + 1 + 2 + 5 + 6 + GTK_EXPAND + + + + + True + True + True + Activate this button to enable +moving the pages with the mouse. + 0 + + + True + False + gtk-fullscreen + 2 + + + + + 2 + 3 + 2 + GTK_EXPAND + + + + + + False + False + 10 + 1 + + + + + True + False + + + + + + True + True + 2 + + + + + True + False + + + Apply + True + True + True + + + + True + True + 0 + + + + + Reset + True + True + True + + + + True + True + 1 + + + + + + + + False + False + 9 + 3 + + + + + 1 + + + + + True + False + Pages +Transformations + center + + + 1 + False + + + + + True + False + + + True + False + 0 + + + True + False + 8 + 2 + + + True + False + 1 + Vertical Shift (mm) + + + 1 + 2 + + + + + True + False + 1 + Scale (%) + + + 2 + 3 + + + + + True + False + 1 + Rotation (degrees) + + + 3 + 4 + + + + + True + True + 3 + + 5 + adjustment7 + True + True + True + + + + + + 1 + 2 + 3 + 4 + GTK_EXPAND + + + + + True + False + 1 + Horizontal Shift (mm) + + + + + True + True + + 5 + adjustment6 + 1 + + + + + + 1 + 2 + GTK_EXPAND + + + + + True + True + + 5 + adjustment5 + 1 + + + + + + 1 + 2 + 1 + 2 + GTK_EXPAND + + + + + True + True + + 5 + adjustment11 + + + + + + 1 + 2 + 2 + 3 + GTK_EXPAND + + + + + True + False + 1 + Vertical Flip + + + 4 + 5 + + + + + True + False + 1 + Horizontal Flip + + + 5 + 6 + + + + + True + False + 1 + Scale Horizontally + + + 6 + 7 + + + + + True + False + 1 + Scale Vertically + + + 7 + 8 + + + + + True + True + + 5 + adjustment12 + + + + + 1 + 2 + 6 + 7 + GTK_EXPAND + + + + + True + True + + 5 + adjustment13 + + + + + 1 + 2 + 7 + 8 + GTK_EXPAND + + + + + + True + True + False + 0.5 + True + + + + 1 + 2 + 4 + 5 + GTK_EXPAND + + + + + + True + True + False + 0.5 + True + + + + 1 + 2 + 5 + 6 + GTK_EXPAND + + + + + + + True + False + <b>Output page transformations</b> + True + + + + + False + True + 0 + + + + + True + False + 0 + + + True + False + 12 + + + True + False + + + 0 + True + True + False + 0.5 + True + True + + + + False + True + 0 + + + + + 90 + True + True + False + 0.5 + True + globalRotation0 + + + + False + True + 10 + 1 + + + + + 180 + True + True + False + 0.5 + True + globalRotation0 + + + + False + True + 2 + + + + + 270 + True + True + False + 0.5 + True + globalRotation0 + + + + False + False + 10 + 3 + + + + + + + + + True + False + <b>Global rotation</b> + True + + + + + False + True + 1 + + + + + True + False + + + + + + Reset All + 100 + True + True + True + + + + False + False + end + 1 + + + + + False + False + 2 + + + + + + + + 2 + + + + + True + False + Global +Transformations + center + + + 2 + False + + + + + True + True + + + True + False + + + True + False + 0 + + + True + False + 3 + 3 + + + + + + + + + True + False + Trailing blank pages + + + 1 + 2 + GTK_FILL + + + + + True + True + + 4 + 0 + + + 1 + 2 + 1 + 2 + + + + + + + True + True + + 4 + 0 + + + 1 + 2 + + GTK_EXPAND + 4 + + + + + True + False + Leading blank pages + + + GTK_FILL + 6 + + + + + Right to left + True + True + False + 0.5 + True + + + 2 + 3 + GTK_FILL + + + + + True + False + + + + 2 + 3 + + + + + True + False + + + + 2 + 3 + 1 + 2 + + + + + + + True + False + <b>Page setup</b> + True + + + + + False + False + 0 + + + + + + + + True + False + 0 + + + True + False + 12 + + + True + False + 3 + 3 + + + + + + + + + + + + True + False + Reference page + + + GTK_FILL + 7 + + + + + True + False + 3 + Creep adjustment + + + 1 + 2 + GTK_FILL + + + + + True + True + + 6 + 1 + + + 1 + 2 + GTK_FILL + + + + + True + True + + 6 + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + False + 0 + 3 + mm + + + 2 + 3 + 1 + 2 + + + + + + + + + + + + True + False + <b>Advanced</b> + True + + + + + True + True + 4 + + + + + + + True + False + Page setup + + + False + + + + + True + False + + + True + False + 0 + + + True + False + 12 + + + True + False + + + True + False + + + True + False + Output file + + + False + True + 0 + + + + + True + True + + %F-bklt.pdf + + + False + False + 1 + + + + + ... + True + True + True + + + False + False + 2 + + + + + False + False + 0 + + + + + True + False + 6 + 2 + + + + + + + + + + + + + + + + + + + + + + + + Show Pdf + True + True + False + 0.5 + True + True + + + + + Overwrite output files + True + True + False + 0.5 + True + + + 2 + 3 + + + + + Save settings with pdf + True + True + False + 0.5 + True + + + 1 + 2 + + + + + Don't compress output + True + True + False + 0.5 + True + + + 3 + 4 + + + + + Safe mode (slow) + True + True + False + 0.5 + True + + + 4 + 5 + + + + + False + True + 1 + + + + + + + + + True + False + <b>Output</b> + True + + + + + True + True + 0 + + + + + + + + + + + 1 + + + + + True + False + Output + + + 1 + False + + + + + True + False + + + True + False + New features under development +May work or not... + center + + + + + + + + True + True + 0 + + + + + True + False + + + Delete rectangle + True + True + True + + + False + False + 0 + + + + + Preview + True + True + False + 0.5 + True + + + True + True + 1 + + + + + True + False + 1 + + + + + True + False + + + Diviser un cahier + True + True + True + True + + + False + False + 0 + + + + + middle line + True + True + False + 0.5 + True + + + True + True + 1 + + + + + + + + True + False + 2 + + + + + True + False + 0 + + + True + False + 6 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + False + Bottom margin + + + 2 + 3 + GTK_FILL + + + + + True + True + + 4 + 0 + True + + + 1 + 2 + 2 + 3 + + + + + + + True + True + + 4 + 0 + True + + + 3 + 4 + 1 + 2 + + GTK_EXPAND + 4 + + + + + + + + + + + True + False + Start from page : + + + 2 + 3 + 1 + 2 + GTK_FILL + 6 + + + + + + + + + + + Add page numbers + True + True + False + 0 + True + + + GTK_FILL + + + + + True + False + Font size + + + 1 + 2 + + + + + True + True + + 4 + 0 + True + + + 1 + 2 + 1 + 2 + + + + + + + + + + + + + + + True + False + <b>Page numbers (Experimental)</b> + True + + + + + True + True + 9 + 3 + + + + + 2 + + + + + True + False + Experimental + + + 2 + False + + + + + 3 + + + + + True + False + Options + + + 3 + False + + + + + False + True + + + + + True + False + + + True + False + + + + + + True + False + + + + + True + True + 0 + + + + + True + False + + + << + True + True + True + + + + False + False + 0 + + + + + < + True + True + True + + + + + False + False + 1 + + + + + 50 + True + True + + 4 + + + + False + True + 2 + + + + + > + True + True + True + + + + + False + False + 3 + + + + + >> + True + True + True + + + + False + False + 4 + + + + + Update + True + True + True + + + + False + False + 5 + + + + + + + + Go + 50 + True + True + True + + + + True + True + 7 + + + + + True + False + --- + + + True + True + 8 + + + + + False + False + 1 + + + + + True + True + + + + + True + True + 1 + + + + + + + False + + + True + False + + + + + + + + + True + False + 9 + end + + + + + + gtk-cancel + True + True + True + True + + + + False + False + 1 + + + + + gtk-ok + True + True + True + True + + + + False + False + 2 + + + + + False + True + 2 + + + + + + diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler.ico b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler.ico new file mode 100644 index 0000000..4e55a27 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler.ico differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler64.ico b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler64.ico new file mode 100644 index 0000000..9ee3785 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler64.ico differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler64.png b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler64.png new file mode 100644 index 0000000..739d50e Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler64.png differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler_g.glade b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler_g.glade new file mode 100644 index 0000000..cc5750a --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/data/pdfshuffler_g.glade @@ -0,0 +1,968 @@ + + + + + -24 + 5 + -14 + 1 + 10 + + + 100 + 1 + 10 + 10 + + + False + 5 + normal + + + True + False + vertical + 2 + + + True + False + end + + + button + True + True + True + + + False + False + 0 + + + + + button + True + True + True + + + False + False + 1 + + + + + False + True + end + 0 + + + + + True + False + 5 + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + + + + 1 + 2 + 1 + 2 + + + + + True + True + + + + 1 + 2 + 2 + 3 + + + + + True + True + + + + 1 + 2 + 3 + 4 + + + + + True + True + + + + 1 + 2 + 4 + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + True + 1 + + + + + + button1 + button2 + + + + True + False + gtk-redo + + + True + False + gtk-zoom-in + + + True + False + gtk-zoom-out + + + True + False + gtk-leave-fullscreen + + + True + False + gtk-undo + + + True + False + gtk-redo + + + True + False + gtk-new + + + True + False + gtk-new + + + True + False + gtk-about + + + True + False + gtk-undo + + + True + False + gtk-leave-fullscreen + + + True + False + gtk-new + + + True + False + + + Rotate _Right + True + False + True + image1 + False + + + + + + Rotate _Left + True + False + True + image2 + False + + + + + + C_rop + True + False + True + image3 + False + + + + + + gtk-clear + True + False + True + True + + + + + + Add _Blank Page Before + True + False + True + image4 + False + + + True + False + + + True + False + _1 + True + + + + + + True + False + _2 + True + + + + + + True + False + _3 + True + + + + + + True + False + _4 + True + + + + + + + + + + True + False + Add Blank Page _After + True + + + True + False + + + True + False + _1 + True + + + + + + True + False + _2 + True + + + + + + True + False + _3 + True + + + + + + True + False + _4 + True + + + + + + + + + + True + False + gtk-open + + + True + False + gtk-save + + + True + False + gtk-save-as + + + True + False + gtk-quit + + + True + False + gtk-delete + + + False + + + True + False + + + True + False + + + True + False + _File + True + + + True + False + + + _Open + True + False + True + image5 + False + + + + + + _Save + True + False + False + True + image6 + False + + + + + Save _As... + True + False + True + image7 + False + + + + + + True + False + + + + + _Quit + True + False + True + image8 + False + + + + + + + + + + True + False + _Edit + True + + + True + False + + + Rotate _Left + True + False + True + image13 + False + + + + + + Rotate _Right + True + False + True + image14 + False + + + + + + Add blank page _before + True + False + True + image15 + False + + + True + False + + + True + False + _1 + True + + + + + + True + False + _2 + True + + + + + + True + False + _3 + True + + + + + + True + False + _4 + True + + + + + + + + + + Add blank page _after + True + False + True + image16 + False + + + True + False + + + True + False + _1 + True + + + + + + True + False + _2 + True + + + + + + True + False + _3 + True + + + + + + True + False + _4 + True + + + + + + + + + + True + False + + + + + _Delete + True + False + True + image9 + False + + + + + + _Crop + True + False + True + image12 + False + + + + + + + + + + True + False + _View + True + + + True + False + + + Zoom _in + True + False + True + image10 + False + + + + + + Zoom _out + True + False + True + image11 + False + + + + + + + + + + True + False + _Help + True + + + True + False + + + _About + True + False + True + image17 + False + + + + + + + + + + False + True + 0 + + + + + True + False + + + True + False + icons + False + 4 + + + True + False + Open + True + gtk-open + + + + False + True + + + + + True + False + Save + True + gtk-save + + + + False + True + + + + + True + False + Save as + True + gtk-save-as + + + + False + True + + + + + True + False + + + False + True + + + + + True + False + Zoom in + True + gtk-zoom-in + + + + False + True + + + + + True + False + Zoom out + True + gtk-zoom-out + + + + False + True + + + + + True + False + + + False + True + + + + + True + False + Rotate left + True + gtk-undo + + + + False + True + + + + + True + False + Rotate right + True + gtk-redo + + + + False + True + + + + + True + False + Crop + True + gtk-leave-fullscreen + + + + False + True + + + + + True + False + Delete + True + gtk-delete + + + + False + True + + + + + True + True + 0 + + + + + True + True + adjustment1 + on + off + False + 5 + 2 + False + right + + + + + True + True + 1 + + + + + False + True + 1 + + + + + True + True + + + + + + True + True + 2 + + + + + True + False + + + False + False + 3 + + + + + + diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Manuel_de_Pdf-Booklet.pdf b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Manuel_de_Pdf-Booklet.pdf new file mode 100644 index 0000000..dc404ae Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Manuel_de_Pdf-Booklet.pdf differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Note_for_Linux_users_3.0.6.html b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Note_for_Linux_users_3.0.6.html new file mode 100644 index 0000000..15264ea --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Note_for_Linux_users_3.0.6.html @@ -0,0 +1,97 @@ + + + + + + + +Important Note for Linux users + + +

Important Note for Linux users

+

You will find the +last version of Linux installers in the beta directory.
+

+

pdfbooklet_3.0.6_all.deb hs been tested and seems OK.
+

+

The .rpm installer has not been tested.
+

+

For the tar.gz files, see the dependencies below.
+
+

+ +


+

+

PdfBooklet is developed originally in Windows and for Windows. +Nevertheless it works perfectly on Linux, we have made the code +compatible. It runs in Python 2.7 or 3.4.

+ +


+

+

1) Installation

+

You sould normally use the .deb file which should work out of the box.
+The .rpm file is not yet tested, and the dependencies are not properly set.
+

+ +

 
+

+

In case these installers do not work for you, we are providing other solutions. 

+

a) Install from source

+

Decompress the file : Install by unzipping – linux.zip in a directory where you have full write rights. Run pdfbooklet.py with the following command :

+

 

+

python pdfbooklet.py

+

or

+

python3 pdfbooklet.py

+

 

+

If you have the dependencies satisfied, the program should run. If +not, either install the missing packages, if you know how to do that, +or use the second solution :

+

 

+

b) Install the pyinstaller bundle

+

To solve the dependencies problem, we have built a bundle with +pyinstaller, which contains everything needed : Python3, Gtk3, Poppler. +

+

Unzip the big file : pdfbooklet 3.x.x pyinstaller.zip, in a directory where you have write rights.

+

Run the file : pdfbooklet

+

c) Install from tarball

+

We offer also the tarballs in 32 and 64 bits if you know how to install them.

+

2) Known problems
+

+
    +
  1. +

    Dependencies problems : see below the modules which must be installed. Otherwise consider using the pyinstaller bundle.

    +
  2. +
  3. +

    pdfbooklet needs read/write rights in the forder where +pdfbooklet.cfg is located and the installer does not always provides +this.
    +

    +
  4. +
+ +

Technical information

+

 

+

       The needed dependencies for python3 are :
+ python-gi
+ python-gi-cairo
+

+

 python3-gi
+ python3-cairo
+

+

 python3-gi-cairo

+

 gir1.2-gtk-3.0

+

 gir1.2-poppler-0.18
+       They are installed with the following commande line :
+

+ +

 - sudo apt-get install  python-gi  python-gi-cairo python3-gi python3-cairo  python3-gi-cairo  gir1.2-gtk-3.0  gir1.2-poppler-0.18

+


+

+ +

 

+

For python 2.7 :

+

- sudo apt-get install  python-gobject-cairo

+

 

+

 

+

 

+ \ No newline at end of file diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Pdf-Booklet_User's_Guide.pdf b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Pdf-Booklet_User's_Guide.pdf new file mode 100644 index 0000000..235e0cc Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/documentation/Pdf-Booklet_User's_Guide.pdf differ diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/icons/hicolor/scalable/pdfbooklet.svg b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/icons/hicolor/scalable/pdfbooklet.svg new file mode 100644 index 0000000..da24e38 --- /dev/null +++ b/pdfbooklet_3.1.2-3_all/usr/share/pdfbooklet/icons/hicolor/scalable/pdfbooklet.svg @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + PDF + PDF + PDF-Shuffler + + diff --git a/pdfbooklet_3.1.2-3_all/usr/share/pixmaps/pdfbooklet.png b/pdfbooklet_3.1.2-3_all/usr/share/pixmaps/pdfbooklet.png new file mode 100644 index 0000000..ce15142 Binary files /dev/null and b/pdfbooklet_3.1.2-3_all/usr/share/pixmaps/pdfbooklet.png differ