Рендеринг.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);
});
}