如何使用 javascript 从 Web 服务返回的二进制字符串构建 PDF 文件

Posted

技术标签:

【中文标题】如何使用 javascript 从 Web 服务返回的二进制字符串构建 PDF 文件【英文标题】:How to build PDF file from binary string returned from a web-service using javascript 【发布时间】:2012-10-04 06:26:48 【问题描述】:

我正在尝试从作为 Ajax 请求响应的二进制流中构建 PDF 文件。

通过XmlHttpRequest我收到以下数据:

%PDF-1.4....
.....
....hole data representing the file
....
%% EOF

到目前为止,我尝试的是通过 data:uri 嵌入我的数据。现在,它没有任何问题,并且工作正常。不幸的是,它在 IE9 和 Firefox 中不起作用。一个可能的原因可能是 FF 和 IE9 在使用 data-uri 时存在问题。

现在,我正在寻找适用于所有浏览器的任何解决方案。这是我的代码:

// responseText encoding 
pdfText = $.base64.decode($.trim(pdfText));

// Now pdfText contains %PDF-1.4 ...... data...... %%EOF

var winlogicalname = "detailPDF";
var winparams = 'dependent=yes,locationbar=no,scrollbars=yes,menubar=yes,'+
            'resizable,screenX=50,screenY=50,width=850,height=1050';

var htmlText = '<embed width=100% height=100%'
                     + ' type="application/pdf"'
                     + ' src="data:application/pdf,'
                     + escape(pdfText)
                     + '"></embed>'; 

                // Open PDF in new browser window
                var detailWindow = window.open ("", winlogicalname, winparams);
                detailWindow.document.write(htmlText);
                detailWindow.document.close();

正如我所说,它适用于 Opera 和 Chrome(Safari 尚未经过测试)。使用 IE 或 FF 会弹出一个空白的新窗口。

是否有任何解决方案,例如在文件系统上构建 PDF 文件 为了让用户下载吗?我需要适用于所有浏览器的解决方案,至少在 IE、FF、Opera、Chrome 和 Safari 中。

我无权编辑 Web 服务实现。所以它必须是客户端的解决方案。有什么想法吗?

【问题讨论】:

我认为最好是每个人都编写一个将pdf文件下载到文件夹的服务,让网络服务器为静态文件提供服务。根据路径,构造文件的URL。 同意@SrinivasReddyThatiparthy。下载到临时文件并将文件提供给用户。如果绝对需要使用对象和嵌入标签的组合,您可以内联显示它 - 请参阅此问题:***.com/questions/1244788/embed-vs-object/… 为什么还要用js抓取数据?你不能用 location= 打开新窗口吗? 【参考方案1】:

我意识到这是一个相当老的问题,但这是我今天想出的解决方案:

doSomethingToRequestData().then(function(downloadedFile) 
  // create a download anchor tag
  var downloadLink      = document.createElement('a');
  downloadLink.target   = '_blank';
  downloadLink.download = 'name_to_give_saved_file.pdf';

  // convert downloaded data to a Blob
  var blob = new Blob([downloadedFile.data],  type: 'application/pdf' );

  // create an object URL from the Blob
  var URL = window.URL || window.webkitURL;
  var downloadUrl = URL.createObjectURL(blob);

  // set object URL as the anchor's href
  downloadLink.href = downloadUrl;

  // append the anchor to document body
  document.body.append(downloadLink);

  // fire a click event on the anchor
  downloadLink.click();

  // cleanup: remove element and revoke object URL
  document.body.removeChild(downloadLink);
  URL.revokeObjectURL(downloadUrl);

【讨论】:

你需要用ajax来做吗?您可以简单地将url作为href并直接下载(但前提是它是一个简单的get请求) 在我们的例子中使用简单的锚不是一个选项,AJAX 是必要的。 这似乎在 React 和其他由库维护您的 dom 的虚拟化 dom 库中无法始终如一地工作,您不应该以这种方式直接与它交互。 @Urasquirrel 现在也不是 2016 年了。时间改变一切。【参考方案2】:

检测浏览器,Chrome 使用 Data-URI,其他浏览器使用PDF.js,如下所示。

PDFJS.getDocument(url_of_pdf)
.then(function(pdf) 
    return pdf.getPage(1);
)
.then(function(page) 
    // get a viewport
    var scale = 1.5;
    var viewport = page.getViewport(scale);
    // get or create a canvas
    var canvas = ...;
    canvas.width = viewport.width;
    canvas.height = viewport.height;

    // render a page
    page.render(
        canvasContext: canvas.getContext('2d'),
        viewport: viewport
    );
)
.catch(function(err) 
    // deal with errors here!
);

【讨论】:

【参考方案3】:

有没有像在文件系统上按顺序构建pdf文件的解决方案 让用户下载?

尝试将XMLHttpRequestresponseType 设置为blob,用a 元素的download 属性替换window.open,以允许从XMLHttpRequest 下载响应为.pdf 文件

var request = new XMLHttpRequest();
request.open("GET", "/path/to/pdf", true); 
request.responseType = "blob";
request.onload = function (e) 
    if (this.status === 200) 
        // `blob` response
        console.log(this.response);
        // create `objectURL` of `this.response` : `.pdf` as `Blob`
        var file = window.URL.createObjectURL(this.response);
        var a = document.createElement("a");
        a.href = file;
        a.download = this.response.name || "detailPDF";
        document.body.appendChild(a);
        a.click();
        // remove `a` following `Save As` dialog, 
        // `window` regains `focus`
        window.onfocus = function ()                      
          document.body.removeChild(a)
        
    ;
;
request.send();

【讨论】:

对我来说也很好,除了最后的removeChild(a),返回错误(类似于“找不到节点”)。所以我只是没有在window.onfocus 上触发此删除,而是直接将document.body.removeChild(a) 放在a.click() 之后。 @zezollo windowclick 事件之前获得关注?可能可以替换为document.body.appendChild(a);a.onclick = function() window.onfocus = function () document.body.removeChild(a); window.onfocus = null; a.click() 否,关闭 pdf 文件查看器(浏览器外部的软件,不是 pdf.js)后出现错误。我无法理解这个错误,因为在检查 html 代码时,它确实在正确的位置包含了 a 元素。无论如何,在click 之后立即删除孩子是否有问题?这真的很好。这也在这里使用:***.com/a/18197341/3926735 不,在元素上调用 .click() 后删除元素不是问题 对我来说,Windows 上最新的 chrome 似乎忽略了 a.click(),Linux 上的 firefox 也是如此【参考方案4】:

您可以使用PDF.js 从 javascript 创建 PDF 文件...它很容易编码...希望这能解决您的疑问!!!

问候!

【讨论】:

【参考方案5】:

@alexandre 使用 base64 的答案可以解决问题。

为什么它适用于 IE 的解释在这里

https://en.m.wikipedia.org/wiki/Data_URI_scheme

在标题“格式”下显示的地方

某些浏览器(Chrome、Opera、Safari、Firefox)接受非标准的 如果同时提供 ;base64 和 ;charset ,则订购,而 Internet Explorer 要求字符集的规范必须在 base64 令牌。

【讨论】:

【参考方案6】:

我在 php 中工作并使用一个函数来解码从服务器发回的二进制数据。我将信息提取到一个简单的 file.php 中,并通过我的服务器查看该文件,所有浏览器都显示 pdf 人工制品。

<?php
   $data = 'dfjhdfjhdfjhdfjhjhdfjhdfjhdfjhdfdfjhdf==blah...blah...blah..'

   $data = base64_decode($data);
    header("Content-type: application/pdf");
    header("Content-Length:" . strlen($data ));
    header("Content-Disposition: inline; filename=label.pdf");
    print $data;
    exit(1);

?>

【讨论】:

-1。有趣但不是用户要求的。他专门要求从 Web 服务到 Javascript 在客户端执行此操作,不要假设用户甚至控制 Web 服务或他正在使用服务器端语言。【参考方案7】:

我改变了这个:

var htmlText = '<embed width=100% height=100%'
                 + ' type="application/pdf"'
                 + ' src="data:application/pdf,'
                 + escape(pdfText)
                 + '"></embed>'; 

var htmlText = '<embed width=100% height=100%'
                 + ' type="application/pdf"'
                 + ' src="data:application/pdf;base64,'
                 + escape(pdfText)
                 + '"></embed>'; 

它对我有用。

【讨论】:

在安卓浏览器中工作? @Alexandre,此解决方案无法扩展。【参考方案8】:

我最近看到另一个关于这个主题的问题 (streaming pdf into iframe using dataurl only works in chrome)。

我在 ast 中构建了 pdf 并将它们流式传输到浏览器。我首先使用 fdf 创建它们,然后使用我自己编写的 pdf 类创建它们 - 在每种情况下,pdf 都是从基于通过 url 传入的几个 GET 参数从 COM 对象检索的数据创建的。

通过查看您在 ajax 调用中收到的发送数据,您似乎就快到了。我已经有几年没有玩过代码了,也没有像我想要的那样记录它,但是 - 我认为你需要做的就是将 iframe 的目标设置为 url你从那里得到pdf。虽然这可能不起作用 - 输出 pdf 的文件也可能必须先输出 html 响应头。

简而言之,这是我使用的输出代码:

//We send to a browser
header('Content-Type: application/pdf');
if(headers_sent())
    $this->Error('Some data has already been output, can\'t send PDF file');
header('Content-Length: '.strlen($this->buffer));
header('Content-Disposition: inline; filename="'.$name.'"');
header('Cache-Control: private, max-age=0, must-revalidate');
header('Pragma: public');
ini_set('zlib.output_compression','0');
echo $this->buffer;

因此,如果没有看到 ajax 调用的完整响应文本,我无法确定它是什么,尽管我倾向于认为输出您请求的 pdf 的代码可能只是等效的上面代码的最后一行。如果它是你可以控制的代码,我会尝试设置标题 - 这样浏览器就可以处理响应文本 - 你不必费心去做任何事情。

我只是为我想要的 pdf 构建了一个 url(一个时间表),然后创建了一个字符串,该字符串表示所需 sie、id 等的 iframe 的 html,该 iframe 使用构建的 url 作为 src。一旦我将 div 的内部 html 设置为构造的 html 字符串,浏览器就会要求提供 pdf,然后在收到时显示它。

function  showPdfTt(studentId)

    var url, tgt;
    title = byId("popupTitle");
    title.innerHTML = "Timetable for " + studentId;
    tgt = byId("popupContent");
    url = "pdftimetable.php?";
    url += "id="+studentId;
    url += "&type=Student";
    tgt.innerHTML = "<iframe onload=\"centerElem(byId('box'))\" src='"+url+"' width=\"700px\" height=\"500px\"></iframe>";

编辑:忘了提 - 您可以通过这种方式发送二进制 pdf。它们包含的流不需要是 ascii85 或十六进制编码。我在 pdf 中的所有流上都使用了 flate,效果很好。

【讨论】:

以上是关于如何使用 javascript 从 Web 服务返回的二进制字符串构建 PDF 文件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用javascript作为客户端和python作为Web服务器在websocets之间进行通信

如何从 Web 服务提供飞行前请求

如何在Web API中返回响应时隐藏模型的各个部分?

如何从网页Javascript调用C#方法? [关闭]

从 Rest 服务接收 excel 文件作为 javascript 响应

如何使用 AWS IoT 向/从 Web 浏览器发送/接收消息