From b7217a22745473c186da794729c9584576bf1c1c Mon Sep 17 00:00:00 2001 From: Tim van der Meij Date: Fri, 18 Dec 2015 22:29:22 +0100 Subject: [PATCH] Implement annotation layer regression testing --- test/annotation_layer_test.css | 73 ++++++++++++++ test/driver.js | 169 ++++++++++++++++++++++++++++++++- test/test_manifest.json | 7 +- 3 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 test/annotation_layer_test.css diff --git a/test/annotation_layer_test.css b/test/annotation_layer_test.css new file mode 100644 index 0000000000000..3101be4cd01ea --- /dev/null +++ b/test/annotation_layer_test.css @@ -0,0 +1,73 @@ +/* Copyright 2015 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Used for annotation layer tests */ + +.annotationLayer { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} + +.annotationLayer > section { + position: absolute; +} + +.annotationLayer .annotLink > a { + position: absolute; + font-size: 1em; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.2; + background: #ff0; + box-shadow: 0px 2px 10px #ff0; +} + +.annotationLayer .annotText > img { + position: absolute; +} + +.annotationLayer .annotTextContentWrapper { + position: absolute; + width: 20em; +} + +.annotationLayer .annotTextContent { + z-index: 200; + float: left; + max-width: 20em; + background-color: #FFFF99; + box-shadow: 0px 2px 5px #333; + border-radius: 2px; + padding: 0.6em; + display: block !important; + font: message-box; +} + +.annotationLayer .annotTextContent > h1 { + font-size: 1em; + border-bottom: 1px solid #000000; + margin: 0; + padding: 0 0 0.2em 0; +} + +.annotationLayer .annotTextContent > p { + margin: 0; + padding: 0.2em 0 0 0; +} diff --git a/test/driver.js b/test/driver.js index fa9c77a4ac261..148a8c4b5c9ba 100644 --- a/test/driver.js +++ b/test/driver.js @@ -19,6 +19,29 @@ var WAITING_TIME = 100; // ms var PDF_TO_CSS_UNITS = 96.0 / 72.0; +/** + * @class + */ +var LinkServiceMock = (function LinkServiceMockClosure() { + function LinkServiceMock() {} + + LinkServiceMock.prototype = { + navigateTo: function (dest) {}, + + getDestinationHash: function (dest) { + return '#'; + }, + + getAnchorUrl: function (hash) { + return '#'; + }, + + executeNamedAction: function (action) {} + }; + + return LinkServiceMock; +})(); + /** * @class */ @@ -89,6 +112,115 @@ var rasterizeTextLayer = (function rasterizeTextLayerClosure() { return rasterizeTextLayer; })(); +/** + * @class + */ +var rasterizeAnnotationLayer = (function rasterizeAnnotationLayerClosure() { + var SVG_NS = 'http://www.w3.org/2000/svg'; + + var annotationLayerStylePromise = null; + function getAnnotationLayerStyle() { + if (annotationLayerStylePromise) { + return annotationLayerStylePromise; + } + annotationLayerStylePromise = new Promise(function (resolve) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', './annotation_layer_test.css'); + xhr.onload = function () { + resolve(xhr.responseText); + }; + xhr.send(null); + }); + return annotationLayerStylePromise; + } + + function inlineAnnotationImages(images) { + var imagePromises = []; + for (var i = 0, ii = images.length; i < ii; i++) { + var imagePromise = new Promise(function(resolve) { + var xhr = new XMLHttpRequest(); + xhr.responseType = 'blob'; + xhr.onload = function() { + var reader = new FileReader(); + reader.onloadend = function() { + resolve(reader.result); + }; + reader.readAsDataURL(xhr.response); + }; + xhr.onerror = function() { + resolve(''); + }; + xhr.open('GET', images[i].src); + xhr.send(); + }); + imagePromises.push(imagePromise); + } + return imagePromises; + } + + function rasterizeAnnotationLayer(ctx, viewport, annotations, page) { + return new Promise(function (resolve) { + // Building SVG with size of the viewport. + var svg = document.createElementNS(SVG_NS, 'svg:svg'); + svg.setAttribute('width', viewport.width + 'px'); + svg.setAttribute('height', viewport.height + 'px'); + + // Adding element to host our HTML (style + annotation layer div). + var foreignObject = document.createElementNS(SVG_NS, 'svg:foreignObject'); + foreignObject.setAttribute('x', '0'); + foreignObject.setAttribute('y', '0'); + foreignObject.setAttribute('width', viewport.width + 'px'); + foreignObject.setAttribute('height', viewport.height + 'px'); + var style = document.createElement('style'); + var stylePromise = getAnnotationLayerStyle(); + foreignObject.appendChild(style); + var div = document.createElement('div'); + div.className = 'annotationLayer'; + + // Rendering annotation layer as HTML. + stylePromise.then(function (styles) { + style.textContent = styles; + + var annotation_viewport = viewport.clone({ dontFlip: true }); + var parameters = { + viewport: annotation_viewport, + div: div, + annotations: annotations, + page: page, + linkService: new LinkServiceMock() + }; + PDFJS.AnnotationLayer.render(parameters); + + // Inline SVG images from text annotations. + var images = div.getElementsByTagName('img'); + var imagePromises = inlineAnnotationImages(images); + var converted = Promise.all(imagePromises).then(function(data) { + for (var i = 0, ii = data.length; i < ii; i++) { + images[i].src = data[i]; + } + }); + + foreignObject.appendChild(div); + svg.appendChild(foreignObject); + + // We need to have UTF-8 encoded XML. + converted.then(function() { + var svg_xml = unescape(encodeURIComponent( + (new XMLSerializer()).serializeToString(svg))); + var img = new Image(); + img.src = 'data:image/svg+xml;base64,' + btoa(svg_xml); + img.onload = function () { + ctx.drawImage(img, 0, 0); + resolve(); + }; + }); + }); + }); + } + + return rasterizeAnnotationLayer; +})(); + /** * @typedef {Object} DriverOptions * @property {HTMLSpanElement} inflight - Field displaying the number of @@ -113,6 +245,7 @@ var Driver = (function DriverClosure() { PDFJS.cMapPacked = true; PDFJS.cMapUrl = '../external/bcmaps/'; PDFJS.enableStats = true; + PDFJS.imageResourcesPath = '/web/images/'; // Set the passed options this.inflight = options.inflight; @@ -320,7 +453,7 @@ var Driver = (function DriverClosure() { self.canvas.height = viewport.height; self._clearCanvas(); - var textLayerCanvas; + var textLayerCanvas, annotationLayerCanvas; var initPromise; if (task.type === 'text') { // Using a dummy canvas for PDF context drawing operations @@ -343,8 +476,36 @@ var Driver = (function DriverClosure() { }); } else { textLayerCanvas = null; - initPromise = Promise.resolve(); + + // Render the annotation layer if necessary. + if (task.annotations) { + // Create a dummy canvas for the drawing operations. + annotationLayerCanvas = self.annotationLayerCanvas; + if (!annotationLayerCanvas) { + annotationLayerCanvas = document.createElement('canvas'); + self.annotationLayerCanvas = annotationLayerCanvas; + } + annotationLayerCanvas.width = viewport.width; + annotationLayerCanvas.height = viewport.height; + var annotationLayerContext = + annotationLayerCanvas.getContext('2d'); + annotationLayerContext.clearRect(0, 0, + annotationLayerCanvas.width, annotationLayerCanvas.height); + + // The annotation builder will draw its content on the canvas. + initPromise = + page.getAnnotations({ intent: 'display' }).then( + function(annotations) { + return rasterizeAnnotationLayer(annotationLayerContext, + viewport, annotations, + page); + }); + } else { + annotationLayerCanvas = null; + initPromise = Promise.resolve(); + } } + var renderContext = { canvasContext: ctx, viewport: viewport @@ -359,6 +520,10 @@ var Driver = (function DriverClosure() { ctx.restore(); ctx.drawImage(textLayerCanvas, 0, 0); } + // If we have annotation layer, compose it on top of the page. + if (annotationLayerCanvas) { + ctx.drawImage(annotationLayerCanvas, 0, 0); + } page.cleanup(); task.stats = page.stats; page.stats = new pdfjsSharedUtil.StatTimer(); diff --git a/test/test_manifest.json b/test/test_manifest.json index 7095734f0eb72..32b74c9add705 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -1600,7 +1600,8 @@ "rounds": 1, "lastPage": 1, "link": true, - "type": "eq" + "type": "eq", + "annotations": true }, { "id": "issue1002", "file": "pdfs/issue1002.pdf", @@ -2052,6 +2053,7 @@ "md5": "56321ea830be9c4f8437ca17ac535b2d", "rounds": 1, "type": "eq", + "annotations": true, "about": "Text widget annotation witout appearance streams." }, { "id": "gesamt", @@ -2585,7 +2587,8 @@ "file": "pdfs/annotation-border-styles.pdf", "md5": "22930fc09c7386e1131b14d936e554af", "rounds": 1, - "type": "eq" + "type": "eq", + "annotations": true }, { "id": "issue5481.pdf", "file": "pdfs/issue5481.pdf",