flashcanvas.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333
  1. /** @preserve FlashCanvas, ${buildDate} ${commitID}
  2. * Copyright 2012 Willow Systems Corp
  3. * Copyright (c) 2009 Tim Cameron Ryan
  4. * Copyright (c) 2009-2011 FlashCanvas Project
  5. * Released under the MIT/X License
  6. */
  7. // Reference:
  8. // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html
  9. // http://dev.w3.org/html5/spec/the-canvas-element.html
  10. // If the browser is IE and does not support HTML5 Canvas
  11. if (window["ActiveXObject"] && !window["CanvasRenderingContext2D"]) {
  12. (function() {
  13. 'use strict'
  14. var window = this
  15. , document = window.document
  16. , undefined
  17. /*
  18. * Constant
  19. */
  20. var NULL = null;
  21. var CANVAS = "canvas";
  22. var CANVAS_RENDERING_CONTEXT_2D = "CanvasRenderingContext2D";
  23. var CANVAS_GRADIENT = "CanvasGradient";
  24. var CANVAS_PATTERN = "CanvasPattern";
  25. var FLASH_CANVAS = "FlashCanvas";
  26. var OBJECT_ID_PREFIX = "external";
  27. var ON_FOCUS = "onfocus";
  28. var ON_PROPERTY_CHANGE = "onpropertychange";
  29. var ON_READY_STATE_CHANGE = "onreadystatechange";
  30. var ON_UNLOAD = "onunload";
  31. var BASE_URL = (function(){
  32. var scripts = this.document.getElementsByTagName("script")
  33. // async script tag injections lead to our script NOT being the last. so
  34. // var script = scripts[scripts.length - 1];
  35. // will not work
  36. // so we just loop over scripts and look for "flashcanvas"
  37. // and go for "last script tag's src" only if path is not matched
  38. // (may happen when flashcanvas script is loaded with name not containing 'flashcanvas')
  39. // backwardCompatibilityUrl: original script was looking at last script tag's src.
  40. // we simulate that for cases when proper URL is not found elsewhere.
  41. var backwardCompatibilityUrl = ''
  42. var i = scripts.length
  43. if (i) {
  44. backwardCompatibilityUrl = scripts[i - 1].src || ''
  45. while (i){
  46. script = scripts[i - 1] // yes, we look from the back of the queue
  47. if (script.src && script.src.match('flashcanvas')) {
  48. // we are trying to return absolute path:
  49. // @see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
  50. // @see http://stackoverflow.com/questions/984510/what-is-my-script-src-url
  51. if (document.documentMode >= 8) {
  52. return script.src;
  53. } else {
  54. return script.getAttribute("src", 4);
  55. }
  56. }
  57. ;i--;
  58. }
  59. }
  60. return backwardCompatibilityUrl
  61. }).call(window).replace(/[^\/]+$/, "") // last part trims all chars following last '/'
  62. // DOMException code
  63. var INDEX_SIZE_ERR = 1;
  64. var NOT_SUPPORTED_ERR = 9;
  65. var INVALID_STATE_ERR = 11;
  66. var SYNTAX_ERR = 12;
  67. var TYPE_MISMATCH_ERR = 17;
  68. var SECURITY_ERR = 18;
  69. /**
  70. * @constructor
  71. */
  72. function Lookup(array) {
  73. for (var i = 0, n = array.length; i < n; i++)
  74. this[array[i]] = i;
  75. }
  76. var properties = new Lookup([
  77. // Canvas element
  78. "toDataURL",
  79. // CanvasRenderingContext2D
  80. "save",
  81. "restore",
  82. "scale",
  83. "rotate",
  84. "translate",
  85. "transform",
  86. "setTransform",
  87. "globalAlpha",
  88. "globalCompositeOperation",
  89. "strokeStyle",
  90. "fillStyle",
  91. "createLinearGradient",
  92. "createRadialGradient",
  93. "createPattern",
  94. "lineWidth",
  95. "lineCap",
  96. "lineJoin",
  97. "miterLimit",
  98. "shadowOffsetX",
  99. "shadowOffsetY",
  100. "shadowBlur",
  101. "shadowColor",
  102. "clearRect",
  103. "fillRect",
  104. "strokeRect",
  105. "beginPath",
  106. "closePath",
  107. "moveTo",
  108. "lineTo",
  109. "quadraticCurveTo",
  110. "bezierCurveTo",
  111. "arcTo",
  112. "rect",
  113. "arc",
  114. "fill",
  115. "stroke",
  116. "clip",
  117. "isPointInPath",
  118. // "drawFocusRing",
  119. "font",
  120. "textAlign",
  121. "textBaseline",
  122. "fillText",
  123. "strokeText",
  124. "measureText",
  125. "drawImage",
  126. "createImageData",
  127. "getImageData",
  128. "putImageData",
  129. // CanvasGradient
  130. "addColorStop",
  131. // Internal use
  132. "direction",
  133. "resize"
  134. ]);
  135. // Whether swf is ready for use
  136. var isReady = {};
  137. // Cache of images loaded by createPattern() or drawImage()
  138. var images = {};
  139. // Monitor the number of loading files
  140. var lock = {};
  141. // Callback functions passed to loadImage()
  142. var callbacks = {};
  143. // SPAN element embedded in the canvas
  144. var spans = {};
  145. var elementIsOrphan = function(e){
  146. var topOfDOM = false
  147. e = e.parentNode
  148. while (e && !topOfDOM){
  149. topOfDOM = e.body
  150. e = e.parentNode
  151. }
  152. return !topOfDOM
  153. }
  154. /**
  155. * 2D context
  156. * @constructor
  157. */
  158. var CanvasRenderingContext2D = function(canvas, swf) {
  159. // back-reference to the canvas
  160. this.canvas = canvas;
  161. // back-reference to the swf
  162. this._swf = swf;
  163. // unique ID of canvas
  164. this._canvasId = swf.id.slice(8);
  165. // initialize drawing states
  166. this._initialize();
  167. // Count CanvasGradient and CanvasPattern objects
  168. this._gradientPatternId = 0;
  169. // Directionality of the canvas element
  170. this._direction = "";
  171. // This ensures that font properties of the canvas element is
  172. // transmitted to Flash.
  173. this._font = "";
  174. // frame update interval
  175. var self = this
  176. this._executeCommandIntervalID = setInterval(function() {
  177. if (elementIsOrphan(self.canvas)) {
  178. clearInterval(self._executeCommandIntervalID)
  179. } else {
  180. if (lock[self._canvasId] === 0) {
  181. self._executeCommand();
  182. }
  183. }
  184. }, 30)
  185. };
  186. CanvasRenderingContext2D.prototype = {
  187. /*
  188. * state
  189. */
  190. save: function() {
  191. // write all properties
  192. this._setCompositing();
  193. this._setShadows();
  194. this._setStrokeStyle();
  195. this._setFillStyle();
  196. this._setLineStyles();
  197. this._setFontStyles();
  198. // push state
  199. this._stateStack.push([
  200. this._globalAlpha,
  201. this._globalCompositeOperation,
  202. this._strokeStyle,
  203. this._fillStyle,
  204. this._lineWidth,
  205. this._lineCap,
  206. this._lineJoin,
  207. this._miterLimit,
  208. this._shadowOffsetX,
  209. this._shadowOffsetY,
  210. this._shadowBlur,
  211. this._shadowColor,
  212. this._font,
  213. this._textAlign,
  214. this._textBaseline
  215. ]);
  216. this._queue.push(properties.save);
  217. },
  218. restore: function() {
  219. // pop state
  220. var stateStack = this._stateStack;
  221. if (stateStack.length) {
  222. var state = stateStack.pop();
  223. this.globalAlpha = state[0];
  224. this.globalCompositeOperation = state[1];
  225. this.strokeStyle = state[2];
  226. this.fillStyle = state[3];
  227. this.lineWidth = state[4];
  228. this.lineCap = state[5];
  229. this.lineJoin = state[6];
  230. this.miterLimit = state[7];
  231. this.shadowOffsetX = state[8];
  232. this.shadowOffsetY = state[9];
  233. this.shadowBlur = state[10];
  234. this.shadowColor = state[11];
  235. this.font = state[12];
  236. this.textAlign = state[13];
  237. this.textBaseline = state[14];
  238. }
  239. this._queue.push(properties.restore);
  240. },
  241. /*
  242. * transformations
  243. */
  244. scale: function(x, y) {
  245. this._queue.push(properties.scale, x, y);
  246. },
  247. rotate: function(angle) {
  248. this._queue.push(properties.rotate, angle);
  249. },
  250. translate: function(x, y) {
  251. this._queue.push(properties.translate, x, y);
  252. },
  253. transform: function(m11, m12, m21, m22, dx, dy) {
  254. this._queue.push(properties.transform, m11, m12, m21, m22, dx, dy);
  255. },
  256. setTransform: function(m11, m12, m21, m22, dx, dy) {
  257. this._queue.push(properties.setTransform, m11, m12, m21, m22, dx, dy);
  258. },
  259. /*
  260. * compositing
  261. */
  262. _setCompositing: function() {
  263. var queue = this._queue;
  264. if (this._globalAlpha !== this.globalAlpha) {
  265. this._globalAlpha = this.globalAlpha;
  266. queue.push(properties.globalAlpha, this._globalAlpha);
  267. }
  268. if (this._globalCompositeOperation !== this.globalCompositeOperation) {
  269. this._globalCompositeOperation = this.globalCompositeOperation;
  270. queue.push(properties.globalCompositeOperation, this._globalCompositeOperation);
  271. }
  272. },
  273. /*
  274. * colors and styles
  275. */
  276. _setStrokeStyle: function() {
  277. if (this._strokeStyle !== this.strokeStyle) {
  278. var style = this._strokeStyle = this.strokeStyle;
  279. if (typeof style === "string") {
  280. // OK
  281. } else if (style instanceof CanvasGradient ||
  282. style instanceof CanvasPattern) {
  283. style = style.id;
  284. } else {
  285. return;
  286. }
  287. this._queue.push(properties.strokeStyle, style);
  288. }
  289. },
  290. _setFillStyle: function() {
  291. if (this._fillStyle !== this.fillStyle) {
  292. var style = this._fillStyle = this.fillStyle;
  293. if (typeof style === "string") {
  294. // OK
  295. } else if (style instanceof CanvasGradient ||
  296. style instanceof CanvasPattern) {
  297. style = style.id;
  298. } else {
  299. return;
  300. }
  301. this._queue.push(properties.fillStyle, style);
  302. }
  303. },
  304. createLinearGradient: function(x0, y0, x1, y1) {
  305. // If any of the arguments are not finite numbers, throws a
  306. // NOT_SUPPORTED_ERR exception.
  307. if (!(isFinite(x0) && isFinite(y0) && isFinite(x1) && isFinite(y1))) {
  308. throwException(NOT_SUPPORTED_ERR);
  309. }
  310. this._queue.push(properties.createLinearGradient, x0, y0, x1, y1);
  311. return new CanvasGradient(this);
  312. },
  313. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  314. // If any of the arguments are not finite numbers, throws a
  315. // NOT_SUPPORTED_ERR exception.
  316. if (!(isFinite(x0) && isFinite(y0) && isFinite(r0) &&
  317. isFinite(x1) && isFinite(y1) && isFinite(r1))) {
  318. throwException(NOT_SUPPORTED_ERR);
  319. }
  320. // If either of the radii are negative, throws an INDEX_SIZE_ERR
  321. // exception.
  322. if (r0 < 0 || r1 < 0) {
  323. throwException(INDEX_SIZE_ERR);
  324. }
  325. this._queue.push(properties.createRadialGradient, x0, y0, r0, x1, y1, r1);
  326. return new CanvasGradient(this);
  327. },
  328. createPattern: function(image, repetition) {
  329. // If the image is null, the implementation must raise a
  330. // TYPE_MISMATCH_ERR exception.
  331. if (!image) {
  332. throwException(TYPE_MISMATCH_ERR);
  333. }
  334. var tagName = image.tagName, src;
  335. var canvasId = this._canvasId;
  336. // If the first argument isn't an img, canvas, or video element,
  337. // throws a TYPE_MISMATCH_ERR exception.
  338. if (tagName) {
  339. tagName = tagName.toLowerCase();
  340. if (tagName === "img") {
  341. src = image.getAttribute("src", 2);
  342. } else if (tagName === CANVAS || tagName === "video") {
  343. // For now, only HTMLImageElement is supported.
  344. return;
  345. } else {
  346. throwException(TYPE_MISMATCH_ERR);
  347. }
  348. }
  349. // Additionally, we accept any object that has a src property.
  350. // This is useful when you'd like to specify a long data URI.
  351. else if (image.src) {
  352. src = image.src;
  353. } else {
  354. throwException(TYPE_MISMATCH_ERR);
  355. }
  356. // If the second argument isn't one of the allowed values, throws a
  357. // SYNTAX_ERR exception.
  358. if (!(repetition === "repeat" || repetition === "no-repeat" ||
  359. repetition === "repeat-x" || repetition === "repeat-y" ||
  360. repetition === "" || repetition === NULL)) {
  361. throwException(SYNTAX_ERR);
  362. }
  363. // Special characters in the filename need escaping.
  364. this._queue.push(properties.createPattern, encodeXML(src), repetition);
  365. // If this is the first time to access the URL, the canvas should be
  366. // locked while the image is being loaded asynchronously.
  367. if (!images[canvasId][src] && isReady[canvasId]) {
  368. this._executeCommand();
  369. ++lock[canvasId];
  370. images[canvasId][src] = true;
  371. }
  372. return new CanvasPattern(this);
  373. },
  374. /*
  375. * line caps/joins
  376. */
  377. _setLineStyles: function() {
  378. var queue = this._queue;
  379. if (this._lineWidth !== this.lineWidth) {
  380. this._lineWidth = this.lineWidth;
  381. queue.push(properties.lineWidth, this._lineWidth);
  382. }
  383. if (this._lineCap !== this.lineCap) {
  384. this._lineCap = this.lineCap;
  385. queue.push(properties.lineCap, this._lineCap);
  386. }
  387. if (this._lineJoin !== this.lineJoin) {
  388. this._lineJoin = this.lineJoin;
  389. queue.push(properties.lineJoin, this._lineJoin);
  390. }
  391. if (this._miterLimit !== this.miterLimit) {
  392. this._miterLimit = this.miterLimit;
  393. queue.push(properties.miterLimit, this._miterLimit);
  394. }
  395. },
  396. /*
  397. * shadows
  398. */
  399. _setShadows: function() {
  400. var queue = this._queue;
  401. if (this._shadowOffsetX !== this.shadowOffsetX) {
  402. this._shadowOffsetX = this.shadowOffsetX;
  403. queue.push(properties.shadowOffsetX, this._shadowOffsetX);
  404. }
  405. if (this._shadowOffsetY !== this.shadowOffsetY) {
  406. this._shadowOffsetY = this.shadowOffsetY;
  407. queue.push(properties.shadowOffsetY, this._shadowOffsetY);
  408. }
  409. if (this._shadowBlur !== this.shadowBlur) {
  410. this._shadowBlur = this.shadowBlur;
  411. queue.push(properties.shadowBlur, this._shadowBlur);
  412. }
  413. if (this._shadowColor !== this.shadowColor) {
  414. this._shadowColor = this.shadowColor;
  415. queue.push(properties.shadowColor, this._shadowColor);
  416. }
  417. },
  418. /*
  419. * rects
  420. */
  421. clearRect: function(x, y, w, h) {
  422. this._queue.push(properties.clearRect, x, y, w, h);
  423. },
  424. fillRect: function(x, y, w, h) {
  425. this._setCompositing();
  426. this._setShadows();
  427. this._setFillStyle();
  428. this._queue.push(properties.fillRect, x, y, w, h);
  429. },
  430. strokeRect: function(x, y, w, h) {
  431. this._setCompositing();
  432. this._setShadows();
  433. this._setStrokeStyle();
  434. this._setLineStyles();
  435. this._queue.push(properties.strokeRect, x, y, w, h);
  436. },
  437. /*
  438. * path API
  439. */
  440. beginPath: function() {
  441. this._queue.push(properties.beginPath);
  442. },
  443. closePath: function() {
  444. this._queue.push(properties.closePath);
  445. },
  446. moveTo: function(x, y) {
  447. this._queue.push(properties.moveTo, x, y);
  448. },
  449. lineTo: function(x, y) {
  450. this._queue.push(properties.lineTo, x, y);
  451. },
  452. quadraticCurveTo: function(cpx, cpy, x, y) {
  453. this._queue.push(properties.quadraticCurveTo, cpx, cpy, x, y);
  454. },
  455. bezierCurveTo: function(cp1x, cp1y, cp2x, cp2y, x, y) {
  456. this._queue.push(properties.bezierCurveTo, cp1x, cp1y, cp2x, cp2y, x, y);
  457. },
  458. arcTo: function(x1, y1, x2, y2, radius) {
  459. // Throws an INDEX_SIZE_ERR exception if the given radius is negative.
  460. if (radius < 0 && isFinite(radius)) {
  461. throwException(INDEX_SIZE_ERR);
  462. }
  463. this._queue.push(properties.arcTo, x1, y1, x2, y2, radius);
  464. },
  465. rect: function(x, y, w, h) {
  466. this._queue.push(properties.rect, x, y, w, h);
  467. },
  468. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  469. // Throws an INDEX_SIZE_ERR exception if the given radius is negative.
  470. if (radius < 0 && isFinite(radius)) {
  471. throwException(INDEX_SIZE_ERR);
  472. }
  473. this._queue.push(properties.arc, x, y, radius, startAngle, endAngle, anticlockwise ? 1 : 0);
  474. },
  475. fill: function() {
  476. this._setCompositing();
  477. this._setShadows();
  478. this._setFillStyle();
  479. this._queue.push(properties.fill);
  480. },
  481. stroke: function() {
  482. this._setCompositing();
  483. this._setShadows();
  484. this._setStrokeStyle();
  485. this._setLineStyles();
  486. this._queue.push(properties.stroke);
  487. },
  488. clip: function() {
  489. this._queue.push(properties.clip);
  490. },
  491. isPointInPath: function(x, y) {
  492. // TODO: Implement
  493. },
  494. /*
  495. * text
  496. */
  497. _setFontStyles: function() {
  498. var queue = this._queue;
  499. if (this._font !== this.font) {
  500. try {
  501. var span = spans[this._canvasId];
  502. span.style.font = this._font = this.font;
  503. var style = span.currentStyle;
  504. var fontSize = span.offsetHeight;
  505. var font = [style.fontStyle, style.fontWeight, fontSize, style.fontFamily].join(" ");
  506. queue.push(properties.font, font);
  507. } catch(e) {
  508. // If this.font cannot be parsed as a CSS font value, then it
  509. // must be ignored.
  510. }
  511. }
  512. if (this._textAlign !== this.textAlign) {
  513. this._textAlign = this.textAlign;
  514. queue.push(properties.textAlign, this._textAlign);
  515. }
  516. if (this._textBaseline !== this.textBaseline) {
  517. this._textBaseline = this.textBaseline;
  518. queue.push(properties.textBaseline, this._textBaseline);
  519. }
  520. if (this._direction !== this.canvas.currentStyle.direction) {
  521. this._direction = this.canvas.currentStyle.direction;
  522. queue.push(properties.direction, this._direction);
  523. }
  524. },
  525. fillText: function(text, x, y, maxWidth) {
  526. this._setCompositing();
  527. this._setFillStyle();
  528. this._setShadows();
  529. this._setFontStyles();
  530. this._queue.push(properties.fillText, encodeXML(text), x, y,
  531. maxWidth === undefined ? Infinity : maxWidth);
  532. },
  533. strokeText: function(text, x, y, maxWidth) {
  534. this._setCompositing();
  535. this._setStrokeStyle();
  536. this._setShadows();
  537. this._setFontStyles();
  538. this._queue.push(properties.strokeText, encodeXML(text), x, y,
  539. maxWidth === undefined ? Infinity : maxWidth);
  540. },
  541. measureText: function(text) {
  542. var span = spans[this._canvasId];
  543. try {
  544. span.style.font = this.font;
  545. } catch(e) {
  546. // If this.font cannot be parsed as a CSS font value, then it must
  547. // be ignored.
  548. }
  549. // Replace space characters with tab characters because innerText
  550. // removes trailing white spaces.
  551. span.innerText = text.replace(/[ \n\f\r]/g, "\t");
  552. return new TextMetrics(span.offsetWidth);
  553. },
  554. /*
  555. * drawing images
  556. */
  557. drawImage: function(image, x1, y1, w1, h1, x2, y2, w2, h2) {
  558. // If the image is null, the implementation must raise a
  559. // TYPE_MISMATCH_ERR exception.
  560. if (!image) {
  561. throwException(TYPE_MISMATCH_ERR);
  562. }
  563. var tagName = image.tagName, src, argc = arguments.length;
  564. var canvasId = this._canvasId;
  565. // If the first argument isn't an img, canvas, or video element,
  566. // throws a TYPE_MISMATCH_ERR exception.
  567. if (tagName) {
  568. tagName = tagName.toLowerCase();
  569. if (tagName === "img") {
  570. src = image.getAttribute("src", 2);
  571. } else if (tagName === CANVAS || tagName === "video") {
  572. // For now, only HTMLImageElement is supported.
  573. return;
  574. } else {
  575. throwException(TYPE_MISMATCH_ERR);
  576. }
  577. }
  578. // Additionally, we accept any object that has a src property.
  579. // This is useful when you'd like to specify a long data URI.
  580. else if (image.src) {
  581. src = image.src;
  582. } else {
  583. throwException(TYPE_MISMATCH_ERR);
  584. }
  585. this._setCompositing();
  586. this._setShadows();
  587. // Special characters in the filename need escaping.
  588. src = encodeXML(src);
  589. if (argc === 3) {
  590. this._queue.push(properties.drawImage, argc, src, x1, y1);
  591. } else if (argc === 5) {
  592. this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1);
  593. } else if (argc === 9) {
  594. // If one of the sw or sh arguments is zero, the implementation
  595. // must raise an INDEX_SIZE_ERR exception.
  596. if (w1 === 0 || h1 === 0) {
  597. throwException(INDEX_SIZE_ERR);
  598. }
  599. this._queue.push(properties.drawImage, argc, src, x1, y1, w1, h1, x2, y2, w2, h2);
  600. } else {
  601. return;
  602. }
  603. // If this is the first time to access the URL, the canvas should be
  604. // locked while the image is being loaded asynchronously.
  605. if (!images[canvasId][src] && isReady[canvasId]) {
  606. this._executeCommand();
  607. ++lock[canvasId];
  608. images[canvasId][src] = true;
  609. }
  610. },
  611. /*
  612. * pixel manipulation
  613. */
  614. // ImageData createImageData(in float sw, in float sh);
  615. // ImageData createImageData(in ImageData imagedata);
  616. createImageData: function() {
  617. // TODO: Implement
  618. },
  619. // ImageData getImageData(in float sx, in float sy, in float sw, in float sh);
  620. getImageData: function(sx, sy, sw, sh) {
  621. // TODO: Implement
  622. },
  623. // void putImageData(in ImageData imagedata, in float dx, in float dy, [Optional] in float dirtyX, in float dirtyY, in float dirtyWidth, in float dirtyHeight);
  624. putImageData: function(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
  625. // TODO: Implement
  626. },
  627. /*
  628. * extended functions
  629. */
  630. loadImage: function(image, onload, onerror) {
  631. var tagName = image.tagName, src;
  632. var canvasId = this._canvasId;
  633. // Get the URL of the image.
  634. if (tagName) {
  635. if (tagName.toLowerCase() === "img") {
  636. src = image.getAttribute("src", 2);
  637. }
  638. } else if (image.src) {
  639. src = image.src;
  640. }
  641. // Do nothing in the following cases:
  642. // - The first argument is neither an img element nor an object
  643. // with a src property,
  644. // - The image has been already cached.
  645. if (!src || images[canvasId][src]) {
  646. return;
  647. }
  648. // Store the objects.
  649. if (onload || onerror) {
  650. callbacks[canvasId][src] = [image, onload, onerror];
  651. }
  652. // Load the image without drawing.
  653. this._queue.push(properties.drawImage, 1, encodeXML(src));
  654. // Execute the command immediately if possible.
  655. if (isReady[canvasId]) {
  656. this._executeCommand();
  657. ++lock[canvasId];
  658. images[canvasId][src] = true;
  659. }
  660. },
  661. /*
  662. * private methods
  663. */
  664. _initialize: function() {
  665. // compositing
  666. this.globalAlpha = this._globalAlpha = 1.0;
  667. this.globalCompositeOperation = this._globalCompositeOperation = "source-over";
  668. // colors and styles
  669. this.strokeStyle = this._strokeStyle = "#000000";
  670. this.fillStyle = this._fillStyle = "#000000";
  671. // line caps/joins
  672. this.lineWidth = this._lineWidth = 1.0;
  673. this.lineCap = this._lineCap = "butt";
  674. this.lineJoin = this._lineJoin = "miter";
  675. this.miterLimit = this._miterLimit = 10.0;
  676. // shadows
  677. this.shadowOffsetX = this._shadowOffsetX = 0;
  678. this.shadowOffsetY = this._shadowOffsetY = 0;
  679. this.shadowBlur = this._shadowBlur = 0;
  680. this.shadowColor = this._shadowColor = "rgba(0, 0, 0, 0.0)";
  681. // text
  682. this.font = this._font = "10px sans-serif";
  683. this.textAlign = this._textAlign = "start";
  684. this.textBaseline = this._textBaseline = "alphabetic";
  685. // command queue
  686. this._queue = [];
  687. // stack of drawing states
  688. this._stateStack = [];
  689. },
  690. _flush: function() {
  691. var queue = this._queue;
  692. this._queue = [];
  693. return queue;
  694. },
  695. _executeCommand: function() {
  696. // execute commands
  697. var commands = this._flush();
  698. if (commands.length > 0) {
  699. try {
  700. return eval( this._swf.CallFunction(
  701. '<invoke name="executeCommand" returntype="javascript"><arguments><string>'
  702. + commands.join("&#0;") + "</string></arguments></invoke>"
  703. ))
  704. } catch (ex) {
  705. }
  706. }
  707. },
  708. _resize: function(width, height) {
  709. // Flush commands in the queue
  710. this._executeCommand();
  711. // Clear back to the initial state
  712. this._initialize();
  713. // Adjust the size of Flash to that of the canvas
  714. if (width > 0) {
  715. this._swf.width = width;
  716. }
  717. if (height > 0) {
  718. this._swf.height = height;
  719. }
  720. // Execute a resize command at the start of the next frame
  721. this._queue.push(properties.resize, width, height);
  722. }
  723. };
  724. /**
  725. * CanvasGradient stub
  726. * @constructor
  727. */
  728. var CanvasGradient = function(ctx) {
  729. this._ctx = ctx;
  730. this.id = ctx._gradientPatternId++;
  731. };
  732. CanvasGradient.prototype = {
  733. addColorStop: function(offset, color) {
  734. // Throws an INDEX_SIZE_ERR exception if the offset is out of range.
  735. if (isNaN(offset) || offset < 0 || offset > 1) {
  736. throwException(INDEX_SIZE_ERR);
  737. }
  738. this._ctx._queue.push(properties.addColorStop, this.id, offset, color);
  739. }
  740. };
  741. /**
  742. * CanvasPattern stub
  743. * @constructor
  744. */
  745. var CanvasPattern = function(ctx) {
  746. this.id = ctx._gradientPatternId++;
  747. };
  748. /**
  749. * TextMetrics stub
  750. * @constructor
  751. */
  752. var TextMetrics = function(width) {
  753. this.width = width;
  754. };
  755. /**
  756. * DOMException
  757. * @constructor
  758. */
  759. var DOMException = function(code) {
  760. var DOMExceptionNames = {
  761. 1: "INDEX_SIZE_ERR",
  762. 9: "NOT_SUPPORTED_ERR",
  763. 11: "INVALID_STATE_ERR",
  764. 12: "SYNTAX_ERR",
  765. 17: "TYPE_MISMATCH_ERR",
  766. 18: "SECURITY_ERR"
  767. }
  768. this.code = code;
  769. this.message = DOMExceptionNames[code];
  770. };
  771. DOMException.prototype = new Error;
  772. /*
  773. * Event handlers
  774. */
  775. /*
  776. * FlashCanvas global object API (not the Canvas API, just initializer etc.)
  777. */
  778. /**
  779. Generates a URL pointing to fashcanvas.swf file by inspecing constants and Window-specific
  780. settings and deriving the path appropirate for that Window.
  781. @public
  782. @function
  783. @param window {Object} Pointer to Window (top, child frames) object instance into which we will dig.
  784. @returns {String} relative or absolute path to the swf file.
  785. */
  786. function getSwfUrl(window) {
  787. return ( (window[FLASH_CANVAS + "Options"] || {})["swfPath"] || BASE_URL ) + "flashcanvas.swf"
  788. }
  789. var registeredEvents = 'registeredEvents'
  790. , canvasesProp = 'canvases'
  791. , initWindow = 'initWindow'
  792. , initElement = 'initElement'
  793. , saveImage = 'saveImage'
  794. , unlock = 'unlock'
  795. , trigger = 'trigger'
  796. var FlashCanvas = {}
  797. FlashCanvas[registeredEvents] = {} // 'canvasID':[[eventName, handler],...]
  798. FlashCanvas[canvasesProp] = {}
  799. FlashCanvas[initWindow] = function(window){
  800. var document = window.document
  801. // IE HTML5 shiv
  802. document.createElement(CANVAS);
  803. // setup default CSS
  804. document.createStyleSheet().cssText =
  805. CANVAS + "{display:inline-block;overflow:hidden;width:300px;height:150px}";
  806. var canvases = this[canvasesProp]
  807. var registeredEvents = this.registeredEvents
  808. var onUnload = function() {
  809. window.detachEvent(ON_UNLOAD, onUnload);
  810. var canvas
  811. , swf
  812. , prop
  813. , NULL = null
  814. , parentWindow
  815. , i, l, e
  816. for (var canvasId in canvases) {
  817. canvas = canvases[canvasId]
  818. swf = canvas.firstChild
  819. parentWindow = canvas.ownerDocument.defaultView ? canvas.ownerDocument.defaultView : canvas.ownerDocument.parentWindow
  820. // parent frame may be handling canvas elemns in self and in children frames. We only kill
  821. // the canvases in "windows" that "unloaded"
  822. if (window === parentWindow) {
  823. // clean up the references of swf.executeCommand and swf.resize
  824. for (prop in swf) {
  825. if (typeof swf[prop] === "function") {
  826. swf[prop] = NULL;
  827. }
  828. }
  829. // clean up the references of canvas.getContext and canvas.toDataURL
  830. for (prop in canvas) {
  831. if (typeof canvas[prop] === "function") {
  832. canvas[prop] = NULL;
  833. }
  834. }
  835. i = 0
  836. l = registeredEvents[canvasId].length
  837. for (; i !== l; i++) {
  838. e = registeredEvents[canvasId][i] // it's an array: [eventName, eventHandler]
  839. swf.detachEvent(e[0], e[1]);
  840. canvas.detachEvent(e[0], e[1]);
  841. }
  842. }
  843. }
  844. // delete exported symbols
  845. window[CANVAS_RENDERING_CONTEXT_2D] = NULL;
  846. window[CANVAS_GRADIENT] = NULL;
  847. window[CANVAS_PATTERN] = NULL;
  848. window[FLASH_CANVAS] = NULL;
  849. }
  850. // prevent IE6 memory leaks
  851. window.attachEvent(ON_UNLOAD, onUnload);
  852. window[CANVAS_RENDERING_CONTEXT_2D] = CanvasRenderingContext2D;
  853. window[CANVAS_GRADIENT] = CanvasGradient;
  854. window[CANVAS_PATTERN] = CanvasPattern;
  855. window[FLASH_CANVAS] = FlashCanvas;
  856. // preload SWF file if it's in the same domain
  857. var swfUrl = getSwfUrl(window)
  858. if (swfUrl.indexOf(window.location.protocol + "//" + window.location.host + "/") === 0) {
  859. window.setTimeout(function(){
  860. var req = new ActiveXObject("Microsoft.XMLHTTP");
  861. req.open("GET", swfUrl, false);
  862. req.send(NULL);
  863. }, 0)
  864. }
  865. function onReadyStateChange() {
  866. if (window.document.readyState === "complete") {
  867. window.document.detachEvent(ON_READY_STATE_CHANGE, onReadyStateChange);
  868. var canvases = window.document.getElementsByTagName(CANVAS);
  869. for (var i = 0, n = canvases.length; i < n; ++i) {
  870. FlashCanvas[initElement](canvases[i]);
  871. }
  872. }
  873. }
  874. // initialize canvas elements
  875. if (window.document.readyState === "complete") {
  876. onReadyStateChange();
  877. } else {
  878. window.document.attachEvent(ON_READY_STATE_CHANGE, onReadyStateChange);
  879. }
  880. }
  881. FlashCanvas[initElement] = function(canvas) {
  882. // Check whether the initialization is required or not.
  883. if (canvas.getContext) {
  884. return canvas;
  885. }
  886. // when init is called from parent frame over canvas sitting in child frame,
  887. // FlashCanvas does not pick up the right "window" or "document" - the one from child frame.
  888. // to avoid making the users specify window, document, we sniff them out from canvas element.
  889. var document = canvas.ownerDocument
  890. , window = document.defaultView ? document.defaultView : document.parentWindow
  891. if (!window[CANVAS_RENDERING_CONTEXT_2D]) {
  892. // this may happen when FlashCanvas.initElement is called from parent fram on a canvas in child frame
  893. // child frame's `window` will not have the canvas methods
  894. this[initWindow](window)
  895. }
  896. // initialize lock
  897. var canvasId = getUniqueId();
  898. var objectId = OBJECT_ID_PREFIX + canvasId;
  899. isReady[canvasId] = false;
  900. images[canvasId] = {};
  901. lock[canvasId] = 1;
  902. callbacks[canvasId] = {};
  903. this.registeredEvents[canvasId] = []
  904. // Set the width and height attributes.
  905. setCanvasSize(canvas);
  906. var swfUrl = getSwfUrl(window)
  907. // on iframes with src = 'about:blank' location.protocol is "about:"
  908. // so, let's not go crafty nuts about this:
  909. var protocol = window.location.protocol === 'https:' ? 'https:' : 'http:'
  910. // embed swf and SPAN element
  911. canvas.innerHTML =
  912. '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"' +
  913. ' codebase="' + protocol + '//fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0"' +
  914. ' width="100%" height="100%" id="' + objectId + '">' +
  915. '<param name="allowScriptAccess" value="always">' +
  916. '<param name="flashvars" value="id=' + objectId + '">' +
  917. '<param name="wmode" value="transparent">' +
  918. // '<param name="movie" value="'+swfUrl+'">'
  919. '</object>' +
  920. '<span style="margin:0;padding:0;border:0;display:inline-block;position:static;height:1em;overflow:visible;white-space:nowrap">' +
  921. '</span>';
  922. this[canvasesProp][canvasId] = canvas;
  923. var swf = canvas.firstChild;
  924. spans[canvasId] = canvas.lastChild;
  925. // Check whether the canvas element is in the DOM tree
  926. var documentContains = document.body.contains;
  927. if (documentContains(canvas)) {
  928. // Load swf file immediately
  929. swf["movie"] = swfUrl;
  930. } else {
  931. // Wait until the element is added to the DOM tree
  932. var intervalId = window.setInterval(function() {
  933. if (documentContains(canvas)) {
  934. window.clearInterval(intervalId);
  935. swf["movie"] = swfUrl;
  936. }
  937. }, 2);
  938. }
  939. // If the browser is IE6 or in quirks mode
  940. if (document.compatMode === "BackCompat" || !window.XMLHttpRequest) {
  941. spans[canvasId].style.overflow = "hidden";
  942. }
  943. // initialize context
  944. var ctx = new CanvasRenderingContext2D(canvas, swf);
  945. // canvas API
  946. canvas.getContext = function(contextId) {
  947. return contextId === "2d" ? ctx : NULL;
  948. };
  949. canvas.toDataURL = function(type, quality) {
  950. if (("" + type).toLowerCase() === "image/jpeg") {
  951. ctx._queue.push(
  952. properties.toDataURL
  953. , type
  954. , typeof quality === "number" ? quality : ""
  955. )
  956. } else {
  957. ctx._queue.push(properties.toDataURL, type);
  958. }
  959. return ctx._executeCommand();
  960. };
  961. // the events handler functions are declared within initElement because
  962. // when it is inited against an iframe, the "window" object points
  963. // elswhere. Thus, we create new set of event handlers for each "window"
  964. // In other words, "window" below is preset.
  965. // forward the event to the parent
  966. var onFocus = function(e) {
  967. var swf = e ? e.srcElement : window.event.srcElement
  968. , canvas = swf.parentNode
  969. swf.blur();
  970. canvas.focus();
  971. }
  972. this.registeredEvents[canvasId].push(
  973. [ON_FOCUS, onFocus]
  974. )
  975. // add event listener
  976. swf.attachEvent(ON_FOCUS, onFocus);
  977. return canvas;
  978. }
  979. FlashCanvas[saveImage] = function(canvas) {
  980. var swf = canvas.firstChild;
  981. swf[saveImage]();
  982. }
  983. FlashCanvas.setOptions = function(options) {
  984. // TODO: Implement
  985. }
  986. FlashCanvas[trigger] = function(canvasId, type) {
  987. var canvas = this[canvasesProp][canvasId];
  988. canvas.fireEvent("on" + type);
  989. }
  990. FlashCanvas[unlock] = function(canvasId, url, error) {
  991. try {
  992. var canvas, swf, width, height;
  993. var _callback, image, callback;
  994. var document, window
  995. // If Flash becomes ready
  996. if (url === undefined) {
  997. canvas = this[canvasesProp][canvasId];
  998. swf = canvas.firstChild;
  999. // when init is called from parent frame over canvas sitting in child frame,
  1000. // FlashCanvas does not pick up the right "window" or "document" - the one from child frame.
  1001. // to avoid making the users specify window, document, we sniff them out from canvas element.
  1002. document = canvas.ownerDocument
  1003. window = document.defaultView ? document.defaultView : document.parentWindow
  1004. // Set the width and height attributes of the canvas element.
  1005. setCanvasSize(canvas);
  1006. width = canvas.width;
  1007. height = canvas.height;
  1008. canvas.style.width = width + "px";
  1009. canvas.style.height = height + "px";
  1010. // Adjust the size of Flash to that of the canvas
  1011. if (width > 0) {
  1012. swf.width = width;
  1013. }
  1014. if (height > 0) {
  1015. swf.height = height;
  1016. }
  1017. swf.resize(width, height);
  1018. // the events handler functions are declared within initElement because
  1019. // when it is inited against an iframe, the "window" object points
  1020. // elswhere. Thus, we create new set of event handlers for each "window"
  1021. // In other words, "window" below is NOT resolved runtime. It's preset.
  1022. var onPropertyChange = function(e) {
  1023. var e = e ? e : window.event
  1024. , prop = e.propertyName
  1025. if (prop === "width" || prop === "height") {
  1026. var canvas = e.srcElement;
  1027. var value = canvas[prop];
  1028. var number = parseInt(value, 10);
  1029. if (isNaN(number) || number < 0) {
  1030. number = (prop === "width") ? 300 : 150;
  1031. }
  1032. if (value === number) {
  1033. canvas.style[prop] = number + "px";
  1034. canvas.getContext("2d")._resize(canvas.width, canvas.height);
  1035. } else {
  1036. canvas[prop] = number;
  1037. }
  1038. }
  1039. }
  1040. this.registeredEvents[canvasId].push(
  1041. [ON_PROPERTY_CHANGE, onPropertyChange]
  1042. )
  1043. // Add event listener
  1044. canvas.attachEvent(ON_PROPERTY_CHANGE, onPropertyChange);
  1045. // ExternalInterface is now ready for use
  1046. isReady[canvasId] = true;
  1047. // Call the onload event handler
  1048. if (typeof canvas.onload === "function") {
  1049. window.setTimeout(function() {
  1050. canvas.onload();
  1051. }, 0);
  1052. }
  1053. }
  1054. // If callback functions were defined
  1055. else if (_callback = callbacks[canvasId][url]) {
  1056. image = _callback[0];
  1057. callback = _callback[1 + error];
  1058. delete callbacks[canvasId][url];
  1059. // Call the onload or onerror callback function.
  1060. if (typeof callback === "function") {
  1061. callback.call(image);
  1062. }
  1063. }
  1064. if (lock[canvasId]) {
  1065. --lock[canvasId];
  1066. }
  1067. } catch (ex) {
  1068. // .unlock is called from within try catch inside flash. We never see errors if we don't
  1069. // capture and display them.
  1070. console.log("Call to FlashCanvas.unlock had thrown an error: ", ex.message)
  1071. throw ex
  1072. }
  1073. }
  1074. /*
  1075. * Utility methods
  1076. */
  1077. // Get a unique ID composed of alphanumeric characters.
  1078. function getUniqueId() {
  1079. return Math.random().toString(36).slice(2) || "0";
  1080. }
  1081. // Escape characters not permitted in XML.
  1082. function encodeXML(str) {
  1083. return ("" + str).replace(/&/g, "&amp;").replace(/</g, "&lt;");
  1084. }
  1085. function throwException(code) {
  1086. throw new DOMException(code);
  1087. }
  1088. // The width and height attributes of a canvas element must have values that
  1089. // are valid non-negative integers.
  1090. function setCanvasSize(canvas) {
  1091. var width = parseInt(canvas.width, 10);
  1092. var height = parseInt(canvas.height, 10);
  1093. if (isNaN(width) || width < 0) {
  1094. width = 300;
  1095. }
  1096. if (isNaN(height) || height < 0) {
  1097. height = 150;
  1098. }
  1099. canvas.width = width;
  1100. canvas.height = height;
  1101. }
  1102. /*
  1103. * initialization
  1104. */
  1105. FlashCanvas.initWindow(window, document)
  1106. // Prevent Closure Compiler from removing the function.
  1107. keep = [
  1108. CanvasRenderingContext2D.measureText,
  1109. CanvasRenderingContext2D.loadImage
  1110. ];
  1111. }).call(window);
  1112. }