Merge pull request #4318 from dferer/improveAnnotationsDisplay

Improved annotations' display/behavior.
This commit is contained in:
Yury Delendik 2014-03-07 09:42:42 -06:00
commit 01b23439a9
8 changed files with 305 additions and 131 deletions

View File

@ -134,7 +134,7 @@ var Page = (function PageClosure() {
}.bind(this));
return promise;
},
getOperatorList: function Page_getOperatorList(handler) {
getOperatorList: function Page_getOperatorList(handler, intent) {
var self = this;
var promise = new LegacyPromise();
@ -169,11 +169,12 @@ var Page = (function PageClosure() {
var contentStream = data[0];
var opList = new OperatorList(handler, self.pageIndex);
var opList = new OperatorList(intent, handler, self.pageIndex);
handler.send('StartRenderPage', {
transparency: partialEvaluator.hasBlendModes(self.resources),
pageIndex: self.pageIndex
pageIndex: self.pageIndex,
intent: intent
});
partialEvaluator.getOperatorList(contentStream, self.resources, opList);
pageListPromise.resolve(opList);
@ -191,7 +192,7 @@ var Page = (function PageClosure() {
}
var annotationsReadyPromise = Annotation.appendToOperatorList(
annotations, pageOpList, pdfManager, partialEvaluator);
annotations, pageOpList, pdfManager, partialEvaluator, intent);
annotationsReadyPromise.then(function () {
pageOpList.flush(true);
promise.resolve(pageOpList);

View File

@ -1376,7 +1376,7 @@ var OperatorList = (function OperatorListClosure() {
}
function OperatorList(messageHandler, pageIndex) {
function OperatorList(intent, messageHandler, pageIndex) {
this.messageHandler = messageHandler;
// When there isn't a message handler the fn array needs to be able to grow
// since we can't flush the operators.
@ -1389,6 +1389,7 @@ var OperatorList = (function OperatorListClosure() {
this.dependencies = {};
this.pageIndex = pageIndex;
this.fnIndex = 0;
this.intent = intent;
}
OperatorList.prototype = {
@ -1449,7 +1450,8 @@ var OperatorList = (function OperatorListClosure() {
lastChunk: lastChunk,
length: this.length
},
pageIndex: this.pageIndex
pageIndex: this.pageIndex,
intent: this.intent
}, null, transfers);
this.dependencies = [];
this.fnIndex = 0;

View File

@ -334,7 +334,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
var pageNum = data.pageIndex + 1;
var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images.
page.getOperatorList(handler).then(function(operatorList) {
page.getOperatorList(handler, data.intent).then(function(operatorList) {
info('page=' + pageNum + ' - getOperatorList: time=' +
(Date.now() - start) + 'ms, len=' + operatorList.fnArray.length);
@ -366,7 +366,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
handler.send('PageError', {
pageNum: pageNum,
error: wrappedException
error: wrappedException,
intent: data.intent
});
});
});

View File

@ -341,10 +341,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
this.stats.enabled = !!globalScope.PDFJS.enableStats;
this.commonObjs = transport.commonObjs;
this.objs = new PDFObjects();
this.receivingOperatorList = false;
this.cleanupAfterRender = false;
this.pendingDestroy = false;
this.renderTasks = [];
this.intentStates = {};
}
PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ {
/**
@ -423,12 +422,21 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
// this call to render.
this.pendingDestroy = false;
var renderingIntent = 'intent' in params ?
(params.intent == 'print' ? 'print' : 'display') :
'display';
if (!this.intentStates[renderingIntent]) {
this.intentStates[renderingIntent] = {};
}
var intentState = this.intentStates[renderingIntent];
// If there is no displayReadyPromise yet, then the operatorList was never
// requested before. Make the request and create the promise.
if (!this.displayReadyPromise) {
this.receivingOperatorList = true;
this.displayReadyPromise = new LegacyPromise();
this.operatorList = {
if (!intentState.displayReadyPromise) {
intentState.receivingOperatorList = true;
intentState.displayReadyPromise = new LegacyPromise();
intentState.operatorList = {
fnArray: [],
argsArray: [],
lastChunk: false
@ -436,18 +444,23 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
this.stats.time('Page Request');
this.transport.messageHandler.send('RenderPageRequest', {
pageIndex: this.pageNumber - 1
pageIndex: this.pageNumber - 1,
intent: renderingIntent
});
}
var internalRenderTask = new InternalRenderTask(complete, params,
this.objs, this.commonObjs,
this.operatorList, this.pageNumber);
this.renderTasks.push(internalRenderTask);
intentState.operatorList,
this.pageNumber);
if (!intentState.renderTasks) {
intentState.renderTasks = [];
}
intentState.renderTasks.push(internalRenderTask);
var renderTask = new RenderTask(internalRenderTask);
var self = this;
this.displayReadyPromise.then(
intentState.displayReadyPromise.then(
function pageDisplayReadyPromise(transparency) {
if (self.pendingDestroy) {
complete();
@ -463,9 +476,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
);
function complete(error) {
var i = self.renderTasks.indexOf(internalRenderTask);
var i = intentState.renderTasks.indexOf(internalRenderTask);
if (i >= 0) {
self.renderTasks.splice(i, 1);
intentState.renderTasks.splice(i, 1);
}
if (self.cleanupAfterRender) {
@ -513,14 +526,17 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
*/
_tryDestroy: function PDFPageProxy__destroy() {
if (!this.pendingDestroy ||
this.renderTasks.length !== 0 ||
this.receivingOperatorList) {
Object.keys(this.intentStates).some(function(intent) {
var intentState = this.intentStates[intent];
return intentState.renderTasks.length !== 0 ||
intentState.receivingOperatorList;
}, this)) {
return;
}
delete this.operatorList;
delete this.displayReadyPromise;
delete this.annotationsPromise;
Object.keys(this.intentStates).forEach(function(intent) {
delete this.intentStates[intent];
}, this);
this.objs.clear();
this.pendingDestroy = false;
},
@ -528,28 +544,33 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
* For internal use only.
* @ignore
*/
_startRenderPage: function PDFPageProxy_startRenderPage(transparency) {
this.displayReadyPromise.resolve(transparency);
_startRenderPage: function PDFPageProxy_startRenderPage(transparency,
intent) {
var intentState = this.intentStates[intent];
intentState.displayReadyPromise.resolve(transparency);
},
/**
* For internal use only.
* @ignore
*/
_renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) {
_renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk,
intent) {
var intentState = this.intentStates[intent];
// Add the new chunk to the current operator list.
for (var i = 0, ii = operatorListChunk.length; i < ii; i++) {
this.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
this.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
intentState.operatorList.argsArray.push(
operatorListChunk.argsArray[i]);
}
this.operatorList.lastChunk = operatorListChunk.lastChunk;
intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
// Notify all the rendering tasks there are more operators to be consumed.
for (var i = 0; i < this.renderTasks.length; i++) {
this.renderTasks[i].operatorListChanged();
for (var i = 0; i < intentState.renderTasks.length; i++) {
intentState.renderTasks[i].operatorListChanged();
}
if (operatorListChunk.lastChunk) {
this.receivingOperatorList = false;
intentState.receivingOperatorList = false;
this._tryDestroy();
}
}
@ -775,13 +796,13 @@ var WorkerTransport = (function WorkerTransportClosure() {
var page = this.pageCache[data.pageIndex];
page.stats.timeEnd('Page Request');
page._startRenderPage(data.transparency);
page._startRenderPage(data.transparency, data.intent);
}, this);
messageHandler.on('RenderPageChunk', function transportRender(data) {
var page = this.pageCache[data.pageIndex];
page._renderPageChunk(data.operatorList);
page._renderPageChunk(data.operatorList, data.intent);
}, this);
messageHandler.on('commonobj', function transportObj(data) {
@ -824,8 +845,9 @@ var WorkerTransport = (function WorkerTransportClosure() {
var pageIndex = data[1];
var type = data[2];
var pageProxy = this.pageCache[pageIndex];
if (pageProxy.objs.hasData(id))
if (pageProxy.objs.hasData(id)) {
return;
}
switch (type) {
case 'JpegStream':
@ -861,10 +883,11 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.workerReadyPromise.reject(data);
}, this);
messageHandler.on('PageError', function transportError(data) {
messageHandler.on('PageError', function transportError(data, intent) {
var page = this.pageCache[data.pageNum - 1];
if (page.displayReadyPromise)
page.displayReadyPromise.reject(data.error);
var intentState = page.intentStates[intent];
if (intentState.displayReadyPromise)
intentState.displayReadyPromise.reject(data.error);
else
error(data.error);
}, this);

View File

@ -21,6 +21,9 @@
'use strict';
var HIGHLIGHT_OFFSET = 4; // px
var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
var Annotation = (function AnnotationClosure() {
// 12.5.5: Algorithm: Appearance streams
function getTransformMatrix(rect, bbox, matrix) {
@ -143,25 +146,50 @@ var Annotation = (function AnnotationClosure() {
},
// TODO(mack): Remove this, it's not really that helpful.
getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect) {
getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect,
borderWidth) {
assert(!isWorker,
'getEmptyContainer() should be called from main thread');
var bWidth = borderWidth || 0;
rect = rect || this.data.rect;
var element = document.createElement(tagName);
element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
element.style.borderWidth = bWidth + 'px';
var width = rect[2] - rect[0] - 2 * bWidth;
var height = rect[3] - rect[1] - 2 * bWidth;
element.style.width = width + 'px';
element.style.height = height + 'px';
return element;
},
isInvisible: function Annotation_isInvisible() {
var data = this.data;
if (data && SUPPORTED_TYPES.indexOf(data.subtype) !== -1) {
return false;
} else {
return !!(data &&
data.annotationFlags && // Default: not invisible
data.annotationFlags & 0x1); // Invisible
}
},
isViewable: function Annotation_isViewable() {
var data = this.data;
return !!(
data &&
(!data.annotationFlags ||
!(data.annotationFlags & 0x22)) && // Hidden or NoView
data.rect // rectangle is nessessary
);
return !!(!this.isInvisible() &&
data &&
(!data.annotationFlags ||
!(data.annotationFlags & 0x22)) && // Hidden or NoView
data.rect); // rectangle is nessessary
},
isPrintable: function Annotation_isPrintable() {
var data = this.data;
return !!(!this.isInvisible() &&
data &&
data.annotationFlags && // Default: not printable
data.annotationFlags & 0x4 && // Print
data.rect); // rectangle is nessessary
},
loadResources: function(keys) {
@ -182,7 +210,7 @@ var Annotation = (function AnnotationClosure() {
return promise;
},
getOperatorList: function Annotation_getToOperatorList(evaluator) {
getOperatorList: function Annotation_getOperatorList(evaluator) {
var promise = new LegacyPromise();
@ -289,7 +317,7 @@ var Annotation = (function AnnotationClosure() {
var annotation = new Constructor(params);
if (annotation.isViewable()) {
if (annotation.isViewable() || annotation.isPrintable()) {
return annotation;
} else {
warn('unimplemented annotation type: ' + subtype);
@ -297,7 +325,7 @@ var Annotation = (function AnnotationClosure() {
};
Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
annotations, opList, pdfManager, partialEvaluator) {
annotations, opList, pdfManager, partialEvaluator, intent) {
function reject(e) {
annotationsReadyPromise.reject(e);
@ -307,7 +335,11 @@ var Annotation = (function AnnotationClosure() {
var annotationPromises = [];
for (var i = 0, n = annotations.length; i < n; ++i) {
annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
if (intent === 'display' && annotations[i].isViewable() ||
intent === 'print' && annotations[i].isPrintable()) {
annotationPromises.push(
annotations[i].getOperatorList(partialEvaluator));
}
}
Promise.all(annotationPromises).then(function(datas) {
opList.addOp(OPS.beginAnnotations, []);
@ -519,9 +551,64 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
return TextWidgetAnnotation;
})();
var InteractiveAnnotation = (function InteractiveAnnotationClosure() {
function InteractiveAnnotation(params) {
Annotation.call(this, params);
}
Util.inherit(InteractiveAnnotation, Annotation, {
hasHtml: function InteractiveAnnotation_hasHtml() {
return true;
},
highlight: function InteractiveAnnotation_highlight() {
if (this.highlightElement &&
this.highlightElement.hasAttribute('hidden')) {
this.highlightElement.removeAttribute('hidden');
}
},
unhighlight: function InteractiveAnnotation_unhighlight() {
if (this.highlightElement &&
!this.highlightElement.hasAttribute('hidden')) {
this.highlightElement.setAttribute('hidden', true);
}
},
initContainer: function InteractiveAnnotation_initContainer() {
var item = this.data;
var rect = item.rect;
var container = this.getEmptyContainer('section', rect, item.borderWidth);
container.style.backgroundColor = item.color;
var color = item.color;
var rgb = [];
for (var i = 0; i < 3; ++i) {
rgb[i] = Math.round(color[i] * 255);
}
item.colorCssRgb = Util.makeCssRgb(rgb);
var highlight = document.createElement('div');
highlight.className = 'annotationHighlight';
highlight.style.left = highlight.style.top = -HIGHLIGHT_OFFSET + 'px';
highlight.style.right = highlight.style.bottom = -HIGHLIGHT_OFFSET + 'px';
highlight.setAttribute('hidden', true);
this.highlightElement = highlight;
container.appendChild(this.highlightElement);
return container;
}
});
return InteractiveAnnotation;
})();
var TextAnnotation = (function TextAnnotationClosure() {
function TextAnnotation(params) {
Annotation.call(this, params);
InteractiveAnnotation.call(this, params);
if (params.data) {
return;
@ -534,22 +621,21 @@ var TextAnnotation = (function TextAnnotationClosure() {
var title = dict.get('T');
data.content = stringToPDFString(content || '');
data.title = stringToPDFString(title || '');
data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name;
if (data.hasAppearance) {
data.name = 'NoIcon';
} else {
data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
}
if (dict.has('C')) {
data.hasBgColor = true;
}
}
var ANNOT_MIN_SIZE = 10;
Util.inherit(TextAnnotation, Annotation, {
getOperatorList: function TextAnnotation_getOperatorList(evaluator) {
var promise = new LegacyPromise();
promise.resolve(new OperatorList());
return promise;
},
hasHtml: function TextAnnotation_hasHtml() {
return true;
},
Util.inherit(TextAnnotation, InteractiveAnnotation, {
getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) {
assert(!isWorker, 'getHtmlElement() shall be called from main thread');
@ -565,23 +651,40 @@ var TextAnnotation = (function TextAnnotationClosure() {
rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
}
var container = this.getEmptyContainer('section', rect);
var container = this.initContainer();
container.className = 'annotText';
var image = document.createElement('img');
var image = document.createElement('img');
image.style.height = container.style.height;
image.style.width = container.style.width;
var iconName = item.name;
image.src = PDFJS.imageResourcesPath + 'annotation-' +
iconName.toLowerCase() + '.svg';
image.alt = '[{{type}} Annotation]';
image.dataset.l10nId = 'text_annotation_type';
image.dataset.l10nArgs = JSON.stringify({type: iconName});
var contentWrapper = document.createElement('div');
contentWrapper.className = 'annotTextContentWrapper';
contentWrapper.style.left = Math.floor(rect[2] - rect[0] + 5) + 'px';
contentWrapper.style.top = '-10px';
var content = document.createElement('div');
content.className = 'annotTextContent';
content.setAttribute('hidden', true);
if (item.hasBgColor) {
var color = item.color;
var rgb = [];
for (var i = 0; i < 3; ++i) {
// Enlighten the color (70%)
var c = Math.round(color[i] * 255);
rgb[i] = Math.round((255 - c) * 0.7) + c;
}
content.style.backgroundColor = Util.makeCssRgb(rgb);
}
var title = document.createElement('h1');
var text = document.createElement('p');
content.style.left = Math.floor(rect[2] - rect[0]) + 'px';
content.style.top = '0px';
title.textContent = item.title;
if (!item.content && !item.title) {
@ -597,28 +700,57 @@ var TextAnnotation = (function TextAnnotationClosure() {
}
text.appendChild(e);
var showAnnotation = function showAnnotation() {
container.style.zIndex += 1;
content.removeAttribute('hidden');
var pinned = false;
var showAnnotation = function showAnnotation(pin) {
if (pin) {
pinned = true;
}
if (content.hasAttribute('hidden')) {
container.style.zIndex += 1;
content.removeAttribute('hidden');
}
};
var hideAnnotation = function hideAnnotation(e) {
if (e.toElement || e.relatedTarget) { // No context menu is used
var hideAnnotation = function hideAnnotation(unpin) {
if (unpin) {
pinned = false;
}
if (!content.hasAttribute('hidden') && !pinned) {
container.style.zIndex -= 1;
content.setAttribute('hidden', true);
}
};
content.addEventListener('mouseover', showAnnotation, false);
content.addEventListener('mouseout', hideAnnotation, false);
image.addEventListener('mouseover', showAnnotation, false);
image.addEventListener('mouseout', hideAnnotation, false);
var toggleAnnotation = function toggleAnnotation() {
if (pinned) {
hideAnnotation(true);
} else {
showAnnotation(true);
}
};
var self = this;
image.addEventListener('click', function image_clickHandler() {
toggleAnnotation();
}, false);
image.addEventListener('mouseover', function image_mouseOverHandler() {
showAnnotation();
}, false);
image.addEventListener('mouseout', function image_mouseOutHandler() {
hideAnnotation();
}, false);
content.addEventListener('click', function content_clickHandler() {
hideAnnotation(true);
}, false);
}
content.appendChild(title);
content.appendChild(text);
contentWrapper.appendChild(content);
container.appendChild(image);
container.appendChild(content);
container.appendChild(contentWrapper);
return container;
}
@ -629,7 +761,7 @@ var TextAnnotation = (function TextAnnotationClosure() {
var LinkAnnotation = (function LinkAnnotationClosure() {
function LinkAnnotation(params) {
Annotation.call(this, params);
InteractiveAnnotation.call(this, params);
if (params.data) {
return;
@ -692,36 +824,28 @@ var LinkAnnotation = (function LinkAnnotationClosure() {
return url;
}
Util.inherit(LinkAnnotation, Annotation, {
Util.inherit(LinkAnnotation, InteractiveAnnotation, {
hasOperatorList: function LinkAnnotation_hasOperatorList() {
return false;
},
hasHtml: function LinkAnnotation_hasHtml() {
return true;
},
getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) {
var rect = this.data.rect;
var element = document.createElement('a');
var borderWidth = this.data.borderWidth;
element.style.borderWidth = borderWidth + 'px';
var color = this.data.color;
var rgb = [];
for (var i = 0; i < 3; ++i) {
rgb[i] = Math.round(color[i] * 255);
}
element.style.borderColor = Util.makeCssRgb(rgb);
element.style.borderStyle = 'solid';
var container = this.initContainer();
container.className = 'annotLink';
var width = rect[2] - rect[0] - 2 * borderWidth;
var height = rect[3] - rect[1] - 2 * borderWidth;
element.style.width = width + 'px';
element.style.height = height + 'px';
var item = this.data;
var rect = item.rect;
element.href = this.data.url || '';
return element;
container.style.borderColor = item.colorCssRgb;
container.style.borderStyle = 'solid';
var link = document.createElement('a');
link.href = link.title = this.data.url || '';
container.appendChild(link);
return container;
}
});

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
viewBox="0 0 40 40">
</svg>

After

Width:  |  Height:  |  Size: 158 B

View File

@ -306,10 +306,13 @@ var PageView = function pageView(container, id, scale,
CustomStyle.setProp('transformOrigin', element, transformOriginStr);
if (data.subtype === 'Link' && !data.url) {
if (data.action) {
bindNamedAction(element, data.action);
} else {
bindLink(element, ('dest' in data) ? data.dest : null);
var link = element.getElementsByTagName('a')[0];
if (link) {
if (data.action) {
bindNamedAction(link, data.action);
} else {
bindLink(link, ('dest' in data) ? data.dest : null);
}
}
}
@ -579,6 +582,7 @@ var PageView = function pageView(container, id, scale,
canvasContext: ctx,
viewport: this.viewport,
textLayer: textLayer,
// intent: 'default', // === 'display'
continueCallback: function pdfViewcContinueCallback(cont) {
if (PDFView.highestPriorityPage !== 'page' + self.id) {
self.renderingState = RenderingStates.PAUSED;
@ -650,13 +654,13 @@ var PageView = function pageView(container, id, scale,
var renderContext = {
canvasContext: ctx,
viewport: viewport
viewport: viewport,
intent: 'print'
};
pdfPage.render(renderContext).promise.then(function() {
// Tell the printEngine that rendering this canvas/page has finished.
obj.done();
self.pdfPage.destroy();
}, function(error) {
console.error(error);
// Tell the printEngine that rendering this canvas/page has failed.
@ -666,7 +670,6 @@ var PageView = function pageView(container, id, scale,
} else {
obj.done();
}
self.pdfPage.destroy();
});
};
};

View File

@ -1247,14 +1247,7 @@ canvas {
background-color: white;
}
.page > a,
.annotationLayer > a {
display: block;
position: absolute;
}
.page > a:hover,
.annotationLayer > a:hover {
.annotLink > a:hover {
opacity: 0.2;
background: #ff0;
box-shadow: 0px 2px 10px #ff0;
@ -1319,29 +1312,49 @@ canvas {
::selection { background:rgba(0,0,255,0.3); }
::-moz-selection { background:rgba(0,0,255,0.3); }
.annotText > div {
z-index: 200;
.annotationHighlight {
position: absolute;
padding: 0.6em;
max-width: 20em;
background-color: #FFFF99;
box-shadow: 0px 2px 10px #333;
border-radius: 7px;
border: 2px #FFFF99 solid;
}
.annotText > img {
position: absolute;
opacity: 0.6;
cursor: pointer;
}
.annotText > img:hover {
opacity: 1;
.annotTextContentWrapper {
position: absolute;
width: 20em;
}
.annotText > div > h1 {
font-size: 1.2em;
.annotTextContent {
z-index: 200;
float: left;
max-width: 20em;
background-color: #FFFF99;
box-shadow: 0px 2px 5px #333;
border-radius: 2px;
padding: 0.6em;
cursor: pointer;
}
.annotTextContent > h1 {
font-size: 1em;
border-bottom: 1px solid #000000;
margin: 0px;
padding-bottom: 0.2em;
}
.annotTextContent > p {
padding-top: 0.2em;
}
.annotLink > a {
position: absolute;
font-size: 1em;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#errorWrapper {