Module pdfimposer

Source Code for Module pdfimposer

   1  #!/usr/bin/env python 
   2  # -*- coding: UTF-8 -*- 
   3   
   4  ######################################################################## 
   5  #  
   6  # pdfimposer - achieve some basic imposition on PDF documents 
   7  # Copyright (C) 2008-2012 Kjö Hansi Glaz <kjo@a4nancy.net.eu.org> 
   8  #  
   9  # This program is  free software; you can redistribute  it and/or modify 
  10  # it under the  terms of the GNU General Public  License as published by 
  11  # the Free Software Foundation; either  version 3 of the License, or (at 
  12  # your option) any later version. 
  13  #  
  14  # This program  is distributed in the  hope that it will  be useful, but 
  15  # WITHOUT   ANY  WARRANTY;   without  even   the  implied   warranty  of 
  16  # MERCHANTABILITY  or FITNESS  FOR A  PARTICULAR PURPOSE.   See  the GNU 
  17  # General Public License for more details. 
  18  #  
  19  # You should have received a copy of the GNU General Public License 
  20  # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  21  # 
  22  ######################################################################## 
  23   
  24  ######################################################################## 
  25  # 
  26  # pdfimposer.py 
  27  # 
  28  # This python module enables to change PDF page layout. It is the backend 
  29  # of BookletImposer, but is designed to be easily usable by from any python 
  30  # script. 
  31  # 
  32  ######################################################################## 
  33   
  34  """ 
  35  Converts PDF documents between different page layouts. 
  36   
  37  This module enables to: 
  38   - convert linear (page by page) PDF documents to booklets; 
  39   - revert booklets to linear documents; 
  40   - reduce multiple input PDF pages and put them on one single output page. 
  41   
  42  The `StreamConverter` class works on StreamIO, while the `FileConverter` 
  43  class works on files. 
  44   
  45  Some convenience functions are also provided. 
  46  """ 
  47  # XXX: File should be ASCII 
  48   
  49  from abc import ABCMeta, abstractmethod 
  50   
  51  import re 
  52  import sys 
  53  import os 
  54  import types 
  55   
  56  import PyPDF2 as pyPdf 
  57   
  58  # XXX: Fix these translatable strings 
  59  try: 
  60      _ 
  61  except NameError: 
  62      _ = lambda x: x 
  63   
  64  __docformat__ = "restructuredtext" 
65 66 ######################################################################## 67 68 # CONSTANTS 69 70 -class PageOrientation:
71 """The page orientation constants""" 72 PORTRAIT = False 73 """The portrait orientation""" 74 LANDSCAPE = True 75 """The lanscape orientation"""
76
77 ######################################################################## 78 79 -class PdfConvError(Exception):
80 """ 81 The base class for all exceptions raised by PdfImposer. 82 83 The attribute "message" contains a message explaining the cause of the 84 error. 85 """
86 - def __init__(self, message=None):
87 Exception.__init__(self) 88 self.message = message
89
90 ######################################################################## 91 92 -class MismachingOrientationsError(PdfConvError):
93 """ 94 This exception is raised if the required layout is incompatible with 95 the input page orientation. 96 97 The attribute "message" contains the problematic layout. 98 """
99 - def __str__(self):
100 return _("The layout %s is incompatible with the input page orientation") \ 101 % self.message
102
103 ######################################################################## 104 105 -class UnknownFormatError(PdfConvError):
106 """ 107 This exception is raised when the user tries to set an unknown page 108 format. 109 110 The attribute "message" contains the problematic format. 111 """
112 - def __str__(self):
113 return _('The page format "%s" is unknown') % self.message
114
115 ######################################################################## 116 117 118 -class UserInterruptError(PdfConvError):
119 """ 120 This exception is raised when the user interrupts the conversion. 121 """
122 - def __str__(self):
123 return _('User interruption') % self.message
124
125 ######################################################################## 126 127 -class AbstractConverter(object):
128 """ 129 The base class for all pdfimposer converter classes. 130 131 It is an abstract class, with some abstract functions which should be 132 overriden : 133 - get_input_height 134 - get_input_width 135 - get_page_count 136 - bookletize 137 - linearize 138 - reduce 139 """ 140 __metaclass__ = ABCMeta 141 142 page_formats = { 143 "A3":(841,1190), 144 "A3":(842,1192), 145 "A4":(595,841), 146 "A4":(595,842), 147 "A5":(420,595), 148 } 149
150 - def __init__(self, 151 layout='2x1', 152 format='A4', 153 copy_pages=False):
154 """ 155 Create an AbstractConverter instance. 156 157 :Parameters: 158 - `layout` The layout of input pages on one output page 159 (see set_layout). 160 - `format` The format of the output paper (see 161 set_output_format). 162 - `copy_pages` Wether the same group of input pages 163 shoud be copied to fill the corresponding output page or not 164 (see set_copy_pages). 165 """ 166 self.layout = None 167 self.output_format = None 168 self.output_orientation = None 169 170 self.set_layout(layout) 171 self.set_output_format(format) 172 self.set_copy_pages(copy_pages) 173 174 def default_progress_callback(msg, prog): 175 print "%s (%i%%)" % (msg, prog*100)
176 177 self.set_progress_callback(default_progress_callback)
178 179 # GETTERS AND SETTERS 180 # =================== 181
182 - def set_output_height(self, height):
183 """ 184 Set the height of the output page. 185 186 :Parameters: 187 - `height` The height of the output page in defalut user 188 space units. 189 """ 190 self.__output_height = int(height)
191
192 - def get_output_height(self):
193 """ 194 Get the height of the output page. 195 196 :Returns: 197 The height of the output page in defalut user space units. 198 """ 199 return self.__output_height
200
201 - def set_output_width(self, width):
202 """ 203 Set the width of the output page. 204 205 - `width` The height of the output page in defalut user space units. 206 """ 207 self.__output_width = int(width)
208
209 - def get_output_width(self):
210 """ 211 Get the width of the output page. 212 213 :Returns: 214 The width of the output page in defalut user space units. 215 """ 216 return self.__output_width
217
218 - def set_pages_in_width(self, num):
219 """ 220 Set the number of input pages to put in the width on one output page. 221 222 :Parameters: 223 - `num` An integer representing the number of pages in width. 224 """ 225 self.__pages_in_width = int(num)
226
227 - def get_pages_in_width(self):
228 """ 229 Get the number of input pages to put in the width on one output page. 230 231 :Returns: 232 An integer representing the number of pages in width. 233 """ 234 return self.__pages_in_width
235
236 - def set_pages_in_height(self, num):
237 """ 238 Set the number of input pages to put in the height on one output page. 239 240 :Parameters: 241 - `num` An integer representing the number of pages in height. 242 """ 243 self.__pages_in_height = int(num)
244
245 - def get_pages_in_height(self):
246 """ 247 Get the number of input pages to put in the height on one output page. 248 249 :Returns: 250 An integer representing the number of pages in height. 251 """ 252 return self.__pages_in_height
253
254 - def set_copy_pages(self, copy_pages):
255 """ 256 Set wether the same group of input pages shoud be copied to fill the 257 corresponding output page or not. 258 259 :Parameters: 260 - `copy_pages` True to get copies of the same group of input page 261 on one output page. False to get diffrent groups of 262 input pages on one output page. 263 """ 264 self.__copy_pages = bool(copy_pages)
265
266 - def get_copy_pages(self):
267 """ 268 Get wether the same group of input pages will be copied to fill the 269 corresponding output page or not. 270 271 :Returns: 272 True if copies of the same group of input page will get 273 copied on one output page. False if diffrent groups of 274 input pages will go on one output page. 275 """ 276 return self.__copy_pages
277
278 - def set_progress_callback(self, progress_callback):
279 """ 280 Register a progress callback function. 281 282 Register a callback function that will be called to inform on the 283 progress of the conversion. 284 285 :Parameters: 286 - `progress_callback` The callback function which is called to 287 return the conversion progress. Its signature 288 must be : a string for the progress message; 289 a number in the range [0, 1] for the progress. 290 """ 291 assert(type(progress_callback) is types.FunctionType) 292 self.__progress_callback = progress_callback
293
294 - def get_progress_callback(self):
295 """ 296 Get the progress callback function. 297 298 Get the callback function that will be called to inform on the 299 progress of the conversion. 300 301 :Returns: 302 The callback function which is called to 303 return the conversion progress. 304 """ 305 return self.__progress_callback
306 307 # SOME GETTERS THAT CALCULATE THE VALUE THEY RETURN FROM OTHER VALUES 308 # ===================================================================
309 - def get_input_size(self):
310 """ 311 Return the page size of the input document. 312 313 :Returns: 314 A tuple (width, height) representing the page size of 315 the input document expressed in default user space units. 316 """ 317 return (self.get_input_width(), self.get_input_height())
318 319 @abstractmethod
320 - def get_input_height(self):
321 """ 322 Return the page height of the input document. 323 324 :Returns: 325 The page height of the input document expressed in default 326 user space units. 327 """ 328 raise NotImplementedError("get_input_height must be implemented in a subclass.")
329 330 @abstractmethod
331 - def get_input_width(self):
332 """ 333 Return the page width of the input document. 334 335 :Returns: 336 The page width of the input document expressed in default 337 user space units. 338 """ 339 raise NotImplementedError("get_input_width must be implemented in a subclass.")
340
341 - def get_input_orientation(self):
342 """ 343 Return the page orientation of the input document. 344 345 :Returns: 346 A constant from PageOrientation, or None (if square paper). 347 """ 348 if self.get_input_height() > self.get_input_width(): 349 return PageOrientation.PORTRAIT 350 elif self.get_input_height() < self.get_input_width(): 351 return PageOrientation.LANDSCAPE 352 else: 353 #XXX: is square 354 return None
355
356 - def set_layout(self, layout):
357 """ 358 Set the layout of input pages on one output page. 359 360 :Parameters: 361 - `layout` A string of the form WxH, where W is the number of input 362 pages to put on the width of the output page and H is 363 the number of input pages to put in the height of an 364 output page. 365 """ 366 pages_in_width, pages_in_height = layout.split('x') 367 self.set_pages_in_width(int(pages_in_width)) 368 self.set_pages_in_height(int(pages_in_height))
369
370 - def get_layout(self):
371 """ 372 Return the layout of input pages on one output page. 373 374 :Returns: 375 A string of the form WxH, where W is the number of input pages 376 to put on the width of the output page and H is the number of 377 input pages to put in the height of an output page. 378 """ 379 return str(self.get_pages_in_width()) + 'x' + str(self.get_pages_in_height())
380
381 - def get_pages_in_sheet(self):
382 """ 383 Calculate the number of input page that will be put on one output page. 384 385 :Returns: 386 An integer representing the number of input pages on one 387 output page. 388 """ 389 return self.get_pages_in_width() * self.get_pages_in_height()
390
391 - def set_output_format(self, format):
392 """ 393 Set the format of the output paper. 394 395 :Parameters: 396 - `format` A string representing name ot the the desired paper 397 format, among the keys of page_formats (e.g. A3, A4, A5). 398 399 :Raises UnknonwFormatError: if the given paper format is not recognized. 400 """ 401 try: 402 width, height = AbstractConverter.page_formats[format] 403 self.set_output_height(height) 404 self.set_output_width(width) 405 except KeyError: 406 raise UnknownFormatError(format)
407
408 - def get_output_format(self):
409 """ 410 Return the format of the output paper. 411 412 :Returns: 413 A string representing the name of the paper format 414 (e.g. A3, A4, A5). 415 """ 416 for output_format in AbstractConverter.page_formats.keys(): 417 if AbstractConverter.page_formats[output_format] == \ 418 (self.get_output_width, self.get_output_height): 419 return output_format
420
421 - def get_input_format(self):
422 """ 423 Return the format of the input paper 424 425 :Returns: 426 A string representing the name of the paper format 427 (e.g. A3, A4, A5). 428 """ 429 width, height = self.get_input_size() 430 if self.get_input_orientation() == PageOrientation.LANDSCAPE: 431 size = height, width 432 else: 433 size = width, height 434 for k in self.page_formats.keys(): 435 if self.page_formats[k] == size: 436 return k
437 438 @abstractmethod
439 - def get_page_count(self):
440 """ 441 Return the number of pages of the input document. 442 443 :Returns: 444 The number of pages of the input document. 445 """ 446 raise NotImplementedError("get_page_count must be implemented in a subclass.")
447
448 - def get_reduction_factor(self):
449 """ 450 Calculate the reduction factor. 451 452 :Returns: 453 The reduction factor to be applied to an input page to 454 obtain its size on the output page. 455 """ 456 return float(self.get_output_width()) / \ 457 (self.get_pages_in_width() * self.get_input_width())
458
459 - def get_increasing_factor(self):
460 """ 461 Calculate the increasing factor. 462 463 :Returns: 464 The increasing factor to be applied to an input page to 465 obtain its size on the output page. 466 """ 467 return float(self.get_pages_in_width() * self.get_output_width()) / \ 468 self.get_input_width()
469
470 - def _set_output_orientation(self, output_orientation):
471 """ 472 Set the orientation of the output paper. 473 474 WARNING: in the current implementation, the orientation of the 475 output paper may be automatically adjusted, even if ti was set 476 manually. 477 478 :Parameters: 479 - `output_orientation` A constant from PageOrientation, 480 or None (if square paper). 481 """ 482 output_orientation = bool(output_orientation) 483 484 w = self.get_output_width() 485 h = self.get_output_height() 486 487 if (output_orientation == PageOrientation.PORTRAIT and w > h) or \ 488 (output_orientation == PageOrientation.LANDSCAPE and h > w): 489 self.set_output_height(w) 490 self.set_output_width(h)
491
492 - def _get_output_orientation(self):
493 """ 494 Return the orientation of the output paper. 495 496 WARNING: in the current implementation, the orientation of the 497 output paper may be automatically adjusted, even if it was set 498 manually. 499 500 :Returns: 501 A constant among from PageOrientation, or None (if square paper). 502 """ 503 if self.get_output_height() > self.get_output_width(): 504 return PageOrientation.PORTRAIT 505 elif self.get_output_height() < self.get_output_width(): 506 return PageOrientation.LANDSCAPE 507 else: 508 return None
509 510 # CONVERSION FUNCTIONS 511 # ==================== 512 513 @abstractmethod
514 - def bookletize(self):
515 """ 516 Convert a linear document to a booklet. 517 518 Convert a linear document to a booklet, arranging the pages as 519 required. 520 """ 521 raise NotImplementedError("bookletize must be implemented in a subclass.")
522 523 @abstractmethod
524 - def linearize(self):
525 """ 526 Convert a booklet to a linear document. 527 528 Convert a booklet to a linear document, arranging the pages as 529 required. 530 """ 531 raise NotImplementedError("linearize must be implemented in a subclass.")
532 533 @abstractmethod
534 - def reduce(self):
535 """ 536 Put multiple input pages on one output page. 537 """ 538 raise NotImplementedError("reduce must be implemented in a subclass.")
539
540 ######################################################################## 541 542 -class StreamConverter(AbstractConverter):
543 """ 544 This class performs conversions on file-like objects (e.g. a StreamIO). 545 """ 546
547 - def __init__(self, 548 input_stream, 549 output_stream, 550 layout='2x1', 551 format='A4', 552 copy_pages=False):
553 """ 554 Create a StreamConverter. 555 556 :Parameters: 557 - `input_stream` The file-like object from which tne input PDF 558 document should be read. 559 - `output_stream` The file-like object to which tne output PDF 560 document should be written. 561 - `layout` The layout of input pages on one output page (see 562 set_layout). 563 - `format` The format of the output paper (see set_output_format). 564 - `copy_pages` Wether the same group of input pages shoud be copied 565 to fill the corresponding output page or not (see 566 set_copy_pages). 567 """ 568 569 AbstractConverter.__init__(self, layout, format, 570 copy_pages) 571 572 573 574 self._output_stream = output_stream 575 self._input_stream = input_stream 576 577 self._inpdf = pyPdf.PdfFileReader(input_stream)
578
579 - def get_input_height(self):
580 page = self._inpdf.getPage(0) 581 height = page.mediaBox.getHeight() 582 return int(height)
583
584 - def get_input_width(self):
585 page = self._inpdf.getPage(0) 586 width = page.mediaBox.getWidth() 587 return int(width)
588
589 - def get_page_count(self):
590 return self._inpdf.getNumPages()
591
592 - def __fix_page_orientation(self, cmp):
593 """ 594 Adapt the output page orientation. 595 596 :Parameters: 597 - `cmp` A comparator function. Takes: number of pages on one 598 direction (int), number of pages on the other direction 599 (int). Must return: the boolean result of the comparaison. 600 601 :Raises MismachingOrientationsError: if the required layout is 602 incompatible with the input page orientation. 603 """ 604 if cmp(self.get_pages_in_width(), self.get_pages_in_height()): 605 if self.get_input_orientation() == PageOrientation.PORTRAIT: 606 if self._get_output_orientation() == PageOrientation.PORTRAIT: 607 self._set_output_orientation(PageOrientation.LANDSCAPE) 608 else: 609 raise MismachingOrientationsError(self.get_layout()) 610 elif cmp(self.get_pages_in_height(), self.get_pages_in_width()): 611 if self.get_input_orientation() == PageOrientation.LANDSCAPE: 612 if self._get_output_orientation() == PageOrientation.LANDSCAPE: 613 self._set_output_orientation(PageOrientation.PORTRAIT) 614 else: 615 raise MismachingOrientationsError(self.get_layout()) 616 else: 617 if self.get_input_orientation() == PageOrientation.LANDSCAPE: 618 if self._get_output_orientation() == PageOrientation.PORTRAIT: 619 self._set_output_orientation(PageOrientation.LANDSCAPE) 620 else: 621 if self._get_output_orientation() == PageOrientation.LANDSCAPE: 622 self._set_output_orientation(PageOrientation.PORTRAIT)
623
625 """ 626 Adapt the output page orientation to impose 627 """ 628 def __is_two_times(op1, op2): 629 if op1 == 2 * op2: 630 return True 631 else: 632 return False
633 self.__fix_page_orientation(__is_two_times)
634
635 - def __fix_page_orientation_for_linearize(self):
636 """ 637 Adapt the output page orientation to linearize 638 """ 639 def __is_half(op1, op2): 640 if op2 == 2 * op1: 641 return True 642 else: 643 return False
644 self.__fix_page_orientation(__is_half) 645
646 - def __get_sequence_for_booklet(self):
647 """ 648 Calculates the page sequence to impose a booklet. 649 650 :Returns: 651 A list of page numbers representing sequence of pages to 652 impose a booklet. The list might contain None where blank 653 pages should be added. 654 """ 655 n_pages = self.get_page_count() 656 pages = range(0, n_pages) 657 658 # Check for missing pages 659 if (n_pages % 4) == 0: 660 n_missing_pages = 0 661 else: 662 n_missing_pages = 4 - (n_pages % 4) 663 # XXX: print a warning if input page number not diviable by 4? 664 665 # Add reference to the missing empty pages to the pages sequence 666 for missing_page in range(0, n_missing_pages): 667 pages.append(None) 668 669 def append_and_copy(list, pages): 670 """ 671 Append pages to the list and copy them if needed 672 """ 673 if self.get_copy_pages(): 674 for i in range(self.get_pages_in_sheet() / 2): 675 list.extend(pages) 676 else: 677 list.extend(pages)
678 679 # Arranges the pages in booklet order 680 sequence = [] 681 while pages: 682 append_and_copy(sequence, [pages.pop(), pages.pop(0)]) 683 append_and_copy(sequence, [pages.pop(0), pages.pop()]) 684 685 return sequence 686
687 - def __get_sequence_for_linearize(self, booklet=True):
688 """ 689 Calculates the page sequence to lineraize a booklet. 690 691 :Returns: 692 A list of page numbers representing sequence of pages to 693 be extracted to linearize a booklet. 694 """ 695 # XXX: is booklet argument useful? 696 697 def append_and_remove_copies(list, pages): 698 sequence.extend(pages) 699 if self.get_copy_pages(): 700 for copy in range(self.get_pages_in_sheet() - len(pages)): 701 sequence.append(None)
702 703 if booklet: 704 sequence = [] 705 try : 706 for i in range(0, self.get_page_count() * 707 self.get_pages_in_sheet(), 4): 708 append_and_remove_copies(sequence, [i / 2, i / 2]) 709 append_and_remove_copies(sequence, [i / 2 + 1, i / 2 + 2]) 710 except IndexError : 711 # XXX: Print a warning 712 pass 713 else: 714 sequence = range(0, self.get_page_count() * self.get_pages_in_sheet()) 715 return sequence 716
717 - def __get_sequence_for_reduce(self):
718 """ 719 Calculates the page sequence to linearly impose reduced pages. 720 721 :Returns: 722 A list of page numbers representing sequence of pages to 723 impose reduced pages. The list might contain None where blank 724 pages should be added. 725 """ 726 if self.get_copy_pages(): 727 sequence = [] 728 for page in range(self.get_page_count()): 729 for copy in range(self.get_pages_in_sheet()): 730 sequence.append(page) 731 else: 732 sequence = range(self.get_page_count()) 733 if len(sequence) % self.get_pages_in_sheet() != 0: 734 for missing_page in range(self.get_pages_in_sheet() - 735 (len(sequence) % self.get_pages_in_sheet())): 736 sequence.append(None) 737 return sequence
738
739 - def __write_output_stream(self, outpdf):
740 """ 741 Writes output to the stream. 742 743 :Parameters: 744 - `outpdf` the object to write to the stream. This object must have a 745 write() method. 746 """ 747 self.get_progress_callback()(_("writing converted file"), 1) 748 outpdf.write(self._output_stream) 749 self.get_progress_callback()(_("done"), 1)
750
751 - def __do_reduce(self, sequence):
752 """ 753 Do actual imposition job. 754 755 :Parameters: 756 - `sequence` a list of page numbers repersenting the sequence of 757 pages to impose. None means blank page. 758 759 """ 760 # XXX: Translated progress messages 761 self.__fix_page_orientation_for_booklet() 762 outpdf = pyPdf.PdfFileWriter() 763 764 current_page = 0 765 while current_page < len(sequence): 766 self.get_progress_callback()( 767 _("creating page %i") % 768 ((current_page + self.get_pages_in_sheet()) / 769 self.get_pages_in_sheet()), 770 float(current_page) / len(sequence) 771 ) 772 page = outpdf.addBlankPage(self.get_output_width(), 773 self.get_output_height()) 774 for vert_pos in range(0, self.get_pages_in_height()): 775 for horiz_pos in range(0, self.get_pages_in_width()): 776 if current_page < len(sequence) and sequence[current_page] is not None: 777 page.mergeScaledTranslatedPage( 778 self._inpdf.getPage(sequence[current_page]), 779 self.get_reduction_factor(), 780 horiz_pos*self.get_output_width() / \ 781 self.get_pages_in_width(), 782 self.get_output_height() - ( 783 (vert_pos + 1) * self.get_output_height() / \ 784 self.get_pages_in_height()) 785 ) 786 current_page += 1 787 page.compressContentStreams() 788 self.__write_output_stream(outpdf)
789
790 - def bookletize(self):
791 self.__do_reduce(self.__get_sequence_for_booklet())
792
793 - def reduce(self):
794 self.__do_reduce(self.__get_sequence_for_reduce())
795
796 - def linearize(self, booklet=True):
797 # XXX: Translated progress messages 798 # XXX: Wrong zoom factor e.g. when layout is 2x1 799 800 self.__fix_page_orientation_for_linearize() 801 sequence = self.__get_sequence_for_linearize() 802 outpdf = pyPdf.PdfFileWriter() 803 804 output_page = 0 805 for input_page in range(0, self.get_page_count()): 806 for vert_pos in range(0, self.get_pages_in_height()): 807 for horiz_pos in range(0, self.get_pages_in_width()): 808 if sequence[output_page] is not None: 809 self.get_progress_callback()( 810 _("extracting page %i") % (output_page + 1), 811 float(output_page) / len(sequence)) 812 page = outpdf.insertBlankPage(self.get_output_width(), 813 self.get_output_height(), 814 sequence[output_page]) 815 page.mergeScaledTranslatedPage( 816 self._inpdf.getPage(input_page), 817 self.get_increasing_factor(), 818 - horiz_pos * self.get_output_width(), 819 (vert_pos - self.get_pages_in_height() + 1) * \ 820 self.get_output_height() 821 ) 822 page.compressContentStreams() 823 output_page += 1 824 self.__write_output_stream(outpdf)
825
826 ######################################################################## 827 828 -class FileConverter(StreamConverter):
829 """ 830 This class performs conversions on true files. 831 """
832 - def __init__(self, 833 infile_name, 834 outfile_name=None, 835 layout='2x1', 836 format='A4', 837 copy_pages=False, 838 overwrite_outfile_callback=None):
839 """ 840 Create a FileConverter. 841 842 :Parameters: 843 - `infile_name` The name to the input PDF file. 844 - `outfile_name` The name of the file where the output PDF 845 should de written. If ommited, defaults to the 846 name of the input PDF postponded by '-conv'. 847 - `layout` The layout of input pages on one output page (see 848 set_layout). 849 - `format` The format of the output paper (see set_output_format). 850 - `copy_pages` Wether the same group of input pages shoud be copied 851 to fill the corresponding output page or not (see 852 set_copy_pages). 853 - `overwrite_outfile_callback` A callback function which is called 854 if outfile_name already exists when trying to open it. Its 855 signature must be : take a string for the outfile_name as an argument; 856 return False not to overwrite the file. If ommited, existing file 857 would be overwritten without confirmation. 858 859 """ 860 # sets [input, output]_stream to None so we can test their presence 861 # in __del__ 862 self._input_stream = None 863 self._output_stream = None 864 865 # outfile_name is set if provided 866 if outfile_name: 867 self.__set_outfile_name(outfile_name) 868 else: 869 self.__set_outfile_name(None) 870 871 # Then infile_nameis set, so if outfile_name was not provided we 872 # can create it from infile_name 873 self.__set_infile_name(infile_name) 874 875 # Setup callback to ask for confirmation before overwriting outfile 876 if overwrite_outfile_callback: 877 assert(type(overwrite_outfile_callback) is types.FunctionType) 878 else: 879 overwrite_outfile_callback = lambda filename: True 880 881 # Now initialize a streamConverter 882 self._input_stream = open(self.get_infile_name(), 'rb') 883 outfile_name = self.get_outfile_name() 884 if (os.path.exists(outfile_name) and not 885 overwrite_outfile_callback(os.path.abspath(outfile_name))): 886 raise UserInterruptError() 887 self._output_stream = open(outfile_name, 'wb') 888 StreamConverter.__init__(self, self._input_stream, self._output_stream, 889 layout, format, copy_pages)
890
891 - def __del__(self):
892 if self._input_stream: 893 try: 894 self._input_stream.close() 895 except IOError: 896 # XXX: Do something better 897 pass 898 if self._output_stream: 899 try: 900 self._output_stream.close() 901 except IOError: 902 # XXX: Do something better 903 pass
904 905 906 # GETTERS AND SETTERS SECTION 907 # =========================== 908
909 - def __set_infile_name(self, name):
910 """ 911 Sets the name of the input PDF file. Also set the name of output PDF 912 file if not already set. 913 914 :Parameters: 915 - `name` the name of the input PDF file. 916 """ 917 self.__infile_name = name 918 919 if not self.__outfile_name: 920 result = re.search("(.+)\.\w*$", name) 921 if result: 922 self.__outfile_name = result.group(1) + '-conv.pdf' 923 else: 924 self.__outfile_name = name + '-conv.pdf'
925
926 - def get_infile_name(self):
927 """ 928 Get the name of the input PDF file. 929 930 :Returns: 931 The name of the input PDF file. 932 """ 933 return self.__infile_name
934
935 - def __set_outfile_name(self, name):
936 """ 937 Sets the name of the output PDF file. 938 939 :Parameters: 940 - `name` the name of the output PDF file. 941 """ 942 self.__outfile_name = name
943
944 - def get_outfile_name(self):
945 """ 946 Get the name of the output PDF file. 947 948 :Returns: 949 The name of the output PDF file. 950 """ 951 return self.__outfile_name
952
953 954 # Convenience functions 955 # ===================== 956 957 -def bookletize_on_stream(input_stream, 958 output_stream, 959 layout='2x1', 960 format='A4', 961 copy_pages=False):
962 """ 963 Convert a linear document to a booklet. 964 965 Convert a linear document to a booklet, arranging the pages as 966 required. 967 968 This is a convenience function around StreamConverter 969 970 :Parameters: 971 - `input_stream` The file-like object from which tne input PDF 972 document should be read. 973 - `output_stream` The file-like object to which tne output PDF 974 document should be written. 975 - `layout` The layout of input pages on one output page (see 976 set_layout). 977 - `format` The format of the output paper (see set_output_format). 978 - `copy_pages` Wether the same group of input pages shoud be copied 979 to fill the corresponding output page or not (see 980 set_copy_pages). 981 """ 982 StreamConverter(layout, format, copy_pages, 983 input_stream, output_stream()).bookletize()
984
985 -def bookletize_on_file(input_file, 986 output_file=None, 987 layout='2x1', 988 format='A4', 989 copy_pages=False):
990 """ 991 Convert a linear PDF file to a booklet. 992 993 Convert a linear PDF file to a booklet, arranging the pages as 994 required. 995 996 This is a convenience function around FileConverter 997 998 :Parameters: 999 - `input_file` The name to the input PDF file. 1000 - `output_file` The name of the file where the output PDF 1001 should de written. If ommited, defaults to the 1002 name of the input PDF postponded by '-conv'. 1003 - `layout` The layout of input pages on one output page (see 1004 set_layout). 1005 - `format` The format of the output paper (see set_output_format). 1006 - `copy_pages` Wether the same group of input pages shoud be copied 1007 to fill the corresponding output page or not (see 1008 set_copy_pages). 1009 """ 1010 FileConverter(input_file, output_file, layout, format, 1011 copy_pages).bookletize()
1012
1013 -def linearize_on_stream(input_stream, 1014 output_stream, 1015 layout='2x1', 1016 format='A4', 1017 copy_pages=False):
1018 """ 1019 Convert a booklet to a linear document. 1020 1021 Convert a booklet to a linear document, arranging the pages as 1022 required. 1023 1024 This is a convenience function around StreamConverter 1025 1026 :Parameters: 1027 - `input_stream` The file-like object from which tne input PDF 1028 document should be read. 1029 - `output_stream` The file-like object to which tne output PDF 1030 document should be written. 1031 - `layout` The layout of output pages on one input page (see 1032 set_layout). 1033 - `format` The format of the output paper (see set_output_format). 1034 - `copy_pages` Wether the same group of input pages shoud be copied 1035 to fill the corresponding output page or not (see 1036 set_copy_pages). 1037 """ 1038 StreamConverter(input_stream, output_stream, layout, 1039 format, copy_pages).linearize()
1040
1041 -def linearize_on_file(input_file, 1042 output_file=None, 1043 layout='2x1', 1044 format='A4', 1045 copy_pages=False):
1046 """ 1047 Convert a booklet to a linear PDF file. 1048 1049 Convert a booklet to a linear PDF file, arranging the pages as 1050 required. 1051 1052 This is a convenience function around FileConverter 1053 1054 :Parameters: 1055 - `input_file` The name to the input PDF file. 1056 - `output_file` The name of the file where the output PDF 1057 should de written. If ommited, defaults to the 1058 name of the input PDF postponded by '-conv'. 1059 - `layout` The layout of input pages on one output page (see 1060 set_layout). 1061 - `format` The format of the output paper (see set_output_format). 1062 - `copy_pages` Wether the same group of input pages shoud be copied 1063 to fill the corresponding output page or not (see 1064 set_copy_pages). 1065 """ 1066 FileConverter(input_file, output_file, layout, format, 1067 copy_pages).linearize()
1068
1069 -def reduce_on_stream(input_stream, 1070 output_stream, 1071 layout='2x1', 1072 format='A4', 1073 copy_pages=False):
1074 """ 1075 Put multiple input pages on one output page. 1076 1077 This is a convenience function around StreamConverter 1078 1079 :Parameters: 1080 - `input_stream` The file-like object from which tne input PDF 1081 document should be read. 1082 - `output_stream` The file-like object to which tne output PDF 1083 document should be written. 1084 - `layout` The layout of input pages on one output page (see 1085 set_layout). 1086 - `format` The format of the output paper (see set_output_format). 1087 - `copy_pages` Wether the same group of input pages shoud be copied 1088 to fill the corresponding output page or not (see 1089 set_copy_pages). 1090 """ 1091 StreamConverter(input_stream, output_stream, layout, format, 1092 copy_pages).reduce()
1093
1094 -def reduce_on_file(input_file, 1095 output_file=None, 1096 layout='2x1', 1097 format='A4', 1098 copy_pages=False):
1099 """ 1100 Put multiple input pages on one output page. 1101 1102 This is a convenience function around FileConverter 1103 1104 :Parameters: 1105 - `input_file` The name to the input PDF file. 1106 - `output_file` The name of the file where the output PDF 1107 should de written. If ommited, defaults to the 1108 name of the input PDF postponded by '-conv'. 1109 - `layout` The layout of input pages on one output page (see 1110 set_layout). 1111 - `format` The format of the output paper (see set_output_format). 1112 - `copy_pages` Wether the same group of input pages shoud be copied 1113 to fill the corresponding output page or not (see 1114 set_copy_pages). 1115 """ 1116 FileConverter(input_file, output_file, layout, format, 1117 copy_pages).reduce()
1118