Skip to content

Commit

Permalink
Annotations: implement support for line annotations
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
timvandermeij committed Apr 9, 2017
1 parent 30d63b0 commit e581240
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 1 deletion.
21 changes: 21 additions & 0 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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') || '');
Expand Down Expand Up @@ -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);
Expand Down
72 changes: 71 additions & 1 deletion src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand All @@ -699,7 +706,7 @@ var PopupAnnotationElement = (function PopupAnnotationElementClosure() {

var selector = '[data-annotation-id="' + this.data.parentId + '"]';
var parentElement = this.layer.querySelector(selector);
if (!parentElement) {
if (!parentElement || IGNORE_TYPES.indexOf(this.data.parentType) >= 0) {
return this.container;
}

Expand Down Expand Up @@ -866,6 +873,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
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added test/pdfs/annotation-line.pdf
Binary file not shown.
7 changes: 7 additions & 0 deletions test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 21 additions & 0 deletions test/unit/annotation_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions web/annotation_layer_builder.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
.annotationLayer .underlineAnnotation,
.annotationLayer .squigglyAnnotation,
.annotationLayer .strikeoutAnnotation,
.annotationLayer .lineAnnotation svg line,
.annotationLayer .fileAttachmentAnnotation {
cursor: pointer;
}

0 comments on commit e581240

Please sign in to comment.