зеркало из https://github.com/mozilla/shumway.git
Merge
This commit is contained in:
Коммит
707328b5ad
|
@ -125,6 +125,7 @@ limitations under the License.
|
|||
<script> console.time("Load SWF Dependencies"); </script>
|
||||
<!-- Load SWF Dependencies -->
|
||||
<script src="../../src/swf/config.js"></script>
|
||||
<script src="../../src/swf/options.js"></script>
|
||||
<script src="../../src/flash/util.js"></script>
|
||||
<script src="../../src/swf/swf.js"></script>
|
||||
<script src="../../src/swf/inflate.js"></script>
|
||||
|
@ -139,7 +140,8 @@ limitations under the License.
|
|||
<script src="../../src/swf/text.js"></script>
|
||||
<script src="../../src/swf/mp3worker.js"></script>
|
||||
<script src="../../src/swf/embed.js"></script>
|
||||
<script src="../../src/swf/renderer.js"></script>
|
||||
<script src="../../src/swf/_renderer.js"></script>
|
||||
<script src="../../src/swf/Renderer.js"></script>
|
||||
<script> console.timeEnd("Load SWF Dependencies"); </script>
|
||||
|
||||
<script> console.time("Load SWF Parser"); </script>
|
||||
|
|
|
@ -61,9 +61,8 @@ function updateAVM2State() {
|
|||
enableRegisterAllocator.value = state.allocator;
|
||||
traceExecution.value = state.trace ? 2 : 0;
|
||||
traceRenderer.value = state.trace ? 2 : 0;
|
||||
disableRenderVisitor.value = state.render ? false : true;
|
||||
disableMouseVisitor.value = state.mouse ? false : true;
|
||||
showQuadTree.value = state.qtree ? true : false;
|
||||
disableRendering.value = state.render ? false : true;
|
||||
disableMouse.value = state.mouse ? false : true;
|
||||
turboMode.value = state.turbo ? true : false;
|
||||
//showRedrawRegions.value = state.redraw ? true : false;
|
||||
//renderAsWireframe.value = state.wireframe ? true : false;
|
||||
|
|
|
@ -49,58 +49,6 @@ var BitmapDefinition = (function () {
|
|||
return {
|
||||
// (bitmapData:BitmapData = null, pixelSnapping:String = "auto", smoothing:Boolean = false)
|
||||
__class__: "flash.display.Bitmap",
|
||||
draw: function(ctx, ratio, colorTransform) {
|
||||
if (!this._bitmapData) {
|
||||
return;
|
||||
}
|
||||
var scaledImage;
|
||||
ctx.save();
|
||||
if (this._pixelSnapping === 'auto' || this._pixelSnapping === 'always') {
|
||||
var transform = this._getConcatenatedTransform(null, true);
|
||||
var EPSILON = 0.001;
|
||||
var aInt = Math.abs(Math.round(transform.a));
|
||||
var dInt = Math.abs(Math.round(transform.d));
|
||||
var snapPixels;
|
||||
if (aInt >= 1 && aInt <= MAX_SNAP_DRAW_SCALE_TO_CACHE &&
|
||||
dInt >= 1 && dInt <= MAX_SNAP_DRAW_SCALE_TO_CACHE &&
|
||||
Math.abs(Math.abs(transform.a) / aInt - 1) <= EPSILON &&
|
||||
Math.abs(Math.abs(transform.d) / dInt - 1) <= EPSILON &&
|
||||
Math.abs(transform.b) <= EPSILON && Math.abs(transform.c) <= EPSILON) {
|
||||
if (aInt === 1 && dInt === 1) {
|
||||
snapPixels = true;
|
||||
} else {
|
||||
var sizeKey = aInt + 'x' + dInt;
|
||||
if (this._snapImageCache.size !== sizeKey) {
|
||||
this._snapImageCache.size = sizeKey;
|
||||
this._snapImageCache.hits = 0;
|
||||
this._snapImageCache.image = null;
|
||||
}
|
||||
if (++this._snapImageCache.hits === CACHE_SNAP_DRAW_AFTER) {
|
||||
this._cacheSnapImage(sizeKey, aInt, dInt);
|
||||
}
|
||||
scaledImage = this._snapImageCache.image;
|
||||
snapPixels = !!scaledImage;
|
||||
}
|
||||
} else {
|
||||
snapPixels = false;
|
||||
}
|
||||
if (snapPixels) {
|
||||
ctx.setTransform(transform.a < 0 ? -1 : 1, 0,
|
||||
0, transform.d < 0 ? -1 : 1,
|
||||
(transform.tx/20)|0, (transform.ty/20)|0);
|
||||
}
|
||||
// TODO this._pixelSnapping === 'always'; does it even make sense in other cases?
|
||||
}
|
||||
|
||||
colorTransform.setAlpha(ctx, true);
|
||||
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled =
|
||||
this._smoothing;
|
||||
ctx.drawImage(scaledImage || this._bitmapData._getDrawable(), 0, 0);
|
||||
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = false;
|
||||
ctx.restore();
|
||||
traceRenderer.value && frameWriter.writeLn("Bitmap.draw() snapping: " + this._pixelSnapping +
|
||||
", dimensions: " + this._bitmapData._drawable.width + " x " + this._bitmapData._drawable.height);
|
||||
},
|
||||
_drawableChanged: function () {
|
||||
this._invalidate();
|
||||
this._snapImageCache.image = null;
|
||||
|
|
|
@ -32,10 +32,6 @@ var LoaderDefinition = (function () {
|
|||
var WORKERS_ENABLED = true;
|
||||
var LOADER_PATH = $RELEASE ? 'shumway-worker.js' : 'swf/resourceloader.js';
|
||||
|
||||
var head = document.head;
|
||||
head.insertBefore(document.createElement('style'), head.firstChild);
|
||||
var style = document.styleSheets[0];
|
||||
|
||||
var def = {
|
||||
__class__: 'flash.display.Loader',
|
||||
|
||||
|
@ -446,13 +442,13 @@ var LoaderDefinition = (function () {
|
|||
},
|
||||
_commitImage : function (imageInfo) {
|
||||
var loader = this;
|
||||
var imgPromiseResolve;
|
||||
var imgPromise = this._lastPromise = new Promise(function (resolve) {
|
||||
imgPromiseResolve = resolve;
|
||||
});
|
||||
var img = new Image();
|
||||
imageInfo.props.img = img;
|
||||
img.onload = function() {
|
||||
//var imgPromiseResolve;
|
||||
//var imgPromise = this._lastPromise = new Promise(function (resolve) {
|
||||
// imgPromiseResolve = resolve;
|
||||
//});
|
||||
//var img = new Image();
|
||||
//imageInfo.props.img = img;
|
||||
//img.onload = function() {
|
||||
var Bitmap = avm2.systemDomain.getClass("flash.display.Bitmap");
|
||||
var BitmapData = avm2.systemDomain.getClass("flash.display.BitmapData");
|
||||
|
||||
|
@ -472,15 +468,19 @@ var LoaderDefinition = (function () {
|
|||
loader._invalidateBounds();
|
||||
loader._content = image;
|
||||
|
||||
imgPromiseResolve(imageInfo);
|
||||
// imgPromiseResolve(imageInfo);
|
||||
|
||||
var loaderInfo = loader._contentLoaderInfo;
|
||||
loaderInfo._width = image.width;
|
||||
loaderInfo._height = image.height;
|
||||
loaderInfo._dispatchEvent("init");
|
||||
};
|
||||
img.src = URL.createObjectURL(imageInfo.data);
|
||||
delete imageInfo.data;
|
||||
//};
|
||||
//img.src = URL.createObjectURL(imageInfo.data);
|
||||
//delete imageInfo.data;
|
||||
|
||||
loader._stage._renderer.defineRenderable(loader._stage._renderer.nextId++,
|
||||
symbol.type,
|
||||
symbol);
|
||||
},
|
||||
_commitSymbol: function (symbol) {
|
||||
var dictionary = this._dictionary;
|
||||
|
@ -560,34 +560,6 @@ var LoaderDefinition = (function () {
|
|||
props.buttonActions = symbol.buttonActions;
|
||||
break;
|
||||
case 'font':
|
||||
var charset = fromCharCode.apply(null, symbol.codes);
|
||||
if (charset) {
|
||||
style.insertRule(
|
||||
'@font-face{' +
|
||||
'font-family:"' + symbol.uniqueName + '";' +
|
||||
'src:url(data:font/opentype;base64,' + btoa(symbol.data) + ')' +
|
||||
'}',
|
||||
style.cssRules.length
|
||||
);
|
||||
|
||||
// HACK non-Gecko browsers need time to load fonts
|
||||
if (!/Mozilla\/5.0.*?rv:(\d+).*? Gecko/.test(window.navigator.userAgent)) {
|
||||
var testDiv = document.createElement('div');
|
||||
testDiv.setAttribute('style', 'position: absolute; top: 0; right: 0;' +
|
||||
'visibility: hidden; z-index: -500;' +
|
||||
'font-family:"' + symbol.uniqueName + '";');
|
||||
testDiv.textContent = 'font test';
|
||||
document.body.appendChild(testDiv);
|
||||
|
||||
var fontPromise = new Promise(function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve();
|
||||
document.body.removeChild(testDiv);
|
||||
}, 200);
|
||||
});
|
||||
promiseQueue.push(fontPromise);
|
||||
}
|
||||
}
|
||||
className = 'flash.text.Font';
|
||||
props.name = symbol.name;
|
||||
props.uniqueName = symbol.uniqueName;
|
||||
|
@ -598,45 +570,13 @@ var LoaderDefinition = (function () {
|
|||
this._registerFont(className, props);
|
||||
break;
|
||||
case 'image':
|
||||
var img = new Image();
|
||||
var imgPromiseResolve;
|
||||
var imgPromise = new Promise(function (resolve) {
|
||||
imgPromiseResolve = resolve;
|
||||
});
|
||||
img.onload = function () {
|
||||
if (symbol.mask) {
|
||||
// Write the symbol image into new canvas and apply
|
||||
// the symbol mask.
|
||||
var maskCanvas = document.createElement('canvas');
|
||||
maskCanvas.width = symbol.width;
|
||||
maskCanvas.height = symbol.height;
|
||||
var maskContext = maskCanvas.getContext('2d');
|
||||
maskContext.drawImage(img, 0, 0);
|
||||
var maskImageData = maskContext.getImageData(0, 0, symbol.width, symbol.height);
|
||||
var maskImageDataBytes = maskImageData.data;
|
||||
var symbolMaskBytes = symbol.mask;
|
||||
var length = maskImageData.width * maskImageData.height;
|
||||
for (var i = 0, j = 3; i < length; i++, j += 4) {
|
||||
maskImageDataBytes[j] = symbolMaskBytes[i];
|
||||
}
|
||||
maskContext.putImageData(maskImageData, 0, 0);
|
||||
// Use the result canvas as symbol image
|
||||
props.img = maskCanvas;
|
||||
}
|
||||
imgPromiseResolve();
|
||||
};
|
||||
img.src = URL.createObjectURL(symbol.data);
|
||||
promiseQueue.push(imgPromise);
|
||||
className = 'flash.display.Bitmap';
|
||||
props.img = img;
|
||||
props.width = symbol.width;
|
||||
props.height = symbol.height;
|
||||
break;
|
||||
case 'label':
|
||||
var drawFn = new Function('c,r,ct', symbol.data);
|
||||
className = 'flash.text.StaticText';
|
||||
props.bbox = symbol.bbox;
|
||||
props.draw = drawFn;
|
||||
break;
|
||||
case 'text':
|
||||
props.bbox = symbol.bbox;
|
||||
|
@ -753,6 +693,10 @@ var LoaderDefinition = (function () {
|
|||
};
|
||||
dictionaryResolved[symbol.id] = symbolInfo;
|
||||
symbolPromiseResolve(symbolInfo);
|
||||
|
||||
props.loader._stage._renderer.defineRenderable(symbol.id,
|
||||
symbol.type,
|
||||
symbol);
|
||||
});
|
||||
},
|
||||
_registerFont: function (className, props) {
|
||||
|
|
|
@ -16,10 +16,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* global finishShapePath */
|
||||
|
||||
var ShapeCache = { };
|
||||
|
||||
var ShapeDefinition = (function () {
|
||||
var def = {
|
||||
__class__: 'flash.display.Shape',
|
||||
|
@ -30,44 +26,9 @@ var ShapeDefinition = (function () {
|
|||
var s = this.symbol;
|
||||
if (s && s.paths) {
|
||||
graphics._paths = s.paths;
|
||||
// TODO: this really should be done only once, but I don't know how I
|
||||
// can know when all the required data has been loaded.
|
||||
for (var i = 0; i < s.paths.length; i++) {
|
||||
s.paths[i] = finishShapePath(s.paths[i], s.dictionaryResolved);
|
||||
}
|
||||
graphics.bbox = s.bbox;
|
||||
graphics.strokeBbox = s.strokeBbox;
|
||||
if (this._stage && this._stage._quality === 'low' && !graphics._bitmap)
|
||||
graphics._cacheAsBitmap(this._bbox);
|
||||
this.ratio = s.ratio || 0;
|
||||
|
||||
var renderable = ShapeCache[s.symbolId];
|
||||
|
||||
var bounds = graphics._getBounds(true);
|
||||
var rect = new Shumway.Geometry.Rectangle(bounds.xMin / 20,
|
||||
bounds.yMin / 20,
|
||||
(bounds.xMax - bounds.xMin) / 20,
|
||||
(bounds.yMax - bounds.yMin) / 20);
|
||||
|
||||
if (!renderable) {
|
||||
renderable = {
|
||||
getBounds: function () {
|
||||
return rect;
|
||||
},
|
||||
properties: { },
|
||||
render: function (ctx) {
|
||||
ctx.save();
|
||||
ctx.translate(-rect.x, -rect.y);
|
||||
graphics.draw(ctx, false, 0, new RenderingColorTransform());
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
ShapeCache[s.symbolId] = renderable;
|
||||
}
|
||||
|
||||
this._layer = new Shumway.Layers.Shape(renderable);
|
||||
this._layer.origin = new Shumway.Geometry.Point(rect.x, rect.y);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -48,6 +48,8 @@ var StageDefinition = (function () {
|
|||
this._stageVideos = [];
|
||||
|
||||
this._concatenatedTransform.invalid = false;
|
||||
|
||||
this._renderer = new Renderer();
|
||||
},
|
||||
|
||||
_setup: function setup(ctx, options) {
|
||||
|
@ -74,7 +76,15 @@ var StageDefinition = (function () {
|
|||
|
||||
displayObject._dispatchEvent('addedToStage');
|
||||
|
||||
if (displayObject._layer) {
|
||||
if (displayObject.symbol) {
|
||||
if (!displayObject._layer) {
|
||||
var renderable = this._renderer.getRenderable(displayObject.symbol.symbolId);
|
||||
var layer = new Shumway.Layers.Shape(renderable);
|
||||
layer.origin = new Shumway.Geometry.Point(renderable.rect.x,
|
||||
renderable.rect.y);
|
||||
displayObject._layer = layer;
|
||||
}
|
||||
|
||||
displayObject._parent._layer.addChild(displayObject._layer);
|
||||
}
|
||||
},
|
||||
|
@ -148,6 +158,7 @@ var StageDefinition = (function () {
|
|||
m.d,
|
||||
m.tx / 20,
|
||||
m.ty / 20);
|
||||
node._layer.alpha = node._alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +197,7 @@ var StageDefinition = (function () {
|
|||
clear: true,
|
||||
imageSmoothing: true,
|
||||
snap: false,
|
||||
alpha: false,
|
||||
alpha: true
|
||||
};
|
||||
|
||||
webGLContext = new WebGLContext(canvas, sceneOptions);
|
||||
|
@ -255,10 +266,12 @@ var StageDefinition = (function () {
|
|||
that._processInvalidations();
|
||||
timelineLeave("INVALIDATE");
|
||||
|
||||
if (sceneOptions.webGL) {
|
||||
timelineEnter("WebGL");
|
||||
webGLStageRenderer.render(stage, sceneOptions);
|
||||
timelineLeave("WebGL");
|
||||
if (!disableRendering.value) {
|
||||
if (sceneOptions.webGL) {
|
||||
timelineEnter("WebGL");
|
||||
webGLStageRenderer.render(stage, sceneOptions);
|
||||
timelineLeave("WebGL");
|
||||
}
|
||||
}
|
||||
if (sceneOptions.canvas2D) {
|
||||
timelineEnter("Canvas2D");
|
||||
|
@ -267,18 +280,20 @@ var StageDefinition = (function () {
|
|||
}
|
||||
}
|
||||
|
||||
if (that._mouseMoved) {
|
||||
that._mouseMoved = false;
|
||||
if (!disableMouse.value) {
|
||||
if (that._mouseMoved) {
|
||||
that._mouseMoved = false;
|
||||
|
||||
if (that._mouseOver) {
|
||||
timelineEnter("MOUSE");
|
||||
that._handleMouse();
|
||||
timelineLeave("MOUSE");
|
||||
if (that._mouseOver) {
|
||||
timelineEnter("MOUSE");
|
||||
that._handleMouse();
|
||||
timelineLeave("MOUSE");
|
||||
|
||||
canvas.style.cursor = that._cursor;
|
||||
canvas.style.cursor = that._cursor;
|
||||
}
|
||||
} else {
|
||||
that._handleMouseButtons();
|
||||
}
|
||||
} else {
|
||||
that._handleMouseButtons();
|
||||
}
|
||||
|
||||
timelineLeave("FRAME");
|
||||
|
|
|
@ -16,47 +16,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var StaticTextCache = { };
|
||||
|
||||
var StaticTextDefinition = (function () {
|
||||
var def = {
|
||||
__class__: 'flash.text.StaticText',
|
||||
|
||||
initialize: function () {
|
||||
var s = this.symbol;
|
||||
if (s) {
|
||||
this.draw = s.draw;
|
||||
|
||||
var renderable = StaticTextCache[s.symbolId];
|
||||
|
||||
var bounds = this.getBounds(null);
|
||||
var rect = new Shumway.Geometry.Rectangle(bounds.xMin / 20,
|
||||
bounds.yMin / 20,
|
||||
(bounds.xMax - bounds.xMin) / 20,
|
||||
(bounds.yMax - bounds.yMin) / 20);
|
||||
|
||||
if (!renderable) {
|
||||
renderable = {
|
||||
source: this,
|
||||
getBounds: function () {
|
||||
return rect;
|
||||
},
|
||||
properties: { },
|
||||
render: function (ctx) {
|
||||
ctx.save();
|
||||
ctx.translate(-rect.x, -rect.y);
|
||||
this.source.draw(ctx, 0, new RenderingColorTransform());
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
StaticTextCache[s.symbolId] = renderable;
|
||||
}
|
||||
|
||||
this._layer = new Shumway.Layers.Shape(renderable);
|
||||
this._layer.origin = new Shumway.Geometry.Point(rect.x, rect.y);
|
||||
}
|
||||
},
|
||||
initialize: function () { },
|
||||
|
||||
get text() {
|
||||
return this._text;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,621 @@
|
|||
/*
|
||||
* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
var timeline;
|
||||
var hudTimeline;
|
||||
|
||||
function timelineEnter(name) {
|
||||
timeline && timeline.enter(name);
|
||||
hudTimeline && hudTimeline.enter(name);
|
||||
}
|
||||
|
||||
function timelineLeave(name) {
|
||||
timeline && timeline.leave(name);
|
||||
hudTimeline && hudTimeline.leave(name);
|
||||
}
|
||||
|
||||
function timelineWrapBroadcastMessage(domain, message) {
|
||||
timelineEnter(message);
|
||||
domain.broadcastMessage(message);
|
||||
timelineLeave(message);
|
||||
}
|
||||
|
||||
function initializeHUD(stage, parentCanvas) {
|
||||
var canvas = document.createElement('canvas');
|
||||
var canvasContainer = document.createElement('div');
|
||||
canvasContainer.appendChild(canvas);
|
||||
canvasContainer.style.position = "absolute";
|
||||
canvasContainer.style.top = "0px";
|
||||
canvasContainer.style.left = "0px";
|
||||
canvasContainer.style.width = "100%";
|
||||
canvasContainer.style.height = "150px";
|
||||
canvasContainer.style.backgroundColor = "rgba(0, 0, 0, 0.4)";
|
||||
canvasContainer.style.pointerEvents = "none";
|
||||
parentCanvas.parentElement.appendChild(canvasContainer);
|
||||
hudTimeline = new Timeline(canvas);
|
||||
hudTimeline.setFrameRate(stage._frameRate);
|
||||
hudTimeline.refreshEvery(10);
|
||||
}
|
||||
|
||||
var BlendModeNameMap = {
|
||||
"normal": 'normal',
|
||||
"multiply": 'multiply',
|
||||
"screen": 'screen',
|
||||
"lighten": 'lighten',
|
||||
"darken": 'darken',
|
||||
"difference": 'difference',
|
||||
"overlay": 'overlay',
|
||||
"hardlight": 'hard-light'
|
||||
};
|
||||
|
||||
function getBlendModeName(blendMode) {
|
||||
// TODO:
|
||||
|
||||
// These Flash blend modes have no canvas equivalent:
|
||||
// - blendModeClass.SUBTRACT
|
||||
// - blendModeClass.INVERT
|
||||
// - blendModeClass.SHADER
|
||||
// - blendModeClass.ADD
|
||||
|
||||
// These blend modes are actually Porter-Duff compositing operators.
|
||||
// The backdrop is the nearest parent with blendMode set to LAYER.
|
||||
// When there is no LAYER parent, they are ignored (treated as NORMAL).
|
||||
// - blendModeClass.ALPHA (destination-in)
|
||||
// - blendModeClass.ERASE (destination-out)
|
||||
// - blendModeClass.LAYER [defines backdrop]
|
||||
|
||||
return BlendModeNameMap[blendMode] || 'normal';
|
||||
}
|
||||
|
||||
var head = document.head;
|
||||
head.insertBefore(document.createElement('style'), head.firstChild);
|
||||
var style = document.styleSheets[0];
|
||||
|
||||
// Used for creating gradients and patterns
|
||||
var factoryCtx = !inWorker ?
|
||||
document.createElement('canvas').getContext('2d') :
|
||||
null;
|
||||
|
||||
function Renderer() {
|
||||
this._renderables = { };
|
||||
}
|
||||
Renderer.prototype.nextId = 0xffff;
|
||||
|
||||
Renderer.prototype.defineRenderable = function defineRenderable(id, type, symbol) {
|
||||
var renderable;
|
||||
switch (type) {
|
||||
case 'shape':
|
||||
renderable = new RenderableShape(symbol, this);
|
||||
break;
|
||||
case 'gradient':
|
||||
renderable = new RenderableGradient(symbol, this);
|
||||
break;
|
||||
case 'pattern':
|
||||
renderable = new RenderablePattern(symbol, this);
|
||||
break;
|
||||
case 'bitmap':
|
||||
renderable = new RenderableBitmap(symbol, this);
|
||||
break;
|
||||
case 'font':
|
||||
renderable = new RenderableFont(symbol, this);
|
||||
break;
|
||||
case 'text':
|
||||
case 'label':
|
||||
renderable = new RenderableText(symbol, this);
|
||||
break;
|
||||
}
|
||||
this._renderables[id] = renderable;
|
||||
};
|
||||
Renderer.prototype.getRenderable = function getRenderable(id) {
|
||||
return this._renderables[id];
|
||||
};
|
||||
Renderer.prototype.undefineRenderable = function undefineRenderable(id) {
|
||||
var renderable = this._renderables[id];
|
||||
delete this._renderables[id];
|
||||
return renderable;
|
||||
};
|
||||
|
||||
function RenderableShape(symbol, renderer) {
|
||||
this.commands = symbol.commands;
|
||||
this.data = symbol.data;
|
||||
this.properties = { renderer: renderer };
|
||||
|
||||
var bbox = symbol.strokeBbox || symbol.bbox;
|
||||
|
||||
this.rect = new Shumway.Geometry.Rectangle(bbox.xMin / 20,
|
||||
bbox.yMin / 20,
|
||||
(bbox.xMax - bbox.xMin) / 20,
|
||||
(bbox.yMax - bbox.yMin) / 20);
|
||||
|
||||
var paths = symbol.paths;
|
||||
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
paths[i] = finishShapePath(paths[i], renderer);
|
||||
}
|
||||
|
||||
this.paths = paths;
|
||||
}
|
||||
RenderableShape.prototype.getBounds = function getBounds() {
|
||||
return this.rect;
|
||||
};
|
||||
RenderableShape.prototype.render = function render(ctx) {
|
||||
ctx.save();
|
||||
ctx.translate(-this.rect.x, -this.rect.y);
|
||||
|
||||
var paths = this.paths;
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = paths[i];
|
||||
|
||||
if (!path.fillStyle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
var commands = path.commands;
|
||||
var data = path.data;
|
||||
var morphData = path.morphData;
|
||||
var formOpen = false;
|
||||
var formOpenX = 0;
|
||||
var formOpenY = 0;
|
||||
if (!path.isMorph) {
|
||||
for (var j = 0, k = 0; j < commands.length; j++) {
|
||||
switch (commands[j]) {
|
||||
case SHAPE_MOVE_TO:
|
||||
formOpen = true;
|
||||
formOpenX = data[k++]/20;
|
||||
formOpenY = data[k++]/20;
|
||||
ctx.moveTo(formOpenX, formOpenY);
|
||||
break;
|
||||
case SHAPE_WIDE_MOVE_TO:
|
||||
ctx.moveTo(data[k++]/20, data[k++]/20);
|
||||
k += 2;
|
||||
break;
|
||||
case SHAPE_LINE_TO:
|
||||
ctx.lineTo(data[k++]/20, data[k++]/20);
|
||||
break;
|
||||
case SHAPE_WIDE_LINE_TO:
|
||||
ctx.lineTo(data[k++]/20, data[k++]/20);
|
||||
k += 2;
|
||||
break;
|
||||
case SHAPE_CURVE_TO:
|
||||
ctx.quadraticCurveTo(data[k++]/20, data[k++]/20,
|
||||
data[k++]/20, data[k++]/20);
|
||||
break;
|
||||
case SHAPE_CUBIC_CURVE_TO:
|
||||
ctx.bezierCurveTo(data[k++]/20, data[k++]/20,
|
||||
data[k++]/20, data[k++]/20,
|
||||
data[k++]/20, data[k++]/20);
|
||||
break;
|
||||
case SHAPE_CIRCLE:
|
||||
if (formOpen) {
|
||||
ctx.lineTo(formOpenX, formOpenY);
|
||||
formOpen = false;
|
||||
}
|
||||
ctx.moveTo((data[k] + data[k+2])/20, data[k+1]/20);
|
||||
ctx.arc(data[k++]/20, data[k++]/20, data[k++]/20, 0, Math.PI * 2,
|
||||
false);
|
||||
break;
|
||||
case SHAPE_ELLIPSE:
|
||||
if (formOpen) {
|
||||
ctx.lineTo(formOpenX, formOpenY);
|
||||
formOpen = false;
|
||||
}
|
||||
var x = data[k++];
|
||||
var y = data[k++];
|
||||
var rX = data[k++];
|
||||
var rY = data[k++];
|
||||
var radius;
|
||||
if (rX !== rY) {
|
||||
ctx.save();
|
||||
var ellipseScale;
|
||||
if (rX > rY) {
|
||||
ellipseScale = rX / rY;
|
||||
radius = rY;
|
||||
x /= ellipseScale;
|
||||
ctx.scale(ellipseScale, 1);
|
||||
} else {
|
||||
ellipseScale = rY / rX;
|
||||
radius = rX;
|
||||
y /= ellipseScale;
|
||||
ctx.scale(1, ellipseScale);
|
||||
}
|
||||
}
|
||||
ctx.moveTo((x + radius)/20, y/20);
|
||||
ctx.arc(x/20, y/20, radius/20, 0, Math.PI * 2, false);
|
||||
if (rX !== rY) {
|
||||
ctx.restore();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Sometimes, the very last command isn't properly set. Ignore it.
|
||||
if (commands[j] === 0 && j === commands.length -1) {
|
||||
break;
|
||||
}
|
||||
console.warn("Unknown drawing command encountered: " +
|
||||
commands[j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var j = 0, k = 0; j < commands.length; j++) {
|
||||
switch (commands[j]) {
|
||||
case SHAPE_MOVE_TO:
|
||||
ctx.moveTo(morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio));
|
||||
break;
|
||||
case SHAPE_LINE_TO:
|
||||
ctx.lineTo(morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio));
|
||||
break;
|
||||
case SHAPE_CURVE_TO:
|
||||
ctx.quadraticCurveTo(morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio));
|
||||
break;
|
||||
default:
|
||||
console.warn("Drawing command not supported for morph " +
|
||||
"shapes: " + commands[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: enable in-path line-style changes
|
||||
if (formOpen) {
|
||||
ctx.lineTo(formOpenX, formOpenY);
|
||||
}
|
||||
var fillStyle = path.fillStyle;
|
||||
if (fillStyle) {
|
||||
if (isNaN(fillStyle.style)) {
|
||||
ctx.fillStyle = fillStyle.style;
|
||||
} else {
|
||||
ctx.fillStyle = this.properties.renderer.getRenderable(fillStyle.style).fillStyle;
|
||||
}
|
||||
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled =
|
||||
fillStyle.smooth;
|
||||
var m = fillStyle.transform;
|
||||
ctx.save();
|
||||
if (m) {
|
||||
ctx.transform(m.a, m.b, m.c, m.d, m.e/20, m.f/20);
|
||||
}
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
var lineStyle = path.lineStyle;
|
||||
// TODO: All widths except for `undefined` and `NaN` draw something
|
||||
if (lineStyle) {
|
||||
ctx.strokeStyle = lineStyle.style;
|
||||
ctx.save();
|
||||
// Flash's lines are always at least 1px/20twips
|
||||
ctx.lineWidth = Math.max(lineStyle.width/20, 1);
|
||||
ctx.lineCap = lineStyle.lineCap;
|
||||
ctx.lineJoin = lineStyle.lineJoin;
|
||||
ctx.miterLimit = lineStyle.miterLimit;
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
function RenderableGradient(symbol, renderer) {
|
||||
this.rect = new Shumway.Geometry.Rectangle(0, 0, 0, 0);
|
||||
this.properties = { };
|
||||
|
||||
var gradient;
|
||||
if (symbol.type === GRAPHICS_FILL_LINEAR_GRADIENT) {
|
||||
gradient = factoryCtx.createLinearGradient(-1, 0, 1, 0);
|
||||
} else {
|
||||
gradient = factoryCtx.createRadialGradient((symbol.focalPoint | 0) / 20,
|
||||
0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
var records = symbol.records;
|
||||
for (var i = 0; i < records.length; i++) {
|
||||
var record = records[i];
|
||||
var colorStr = rgbaObjToStr(record.color);
|
||||
gradient.addColorStop(record.ratio / 255, colorStr);
|
||||
}
|
||||
|
||||
this.fillStyle = gradient;
|
||||
}
|
||||
RenderableGradient.prototype.getBounds = function getBounds() {
|
||||
return this.rect;
|
||||
};
|
||||
RenderableGradient.prototype.render = function render(ctx) {
|
||||
// TODO
|
||||
};
|
||||
|
||||
function RenderablePattern(symbol, renderer) {
|
||||
var bitmap = renderer.getRenderable(style.bitmapId);
|
||||
|
||||
if (!bitmap) {
|
||||
this.fillStyle = 'green';
|
||||
return;
|
||||
}
|
||||
|
||||
var rect = bitmap.rect;
|
||||
this.rect = new Shumway.Geometry.Rectangle(rect.x, rect.y, rect.w, rect.h);
|
||||
|
||||
var repeat = (symbol.type === GRAPHICS_FILL_REPEATING_BITMAP) ||
|
||||
(symbol.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP);
|
||||
|
||||
this.fillStyle = factoryCtx.createPattern(bitmap.img,
|
||||
repeat ? 'repeat' : 'no-repeat');
|
||||
}
|
||||
RenderablePattern.prototype.getBounds = function getBounds() {
|
||||
return this.rect;
|
||||
};
|
||||
RenderablePattern.prototype.render = function render(ctx) {
|
||||
// TODO
|
||||
};
|
||||
|
||||
function RenderableBitmap(symbol, renderer) {
|
||||
this.properties = { };
|
||||
this.rect = new Shumway.Geometry.Rectangle(symbol.width / 20,
|
||||
symbol.height / 20);
|
||||
|
||||
var img = new Image();
|
||||
//var imgPromiseResolve;
|
||||
//var imgPromise = new Promise(function (resolve) {
|
||||
// imgPromiseResolve = resolve;
|
||||
//});
|
||||
img.onload = function () {
|
||||
if (symbol.mask) {
|
||||
// Write the image into new canvas and apply the mask.
|
||||
var maskCanvas = document.createElement('canvas');
|
||||
maskCanvas.width = symbol.width;
|
||||
maskCanvas.height = symbol.height;
|
||||
var maskContext = maskCanvas.getContext('2d');
|
||||
maskContext.drawImage(img, 0, 0);
|
||||
var maskImageData = maskContext.getImageData(0, 0, symbol.width, symbol.height);
|
||||
var maskImageDataBytes = maskImageData.data;
|
||||
var symbolMaskBytes = symbol.mask;
|
||||
var length = maskImageData.width * maskImageData.height;
|
||||
for (var i = 0, j = 3; i < length; i++, j += 4) {
|
||||
maskImageDataBytes[j] = symbolMaskBytes[i];
|
||||
}
|
||||
maskContext.putImageData(maskImageData, 0, 0);
|
||||
// Use the result canvas as renderable image
|
||||
props.img = maskCanvas;
|
||||
}
|
||||
//imgPromiseResolve();
|
||||
};
|
||||
img.src = URL.createObjectURL(symbol.data);
|
||||
//promiseQueue.push(imgPromise);
|
||||
|
||||
this.img = img;
|
||||
}
|
||||
RenderableBitmap.prototype.getBounds = function getBounds() {
|
||||
return this.rect;
|
||||
};
|
||||
RenderableBitmap.prototype.render = function render(ctx) {
|
||||
// if (!this._bitmapData) {
|
||||
// return;
|
||||
// }
|
||||
// var scaledImage;
|
||||
// ctx.save();
|
||||
// if (this._pixelSnapping === 'auto' || this._pixelSnapping === 'always') {
|
||||
// var transform = this._getConcatenatedTransform(null, true);
|
||||
// var EPSILON = 0.001;
|
||||
// var aInt = Math.abs(Math.round(transform.a));
|
||||
// var dInt = Math.abs(Math.round(transform.d));
|
||||
// var snapPixels;
|
||||
// if (aInt >= 1 && aInt <= MAX_SNAP_DRAW_SCALE_TO_CACHE &&
|
||||
// dInt >= 1 && dInt <= MAX_SNAP_DRAW_SCALE_TO_CACHE &&
|
||||
// Math.abs(Math.abs(transform.a) / aInt - 1) <= EPSILON &&
|
||||
// Math.abs(Math.abs(transform.d) / dInt - 1) <= EPSILON &&
|
||||
// Math.abs(transform.b) <= EPSILON && Math.abs(transform.c) <= EPSILON) {
|
||||
// if (aInt === 1 && dInt === 1) {
|
||||
// snapPixels = true;
|
||||
// } else {
|
||||
// var sizeKey = aInt + 'x' + dInt;
|
||||
// if (this._snapImageCache.size !== sizeKey) {
|
||||
// this._snapImageCache.size = sizeKey;
|
||||
// this._snapImageCache.hits = 0;
|
||||
// this._snapImageCache.image = null;
|
||||
// }
|
||||
// if (++this._snapImageCache.hits === CACHE_SNAP_DRAW_AFTER) {
|
||||
// this._cacheSnapImage(sizeKey, aInt, dInt);
|
||||
// }
|
||||
// scaledImage = this._snapImageCache.image;
|
||||
// snapPixels = !!scaledImage;
|
||||
// }
|
||||
// } else {
|
||||
// snapPixels = false;
|
||||
// }
|
||||
// if (snapPixels) {
|
||||
// ctx.setTransform(transform.a < 0 ? -1 : 1, 0,
|
||||
// 0, transform.d < 0 ? -1 : 1,
|
||||
// (transform.tx/20)|0, (transform.ty/20)|0);
|
||||
// }
|
||||
// // TODO this._pixelSnapping === 'always'; does it even make sense in other cases?
|
||||
// }
|
||||
//
|
||||
// colorTransform.setAlpha(ctx, true);
|
||||
// ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled =
|
||||
// this._smoothing;
|
||||
// ctx.drawImage(scaledImage || this._bitmapData._getDrawable(), 0, 0);
|
||||
// ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = false;
|
||||
// ctx.restore();
|
||||
// traceRenderer.value && frameWriter.writeLn("Bitmap.draw() snapping: " + this._pixelSnapping +
|
||||
// ", dimensions: " + this._bitmapData._drawable.width + " x " + this._bitmapData._drawable.height);
|
||||
ctx.drawImage(this.img, 0, 0);
|
||||
};
|
||||
|
||||
function RenderableFont(symbol, renderer) {
|
||||
var charset = fromCharCode.apply(null, symbol.codes);
|
||||
if (charset) {
|
||||
style.insertRule(
|
||||
'@font-face{' +
|
||||
'font-family:"' + symbol.uniqueName + '";' +
|
||||
'src:url(data:font/opentype;base64,' + btoa(symbol.data) + ')' +
|
||||
'}',
|
||||
style.cssRules.length
|
||||
);
|
||||
|
||||
// HACK non-Gecko browsers need time to load fonts
|
||||
//if (!/Mozilla\/5.0.*?rv:(\d+).*? Gecko/.test(window.navigator.userAgent)) {
|
||||
// var testDiv = document.createElement('div');
|
||||
// testDiv.setAttribute('style', 'position: absolute; top: 0; right: 0;' +
|
||||
// 'visibility: hidden; z-index: -500;' +
|
||||
// 'font-family:"' + symbol.uniqueName + '";');
|
||||
// testDiv.textContent = 'font test';
|
||||
// document.body.appendChild(testDiv);
|
||||
|
||||
// var fontPromise = new Promise(function (resolve) {
|
||||
// setTimeout(function () {
|
||||
// resolve();
|
||||
// document.body.removeChild(testDiv);
|
||||
// }, 200);
|
||||
// });
|
||||
// promiseQueue.push(fontPromise);
|
||||
//}
|
||||
}
|
||||
}
|
||||
RenderableFont.prototype.getBounds = function getBounds() {
|
||||
// TODO
|
||||
};
|
||||
RenderableFont.prototype.render = function render(ctx) {
|
||||
// TODO
|
||||
};
|
||||
|
||||
function RenderableText(symbol, renderer) {
|
||||
this.properties = { };
|
||||
this.rect = new Shumway.Geometry.Rectangle(0, 0, 0, 0);
|
||||
|
||||
if (symbol.data) {
|
||||
this.render = new Function('c', symbol.data);
|
||||
}
|
||||
}
|
||||
RenderableText.prototype.getBounds = function getBounds() {
|
||||
return this.rect;
|
||||
};
|
||||
RenderableText.prototype.render = function render(ctx) {
|
||||
// this.ensureDimensions();
|
||||
// var bounds = this._bbox;
|
||||
// var width = bounds.xMax / 20;
|
||||
// var height = bounds.yMax / 20;
|
||||
// if (width <= 0 || height <= 0) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// ctx.save();
|
||||
// ctx.beginPath();
|
||||
// ctx.rect(0, 0, width + 1, height + 1);
|
||||
// ctx.clip();
|
||||
// if (this._background) {
|
||||
// ctx.fillStyle = this._backgroundColorStr;
|
||||
// ctx.fill();
|
||||
// }
|
||||
// if (this._border) {
|
||||
// ctx.strokeStyle = this._borderColorStr;
|
||||
// ctx.lineCap = "square";
|
||||
// ctx.lineWidth = 1;
|
||||
// ctx.strokeRect(0.5, 0.5, width|0, height|0);
|
||||
// }
|
||||
// ctx.closePath();
|
||||
//
|
||||
// if (this._lines.length === 0) {
|
||||
// ctx.restore();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// ctx.translate(2, 2);
|
||||
// ctx.save();
|
||||
// var runs = this._content.textruns;
|
||||
// var offsetY = this._lines[this._scrollV - 1].y;
|
||||
// for (var i = 0; i < runs.length; i++) {
|
||||
// var run = runs[i];
|
||||
// if (run.type === 'f') {
|
||||
// ctx.restore();
|
||||
// ctx.font = run.format.str;
|
||||
// // TODO: only apply color and alpha if it actually changed
|
||||
// ctx.fillStyle = run.format.color;
|
||||
// ctx.save();
|
||||
// } else {
|
||||
// assert(run.type === 't', 'Invalid run type: ' + run.type);
|
||||
// if (run.y < offsetY) {
|
||||
// continue;
|
||||
// }
|
||||
// ctx.fillText(run.text, run.x - this._drawingOffsetH, run.y - offsetY);
|
||||
// }
|
||||
// }
|
||||
// ctx.restore();
|
||||
// ctx.restore();
|
||||
//}
|
||||
};
|
||||
|
||||
function initStyle(style, renderer) {
|
||||
if (style.type === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (style.type === GRAPHICS_FILL_SOLID) {
|
||||
// Solid fill styles are fully processed in shape.js's processStyle
|
||||
return;
|
||||
}
|
||||
|
||||
var id = renderer.nextId++;
|
||||
|
||||
switch (style.type) {
|
||||
case GRAPHICS_FILL_LINEAR_GRADIENT:
|
||||
case GRAPHICS_FILL_RADIAL_GRADIENT:
|
||||
case GRAPHICS_FILL_FOCAL_RADIAL_GRADIENT:
|
||||
renderer.defineRenderable(id, 'gradient', style);
|
||||
break;
|
||||
case GRAPHICS_FILL_REPEATING_BITMAP:
|
||||
case GRAPHICS_FILL_CLIPPED_BITMAP:
|
||||
case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP:
|
||||
case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP:
|
||||
renderer.defineRenderable(id, 'pattern', style);
|
||||
break;
|
||||
default:
|
||||
fail('invalid fill style', 'shape');
|
||||
}
|
||||
|
||||
style.style = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* For shapes parsed in a worker thread, we have to finish their
|
||||
* paths after receiving the data in the main thread.
|
||||
*
|
||||
* This entails creating proper instances for all the contained data types.
|
||||
*/
|
||||
function finishShapePath(path, renderer) {
|
||||
assert(!inWorker);
|
||||
|
||||
if (path.fullyInitialized) {
|
||||
return path;
|
||||
}
|
||||
if (!(path instanceof ShapePath)) {
|
||||
var untypedPath = path;
|
||||
path = new ShapePath(path.fillStyle, path.lineStyle, 0, 0, path.isMorph);
|
||||
// See the comment in the ShapePath ctor for why we're recreating the
|
||||
// typed arrays here.
|
||||
path.commands = new Uint8Array(untypedPath.buffers[0]);
|
||||
path.data = new Int32Array(untypedPath.buffers[1]);
|
||||
if (untypedPath.isMorph) {
|
||||
path.morphData = new Int32Array(untypedPath.buffers[2]);
|
||||
}
|
||||
path.buffers = null;
|
||||
}
|
||||
path.fillStyle && initStyle(path.fillStyle, renderer);
|
||||
path.lineStyle && initStyle(path.lineStyle, renderer);
|
||||
path.fullyInitialized = true;
|
||||
return path;
|
||||
}
|
|
@ -15,43 +15,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*global rgbaObjToStr, FirefoxCom, Timer, FrameCounter, metrics, coreOptions, OptionSet, Option, appendToFrameTerminal, frameWriter, randomStyle, Timeline*/
|
||||
|
||||
var rendererOptions = coreOptions.register(new OptionSet("Renderer Options"));
|
||||
var traceRenderer = rendererOptions.register(new Option("tr", "traceRenderer", "number", 0, "trace renderer execution"));
|
||||
var disableRenderVisitor = rendererOptions.register(new Option("drv", "disableRenderVisitor", "boolean", false, "disable render visitor"));
|
||||
var disableMouseVisitor = rendererOptions.register(new Option("dmv", "disableMouseVisitor", "boolean", false, "disable mouse visitor"));
|
||||
var showRedrawRegions = rendererOptions.register(new Option("rr", "showRedrawRegions", "boolean", false, "show redraw regions"));
|
||||
var renderAsWireframe = rendererOptions.register(new Option("raw", "renderAsWireframe", "boolean", false, "render as wireframe"));
|
||||
var showQuadTree = rendererOptions.register(new Option("qt", "showQuadTree", "boolean", false, "show quad tree"));
|
||||
var turboMode = rendererOptions.register(new Option("", "turbo", "boolean", false, "turbo mode"));
|
||||
var forceHidpi = rendererOptions.register(new Option("", "forceHidpi", "boolean", false, "force hidpi"));
|
||||
var skipFrameDraw = rendererOptions.register(new Option("", "skipFrameDraw", "boolean", true, "skip frame when not on time"));
|
||||
var hud = rendererOptions.register(new Option("", "hud", "boolean", false, "show hud mode"));
|
||||
|
||||
var enableConstructChildren = rendererOptions.register(new Option("", "constructChildren", "boolean", true, "Construct Children"));
|
||||
var enableEnterFrame = rendererOptions.register(new Option("", "enterFrame", "boolean", true, "Enter Frame"));
|
||||
var enableAdvanceFrame = rendererOptions.register(new Option("", "advanceFrame", "boolean", true, "Advance Frame"));
|
||||
|
||||
|
||||
var stageOptions = coreOptions.register(new OptionSet("Stage Renderer Options"));
|
||||
var perspectiveCamera = stageOptions.register(new Option("", "pc", "boolean", false, "Use perspective camera."));
|
||||
|
||||
var perspectiveCameraFOV = stageOptions.register(new Option("", "pcFOV", "number", 60, "Perspective Camera FOV."));
|
||||
var perspectiveCameraDistance = stageOptions.register(new Option("", "pcDistance", "number", 1, "Perspective Camera Distance."));
|
||||
var perspectiveCameraAngle = stageOptions.register(new Option("", "pcAngle", "number", 0, "Perspective Camera Angle."));
|
||||
var perspectiveCameraAngleRotate = stageOptions.register(new Option("", "pcRotate", "boolean", false, "Rotate Use perspective camera."));
|
||||
var perspectiveCameraSpacing = stageOptions.register(new Option("", "pcSpacing", "number", 0.1, "Element Spacing."));
|
||||
var perspectiveCameraSpacingInflate = stageOptions.register(new Option("", "pcInflate", "boolean", false, "Rotate Use perspective camera."));
|
||||
|
||||
var drawTiles = stageOptions.register(new Option("", "drawTiles", "boolean", false, "Draw tiles."));
|
||||
var drawTextures = stageOptions.register(new Option("", "drawTextures", "boolean", false, "Draw textures."));
|
||||
|
||||
if (typeof FirefoxCom !== 'undefined') {
|
||||
turboMode.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.turboMode', def: false});
|
||||
hud.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.hud', def: false});
|
||||
forceHidpi.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.force_hidpi', def: false});
|
||||
}
|
||||
/*global rgbaObjToStr, Timer, FrameCounter, metrics, coreOptions, OptionSet, Option, appendToFrameTerminal, frameWriter, randomStyle, Timeline*/
|
||||
|
||||
var CanvasCache = {
|
||||
cache: [],
|
||||
|
@ -103,36 +67,6 @@ function visitContainer(container, visitor, context) {
|
|||
visitor.childrenEnd(container);
|
||||
}
|
||||
|
||||
var BlendModeNameMap = {
|
||||
"normal": 'normal',
|
||||
"multiply": 'multiply',
|
||||
"screen": 'screen',
|
||||
"lighten": 'lighten',
|
||||
"darken": 'darken',
|
||||
"difference": 'difference',
|
||||
"overlay": 'overlay',
|
||||
"hardlight": 'hard-light'
|
||||
};
|
||||
|
||||
function getBlendModeName(blendMode) {
|
||||
// TODO:
|
||||
|
||||
// These Flash blend modes have no canvas equivalent:
|
||||
// - blendModeClass.SUBTRACT
|
||||
// - blendModeClass.INVERT
|
||||
// - blendModeClass.SHADER
|
||||
// - blendModeClass.ADD
|
||||
|
||||
// These blend modes are actually Porter-Duff compositing operators.
|
||||
// The backdrop is the nearest parent with blendMode set to LAYER.
|
||||
// When there is no LAYER parent, they are ignored (treated as NORMAL).
|
||||
// - blendModeClass.ALPHA (destination-in)
|
||||
// - blendModeClass.ERASE (destination-out)
|
||||
// - blendModeClass.LAYER [defines backdrop]
|
||||
|
||||
return BlendModeNameMap[blendMode] || 'normal';
|
||||
}
|
||||
|
||||
function RenderVisitor(root, ctx, invalidPath, refreshStage) {
|
||||
this.root = root;
|
||||
this.ctx = ctx;
|
||||
|
@ -520,14 +454,7 @@ function RenderingContext(refreshStage, invalidPath) {
|
|||
function renderDisplayObject(child, ctx, context) {
|
||||
var m = child._currentTransform;
|
||||
if (m) {
|
||||
if (m.a * m.d == m.b * m.c) {
|
||||
// Workaround for bug 844184 -- the object is invisible
|
||||
ctx.closePath();
|
||||
ctx.rect(0, 0, 0, 0);
|
||||
ctx.clip();
|
||||
} else {
|
||||
ctx.transform(m.a, m.b, m.c, m.d, m.tx/20, m.ty/20);
|
||||
}
|
||||
ctx.transform(m.a, m.b, m.c, m.d, m.tx/20, m.ty/20);
|
||||
}
|
||||
|
||||
if (!renderAsWireframe.value) {
|
||||
|
@ -630,40 +557,48 @@ function sampleEnd() {
|
|||
}
|
||||
}
|
||||
|
||||
var timeline;
|
||||
var hudTimeline;
|
||||
function createRenderDummyBalls(ctx, stage) {
|
||||
var dummyBalls;
|
||||
var radius = 10;
|
||||
var speed = 1;
|
||||
var m = stage._concatenatedTransform;
|
||||
var scaleX = m.a, scaleY = m.d;
|
||||
dummyBalls = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
dummyBalls.push({
|
||||
position: {
|
||||
x: radius + Math.random() * ((ctx.canvas.width - 2 * radius) / scaleX),
|
||||
y: radius + Math.random() * ((ctx.canvas.height - 2 * radius) / scaleY)
|
||||
},
|
||||
velocity: {x: speed * (Math.random() - 0.5), y: speed * (Math.random() - 0.5)}
|
||||
});
|
||||
}
|
||||
ctx.fillStyle = "black";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
|
||||
function timelineEnter(name) {
|
||||
timeline && timeline.enter(name);
|
||||
hudTimeline && hudTimeline.enter(name);
|
||||
}
|
||||
|
||||
function timelineLeave(name) {
|
||||
timeline && timeline.leave(name);
|
||||
hudTimeline && hudTimeline.leave(name);
|
||||
}
|
||||
|
||||
function timelineWrapBroadcastMessage(domain, message) {
|
||||
timelineEnter(message);
|
||||
domain.broadcastMessage(message);
|
||||
timelineLeave(message);
|
||||
}
|
||||
|
||||
function initializeHUD(stage, parentCanvas) {
|
||||
var canvas = document.createElement('canvas');
|
||||
var canvasContainer = document.createElement('div');
|
||||
canvasContainer.appendChild(canvas);
|
||||
canvasContainer.style.position = "absolute";
|
||||
canvasContainer.style.top = "0px";
|
||||
canvasContainer.style.left = "0px";
|
||||
canvasContainer.style.width = "100%";
|
||||
canvasContainer.style.height = "150px";
|
||||
canvasContainer.style.backgroundColor = "rgba(0, 0, 0, 0.4)";
|
||||
// canvasContainer.style.pointerEvents = canvas.style.pointerEvents = "none";
|
||||
parentCanvas.parentElement.appendChild(canvasContainer);
|
||||
hudTimeline = new Timeline(canvas);
|
||||
hudTimeline.setFrameRate(stage._frameRate);
|
||||
hudTimeline.refreshEvery(10);
|
||||
return function renderDummyBalls() {
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
ctx.strokeStyle = "green";
|
||||
dummyBalls.forEach(function (ball) {
|
||||
var position = ball.position;
|
||||
var velocity = ball.velocity;
|
||||
ctx.beginPath();
|
||||
ctx.arc(position.x, position.y, radius, 0, Math.PI * 2, true);
|
||||
ctx.stroke();
|
||||
var x = (position.x + velocity.x);
|
||||
var y = (position.y + velocity.y);
|
||||
if (x < radius || x > ctx.canvas.width / scaleX - radius) {
|
||||
velocity.x *= -1;
|
||||
}
|
||||
if (y < radius || y > ctx.canvas.height / scaleY - radius) {
|
||||
velocity.y *= -1;
|
||||
}
|
||||
position.x += velocity.x;
|
||||
position.y += velocity.y;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function renderStage(stage, ctx, events) {
|
||||
|
@ -732,9 +667,8 @@ function renderStage(stage, ctx, events) {
|
|||
|
||||
updateRenderTransform();
|
||||
|
||||
var frameTime = 0;
|
||||
var maxDelay = 1000 / stage._frameRate;
|
||||
var nextRenderAt = performance.now();
|
||||
var frameScheduler = new FrameScheduler();
|
||||
stage._frameScheduler = frameScheduler;
|
||||
|
||||
var requestAnimationFrame = window.requestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
|
@ -743,52 +677,7 @@ function renderStage(stage, ctx, events) {
|
|||
window.msRequestAnimationFrame ||
|
||||
window.setTimeout;
|
||||
|
||||
var renderDummyBalls;
|
||||
|
||||
var dummyBalls;
|
||||
if (typeof FirefoxCom !== 'undefined' &&
|
||||
FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.dummyMode', def: false})) {
|
||||
var radius = 10;
|
||||
var speed = 1;
|
||||
var m = stage._concatenatedTransform;
|
||||
var scaleX = m.a, scaleY = m.d;
|
||||
dummyBalls = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
dummyBalls.push({
|
||||
position: {
|
||||
x: radius + Math.random() * ((ctx.canvas.width - 2 * radius) / scaleX),
|
||||
y: radius + Math.random() * ((ctx.canvas.height - 2 * radius) / scaleY)
|
||||
},
|
||||
velocity: {x: speed * (Math.random() - 0.5), y: speed * (Math.random() - 0.5)}
|
||||
});
|
||||
}
|
||||
ctx.fillStyle = "black";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
|
||||
renderDummyBalls = function () {
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
ctx.strokeStyle = "green";
|
||||
dummyBalls.forEach(function (ball) {
|
||||
var position = ball.position;
|
||||
var velocity = ball.velocity;
|
||||
ctx.beginPath();
|
||||
ctx.arc(position.x, position.y, radius, 0, Math.PI * 2, true);
|
||||
ctx.stroke();
|
||||
var x = (position.x + velocity.x);
|
||||
var y = (position.y + velocity.y);
|
||||
if (x < radius || x > ctx.canvas.width / scaleX - radius) {
|
||||
velocity.x *= -1;
|
||||
}
|
||||
if (y < radius || y > ctx.canvas.height / scaleY - radius) {
|
||||
velocity.y *= -1;
|
||||
}
|
||||
position.x += velocity.x;
|
||||
position.y += velocity.y;
|
||||
});
|
||||
};
|
||||
}
|
||||
var renderDummyBalls = dummyAnimation.value && createRenderDummyBalls(ctx, stage);
|
||||
|
||||
console.timeEnd("Initialize Renderer");
|
||||
console.timeEnd("Total");
|
||||
|
@ -797,11 +686,9 @@ function renderStage(stage, ctx, events) {
|
|||
var frameCount = 0;
|
||||
var frameFPSAverage = new metrics.Average(120);
|
||||
|
||||
function drawFrame(renderFrame, frameRequested) {
|
||||
if (!skipFrameDraw.value) {
|
||||
frameRequested = true; // e.g. for testing we need to draw all frames
|
||||
}
|
||||
var frameRequested = true;
|
||||
|
||||
function drawFrame(renderFrame, repaint) {
|
||||
sampleStart();
|
||||
|
||||
var refreshStage = false;
|
||||
|
@ -849,9 +736,20 @@ function renderStage(stage, ctx, events) {
|
|||
domain.broadcastMessage("render", "render");
|
||||
}
|
||||
|
||||
if (isCanvasVisible(ctx.canvas) && (refreshStage || renderFrame) &&
|
||||
frameRequested) {
|
||||
var drawEnabled = isCanvasVisible(ctx.canvas) &&
|
||||
(refreshStage || renderFrame) &&
|
||||
(frameRequested || repaint || !skipFrameDraw.value);
|
||||
// checking if we need to skip painting, however not doing it in repaint
|
||||
// mode or during testing
|
||||
if (drawEnabled && !repaint && skipFrameDraw.value &&
|
||||
frameScheduler.shallSkipDraw) {
|
||||
drawEnabled = false;
|
||||
frameScheduler.skipDraw();
|
||||
traceRenderer.value && appendToFrameTerminal("Skip Frame Draw", "red");
|
||||
}
|
||||
if (drawEnabled) {
|
||||
|
||||
frameScheduler.startDraw();
|
||||
var invalidPath = null;
|
||||
|
||||
traceRenderer.value && frameWriter.enter("> Invalidation");
|
||||
|
@ -878,6 +776,7 @@ function renderStage(stage, ctx, events) {
|
|||
invalidPath.draw(ctx);
|
||||
ctx.stroke();
|
||||
}
|
||||
frameScheduler.endDraw();
|
||||
}
|
||||
|
||||
if (mouseMoved && !disableMouseVisitor.value) {
|
||||
|
@ -910,10 +809,7 @@ function renderStage(stage, ctx, events) {
|
|||
sampleEnd();
|
||||
}
|
||||
|
||||
var frameRequested = true;
|
||||
var skipNextFrameDraw = false;
|
||||
(function draw() {
|
||||
var now = performance.now();
|
||||
var renderFrame = true;
|
||||
if (events.onBeforeFrame) {
|
||||
var e = { cancel: false };
|
||||
|
@ -921,35 +817,25 @@ function renderStage(stage, ctx, events) {
|
|||
renderFrame = !e.cancel;
|
||||
}
|
||||
|
||||
frameTime = now;
|
||||
if (renderFrame && renderDummyBalls) {
|
||||
renderDummyBalls();
|
||||
if (renderDummyBalls) {
|
||||
if (renderFrame) {
|
||||
renderDummyBalls();
|
||||
events.onAfterFrame && events.onAfterFrame();
|
||||
}
|
||||
setTimeout(draw);
|
||||
return;
|
||||
}
|
||||
|
||||
drawFrame(renderFrame, frameRequested && !skipNextFrameDraw);
|
||||
frameScheduler.startFrame(stage._frameRate);
|
||||
drawFrame(renderFrame, false);
|
||||
frameScheduler.endFrame();
|
||||
frameRequested = false;
|
||||
|
||||
maxDelay = 1000 / stage._frameRate;
|
||||
if (!turboMode.value) {
|
||||
nextRenderAt += maxDelay;
|
||||
var wasLate = false;
|
||||
while (nextRenderAt < now) {
|
||||
wasLate = true;
|
||||
nextRenderAt += maxDelay;
|
||||
}
|
||||
if (wasLate && !skipNextFrameDraw) {
|
||||
// skips painting of the very next frame if we are not keeping up
|
||||
skipNextFrameDraw = true;
|
||||
traceRenderer.value && appendToFrameTerminal("Skip Frame Draw", "red");
|
||||
} else {
|
||||
// .. but giving it a chance to draw sometime
|
||||
skipNextFrameDraw = false;
|
||||
}
|
||||
} else {
|
||||
nextRenderAt = now;
|
||||
if (!frameScheduler.isOnTime) {
|
||||
traceRenderer.value && appendToFrameTerminal("Frame Is Late", "red");
|
||||
}
|
||||
|
||||
|
||||
if (renderFrame && events.onAfterFrame) {
|
||||
events.onAfterFrame();
|
||||
}
|
||||
|
@ -961,7 +847,7 @@ function renderStage(stage, ctx, events) {
|
|||
return;
|
||||
}
|
||||
|
||||
setTimeout(draw, Math.max(0, nextRenderAt - performance.now()));
|
||||
setTimeout(draw, turboMode.value ? 0 : frameScheduler.nextFrameIn);
|
||||
})();
|
||||
|
||||
(function frame() {
|
||||
|
@ -969,11 +855,107 @@ function renderStage(stage, ctx, events) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (stage._invalid || stage._mouseMoved) {
|
||||
frameRequested = true;
|
||||
if ((stage._invalid || stage._mouseMoved) && !renderDummyBalls) {
|
||||
drawFrame(false, true);
|
||||
}
|
||||
|
||||
frameRequested = true;
|
||||
requestAnimationFrame(frame);
|
||||
})();
|
||||
}
|
||||
|
||||
var FrameScheduler = (function () {
|
||||
var STATS_TO_REMEMBER = 50;
|
||||
var MAX_DRAWS_TO_SKIP = 2;
|
||||
var INTERVAL_PADDING_MS = 4;
|
||||
var SPEED_ADJUST_RATE = 0.9;
|
||||
function FrameScheduler() {
|
||||
this._drawStats = [];
|
||||
this._drawStatsSum = 0;
|
||||
this._drawStarted = 0;
|
||||
this._drawsSkipped = 0;
|
||||
this._expectedNextFrameAt = performance.now();
|
||||
this._onTime = true;
|
||||
this._trackDelta = false;
|
||||
this._delta = 0;
|
||||
this._onTimeDelta = 0;
|
||||
}
|
||||
FrameScheduler.prototype = {
|
||||
get shallSkipDraw() {
|
||||
if (this._drawsSkipped >= MAX_DRAWS_TO_SKIP) {
|
||||
return false;
|
||||
}
|
||||
var averageDraw = this._drawStats.length < STATS_TO_REMEMBER ? 0 :
|
||||
this._drawStatsSum / this._drawStats.length;
|
||||
var estimatedDrawEnd = performance.now() + averageDraw;
|
||||
return estimatedDrawEnd + INTERVAL_PADDING_MS > this._expectedNextFrameAt;
|
||||
},
|
||||
get nextFrameIn() {
|
||||
return Math.max(0, this._expectedNextFrameAt - performance.now());
|
||||
},
|
||||
get isOnTime() {
|
||||
return this._onTime;
|
||||
},
|
||||
startFrame: function (frameRate) {
|
||||
var interval = 1000 / frameRate;
|
||||
|
||||
var adjustedInterval = interval;
|
||||
var delta = this._onTimeDelta + this._delta;
|
||||
if (delta !== 0) {
|
||||
if (delta < 0) {
|
||||
adjustedInterval *= SPEED_ADJUST_RATE;
|
||||
} else if (delta > 0) {
|
||||
adjustedInterval /= SPEED_ADJUST_RATE;
|
||||
}
|
||||
this._onTimeDelta += (interval - adjustedInterval);
|
||||
}
|
||||
|
||||
this._expectedNextFrameAt += adjustedInterval;
|
||||
this._onTime = true;
|
||||
},
|
||||
endFrame: function () {
|
||||
var estimatedNextFrameStart = performance.now() + INTERVAL_PADDING_MS;
|
||||
if (estimatedNextFrameStart > this._expectedNextFrameAt) {
|
||||
if (this._trackDelta) {
|
||||
this._onTimeDelta += (this._expectedNextFrameAt - estimatedNextFrameStart);
|
||||
console.log(this._onTimeDelta);
|
||||
}
|
||||
this._expectedNextFrameAt = estimatedNextFrameStart;
|
||||
this._onTime = false;
|
||||
}
|
||||
},
|
||||
startDraw: function () {
|
||||
this._drawsSkipped = 0;
|
||||
this._drawStarted = performance.now();
|
||||
},
|
||||
endDraw: function () {
|
||||
var drawTime = performance.now() - this._drawStarted;
|
||||
this._drawStats.push(drawTime);
|
||||
this._drawStatsSum += drawTime;
|
||||
while (this._drawStats.length > STATS_TO_REMEMBER) {
|
||||
this._drawStatsSum -= this._drawStats.shift();
|
||||
}
|
||||
},
|
||||
skipDraw: function () {
|
||||
this._drawsSkipped++;
|
||||
},
|
||||
setDelta: function (value) {
|
||||
if (!this._trackDelta) {
|
||||
return;
|
||||
}
|
||||
this._delta = value;
|
||||
},
|
||||
startTrackDelta: function () {
|
||||
this._trackDelta = true;
|
||||
},
|
||||
endTrackDelta: function () {
|
||||
if (!this._trackDelta) {
|
||||
return;
|
||||
}
|
||||
this._trackDelta = false;
|
||||
this._delta = 0;
|
||||
this._onTimeDelta = 0;
|
||||
}
|
||||
};
|
||||
return FrameScheduler;
|
||||
})();
|
|
@ -0,0 +1,43 @@
|
|||
/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
/*
|
||||
* Copyright 2013 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.
|
||||
*/
|
||||
|
||||
var rendererOptions = coreOptions.register(new OptionSet("Renderer Options"));
|
||||
var traceRenderer = rendererOptions.register(new Option("tr", "traceRenderer", "number", 0, "trace renderer execution"));
|
||||
var disableRendering = rendererOptions.register(new Option("drv", "disableRendering", "boolean", false, "disable rendering"));
|
||||
var disableMouse = rendererOptions.register(new Option("dmv", "disableMouse", "boolean", false, "disable mouse handling"));
|
||||
//var showRedrawRegions = rendererOptions.register(new Option("rr", "showRedrawRegions", "boolean", false, "show redraw regions"));
|
||||
//var renderAsWireframe = rendererOptions.register(new Option("raw", "renderAsWireframe", "boolean", false, "render as wireframe"));
|
||||
var turboMode = rendererOptions.register(new Option("", "turbo", "boolean", false, "turbo mode"));
|
||||
var forceHidpi = rendererOptions.register(new Option("", "forceHidpi", "boolean", false, "force hidpi"));
|
||||
var skipFrameDraw = rendererOptions.register(new Option("", "skipFrameDraw", "boolean", true, "skip frame when not on time"));
|
||||
var hud = rendererOptions.register(new Option("", "hud", "boolean", false, "show hud mode"));
|
||||
var dummyAnimation = rendererOptions.register(new Option("", "dummy", "boolean", false, "show test balls animation"));
|
||||
|
||||
var enableConstructChildren = rendererOptions.register(new Option("", "constructChildren", "boolean", true, "Construct Children"));
|
||||
var enableEnterFrame = rendererOptions.register(new Option("", "enterFrame", "boolean", true, "Enter Frame"));
|
||||
var enableAdvanceFrame = rendererOptions.register(new Option("", "advanceFrame", "boolean", true, "Advance Frame"));
|
||||
|
||||
var stageOptions = coreOptions.register(new OptionSet("Stage Renderer Options"));
|
||||
var perspectiveCamera = stageOptions.register(new Option("", "pc", "boolean", false, "Use perspective camera."));
|
||||
|
||||
var perspectiveCameraFOV = stageOptions.register(new Option("", "pcFOV", "number", 60, "Perspective Camera FOV."));
|
||||
var perspectiveCameraDistance = stageOptions.register(new Option("", "pcDistance", "number", 1, "Perspective Camera Distance."));
|
||||
var perspectiveCameraAngle = stageOptions.register(new Option("", "pcAngle", "number", 0, "Perspective Camera Angle."));
|
||||
var perspectiveCameraAngleRotate = stageOptions.register(new Option("", "pcRotate", "boolean", false, "Rotate Use perspective camera."));
|
||||
var perspectiveCameraSpacing = stageOptions.register(new Option("", "pcSpacing", "number", 0.1, "Element Spacing."));
|
||||
var perspectiveCameraSpacingInflate = stageOptions.register(new Option("", "pcInflate", "boolean", false, "Rotate Use perspective camera."));
|
302
src/swf/shape.js
302
src/swf/shape.js
|
@ -697,156 +697,6 @@ ShapePath.prototype = {
|
|||
this.commands.push(SHAPE_ELLIPSE);
|
||||
this.data.push(x, y, radiusX, radiusY);
|
||||
},
|
||||
draw: function(ctx, clip, ratio, colorTransform) {
|
||||
if (clip && !this.fillStyle) {
|
||||
return;
|
||||
}
|
||||
ctx.beginPath();
|
||||
var commands = this.commands;
|
||||
var data = this.data;
|
||||
var morphData = this.morphData;
|
||||
var formOpen = false;
|
||||
var formOpenX = 0;
|
||||
var formOpenY = 0;
|
||||
if (!this.isMorph) {
|
||||
for (var j = 0, k = 0; j < commands.length; j++) {
|
||||
switch (commands[j]) {
|
||||
case SHAPE_MOVE_TO:
|
||||
formOpen = true;
|
||||
formOpenX = data[k++]/20;
|
||||
formOpenY = data[k++]/20;
|
||||
ctx.moveTo(formOpenX, formOpenY);
|
||||
break;
|
||||
case SHAPE_WIDE_MOVE_TO:
|
||||
ctx.moveTo(data[k++]/20, data[k++]/20);
|
||||
k += 2;
|
||||
break;
|
||||
case SHAPE_LINE_TO:
|
||||
ctx.lineTo(data[k++]/20, data[k++]/20);
|
||||
break;
|
||||
case SHAPE_WIDE_LINE_TO:
|
||||
ctx.lineTo(data[k++]/20, data[k++]/20);
|
||||
k += 2;
|
||||
break;
|
||||
case SHAPE_CURVE_TO:
|
||||
ctx.quadraticCurveTo(data[k++]/20, data[k++]/20,
|
||||
data[k++]/20, data[k++]/20);
|
||||
break;
|
||||
case SHAPE_CUBIC_CURVE_TO:
|
||||
ctx.bezierCurveTo(data[k++]/20, data[k++]/20,
|
||||
data[k++]/20, data[k++]/20,
|
||||
data[k++]/20, data[k++]/20);
|
||||
break;
|
||||
case SHAPE_CIRCLE:
|
||||
if (formOpen) {
|
||||
ctx.lineTo(formOpenX, formOpenY);
|
||||
formOpen = false;
|
||||
}
|
||||
ctx.moveTo((data[k] + data[k+2])/20, data[k+1]/20);
|
||||
ctx.arc(data[k++]/20, data[k++]/20, data[k++]/20, 0, Math.PI * 2,
|
||||
false);
|
||||
break;
|
||||
case SHAPE_ELLIPSE:
|
||||
if (formOpen) {
|
||||
ctx.lineTo(formOpenX, formOpenY);
|
||||
formOpen = false;
|
||||
}
|
||||
var x = data[k++];
|
||||
var y = data[k++];
|
||||
var rX = data[k++];
|
||||
var rY = data[k++];
|
||||
var radius;
|
||||
if (rX !== rY) {
|
||||
ctx.save();
|
||||
var ellipseScale;
|
||||
if (rX > rY) {
|
||||
ellipseScale = rX / rY;
|
||||
radius = rY;
|
||||
x /= ellipseScale;
|
||||
ctx.scale(ellipseScale, 1);
|
||||
} else {
|
||||
ellipseScale = rY / rX;
|
||||
radius = rX;
|
||||
y /= ellipseScale;
|
||||
ctx.scale(1, ellipseScale);
|
||||
}
|
||||
}
|
||||
ctx.moveTo((x + radius)/20, y/20);
|
||||
ctx.arc(x/20, y/20, radius/20, 0, Math.PI * 2, false);
|
||||
if (rX !== rY) {
|
||||
ctx.restore();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Sometimes, the very last command isn't properly set. Ignore it.
|
||||
if (commands[j] === 0 && j === commands.length -1) {
|
||||
break;
|
||||
}
|
||||
console.warn("Unknown drawing command encountered: " +
|
||||
commands[j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var j = 0, k = 0; j < commands.length; j++) {
|
||||
switch (commands[j]) {
|
||||
case SHAPE_MOVE_TO:
|
||||
ctx.moveTo(morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio));
|
||||
break;
|
||||
case SHAPE_LINE_TO:
|
||||
ctx.lineTo(morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio));
|
||||
break;
|
||||
case SHAPE_CURVE_TO:
|
||||
ctx.quadraticCurveTo(morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio),
|
||||
morph(data[k]/20, morphData[k++]/20, ratio));
|
||||
break;
|
||||
default:
|
||||
console.warn("Drawing command not supported for morph " +
|
||||
"shapes: " + commands[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: enable in-path line-style changes
|
||||
// if (formOpen) {
|
||||
// ctx.lineTo(formOpenX, formOpenY);
|
||||
// }
|
||||
if (!clip) {
|
||||
var fillStyle = this.fillStyle;
|
||||
if (fillStyle) {
|
||||
colorTransform.setFillStyle(ctx, fillStyle.style);
|
||||
ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled =
|
||||
fillStyle.smooth;
|
||||
var m = fillStyle.transform;
|
||||
ctx.save();
|
||||
colorTransform.setAlpha(ctx);
|
||||
if (m) {
|
||||
ctx.transform(m.a, m.b, m.c, m.d, m.e/20, m.f/20);
|
||||
}
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
var lineStyle = this.lineStyle;
|
||||
// TODO: All widths except for `undefined` and `NaN` draw something
|
||||
if (lineStyle) {
|
||||
colorTransform.setStrokeStyle(ctx, lineStyle.style);
|
||||
ctx.save();
|
||||
colorTransform.setAlpha(ctx);
|
||||
// Flash's lines are always at least 1px/20twips
|
||||
ctx.lineWidth = Math.max(lineStyle.width/20, 1);
|
||||
ctx.lineCap = lineStyle.lineCap;
|
||||
ctx.lineJoin = lineStyle.lineJoin;
|
||||
ctx.miterLimit = lineStyle.miterLimit;
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
} else {
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.closePath();
|
||||
},
|
||||
isPointInPath: function(x, y) {
|
||||
if (!(this.fillStyle || this.lineStyle)) {
|
||||
return false;
|
||||
|
@ -1667,156 +1517,4 @@ function morph(start, end, ratio) {
|
|||
return start + (end - start) * ratio;
|
||||
}
|
||||
|
||||
/**
|
||||
* For shapes parsed in a worker thread, we have to finish their
|
||||
* paths after receiving the data in the main thread.
|
||||
*
|
||||
* This entails creating proper instances for all the contained data types.
|
||||
*/
|
||||
function finishShapePath(path, dictionaryResolved) {
|
||||
assert(!inWorker);
|
||||
|
||||
if (path.fullyInitialized) {
|
||||
return path;
|
||||
}
|
||||
if (!(path instanceof ShapePath)) {
|
||||
var untypedPath = path;
|
||||
path = new ShapePath(path.fillStyle, path.lineStyle, 0, 0, path.isMorph);
|
||||
// See the comment in the ShapePath ctor for why we're recreating the
|
||||
// typed arrays here.
|
||||
path.commands = new Uint8Array(untypedPath.buffers[0]);
|
||||
path.data = new Int32Array(untypedPath.buffers[1]);
|
||||
if (untypedPath.isMorph) {
|
||||
path.morphData = new Int32Array(untypedPath.buffers[2]);
|
||||
}
|
||||
path.buffers = null;
|
||||
}
|
||||
path.fillStyle && initStyle(path.fillStyle, dictionaryResolved);
|
||||
path.lineStyle && initStyle(path.lineStyle, dictionaryResolved);
|
||||
path.fullyInitialized = true;
|
||||
return path;
|
||||
}
|
||||
|
||||
var inWorker = (typeof window) === 'undefined';
|
||||
// Used for creating gradients and patterns
|
||||
var factoryCtx = !inWorker ?
|
||||
document.createElement('canvas').getContext('2d') :
|
||||
null;
|
||||
|
||||
/**
|
||||
* @param {Array} colorStops
|
||||
* @returns {Function}
|
||||
*/
|
||||
function buildLinearGradientFactory(colorStops) {
|
||||
var defaultGradient = factoryCtx.createLinearGradient(-1, 0, 1, 0);
|
||||
for (var i = 0; i < colorStops.length; i++) {
|
||||
defaultGradient.addColorStop(colorStops[i].ratio, colorStops[i].color);
|
||||
}
|
||||
|
||||
var fn = function createLinearGradient(ctx, colorTransform) {
|
||||
var gradient = ctx.createLinearGradient(-1, 0, 1, 0);
|
||||
for (var i = 0; i < colorStops.length; i++) {
|
||||
colorTransform.addGradientColorStop(gradient, colorStops[i].ratio, colorStops[i].color);
|
||||
}
|
||||
return gradient;
|
||||
};
|
||||
fn.defaultFillStyle = defaultGradient;
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} focalPoint
|
||||
* @param {Array} colorStops
|
||||
* @returns {Function}
|
||||
*/
|
||||
function buildRadialGradientFactory(focalPoint, colorStops) {
|
||||
var defaultGradient = factoryCtx.createRadialGradient(focalPoint, 0, 0, 0, 0, 1);
|
||||
for (var i = 0; i < colorStops.length; i++) {
|
||||
defaultGradient.addColorStop(colorStops[i].ratio, colorStops[i].color);
|
||||
}
|
||||
|
||||
var fn = function createRadialGradient(ctx, colorTransform) {
|
||||
var gradient = ctx.createRadialGradient(focalPoint, 0, 0, 0, 0, 1);
|
||||
for (var i = 0; i < colorStops.length; i++) {
|
||||
colorTransform.addGradientColorStop(gradient, colorStops[i].ratio, colorStops[i].color);
|
||||
}
|
||||
return gradient;
|
||||
};
|
||||
fn.defaultFillStyle = defaultGradient;
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} img
|
||||
* @param {String} repeat
|
||||
*/
|
||||
function buildBitmapPatternFactory(img, repeat) {
|
||||
var defaultPattern = factoryCtx.createPattern(img, repeat);
|
||||
|
||||
var cachedTransform, cachedTransformKey;
|
||||
var fn = function createBitmapPattern(ctx, colorTransform) {
|
||||
if (!colorTransform.mode) {
|
||||
return defaultPattern;
|
||||
}
|
||||
var key = colorTransform.getTransformFingerprint();
|
||||
if (key === cachedTransformKey) {
|
||||
return cachedTransform;
|
||||
}
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
var ctx = canvas.getContext('2d');
|
||||
colorTransform.setAlpha(ctx, true);
|
||||
ctx.drawImage(img, 0, 0);
|
||||
cachedTransform = ctx.createPattern(canvas, repeat);
|
||||
cachedTransformKey = key;
|
||||
return cachedTransform;
|
||||
};
|
||||
fn.defaultFillStyle = defaultPattern;
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
function initStyle(style, dictionaryResolved) {
|
||||
if (style.type === undefined) {
|
||||
return;
|
||||
}
|
||||
switch (style.type) {
|
||||
case GRAPHICS_FILL_SOLID:
|
||||
// Solid fill styles are fully processed in shape.js's processStyle
|
||||
break;
|
||||
case GRAPHICS_FILL_LINEAR_GRADIENT:
|
||||
case GRAPHICS_FILL_RADIAL_GRADIENT:
|
||||
case GRAPHICS_FILL_FOCAL_RADIAL_GRADIENT:
|
||||
var records = style.records, colorStops = [];
|
||||
for (var j = 0, n = records.length; j < n; j++) {
|
||||
var record = records[j];
|
||||
var colorStr = rgbaObjToStr(record.color);
|
||||
colorStops.push({ratio: record.ratio / 255, color: colorStr});
|
||||
}
|
||||
|
||||
var gradientConstructor;
|
||||
var isLinear = style.type === GRAPHICS_FILL_LINEAR_GRADIENT;
|
||||
if (isLinear) {
|
||||
gradientConstructor = buildLinearGradientFactory(colorStops);
|
||||
} else {
|
||||
gradientConstructor = buildRadialGradientFactory((style.focalPoint|0)/20, colorStops);
|
||||
}
|
||||
style.style = gradientConstructor;
|
||||
break;
|
||||
case GRAPHICS_FILL_REPEATING_BITMAP:
|
||||
case GRAPHICS_FILL_CLIPPED_BITMAP:
|
||||
case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP:
|
||||
case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP:
|
||||
var bitmap = dictionaryResolved[style.bitmapId];
|
||||
var repeat = (style.type === GRAPHICS_FILL_REPEATING_BITMAP) ||
|
||||
(style.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP);
|
||||
style.style = buildBitmapPatternFactory(bitmap.props.img,
|
||||
repeat ? "repeat" : "no-repeat");
|
||||
break;
|
||||
default:
|
||||
fail('invalid fill style', 'shape');
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче