~ubuntu-branches/ubuntu/vivid/pdfposter/vivid-proposed

« back to all changes in this revision

Viewing changes to .pc/PyPDF2_migration.diff/pdftools/pdfposter/__init__.py

  • Committer: Package Import Robot
  • Author(s): Elena Grandi
  • Date: 2014-09-18 18:51:17 UTC
  • Revision ID: package-import@ubuntu.com-20140918185117-41wdx3poidxhg5l6
Tags: 0.6.0-2
* Migrate from PyPdf to PyPDF2 (Closes: #763256)
* Update maintainer email
* debian/control: bump Standards-Version to 3.9.6 (no changes needed).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
"""
 
3
pdftools.pdfposter - scale and tile PDF images/pages to print on multiple pages.
 
4
"""
 
5
#
 
6
# Copyright 2008-2013 by Hartmut Goebel <h.goebel@crazy-compilers.com>
 
7
#
 
8
# This program is free software: you can redistribute it and/or modify
 
9
# it under the terms of the GNU General Public License as published by
 
10
# the Free Software Foundation, either version 3 of the License, or
 
11
# (at your option) any later version.
 
12
#
 
13
# This program is distributed in the hope that it will be useful, but
 
14
# WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 
16
# General Public License for more details.
 
17
#
 
18
# You should have received a copy of the GNU General Public License
 
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 
20
#
 
21
 
 
22
__author__ = "Hartmut Goebel <h.goebel@crazy-compilers.com>"
 
23
__copyright__ = "Copyright 2008-2013 by Hartmut Goebel <h.goebel@crazy-compilers.com>"
 
24
__licence__ = "GNU General Public License version 3 (GPL v3)"
 
25
__version__ = "0.6.0"
 
26
 
 
27
# ignore some warnings for pyPDF < 1.13
 
28
import warnings
 
29
warnings.filterwarnings('ignore', "the sets module is deprecated")
 
30
warnings.filterwarnings('ignore', "the md5 module is deprecated")
 
31
 
 
32
from pyPdf.pdf import PdfFileWriter, PdfFileReader, PageObject, getRectangle, \
 
33
     ArrayObject, ContentStream, NameObject, FloatObject, RectangleObject
 
34
 
 
35
import logging
 
36
from logging import log
 
37
import math
 
38
 
 
39
DEFAULT_MEDIASIZE = 'a4'
 
40
 
 
41
mm = 72 / 25.4
 
42
 
 
43
# Taken from poster.c
 
44
papersizes = {
 
45
    'pt'  : (1, 1),
 
46
    'inch': (72, 72),
 
47
    'ft'  : (864, 864), # 12 inch
 
48
    'mm'  : (mm, mm),
 
49
    'cm'  : (10 *mm, 10 *mm),
 
50
    'meter':(1000* mm, 1000* mm),
 
51
 
 
52
    # American page sizes (taken from psposter.c)
 
53
    "monarch"  : (279, 540),
 
54
    "statement": (396, 612),
 
55
    "executive": (540, 720),
 
56
    "quarto"   : (610, 780),
 
57
    "letter"   : (612, 792),
 
58
    "folio"    : (612, 936),
 
59
    "legal"    : (612, 1008),
 
60
    "tabloid"  : (792, 1224),
 
61
    "ledger"   : (792, 1224),
 
62
 
 
63
    # ISO page sizes (taken from psposter.c)
 
64
    "a0" : (2384, 3370),
 
65
    "a1" : (1684, 2384),
 
66
    "a2" : (1191, 1684),
 
67
    "a3" : (842, 1191),
 
68
    "a4" : (595, 842),
 
69
    "a5" : (420, 595),
 
70
    "a6" : (298, 420),
 
71
    "a7" : (210, 298),
 
72
    "a8" : (147, 210),
 
73
    "a9" : (105, 147),
 
74
    "a10": (74, 105),
 
75
 
 
76
    "dinlang"   : (281, 595), # 1/3 a4
 
77
    "envdinlang": (312, 624), # envelobe for din-lang
 
78
 
 
79
    "b0" : (2835, 4008),
 
80
    "b1" : (2004, 2835),
 
81
    "b2" : (1417, 2004),
 
82
    "b3" : (1001, 1417),
 
83
    "b4" : (709, 1001),
 
84
    "b5" : (499, 709),
 
85
    "b6" : (354, 499),
 
86
    "b7" : (249, 354),
 
87
    "b8" : (176, 249),
 
88
    "b9" : (125, 176),
 
89
    "b10": (88, 125),
 
90
 
 
91
    "c4" : (649, 918),
 
92
    "c5" : (459, 649),
 
93
    "c6" : (323, 459),
 
94
 
 
95
    # Japanese page sizes (taken from psposter.c)
 
96
    "jb0" : (2920, 4127),
 
97
    "jb1" : (2064, 2920),
 
98
    "jb2" : (1460, 2064),
 
99
    "jb3" : (1032, 1460),
 
100
    "jb4" : (729, 1032),
 
101
    "jb5" : (516, 729),
 
102
    "jb6" : (363, 516),
 
103
    "jb7" : (258, 363),
 
104
    "jb8" : (181, 258),
 
105
    "jb9" : (128, 181),
 
106
    "jb10": (91, 128),
 
107
 
 
108
    "comm10": (298, 684),
 
109
    "com10" : (298, 684),
 
110
    "env10" : (298, 684),
 
111
    }
 
112
 
 
113
class DecryptionError(ValueError): pass
 
114
    
 
115
 
 
116
PAGE_BOXES = ("/MediaBox", "/CropBox", "/BleedBox", "/TrimBox", "/ArtBox")
 
117
 
 
118
def rectangle2box(pdfbox):
 
119
    return {
 
120
        'width'   : pdfbox.getUpperRight_x()-pdfbox.getLowerLeft_x(),
 
121
        'height'  : pdfbox.getUpperRight_y()-pdfbox.getLowerLeft_y(),
 
122
        'offset_x': pdfbox.getLowerLeft_x(),
 
123
        'offset_y': pdfbox.getLowerLeft_y(),
 
124
        # the following are unused, but need to be set to make
 
125
        # `rotate_box()` work
 
126
        'units_x' : None,
 
127
        'units_y' : None,
 
128
        }
 
129
 
 
130
def rotate_box(box):
 
131
    for a,b in (
 
132
        ('width', 'height'),
 
133
        ('offset_x', 'offset_y'),
 
134
        ('units_x', 'units_y')):
 
135
        box[a], box[b] = box[b], box[a]
 
136
 
 
137
def rotate2portrait(box, which):
 
138
    'if box is landscape spec, rotate to portrait'
 
139
    if (  box['width' ]-box['offset_x'] 
 
140
        > box['height']-box['offset_y']):
 
141
        rotate_box(box)
 
142
        log(18, 'Rotating %s specs to portrait format', which)
 
143
        return True
 
144
 
 
145
def decide_num_pages(inbox, mediabox, posterbox, scale=None):
 
146
    """decide on number of pages"""
 
147
    # avoid changing original posterbox when handling multiple pages
 
148
    # (if --scale, posterbox is None)
 
149
    posterbox = posterbox and posterbox.copy()
 
150
    cutmargin   = {'x': 0, 'y': 0} # todo
 
151
    whitemargin = {'x': 0, 'y': 0} # todo
 
152
    # media and image sizes (inbox) are fixed already
 
153
    # available drawing area per sheet
 
154
    drawable_x = mediabox['width' ] - 2*cutmargin['x']
 
155
    drawable_y = mediabox['height'] - 2*cutmargin['y']
 
156
 
 
157
    rotate = False
 
158
 
 
159
    inbox_x = float(inbox['width' ])
 
160
    inbox_y = float(inbox['height'])
 
161
    log(17, 'input  dimensions: %.2f %.2f (trimbox of input page)',
 
162
            inbox_x, inbox_y)
 
163
 
 
164
    if not scale:
 
165
        # user did not specify scale factor, calculate from output size
 
166
        # todo: fix assuming posterbox offset = 0,0
 
167
        log(17, 'output dimensions: %.2f %.2f (poster size)',
 
168
            posterbox['width'], posterbox['height'])
 
169
 
 
170
        # ensure poster spec are portrait
 
171
        if rotate2portrait(posterbox, 'poster'):
 
172
            rotate = rotate != True # xor
 
173
 
 
174
        # if the input page has landscape format rotate the
 
175
        # poster spec to landscape, too
 
176
        if inbox_x > inbox_y:
 
177
            log(18, 'Rotating poster specs since input page is landscape')
 
178
            rotate = rotate != True # xor
 
179
            rotate_box(posterbox)
 
180
            log(18, 'rotated output dimensions: %.2f %.2f (poster size)',
 
181
                posterbox['width'], posterbox['height'])
 
182
 
 
183
        scale = min(posterbox['width' ] / inbox_x,
 
184
                    posterbox['height'] / inbox_y)
 
185
        log(18, 'Calculated page scaling factor: %f', scale)
 
186
 
 
187
    # use round() to avoid floating point roundup errors
 
188
    size_x = round(inbox_x*scale - whitemargin['x'], 4)
 
189
    size_y = round(inbox_y*scale - whitemargin['y'], 4)
 
190
    log(17, 'output dimensions: %.2f %.2f (calculated)', size_x, size_y)
 
191
 
 
192
    # num pages without rotation
 
193
    nx0 = int(math.ceil( size_x / drawable_x))
 
194
    ny0 = int(math.ceil( size_y / drawable_y))
 
195
    # num pages with rotation
 
196
    nx1 = int(math.ceil( size_x / drawable_y))
 
197
    ny1 = int(math.ceil( size_y / drawable_x))
 
198
 
 
199
    log(17, 'Pages w/o rotation %s x %s' , nx0, ny0)
 
200
    log(17, 'Pages w/  rotation %s x %s' , nx1, ny1)
 
201
 
 
202
    # Decide for rotation to get the minimum page count.
 
203
    # (Rotation is considered as media versus input page, which is
 
204
    # totally independent of the portrait or landscape style of the
 
205
    # final poster.)
 
206
    rotate = (rotate and (nx0*ny0) == (nx1*ny1)) or (nx0*ny0) > (nx1*ny1)
 
207
    log(17, 'Decided for rotation: %s', rotate and 'yes' or 'no')
 
208
 
 
209
    if rotate:
 
210
        ncols = nx1
 
211
        nrows = ny1
 
212
    else:
 
213
        ncols = nx0
 
214
        nrows = ny0
 
215
 
 
216
    log(19, "Deciding for %d column%s and %d row%s of %s pages.",
 
217
            ncols, (ncols==1) and "s" or "",
 
218
            nrows, (nrows==1) and "s" or "",
 
219
            rotate and "landscape" or "portrait")
 
220
    return ncols, nrows, scale, rotate
 
221
 
 
222
 
 
223
def copyPage(page):
 
224
    from pyPdf.pdf import RectangleObject, NameObject
 
225
    newpage = PageObject(page.pdf)
 
226
    newpage.update(page)
 
227
    # Copy Rectangles to be manipulatable
 
228
    for attr in PAGE_BOXES:
 
229
        if page.has_key(attr):
 
230
            newpage[NameObject(attr)] = RectangleObject(list(page[attr]))
 
231
    return newpage
 
232
 
 
233
def _clip_pdf_page(page, x, y, width, height):
 
234
    content = ContentStream(page["/Contents"].getObject(), page.pdf)
 
235
    content.operations[:0] = [
 
236
        ([], 'q'), # save graphic state
 
237
        ([], 'n'), # cancel path w/ filling or stroking
 
238
        (RectangleObject((x, y, width, height)), 're'), # rectangle path
 
239
        ([], 'W*'), # clip
 
240
        ]
 
241
    content.operations.append([[], "Q"]) # restore graphic state
 
242
    page[NameObject('/Contents')] = content
 
243
 
 
244
 
 
245
def _scale_pdf_page(page, factor):
 
246
    for boxname in PAGE_BOXES:
 
247
        # skip if real box does not exits (avoid fallback to other boxes)
 
248
        if not page.get(boxname):
 
249
            continue
 
250
        box = getRectangle(page, boxname, None)
 
251
        box.lowerLeft  = [float(i) * factor for i in box.lowerLeft ]
 
252
        box.upperRight = [float(i) * factor for i in box.upperRight]
 
253
        #print boxname, type(box), box
 
254
    # put transformation matrix in front of page content
 
255
    content = ContentStream(page["/Contents"].getObject(), page.pdf)
 
256
    content.operations.insert(0, [[], '%f 0 0 %f 0 0 cm' %(factor,factor)] )
 
257
    page[NameObject('/Contents')] = content
 
258
 
 
259
 
 
260
def posterize(outpdf, page, mediabox, posterbox, scale, use_ArtBox=False):
 
261
    """
 
262
    page: input page
 
263
    mediabox : size secs of the media to print on
 
264
    posterbox: size secs of the resulting poster
 
265
    scale: scale factor (to be used instead of posterbox)
 
266
    """
 
267
    if use_ArtBox:
 
268
        inbox = rectangle2box(page.artBox)
 
269
    else:
 
270
        inbox = rectangle2box(page.trimBox)
 
271
    _clip_pdf_page(page, inbox['offset_x'], inbox['offset_y'],
 
272
                   inbox['width'], inbox['height'])
 
273
    ncols, nrows, scale, rotate = decide_num_pages(inbox, mediabox,
 
274
                                                   posterbox, scale)
 
275
    mediabox = mediabox.copy()
 
276
    _scale_pdf_page(page, scale)
 
277
    if rotate:
 
278
        page.rotateClockwise(90)
 
279
        rotate_box(inbox)
 
280
        rotate_box(mediabox)
 
281
    # area to put on each page (allows for overlay of margin)
 
282
    h_step = mediabox['width']  - mediabox['offset_x']
 
283
    v_step = mediabox['height'] - mediabox['offset_y']
 
284
    
 
285
    if use_ArtBox:
 
286
        trimbox = rectangle2box(page.artBox)
 
287
    else:
 
288
        trimbox = rectangle2box(page.trimBox)
 
289
    h_pos = float(trimbox['offset_x'])
 
290
    h_max, v_max = float(trimbox['width']), float(trimbox['height'])
 
291
    for col in range(ncols):
 
292
        v_pos = float(trimbox['offset_y']) + (nrows-1) * v_step
 
293
        for row in range(nrows):
 
294
            log(17, 'Creating page with offset: %.2f %.2f' % (h_pos, v_pos))
 
295
            newpage = copyPage(page)
 
296
            # todo: if remaining area is smaller than mediaBox, add a
 
297
            # transparent fill box behind, so the real content is in
 
298
            # the lower left corner
 
299
            newpage.mediaBox = RectangleObject((h_pos, v_pos,
 
300
                                                h_pos + h_step,
 
301
                                                v_pos + v_step))
 
302
            newpage.trimBox = RectangleObject((h_pos, v_pos,
 
303
                                               min(h_max, h_pos + h_step),
 
304
                                               min(v_max, v_pos + v_step)))
 
305
            newpage.artBox = newpage.trimBox
 
306
            outpdf.addPage(newpage)
 
307
            v_pos -= v_step
 
308
        h_pos += h_step
 
309
 
 
310
def password_hook():
 
311
    import getpass
 
312
    return getpass.getpass()
 
313
 
 
314
def main(opts, infilename, outfilename, password_hook=password_hook):
 
315
    logging.basicConfig(level=20-opts.verbose, format="%(message)s")
 
316
    outpdf = PdfFileWriter()
 
317
    inpdf = PdfFileReader(open(infilename, 'rb'))
 
318
 
 
319
    if inpdf.isEncrypted:
 
320
        log(16, 'File is encrypted')
 
321
        # try empty password first
 
322
        if not inpdf.decrypt(''):
 
323
            if not inpdf.decrypt(password_hook()):
 
324
                raise DecryptionError("Can't decrypt PDF. Wrong Password?")
 
325
 
 
326
    log(18, 'Mediasize : %(units_x)sx%(units_y)s %(unit)s' % opts.media_size)
 
327
    log(17, '            %(width).2f %(height).2f dots' % opts.media_size)
 
328
    if opts.scale:
 
329
        log(18, 'Scaling by: %f' % opts.scale)
 
330
    else:
 
331
        log(18, 'Postersize: %(units_x)sx%(units_y)s %(unit)s' % opts.poster_size)
 
332
        log(17, '            %(width).2f %(height).2f dots' % opts.poster_size)
 
333
 
 
334
    for i, page in enumerate(inpdf.pages):
 
335
        log(19, '---- processing page %i -----', i+1)
 
336
        posterize(outpdf, page, opts.media_size, opts.poster_size, opts.scale,
 
337
                  opts.use_ArtBox)
 
338
    if not opts.dry_run:
 
339
        outpdf.write(open(outfilename, 'wb'))