Рендеринг.pdf в один холст, используя pdf.js и ImageData

Я пытаюсь прочитать весь документ.pdf, используя PDF.js, а затем отобразить все страницы на одном холсте.

Моя идея: визуализировать каждую страницу на холсте и получить ImageData (context.getImageData()), очистить холст сделать следующую страницу. Я храню все ImageDatas в массиве, и как только все страницы будут там, я хочу поместить все ImageDatas из массива на один холст.

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

Итак, с моей точки зрения, это должно работать, верно? Когда я запускаю это, я получаю достаточно большой холст, который содержит все страницы PDF, но не показывает PDF...

Спасибо за помощь.

4 ответа

Решение

Я не могу говорить с частью вашего кода, которая переводит PDF в холст, но я вижу некоторые проблемы.

  • Каждый сброс настроек canvas.width или canvas.height автоматически очищает содержимое холста. Так что в верхнем разделе ваш clearRect не нужен, потому что холст очищается с помощью canvas.width до каждого вашего page.render.
  • Что еще более важно, в нижнем разделе все ваши предыдущие рисунки в формате pdf очищаются при каждом изменении размера холста (упс!).
  • getImageData() получает массив, в котором каждый пиксель представлен 4 последовательными элементами этого массива (красный, затем зеленый, затем синий, затем альфа). Так как getImageData() является массивом, поэтому он не имеет страниц [i].width или pages[i].height - он имеет только страницы [i].length. Эта длина массива не может быть использована для определения ширины или высоты.

Итак, для начала я бы изменил ваш код на это (очень, очень непроверенный!):

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

});

В качестве альтернативы, вот более традиционный способ выполнения вашей задачи:

Используйте один холст "показа" и позвольте пользователю "пролистать" каждую нужную страницу.

Поскольку вы уже начинаете с рисования каждой страницы на холсте, почему бы не сохранить отдельный скрытый холст для каждой страницы. Затем, когда пользователь хочет видеть страницу № 6, вы просто копируете скрытый холст № 6 на свой холст дисплея.

Разработчики Mozilla используют этот подход в своей демонстрации pdfJS здесь: http://mozilla.github.com/pdf.js/web/viewer.html

Вы можете проверить код для зрителя здесь: http://mozilla.github.com/pdf.js/web/viewer.js

Операции PDF асинхронны на всех этапах. Это означает, что вам также нужно поймать обещание на последнем рендере. Если вы не поймаете его, вы получите только пустой холст, так как рендеринг не закончен, пока цикл не перейдет на следующую страницу.

Совет: я бы также порекомендовал вам использовать что-то кроме getImageData так как при этом будет храниться несжатый битовый массив, например data-uri, а не сжатые данные.

Вот немного другой подход, исключающий цикл for и лучше использующий обещания для этой цели:

Живая скрипка

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

}

Когда вам нужно получить страницу, вы просто создаете элемент изображения и устанавливаете data-uri в качестве источника:

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
}

Из-за загрузки изображения оно также будет асинхронным, поэтому нам нужен обратный вызов. Если вам не нужна асинхронная природа, вы также можете выполнить этот шаг (создание и настройка элемента изображения) в обещании рендеринга, приведенном выше для хранения элементов изображения вместо data-uris.

Надеюсь это поможет!

Это не ответ, а целые данные HTML, так что информация может быть более полной. Цель состоит в том, чтобы использовать минимальное решение pdf.js для отображения нескольких страниц pdf, поскольку пример helloworld может отображать только одну страницу. Следующий JavasScript не работает, надеюсь, что кто-то может решить проблему.

<!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>

Вы можете передать номер страницы обещаниям, получить данные страницы холста и отобразить в правильном порядке на холсте

    var renderPageFactory = function (pdfDoc, num) {
        return function () {

            var localCanvas = document.createElement('canvas');

            ///return pdfDoc.getPage(num).then(renderPage);
            return  pdfDoc.getPage(num).then((page) => {
                renderPage(page, localCanvas, num);
            });


        };
    };

    var renderPages = function (pdfDoc) {
        var renderedPage = $q.resolve();
        for (var num = 1; num <= pdfDoc.numPages; num++) {
            // Wait for the last page t render, then render the next
            renderedPage = renderedPage.then(renderPageFactory(pdfDoc, num));
        }
    };

    renderPages(pdf);

Полный пример

   function renderPDF(url, canvas) {



    var pdf = null;
    PDFJS.disableWorker = true;
    var pages = new Array();

    var context = canvas.getContext('2d');
    var scale = 1;

    var canvasWidth = 256;
    var canvasHeight = 0;
    var pageStarts = new Array();
    pageStarts[0] = 0;





    var k = 0;

    function finishPage(localCanvas, num) {
        var ctx = localCanvas.getContext('2d');

        pages[num] = ctx.getImageData(0, 0, localCanvas.width, localCanvas.height);

        // calculate the accumulated with of the final display canvas
        canvasHeight += localCanvas.height;
        // save the "Y" starting position of this pages[i]
        pageStarts[num] = pageStarts[num -1] + localCanvas.height;

        if (k + 1 >= pdf.numPages)
        {


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

            var img = canvas.toDataURL("image/png");
            $scope.printPOS(img);
        }

        k++;


    }

    function renderPage(page, localCanvas, num) {

        var ctx = localCanvas.getContext('2d');

        var viewport = page.getViewport(scale);


        // var viewport = page.getViewport(canvas.width / page.getViewport(1.0).width);
        // changing canvas.width and/or canvas.height auto-clears the canvas
        localCanvas.width = viewport.width;

        /// viewport.width = canvas.width;
        localCanvas.height = viewport.height;

        var renderTask = page.render({canvasContext: ctx, viewport: viewport});


        renderTask.then(() => {
            finishPage(localCanvas, num);
        });


    }





    PDFJS.getDocument(url).then(function getPdfHelloWorld(_pdf) {

        pdf = _pdf;



        var renderPageFactory = function (pdfDoc, num) {
            return function () {

                var localCanvas = document.createElement('canvas');

                ///return pdfDoc.getPage(num).then(renderPage);
                return  pdfDoc.getPage(num).then((page) => {
                    renderPage(page, localCanvas, num);
                });


            };
        };

        var renderPages = function (pdfDoc) {
            var renderedPage = $q.resolve();
            for (var num = 1; num <= pdfDoc.numPages; num++) {
                // Wait for the last page t render, then render the next
                renderedPage = renderedPage.then(renderPageFactory(pdfDoc, num));
            }
        };

        renderPages(pdf);






    });





}

Другие вопросы по тегам