From bd7f121c8300b75aba4a21d5ef6022baa00b4eac Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Thu, 19 Nov 2015 11:03:52 -0600 Subject: [PATCH] Better "text" testing. --- test/driver.js | 170 +++++++++++++++++++-------------------- test/test_manifest.json | 6 -- test/text_layer_test.css | 38 +++++++++ web/viewer.css | 22 ++++- 4 files changed, 144 insertions(+), 92 deletions(-) create mode 100644 test/text_layer_test.css diff --git a/test/driver.js b/test/driver.js index c6c295f88..c41ec7012 100644 --- a/test/driver.js +++ b/test/driver.js @@ -22,80 +22,71 @@ var PDF_TO_CSS_UNITS = 96.0 / 72.0; /** * @class */ -var NullTextLayerBuilder = (function NullTextLayerBuilderClosure() { - /** - * @constructs NullTextLayerBuilder - */ - function NullTextLayerBuilder() {} +var rasterizeTextLayer = (function rasterizeTextLayerClosure() { + var SVG_NS = 'http://www.w3.org/2000/svg'; - NullTextLayerBuilder.prototype = { - beginLayout: function NullTextLayerBuilder_BeginLayout() {}, - endLayout: function NullTextLayerBuilder_EndLayout() {}, - appendText: function NullTextLayerBuilder_AppendText() {} - }; - - return NullTextLayerBuilder; -})(); - -/** - * @class - */ -var SimpleTextLayerBuilder = (function SimpleTextLayerBuilderClosure() { - /** - * @constructs SimpleTextLayerBuilder - */ - function SimpleTextLayerBuilder(ctx, viewport) { - this.ctx = ctx; - this.viewport = viewport; - this.textCounter = 0; - - // clear canvas - ctx.save(); - ctx.fillStyle = 'rgb(255,255,255)'; - ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - ctx.restore(); + var textLayerStylePromise = null; + function getTextLayerStyle() { + if (textLayerStylePromise) { + return textLayerStylePromise; + } + textLayerStylePromise = new Promise(function (resolve) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', './text_layer_test.css'); + xhr.onload = function () { + resolve(xhr.responseText); + }; + xhr.send(null); + }); + return textLayerStylePromise; } - SimpleTextLayerBuilder.prototype = { - appendText: function SimpleTextLayerBuilder_AppendText(geom, styles) { - var style = styles[geom.fontName]; - var ctx = this.ctx, viewport = this.viewport; - var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform); - var angle = Math.atan2(tx[1], tx[0]); - var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3])); - var fontAscent = (style.ascent ? style.ascent * fontHeight : - (style.descent ? (1 + style.descent) * fontHeight : fontHeight)); + function rasterizeTextLayer(ctx, viewport, textContent) { + 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'); + // items are transformed to have 1px font size + svg.setAttribute('font-size', 1); - ctx.save(); - ctx.beginPath(); - ctx.strokeStyle = 'red'; - ctx.fillStyle = 'yellow'; - ctx.translate(tx[4] + (fontAscent * Math.sin(angle)), - tx[5] - (fontAscent * Math.cos(angle))); - ctx.rotate(angle); - ctx.rect(0, 0, geom.width * viewport.scale, geom.height * viewport.scale); - ctx.stroke(); - ctx.fill(); - ctx.restore(); - ctx.font = fontHeight + 'px ' + style.fontFamily; - ctx.fillStyle = 'black'; - ctx.fillText(geom.str, tx[4], tx[5]); + // Adding element to host our HTML (style + text 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 = getTextLayerStyle(); + foreignObject.appendChild(style); + var div = document.createElement('div'); + div.className = 'textLayer'; + foreignObject.appendChild(div); - this.textCounter++; - }, + // Rendering text layer as HTML. + var task = PDFJS.renderTextLayer({ + textContent: textContent, + container: div, + viewport: viewport + }); + Promise.all([stylePromise, task.promise]).then(function (results) { + style.textContent = results[0]; + svg.appendChild(foreignObject); - setTextContent: - function SimpleTextLayerBuilder_SetTextContent(textContent) { - this.ctx.save(); - var textItems = textContent.items; - for (var i = 0, ii = textItems.length; i < ii; i++) { - this.appendText(textItems[i], textContent.styles); - } - this.ctx.restore(); - } - }; + // We need to have UTF-8 encoded XML. + 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 SimpleTextLayerBuilder; + return rasterizeTextLayer; })(); /** @@ -328,35 +319,44 @@ var Driver = (function DriverClosure() { self.canvas.height = viewport.height; self._clearCanvas(); - var drawContext, textLayerBuilder; - var resolveInitPromise; - var initPromise = new Promise(function (resolve) { - resolveInitPromise = resolve; - }); + var textLayerCanvas; + var initPromise; if (task.type === 'text') { // Using a dummy canvas for PDF context drawing operations - if (!self.dummyCanvas) { - self.dummyCanvas = document.createElement('canvas'); + textLayerCanvas = self.textLayerCanvas; + if (!textLayerCanvas) { + textLayerCanvas = document.createElement('canvas'); + self.textLayerCanvas = textLayerCanvas; } - drawContext = self.dummyCanvas.getContext('2d'); + textLayerCanvas.width = viewport.width; + textLayerCanvas.height = viewport.height; + var textLayerContext = textLayerCanvas.getContext('2d'); + textLayerContext.clearRect(0, 0, + textLayerCanvas.width, textLayerCanvas.height); // The text builder will draw its content on the test canvas - textLayerBuilder = new SimpleTextLayerBuilder(ctx, viewport); - - page.getTextContent().then(function(textContent) { - textLayerBuilder.setTextContent(textContent); - resolveInitPromise(); + initPromise = page.getTextContent().then(function(textContent) { + return rasterizeTextLayer(textLayerContext, viewport, + textContent); }); } else { - drawContext = ctx; - textLayerBuilder = new NullTextLayerBuilder(); - resolveInitPromise(); + textLayerCanvas = null; + initPromise = Promise.resolve(); } var renderContext = { - canvasContext: drawContext, + canvasContext: ctx, viewport: viewport }; var completeRender = (function(error) { - page.destroy(); + // if text layer is present, compose it on top of the page + if (textLayerCanvas) { + ctx.save(); + ctx.globalCompositeOperation = 'screen'; + ctx.fillStyle = 'rgb(128, 255, 128)'; // making it green + ctx.fillRect(0, 0, viewport.width, viewport.height); + ctx.restore(); + ctx.drawImage(textLayerCanvas, 0, 0); + } + page.cleanup(); task.stats = page.stats; page.stats = new StatTimer(); self._snapshot(task, error); diff --git a/test/test_manifest.json b/test/test_manifest.json index d478031c1..1bb299cee 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -323,12 +323,6 @@ "lastPage": 1, "about": "The same file as issue2337." }, - { "id": "thuluthfont-text", - "file": "pdfs/ThuluthFeatures.pdf", - "md5": "b7e18bf7a3d6a9c82aefa12d721072fc", - "rounds": 1, - "type": "text" - }, { "id": "freeculture", "file": "pdfs/freeculture.pdf", "md5": "dcdf3a8268e6a18938a42d5149efcfca", diff --git a/test/text_layer_test.css b/test/text_layer_test.css new file mode 100644 index 000000000..ed62e6c99 --- /dev/null +++ b/test/text_layer_test.css @@ -0,0 +1,38 @@ +/* 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 in 'text' tests */ + +.textLayer { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; +} +.textLayer > div { + position: absolute; + white-space: pre; + -webkit-transform-origin: 0% 0%; + -moz-transform-origin: 0% 0%; + -o-transform-origin: 0% 0%; + -ms-transform-origin: 0% 0%; + transform-origin: 0% 0%; + border: solid 1px rgba(255, 0, 0, 0.5); + background-color: rgba(255, 255, 32, 0.1); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} diff --git a/web/viewer.css b/web/viewer.css index b012631e6..b7826849f 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -1515,7 +1515,27 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * { font-size: 10px; } -#viewer.textLayer-visible .textLayer > div, +#viewer.textLayer-visible .textLayer { + opacity: 1.0; +} + +#viewer.textLayer-visible .canvasWrapper { + background-color: rgb(128,255,128); +} + +#viewer.textLayer-visible .canvasWrapper canvas { + mix-blend-mode: screen; +} + +#viewer.textLayer-visible .textLayer > div { + background-color: rgba(255, 255, 0, 0.1); + color: black; + border: solid 1px rgba(255, 0, 0, 0.5); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + #viewer.textLayer-hover .textLayer > div:hover { background-color: white; color: black;