如何将从 http 调用收到的 pdf 加载到查看器中?

问题描述

我正在从以下位置获取 pdf:

return this.http.get(path,{ observe: 'response',responseType: 'arraybuffer' });

并将其作为 blob 处理:

let blob: Blob = new Blob([response.body],{ type: 'application/pdf' });

我现在希望将该文件存储在本地并通过本地 url 引用它。我需要使用 url 的原因是因为 viewer.loadModel() 需要它作为参数。我曾尝试使用 let url = window.URL.createObjectURL(this.blobToFile(blob,'viewer.pdf')); 获取网址。

当我这样做时,我得到:https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a 但是,如果没有 '.pdf' 后面的文件,查看者是不知道文件类型的。如果我把 .pdf 放在那个 url 后面:https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a.pdf,它似乎加载了一些数据,但我收到以下错误

core.js:6241 ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'getData' of undefined
TypeError: Cannot read property 'getData' of undefined
    at v (Viewer3D.js:1335)
    at PDFLoader.js:1358
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:386)
    at Object.onInvoke (core.js:41697)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:385)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:143)
    at zone.js:891
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:41675)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
    at resolvePromise (zone.js:832)
    at zone.js:739
    at zone.js:755
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:386)
    at Object.onInvoke (core.js:41697)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:385)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:143)
    at zone.js:891
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:41675)

在下面的最后一行:(Viewer3D.js:1335)

Viewer3D.prototype.loadModel = async function(url,options,onSuccessCallback,onErrorCallback,onWorkerStart) {
        var self = this;

        // Kind of sucks,but I Couldn't think of a better way because loaders
        // are so
        var reservation = self.impl._reserveLoadingFile();     // Reserve a slot for a loading file
        options = options || {};

        if (typeof options.skipPropertyDb === "undefined") {
            var skipParam = getParameterByName("skipPropertyDb") || "";
            options.skipPropertyDb = skipParam === "true" ? true : (skipParam === "false" ? false : undefined);
        }

        var loaderInstance;
        function onDone( error,model ) {
            self.impl._removeLoadingFile(loaderInstance);
            if (error) {
                self.dispatchEvent({ type: et.LOADER_LOAD_ERROR_EVENT,error: error,loader: loaderInstance })
                onError( error.code,error.msg,error.args );
                return;
            }

            model.getData().underlayRaster = options.underlayRaster && model.getLeaflet();

有没有办法可以从 blob 本地存储 pdf,然后获取一个可以使用它的本地 URL?或者有什么方法可以传递不记名令牌以便加载程序可以访问存储?我不知道还能做什么,因为viewer.loadModel() 似乎只有在我使用文件的本地绝对路径时才有效(例如https://localhost:3000/Content/Documents/qcad1.pdf") . 我还想提一下,我无法通过模型衍生 API 处理它,因为如果我这样做,它会丢失 pdf 向量。

更多背景: 这是我加载模型的地方...

Autodesk.Viewing.Initializer({ env: 'Local' },() => {
            this.viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer'));
            this.viewer.start();
            this.viewer.setTheme('dark-theme');
            this.viewer.loadExtension('Autodesk.Viewing.Markupscore');

            this.viewer.loadModel(this.pdfUrl,{},this.initializeMeasureSettings.bind(this),() => {
                    console.log('File Could not be loaded');

                    // On error was hoping to load a sample model but it loses context to the viewer somehow
                    //this.viewer.loadModel(this.samplePDF,this.initializeMeasureSettings);
                });

            this.viewer.addEventListener('measurement-changed',this.onMeasurementChanged.bind(this));
            this.viewer.addEventListener('measurement-completed',this.onMeasurementCompleted.bind(this));
            this.viewer.addEventListener('finished-calibration',this.onCalibrationFinished.bind(this));
            this.viewer.addEventListener('delete-measurement',this.onObjectDeleted.bind(this));
            this.viewer.addEventListener('extensionLoaded',this.extensionLoaded.bind(this));

        });

解决方法

所以我最终深入研究了 pdfLoader,我找到了一种向下传递不记名令牌的方法,PDFJS 在从 url 加载文件时使用该令牌!

就在调用 viewer.loadModel 之前,我设置了:

Autodesk.Viewing.endpoint.HTTP_REQUEST_HEADERS = { Authorization: `Bearer ${this.authenticationService.getToken}` };

这是访问 PDFLoader.js:749 params.httpHeaders = av.endpoint.HTTP_REQUEST_HEADERS;

这个 params 对象最终被传递到 Promise.resolve(第 1370 行)中的 PDFJS.getDocument(params)

这是 pdf.js 中的标准功能,但也许 Autodesk 可以通过允许它在 viewer.loadModel() 中的选项对象中传递它来使其更容易。

无论如何,这允许我从后端安全地下载文件并将它们加载到查看器中。

,

恐怕 loadModel 方法不支持以任何方式指定自定义 HTTP 请求标头,因此如果对您的 PDF 文件的访问受访问令牌保护,那将会很棘手。

您可以尝试“破解” loadModel 函数内部的逻辑,不是通过将 .pdf 直接附加到数据 URI,而是通过在 URL 查询或哈希中伪造扩展名,例如:

https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a?name=foo.pdf

https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a#foo.pdf

可能足以说服查看者使用 PDF 加载器,同时它不会破坏 URL 的含义。