Add support of Ink annotation

This commit is contained in:
Simon Leblanc 2018-09-30 16:29:16 +02:00
parent 1cfb723dd4
commit b5806735d8
7 changed files with 173 additions and 1 deletions

View File

@ -106,6 +106,9 @@ class AnnotationFactory {
case 'Polygon': case 'Polygon':
return new PolygonAnnotation(parameters); return new PolygonAnnotation(parameters);
case 'Ink':
return new InkAnnotation(parameters);
case 'Highlight': case 'Highlight':
return new HighlightAnnotation(parameters); return new HighlightAnnotation(parameters);
@ -1013,6 +1016,34 @@ class PolygonAnnotation extends PolylineAnnotation {
} }
} }
class InkAnnotation extends Annotation {
constructor(parameters) {
super(parameters);
this.data.annotationType = AnnotationType.INK;
let dict = parameters.dict;
const xref = parameters.xref;
let originalInkLists = dict.getArray('InkList');
this.data.inkLists = [];
for (let i = 0, ii = originalInkLists.length; i < ii; ++i) {
// The raw ink lists array contains arrays of numbers representing
// the alternating horizontal and vertical coordinates, respectively,
// of each vertex. Convert this to an array of objects with x and y
// coordinates.
this.data.inkLists.push([]);
for (let j = 0, jj = originalInkLists[i].length; j < jj; j += 2) {
this.data.inkLists[i].push({
x: xref.fetchIfRef(originalInkLists[i][j]),
y: xref.fetchIfRef(originalInkLists[i][j + 1]),
});
}
}
this._preparePopup(dict);
}
}
class HighlightAnnotation extends Annotation { class HighlightAnnotation extends Annotation {
constructor(parameters) { constructor(parameters) {
super(parameters); super(parameters);

View File

@ -83,6 +83,9 @@ class AnnotationElementFactory {
case AnnotationType.POLYLINE: case AnnotationType.POLYLINE:
return new PolylineAnnotationElement(parameters); return new PolylineAnnotationElement(parameters);
case AnnotationType.INK:
return new InkAnnotationElement(parameters);
case AnnotationType.POLYGON: case AnnotationType.POLYGON:
return new PolygonAnnotationElement(parameters); return new PolygonAnnotationElement(parameters);
@ -628,7 +631,14 @@ class PopupAnnotationElement extends AnnotationElement {
render() { render() {
// Do not render popup annotations for parent elements with these types as // Do not render popup annotations for parent elements with these types as
// they create the popups themselves (because of custom trigger divs). // they create the popups themselves (because of custom trigger divs).
const IGNORE_TYPES = ['Line', 'Square', 'Circle', 'PolyLine', 'Polygon']; const IGNORE_TYPES = [
'Line',
'Square',
'Circle',
'PolyLine',
'Polygon',
'Ink',
];
this.container.className = 'popupAnnotation'; this.container.className = 'popupAnnotation';
@ -1006,6 +1016,73 @@ class PolygonAnnotationElement extends PolylineAnnotationElement {
} }
} }
class InkAnnotationElement extends AnnotationElement {
constructor(parameters) {
let isRenderable = !!(parameters.data.hasPopup ||
parameters.data.title || parameters.data.contents);
super(parameters, isRenderable, /* ignoreBorder = */ true);
this.containerClassName = 'inkAnnotation';
// Use the polyline SVG element since it allows us to use coordinates
// directly and to draw both straight lines and curves.
this.svgElementName = 'svg:polyline';
}
/**
* Render the ink annotation's HTML element in the empty container.
*
* @public
* @memberof InkAnnotationElement
* @returns {HTMLSectionElement}
*/
render() {
this.container.className = this.containerClassName;
// Create an invisible polyline with the same points that acts as the
// trigger for the popup.
let data = this.data;
let width = data.rect[2] - data.rect[0];
let height = data.rect[3] - data.rect[1];
let svg = this.svgFactory.create(width, height);
let inkLists = data.inkLists;
for (let i = 0, ii = inkLists.length; i < ii; i++) {
let inkList = inkLists[i];
let points = [];
// Convert the ink list to a single points string that the SVG
// polyline element expects ("x1,y1 x2,y2 ..."). PDF coordinates are
// calculated from a bottom left origin, so transform the polyline
// coordinates to a top left origin for the SVG element.
for (let j = 0, jj = inkList.length; j < jj; j++) {
let x = inkList[j].x - data.rect[0];
let y = data.rect[3] - inkList[j].y;
points.push(x + ',' + y);
}
points = points.join(' ');
let borderWidth = data.borderStyle.width;
let polyline = this.svgFactory.createElement(this.svgElementName);
polyline.setAttribute('points', points);
polyline.setAttribute('stroke-width', borderWidth);
polyline.setAttribute('stroke', 'transparent');
polyline.setAttribute('fill', 'none');
// Create the popup ourselves so that we can bind it to the polyline
// instead of to the entire container (which is the default).
this._createPopup(this.container, polyline, data);
svg.appendChild(polyline);
}
this.container.append(svg);
return this.container;
}
}
class HighlightAnnotationElement extends AnnotationElement { class HighlightAnnotationElement extends AnnotationElement {
constructor(parameters) { constructor(parameters) {
let isRenderable = !!(parameters.data.hasPopup || let isRenderable = !!(parameters.data.hasPopup ||

View File

@ -272,6 +272,7 @@
!text_clip_cff_cid.pdf !text_clip_cff_cid.pdf
!issue4801.pdf !issue4801.pdf
!issue5334.pdf !issue5334.pdf
!annotation-caret-ink.pdf
!bug1186827.pdf !bug1186827.pdf
!issue215.pdf !issue215.pdf
!issue5044.pdf !issue5044.pdf

Binary file not shown.

View File

@ -1776,6 +1776,13 @@
"lastPage": 1, "lastPage": 1,
"type": "load" "type": "load"
}, },
{ "id": "annotation-caret-ink",
"file": "pdfs/annotation-caret-ink.pdf",
"md5": "6218ca235580d1975474c979e0128c2d",
"rounds": 1,
"type": "eq",
"annotations": true
},
{ "id": "bug1130815-eq", { "id": "bug1130815-eq",
"file": "pdfs/bug1130815.pdf", "file": "pdfs/bug1130815.pdf",
"md5": "3ff3b550c3af766991b2a1b11d00de85", "md5": "3ff3b550c3af766991b2a1b11d00de85",

View File

@ -1435,4 +1435,59 @@ describe('annotation', function() {
}, done.fail); }, done.fail);
}); });
}); });
describe('InkAnnotation', function() {
it('should handle a single ink list', function(done) {
const inkDict = new Dict();
inkDict.set('Type', Name.get('Annot'));
inkDict.set('Subtype', Name.get('Ink'));
inkDict.set('InkList', [[1, 1, 1, 2, 2, 2, 3, 3]]);
const inkRef = new Ref(142, 0);
const xref = new XRefMock([
{ ref: inkRef, data: inkDict, }
]);
AnnotationFactory.create(xref, inkRef, pdfManagerMock,
idFactoryMock).then(({ data, }) => {
expect(data.annotationType).toEqual(AnnotationType.INK);
expect(data.inkLists.length).toEqual(1);
expect(data.inkLists[0]).toEqual([
{ x: 1, y: 1, },
{ x: 1, y: 2, },
{ x: 2, y: 2, },
{ x: 3, y: 3, },
]);
done();
}, done.fail);
});
it('should handle multiple ink lists', function(done) {
const inkDict = new Dict();
inkDict.set('Type', Name.get('Annot'));
inkDict.set('Subtype', Name.get('Ink'));
inkDict.set('InkList', [
[1, 1, 1, 2],
[3, 3, 4, 5],
]);
const inkRef = new Ref(143, 0);
const xref = new XRefMock([
{ ref: inkRef, data: inkDict, }
]);
AnnotationFactory.create(xref, inkRef, pdfManagerMock,
idFactoryMock).then(({ data, }) => {
expect(data.annotationType).toEqual(AnnotationType.INK);
expect(data.inkLists.length).toEqual(2);
expect(data.inkLists[0]).toEqual([
{ x: 1, y: 1, }, { x: 1, y: 2, }
]);
expect(data.inkLists[1]).toEqual([
{ x: 3, y: 3, }, { x: 4, y: 5, }
]);
done();
}, done.fail);
});
});
}); });

View File

@ -188,6 +188,7 @@
.annotationLayer .circleAnnotation svg ellipse, .annotationLayer .circleAnnotation svg ellipse,
.annotationLayer .polylineAnnotation svg polyline, .annotationLayer .polylineAnnotation svg polyline,
.annotationLayer .polygonAnnotation svg polygon, .annotationLayer .polygonAnnotation svg polygon,
.annotationLayer .inkAnnotation svg polyline,
.annotationLayer .stampAnnotation, .annotationLayer .stampAnnotation,
.annotationLayer .fileAttachmentAnnotation { .annotationLayer .fileAttachmentAnnotation {
cursor: pointer; cursor: pointer;