J'ai un input.pdf qui est "normal" (un certain nombre de pages ont toutes la même orientation et direction) et je veux créer un nouveau pdf qui peut réorganiser arbitrairement les pages d'entrée
Par exemple:
Je n'ai besoin que de rotation et de mise à l'échelle. Chaque page d'entrée sera présente dans son intégralité comme un composant de la sortie. Je n'ai pas besoin de jouer avec le texte, les couleurs, le recadrage, etc.
En pseudo-code, voici toutes les fonctionnalités dont j'ai besoin:
in = open_pdf("input.pdf") out = new_pdf () p = createpage (size) p.add (in.get_page(123), origin=(0,100), scale=(0.5,0.5), angle=degrees(270)) p.add (...) out.add(p) out.save("output.pdf")
Puis-je faire cela en Python?
Si ce n'est pas Python, un autre langage de script convivial pour Linux?
3 Réponses :
Avec PyPDF2 , vous pouvez écrire un script pour accomplir cette tâche qui ressemble beaucoup à votre pseudocode.
Voici un exemple de code, en utilisant une version nocturne du manuel Homotopy Type Theory comme entrée:
#!/usr/bin/env python3 from PyPDF2 import PdfFileReader, PdfFileWriter # matrix helper class class AfMatrix: """ A matrix of a 2D affine transform. """ __slots__ = ('__a', '__b', '__c', '__d', '__e', '__f') def __init__(self, a, b, c, d, e, f): self.__a = float(a) self.__b = float(b) self.__c = float(c) self.__d = float(d) self.__e = float(e) self.__f = float(f) def __iter__(self): yield self.__a yield self.__b yield self.__c yield self.__d yield self.__e yield self.__f def __hash__(self): return hash(tuple(self)) def __eq__(self, other): return tuple(self) == tuple(other) @classmethod def compose(cls, *what): a, b, c, d, e, f = ( 1, 0, 0, 1, 0, 0, ) for rhs in what: A, B, C, D, E, F = rhs a, b, c, d, e, f = ( a * A + b * C, a * B + b * D, c * A + d * C, c * B + d * D, e * A + f * C + E, e * B + f * D + F, ) return cls( a, b, c, d, e, f ) @classmethod def translate(cls, x=0, y=0): return cls( 1, 0, 0, 1, x, y ) def __takes_origin(func): def translated_func(cls, *args, origin=(0, 0), **kwargs): if origin == (0, 0): return func(cls, *args, **kwargs) return cls.compose( cls.translate(-origin[0], -origin[1]), func(cls, *args, **kwargs), cls.translate(origin[0], origin[1]) ) return translated_func @classmethod @__takes_origin def shear(cls, x=1, y=1): return cls( x, 0, 0, y, 0, 0 ) @classmethod @__takes_origin def rotate(cls, angle): from math import cos, sin, radians angle = radians(angle) C = cos(angle) S = sin(angle) return cls( C, S, -S, C, 0, 0 ) # reader = PdfFileReader('hott-online-1272-ga50f9bd.pdf') writer = PdfFileWriter() ipgs = [reader.getPage(i) for i in range(8)] # page 1 writer.addPage(ipgs[0]) # page 2 opg1src = ipgs[2:5] opg1 = writer.addBlankPage(0, 0) yaccum = 0 for ipg in opg1src: opg1.mergeTransformedPage(ipg, AfMatrix.compose( AfMatrix.rotate(90), AfMatrix.translate(x=ipg.mediaBox.getHeight(), y=yaccum) ), expand=True) yaccum += ipg.mediaBox.getWidth() # page 3 opg2 = writer.addBlankPage( ipgs[6].mediaBox.getWidth(), ipgs[6].mediaBox.getHeight() ) opg2.mergeTransformedPage(ipgs[6], ( AfMatrix.shear(x=1/3) ), expand=True) opg2.mergeTransformedPage(ipgs[7], AfMatrix.compose( AfMatrix.translate( x=-opg2.mediaBox.getWidth() / 8, y=-opg2.mediaBox.getHeight() / 8 ), AfMatrix.rotate(-15, origin=(opg2.mediaBox.getWidth(), 0)), AfMatrix.shear(x=0.75, y=0.75, origin=(opg2.mediaBox.getWidth(), 0)) ), expand=False) # output with open('sample.pdf', 'wb') as ostream: writer.write(ostream)
Et voici le résultat:
Remarque sur les matrices de transformation: dans PDF et PostScript, la coordonnée X croît vers la droite et la coordonnée Y croît vers le haut, comme dans la coutume mathématique (et contrairement à la coutume en infographie, où Y croît vers le bas). Contrairement à la coutume mathématique, les points sont traités comme des vecteurs de ligne au lieu de vecteurs de colonne et apparaissent donc sur le côté gauche de la multiplication matricielle. Cela signifie que les transformations matricielles composent de gauche à droite au lieu de droite à gauche: l'opération la plus à gauche est appliquée en premier. Aussi, pour faire sortir les rotations par angles positifs comme dans le sens antihoraire (encore une fois comme la coutume mathématique), la matrice de rotation ci-dessus apparaît transposée dans sa forme habituelle.
Lors de la transformation de pages, méfiez-vous du contenu qui tombe hors des limites de la page sur la page d'origine; sur la nouvelle page, il peut en fait être rendu. (Je n'ai pas encore trouvé de solution à cela.)
Merci, cela fonctionne presque pastebin.com/8gFMN44h les bordures roses / bleues d'origine étaient bien serrées au bord de la page mais maintenant elles apparaissent avec une marge imgur.com/a/nN5EheB est-ce un problème d'attribut de page ou ai-je foiré mon matrices?
Testez l'entrée / la sortie pour le script lié ci-dessus ici
Je ne sais pas ce que vous voulez dire. Si vous regardez le test-input.pdf dans l'archive tar que j'ai partagée ci-dessus, il semble que les bordures colorées soient assez serrées à la page. S'ils débordent, ce n'est pas de beaucoup, pas de quoi que ce soit près des grandes marges indiquées dans la sortie. Il se passe autre chose.
Je ne peux pas ouvrir le fichier, car je n'ai pas de compte Google. Mais vous pouvez observer le même phénomène avec le manuel HoTT: certains des symboles sigma et pi sur la couverture sont normalement rendus au-delà des limites de la page et commencent à apparaître lorsque le contenu est mis à l'échelle.
dropbox.com/s/d96bvnzjtqfr4w4/book-layout-test.tar?dl=0 veuillez y jeter un œil
Ah, maintenant j'ai compris. Vous n'avez pas transmis le point d'origine à la méthode d' scale
(nommée shear
dans mon exemple). Cela, et la transformation finale est décalée d'un quart de la hauteur de la page.
Allez-vous accepter maintenant?
L'origine est (0,0) par défaut, alors quelle différence cela fait-il? Et que voulez-vous dire "la transformation finale est décalée d'un quart de la hauteur de la page"? Le y_offset est soit 0 soit height / 2, d'où obtenez-vous un quart? Et si c'est l'explication, pourquoi le décalage x est-il également erroné? Je veux accepter cette réponse, mais je veux aussi aller au fond des choses. Ma solution de contournement actuelle est la suivante: pastebin.com/mSiLYKv3
La transformation de cisaillement a toujours au moins un point fixe, qui est passé comme paramètre d' origin
. Par défaut, l' origin
est (0, 0)
, qui est le coin inférieur gauche de la page (de sortie): le définir au centre de la page (de sortie) rend les pages d'entrée positionnées les unes à côté des autres.
Un problème non lié est que la valeur de départ de y_offset
est zéro, ce qui signifie que les pages (d'entrée) sont positionnées à partir du milieu de la page (de sortie). Soustrayez un quart de la hauteur de la page (sortie) pour les regrouper à partir du bas.
import fitz # <- PyMuPDF v 1.9.3 doc = fitz.open("mypdf.pdf") # open the PDF page = doc[n] # read page n (zero-based) page.setRotate(-90) # rotate page by 90 degrees counter-clockwise doc.save(doc.name, incremental = True) # update the file - a sub-second matter doc.close()
mergeRotatedScaledTranslatedPage (page2, rotation, échelle, tx, ty, expand = False)
rotation (degré): doit être flottant
échelle (entre 0,0 et 1,0): doit être flottante.
Ceci est similaire à mergePage, mais le flux à fusionner est traduit, pivoté et mis à l'échelle en appliquant une matrice de transformation. : param PageObject page2: la page à fusionner dans celle-ci. Doit être une instance de: class: PageObject<PageObject>
. : param float tx: La translation sur l'axe X: param float ty: La translation sur l'axe Y: param float rotation: L'angle de rotation, en degrés: param float scale: Le facteur d'échelle: param bool expand: Indique si la page doit être développé pour s'adapter aux dimensions de la page à fusionner.
from PyPDF2 import PdfFileReader, PdfFileWriter,pdf from pathlib import Path # mergeRotatedScaledTranslatedPage(page2, rotation, scale, tx, ty, expand=False) inpdf = PdfFileReader(open('input.pdf', 'rb')); outpdf = PdfFileWriter(); # ----- page one ----- page_1_in_output_file = outpdf.addBlankPage(1000,1000) page_in_input_file = inpdf.getPage(0) page_1_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 90.0 ,0.9,500,100,expand = False) page_in_input_file = inpdf.getPage(1) page_1_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 90.0 ,0.9,500,200,expand = False) page_in_input_file = inpdf.getPage(2) page_1_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 90.0 ,0.9,500,300,expand = False) # ----- page Two ----- page_2_in_output_file = outpdf.addBlankPage(1000,1000) page_in_input_file = inpdf.getPage(3) page_2_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 0.0 ,0.99,0,0,expand = False) # ----- page Three ----- page_3_in_output_file = outpdf.addBlankPage(1000,1000) page_in_input_file = inpdf.getPage(4) page_3_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 45.0 ,0.9,500,100,expand = False) page_in_input_file = inpdf.getPage(5) page_3_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 145.0 ,0.25,500,200,expand = False) page_in_input_file = inpdf.getPage(6) page_3_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 190.0 ,0.3,400,300,expand = False) # ----- page Four ----- page_4_in_output_file = outpdf.addBlankPage(1000,1000) page_in_input_file = inpdf.getPage(7) page_4_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 290.0 ,0.9,500,400,expand = False) # ----- page Five ----- page_5_in_output_file = outpdf.addBlankPage(1000,1000) page_in_input_file = inpdf.getPage(8) page_5_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 90.0 ,0.2,500,100,expand = False) page_in_input_file = inpdf.getPage(9) page_5_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 90.0 ,0.5,500,200,expand = False) page_in_input_file = inpdf.getPage(10) page_5_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 90.0 ,0.5,500,300,expand = False) # ----- page Six ----- page_6_in_output_file = outpdf.addBlankPage(1000,1000) page_in_input_file = inpdf.getPage(11) page_6_in_output_file.mergeRotatedScaledTranslatedPage(page_in_input_file, 180.0 ,0.9,500,100,expand = False) with Path("output.pdf").open(mode="wb") as output_file: outpdf.write(output_file);
apparemment, il y a au moins un moyen