Convert PDFPageProxy, in src/display/api.js, to an ES6 class

This changes all occurrences of `var` to `let`/`const` in this code, and updates the signatures of a couple of methods to use object destructuring.
Finally, when creating `InternalRenderTask` instances *only* the necessary parameter are now provided, since passing through the `RenderParameters` as-is seems completely unnecessary.
This commit is contained in:
Jonas Jenwald 2018-11-08 14:13:42 +01:00
parent 2c003a82d5
commit 5a0d64a6de

View File

@ -850,385 +850,395 @@ class PDFDocumentProxy {
/** /**
* Proxy to a PDFPage in the worker thread. * Proxy to a PDFPage in the worker thread.
* @class
* @alias PDFPageProxy * @alias PDFPageProxy
*/ */
var PDFPageProxy = (function PDFPageProxyClosure() { class PDFPageProxy {
function PDFPageProxy(pageIndex, pageInfo, transport, pdfBug = false) { constructor(pageIndex, pageInfo, transport, pdfBug = false) {
this.pageIndex = pageIndex; this.pageIndex = pageIndex;
this._pageInfo = pageInfo; this._pageInfo = pageInfo;
this.transport = transport; this._transport = transport;
this._stats = (pdfBug ? new StatTimer() : DummyStatTimer); this._stats = (pdfBug ? new StatTimer() : DummyStatTimer);
this._pdfBug = pdfBug; this._pdfBug = pdfBug;
this.commonObjs = transport.commonObjs; this.commonObjs = transport.commonObjs;
this.objs = new PDFObjects(); this.objs = new PDFObjects();
this.cleanupAfterRender = false; this.cleanupAfterRender = false;
this.pendingCleanup = false; this.pendingCleanup = false;
this.intentStates = Object.create(null); this.intentStates = Object.create(null);
this.destroyed = false; this.destroyed = false;
} }
PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ {
/**
* @return {number} Page number of the page. First page is 1.
*/
get pageNumber() {
return this.pageIndex + 1;
},
/**
* @return {number} The number of degrees the page is rotated clockwise.
*/
get rotate() {
return this._pageInfo.rotate;
},
/**
* @return {Object} The reference that points to this page. It has 'num' and
* 'gen' properties.
*/
get ref() {
return this._pageInfo.ref;
},
/**
* @return {number} The default size of units in 1/72nds of an inch.
*/
get userUnit() {
return this._pageInfo.userUnit;
},
/**
* @return {Array} An array of the visible portion of the PDF page in the
* user space units - [x1, y1, x2, y2].
*/
get view() {
return this._pageInfo.view;
},
/** /**
* @param {number} scale The desired scale of the viewport. * @return {number} Page number of the page. First page is 1.
* @param {number} rotate Degrees to rotate the viewport. If omitted this */
* defaults to the page rotation. get pageNumber() {
* @param {boolean} dontFlip (optional) If true, axis Y will not be flipped. return this.pageIndex + 1;
* @return {PageViewport} Contains 'width' and 'height' properties }
* along with transforms required for rendering.
*/
getViewport(scale, rotate = this.rotate, dontFlip = false) {
return new PageViewport({
viewBox: this.view,
scale,
rotation: rotate,
dontFlip,
});
},
/**
* @param {GetAnnotationsParameters} params - Annotation parameters.
* @return {Promise} A promise that is resolved with an {Array} of the
* annotation objects.
*/
getAnnotations: function PDFPageProxy_getAnnotations(params) {
var intent = (params && params.intent) || null;
if (!this.annotationsPromise || this.annotationsIntent !== intent) { /**
this.annotationsPromise = this.transport.getAnnotations(this.pageIndex, * @return {number} The number of degrees the page is rotated clockwise.
intent); */
this.annotationsIntent = intent; get rotate() {
} return this._pageInfo.rotate;
return this.annotationsPromise; }
},
/**
* Begins the process of rendering a page to the desired context.
* @param {RenderParameters} params Page render parameters.
* @return {RenderTask} An object that contains the promise, which
* is resolved when the page finishes rendering.
*/
render: function PDFPageProxy_render(params) {
let stats = this._stats;
stats.time('Overall');
// If there was a pending destroy cancel it so no cleanup happens during /**
// this call to render. * @return {Object} The reference that points to this page. It has 'num' and
this.pendingCleanup = false; * 'gen' properties.
*/
get ref() {
return this._pageInfo.ref;
}
var renderingIntent = (params.intent === 'print' ? 'print' : 'display'); /**
var canvasFactory = params.canvasFactory || new DOMCanvasFactory(); * @return {number} The default size of units in 1/72nds of an inch.
let webGLContext = new WebGLContext({ */
enable: params.enableWebGL, get userUnit() {
}); return this._pageInfo.userUnit;
}
if (!this.intentStates[renderingIntent]) { /**
this.intentStates[renderingIntent] = Object.create(null); * @return {Array} An array of the visible portion of the PDF page in the
} * user space units - [x1, y1, x2, y2].
var intentState = this.intentStates[renderingIntent]; */
get view() {
return this._pageInfo.view;
}
// If there's no displayReadyCapability yet, then the operatorList /**
// was never requested before. Make the request and create the promise. * @param {number} scale The desired scale of the viewport.
if (!intentState.displayReadyCapability) { * @param {number} rotate Degrees to rotate the viewport. If omitted this
intentState.receivingOperatorList = true; * defaults to the page rotation.
intentState.displayReadyCapability = createPromiseCapability(); * @param {boolean} dontFlip (optional) If true, axis Y will not be flipped.
intentState.operatorList = { * @return {PageViewport} Contains 'width' and 'height' properties
fnArray: [], * along with transforms required for rendering.
argsArray: [], */
lastChunk: false, getViewport(scale, rotate = this.rotate, dontFlip = false) {
}; return new PageViewport({
viewBox: this.view,
scale,
rotation: rotate,
dontFlip,
});
}
stats.time('Page Request'); /**
this.transport.messageHandler.send('RenderPageRequest', { * @param {GetAnnotationsParameters} params - Annotation parameters.
pageIndex: this.pageNumber - 1, * @return {Promise} A promise that is resolved with an {Array} of the
intent: renderingIntent, * annotation objects.
renderInteractiveForms: (params.renderInteractiveForms === true), */
}); getAnnotations({ intent = null, } = {}) {
} if (!this.annotationsPromise || this.annotationsIntent !== intent) {
this.annotationsPromise = this._transport.getAnnotations(this.pageIndex,
intent);
this.annotationsIntent = intent;
}
return this.annotationsPromise;
}
var complete = (error) => { /**
var i = intentState.renderTasks.indexOf(internalRenderTask); * Begins the process of rendering a page to the desired context.
if (i >= 0) { * @param {RenderParameters} params Page render parameters.
intentState.renderTasks.splice(i, 1); * @return {RenderTask} An object that contains the promise, which
} * is resolved when the page finishes rendering.
*/
render({ canvasContext, viewport, intent = 'display', enableWebGL = false,
renderInteractiveForms = false, transform = null, imageLayer = null,
canvasFactory = null, background = null, }) {
const stats = this._stats;
stats.time('Overall');
if (this.cleanupAfterRender) { // If there was a pending destroy cancel it so no cleanup happens during
this.pendingCleanup = true; // this call to render.
} this.pendingCleanup = false;
this._tryCleanup();
if (error) { const renderingIntent = (intent === 'print' ? 'print' : 'display');
internalRenderTask.capability.reject(error); const canvasFactoryInstance = canvasFactory || new DOMCanvasFactory();
} else { const webGLContext = new WebGLContext({
internalRenderTask.capability.resolve(); enable: enableWebGL,
} });
stats.timeEnd('Rendering');
stats.timeEnd('Overall'); if (!this.intentStates[renderingIntent]) {
this.intentStates[renderingIntent] = Object.create(null);
}
const intentState = this.intentStates[renderingIntent];
// If there's no displayReadyCapability yet, then the operatorList
// was never requested before. Make the request and create the promise.
if (!intentState.displayReadyCapability) {
intentState.receivingOperatorList = true;
intentState.displayReadyCapability = createPromiseCapability();
intentState.operatorList = {
fnArray: [],
argsArray: [],
lastChunk: false,
}; };
var internalRenderTask = new InternalRenderTask(complete, params, stats.time('Page Request');
this._transport.messageHandler.send('RenderPageRequest', {
pageIndex: this.pageNumber - 1,
intent: renderingIntent,
renderInteractiveForms: renderInteractiveForms === true,
});
}
const complete = (error) => {
const i = intentState.renderTasks.indexOf(internalRenderTask);
if (i >= 0) {
intentState.renderTasks.splice(i, 1);
}
if (this.cleanupAfterRender) {
this.pendingCleanup = true;
}
this._tryCleanup();
if (error) {
internalRenderTask.capability.reject(error);
} else {
internalRenderTask.capability.resolve();
}
stats.timeEnd('Rendering');
stats.timeEnd('Overall');
};
const internalRenderTask = new InternalRenderTask(complete, {
canvasContext,
viewport,
transform,
imageLayer,
background,
},
this.objs, this.objs,
this.commonObjs, this.commonObjs,
intentState.operatorList, intentState.operatorList,
this.pageNumber, this.pageNumber,
canvasFactory, canvasFactoryInstance,
webGLContext, webGLContext,
this._pdfBug); this._pdfBug);
internalRenderTask.useRequestAnimationFrame = renderingIntent !== 'print'; internalRenderTask.useRequestAnimationFrame = renderingIntent !== 'print';
if (!intentState.renderTasks) { if (!intentState.renderTasks) {
intentState.renderTasks = []; intentState.renderTasks = [];
} }
intentState.renderTasks.push(internalRenderTask); intentState.renderTasks.push(internalRenderTask);
var renderTask = internalRenderTask.task; const renderTask = internalRenderTask.task;
intentState.displayReadyCapability.promise.then((transparency) => { intentState.displayReadyCapability.promise.then((transparency) => {
if (this.pendingCleanup) { if (this.pendingCleanup) {
complete(); complete();
return;
}
stats.time('Rendering');
internalRenderTask.initializeGraphics(transparency);
internalRenderTask.operatorListChanged();
}).catch(complete);
return renderTask;
},
/**
* @return {Promise} A promise resolved with an {@link PDFOperatorList}
* object that represents page's operator list.
*/
getOperatorList: function PDFPageProxy_getOperatorList() {
function operatorListChanged() {
if (intentState.operatorList.lastChunk) {
intentState.opListReadCapability.resolve(intentState.operatorList);
var i = intentState.renderTasks.indexOf(opListTask);
if (i >= 0) {
intentState.renderTasks.splice(i, 1);
}
}
}
var renderingIntent = 'oplist';
if (!this.intentStates[renderingIntent]) {
this.intentStates[renderingIntent] = Object.create(null);
}
var intentState = this.intentStates[renderingIntent];
var opListTask;
if (!intentState.opListReadCapability) {
opListTask = {};
opListTask.operatorListChanged = operatorListChanged;
intentState.receivingOperatorList = true;
intentState.opListReadCapability = createPromiseCapability();
intentState.renderTasks = [];
intentState.renderTasks.push(opListTask);
intentState.operatorList = {
fnArray: [],
argsArray: [],
lastChunk: false,
};
this._stats.time('Page Request');
this.transport.messageHandler.send('RenderPageRequest', {
pageIndex: this.pageIndex,
intent: renderingIntent,
});
}
return intentState.opListReadCapability.promise;
},
/**
* @param {getTextContentParameters} params - getTextContent parameters.
* @return {ReadableStream} ReadableStream to read textContent chunks.
*/
streamTextContent(params = {}) {
const TEXT_CONTENT_CHUNK_SIZE = 100;
return this.transport.messageHandler.sendWithStream('GetTextContent', {
pageIndex: this.pageNumber - 1,
normalizeWhitespace: (params.normalizeWhitespace === true),
combineTextItems: (params.disableCombineTextItems !== true),
}, {
highWaterMark: TEXT_CONTENT_CHUNK_SIZE,
size(textContent) {
return textContent.items.length;
},
});
},
/**
* @param {getTextContentParameters} params - getTextContent parameters.
* @return {Promise} That is resolved a {@link TextContent}
* object that represent the page text content.
*/
getTextContent: function PDFPageProxy_getTextContent(params) {
params = params || {};
let readableStream = this.streamTextContent(params);
return new Promise(function(resolve, reject) {
function pump() {
reader.read().then(function({ value, done, }) {
if (done) {
resolve(textContent);
return;
}
Object.assign(textContent.styles, value.styles);
textContent.items.push(...value.items);
pump();
}, reject);
}
let reader = readableStream.getReader();
let textContent = {
items: [],
styles: Object.create(null),
};
pump();
});
},
/**
* Destroys page object.
*/
_destroy: function PDFPageProxy_destroy() {
this.destroyed = true;
this.transport.pageCache[this.pageIndex] = null;
var waitOn = [];
Object.keys(this.intentStates).forEach(function(intent) {
if (intent === 'oplist') {
// Avoid errors below, since the renderTasks are just stubs.
return;
}
var intentState = this.intentStates[intent];
intentState.renderTasks.forEach(function(renderTask) {
var renderCompleted = renderTask.capability.promise.
catch(function () {}); // ignoring failures
waitOn.push(renderCompleted);
renderTask.cancel();
});
}, this);
this.objs.clear();
this.annotationsPromise = null;
this.pendingCleanup = false;
return Promise.all(waitOn);
},
/**
* Cleans up resources allocated by the page.
* @param {boolean} resetStats - (optional) Reset page stats, if enabled.
* The default value is `false`.
*/
cleanup(resetStats = false) {
this.pendingCleanup = true;
this._tryCleanup(resetStats);
},
/**
* For internal use only. Attempts to clean up if rendering is in a state
* where that's possible.
* @ignore
*/
_tryCleanup(resetStats = false) {
if (!this.pendingCleanup ||
Object.keys(this.intentStates).some(function(intent) {
var intentState = this.intentStates[intent];
return (intentState.renderTasks.length !== 0 ||
intentState.receivingOperatorList);
}, this)) {
return; return;
} }
stats.time('Rendering');
internalRenderTask.initializeGraphics(transparency);
internalRenderTask.operatorListChanged();
}).catch(complete);
Object.keys(this.intentStates).forEach(function(intent) { return renderTask;
delete this.intentStates[intent]; }
}, this);
this.objs.clear();
this.annotationsPromise = null;
if (resetStats && this._stats instanceof StatTimer) {
this._stats = new StatTimer();
}
this.pendingCleanup = false;
},
/**
* For internal use only.
* @ignore
*/
_startRenderPage: function PDFPageProxy_startRenderPage(transparency,
intent) {
var intentState = this.intentStates[intent];
// TODO Refactor RenderPageRequest to separate rendering
// and operator list logic
if (intentState.displayReadyCapability) {
intentState.displayReadyCapability.resolve(transparency);
}
},
/**
* For internal use only.
* @ignore
*/
_renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk,
intent) {
var intentState = this.intentStates[intent];
var i, ii;
// Add the new chunk to the current operator list.
for (i = 0, ii = operatorListChunk.length; i < ii; i++) {
intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
intentState.operatorList.argsArray.push(
operatorListChunk.argsArray[i]);
}
intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
// Notify all the rendering tasks there are more operators to be consumed. /**
for (i = 0; i < intentState.renderTasks.length; i++) { * @return {Promise} A promise resolved with an {@link PDFOperatorList}
intentState.renderTasks[i].operatorListChanged(); * object that represents page's operator list.
*/
getOperatorList() {
function operatorListChanged() {
if (intentState.operatorList.lastChunk) {
intentState.opListReadCapability.resolve(intentState.operatorList);
const i = intentState.renderTasks.indexOf(opListTask);
if (i >= 0) {
intentState.renderTasks.splice(i, 1);
}
}
}
const renderingIntent = 'oplist';
if (!this.intentStates[renderingIntent]) {
this.intentStates[renderingIntent] = Object.create(null);
}
const intentState = this.intentStates[renderingIntent];
let opListTask;
if (!intentState.opListReadCapability) {
opListTask = {};
opListTask.operatorListChanged = operatorListChanged;
intentState.receivingOperatorList = true;
intentState.opListReadCapability = createPromiseCapability();
intentState.renderTasks = [];
intentState.renderTasks.push(opListTask);
intentState.operatorList = {
fnArray: [],
argsArray: [],
lastChunk: false,
};
this._stats.time('Page Request');
this._transport.messageHandler.send('RenderPageRequest', {
pageIndex: this.pageIndex,
intent: renderingIntent,
});
}
return intentState.opListReadCapability.promise;
}
/**
* @param {getTextContentParameters} params - getTextContent parameters.
* @return {ReadableStream} ReadableStream to read textContent chunks.
*/
streamTextContent({ normalizeWhitespace = false,
disableCombineTextItems = false, } = {}) {
const TEXT_CONTENT_CHUNK_SIZE = 100;
return this._transport.messageHandler.sendWithStream('GetTextContent', {
pageIndex: this.pageNumber - 1,
normalizeWhitespace: normalizeWhitespace === true,
combineTextItems: disableCombineTextItems !== true,
}, {
highWaterMark: TEXT_CONTENT_CHUNK_SIZE,
size(textContent) {
return textContent.items.length;
},
});
}
/**
* @param {getTextContentParameters} params - getTextContent parameters.
* @return {Promise} That is resolved a {@link TextContent}
* object that represent the page text content.
*/
getTextContent(params = {}) {
const readableStream = this.streamTextContent(params);
return new Promise(function(resolve, reject) {
function pump() {
reader.read().then(function({ value, done, }) {
if (done) {
resolve(textContent);
return;
}
Object.assign(textContent.styles, value.styles);
textContent.items.push(...value.items);
pump();
}, reject);
} }
if (operatorListChunk.lastChunk) { const reader = readableStream.getReader();
intentState.receivingOperatorList = false; const textContent = {
this._tryCleanup(); items: [],
} styles: Object.create(null),
}, };
pump();
});
}
/** /**
* @return {Object} Returns page stats, if enabled. * Destroys page object.
*/ */
get stats() { _destroy() {
return (this._stats instanceof StatTimer ? this._stats : null); this.destroyed = true;
}, this._transport.pageCache[this.pageIndex] = null;
};
return PDFPageProxy; const waitOn = [];
})(); Object.keys(this.intentStates).forEach(function(intent) {
if (intent === 'oplist') {
// Avoid errors below, since the renderTasks are just stubs.
return;
}
const intentState = this.intentStates[intent];
intentState.renderTasks.forEach(function(renderTask) {
const renderCompleted = renderTask.capability.promise.
catch(function() {}); // ignoring failures
waitOn.push(renderCompleted);
renderTask.cancel();
});
}, this);
this.objs.clear();
this.annotationsPromise = null;
this.pendingCleanup = false;
return Promise.all(waitOn);
}
/**
* Cleans up resources allocated by the page.
* @param {boolean} resetStats - (optional) Reset page stats, if enabled.
* The default value is `false`.
*/
cleanup(resetStats = false) {
this.pendingCleanup = true;
this._tryCleanup(resetStats);
}
/**
* For internal use only. Attempts to clean up if rendering is in a state
* where that's possible.
* @ignore
*/
_tryCleanup(resetStats = false) {
if (!this.pendingCleanup ||
Object.keys(this.intentStates).some(function(intent) {
const intentState = this.intentStates[intent];
return (intentState.renderTasks.length !== 0 ||
intentState.receivingOperatorList);
}, this)) {
return;
}
Object.keys(this.intentStates).forEach(function(intent) {
delete this.intentStates[intent];
}, this);
this.objs.clear();
this.annotationsPromise = null;
if (resetStats && this._stats instanceof StatTimer) {
this._stats = new StatTimer();
}
this.pendingCleanup = false;
}
/**
* For internal use only.
* @ignore
*/
_startRenderPage(transparency, intent) {
const intentState = this.intentStates[intent];
// TODO Refactor RenderPageRequest to separate rendering
// and operator list logic
if (intentState.displayReadyCapability) {
intentState.displayReadyCapability.resolve(transparency);
}
}
/**
* For internal use only.
* @ignore
*/
_renderPageChunk(operatorListChunk, intent) {
const intentState = this.intentStates[intent];
// Add the new chunk to the current operator list.
for (let i = 0, ii = operatorListChunk.length; i < ii; i++) {
intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
intentState.operatorList.argsArray.push(
operatorListChunk.argsArray[i]);
}
intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
// Notify all the rendering tasks there are more operators to be consumed.
for (let i = 0; i < intentState.renderTasks.length; i++) {
intentState.renderTasks[i].operatorListChanged();
}
if (operatorListChunk.lastChunk) {
intentState.receivingOperatorList = false;
this._tryCleanup();
}
}
/**
* @return {Object} Returns page stats, if enabled.
*/
get stats() {
return (this._stats instanceof StatTimer ? this._stats : null);
}
}
class LoopbackPort { class LoopbackPort {
constructor(defer = true) { constructor(defer = true) {