diff --git a/src/canvas.js b/src/canvas.js index 5db39ebbb..24f90d967 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -1450,6 +1450,38 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.restore(); }, + beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform, + matrix, border) { + this.save(); + + if (rect && isArray(rect) && 4 == rect.length) { + var width = rect[2] - rect[0]; + var height = rect[3] - rect[1]; + + if (border) { + // TODO(mack): Support different border styles + this.save(); + var rgb = border.rgb; + this.setStrokeRGBColor(rgb[0], rgb[1], rgb[2]); + this.setLineWidth(border.width); + this.rectangle(rect[0], rect[1], width, height); + this.stroke(); + this.restore(); + } + + this.rectangle(rect[0], rect[1], width, height); + this.clip(); + this.endPath(); + } + + this.transform.apply(this, transform); + this.transform.apply(this, matrix); + }, + + endAnnotation: function CanvasGraphics_endAnnotation() { + this.restore(); + }, + paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) { var domImage = this.objs.get(objId); if (!domImage) { diff --git a/src/core.js b/src/core.js index df9d13cfe..bb292c30d 100644 --- a/src/core.js +++ b/src/core.js @@ -100,7 +100,28 @@ function getPdf(arg, callback) { globalScope.PDFJS.getPdf = getPdf; globalScope.PDFJS.pdfBug = false; + var Page = (function PageClosure() { + + function getDefaultAnnotationAppearance(annotationDict) { + var appearanceState = annotationDict.get('AP'); + if (!isDict(appearanceState)) { + return; + } + + var appearance; + var appearances = appearanceState.get('N'); + if (isDict(appearances)) { + var as = annotationDict.get('AS'); + if (as && appearances.has(as.name)) { + appearance = appearances.get(as.name); + } + } else { + appearance = appearances; + } + return appearance; + } + function Page(xref, pageIndex, pageDict, ref) { this.pageIndex = pageIndex; this.pageDict = pageDict; @@ -198,6 +219,17 @@ var Page = (function PageClosure() { 'p' + this.pageIndex + '_'); var list = pe.getOperatorList(contentStream, resources, dependency); + + var annotations = this.getAnnotationsForDraw(); + var annotationEvaluator = new PartialEvaluator( + xref, handler, this.pageIndex, + 'p' + this.pageIndex + '_annotation'); + var annotationsList = annotationEvaluator.getAnnotationsOperatorList( + annotations, dependency); + + Util.concatenateToArray(list.fnArray, annotationsList.fnArray); + Util.concatenateToArray(list.argsArray, annotationsList.argsArray); + pe.optimizeQueue(list); return list; }, @@ -227,7 +259,59 @@ var Page = (function PageClosure() { } return links; }, + getAnnotations: function Page_getAnnotations() { + var annotations = this.getAnnotationsBase(); + var items = []; + for (var i = 0, length = annotations.length; i < length; ++i) { + items.push(annotations[i].item); + } + return items; + }, + + getAnnotationsForDraw: function Page_getAnnotationsForDraw() { + var annotations = this.getAnnotationsBase(); + var items = []; + for (var i = 0, length = annotations.length; i < length; ++i) { + var item = annotations[i].item; + var annotationDict = annotations[i].dict; + + item.annotationFlags = annotationDict.get('F'); + + var appearance = getDefaultAnnotationAppearance(annotationDict); + if (appearance && + // TODO(mack): The proper implementation requires that the + // appearance stream overrides Name, but we're currently + // doing it the other way around for 'Text' annotations since we + // have special rendering for it + item.type !== 'Text') { + + item.appearance = appearance; + var appearanceDict = appearance.dict; + item.resources = appearanceDict.get('Resources'); + item.bbox = appearanceDict.get('BBox') || [0, 0, 1, 1]; + item.matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0]; + } + + var border = annotationDict.get('BS'); + if (isDict(border) && !item.appearance) { + var borderWidth = border.has('W') ? border.get('W') : 1; + if (borderWidth !== 0) { + item.border = { + width: borderWidth, + type: border.get('S') || 'S', + rgb: annotationDict.get('C') || [0, 0, 1] + }; + } + } + + items.push(item); + } + + return items; + }, + + getAnnotationsBase: function Page_getAnnotationsBase() { var xref = this.xref; function getInheritableProperty(annotation, name) { var item = annotation; @@ -267,11 +351,13 @@ var Page = (function PageClosure() { var subtype = annotation.get('Subtype'); if (!isName(subtype)) continue; - var rect = annotation.get('Rect'); var item = {}; item.type = subtype.name; - item.rect = rect; + var rect = annotation.get('Rect'); + item.rect = Util.normalizeRect(rect); + + var includeAnnotation = true; switch (subtype.name) { case 'Link': var a = annotation.get('A'); @@ -316,6 +402,14 @@ var Page = (function PageClosure() { var fieldType = getInheritableProperty(annotation, 'FT'); if (!isName(fieldType)) break; + + // Do not display digital signatures since we do not currently + // validate them. + if (fieldType.name === 'Sig') { + includeAnnotation = false; + break; + } + item.fieldType = fieldType.name; // Building the full field name by collecting the field and // its ancestors 'T' properties and joining them using '.'. @@ -364,10 +458,18 @@ var Page = (function PageClosure() { annotation.get('Name').name; break; default: - TODO('unimplemented annotation type: ' + subtype.name); + var appearance = getDefaultAnnotationAppearance(annotation); + if (!appearance) { + TODO('unimplemented annotation type: ' + subtype.name); + } break; } - items.push(item); + if (includeAnnotation) { + items.push({ + item: item, + dict: annotation + }); + } } return items; } diff --git a/src/evaluator.js b/src/evaluator.js index 648762b81..6eef31c88 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -19,7 +19,7 @@ IDENTITY_MATRIX, info, isArray, isCmd, isDict, isEOF, isName, isNum, isStream, isString, JpegStream, Lexer, Metrics, Name, Parser, Pattern, PDFImage, PDFJS, serifFonts, stdFontMap, symbolsFonts, - TilingPattern, TODO, warn */ + TilingPattern, TODO, warn, Util */ 'use strict'; @@ -588,6 +588,73 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return queue; }, + getAnnotationsOperatorList: + function PartialEvaluator_getAnnotationsOperatorList(annotations, + dependency) { + // 12.5.5: Algorithm: Appearance streams + function getTransformMatrix(rect, bbox, matrix) { + var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix); + var minX = bounds[0]; + var minY = bounds[1]; + var maxX = bounds[2]; + var maxY = bounds[3]; + var width = rect[2] - rect[0]; + var height = rect[3] - rect[1]; + var xRatio = width / (maxX - minX); + var yRatio = height / (maxY - minY); + return [ + xRatio, + 0, + 0, + yRatio, + rect[0] - minX * xRatio, + rect[1] - minY * yRatio + ]; + } + + var fnArray = []; + var argsArray = []; + // deal with annotations + for (var i = 0, length = annotations.length; i < length; ++i) { + var annotation = annotations[i]; + + // check whether we can visualize annotation + if (!annotation || + !annotation.annotationFlags || + (annotation.annotationFlags & 0x0022) || // Hidden or NoView + !annotation.rect || // rectangle is nessessary + !annotation.appearance) { // appearance is nessessary + continue; + } + + // apply rectangle + var rect = annotation.rect; + var bbox = annotation.bbox; + var matrix = annotation.matrix; + var transform = getTransformMatrix(rect, bbox, matrix); + var border = annotation.border; + + fnArray.push('beginAnnotation'); + argsArray.push([rect, transform, matrix, border]); + + if (annotation.appearance) { + var list = this.getOperatorList(annotation.appearance, + annotation.resources, dependency); + + Util.concatenateToArray(fnArray, list.fnArray); + Util.concatenateToArray(argsArray, list.argsArray); + } + + fnArray.push('endAnnotation'); + argsArray.push([]); + } + + return { + fnArray: fnArray, + argsArray: argsArray + }; + }, + optimizeQueue: function PartialEvaluator_optimizeQueue(queue) { var fnArray = queue.fnArray, argsArray = queue.argsArray; // grouping paintInlineImageXObject's into paintInlineImageXObjectGroup diff --git a/src/util.js b/src/util.js index 50e1034e0..bfb150315 100644 --- a/src/util.js +++ b/src/util.js @@ -358,6 +358,10 @@ var Util = PDFJS.Util = (function UtilClosure() { return num < 0 ? -1 : 1; }; + Util.concatenateToArray = function concatenateToArray(arr1, arr2) { + return Array.prototype.push.apply(arr1, arr2); + }; + return Util; })(); diff --git a/test/pdfs/annotation-as.pdf.link b/test/pdfs/annotation-as.pdf.link new file mode 100644 index 000000000..0b874b7da --- /dev/null +++ b/test/pdfs/annotation-as.pdf.link @@ -0,0 +1 @@ +https://bug741239.bugzilla.mozilla.org/attachment.cgi?id=611315 diff --git a/test/test_manifest.json b/test/test_manifest.json index f25d5368d..d03f93f80 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -924,6 +924,13 @@ "rounds": 1, "type": "eq" }, + { "id": "annotation-as", + "file": "pdfs/annotation-as.pdf", + "md5": "e51500c8adc9edcdcc8ebc6a575c90ab", + "rounds": 1, + "link": true, + "type": "eq" + }, { "id": "javauninstall-7", "file": "pdfs/javauninstall-7.pdf", "md5": "c9eb59503923c9125b9660e348618675",