2013-03-11 5 views
9

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

Моя идея: вынести каждую страницу на холст и получить 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 ...

Благодарим за помощь.

ответ

7

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

  • Каждый сброс canvas.width или canvas.height автоматически стирает содержимое холста. Таким образом, в верхней части вашего clearRect не требуется, потому что canvas очищается canvas.width перед каждой вашей страницей.
  • Что еще более важно, в нижней части все ваши предыдущие чертежи PDF очищаются каждым изменением размера холста (oops!).
  • 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

+0

очистное содержания действительно оказалось быть проблемой Спасибо ^^ –

+0

@markE, я пытался вам решение, но это не сработало. Все, что мне нужно, это использовать пример Helloworld для отображения всех страниц в формате pdf (проект pdf.js слишком сложный и не подходит для моих нужд). Не могли бы вы предложить некоторые исправления? –

+0

Многие/все браузеры накладывают [макс. Ограничение размера] (http://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element) на элементы холста, поэтому для достаточно больших PDF-файлов он выиграл ' t работать в любом случае. В последнее время я много борется с этим, и лучшее решение IMO было, как вы предлагаете, показывать одну страницу за раз. – Marxama

1

Это не ответ, а целый HTML данные, такие, что информация может быть более полной. Цель состоит в том, чтобы использовать минимальное решение pdf.js для отображения нескольких страниц PDF, потому что helloworld example может отображать только одну страницу. Следующий 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> 
15

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

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

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

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

} 

Когда вы после этого нужно извлечь страницу, которую вы просто создать элемент изображения и установить данные-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 
} 

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

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

+0

Большое спасибо за ваш ответ, Espistemex. Я попробовал вам решения, но не смог. Из-за моего незнания с холстом, пожалуйста, просветите меня дальше. Вопросы: 1) Я просто добавляю еще одно выражение: var url = 'example.pdf'; " перед вашими кодами, чтобы указать источник файла pdf. Это правильно? 2) Я не знаю, как использовать вторую часть вашего кода (часть img). Не могли бы вы подробнее рассказать? Благодарю. –

+1

@ yltang52 Я добавил скрипку/демо. Сначала URL должен быть указан с правильным или абсолютным URL-адресом. Я добавил больше комментариев/информации в ответ, но, возможно, демонстрация еще более понятна, поскольку она показывает, что происходит. – K3N

+0

Я пробовал твою скрипку, и это сработало. Потрясающе! Дальнейшие вопросы: 1) я изменил URL-адрес на '' http: // www.cyut.edu.tw/~ yltang/example.pdf'', и он не будет отображаться. На самом деле файл есть. Не возражаете ли вы попытаться понять, почему ваш файл работал, но не мой? 2) Как отобразить файл на одной странице за другой по вертикали? 3) Решение работает только на Firefox, верно? –

0

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

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






    }); 





} 

Смежные вопросы