PageRenderTime 74ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/src/com/googlecode/flashcanvas/CanvasRenderingContext2D.as

http://fxcanvas.googlecode.com/
ActionScript | 1270 lines | 994 code | 146 blank | 130 comment | 113 complexity | 2be112b718c3fdcb3824bff89b9258c0 MD5 | raw file
  1. /*
  2. * FlashCanvas
  3. *
  4. * Copyright (c) 2009 Shinya Muramatsu
  5. * Licensed under the MIT License.
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining a
  8. * copy of this software and associated documentation files (the "Software"),
  9. * to deal in the Software without restriction, including without limitation
  10. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  11. * and/or sell copies of the Software, and to permit persons to whom the
  12. * Software is furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in
  15. * all copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  20. * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  23. * DEALINGS IN THE SOFTWARE.
  24. *
  25. * @author Colin (developed original ASCanvas)
  26. * @author Tim Cameron Ryan (developed haXe version)
  27. * @author Shinya Muramatsu (flashcanvas)
  28. */
  29. // TODO stroke to path conversion
  30. // TODO Canvas composite operations 'source-over','source-in','source-out','source-atop',
  31. // 'destination-over','destination-in','destination-out','destination-atop',
  32. // 'copy','xor'
  33. package com.googlecode.flashcanvas
  34. {
  35. import flash.display.Bitmap;
  36. import flash.display.BitmapData;
  37. import flash.display.BlendMode;
  38. import flash.display.CapsStyle;
  39. import flash.display.Graphics;
  40. import flash.display.InterpolationMethod;
  41. import flash.display.JointStyle;
  42. import flash.display.LineScaleMode;
  43. import flash.display.Shape;
  44. import flash.display.Sprite;
  45. import flash.display.SpreadMethod;
  46. import flash.events.Event;
  47. import flash.geom.ColorTransform;
  48. import flash.geom.Matrix;
  49. import flash.geom.Point;
  50. import flash.geom.Rectangle;
  51. import flash.text.TextFormat;
  52. import flash.text.TextField;
  53. import flash.text.TextFieldAutoSize;
  54. import flash.text.TextFormat;
  55. import flash.text.TextLineMetrics;
  56. public class CanvasRenderingContext2D
  57. {
  58. // back-reference to the canvas
  59. public var _canvas:Canvas;
  60. // vector shape
  61. private var shape:Shape;
  62. // clipping region
  63. private var clippingMask:Shape;
  64. // current path
  65. public var path:Path;
  66. // first point of the current subpath
  67. private var startingPoint:Point;
  68. // last point of the current subpath
  69. private var currentPoint:Point;
  70. // stack of drawing states
  71. private var stateStack:Array = [];
  72. // drawing state
  73. public var state:State;
  74. public function CanvasRenderingContext2D(canvas:Canvas)
  75. {
  76. _canvas = canvas;
  77. shape = new Shape();
  78. clippingMask = new Shape();
  79. shape.mask = clippingMask;
  80. path = new Path();
  81. startingPoint = new Point();
  82. currentPoint = new Point();
  83. state = new State();
  84. }
  85. public function resize(width:int, height:int):void
  86. {
  87. // initialize bitmapdata
  88. _canvas.resize(width, height);
  89. // initialize drawing states
  90. stateStack = [];
  91. state = new State();
  92. // draw initial clipping region
  93. beginPath();
  94. rect(0, 0, width, height);
  95. clip();
  96. // clear the current path
  97. beginPath();
  98. }
  99. /*
  100. * back-reference to the canvas
  101. */
  102. public function get canvas():Canvas
  103. {
  104. return _canvas;
  105. }
  106. /*
  107. * state
  108. */
  109. public function save():void
  110. {
  111. stateStack.push(state.clone());
  112. }
  113. public function restore():void
  114. {
  115. if (stateStack.length == 0)
  116. return;
  117. state = stateStack.pop();
  118. // redraw clipping image
  119. var graphics:Graphics = clippingMask.graphics;
  120. graphics.clear();
  121. graphics.beginFill(0x000000);
  122. state.clippingPath.draw(graphics);
  123. graphics.endFill();
  124. }
  125. /*
  126. * transformations
  127. */
  128. public function scale(sx:Number, sy:Number):void
  129. {
  130. var matrix:Matrix = state.transformMatrix.clone();
  131. state.transformMatrix.identity();
  132. state.transformMatrix.scale(sx, sy);
  133. state.transformMatrix.concat(matrix);
  134. state.lineScale *= Math.sqrt(Math.abs(sx * sy));
  135. }
  136. public function rotate(angle:Number):void
  137. {
  138. var matrix:Matrix = state.transformMatrix.clone();
  139. state.transformMatrix.identity();
  140. state.transformMatrix.rotate(angle);
  141. state.transformMatrix.concat(matrix);
  142. }
  143. public function translate(tx:Number, ty:Number):void
  144. {
  145. var matrix:Matrix = state.transformMatrix.clone();
  146. state.transformMatrix.identity();
  147. state.transformMatrix.translate(tx, ty);
  148. state.transformMatrix.concat(matrix);
  149. }
  150. public function transform(m11:Number, m12:Number, m21:Number, m22:Number, dx:Number, dy:Number):void
  151. {
  152. var matrix:Matrix = state.transformMatrix.clone();
  153. state.transformMatrix = new Matrix(m11, m12, m21, m22, dx, dy);
  154. state.transformMatrix.concat(matrix);
  155. state.lineScale *= Math.sqrt(Math.abs(m11 * m22 - m12 * m21));
  156. }
  157. public function setTransform(... args):void
  158. {
  159. var m11:Number, m12:Number, m21:Number, m22:Number, dx:Number, dy:Number;
  160. if (args.length > 1) {
  161. m11 = args[0];
  162. m12 = args[1];
  163. m21 = args[2];
  164. m22 = args[3];
  165. dx = args[4];
  166. dy = args[5];
  167. } else {
  168. m11 = args[0].a;
  169. m12 = args[0].b;
  170. m21 = args[0].c;
  171. m22 = args[0].d;
  172. dx = args[0].tx;
  173. dy = args[0].tx;
  174. }
  175. state.transformMatrix = new Matrix(m11, m12, m21, m22, dx, dy);
  176. state.lineScale = Math.sqrt(Math.abs(m11 * m22 - m12 * m21));
  177. }
  178. /*
  179. * compositing
  180. */
  181. public function get globalAlpha():Number
  182. {
  183. return state.globalAlpha;
  184. }
  185. public function set globalAlpha(value:Number):void
  186. {
  187. state.globalAlpha = value;
  188. }
  189. public function get globalCompositeOperation():String
  190. {
  191. return state.globalCompositeOperation;
  192. }
  193. public function set globalCompositeOperation(value:String):void
  194. {
  195. state.globalCompositeOperation = value;
  196. }
  197. /*
  198. * colors and styles
  199. */
  200. public function get strokeStyle():*
  201. {
  202. if (state.strokeStyle is CSSColor)
  203. return state.strokeStyle.toString();
  204. else
  205. return state.strokeStyle;
  206. }
  207. public function set strokeStyle(value:*):void
  208. {
  209. if (value is String)
  210. state.strokeStyle = new CSSColor(value);
  211. else if (value is CanvasGradient || value is CanvasPattern)
  212. state.strokeStyle = value;
  213. }
  214. public function get fillStyle():*
  215. {
  216. if (state.fillStyle is CSSColor)
  217. return state.fillStyle.toString();
  218. else
  219. return state.fillStyle;
  220. }
  221. public function set fillStyle(value:*):void
  222. {
  223. if (value is String)
  224. state.fillStyle = new CSSColor(value);
  225. else if (value is CanvasGradient || value is CanvasPattern)
  226. state.fillStyle = value;
  227. }
  228. public function createLinearGradient(x0:Number, y0:Number, x1:Number, y1:Number):LinearGradient
  229. {
  230. return new LinearGradient(x0, y0, x1, y1);
  231. }
  232. public function createRadialGradient(x0:Number, y0:Number, r0:Number, x1:Number, y1:Number, r1:Number):RadialGradient
  233. {
  234. return new RadialGradient(x0, y0, r0, x1, y1, r1);
  235. }
  236. public function createPattern(image:*, repetition:String):CanvasPattern
  237. {
  238. return new CanvasPattern(image, repetition, this);
  239. }
  240. /*
  241. * line caps/joins
  242. */
  243. public function get lineWidth():Number
  244. {
  245. return state.lineWidth;
  246. }
  247. public function set lineWidth(value:Number):void
  248. {
  249. state.lineWidth = value;
  250. }
  251. public function get lineCap():String
  252. {
  253. if (state.lineCap == CapsStyle.NONE)
  254. return "butt";
  255. else if (state.lineCap == CapsStyle.ROUND)
  256. return "round";
  257. else
  258. return "square";
  259. }
  260. public function set lineCap(value:String):void
  261. {
  262. value = value.toLowerCase();
  263. if (value == "butt")
  264. state.lineCap = CapsStyle.NONE;
  265. else if (value == "round")
  266. state.lineCap = CapsStyle.ROUND;
  267. else if (value == "square")
  268. state.lineCap = CapsStyle.SQUARE;
  269. }
  270. public function get lineJoin():String
  271. {
  272. if (state.lineJoin == JointStyle.BEVEL)
  273. return "bevel";
  274. else if (state.lineJoin == JointStyle.ROUND)
  275. return "round";
  276. else
  277. return "miter";
  278. }
  279. public function set lineJoin(value:String):void
  280. {
  281. value = value.toLowerCase();
  282. if (value == "bevel")
  283. state.lineJoin = JointStyle.BEVEL;
  284. else if (value == "round")
  285. state.lineJoin = JointStyle.ROUND;
  286. else if (value == "miter")
  287. state.lineJoin = JointStyle.MITER;
  288. }
  289. public function get miterLimit():Number
  290. {
  291. return state.miterLimit;
  292. }
  293. public function set miterLimit(value:Number):void
  294. {
  295. state.miterLimit = value;
  296. }
  297. /*
  298. * shadows
  299. */
  300. public function get shadowOffsetX():Number
  301. {
  302. return state.shadowOffsetX;
  303. }
  304. public function set shadowOffsetX(value:Number):void
  305. {
  306. state.shadowOffsetX = value;
  307. }
  308. public function get shadowOffsetY():Number
  309. {
  310. return state.shadowOffsetY;
  311. }
  312. public function set shadowOffsetY(value:Number):void
  313. {
  314. state.shadowOffsetY = value;
  315. }
  316. public function get shadowBlur():Number
  317. {
  318. return state.shadowBlur;
  319. }
  320. public function set shadowBlur(value:Number):void
  321. {
  322. state.shadowBlur = value;
  323. }
  324. public function get shadowColor():String
  325. {
  326. return state.shadowColor.toString();
  327. }
  328. public function set shadowColor(value:String):void
  329. {
  330. state.shadowColor = new CSSColor(value);
  331. }
  332. /*
  333. * rects
  334. */
  335. public function clearRect(x:Number, y:Number, w:Number, h:Number):void
  336. {
  337. if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h))
  338. return;
  339. var graphics:Graphics = shape.graphics;
  340. graphics.beginFill(0x000000);
  341. graphics.drawRect(x, y, w, h);
  342. graphics.endFill();
  343. _canvas.bitmapData.draw(shape, state.transformMatrix, null, BlendMode.ERASE);
  344. graphics.clear();
  345. }
  346. public function fillRect(x:Number, y:Number, w:Number, h:Number):void
  347. {
  348. if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h))
  349. return;
  350. var p1:Point = _getTransformedPoint(x, y);
  351. var p2:Point = _getTransformedPoint(x + w, y);
  352. var p3:Point = _getTransformedPoint(x + w, y + h);
  353. var p4:Point = _getTransformedPoint(x, y + h);
  354. var graphics:Graphics = shape.graphics;
  355. _setFillStyle(graphics);
  356. graphics.moveTo(p1.x, p1.y);
  357. graphics.lineTo(p2.x, p2.y);
  358. graphics.lineTo(p3.x, p3.y);
  359. graphics.lineTo(p4.x, p4.y);
  360. graphics.lineTo(p1.x, p1.y);
  361. graphics.endFill();
  362. _renderShape();
  363. }
  364. public function strokeRect(x:Number, y:Number, w:Number, h:Number):void
  365. {
  366. if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h))
  367. return;
  368. var p1:Point = _getTransformedPoint(x, y);
  369. var p2:Point = _getTransformedPoint(x + w, y);
  370. var p3:Point = _getTransformedPoint(x + w, y + h);
  371. var p4:Point = _getTransformedPoint(x, y + h);
  372. var graphics:Graphics = shape.graphics;
  373. _setStrokeStyle(graphics);
  374. graphics.moveTo(p1.x, p1.y);
  375. graphics.lineTo(p2.x, p2.y);
  376. graphics.lineTo(p3.x, p3.y);
  377. graphics.lineTo(p4.x, p4.y);
  378. graphics.lineTo(p1.x, p1.y);
  379. _renderShape();
  380. }
  381. /*
  382. * path API
  383. */
  384. public function beginPath():void
  385. {
  386. path.initialize();
  387. }
  388. public function closePath():void
  389. {
  390. if (path.commands.length == 0)
  391. return;
  392. path.commands.push(GraphicsPathCommand.LINE_TO);
  393. path.data.push(startingPoint.x, startingPoint.y);
  394. currentPoint.x = startingPoint.x;
  395. currentPoint.y = startingPoint.y;
  396. }
  397. public function moveTo(x:Number, y:Number):void
  398. {
  399. if (!isFinite(x) || !isFinite(y))
  400. return;
  401. var p:Point = _getTransformedPoint(x, y);
  402. path.commands.push(GraphicsPathCommand.MOVE_TO);
  403. path.data.push(p.x, p.y);
  404. startingPoint.x = currentPoint.x = p.x;
  405. startingPoint.y = currentPoint.y = p.y;
  406. }
  407. public function lineTo(x:Number, y:Number):void
  408. {
  409. if (!isFinite(x) || !isFinite(y))
  410. return;
  411. // check that path contains subpaths
  412. if (path.commands.length == 0)
  413. moveTo(x, y);
  414. var p:Point = _getTransformedPoint(x, y);
  415. path.commands.push(GraphicsPathCommand.LINE_TO);
  416. path.data.push(p.x, p.y);
  417. currentPoint.x = p.x;
  418. currentPoint.y = p.y;
  419. }
  420. public function quadraticCurveTo(cpx:Number, cpy:Number, x:Number, y:Number):void
  421. {
  422. if (!isFinite(cpx) || !isFinite(cpy) || !isFinite(x) || !isFinite(y))
  423. return;
  424. // check that path contains subpaths
  425. if (path.commands.length == 0)
  426. moveTo(cpx, cpy);
  427. var cp:Point = _getTransformedPoint(cpx, cpy);
  428. var p:Point = _getTransformedPoint(x, y);
  429. path.commands.push(GraphicsPathCommand.CURVE_TO);
  430. path.data.push(cp.x, cp.y, p.x, p.y);
  431. currentPoint.x = p.x;
  432. currentPoint.y = p.y;
  433. }
  434. /*
  435. * Cubic bezier curve is approximated by four quadratic bezier curves.
  436. * The approximation uses Fixed MidPoint algorithm by Timothee Groleau.
  437. *
  438. * @see http://www.timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm
  439. */
  440. public function bezierCurveTo(cp1x:Number, cp1y:Number, cp2x:Number, cp2y:Number, x:Number, y:Number):void
  441. {
  442. if (!isFinite(cp1x) || !isFinite(cp1y) || !isFinite(cp2x) || !isFinite(cp2y) || !isFinite(x) || !isFinite(y))
  443. return;
  444. // check that path contains subpaths
  445. if (path.commands.length == 0)
  446. moveTo(cp1x, cp1y);
  447. var p0:Point = currentPoint;
  448. var p1:Point = _getTransformedPoint(cp1x, cp1y);
  449. var p2:Point = _getTransformedPoint(cp2x, cp2y);
  450. var p3:Point = _getTransformedPoint(x, y);
  451. // calculate base points
  452. var bp1:Point = Point.interpolate(p0, p1, 0.25);
  453. var bp2:Point = Point.interpolate(p3, p2, 0.25);
  454. // get 1/16 of the [p3, p0] segment
  455. var dx:Number = (p3.x - p0.x) / 16;
  456. var dy:Number = (p3.y - p0.y) / 16;
  457. // calculate control points
  458. var cp1:Point = Point.interpolate( p1, p0, 0.375);
  459. var cp2:Point = Point.interpolate(bp2, bp1, 0.375);
  460. var cp3:Point = Point.interpolate(bp1, bp2, 0.375);
  461. var cp4:Point = Point.interpolate( p2, p3, 0.375);
  462. cp2.x -= dx;
  463. cp2.y -= dy;
  464. cp3.x += dx;
  465. cp3.y += dy;
  466. // calculate anchor points
  467. var ap1:Point = Point.interpolate(cp1, cp2, 0.5);
  468. var ap2:Point = Point.interpolate(bp1, bp2, 0.5);
  469. var ap3:Point = Point.interpolate(cp3, cp4, 0.5);
  470. // four quadratic subsegments
  471. path.commands.push(
  472. GraphicsPathCommand.CURVE_TO,
  473. GraphicsPathCommand.CURVE_TO,
  474. GraphicsPathCommand.CURVE_TO,
  475. GraphicsPathCommand.CURVE_TO
  476. );
  477. path.data.push(
  478. cp1.x, cp1.y, ap1.x, ap1.y,
  479. cp2.x, cp2.y, ap2.x, ap2.y,
  480. cp3.x, cp3.y, ap3.x, ap3.y,
  481. cp4.x, cp4.y, p3.x, p3.y
  482. );
  483. currentPoint.x = p3.x;
  484. currentPoint.y = p3.y;
  485. }
  486. /*
  487. * arcTo() is decomposed into lineTo() and arc().
  488. *
  489. * @see http://d.hatena.ne.jp/mindcat/20100131/1264958828
  490. */
  491. public function arcTo(x1:Number, y1:Number, x2:Number, y2:Number, radius:Number):void
  492. {
  493. if (!isFinite(x1) || !isFinite(y1) || !isFinite(x2) || !isFinite(y2) || !isFinite(radius))
  494. return;
  495. // check that path contains subpaths
  496. if (path.commands.length == 0)
  497. moveTo(x1, y1);
  498. var p0:Point = _getUntransformedPoint(currentPoint.x, currentPoint.y);
  499. var a1:Number = p0.y - y1;
  500. var b1:Number = p0.x - x1;
  501. var a2:Number = y2 - y1;
  502. var b2:Number = x2 - x1;
  503. var mm:Number = Math.abs(a1 * b2 - b1 * a2);
  504. if (mm < 1.0e-8 || radius === 0)
  505. {
  506. lineTo(x1, y1);
  507. }
  508. else
  509. {
  510. var dd:Number = a1 * a1 + b1 * b1;
  511. var cc:Number = a2 * a2 + b2 * b2;
  512. var tt:Number = a1 * a2 + b1 * b2;
  513. var k1:Number = radius * Math.sqrt(dd) / mm;
  514. var k2:Number = radius * Math.sqrt(cc) / mm;
  515. var j1:Number = k1 * tt / dd;
  516. var j2:Number = k2 * tt / cc;
  517. var cx:Number = k1 * b2 + k2 * b1;
  518. var cy:Number = k1 * a2 + k2 * a1;
  519. var px:Number = b1 * (k2 + j1);
  520. var py:Number = a1 * (k2 + j1);
  521. var qx:Number = b2 * (k1 + j2);
  522. var qy:Number = a2 * (k1 + j2);
  523. var startAngle:Number = Math.atan2(py - cy, px - cx);
  524. var endAngle:Number = Math.atan2(qy - cy, qx - cx);
  525. lineTo(px + x1, py + y1);
  526. arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1);
  527. }
  528. }
  529. /*
  530. * Arc is approximated with bezier curves. It uses four segments per circle.
  531. */
  532. public function arc(cx:Number, cy:Number, radius:Number,
  533. startAngle:Number, endAngle:Number,
  534. anticlockwise:Boolean = false):void
  535. {
  536. if (!isFinite(cx) || !isFinite(cy) || !isFinite(radius) ||
  537. !isFinite(startAngle) || !isFinite(endAngle))
  538. return;
  539. var startX:Number = cx + radius * Math.cos(startAngle);
  540. var startY:Number = cy + radius * Math.sin(startAngle);
  541. // check that path contains subpaths
  542. if (path.commands.length == 0)
  543. moveTo(startX, startY);
  544. else
  545. lineTo(startX, startY);
  546. if (startAngle == endAngle)
  547. return;
  548. // note: with more segments, lines will not be smoother, it's
  549. // due to flash rendering feature (bug?)
  550. var segs:int = 4
  551. var sweep:Number = endAngle - startAngle
  552. var PI2:Number = Math.PI * 2;
  553. // fixme Is it possible to avoid while?
  554. // Why modulo is not working here in the same time?
  555. if (anticlockwise)
  556. {
  557. if (sweep <= -PI2)
  558. sweep = PI2;
  559. else while (sweep >= 0)
  560. sweep -= PI2;
  561. }
  562. else
  563. {
  564. if (sweep >= PI2)
  565. sweep = PI2;
  566. else while (sweep <= 0)
  567. sweep += PI2;
  568. }
  569. if( sweep == 0 )
  570. return
  571. var theta:Number = sweep/(segs*2);
  572. var theta2:Number = theta*2
  573. // rotate segments from start angle
  574. var rot:Number = startAngle
  575. for(var i:int=0; i<segs; i++) {
  576. drawArcSegment(cx, cy, radius, theta, rot);
  577. rot = rot + theta2;
  578. }
  579. }
  580. // Research paper:
  581. // How to determine the control points of a B?Šzier curve that approximates a small circular arc
  582. // Richard A DeVeneza, Nov 2004
  583. // http://www.tinaja.com/glib/bezcirc2.pdf
  584. //
  585. public function drawArcSegment(cx:Number, cy:Number, r:Number,
  586. theta:Number, phi:Number):void
  587. {
  588. var x0:Number = Math.cos(theta);
  589. var y0:Number = Math.sin(theta);
  590. //var x3:Number = x0;
  591. //var y3:Number = -y0;
  592. var x1:Number = (4-x0)/3;
  593. var y1:Number = ((1-x0)*(3-x0))/(3*y0);
  594. var x2:Number = x1;
  595. var y2:Number = -y1;
  596. // rotate arc segment at phi and make it fixed on left side
  597. phi = ((theta) + phi);
  598. var c:Number = Math.cos(phi);
  599. var s:Number = -Math.sin(phi);
  600. // rotate empty matrix
  601. // m11 = 1, m12 = 0, m21 = 0, m22 = 1
  602. // m11 = m11*c + m21*s = c
  603. // m21 = m21*c + m22*s = s
  604. // m12 = m11*-s + m21*c = -s
  605. // m22 = m21*-s + m22*c = c
  606. //
  607. // mupltiply point
  608. // x = x*m11 + y*m21 = x*c + y*s
  609. // y = x*m12 + y*m22 = x*-s + y*c
  610. //
  611. // using it for scaling, translating and rotating
  612. var x0_:Number = ((x0*c) + (y0*s))*r + cx;
  613. y0 = ((x0*-s) + (y0*c))*r + cy;
  614. x0 = x0_;
  615. var x1_:Number = ((x1*c) + (y1*s))*r + cx;
  616. y1 = ((x1*-s) + (y1*c))*r + cy ;
  617. x1 = x1_;
  618. var x2_:Number = ((x2*c) + (y2*s))*r + cx;
  619. y2 = ((x2*-s) + (y2*c))*r + cy ;
  620. x2 = x2_;
  621. //var x3_:Number = ((x3*c) + (y3*s))*r + cx;
  622. //y3 = ((x3*-s) + (y3*c))*r + cy ;
  623. //x3 = x3_;
  624. bezierCurveTo(x2,y2,x1,y1,x0,y0)
  625. }
  626. public function rect(x:Number, y:Number, w:Number, h:Number):void
  627. {
  628. if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h))
  629. return;
  630. var p1:Point = _getTransformedPoint(x, y);
  631. var p2:Point = _getTransformedPoint(x + w, y);
  632. var p3:Point = _getTransformedPoint(x + w, y + h);
  633. var p4:Point = _getTransformedPoint(x, y + h);
  634. path.commands.push(
  635. GraphicsPathCommand.MOVE_TO,
  636. GraphicsPathCommand.LINE_TO,
  637. GraphicsPathCommand.LINE_TO,
  638. GraphicsPathCommand.LINE_TO,
  639. GraphicsPathCommand.LINE_TO
  640. );
  641. path.data.push(
  642. p1.x, p1.y,
  643. p2.x, p2.y,
  644. p3.x, p3.y,
  645. p4.x, p4.y,
  646. p1.x, p1.y
  647. );
  648. startingPoint.x = currentPoint.x = p1.x;
  649. startingPoint.y = currentPoint.y = p1.y;
  650. }
  651. public function fill():void
  652. {
  653. var graphics:Graphics = shape.graphics;
  654. _setFillStyle(graphics);
  655. path.draw(graphics);
  656. graphics.endFill();
  657. _renderShape();
  658. }
  659. public function stroke():void
  660. {
  661. var graphics:Graphics = shape.graphics;
  662. _setStrokeStyle(graphics);
  663. path.draw(graphics);
  664. _renderShape();
  665. }
  666. public function clip():void
  667. {
  668. // extract path
  669. state.clippingPath = path.clone();
  670. // draw clip path
  671. var graphics:Graphics = clippingMask.graphics;
  672. graphics.clear();
  673. graphics.beginFill(0x000000);
  674. path.draw(graphics);
  675. graphics.endFill();
  676. }
  677. public function isPointInPath(x:Number, y:Number):Boolean
  678. {
  679. return false
  680. }
  681. /*
  682. * text
  683. */
  684. public function get font():*
  685. {
  686. return state.font;
  687. }
  688. public function set font(value:String):void
  689. {
  690. state.font = value;
  691. }
  692. // fixme font style regexp
  693. private function _parseFont():TextFormat
  694. {
  695. var format:TextFormat = new TextFormat;
  696. var fontData:Array = state.font.replace(/\s+/g, " ").split(" ");
  697. var italic:Boolean = fontData[0] == "italic",
  698. bold:Boolean = false,
  699. size:Number = 15,
  700. font:String = "sans-serif";
  701. if(fontData.length == 4) {
  702. var weight:Number = parseInt(fontData[1]);
  703. bold = (!isNaN(weight) && weight > 400 || fontData[1] == "bold");
  704. }
  705. size = parseFloat(fontData[fontData.length == 2 ? 0 : 2])
  706. font = fontData.slice(fontData.length == 2 ? 1 : 3)
  707. .join(" ").replace(/["']/g, "");
  708. format.italic = italic;
  709. format.size = size;
  710. if(font == "sans" || font == "sans-serif") // fix for compatibility
  711. format.font = "_sans"
  712. else if(font == "serif")
  713. format.font = "_serif"
  714. else
  715. format.font = font
  716. format.bold = bold
  717. return format;
  718. }
  719. public function get textAlign():*
  720. {
  721. return state.textAlign;
  722. }
  723. public function set textAlign(value:String):void
  724. {
  725. value = value.toLowerCase();
  726. switch(value) {
  727. case "start":
  728. case "end":
  729. case "left":
  730. case "right":
  731. case "center":
  732. state.textAlign = value;
  733. }
  734. }
  735. public function get textBaseline():*
  736. {
  737. return state.textBaseline;
  738. }
  739. public function set textBaseline(value:String):void
  740. {
  741. value = value.toLowerCase();
  742. switch(value) {
  743. case "top":
  744. case "hanging":
  745. case "middle":
  746. case "alphabetic":
  747. case "ideographic":
  748. case "bottom":
  749. state.textBaseline = value;
  750. }
  751. }
  752. public function fillText(text:String, x:Number, y:Number, maxWidth:Number = Infinity):void
  753. {
  754. _renderText(text, x, y, maxWidth);
  755. }
  756. public function strokeText(text:String, x:Number, y:Number, maxWidth:Number = Infinity):void
  757. {
  758. _renderText(text, x, y, maxWidth, true);
  759. }
  760. public function measureText(text:String):TextLineMetrics
  761. {
  762. // parse font style
  763. var textFormat:TextFormat = _parseFont();
  764. // Create TextField object
  765. var textField:TextField = new TextField();
  766. textField.autoSize = TextFieldAutoSize.LEFT;
  767. textField.defaultTextFormat = textFormat;
  768. textField.text = text.replace(/[\t\n\f\r]/g, " ");
  769. var metrics:TextLineMetrics = textField.getLineMetrics(0);
  770. return metrics;
  771. }
  772. private function _renderText(text:String, x:Number, y:Number, maxWidth:Number, isStroke:Boolean = false):void
  773. {
  774. if (/^\s*$/.test(text))
  775. return;
  776. if (!isFinite(x) || !isFinite(y) || isNaN(maxWidth))
  777. return;
  778. // If maxWidth is less than or equal to zero, return without doing
  779. // anything.
  780. if (maxWidth <= 0)
  781. return;
  782. var textFormat:TextFormat = _parseFont();
  783. var style:Object = isStroke ? state.strokeStyle : state.fillStyle;
  784. // Set text color
  785. if (style is CSSColor)
  786. textFormat.color = style.color;
  787. // Create TextField object
  788. var textField:TextField = new TextField();
  789. textField.autoSize = TextFieldAutoSize.LEFT;
  790. textField.defaultTextFormat = textFormat;
  791. textField.text = text.replace(/[\t\n\f\r]/g, " ");
  792. // Get the size of the text
  793. var width:int = textField.textWidth;
  794. var height:int = textField.textHeight;
  795. var ascent:int = textField.getLineMetrics(0).ascent;
  796. // Remove 2px margins around the text
  797. var matrix:Matrix = new Matrix();
  798. matrix.translate(-2, -2);
  799. // Convert the text into BitmapData
  800. var bitmapData:BitmapData = new BitmapData(width, height, true, 0);
  801. bitmapData.draw(textField, matrix);
  802. // Adjust x coordinates
  803. switch (state.textAlign)
  804. {
  805. case "start": break;
  806. case "end": x -= width; break;
  807. case "left": break;
  808. case "right": x -= width; break;
  809. case "center": x -= width / 2; break;
  810. }
  811. // Adjust y coordinates
  812. switch (state.textBaseline)
  813. {
  814. case "top":
  815. case "hanging": break;
  816. case "middle": y -= height / 2; break;
  817. case "alphabetic":
  818. case "ideographic": y -= ascent; break;
  819. case "bottom": y -= height; break;
  820. }
  821. // Create transformation matrix
  822. matrix = new Matrix();
  823. matrix.translate(x, y);
  824. matrix.concat(state.transformMatrix);
  825. // Calculate alpha multiplier
  826. var alpha:Number = state.globalAlpha;
  827. if (style is CSSColor)
  828. alpha *= style.alpha;
  829. var colorTransform:ColorTransform = null;
  830. if (alpha < 1)
  831. {
  832. // Make the BitmapData translucent
  833. colorTransform = new ColorTransform(1, 1, 1, alpha);
  834. }
  835. // draw image with applied clipping path
  836. var mask:Sprite = new Sprite()
  837. // draw clip path
  838. var graphics:Graphics = mask.graphics;
  839. graphics.clear();
  840. graphics.beginFill(0x000000);
  841. state.clippingPath.draw(graphics);
  842. graphics.endFill();
  843. var tempData:BitmapData = new BitmapData(canvas.width, canvas.height, true, 0)
  844. var mc:Bitmap = new Bitmap(tempData, "auto", true);
  845. mc.mask = mask
  846. mc.bitmapData.draw(bitmapData, matrix);
  847. matrix = new Matrix();
  848. matrix.identity()
  849. // Render the BitmapData to the Canvas
  850. _canvas.bitmapData.draw(mc, matrix, colorTransform, __blendMode(), null, true);
  851. // Release the memory
  852. tempData.dispose();
  853. bitmapData.dispose();
  854. }
  855. /*
  856. * drawing images
  857. */
  858. public function drawImage(bitmapData:BitmapData, ...args:Array):void
  859. {
  860. __drawImage(bitmapData.clone(), args, state)
  861. }
  862. /*
  863. * pixel manipulation
  864. */
  865. public function createImageData():*
  866. {
  867. // TODO: Implement
  868. }
  869. public function getImageData(sx:Number, sy:Number, sw:Number, sh:Number):*
  870. {
  871. // TODO: Implement
  872. }
  873. public function putImageData():void
  874. {
  875. // TODO: Implement
  876. }
  877. /*
  878. * private methods
  879. */
  880. private function _getTransformedPoint(x:Number, y:Number):Point
  881. {
  882. return state.transformMatrix.transformPoint(new Point(x, y));
  883. }
  884. private function _getUntransformedPoint(x:Number, y:Number):Point
  885. {
  886. var matrix:Matrix = state.transformMatrix.clone();
  887. matrix.invert();
  888. return matrix.transformPoint(new Point(x, y));
  889. }
  890. private function _setStrokeStyle(graphics:Graphics, pixelHinting:Boolean = true):void
  891. {
  892. var strokeStyle:Object = state.strokeStyle;
  893. var thickness:Number = state.lineWidth * state.lineScale;
  894. if (strokeStyle is CSSColor)
  895. {
  896. var color:uint = strokeStyle.color;
  897. var alpha:Number = strokeStyle.alpha * state.globalAlpha;
  898. if (thickness < 1)
  899. alpha *= thickness;
  900. graphics.lineStyle(thickness, color, alpha,
  901. pixelHinting, LineScaleMode.NORMAL,
  902. state.lineCap,
  903. state.lineJoin, state.miterLimit);
  904. }
  905. else if (strokeStyle is CanvasGradient)
  906. {
  907. var alphas:Array = strokeStyle.alphas;
  908. if (state.globalAlpha < 1)
  909. {
  910. for (var i:int = 0, n:int = alphas.length; i < n; i++)
  911. {
  912. alphas[i] *= state.globalAlpha;
  913. }
  914. }
  915. var matrix:Matrix = strokeStyle.matrix.clone();
  916. matrix.concat(state.transformMatrix);
  917. graphics.lineStyle(thickness);
  918. graphics.lineGradientStyle(strokeStyle.type, strokeStyle.colors, alphas, strokeStyle.ratios, matrix, SpreadMethod.PAD, InterpolationMethod.RGB, strokeStyle.focalPointRatio);
  919. }
  920. else if (strokeStyle is CanvasPattern)
  921. {
  922. // FIXME
  923. }
  924. }
  925. private function _setFillStyle(graphics:Graphics):void
  926. {
  927. // disable stroke
  928. graphics.lineStyle();
  929. var fillStyle:Object = state.fillStyle;
  930. if (fillStyle is CSSColor)
  931. {
  932. var color:uint = fillStyle.color;
  933. var alpha:Number = fillStyle.alpha * state.globalAlpha;
  934. graphics.beginFill(color, alpha);
  935. }
  936. else if (fillStyle is CanvasGradient)
  937. {
  938. var alphas:Array = fillStyle.alphas;
  939. if (state.globalAlpha < 1)
  940. {
  941. for (var i:int = 0, n:int = alphas.length; i < n; i++)
  942. {
  943. alphas[i] *= state.globalAlpha;
  944. }
  945. }
  946. var matrix:Matrix = fillStyle.matrix.clone();
  947. matrix.concat(state.transformMatrix);
  948. graphics.beginGradientFill(fillStyle.type, fillStyle.colors, alphas, fillStyle.ratios, matrix, SpreadMethod.PAD, InterpolationMethod.RGB, fillStyle.focalPointRatio);
  949. }
  950. else if (fillStyle is CanvasPattern)
  951. {
  952. _fillPattern(state);
  953. }
  954. }
  955. private function _fillPattern(state:Object):void
  956. {
  957. var fillStyle:Object = state.fillStyle;
  958. var graphics:Graphics = shape.graphics;
  959. // TODO: support repetition other than 'repeat'.
  960. graphics.beginBitmapFill(fillStyle.bitmapData, state.transformMatrix);
  961. }
  962. private function _renderShape():void
  963. {
  964. _canvas.bitmapData.draw(shape, null, null, __blendMode());
  965. shape.graphics.clear();
  966. }
  967. public function __drawImage(bitmapData:BitmapData, args:Array, _state:State):void
  968. {
  969. var source:BitmapData;
  970. var sx:Number;
  971. var sy:Number;
  972. var sw:Number;
  973. var sh:Number;
  974. var dx:Number;
  975. var dy:Number;
  976. var dw:Number;
  977. var dh:Number;
  978. if (args.length == 8)
  979. {
  980. // Define the source and destination rectangles
  981. sx = args[0];
  982. sy = args[1];
  983. sw = args[2];
  984. sh = args[3];
  985. dx = args[4];
  986. dy = args[5];
  987. dw = args[6];
  988. dh = args[7];
  989. // Clip the region within the source rectangle
  990. var sourceRect:Rectangle = new Rectangle(sx, sy, sw, sh);
  991. var destPoint:Point = new Point();
  992. source = new BitmapData(sw, sh, true, 0);
  993. source.copyPixels(bitmapData, sourceRect, destPoint);
  994. }
  995. else
  996. {
  997. // Get BitmapData of the image
  998. source = bitmapData;
  999. // Define the destination rectangle
  1000. dx = args[0];
  1001. dy = args[1];
  1002. dw = args[2] || source.width;
  1003. dh = args[3] || source.height;
  1004. }
  1005. // Create transformation matrix
  1006. var matrix:Matrix = new Matrix();
  1007. matrix.scale(dw / source.width, dh / source.height);
  1008. matrix.translate(dx, dy);
  1009. matrix.concat(_state.transformMatrix);
  1010. var colorTransform:ColorTransform = null;
  1011. if (state.globalAlpha < 1)
  1012. {
  1013. // Make the image translucent
  1014. colorTransform = new ColorTransform(1, 1, 1, state.globalAlpha);
  1015. }
  1016. // draw image with applied clipping path
  1017. var mask:Sprite = new Sprite()
  1018. // draw clip path
  1019. var graphics:Graphics = mask.graphics;
  1020. graphics.clear();
  1021. graphics.beginFill(0x000000);
  1022. state.clippingPath.draw(graphics);
  1023. graphics.endFill();
  1024. var tempData:BitmapData = new BitmapData(canvas.width, canvas.height, true, 0)
  1025. var mc:Bitmap = new Bitmap(tempData, "auto", true);
  1026. mc.mask = mask
  1027. mc.bitmapData.draw(source, matrix);
  1028. matrix = new Matrix()
  1029. matrix.identity()
  1030. // Draw the image on the Canvas
  1031. _canvas.bitmapData.draw(mc, matrix, colorTransform, __blendMode(), null, true);
  1032. // Release the memory
  1033. source.dispose();
  1034. mc.bitmapData.dispose();
  1035. bitmapData.dispose();
  1036. tempData.dispose();
  1037. }
  1038. // There must be some language similar to pixel shaders for
  1039. // pixel blending operations in Canvas...
  1040. //
  1041. public function __blendMode():String
  1042. {
  1043. switch (state.globalCompositeOperation) {
  1044. case "lighter":
  1045. return BlendMode.ADD;
  1046. case "source-over":
  1047. return BlendMode.NORMAL;
  1048. default:
  1049. throw new Error(state.globalCompositeOperation + " not implemented");
  1050. }
  1051. }
  1052. }
  1053. }