diff --git a/src/core.js b/src/core.js
index 90a7eda50..e425e9ab5 100644
--- a/src/core.js
+++ b/src/core.js
@@ -63,13 +63,8 @@ var Page = (function PageClosure() {
function Page(xref, pageNumber, pageDict, ref) {
this.pageNumber = pageNumber;
this.pageDict = pageDict;
- this.stats = {
- create: Date.now(),
- compile: 0.0,
- fonts: 0.0,
- images: 0.0,
- render: 0.0
- };
+ this.stats = new StatTimer();
+ this.stats.enabled = !!globalScope.PDFJS.enableStats;
this.xref = xref;
this.ref = ref;
@@ -200,6 +195,8 @@ var Page = (function PageClosure() {
return this.IRQueue;
}
+ this.stats.time('Build IR Queue');
+
var xref = this.xref;
var content = xref.fetchIfRef(this.content);
var resources = xref.fetchIfRef(this.resources);
@@ -217,11 +214,14 @@ var Page = (function PageClosure() {
var pe = this.pe = new PartialEvaluator(
xref, handler, 'p' + this.pageNumber + '_');
var IRQueue = {};
- return (this.IRQueue = pe.getIRQueue(content, resources, IRQueue,
- dependency));
+ this.IRQueue = pe.getIRQueue(content, resources, IRQueue, dependency);
+
+ this.stats.timeEnd('Build IR Queue');
+ return this.IRQueue;
},
ensureFonts: function pageEnsureFonts(fonts, callback) {
+ this.stats.time('Font Loading');
// Convert the font names to the corresponding font obj.
for (var i = 0, ii = fonts.length; i < ii; i++) {
fonts[i] = this.objs.objs[fonts[i]].data;
@@ -231,7 +231,7 @@ var Page = (function PageClosure() {
var fontObjs = FontLoader.bind(
fonts,
function pageEnsureFontsFontObjs(fontObjs) {
- this.stats.fonts = Date.now();
+ this.stats.timeEnd('Font Loading');
callback.call(this);
}.bind(this),
@@ -240,6 +240,8 @@ var Page = (function PageClosure() {
},
display: function pageDisplay(gfx, callback) {
+ var stats = this.stats;
+ stats.time('Rendering');
var xref = this.xref;
var resources = xref.fetchIfRef(this.resources);
var mediaBox = xref.fetchIfRef(this.mediaBox);
@@ -266,8 +268,9 @@ var Page = (function PageClosure() {
function next() {
startIdx = gfx.executeIRQueue(IRQueue, startIdx, next, stepper);
if (startIdx == length) {
- self.stats.render = Date.now();
gfx.endDrawing();
+ stats.timeEnd('Rendering');
+ stats.timeEnd('Overall');
if (callback) callback();
}
}
@@ -431,15 +434,14 @@ var Page = (function PageClosure() {
return items;
},
startRendering: function pageStartRendering(ctx, callback, textLayer) {
- this.startRenderingTime = Date.now();
-
+ var stats = this.stats;
+ stats.time('Overall');
// If there is no displayReadyPromise yet, then the IRQueue was never
// requested before. Make the request and create the promise.
if (!this.displayReadyPromise) {
this.pdf.startRendering(this);
this.displayReadyPromise = new Promise();
}
-
// Once the IRQueue and fonts are loaded, perform the actual rendering.
this.displayReadyPromise.then(
function pageDisplayReadyPromise() {
@@ -731,7 +733,7 @@ var PDFDoc = (function PDFDocClosure() {
var pageNum = data.pageNum;
var page = this.pageCache[pageNum];
var depFonts = data.depFonts;
-
+ page.stats.timeEnd('Page Request');
page.startRenderingFromIRQueue(data.IRQueue, depFonts);
}, this);
@@ -840,6 +842,7 @@ var PDFDoc = (function PDFDocClosure() {
startRendering: function pdfDocStartRendering(page) {
// The worker might not be ready to receive the page request yet.
this.workerReadyPromise.then(function pdfDocStartRenderingThen() {
+ page.stats.time('Page Request');
this.messageHandler.send('page_request', page.pageNumber + 1);
}.bind(this));
},
diff --git a/src/util.js b/src/util.js
index 93bd36b55..b6869b30f 100644
--- a/src/util.js
+++ b/src/util.js
@@ -416,3 +416,55 @@ var Promise = (function PromiseClosure() {
return Promise;
})();
+var StatTimer = (function StatTimerClosure() {
+ function rpad(str, pad, length) {
+ while (str.length < length)
+ str += pad;
+ return str;
+ }
+ function StatTimer() {
+ this.started = {};
+ this.times = [];
+ this.enabled = true;
+ }
+ StatTimer.prototype = {
+ time: function statTimerTime(name) {
+ if (!this.enabled)
+ return;
+ if (name in this.started)
+ throw 'Timer is already running for ' + name;
+ this.started[name] = Date.now();
+ },
+ timeEnd: function statTimerTimeEnd(name) {
+ if (!this.enabled)
+ return;
+ if (!(name in this.started))
+ throw 'Timer has not been started for ' + name;
+ this.times.push({
+ 'name': name,
+ 'start': this.started[name],
+ 'end': Date.now()
+ });
+ // Remove timer from started so it can be called again.
+ delete this.started[name];
+ },
+ toString: function statTimerToString() {
+ var times = this.times;
+ var out = '';
+ // Find the longest name for padding purposes.
+ var longest = 0;
+ for (var i = 0, ii = times.length; i < ii; ++i) {
+ var name = times[i]['name'];
+ if (name.length > longest)
+ longest = name.length;
+ }
+ for (var i = 0, ii = times.length; i < ii; ++i) {
+ var span = times[i];
+ var duration = span.end - span.start;
+ out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
+ }
+ return out;
+ }
+ };
+ return StatTimer;
+})();
diff --git a/web/debugger.js b/web/debugger.js
index 43407fdaa..00f5f6fd4 100644
--- a/web/debugger.js
+++ b/web/debugger.js
@@ -318,6 +318,58 @@ var Stepper = (function StepperClosure() {
return Stepper;
})();
+var Stats = (function Stats() {
+ var stats = [];
+ function clear(node) {
+ while (node.hasChildNodes())
+ node.removeChild(node.lastChild);
+ }
+ function getStatIndex(pageNumber) {
+ for (var i = 0, ii = stats.length; i < ii; ++i)
+ if (stats[i].pageNumber === pageNumber)
+ return i;
+ return false;
+ }
+ return {
+ // Poperties/functions needed by PDFBug.
+ id: 'Stats',
+ name: 'Stats',
+ panel: null,
+ manager: null,
+ init: function init() {
+ this.panel.setAttribute('style', 'padding: 5px;');
+ PDFJS.enableStats = true;
+ },
+ enabled: false,
+ active: false,
+ // Stats specific functions.
+ add: function(pageNumber, stat) {
+ if (!stat)
+ return;
+ var statsIndex = getStatIndex(pageNumber);
+ if (statsIndex !== false) {
+ var b = stats[statsIndex];
+ this.panel.removeChild(b.div);
+ stats.splice(statsIndex, 1);
+ }
+ var wrapper = document.createElement('div');
+ wrapper.className = 'stats';
+ var title = document.createElement('div');
+ title.className = 'title';
+ title.textContent = 'Page: ' + pageNumber;
+ var statsDiv = document.createElement('div');
+ statsDiv.textContent = stat.toString();
+ wrapper.appendChild(title);
+ wrapper.appendChild(statsDiv);
+ stats.push({ pageNumber: pageNumber, div: wrapper });
+ stats.sort(function(a, b) { return a.pageNumber - b.pageNumber});
+ clear(this.panel);
+ for (var i = 0, ii = stats.length; i < ii; ++i)
+ this.panel.appendChild(stats[i].div);
+ }
+ };
+})();
+
// Manages all the debugging tools.
var PDFBug = (function PDFBugClosure() {
var panelWidth = 300;
@@ -327,8 +379,29 @@ var PDFBug = (function PDFBugClosure() {
return {
tools: [
FontInspector,
- StepperManager
+ StepperManager,
+ Stats
],
+ enable: function(ids) {
+ var all = false, tools = this.tools;
+ if (ids.length === 1 && ids[0] === 'all')
+ all = true;
+ for (var i = 0; i < tools.length; ++i) {
+ var tool = tools[i];
+ if (all || ids.indexOf(tool.id) !== -1)
+ tool.enabled = true;
+ }
+ if (!all) {
+ // Sort the tools by the order they are enabled.
+ tools.sort(function(a, b) {
+ var indexA = ids.indexOf(a.id);
+ indexA = indexA < 0 ? tools.length : indexA;
+ var indexB = ids.indexOf(b.id);
+ indexB = indexB < 0 ? tools.length : indexB;
+ return indexA - indexB;
+ });
+ }
+ },
init: function init() {
/*
* Basic Layout:
diff --git a/web/viewer.css b/web/viewer.css
index befaf7aa6..fdce0288a 100644
--- a/web/viewer.css
+++ b/web/viewer.css
@@ -65,16 +65,6 @@ body {
line-height: 16px;
}
-span#info {
- display: none;
-}
-
-@-moz-document regexp("http:.*debug=1.*") {
- span#info {
- display: inline-block;
- }
-}
-
/* === Sidebar === */
#sidebar {
position: fixed;
@@ -442,3 +432,11 @@ canvas {
background: yellow;
opacity: 0.3;
}
+#PDFBug .stats {
+ font-size: 10px;
+ white-space: pre;
+ font-family: courier;
+}
+#PDFBug .stats .title {
+ font-weight: bold;
+}
diff --git a/web/viewer.html b/web/viewer.html
index 3d1b3c4b7..34b2e77cb 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -102,7 +102,6 @@
- --