如何使用 pdf.js 将每个用户上传的 pdf 文件的第一页显示为 Django 中的预览?

Posted

技术标签:

【中文标题】如何使用 pdf.js 将每个用户上传的 pdf 文件的第一页显示为 Django 中的预览?【英文标题】:How can I use pdf.js to display the first page of every user-uploaded pdf file as a preview in Django? 【发布时间】:2021-12-29 02:16:17 【问题描述】:

到目前为止,我已成功显示一个 pdf 文件的第一页预览,但它不适用于其余其他文件。

models.py

import uuid
from django.db import models


class PdfUploader(models.Model):
    uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
    docfile = models.FileField(upload_to='documents/%Y/%m/%d')
    uploaded_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'pdf_uploader'
        ordering = ['-uploaded_at']

    @property
    def filename(self):
        return self.docfile.name.split("/")[4].replace('_',' ').replace('-',' ')

views.py

class PdfUploadView(CreateView):
    def get(self, request, *args, **kwargs):
        context = 'form': PdfUploadForm()
        return render(request, 'partials/pdf_upload_form.htm', context)

    def post(self, request, *args, **kwargs):
        form = PdfUploadForm(request.POST, request.FILES)
        files = request.FILES.getlist('docfile')
        if form.is_valid():
            for f in files:
                file_instance = PdfUploader(docfile=f)
                file_instance.save()
            return HttpResponseRedirect(reverse_lazy('pdf-list'))
        return render(request, 'partials/pdf_upload_form.htm', 'form': form)

pdf_upload_form.htm

% block "content" %
<div role="main" class="main">
    <section class="section section-default pt-5 m-0">
        <div class="container">
            <form method="post" enctype="multipart/form-data">
                % csrf_token %
                 form.as_p 
                <button type="submit">Upload</button>
            </form>
        </div>
    </section>
</div>
% endblock %

pdf_lists.htm 通过关注官方official django docs,我将上下文变量作为 JSON 传递给 pdf.js。

% for obj in pdfs %
    <tr>
         <td>
               forloop.counter 
         </td>
         <td>
             <a href=" obj.docfile.url " target="_blank" rel="noopener noreferrer">obj.filename</a>
         </td>
         <td>
               obj.uploaded_at|date:"d-M-Y" 
         </td>
         <td>
             <a href="obj.docfile.url" target="_blank" rel="noopener noreferrer">
                 <canvas id="the-canvas" style="height:250px;">
                 </canvas>
                 obj.docfile.url|json_script:'mydata'
             </a>
         </td>
    </tr>
% endfor %

pdf.js 现在我正在阅读之前传递的 JSON,其中包含用户提交的 pdf 文件的路径,以使用 JS 进一步处理它以显示 pdf 的第一页作为预览。

const mydata = JSON.parse(document.getElementById('mydata').textContent);
console.log(mydata);

// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc = '//mozilla.github.io/pdf.js/build/pdf.worker.js';

// Asynchronous download of PDF

var loadingTask = pdfjsLib.getDocument(mydata);
loadingTask.promise.then(function (pdf) 
    console.log('PDF loaded');

    // Fetch the first page
    var pageNumber = 1;
    pdf.getPage(pageNumber).then(function (page) 
        console.log('Page loaded');

        var scale = 0.5;
        var viewport = page.getViewport( scale: scale );

        // Prepare canvas using PDF page dimensions
        var canvas = document.getElementById('the-canvas');
        var context = canvas.getContext('2d');
        canvas.height = viewport.height;
        canvas.width = viewport.width;

        // Render PDF page into canvas context
        var renderContext = 
            canvasContext: context,
            viewport: viewport
        ;
        var renderTask = page.render(renderContext);
        renderTask.promise.then(function () 
            console.log('Page rendered');
        );
    );
, function (reason) 
    // PDF loading error
    console.error(reason);
);

结果截图: 如您所见,第一个 pdf 文件显示预览,而其余的则不显示。

【问题讨论】:

【参考方案1】:

问题是你只调用你的脚本 1 次。它的输入是 1 个 ID 为“mydata”的对象和一个 ID 为“the-canvas”的画布作为输出。

你应该做什么:

首先,为每个 data 和每个 canvas 元素分配一个唯一 ID。

画布很简单:

  <canvas id="the-canvas forloop.counter " style="height:250px;">
  </canvas>

对于数据,由于 json_script 过滤器,它有点复杂,如下所示:

% with mydata_id="mydata"|add:forloop.counter %
    obj.docfile.url|json_script:mydata_id
% endwith %

那么你还必须知道pdfs的长度,所以也许在for循环之后添加这样的东西,又快又脏:

<script> const mypdfslength =  pdfs | length ; </script>

请确保将其放在您的 JS 之前。

接下来,在您的 js 中,您必须将所有代码放入从 0 到 mypdfslength 的 for 循环中。

当然,在解析 mydatacanvas 时,请确保通过它们的新 ID 引用它们,考虑到 i 是你的 for 循环索引:

const mydata = JSON.parse(document.getElementById(`mydatai`).textContent);

var canvas = document.getElementById(`the-canvasi`);

就是这样。

免责声明:我没有真正测试它,但这绝对是方向。

【讨论】:

非常感谢您调查此问题。我按照上面概述的说明进行操作。但是现在它抛出了这个错误 Uncaught TypeError: Cannot read properties of null (reading 'textContent') 正如我所提到的,这是解决问题的方向,而不是秘诀。所以它找不到元素mydata0 或任何数字。我建议您通过查看您获得的页面的 html 源代码来调试它。您在数据脚本子句中看到了哪些 ID?它们是否正确生成,它们的格式是mydataNUMBER 吗? 我检查了我的 html 源代码,一切都按正确的顺序排列。并且上述错误已经以某种方式得到解决。错误消息现在是“在多个渲染操作期间不能使用同一个画布”。 p.s.我对javascript不熟悉。【参考方案2】:

可以通过为每个 PDF 文件创建特定的画布来实现。 请用您的服务器文件替换 PDF 文件。

这是js代码

function LoadAndPrint()

    var files = [name:'sample1.pdf',url:'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf',name:'sample2.pdf',url:'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf'];

    var pdfjsLib = window['pdfjs-dist/build/pdf'];
    pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://mozilla.github.io/pdf.js/build/pdf.worker.js';

    files.forEach(myFunc);
    function myFunc(file,i)

        idContainer.innerHTML += 
        '<span>'+file.name+'</span><canvas id="the-canvas'+i+'"></canvas><hr><br>';

        var loadingTask = pdfjsLib.getDocument(file.url);
        loadingTask.promise.then(function(pdf) 
            console.log('PDF loaded');

            var pageNumber = 1;
            pdf.getPage(pageNumber).then(function(page) 
            console.log('Page loaded');

                var scale = 1.1;
                var viewport = page.getViewport(scale: scale);

                var canvas = document.getElementById("the-canvas"+i);
                var context = canvas.getContext('2d');
                canvas.height = viewport.height;
                canvas.width = viewport.width;

                var renderContext = 
                  canvasContext: context,
                  viewport: viewport
                ;

                var renderTask = page.render(renderContext);
                renderTask.promise.then(function () 
                  console.log('Page rendered');
                );
            );
        , function (reason) 
          console.error(reason);
        );
    

.html

<!DOCTYPE html>
    <head>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <script src="https://mozilla.github.io/pdf.js/build/pdf.js"></script>
    </head>

    <body>
        <button id="idPrint" onclick="LoadAndPrint()">Load and Print</button><br>
        <div id="idContainer"></div>
    </body>
</html>

这里是 JSFiffle

【讨论】:

以上是关于如何使用 pdf.js 将每个用户上传的 pdf 文件的第一页显示为 Django 中的预览?的主要内容,如果未能解决你的问题,请参考以下文章

pdf在线预览解决方案——pdf.js使用

pdf在线预览解决方案——pdf.js使用

pdf.js如何跨域读取pdf文件?

如何将pdf.js嵌入fancybox

将 pdf.js 与 Qt5.8 一起使用

如何使用 pdf.js 裁剪 PDF 页面的矩形区域