Load built-in CMap files using the Fetch API when possible

This commit is contained in:
Jonas Jenwald 2019-02-24 15:22:25 +01:00
parent df7756d2a4
commit cbc07f985b
3 changed files with 75 additions and 40 deletions

View File

@ -15,7 +15,7 @@
import { import {
assert, CMapCompressionType, removeNullCharacters, stringToBytes, assert, CMapCompressionType, removeNullCharacters, stringToBytes,
unreachable, Util, warn unreachable, URL, Util, warn
} from '../shared/util'; } from '../shared/util';
const DEFAULT_LINK_REL = 'noopener noreferrer nofollow'; const DEFAULT_LINK_REL = 'noopener noreferrer nofollow';
@ -66,19 +66,41 @@ class DOMCMapReaderFactory {
this.isCompressed = isCompressed; this.isCompressed = isCompressed;
} }
fetch({ name, }) { async fetch({ name, }) {
if (!this.baseUrl) { if (!this.baseUrl) {
return Promise.reject(new Error( throw new Error(
'The CMap "baseUrl" parameter must be specified, ensure that ' + '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) { 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) => { const url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : '');
let url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : ''); const compressionType = (this.isCompressed ? CMapCompressionType.BINARY :
CMapCompressionType.NONE);
let request = new XMLHttpRequest(); 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}`);
});
}
// The Fetch API is not supported.
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('GET', url, true); request.open('GET', url, true);
if (this.isCompressed) { if (this.isCompressed) {
@ -89,27 +111,24 @@ class DOMCMapReaderFactory {
return; return;
} }
if (request.status === 200 || request.status === 0) { if (request.status === 200 || request.status === 0) {
let data; let cMapData;
if (this.isCompressed && request.response) { if (this.isCompressed && request.response) {
data = new Uint8Array(request.response); cMapData = new Uint8Array(request.response);
} else if (!this.isCompressed && request.responseText) { } else if (!this.isCompressed && request.responseText) {
data = stringToBytes(request.responseText); cMapData = stringToBytes(request.responseText);
} }
if (data) { if (cMapData) {
resolve({ resolve({ cMapData, compressionType, });
cMapData: data,
compressionType: this.isCompressed ?
CMapCompressionType.BINARY : CMapCompressionType.NONE,
});
return; return;
} }
} }
reject(new Error('Unable to load ' + reject(new Error(request.statusText));
(this.isCompressed ? 'binary ' : '') +
'CMap at: ' + url));
}; };
request.send(null); request.send(null);
}).catch((reason) => {
throw new Error(`Unable to load ${this.isCompressed ? 'binary ' : ''}` +
`CMap at: ${url}`);
}); });
} }
} }
@ -428,6 +447,23 @@ class DummyStatTimer {
} }
} }
function isFetchSupported() {
return (typeof fetch !== 'undefined' &&
typeof Response !== 'undefined' && 'body' in Response.prototype &&
// eslint-disable-next-line no-restricted-globals
typeof ReadableStream !== 'undefined');
}
function isValidFetchUrl(url, baseUrl) {
try {
const { protocol, } = baseUrl ? new URL(url, baseUrl) : new URL(url);
// The Fetch API only supports the http/https protocols, and not file/ftp.
return (protocol === 'http:' || protocol === 'https:');
} catch (ex) {
return false; // `new URL()` will throw on incorrect data.
}
}
function loadScript(src) { function loadScript(src) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let script = document.createElement('script'); let script = document.createElement('script');
@ -453,5 +489,6 @@ export {
DOMSVGFactory, DOMSVGFactory,
StatTimer, StatTimer,
DummyStatTimer, DummyStatTimer,
isFetchSupported,
loadScript, loadScript,
}; };

View File

@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* eslint-disable no-unused-vars, no-restricted-globals */ /* eslint-disable no-unused-vars */
'use strict'; 'use strict';
@ -37,8 +37,7 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => { pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
return new PDFNodeStream(params); return new PDFNodeStream(params);
}); });
} else if (typeof Response !== 'undefined' && 'body' in Response.prototype && } else if (pdfjsDisplayDisplayUtils.isFetchSupported()) {
typeof ReadableStream !== 'undefined') {
let PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream; let PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream;
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => { pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
return new PDFFetchStream(params); return new PDFFetchStream(params);
@ -65,8 +64,8 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
return true; return true;
} }
}; };
if (typeof Response !== 'undefined' && 'body' in Response.prototype && if (pdfjsDisplayDisplayUtils.isFetchSupported() &&
typeof ReadableStream !== 'undefined' && isChromeWithFetchCredentials()) { isChromeWithFetchCredentials()) {
PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream; PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream;
} }
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => { pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {

View File

@ -99,32 +99,31 @@ class NodeCMapReaderFactory {
this.isCompressed = isCompressed; this.isCompressed = isCompressed;
} }
fetch({ name, }) { async fetch({ name, }) {
if (!this.baseUrl) { if (!this.baseUrl) {
return Promise.reject(new Error( throw new Error(
'The CMap "baseUrl" parameter must be specified, ensure that ' + '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) { 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) => { const url = this.baseUrl + name + (this.isCompressed ? '.bcmap' : '');
let 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) => { fs.readFile(url, (error, data) => {
if (error || !data) { if (error || !data) {
reject(new Error('Unable to load ' + reject(new Error(error));
(this.isCompressed ? 'binary ' : '') +
'CMap at: ' + url));
return; return;
} }
resolve({ resolve({ cMapData: new Uint8Array(data), compressionType, });
cMapData: new Uint8Array(data),
compressionType: this.isCompressed ?
CMapCompressionType.BINARY : CMapCompressionType.NONE,
});
}); });
}).catch((reason) => {
throw new Error(`Unable to load ${this.isCompressed ? 'binary ' : ''}` +
`CMap at: ${url}`);
}); });
} }
} }