/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ 'use strict'; var JpegStreamProxyCounter = 0; // WebWorker Proxy for JpegStream. var JpegStreamProxy = (function() { function constructor(bytes, dict) { this.id = JpegStreamProxyCounter++; this.dict = dict; // Tell the main thread to create an image. postMessage({ action: 'jpeg_stream', data: { id: this.id, raw: bytesToString(bytes) } }); } constructor.prototype = { getImage: function() { return this; }, getChar: function() { error('internal error: getChar is not valid on JpegStream'); } }; return constructor; })(); // Really simple GradientProxy. There is currently only one active gradient at // the time, meaning you can't create a gradient, create a second one and then // use the first one again. As this isn't used in pdf.js right now, it's okay. function GradientProxy(cmdQueue, x0, y0, x1, y1) { cmdQueue.push(['$createLinearGradient', [x0, y0, x1, y1]]); this.addColorStop = function(i, rgba) { cmdQueue.push(['$addColorStop', [i, rgba]]); } } // Really simple PatternProxy. var patternProxyCounter = 0; function PatternProxy(cmdQueue, object, kind) { this.id = patternProxyCounter++; if (!(object instanceof CanvasProxy)) { throw 'unkown type to createPattern'; } // Flush the object here to ensure it's available on the main thread. // TODO: Make some kind of dependency management, such that the object // gets flushed only if needed. object.flush(); cmdQueue.push(['$createPatternFromCanvas', [this.id, object.id, kind]]); } var canvasProxyCounter = 0; function CanvasProxy(width, height) { this.id = canvasProxyCounter++; // The `stack` holds the rendering calls and gets flushed to the main thead. var cmdQueue = this.cmdQueue = []; // Dummy context that gets exposed. var ctx = {}; this.getContext = function(type) { if (type != '2d') { throw 'CanvasProxy can only provide a 2d context.'; } return ctx; } // Expose only the minimum of the canvas object - there is no dom to do // more here. this.width = width; this.height = height; ctx.canvas = this; // Setup function calls to `ctx`. var ctxFunc = [ 'createRadialGradient', 'arcTo', 'arc', 'fillText', 'strokeText', 'createImageData', 'drawWindow', 'save', 'restore', 'scale', 'rotate', 'translate', 'transform', 'setTransform', 'clearRect', 'fillRect', 'strokeRect', 'beginPath', 'closePath', 'moveTo', 'lineTo', 'quadraticCurveTo', 'bezierCurveTo', 'rect', 'fill', 'stroke', 'clip', 'measureText', 'isPointInPath', // These functions are necessary to track the rendering currentX state. // The exact values can be computed on the main thread only, as the // worker has no idea about text width. '$setCurrentX', '$addCurrentX', '$saveCurrentX', '$restoreCurrentX', '$showText', '$setFont' ]; function buildFuncCall(name) { return function() { // console.log("funcCall", name) cmdQueue.push([name, Array.prototype.slice.call(arguments)]); } } var name; for (var i = 0; i < ctxFunc.length; i++) { name = ctxFunc[i]; ctx[name] = buildFuncCall(name); } // Some function calls that need more work. ctx.createPattern = function(object, kind) { return new PatternProxy(cmdQueue, object, kind); } ctx.createLinearGradient = function(x0, y0, x1, y1) { return new GradientProxy(cmdQueue, x0, y0, x1, y1); } ctx.getImageData = function(x, y, w, h) { return { width: w, height: h, data: Uint8ClampedArray(w * h * 4) }; } ctx.putImageData = function(data, x, y, width, height) { cmdQueue.push(['$putImageData', [data, x, y, width, height]]); } ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { if (image instanceof CanvasProxy) { // Send the image/CanvasProxy to the main thread. image.flush(); cmdQueue.push(['$drawCanvas', [image.id, x, y, sx, sy, swidth, sheight]]); } else if (image instanceof JpegStreamProxy) { cmdQueue.push(['$drawImage', [image.id, x, y, sx, sy, swidth, sheight]]); } else { throw 'unkown type to drawImage'; } } // Setup property access to `ctx`. var ctxProp = { // "canvas" 'globalAlpha': '1', 'globalCompositeOperation': 'source-over', 'strokeStyle': '#000000', 'fillStyle': '#000000', 'lineWidth': '1', 'lineCap': 'butt', 'lineJoin': 'miter', 'miterLimit': '10', 'shadowOffsetX': '0', 'shadowOffsetY': '0', 'shadowBlur': '0', 'shadowColor': 'rgba(0, 0, 0, 0)', 'font': '10px sans-serif', 'textAlign': 'start', 'textBaseline': 'alphabetic', 'mozTextStyle': '10px sans-serif', 'mozImageSmoothingEnabled': 'true' }; function buildGetter(name) { return function() { return ctx['$' + name]; } } function buildSetter(name) { return function(value) { cmdQueue.push(['$', name, value]); return ctx['$' + name] = value; } } // Setting the value to `stroke|fillStyle` needs special handling, as it // might gets an gradient/pattern. function buildSetterStyle(name) { return function(value) { if (value instanceof GradientProxy) { cmdQueue.push(['$' + name + 'Gradient']); } else if (value instanceof PatternProxy) { cmdQueue.push(['$' + name + 'Pattern', [value.id]]); } else { cmdQueue.push(['$', name, value]); return ctx['$' + name] = value; } } } for (var name in ctxProp) { ctx['$' + name] = ctxProp[name]; ctx.__defineGetter__(name, buildGetter(name)); // Special treatment for `fillStyle` and `strokeStyle`: The passed style // might be a gradient. Need to check for that. if (name == 'fillStyle' || name == 'strokeStyle') { ctx.__defineSetter__(name, buildSetterStyle(name)); } else { ctx.__defineSetter__(name, buildSetter(name)); } } } /** * Sends the current cmdQueue of the CanvasProxy over to the main thread and * resets the cmdQueue. */ CanvasProxy.prototype.flush = function() { postMessage({ action: 'canvas_proxy_cmd_queue', data: { id: this.id, cmdQueue: this.cmdQueue, width: this.width, height: this.height } }); this.cmdQueue.length = 0; };