【问题标题】:Render .pdf to single Canvas using pdf.js and ImageData使用 pdf.js 和 ImageData 将 .pdf 渲染到单个 Canvas
【发布时间】:2013-03-11 14:25:07
【问题描述】:

我正在尝试使用 PDF.js 读取整个 .pdf 文档,然后在单个画布上呈现所有页面。

我的想法:将每个页面渲染到画布上并获取 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...

感谢您的帮助。

【问题讨论】:

    标签: javascript html5-canvas getimagedata pdf.js putimagedata


    【解决方案1】:

    PDF 操作在所有阶段都是异步的。这意味着您还需要在最后一次渲染时获得承诺。如果你没有捕捉到它,你只会得到一个空白画布,因为在循环继续到下一页之前渲染还没有完成。

    提示:我还建议您使用 getImageData 以外的其他内容,因为这将存储未压缩的位图,例如 data-uri 而不是压缩数据。

    这是一种稍微不同的方法,它消除了 for 循环,并为此目的更好地使用了 Promise:

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

    当您需要检索页面时,您只需创建一个图像元素并将 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
    }
    

    由于图像加载,它本质上也是异步的,这就是我们需要回调的原因。如果您不想要异步特性,那么您也可以在上面的渲染承诺中执行此步骤(创建和设置图像元素)存储图像元素而不是数据 uris。

    希望这会有所帮助!

    【讨论】:

    • @yltang52 我添加了一个小提琴/演示。必须首先使用有效的相对或绝对 url 指定 url。我在答案中添加了更多评论/信息,但也许演示更清楚,因为它显示了发生了什么。
    • @yltang52 1) 这是一个 CORS 问题。该文件需要在同一台服务器上或允许使用 CORS。我使用 CORS 代理来让另一个文件工作,我在这里对你的文件做了同样的事情:jsfiddle.net/epistemex/LUNaJ/3。这是浏览器中的一种安全机制。 2) 这都是关于格式化、CSS、父容器等的。 3) 也可以在 Chrome/Opera 中工作,而且速度也更快 :) 希望这会有所帮助!不过,我会针对 2) 中的详细信息提出新问题。
    • 这是迄今为止我读过的最好的答案,它一定是问题的正确答案。我的情况是一个接一个地显示多个 pdf 文件,所以我使用您的解决方案完成了它。非常感谢你拯救了我的一天。
    • @MichaelKupietz 这是由于使用了 cors 和 cors-proxy。用不同的 cors-proxy 更新,直到它下降。看看新的更新是否有效。在现实世界中,您当然会使用页面来源内的链接,或者至少允许从您的来源使用 cors。
    • 啊,谢谢!这看起来对我正在进行的项目有很大帮助。
    【解决方案2】:

    我无法说出将 pdf 呈现为画布的代码部分,但我确实看到了一些问题。

    • 每次 重置 canvas.width 或 canvas.height 会自动清除画布内容。因此,在顶部,不需要您的 clearRect,因为在您的每个 page.render 之前,canvas.width 都会清除画布。
    • 更重要的是,在底部,每次调整画布大小都会清除您之前的所有 pdf 绘图(哎呀!)。
    • getImageData() 得到一个 数组,其中每个像素由该数组的 4 个连续元素表示(红色然后绿色然后蓝色然后 alpha)。由于 getImageData() 是一个数组,所以它没有 pages[i].width 或 pages[i].height——它只有 pages[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

    【讨论】:

    • 内容的清除确实是个问题,谢谢^^
    • @markE,我试过你的解决方案,但它没有用。我只需要使用 Helloworld 示例来显示整个 pdf 页面(pdf.js 项目太复杂,不适合我的需要)。您能提出一些更正的建议吗?
    • 许多/所有浏览器在画布元素上强加max size limitation,因此对于足够大的PDF,它无论如何都不起作用。我最近一直在为此苦苦挣扎,正如您所建议的,IMO 的最佳解决方案是一次显示一页。
    • 谁能建议如何使用 findcontroller 通过这个例子来搜索文本。请建议如何使用此 pdf 搜索文本。
    • 可能的链接,但没有看到提及画布元素github.com/mozilla/pdf.js/blob/master/web/viewer.html
    【解决方案3】:

    您可以将数字页面传递给承诺,获取该页面的画布数据并在画布上以正确的顺序呈现

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

    【讨论】:

      猜你喜欢
      • 2013-11-18
      • 2017-10-28
      • 1970-01-01
      • 1970-01-01
      • 2014-09-29
      • 2015-04-12
      • 2019-12-28
      • 2012-03-23
      • 2019-05-28
      相关资源
      最近更新 更多