diff --git a/examples/acroforms/index.html b/examples/acroforms/index.html
index 45916d23c..a791aa0ac 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 2d9b2ab0e..4400999f4 100644
--- a/examples/helloworld/index.html
+++ b/examples/helloworld/index.html
@@ -11,6 +11,7 @@
+
diff --git a/make.js b/make.js
index 6be2eace1..62b71ca26 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 137eaefab..a59c766a5 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 d6e539241..320f83d4b 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 9a1840479..bf247df91 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 bf0780815..85cba5d67 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);
- }
- tmpCtx.putImageData(data, 0, 0);
+ var canvas;
+ if (WebGLUtils.isEnabled) {
+ canvas = WebGLUtils.drawFigures(width, height, backgroundColor,
+ figures, context);
- return {canvas: tmpCanvas.canvas, scaleX: 1 / scaleX, scaleY: 1 / scaleY};
+ // 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;
+ }
+
+ 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 000000000..13d68bc13
--- /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 12f1819cc..b07044b87 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 72439ee76..a18a86927 100644
--- a/test/test_slave.html
+++ b/test/test_slave.html
@@ -26,6 +26,7 @@ limitations under the License.
+
diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html
index 71b06243c..f56be2464 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 8ef7ad90c..77b82cf5c 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 9f014f3b5..c7cecbc60 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -54,6 +54,7 @@ http://sourceforge.net/adobe/cmap/wiki/License/
+
diff --git a/web/viewer.js b/web/viewer.js
index efdc5ce0f..ff57253cf 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) {
@@ -1660,8 +1670,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;
@@ -1719,6 +1731,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');
}