diff --git a/examples/acroforms/index.html b/examples/acroforms/index.html index 45916d23c6cda..a791aa0ac077f 100644 --- a/examples/acroforms/index.html +++ b/examples/acroforms/index.html @@ -11,6 +11,7 @@ + diff --git a/examples/helloworld/index.html b/examples/helloworld/index.html index 2d9b2ab0e66e5..4400999f449ba 100644 --- a/examples/helloworld/index.html +++ b/examples/helloworld/index.html @@ -11,6 +11,7 @@ + diff --git a/make.js b/make.js index 6be2eace1ea45..62b71ca26e68b 100644 --- a/make.js +++ b/make.js @@ -320,6 +320,7 @@ target.bundle = function(args) { 'display/api.js', 'display/metadata.js', 'display/canvas.js', + 'display/webgl.js', 'display/pattern_helper.js', 'display/font_loader.js' ]); diff --git a/src/core/pattern.js b/src/core/pattern.js index 137eaefabcb40..a59c766a597c1 100644 --- a/src/core/pattern.js +++ b/src/core/pattern.js @@ -670,6 +670,38 @@ Shadings.Mesh = (function MeshClosure() { mesh.bounds = [minX, minY, maxX, maxY]; } + function packData(mesh) { + var i, ii, j, jj; + + var coords = mesh.coords; + var coordsPacked = new Float32Array(coords.length * 2); + for (i = 0, j = 0, ii = coords.length; i < ii; i++) { + var xy = coords[i]; + coordsPacked[j++] = xy[0]; + coordsPacked[j++] = xy[1]; + } + mesh.coords = coordsPacked; + + var colors = mesh.colors; + var colorsPacked = new Uint8Array(colors.length * 3); + for (i = 0, j = 0, ii = colors.length; i < ii; i++) { + var c = colors[i]; + colorsPacked[j++] = c[0]; + colorsPacked[j++] = c[1]; + colorsPacked[j++] = c[2]; + } + mesh.colors = colorsPacked; + + var figures = mesh.figures; + for (i = 0, ii = figures.length; i < ii; i++) { + var figure = figures[i], ps = figure.coords, cs = figure.colors; + for (j = 0, jj = ps.length; j < jj; j++) { + ps[j] *= 2; + cs[j] *= 3; + } + } + } + function Mesh(stream, matrix, xref, res) { assert(isStream(stream), 'Mesh data is not a stream'); var dict = stream.dict; @@ -757,35 +789,14 @@ Shadings.Mesh = (function MeshClosure() { } // calculate bounds updateBounds(this); + + packData(this); } Mesh.prototype = { getIR: function Mesh_getIR() { - var type = this.shadingType; - var i, ii, j; - var coords = this.coords; - var coordsPacked = new Float32Array(coords.length * 2); - for (i = 0, j = 0, ii = coords.length; i < ii; i++) { - var xy = coords[i]; - coordsPacked[j++] = xy[0]; - coordsPacked[j++] = xy[1]; - } - var colors = this.colors; - var colorsPacked = new Uint8Array(colors.length * 3); - for (i = 0, j = 0, ii = colors.length; i < ii; i++) { - var c = colors[i]; - colorsPacked[j++] = c[0]; - colorsPacked[j++] = c[1]; - colorsPacked[j++] = c[2]; - } - var figures = this.figures; - var bbox = this.bbox; - var bounds = this.bounds; - var matrix = this.matrix; - var background = this.background; - - return ['Mesh', type, coordsPacked, colorsPacked, figures, bounds, - matrix, bbox, background]; + return ['Mesh', this.shadingType, this.coords, this.colors, this.figures, + this.bounds, this.matrix, this.bbox, this.background]; } }; diff --git a/src/display/api.js b/src/display/api.js index d6e53924164b2..320f83d4b912c 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -115,6 +115,13 @@ PDFJS.postMessageTransfers = (PDFJS.postMessageTransfers === undefined ? PDFJS.disableCreateObjectURL = (PDFJS.disableCreateObjectURL === undefined ? false : PDFJS.disableCreateObjectURL); +/** + * Disables WebGL usage. + * @var {boolean} + */ +PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ? + true : PDFJS.disableWebGL); + /** * Controls the logging level. * The constants from PDFJS.VERBOSITY_LEVELS should be used: diff --git a/src/display/canvas.js b/src/display/canvas.js index 9a18404797fab..bf247df915f6b 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -17,7 +17,8 @@ /* globals ColorSpace, DeviceCmykCS, DeviceGrayCS, DeviceRgbCS, error, PDFJS, FONT_IDENTITY_MATRIX, Uint32ArrayView, IDENTITY_MATRIX, ImageData, ImageKind, isArray, isNum, TilingPattern, OPS, Promise, Util, warn, - assert, info, shadow, TextRenderingMode, getShadingPatternFromIR */ + assert, info, shadow, TextRenderingMode, getShadingPatternFromIR, + WebGLUtils */ 'use strict'; @@ -26,6 +27,7 @@ // Minimal font size that would be used during canvas fillText operations. var MIN_FONT_SIZE = 16; +var MAX_GROUP_SIZE = 4096; var COMPILE_TYPE3_GLYPHS = true; @@ -600,15 +602,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } } - function composeSMask(ctx, smask, layerCtx) { - var mask = smask.canvas; - var maskCtx = smask.context; - var width = mask.width, height = mask.height; - + function genericComposeSMask(maskCtx, layerCtx, width, height, + subtype, backdrop) { var addBackdropFn; - if (smask.backdrop) { - var cs = smask.colorSpace || ColorSpace.singletons.rgb; - var backdrop = cs.getRgb(smask.backdrop, 0); + if (backdrop) { addBackdropFn = function (r0, g0, b0, bytes) { var length = bytes.length; for (var i = 3; i < length; i += 4) { @@ -630,7 +627,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } var composeFn; - if (smask.subtype === 'Luminosity') { + if (subtype === 'Luminosity') { composeFn = function (maskDataBytes, layerDataBytes) { var length = maskDataBytes.length; for (var i = 3; i < length; i += 4) { @@ -651,7 +648,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } // processing image in chunks to save memory - var chunkSize = 16; + var PIXELS_TO_PROCESS = 65536; + var chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width)); for (var row = 0; row < height; row += chunkSize) { var chunkHeight = Math.min(chunkSize, height - row); var maskData = maskCtx.getImageData(0, row, width, chunkHeight); @@ -662,9 +660,30 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { maskCtx.putImageData(layerData, 0, row); } + } - ctx.setTransform(1, 0, 0, 1, 0, 0); - ctx.drawImage(mask, smask.offsetX, smask.offsetY); + function composeSMask(ctx, smask, layerCtx) { + var mask = smask.canvas; + var maskCtx = smask.context; + + ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY, + smask.offsetX, smask.offsetY); + + var backdrop; + if (smask.backdrop) { + var cs = smask.colorSpace || ColorSpace.singletons.rgb; + backdrop = cs.getRgb(smask.backdrop, 0); + } + if (WebGLUtils.isEnabled) { + var composed = WebGLUtils.composeSMask(layerCtx.canvas, mask, + {subtype: smask.subtype, backdrop: backdrop}); + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.drawImage(composed, smask.offsetX, smask.offsetY); + return; + } + genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height, + smask.subtype, backdrop); + ctx.drawImage(mask, 0, 0); } var LINE_CAP_STYLES = ['butt', 'round', 'square']; @@ -781,6 +800,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { endDrawing: function CanvasGraphics_endDrawing() { this.ctx.restore(); CachedCanvases.clear(); + WebGLUtils.clear(); if (this.textLayer) { this.textLayer.endLayout(); @@ -904,6 +924,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.ctx.save(); var groupCtx = scratchCanvas.context; + groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY); groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY); groupCtx.transform.apply(groupCtx, currentTransform); @@ -1792,8 +1813,19 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; // Use ceil in case we're between sizes so we don't create canvas that is // too small and make the canvas at least 1x1 pixels. - var drawnWidth = Math.max(Math.ceil(bounds[2] - bounds[0]), 1); - var drawnHeight = Math.max(Math.ceil(bounds[3] - bounds[1]), 1); + var offsetX = Math.floor(bounds[0]); + var offsetY = Math.floor(bounds[1]); + var drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1); + var drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1); + var scaleX = 1, scaleY = 1; + if (drawnWidth > MAX_GROUP_SIZE) { + scaleX = drawnWidth / MAX_GROUP_SIZE; + drawnWidth = MAX_GROUP_SIZE; + } + if (drawnHeight > MAX_GROUP_SIZE) { + scaleY = drawnHeight / MAX_GROUP_SIZE; + drawnHeight = MAX_GROUP_SIZE; + } var cacheId = 'groupAt' + this.groupLevel; if (group.smask) { @@ -1806,8 +1838,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // Since we created a new canvas that is just the size of the bounding box // we have to translate the group ctx. - var offsetX = bounds[0]; - var offsetY = bounds[1]; + groupCtx.scale(1 / scaleX, 1 / scaleY); groupCtx.translate(-offsetX, -offsetY); groupCtx.transform.apply(groupCtx, currentTransform); @@ -1818,6 +1849,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { context: groupCtx, offsetX: offsetX, offsetY: offsetY, + scaleX: scaleX, + scaleY: scaleY, subtype: group.smask.subtype, backdrop: group.smask.backdrop, colorSpace: group.colorSpace && ColorSpace.fromIR(group.colorSpace) @@ -1827,6 +1860,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { // right location. currentCtx.setTransform(1, 0, 0, 1, 0, 0); currentCtx.translate(offsetX, offsetY); + currentCtx.scale(scaleX, scaleY); } // The transparency group inherits all off the current graphics state // except the blend mode, soft mask, and alpha constants. diff --git a/src/display/pattern_helper.js b/src/display/pattern_helper.js index bf0780815c72f..85cba5d679a99 100644 --- a/src/display/pattern_helper.js +++ b/src/display/pattern_helper.js @@ -15,7 +15,7 @@ * limitations under the License. */ /* globals CanvasGraphics, CachedCanvases, ColorSpace, Util, error, info, - isArray, makeCssRgb */ + isArray, makeCssRgb, WebGLUtils */ 'use strict'; @@ -55,28 +55,27 @@ var createMeshCanvas = (function createMeshCanvasClosure() { var coords = context.coords, colors = context.colors; var bytes = data.data, rowSize = data.width * 4; var tmp; - if (coords[p1 * 2 + 1] > coords[p2 * 2 + 1]) { + if (coords[p1 + 1] > coords[p2 + 1]) { tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; } - if (coords[p2 * 2 + 1] > coords[p3 * 2 + 1]) { + if (coords[p2 + 1] > coords[p3 + 1]) { tmp = p2; p2 = p3; p3 = tmp; tmp = c2; c2 = c3; c3 = tmp; } - if (coords[p1 * 2 + 1] > coords[p2 * 2 + 1]) { + if (coords[p1 + 1] > coords[p2 + 1]) { tmp = p1; p1 = p2; p2 = tmp; tmp = c1; c1 = c2; c2 = tmp; } - var x1 = (coords[p1 * 2] + context.offsetX) * context.scaleX; - var y1 = (coords[p1 * 2 + 1] + context.offsetY) * context.scaleY; - var x2 = (coords[p2 * 2] + context.offsetX) * context.scaleX; - var y2 = (coords[p2 * 2 + 1] + context.offsetY) * context.scaleY; - var x3 = (coords[p3 * 2] + context.offsetX) * context.scaleX; - var y3 = (coords[p3 * 2 + 1] + context.offsetY) * context.scaleY; + var x1 = (coords[p1] + context.offsetX) * context.scaleX; + var y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY; + var x2 = (coords[p2] + context.offsetX) * context.scaleX; + var y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY; + var x3 = (coords[p3] + context.offsetX) * context.scaleX; + var y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY; if (y1 >= y3) { return; } - var c1i = c1 * 3, c2i = c2 * 3, c3i = c3 * 3; - var c1r = colors[c1i], c1g = colors[c1i + 1], c1b = colors[c1i + 2]; - var c2r = colors[c2i], c2g = colors[c2i + 1], c2b = colors[c2i + 2]; - var c3r = colors[c3i], c3g = colors[c3i + 1], c3b = colors[c3i + 2]; + var c1r = colors[c1], c1g = colors[c1 + 1], c1b = colors[c1 + 2]; + var c2r = colors[c2], c2g = colors[c2 + 1], c2b = colors[c2 + 2]; + var c3r = colors[c3], c3g = colors[c3 + 1], c3b = colors[c3 + 2]; var minY = Math.round(y1), maxY = Math.round(y3); var xa, car, cag, cab; @@ -156,39 +155,59 @@ var createMeshCanvas = (function createMeshCanvasClosure() { // MAX_PATTERN_SIZE is used to avoid OOM situation. var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough - var boundsWidth = bounds[2] - bounds[0]; - var boundsHeight = bounds[3] - bounds[1]; + var offsetX = Math.floor(bounds[0]); + var offsetY = Math.floor(bounds[1]); + var boundsWidth = Math.ceil(bounds[2]) - offsetX; + var boundsHeight = Math.ceil(bounds[3]) - offsetY; var width = Math.min(Math.ceil(Math.abs(boundsWidth * combinesScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); var height = Math.min(Math.ceil(Math.abs(boundsHeight * combinesScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE); - var scaleX = width / boundsWidth; - var scaleY = height / boundsHeight; - - var tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); - var tmpCtx = tmpCanvas.context; - if (backgroundColor) { - tmpCtx.fillStyle = makeCssRgb(backgroundColor); - tmpCtx.fillRect(0, 0, width, height); - } + var scaleX = boundsWidth / width; + var scaleY = boundsHeight / height; var context = { coords: coords, colors: colors, - offsetX: -bounds[0], - offsetY: -bounds[1], - scaleX: scaleX, - scaleY: scaleY + offsetX: -offsetX, + offsetY: -offsetY, + scaleX: 1 / scaleX, + scaleY: 1 / scaleY }; - var data = tmpCtx.getImageData(0, 0, width, height); - for (var i = 0; i < figures.length; i++) { - drawFigure(data, figures[i], context); + var canvas; + if (WebGLUtils.isEnabled) { + canvas = WebGLUtils.drawFigures(width, height, backgroundColor, + figures, context); + + // https://bugzilla.mozilla.org/show_bug.cgi?id=972126 + var tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); + tmpCanvas.context.drawImage(canvas, 0, 0); + canvas = tmpCanvas.canvas; + } else { + var tmpCanvas = CachedCanvases.getCanvas('mesh', width, height, false); + var tmpCtx = tmpCanvas.context; + + var data = tmpCtx.createImageData(width, height); + if (backgroundColor) { + var bytes = data.data; + for (var i = 0, ii = bytes.length; i < ii; i += 4) { + bytes[i] = backgroundColor[0]; + bytes[i + 1] = backgroundColor[1]; + bytes[i + 2] = backgroundColor[2]; + bytes[i + 3] = 255; + } + } + for (var i = 0; i < figures.length; i++) { + drawFigure(data, figures[i], context); + } + tmpCtx.putImageData(data, 0, 0); + canvas = tmpCanvas.canvas; } - tmpCtx.putImageData(data, 0, 0); - return {canvas: tmpCanvas.canvas, scaleX: 1 / scaleX, scaleY: 1 / scaleY}; + return {canvas: canvas, offsetX: offsetX, offsetY: offsetY, + scaleX: scaleX, scaleY: scaleY}; } return createMeshCanvas; })(); @@ -222,7 +241,6 @@ ShadingIRs.Mesh = { // Rasterizing on the main thread since sending/queue large canvases // might cause OOM. - // TODO consider using WebGL or asm.js to perform rasterization var temporaryPatternCanvas = createMeshCanvas(bounds, combinedScale, coords, colors, figures, shadingFill ? null : background); @@ -233,7 +251,8 @@ ShadingIRs.Mesh = { } } - ctx.translate(bounds[0], bounds[1]); + ctx.translate(temporaryPatternCanvas.offsetX, + temporaryPatternCanvas.offsetY); ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY); diff --git a/src/display/webgl.js b/src/display/webgl.js new file mode 100644 index 0000000000000..13d68bc13d928 --- /dev/null +++ b/src/display/webgl.js @@ -0,0 +1,428 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* Copyright 2014 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. + */ +/* globals PDFJS, shadow */ +/* jshint -W043 */ + +'use strict'; + +var WebGLUtils = (function WebGLUtilsClosure() { + function loadShader(gl, code, shaderType) { + var shader = gl.createShader(shaderType); + gl.shaderSource(shader, code); + gl.compileShader(shader); + var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (!compiled) { + var errorMsg = gl.getShaderInfoLog(shader); + throw new Error('Error during shader compilation: ' + errorMsg); + } + return shader; + } + function createVertexShader(gl, code) { + return loadShader(gl, code, gl.VERTEX_SHADER); + } + function createFragmentShader(gl, code) { + return loadShader(gl, code, gl.FRAGMENT_SHADER); + } + function createProgram(gl, shaders) { + var program = gl.createProgram(); + for (var i = 0, ii = shaders.length; i < ii; ++i) { + gl.attachShader(program, shaders[i]); + } + gl.linkProgram(program); + var linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + var errorMsg = gl.getProgramInfoLog(program); + throw new Error('Error during program linking: ' + errorMsg); + } + return program; + } + function createTexture(gl, image, textureId) { + gl.activeTexture(textureId); + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Set the parameters so we can render any size image. + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + // Upload the image into the texture. + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); + return texture; + } + + var currentGL, currentCanvas; + function generageGL() { + if (currentGL) { + return; + } + currentCanvas = document.createElement('canvas'); + currentGL = currentCanvas.getContext('webgl', + { premultipliedalpha: false }); + } + + var smaskVertexShaderCode = '\ + attribute vec2 a_position; \ + attribute vec2 a_texCoord; \ + \ + uniform vec2 u_resolution; \ + \ + varying vec2 v_texCoord; \ + \ + void main() { \ + vec2 clipSpace = (a_position / u_resolution) * 2.0 - 1.0; \ + gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \ + \ + v_texCoord = a_texCoord; \ + } '; + + var smaskFragmentShaderCode = '\ + precision mediump float; \ + \ + uniform vec4 u_backdrop; \ + uniform int u_subtype; \ + uniform sampler2D u_image; \ + uniform sampler2D u_mask; \ + \ + varying vec2 v_texCoord; \ + \ + void main() { \ + vec4 imageColor = texture2D(u_image, v_texCoord); \ + vec4 maskColor = texture2D(u_mask, v_texCoord); \ + if (u_backdrop.a > 0.0) { \ + maskColor.rgb = maskColor.rgb * maskColor.a + \ + u_backdrop.rgb * (1.0 - maskColor.a); \ + } \ + float lum; \ + if (u_subtype == 0) { \ + lum = maskColor.a; \ + } else { \ + lum = maskColor.r * 0.3 + maskColor.g * 0.59 + \ + maskColor.b * 0.11; \ + } \ + imageColor.a *= lum; \ + imageColor.rgb *= imageColor.a; \ + gl_FragColor = imageColor; \ + } '; + + var smaskCache = null; + + function initSmaskGL() { + var canvas, gl; + + generageGL(); + canvas = currentCanvas; + currentCanvas = null; + gl = currentGL; + currentGL = null; + + // setup a GLSL program + var vertexShader = createVertexShader(gl, smaskVertexShaderCode); + var fragmentShader = createFragmentShader(gl, smaskFragmentShaderCode); + var program = createProgram(gl, [vertexShader, fragmentShader]); + gl.useProgram(program); + + var cache = {}; + cache.gl = gl; + cache.canvas = canvas; + cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); + cache.positionLocation = gl.getAttribLocation(program, 'a_position'); + cache.backdropLocation = gl.getUniformLocation(program, 'u_backdrop'); + cache.subtypeLocation = gl.getUniformLocation(program, 'u_subtype'); + + var texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); + var texLayerLocation = gl.getUniformLocation(program, 'u_image'); + var texMaskLocation = gl.getUniformLocation(program, 'u_mask'); + + // provide texture coordinates for the rectangle. + var texCoordBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + 0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 0.0, 1.0, + 1.0, 0.0, + 1.0, 1.0]), gl.STATIC_DRAW); + gl.enableVertexAttribArray(texCoordLocation); + gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); + + gl.uniform1i(texLayerLocation, 0); + gl.uniform1i(texMaskLocation, 1); + + smaskCache = cache; + } + + function composeSMask(layer, mask, properties) { + var width = layer.width, height = layer.height; + + if (!smaskCache) { + initSmaskGL(); + } + var cache = smaskCache,canvas = cache.canvas, gl = cache.gl; + canvas.width = width; + canvas.height = height; + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.uniform2f(cache.resolutionLocation, width, height); + + if (properties.backdrop) { + gl.uniform4f(cache.resolutionLocation, properties.backdrop[0], + properties.backdrop[1], properties.backdrop[2], 1); + } else { + gl.uniform4f(cache.resolutionLocation, 0, 0, 0, 0); + } + gl.uniform1i(cache.subtypeLocation, + properties.subtype === 'Luminosity' ? 1 : 0); + + // Create a textures + var texture = createTexture(gl, layer, gl.TEXTURE0); + var maskTexture = createTexture(gl, mask, gl.TEXTURE1); + + + // Create a buffer and put a single clipspace rectangle in + // it (2 triangles) + var buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ + 0, 0, + width, 0, + 0, height, + 0, height, + width, 0, + width, height]), gl.STATIC_DRAW); + gl.enableVertexAttribArray(cache.positionLocation); + gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0); + + // draw + gl.clearColor(0, 0, 0, 0); + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + + gl.flush(); + + gl.deleteTexture(texture); + gl.deleteTexture(maskTexture); + gl.deleteBuffer(buffer); + + return canvas; + } + + var figuresVertexShaderCode = '\ + attribute vec2 a_position; \ + attribute vec3 a_color; \ + \ + uniform vec2 u_resolution; \ + uniform vec2 u_scale; \ + uniform vec2 u_offset; \ + \ + varying vec4 v_color; \ + \ + void main() { \ + vec2 position = (a_position + u_offset) * u_scale; \ + vec2 clipSpace = (position / u_resolution) * 2.0 - 1.0; \ + gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); \ + \ + v_color = vec4(a_color / 255.0, 1.0); \ + } '; + + var figuresFragmentShaderCode = '\ + precision mediump float; \ + \ + varying vec4 v_color; \ + \ + void main() { \ + gl_FragColor = v_color; \ + } '; + + var figuresCache = null; + + function initFiguresGL() { + var canvas, gl; + + generageGL(); + canvas = currentCanvas; + currentCanvas = null; + gl = currentGL; + currentGL = null; + + // setup a GLSL program + var vertexShader = createVertexShader(gl, figuresVertexShaderCode); + var fragmentShader = createFragmentShader(gl, figuresFragmentShaderCode); + var program = createProgram(gl, [vertexShader, fragmentShader]); + gl.useProgram(program); + + var cache = {}; + cache.gl = gl; + cache.canvas = canvas; + cache.resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); + cache.scaleLocation = gl.getUniformLocation(program, 'u_scale'); + cache.offsetLocation = gl.getUniformLocation(program, 'u_offset'); + cache.positionLocation = gl.getAttribLocation(program, 'a_position'); + cache.colorLocation = gl.getAttribLocation(program, 'a_color'); + + figuresCache = cache; + } + + function drawFigures(width, height, backgroundColor, figures, context) { + if (!figuresCache) { + initFiguresGL(); + } + var cache = figuresCache, canvas = cache.canvas, gl = cache.gl; + + canvas.width = width; + canvas.height = height; + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + gl.uniform2f(cache.resolutionLocation, width, height); + + // count triangle points + var count = 0; + for (var i = 0, ii = figures.length; i < ii; i++) { + switch (figures[i].type) { + case 'lattice': + var rows = (figures[i].coords.length / figures[i].verticesPerRow) | 0; + count += (rows - 1) * (figures[i].verticesPerRow - 1) * 6; + break; + case 'triangles': + count += figures[i].coords.length; + break; + } + } + // transfer data + var coords = new Float32Array(count * 2); + var colors = new Uint8Array(count * 3); + var coordsMap = context.coords, colorsMap = context.colors; + var pIndex = 0, cIndex = 0; + for (var i = 0, ii = figures.length; i < ii; i++) { + var figure = figures[i], ps = figure.coords, cs = figure.colors; + switch (figure.type) { + case 'lattice': + var cols = figure.verticesPerRow; + var rows = (ps.length / cols) | 0; + for (var row = 1; row < rows; row++) { + var offset = row * cols + 1; + for (var col = 1; col < cols; col++, offset++) { + coords[pIndex] = coordsMap[ps[offset - cols - 1]]; + coords[pIndex + 1] = coordsMap[ps[offset - cols - 1] + 1]; + coords[pIndex + 2] = coordsMap[ps[offset - cols]]; + coords[pIndex + 3] = coordsMap[ps[offset - cols] + 1]; + coords[pIndex + 4] = coordsMap[ps[offset - 1]]; + coords[pIndex + 5] = coordsMap[ps[offset - 1] + 1]; + colors[cIndex] = colorsMap[cs[offset - cols - 1]]; + colors[cIndex + 1] = colorsMap[cs[offset - cols - 1] + 1]; + colors[cIndex + 2] = colorsMap[cs[offset - cols - 1] + 2]; + colors[cIndex + 3] = colorsMap[cs[offset - cols]]; + colors[cIndex + 4] = colorsMap[cs[offset - cols] + 1]; + colors[cIndex + 5] = colorsMap[cs[offset - cols] + 2]; + colors[cIndex + 6] = colorsMap[cs[offset - 1]]; + colors[cIndex + 7] = colorsMap[cs[offset - 1] + 1]; + colors[cIndex + 8] = colorsMap[cs[offset - 1] + 2]; + + coords[pIndex + 6] = coords[pIndex + 2]; + coords[pIndex + 7] = coords[pIndex + 3]; + coords[pIndex + 8] = coords[pIndex + 4]; + coords[pIndex + 9] = coords[pIndex + 5]; + coords[pIndex + 10] = coordsMap[ps[offset]]; + coords[pIndex + 11] = coordsMap[ps[offset] + 1]; + colors[cIndex + 9] = colors[cIndex + 3]; + colors[cIndex + 10] = colors[cIndex + 4]; + colors[cIndex + 11] = colors[cIndex + 5]; + colors[cIndex + 12] = colors[cIndex + 6]; + colors[cIndex + 13] = colors[cIndex + 7]; + colors[cIndex + 14] = colors[cIndex + 8]; + colors[cIndex + 15] = colorsMap[cs[offset]]; + colors[cIndex + 16] = colorsMap[cs[offset] + 1]; + colors[cIndex + 17] = colorsMap[cs[offset] + 2]; + pIndex += 12; + cIndex += 18; + } + } + break; + case 'triangles': + for (var j = 0, jj = ps.length; j < jj; j++) { + coords[pIndex] = coordsMap[ps[j]]; + coords[pIndex + 1] = coordsMap[ps[j] + 1]; + colors[cIndex] = colorsMap[cs[i]]; + colors[cIndex + 1] = colorsMap[cs[j] + 1]; + colors[cIndex + 2] = colorsMap[cs[j] + 2]; + pIndex += 2; + cIndex += 3; + } + break; + } + } + + // draw + if (backgroundColor) { + gl.clearColor(backgroundColor[0] / 255, backgroundColor[1] / 255, + backgroundColor[2] / 255, 1.0); + } else { + gl.clearColor(0, 0, 0, 0); + } + gl.clear(gl.COLOR_BUFFER_BIT); + + var coordsBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, coordsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, coords, gl.STATIC_DRAW); + gl.enableVertexAttribArray(cache.positionLocation); + gl.vertexAttribPointer(cache.positionLocation, 2, gl.FLOAT, false, 0, 0); + + var colorsBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, colorsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); + gl.enableVertexAttribArray(cache.colorLocation); + gl.vertexAttribPointer(cache.colorLocation, 3, gl.UNSIGNED_BYTE, false, + 0, 0); + + gl.uniform2f(cache.scaleLocation, context.scaleX, context.scaleY); + gl.uniform2f(cache.offsetLocation, context.offsetX, context.offsetY); + + gl.drawArrays(gl.TRIANGLES, 0, count); + + gl.flush(); + + gl.deleteBuffer(coordsBuffer); + gl.deleteBuffer(colorsBuffer); + + return canvas; + } + + function cleanup() { + smaskCache = null; + figuresCache = null; + } + + return { + get isEnabled() { + if (PDFJS.disableWebGL) { + return false; + } + var enabled = false; + try { + generageGL(); + enabled = !!currentGL; + } catch (e) { } + return shadow(this, 'isEnabled', enabled); + }, + composeSMask: composeSMask, + drawFigures: drawFigures, + clear: cleanup + }; +})(); diff --git a/test/font/font_test.html b/test/font/font_test.html index 12f1819cc0f9c..b07044b872f26 100644 --- a/test/font/font_test.html +++ b/test/font/font_test.html @@ -19,6 +19,7 @@ + diff --git a/test/test_slave.html b/test/test_slave.html index 72439ee764ba1..a18a869275b56 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -26,6 +26,7 @@ + diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html index 71b06243cf9de..f56be246431d9 100644 --- a/test/unit/unit_test.html +++ b/test/unit/unit_test.html @@ -18,6 +18,7 @@ + diff --git a/web/default_preferences.js b/web/default_preferences.js index 8ef7ad90c0740..77b82cf5c1229 100644 --- a/web/default_preferences.js +++ b/web/default_preferences.js @@ -22,5 +22,6 @@ var DEFAULT_PREFERENCES = { showPreviousViewOnLoad: true, defaultZoomValue: '', ifAvailableShowOutlineOnLoad: false, - enableHandToolOnLoad: false + enableHandToolOnLoad: false, + enableWebGL: false }; diff --git a/web/viewer.html b/web/viewer.html index 9f014f3b52832..c7cecbc60ac29 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -54,6 +54,7 @@ + diff --git a/web/viewer.js b/web/viewer.js index 3ec7665a08a32..32df033776d69 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -216,10 +216,20 @@ var PDFView = { pageCountField: document.getElementById('pageCountField') }); - this.initialized = true; container.addEventListener('scroll', function() { self.lastScroll = Date.now(); }, false); + + var initializedPromise = Promise.all([ + Preferences.get('enableWebGL').then(function (value) { + PDFJS.disableWebGL = !value; + }) + // TODO move more preferences and other async stuff here + ]); + + return initializedPromise.then(function () { + PDFView.initialized = true; + }); }, getPage: function pdfViewGetPage(n) { @@ -1652,8 +1662,10 @@ var DocumentOutlineView = function documentOutlineView(outline) { //#endif function webViewerLoad(evt) { - PDFView.initialize(); + PDFView.initialize().then(webViewerInitialized); +} +function webViewerInitialized() { //#if (GENERIC || B2G) var params = PDFView.parseQueryString(document.location.search.substring(1)); var file = 'file' in params ? params.file : DEFAULT_URL; @@ -1711,6 +1723,10 @@ function webViewerLoad(evt) { PDFJS.disableHistory = (hashParams['disableHistory'] === 'true'); } + if ('webgl' in hashParams) { + PDFJS.disableWebGL = (hashParams['webgl'] !== 'true'); + } + if ('useOnlyCssZoom' in hashParams) { USE_ONLY_CSS_ZOOM = (hashParams['useOnlyCssZoom'] === 'true'); }