Skip to content

Commit

Permalink
Load built-in CMap files using the Fetch API when possible
Browse files Browse the repository at this point in the history
  • Loading branch information
Snuffleupagus committed Feb 26, 2019
1 parent 6520590 commit 5473178
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 40 deletions.
73 changes: 53 additions & 20 deletions src/display/display_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@

import {
assert, CMapCompressionType, removeNullCharacters, stringToBytes,
unreachable, Util, warn
unreachable, URL, Util, warn
} from '../shared/util';
import isFetchSupported from '../shared/is_fetch_supported';

const DEFAULT_LINK_REL = 'noopener noreferrer nofollow';
const SVG_NS = 'http://www.w3.org/2000/svg';
Expand Down Expand Up @@ -66,19 +67,41 @@ class DOMCMapReaderFactory {
this.isCompressed = isCompressed;
}

fetch({ name, }) {
async fetch({ name, }) {
if (!this.baseUrl) {
return Promise.reject(new Error(
throw new Error(
'The CMap "baseUrl" parameter must be specified, ensure that ' +
'the "cMapUrl" and "cMapPacked" API parameters are provided.'));
'the "cMapUrl" and "cMapPacked" API parameters are provided.');
}
if (!name) {
return Promise.reject(new Error('CMap name must be specified.'));
throw new Error('CMap name must be specified.');
}
const url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : '');
const compressionType = (this.isCompressed ? CMapCompressionType.BINARY :
CMapCompressionType.NONE);

if ((typeof PDFJSDev !== 'undefined' && PDFJSDev.test('MOZCENTRAL')) ||
isFetchSupported() && isValidFetchUrl(url, document.baseURI)) {
return fetch(url).then(async (response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
let cMapData;
if (this.isCompressed) {
cMapData = new Uint8Array(await response.arrayBuffer());
} else {
cMapData = stringToBytes(await response.text());
}
return { cMapData, compressionType, };
}).catch((reason) => {
throw new Error(`Unable to load ${this.isCompressed ? 'binary ' : ''}` +
`CMap at: ${url}`);
});
}
return new Promise((resolve, reject) => {
let url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : '');

let request = new XMLHttpRequest();
// The Fetch API is not supported.
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', url, true);

if (this.isCompressed) {
Expand All @@ -89,27 +112,24 @@ class DOMCMapReaderFactory {
return;
}
if (request.status === 200 || request.status === 0) {
let data;
let cMapData;
if (this.isCompressed && request.response) {
data = new Uint8Array(request.response);
cMapData = new Uint8Array(request.response);
} else if (!this.isCompressed && request.responseText) {
data = stringToBytes(request.responseText);
cMapData = stringToBytes(request.responseText);
}
if (data) {
resolve({
cMapData: data,
compressionType: this.isCompressed ?
CMapCompressionType.BINARY : CMapCompressionType.NONE,
});
if (cMapData) {
resolve({ cMapData, compressionType, });
return;
}
}
reject(new Error('Unable to load ' +
(this.isCompressed ? 'binary ' : '') +
'CMap at: ' + url));
reject(new Error(request.statusText));
};

request.send(null);
}).catch((reason) => {
throw new Error(`Unable to load ${this.isCompressed ? 'binary ' : ''}` +
`CMap at: ${url}`);
});
}
}
Expand Down Expand Up @@ -428,6 +448,19 @@ class DummyStatTimer {
}
}

function isValidFetchUrl(url, baseUrl) {
if (!url) {
return false;
}
try {
const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
// The Fetch API only supports the http/https protocols, and not file/ftp.
return /http?:/.test(absoluteUrl.protocol);
} catch (ex) {
return false; // `new URL()` will throw on incorrect data.
}
}

function loadScript(src) {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
Expand Down
10 changes: 5 additions & 5 deletions src/pdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable no-unused-vars, no-restricted-globals */
/* eslint-disable no-unused-vars */

'use strict';

Expand All @@ -32,13 +32,13 @@ let pdfjsDisplayAPICompatibility = require('./display/api_compatibility.js');

if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
const isNodeJS = require('./shared/is_node.js');
const isFetchSupported = require('./shared/is_fetch_supported.js');
if (isNodeJS()) {
let PDFNodeStream = require('./display/node_stream.js').PDFNodeStream;
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
return new PDFNodeStream(params);
});
} else if (typeof Response !== 'undefined' && 'body' in Response.prototype &&
typeof ReadableStream !== 'undefined') {
} else if (isFetchSupported()) {
let PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream;
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
return new PDFFetchStream(params);
Expand All @@ -50,6 +50,7 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
});
}
} else if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME')) {
const isFetchSupported = require('./shared/is_fetch_supported.js');
let PDFNetworkStream = require('./display/network.js').PDFNetworkStream;
let PDFFetchStream;
let isChromeWithFetchCredentials = function() {
Expand All @@ -65,8 +66,7 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
return true;
}
};
if (typeof Response !== 'undefined' && 'body' in Response.prototype &&
typeof ReadableStream !== 'undefined' && isChromeWithFetchCredentials()) {
if (isFetchSupported() && isChromeWithFetchCredentials()) {
PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream;
}
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
Expand Down
22 changes: 22 additions & 0 deletions src/shared/is_fetch_supported.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* Copyright 2019 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable no-restricted-globals */
/* globals module */

module.exports = function isFetchSupported() {
return (typeof fetch !== 'undefined' &&
typeof Response !== 'undefined' && 'body' in Response.prototype &&
typeof ReadableStream !== 'undefined');
};
29 changes: 14 additions & 15 deletions test/unit/test_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,32 +99,31 @@ class NodeCMapReaderFactory {
this.isCompressed = isCompressed;
}

fetch({ name, }) {
async fetch({ name, }) {
if (!this.baseUrl) {
return Promise.reject(new Error(
throw new Error(
'The CMap "baseUrl" parameter must be specified, ensure that ' +
'the "cMapUrl" and "cMapPacked" API parameters are provided.'));
'the "cMapUrl" and "cMapPacked" API parameters are provided.');
}
if (!name) {
return Promise.reject(new Error('CMap name must be specified.'));
throw new Error('CMap name must be specified.');
}
return new Promise((resolve, reject) => {
let url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : '');
const url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : '');
const compressionType = (this.isCompressed ? CMapCompressionType.BINARY :
CMapCompressionType.NONE);

let fs = require('fs');
return new Promise((resolve, reject) => {
const fs = require('fs');
fs.readFile(url, (error, data) => {
if (error || !data) {
reject(new Error('Unable to load ' +
(this.isCompressed ? 'binary ' : '') +
'CMap at: ' + url));
reject(new Error(error));
return;
}
resolve({
cMapData: new Uint8Array(data),
compressionType: this.isCompressed ?
CMapCompressionType.BINARY : CMapCompressionType.NONE,
});
resolve({ cMapData: new Uint8Array(data), compressionType, });
});
}).catch((reason) => {
throw new Error(`Unable to load ${this.isCompressed ? 'binary ' : ''}` +
`CMap at: ${url}`);
});
}
}
Expand Down

0 comments on commit 5473178

Please sign in to comment.