Annotations: implement support for line annotations

This patch implements support for line annotations. Other viewers only
show the popup annotation when hovering over the line, which may have
any orientation. To make this possible, we render an invisible line (SVG
element) over the line on the canvas that acts as the trigger for the
popup annotation. This invisible line has the same starting coordinates,
ending coordinates and width of the line on the canvas.
This commit is contained in:
Tim van der Meij 2017-04-02 20:50:17 +02:00
parent 30d63b0c50
commit e15a2ec523
No known key found for this signature in database
GPG Key ID: 8C3FD2925A5F2762
7 changed files with 125 additions and 0 deletions

View File

@ -115,6 +115,9 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
case 'Popup':
return new PopupAnnotation(parameters);
case 'Line':
return new LineAnnotation(parameters);
case 'Highlight':
return new HighlightAnnotation(parameters);
@ -955,6 +958,8 @@ var PopupAnnotation = (function PopupAnnotationClosure() {
return;
}
var parentSubtype = parentItem.get('Subtype');
this.data.parentType = isName(parentSubtype) ? parentSubtype.name : null;
this.data.parentId = dict.getRaw('Parent').toString();
this.data.title = stringToPDFString(parentItem.get('T') || '');
this.data.contents = stringToPDFString(parentItem.get('Contents') || '');
@ -983,6 +988,22 @@ var PopupAnnotation = (function PopupAnnotationClosure() {
return PopupAnnotation;
})();
var LineAnnotation = (function LineAnnotationClosure() {
function LineAnnotation(parameters) {
Annotation.call(this, parameters);
this.data.annotationType = AnnotationType.LINE;
var dict = parameters.dict;
this.data.lineCoordinates = Util.normalizeRect(dict.getArray('L'));
this._preparePopup(dict);
}
Util.inherit(LineAnnotation, Annotation, {});
return LineAnnotation;
})();
var HighlightAnnotation = (function HighlightAnnotationClosure() {
function HighlightAnnotation(parameters) {
Annotation.call(this, parameters);

View File

@ -93,6 +93,9 @@ AnnotationElementFactory.prototype =
case AnnotationType.POPUP:
return new PopupAnnotationElement(parameters);
case AnnotationType.LINE:
return new LineAnnotationElement(parameters);
case AnnotationType.HIGHLIGHT:
return new HighlightAnnotationElement(parameters);
@ -681,6 +684,10 @@ var ChoiceWidgetAnnotationElement = (
* @alias PopupAnnotationElement
*/
var PopupAnnotationElement = (function PopupAnnotationElementClosure() {
// Do not render popup annotations for parent elements with these types as
// they create the popups themselves (because of custom trigger divs).
var IGNORE_TYPES = ['Line'];
function PopupAnnotationElement(parameters) {
var isRenderable = !!(parameters.data.title || parameters.data.contents);
AnnotationElement.call(this, parameters, isRenderable);
@ -697,6 +704,10 @@ var PopupAnnotationElement = (function PopupAnnotationElementClosure() {
render: function PopupAnnotationElement_render() {
this.container.className = 'popupAnnotation';
if (IGNORE_TYPES.indexOf(this.data.parentType) >= 0) {
return this.container;
}
var selector = '[data-annotation-id="' + this.data.parentId + '"]';
var parentElement = this.layer.querySelector(selector);
if (!parentElement) {
@ -866,6 +877,69 @@ var PopupElement = (function PopupElementClosure() {
return PopupElement;
})();
/**
* @class
* @alias LineAnnotationElement
*/
var LineAnnotationElement = (function LineAnnotationElementClosure() {
var SVG_NS = 'http://www.w3.org/2000/svg';
function LineAnnotationElement(parameters) {
var isRenderable = !!(parameters.data.hasPopup ||
parameters.data.title || parameters.data.contents);
AnnotationElement.call(this, parameters, isRenderable,
/* ignoreBorder = */ true);
}
Util.inherit(LineAnnotationElement, AnnotationElement, {
/**
* Render the line annotation's HTML element in the empty container.
*
* @public
* @memberof LineAnnotationElement
* @returns {HTMLSectionElement}
*/
render: function LineAnnotationElement_render() {
this.container.className = 'lineAnnotation';
// Create an invisible line with the same starting and ending coordinates
// that acts as the trigger for the popup. Only the line itself should
// trigger the popup, not the entire container.
var data = this.data;
var width = data.rect[2] - data.rect[0];
var height = data.rect[3] - data.rect[1];
var svg = document.createElementNS(SVG_NS, 'svg:svg');
svg.setAttributeNS(null, 'version', '1.1');
svg.setAttributeNS(null, 'width', width + 'px');
svg.setAttributeNS(null, 'height', height + 'px');
svg.setAttributeNS(null, 'preserveAspectRatio', 'none');
svg.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height);
// PDF coordinates are calculated from a bottom left origin, so transform
// the line coordinates to a top left origin for the SVG element.
var line = document.createElementNS(SVG_NS, 'svg:line');
line.setAttributeNS(null, 'x1', data.rect[2] - data.lineCoordinates[0]);
line.setAttributeNS(null, 'y1', data.rect[3] - data.lineCoordinates[1]);
line.setAttributeNS(null, 'x2', data.rect[2] - data.lineCoordinates[2]);
line.setAttributeNS(null, 'y2', data.rect[3] - data.lineCoordinates[3]);
line.setAttributeNS(null, 'stroke-width', data.borderStyle.width);
line.setAttributeNS(null, 'stroke', 'transparent');
svg.appendChild(line);
this.container.append(svg);
// Create the popup ourselves so that we can bind it to the line instead
// of to the entire container (which is the default).
this._createPopup(this.container, line, this.data);
return this.container;
}
});
return LineAnnotationElement;
})();
/**
* @class
* @alias HighlightAnnotationElement

View File

@ -268,6 +268,7 @@
!annotation-strikeout.pdf
!annotation-squiggly.pdf
!annotation-highlight.pdf
!annotation-line.pdf
!annotation-fileattachment.pdf
!annotation-text-widget.pdf
!annotation-choice-widget.pdf

BIN
test/pdfs/annotation-line.pdf Executable file

Binary file not shown.

View File

@ -3357,6 +3357,13 @@
"type": "eq",
"annotations": true
},
{ "id": "annotation-line",
"file": "pdfs/annotation-line.pdf",
"md5": "fde60608be2748f10fb6522cba425ca1",
"rounds": 1,
"type": "eq",
"annotations": true
},
{ "id": "annotation-fileattachment",
"file": "pdfs/annotation-fileattachment.pdf",
"md5": "d20ecee4b53c81b2dd44c8715a1b4a83",

View File

@ -1287,6 +1287,27 @@ describe('annotation', function() {
});
});
describe('LineAnnotation', function() {
it('should set the line coordinates', function() {
var lineDict = new Dict();
lineDict.set('Type', Name.get('Annot'));
lineDict.set('Subtype', Name.get('Line'));
lineDict.set('L', [1, 2, 3, 4]);
var lineRef = new Ref(122, 0);
var xref = new XRefMock([
{ ref: lineRef, data: lineDict, }
]);
var annotation = annotationFactory.create(xref, lineRef, pdfManagerMock,
idFactoryMock);
var data = annotation.data;
expect(data.annotationType).toEqual(AnnotationType.LINE);
expect(data.lineCoordinates).toEqual([1, 2, 3, 4]);
});
});
describe('FileAttachmentAnnotation', function() {
it('should correctly parse a file attachment', function() {
var fileStream = new StringStream(

View File

@ -143,6 +143,7 @@
.annotationLayer .underlineAnnotation,
.annotationLayer .squigglyAnnotation,
.annotationLayer .strikeoutAnnotation,
.annotationLayer .lineAnnotation svg line,
.annotationLayer .fileAttachmentAnnotation {
cursor: pointer;
}