Merge pull request #10585 from Snuffleupagus/cmap-fetch
Load built-in CMap files using the Fetch API when possible
This commit is contained in:
commit
21d70b19c2
@ -15,7 +15,7 @@
|
||||
|
||||
import {
|
||||
assert, CMapCompressionType, removeNullCharacters, stringToBytes,
|
||||
unreachable, Util, warn
|
||||
unreachable, URL, Util, warn
|
||||
} from '../shared/util';
|
||||
|
||||
const DEFAULT_LINK_REL = 'noopener noreferrer nofollow';
|
||||
@ -66,19 +66,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.');
|
||||
}
|
||||
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 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);
|
||||
|
||||
if (this.isCompressed) {
|
||||
@ -89,27 +111,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}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let script = document.createElement('script');
|
||||
@ -453,5 +489,7 @@ export {
|
||||
DOMSVGFactory,
|
||||
StatTimer,
|
||||
DummyStatTimer,
|
||||
isFetchSupported,
|
||||
isValidFetchUrl,
|
||||
loadScript,
|
||||
};
|
||||
|
24
src/pdf.js
24
src/pdf.js
@ -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';
|
||||
|
||||
@ -37,15 +37,17 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
|
||||
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
|
||||
return new PDFNodeStream(params);
|
||||
});
|
||||
} else if (typeof Response !== 'undefined' && 'body' in Response.prototype &&
|
||||
typeof ReadableStream !== 'undefined') {
|
||||
let PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream;
|
||||
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
|
||||
return new PDFFetchStream(params);
|
||||
});
|
||||
} else {
|
||||
let PDFNetworkStream = require('./display/network.js').PDFNetworkStream;
|
||||
let PDFFetchStream;
|
||||
if (pdfjsDisplayDisplayUtils.isFetchSupported()) {
|
||||
PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream;
|
||||
}
|
||||
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
|
||||
if (PDFFetchStream &&
|
||||
pdfjsDisplayDisplayUtils.isValidFetchUrl(params.url)) {
|
||||
return new PDFFetchStream(params);
|
||||
}
|
||||
return new PDFNetworkStream(params);
|
||||
});
|
||||
}
|
||||
@ -65,13 +67,13 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
if (typeof Response !== 'undefined' && 'body' in Response.prototype &&
|
||||
typeof ReadableStream !== 'undefined' && isChromeWithFetchCredentials()) {
|
||||
if (pdfjsDisplayDisplayUtils.isFetchSupported() &&
|
||||
isChromeWithFetchCredentials()) {
|
||||
PDFFetchStream = require('./display/fetch_stream.js').PDFFetchStream;
|
||||
}
|
||||
pdfjsDisplayAPI.setPDFNetworkStreamFactory((params) => {
|
||||
if (PDFFetchStream && /^https?:/i.test(params.url)) {
|
||||
// "fetch" is only supported for http(s), not file/ftp.
|
||||
if (PDFFetchStream &&
|
||||
pdfjsDisplayDisplayUtils.isValidFetchUrl(params.url)) {
|
||||
return new PDFFetchStream(params);
|
||||
}
|
||||
return new PDFNetworkStream(params);
|
||||
|
@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
DOMSVGFactory, getFilenameFromUrl
|
||||
DOMSVGFactory, getFilenameFromUrl, isValidFetchUrl
|
||||
} from '../../src/display/display_utils';
|
||||
import isNodeJS from '../../src/shared/is_node';
|
||||
|
||||
@ -94,4 +94,28 @@ describe('display_utils', function() {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidFetchUrl', function() {
|
||||
it('handles invalid Fetch URLs', function() {
|
||||
expect(isValidFetchUrl(null)).toEqual(false);
|
||||
expect(isValidFetchUrl(100)).toEqual(false);
|
||||
expect(isValidFetchUrl('foo')).toEqual(false);
|
||||
expect(isValidFetchUrl('/foo', 100)).toEqual(false);
|
||||
});
|
||||
|
||||
it('handles relative Fetch URLs', function() {
|
||||
expect(isValidFetchUrl('/foo', 'file://www.example.com')).toEqual(false);
|
||||
expect(isValidFetchUrl('/foo', 'http://www.example.com')).toEqual(true);
|
||||
});
|
||||
|
||||
it('handles unsupported Fetch protocols', function() {
|
||||
expect(isValidFetchUrl('file://www.example.com')).toEqual(false);
|
||||
expect(isValidFetchUrl('ftp://www.example.com')).toEqual(false);
|
||||
});
|
||||
|
||||
it('handles supported Fetch protocols', function() {
|
||||
expect(isValidFetchUrl('http://www.example.com')).toEqual(true);
|
||||
expect(isValidFetchUrl('https://www.example.com')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user