web-dev-qa-db-fra.com

Rendre le fichier .pdf en un fichier Canvas unique en utilisant pdf.js et ImageData

J'essaie de lire un document .pdf entier à l'aide de PDF.js, puis de restituer toutes les pages sur un même document.

Mon idée: rendre chaque page sur un canevas et obtenir le ImageData (context.getImageData ()), effacer le canevas pour faire la page suivante. Je stocke tous les ImageDatas dans un tableau et une fois que toutes les pages y figurent, je souhaite placer tous les ImageDatas du tableau sur un seul et même canevas.

var pdf = null;
PDFJS.disableWorker = true;
var pages = new Array();
    //Prepare some things
    var canvas = document.getElementById('cv');
    var context = canvas.getContext('2d');
    var scale = 1.5;
    PDFJS.getDocument(url).then(function getPdfHelloWorld(_pdf) {
        pdf = _pdf;
        //Render all the pages on a single canvas
        for(var i = 1; i <= pdf.numPages; i ++){
            pdf.getPage(i).then(function getPage(page){
                var viewport = page.getViewport(scale);
                canvas.width = viewport.width;
                canvas.height = viewport.height;
                page.render({canvasContext: context, viewport: viewport});
                pages[i-1] = context.getImageData(0, 0, canvas.width, canvas.height);
                context.clearRect(0, 0, canvas.width, canvas.height);
                p.Out("pre-rendered page " + i);
            });
        }

    //Now we have all 'dem Pages in "pages" and need to render 'em out
    canvas.height = 0;
    var start = 0;
    for(var i = 0; i < pages.length; i++){
        if(canvas.width < pages[i].width) canvas.width = pages[i].width;
        canvas.height = canvas.height + pages[i].height;
        context.putImageData(pages[i], 0, start);
        start += pages[i].height;
    }
    });

Donc, si je comprends bien, cela devrait fonctionner, non? Lorsque je lance ceci, je me retrouve avec la toile qui est assez grande pour contenir toutes les pages du pdf mais ne montre pas le pdf ...

Merci pour l'aide.

12
H_end-rik

Je ne peux pas parler de la partie de votre code qui convertit le pdf en canevas, mais je vois des problèmes.

  • Chaque réinitialiser canvas.width ou canvas.height efface automatiquement le contenu de la toile. Ainsi, dans la section supérieure, votre clearRect n'est pas nécessaire car le canevas est effacé par canvas.width avant chaque page.render. 
  • Plus important encore, dans la section inférieure, tous vos dessins pdf précédents sont effacés par chaque redimensionnement de la toile (oups!).
  • getImageData () obtient un array où chaque pixel est représenté par 4 éléments consécutifs de ce tableau (rouge puis vert puis bleu puis alpha). Étant donné que getImageData () est un tableau, il n’a donc pas de pages [i] .width ni de pages [i] .height; il n’a que des pages [i] .length. Cette longueur de tableau ne peut pas être utilisée pour déterminer des largeurs ou des hauteurs.

Donc, pour commencer, je commencerais par changer votre code en ceci (très, très non testé!):

var pdf = null;
PDFJS.disableWorker = true;
var pages = new Array();
//Prepare some things
var canvas = document.getElementById('cv');
var context = canvas.getContext('2d');
var scale = 1.5;
var canvasWidth=0;
var canvasHeight=0;
var pageStarts=new Array();
pageStarts[0]=0;

PDFJS.getDocument(url).then(function getPdfHelloWorld(_pdf) {
    pdf = _pdf;
    //Render all the pages on a single canvas
    for(var i = 1; i <= pdf.numPages; i ++){
        pdf.getPage(i).then(function getPage(page){
            var viewport = page.getViewport(scale);
            // changing canvas.width and/or canvas.height auto-clears the canvas
            canvas.width = viewport.width;
            canvas.height = viewport.height;
            page.render({canvasContext: context, viewport: viewport});
            pages[i-1] = context.getImageData(0, 0, canvas.width, canvas.height);
            // calculate the width of the final display canvas
            if(canvas.width>maxCanvasWidth){
              maxCanvasWidth=canvas.width;
            }
            // calculate the accumulated with of the final display canvas
            canvasHeight+=canvas.height;
            // save the "Y" starting position of this pages[i]
            pageStarts[i]=pageStarts[i-1]+canvas.height;
            p.Out("pre-rendered page " + i);
        });
    }


    canvas.width=canvasWidth; 
    canvas.height = canvasHeight;  // this auto-clears all canvas contents
    for(var i = 0; i < pages.length; i++){
        context.putImageData(pages[i], 0, pageStarts[i]);
    }

});

Vous pouvez également utiliser votre méthode de manière plus traditionnelle:

Utilisez un seul canevas «display» et permettez à l'utilisateur de «feuilleter» chaque page désirée.

Puisque vous commencez déjà par dessiner chaque page dans un canevas, pourquoi ne pas conserver un canevas caché distinct pour chaque page. Ensuite, lorsque l'utilisateur souhaite voir la page n ° 6, il vous suffit de copier le canevas masqué n ° 6 sur votre canevas d'affichage.

Les développeurs de Mozilla utilisent cette approche dans leur démo pdfJS ici: http://mozilla.github.com/pdf.js/web/viewer.html

Vous pouvez consulter le code du lecteur ici: http://mozilla.github.com/pdf.js/web/viewer.js

8
markE

Les opérations PDF sont asynchrones à toutes les étapes. Cela signifie que vous devez également saisir la promesse lors du dernier rendu. Si vous ne l'attrapez pas, vous obtiendrez uniquement un canevas vierge car le rendu n'est pas terminé avant que la boucle ne passe à la page suivante.

Conseil: Je vous recommanderais également d'utiliser autre chose que getImageData, car cette option stockera des images non compressées, par exemple, data-uri, qui est plutôt des données compressées.

Voici une approche légèrement différente qui élimine la boucle perdue et utilise mieux les promesses à cette fin:

LIVE FIDDLE 

var canvas = document.createElement('canvas'), // single off-screen canvas
    ctx = canvas.getContext('2d'),             // to render to
    pages = [],
    currentPage = 1,
    url = 'path/to/document.pdf';              // specify a valid url

PDFJS.getDocument(url).then(iterate);   // load PDF document

/* To avoid too many levels, which easily happen when using chained promises,
   the function is separated and just referenced in the first promise callback
*/

function iterate(pdf) {

    // init parsing of first page
    if (currentPage <= pdf.numPages) getPage();

    // main entry point/function for loop
    function getPage() {

        // when promise is returned do as usual
        pdf.getPage(currentPage).then(function(page) {

            var scale = 1.5;
            var viewport = page.getViewport(scale);

            canvas.height = viewport.height;
            canvas.width = viewport.width;

            var renderContext = {
                canvasContext: ctx,
                viewport: viewport
            };

            // now, tap into the returned promise from render:
            page.render(renderContext).then(function() {

                // store compressed image data in array
                pages.Push(canvas.toDataURL());

                if (currentPage < pdf.numPages) {
                    currentPage++;
                    getPage();        // get next page
                }
                else {
                    done();           // call done() when all pages are parsed
                }
            });
        });
    }

}

Lorsque vous devez ensuite récupérer une page, vous créez simplement un élément d'image et définissez le data-uri en tant que source:

function drawPage(index, callback) {
    var img = new Image;
    img.onload = function() {
        /* this will draw the image loaded onto canvas at position 0,0
           at the optional width and height of the canvas.
           'this' is current image loaded 
        */
        ctx.drawImage(this, 0, 0, ctx.canvas.width, ctx.canvas.height);
        callback();          // invoke callback when we're done
    }
    img.src = pages[index];  // start loading the data-uri as source
}

En raison du chargement de l'image, elle sera également de nature asynchrone, raison pour laquelle nous avons besoin du rappel. Si vous ne souhaitez pas utiliser la nature asynchrone, vous pouvez également effectuer cette étape (création et définition de l'élément image) dans la promesse de rendu au-dessus de la sauvegarde d'éléments d'image au lieu de données-uris.

J'espère que cela t'aides!

18
user1693593

Ce n'est pas une réponse, mais une donnée HTML complète telle que l'information peut être plus complète. Le but est d'utiliser une solution minimale pdf.js pour afficher plusieurs pages pdf car le exemple helloworld ne peut restituer qu'une seule page. Le JavasScript suivant ne fonctionne pas, espérons que quelqu'un pourra résoudre le problème.

<!doctype html>
<html>
<head>
<meta charset=utf-8>
<!-- Use latest PDF.js build from Github -->
<script src=https://raw.github.com/mozilla/pdf.js/gh-pages/build/pdf.js></script>
</head>
<body>
<canvas id=the-canvas style="border:1px solid black"></canvas>

<script>
var pdf = null;
PDFJS.disableWorker = true;
var pages = new Array();
var canvas = document.getElementById('the-canvas');
var context = canvas.getContext('2d');
var scale = 1.5;
var canvasWidth = 0;
var canvasHeight = 0;
var pageStarts = new Array();
pageStarts[0] = 0;
var url = 'pdfjs.pdf';

PDFJS.getDocument(url).then(function getPdfHelloWorld(_pdf) {
  pdf = _pdf;
  //Render all the pages on a single canvas
  for(var i=1; i<=pdf.numPages; i++) {
    pdf.getPage(i).then(function getPage(page) {
      var viewport = page.getViewport(scale);
      canvas.width = viewport.width;    // changing canvas.width and/or canvas.height auto-clears the canvas
      canvas.height = viewport.height;
      page.render({canvasContext:context, viewport:viewport});
      pages[i-1] = context.getImageData(0, 0, canvas.width, canvas.height);
      if(canvas.width>canvasWidth) {  // calculate the width of the final display canvas
        canvasWidth = canvas.width;
      }
      canvasHeight += canvas.height;   // calculate the accumulated with of the final display canvas
      pageStarts[i] = pageStarts[i-1] + canvas.height;    // save the "Y" starting position of this pages[i]
    });
  }
  canvas.width = canvasWidth;
  canvas.height = canvasHeight;  // this auto-clears all canvas contents
  for(var i=0; i<pages.length; i++) {
    context.putImageData(pages[i], 0, pageStarts[i]);
  }
});
</script>

</body>
</html>
2
Randy Tang