8
votes

Comment puis-je faire pivoter, réorganiser arbitrairement des pages PDF, etc. en Python?

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:

entrez la description de l'image ici

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?


1 commentaires

apparemment, il y a au moins un moyen


3 Réponses :


8
votes

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:

Pages du manuel HoTT, transformées par le script. La page 1 contient la couverture du livre, non modifiée. La page 2 contient trois pages de couverture, pivotées de 90 ° dans le sens antihoraire, disposées côte à côte de bas en haut. La page 3 contient les deux premières pages de la table des matières, cisaillées et inclinées.

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.)


10 commentaires

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.



2
votes
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()

0 commentaires

0
votes

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.

Fichier d'entrée: Fichier d'entrée

Fichier de sortie: Fichier de sortie

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);


0 commentaires