diff --git a/make.js b/make.js index 4073da4a5..8d4422720 100644 --- a/make.js +++ b/make.js @@ -198,7 +198,8 @@ target.jsdoc = function() { var JSDOC_FILES = [ 'src/doc_helper.js', 'src/display/api.js', - 'src/shared/util.js' + 'src/shared/util.js', + 'src/core/annotation.js' ]; if (test('-d', JSDOC_DIR)) { diff --git a/src/core/annotation.js b/src/core/annotation.js index 32974b568..6a4c61011 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -16,8 +16,8 @@ */ /* globals PDFJS, Util, isDict, isName, stringToPDFString, warn, Dict, Stream, stringToBytes, assert, Promise, isArray, ObjectLoader, OperatorList, - isValidUrl, OPS, createPromiseCapability, AnnotationType, - stringToUTF8String */ + isValidUrl, OPS, createPromiseCapability, AnnotationType, + stringToUTF8String, AnnotationBorderStyleType */ 'use strict'; @@ -102,45 +102,8 @@ var Annotation = (function AnnotationClosure() { } } - // Some types of annotations have border style dict which has more - // info than the border array - if (dict.has('BS')) { - var borderStyle = dict.get('BS'); - data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1; - } else { - var borderArray = dict.get('Border') || [0, 0, 1]; - data.borderWidth = borderArray[2] || 0; - - // TODO: implement proper support for annotations with line dash patterns. - var dashArray = borderArray[3]; - if (data.borderWidth > 0 && dashArray) { - if (!isArray(dashArray)) { - // Ignore the border if dashArray is not actually an array, - // this is consistent with the behaviour in Adobe Reader. - data.borderWidth = 0; - } else { - var dashArrayLength = dashArray.length; - if (dashArrayLength > 0) { - // According to the PDF specification: the elements in a dashArray - // shall be numbers that are nonnegative and not all equal to zero. - var isInvalid = false; - var numPositive = 0; - for (var i = 0; i < dashArrayLength; i++) { - var validNumber = (+dashArray[i] >= 0); - if (!validNumber) { - isInvalid = true; - break; - } else if (dashArray[i] > 0) { - numPositive++; - } - } - if (isInvalid || numPositive === 0) { - data.borderWidth = 0; - } - } - } - } - } + this.borderStyle = data.borderStyle = new AnnotationBorderStyle(); + this.setBorderStyle(dict); this.appearance = getDefaultAppearance(dict); data.hasAppearance = !!this.appearance; @@ -148,6 +111,41 @@ var Annotation = (function AnnotationClosure() { } Annotation.prototype = { + /** + * Set the border style (as AnnotationBorderStyle object). + * + * @public + * @memberof Annotation + * @param {Dict} borderStyle - The border style dictionary + */ + setBorderStyle: function Annotation_setBorderStyle(borderStyle) { + if (!isDict(borderStyle)) { + return; + } + if (borderStyle.has('BS')) { + var dict = borderStyle.get('BS'); + var dictType; + + if (!dict.has('Type') || (isName(dictType = dict.get('Type')) && + dictType.name === 'Border')) { + this.borderStyle.setWidth(dict.get('W')); + this.borderStyle.setStyle(dict.get('S')); + this.borderStyle.setDashArray(dict.get('D')); + } + } else if (borderStyle.has('Border')) { + var array = borderStyle.get('Border'); + if (isArray(array) && array.length >= 3) { + this.borderStyle.setHorizontalCornerRadius(array[0]); + this.borderStyle.setVerticalCornerRadius(array[1]); + this.borderStyle.setWidth(array[2]); + this.borderStyle.setStyle('S'); + + if (array.length === 4) { // Dash array available + this.borderStyle.setDashArray(array[3]); + } + } + } + }, getData: function Annotation_getData() { return this.data; @@ -334,6 +332,144 @@ var Annotation = (function AnnotationClosure() { return Annotation; })(); +/** + * Contains all data regarding an annotation's border style. + * + * @class + */ +var AnnotationBorderStyle = (function AnnotationBorderStyleClosure() { + /** + * @constructor + * @private + */ + function AnnotationBorderStyle() { + this.width = 1; + this.style = AnnotationBorderStyleType.SOLID; + this.dashArray = [3]; + this.horizontalCornerRadius = 0; + this.verticalCornerRadius = 0; + } + + AnnotationBorderStyle.prototype = { + /** + * Set the width. + * + * @public + * @memberof AnnotationBorderStyle + * @param {integer} width - The width + */ + setWidth: function AnnotationBorderStyle_setWidth(width) { + if (width === (width | 0)) { + this.width = width; + } + }, + + /** + * Set the style. + * + * @public + * @memberof AnnotationBorderStyle + * @param {Object} style - The style object + * @see {@link shared/util.js} + */ + setStyle: function AnnotationBorderStyle_setStyle(style) { + if (!style) { + return; + } + switch (style.name) { + case 'S': + this.style = AnnotationBorderStyleType.SOLID; + break; + + case 'D': + this.style = AnnotationBorderStyleType.DASHED; + break; + + case 'B': + this.style = AnnotationBorderStyleType.BEVELED; + break; + + case 'I': + this.style = AnnotationBorderStyleType.INSET; + break; + + case 'U': + this.style = AnnotationBorderStyleType.UNDERLINE; + break; + + default: + break; + } + }, + + /** + * Set the dash array. + * + * @public + * @memberof AnnotationBorderStyle + * @param {Array} dashArray - The dash array with at least one element + */ + setDashArray: function AnnotationBorderStyle_setDashArray(dashArray) { + // We validate the dash array, but we do not use it because CSS does not + // allow us to change spacing of dashes. For more information, visit + // http://www.w3.org/TR/css3-background/#the-border-style. + if (isArray(dashArray) && dashArray.length > 0) { + // According to the PDF specification: the elements in a dashArray + // shall be numbers that are nonnegative and not all equal to zero. + var isValid = true; + var allZeros = true; + for (var i = 0, len = dashArray.length; i < len; i++) { + var element = dashArray[i]; + var validNumber = (+element >= 0); + if (!validNumber) { + isValid = false; + break; + } else if (element > 0) { + allZeros = false; + } + } + if (isValid && !allZeros) { + this.dashArray = dashArray; + } else { + this.width = 0; // Adobe behavior when the array is invalid. + } + } else if (dashArray) { + this.width = 0; // Adobe behavior when the array is invalid. + } + }, + + /** + * Set the horizontal corner radius (from a Border dictionary). + * + * @public + * @memberof AnnotationBorderStyle + * @param {integer} radius - The horizontal corner radius + */ + setHorizontalCornerRadius: + function AnnotationBorderStyle_setHorizontalCornerRadius(radius) { + if (radius === (radius | 0)) { + this.horizontalCornerRadius = radius; + } + }, + + /** + * Set the vertical corner radius (from a Border dictionary). + * + * @public + * @memberof AnnotationBorderStyle + * @param {integer} radius - The vertical corner radius + */ + setVerticalCornerRadius: + function AnnotationBorderStyle_setVerticalCornerRadius(radius) { + if (radius === (radius | 0)) { + this.verticalCornerRadius = radius; + } + } + }; + + return AnnotationBorderStyle; +})(); + var WidgetAnnotation = (function WidgetAnnotationClosure() { function WidgetAnnotation(params) { diff --git a/src/display/annotation_helper.js b/src/display/annotation_helper.js index a64ee35f7..215c7c281 100644 --- a/src/display/annotation_helper.js +++ b/src/display/annotation_helper.js @@ -14,7 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* globals PDFJS, Util, AnnotationType */ +/* globals PDFJS, Util, AnnotationType, AnnotationBorderStyleType, warn, + CustomStyle */ 'use strict'; @@ -50,19 +51,64 @@ var AnnotationUtils = (function AnnotationUtilsClosure() { var width = item.rect[2] - item.rect[0]; var height = item.rect[3] - item.rect[1]; - var bWidth = item.borderWidth || 0; - if (bWidth) { - width = width - 2 * bWidth; - height = height - 2 * bWidth; - cstyle.borderWidth = bWidth + 'px'; - var color = item.color; - if (drawBorder && color) { - cstyle.borderStyle = 'solid'; - cstyle.borderColor = Util.makeCssRgb(Math.round(color[0] * 255), - Math.round(color[1] * 255), - Math.round(color[2] * 255)); + // Border + if (item.borderStyle.width > 0) { + // Border width + container.style.borderWidth = item.borderStyle.width + 'px'; + if (item.borderStyle.style !== AnnotationBorderStyleType.UNDERLINE) { + // Underline styles only have a bottom border, so we do not need + // to adjust for all borders. This yields a similar result as + // Adobe Acrobat/Reader. + width = width - 2 * item.borderStyle.width; + height = height - 2 * item.borderStyle.width; + } + + // Horizontal and vertical border radius + var horizontalRadius = item.borderStyle.horizontalCornerRadius; + var verticalRadius = item.borderStyle.verticalCornerRadius; + if (horizontalRadius > 0 || verticalRadius > 0) { + var radius = horizontalRadius + 'px / ' + verticalRadius + 'px'; + CustomStyle.setProp('borderRadius', container, radius); + } + + // Border style + switch (item.borderStyle.style) { + case AnnotationBorderStyleType.SOLID: + container.style.borderStyle = 'solid'; + break; + + case AnnotationBorderStyleType.DASHED: + container.style.borderStyle = 'dashed'; + break; + + case AnnotationBorderStyleType.BEVELED: + warn('Unimplemented border style: beveled'); + break; + + case AnnotationBorderStyleType.INSET: + warn('Unimplemented border style: inset'); + break; + + case AnnotationBorderStyleType.UNDERLINE: + container.style.borderBottomStyle = 'solid'; + break; + + default: + break; + } + + // Border color + if (item.color) { + container.style.borderColor = + Util.makeCssRgb(Math.round(item.color[0] * 255), + Math.round(item.color[1] * 255), + Math.round(item.color[2] * 255)); + } else { + // Default color is black, but that's not obvious from the spec. + container.style.borderColor = 'rgb(0,0,0)'; } } + cstyle.width = width + 'px'; cstyle.height = height + 'px'; return container; diff --git a/src/shared/util.js b/src/shared/util.js index ee38bea39..7edd0c2dd 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -50,6 +50,14 @@ var AnnotationType = { LINK: 3 }; +var AnnotationBorderStyleType = { + SOLID: 1, + DASHED: 2, + BEVELED: 3, + INSET: 4, + UNDERLINE: 5 +}; + var StreamType = { UNKNOWN: 0, FLATE: 1, diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 17f842fb8..8af0d7bbf 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -131,6 +131,7 @@ !issue5334.pdf !issue5549.pdf !issue5475.pdf +!annotation-border-styles.pdf !issue5481.pdf !issue5567.pdf !issue5701.pdf diff --git a/test/pdfs/annotation-border-styles.pdf b/test/pdfs/annotation-border-styles.pdf new file mode 100644 index 000000000..0eefa6dd9 Binary files /dev/null and b/test/pdfs/annotation-border-styles.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 9aaa6caaa..543d2e767 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -2226,6 +2226,12 @@ "type": "eq", "about": "Free image obtained from www.unsplash.com" }, + { "id": "annotation-border-styles.pdf", + "file": "pdfs/annotation-border-styles.pdf", + "md5": "22930fc09c7386e1131b14d936e554af", + "rounds": 1, + "type": "eq" + }, { "id": "issue5481.pdf", "file": "pdfs/issue5481.pdf", "md5": "cf00bd25b15b7e23542b48a626585c36", diff --git a/test/unit/annotation_layer_spec.js b/test/unit/annotation_layer_spec.js new file mode 100644 index 000000000..728c59e3d --- /dev/null +++ b/test/unit/annotation_layer_spec.js @@ -0,0 +1,84 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* globals expect, it, describe, Dict, AnnotationBorderStyle, + AnnotationBorderStyleType */ + +'use strict'; + +describe('Annotation layer', function() { + describe('AnnotationBorderStyle', function() { + it('should set and get a valid width', function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setWidth(3); + + expect(borderStyle.width).toEqual(3); + }); + + it('should not set and get an invalid width', function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setWidth('three'); + + expect(borderStyle.width).toEqual(1); + }); + + it('should set and get a valid style', function() { + var borderStyle = new AnnotationBorderStyle(); + var dict = new Dict(); + dict.name = 'D'; + borderStyle.setStyle(dict); + + expect(borderStyle.style).toEqual(AnnotationBorderStyleType.DASHED); + }); + + it('should not set and get an invalid style', function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setStyle('Dashed'); + + expect(borderStyle.style).toEqual(AnnotationBorderStyleType.SOLID); + }); + + it('should set and get a valid dash array', function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setDashArray([1, 2, 3]); + + expect(borderStyle.dashArray).toEqual([1, 2, 3]); + }); + + it('should not set and get an invalid dash array', function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setDashArray([0, 0]); + + expect(borderStyle.dashArray).toEqual([3]); + }); + + it('should set and get a valid horizontal corner radius', function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setHorizontalCornerRadius(3); + + expect(borderStyle.horizontalCornerRadius).toEqual(3); + }); + + it('should not set and get an invalid horizontal corner radius', + function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setHorizontalCornerRadius('three'); + + expect(borderStyle.horizontalCornerRadius).toEqual(0); + }); + + it('should set and get a valid vertical corner radius', function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setVerticalCornerRadius(3); + + expect(borderStyle.verticalCornerRadius).toEqual(3); + }); + + it('should not set and get an invalid horizontal corner radius', + function() { + var borderStyle = new AnnotationBorderStyle(); + borderStyle.setVerticalCornerRadius('three'); + + expect(borderStyle.verticalCornerRadius).toEqual(0); + }); + }); +}); diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html index 9b43e01a3..9193dbcd7 100644 --- a/test/unit/unit_test.html +++ b/test/unit/unit_test.html @@ -56,6 +56,7 @@ +