PageRenderTime 61ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/svgweb/tests/non-licensed/perf/test7/src/org/svgweb/SVGViewerWeb.as

https://github.com/dionjwa/Hydrax
ActionScript | 1115 lines | 757 code | 130 blank | 228 comment | 125 complexity | c07fd865adef2a110d8e6bd09dd59464 MD5 | raw file
  1. /*
  2. Copyright (c) 2009 by contributors:
  3. * James Hight (http://labs.zavoo.com/)
  4. * Richard R. Masters
  5. * Google Inc. (Brad Neuberg - http://codinginparadise.org)
  6. Licensed under the Apache License, Version 2.0 (the "License");
  7. you may not use this file except in compliance with the License.
  8. You may obtain a copy of the License at
  9. http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing, software
  11. distributed under the License is distributed on an "AS IS" BASIS,
  12. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. See the License for the specific language governing permissions and
  14. limitations under the License.
  15. */
  16. /*
  17. SVGViewer is a flash sprite which is the parent for a tree of SVGNodes
  18. which are sprites initialized from XML. The top most SVGNode is an SVGSVGNode.
  19. The xml is parsed and xml children are walked when the object is rendered.
  20. Child SVGNodes are added and when they are rendered, their xml is walked
  21. and so on.
  22. */
  23. package org.svgweb
  24. {
  25. import org.svgweb.core.SVGNode;
  26. import org.svgweb.core.SVGSprite;
  27. import org.svgweb.core.SVGViewer;
  28. import org.svgweb.nodes.SVGSVGNode;
  29. import org.svgweb.nodes.SVGGroupNode;
  30. import org.svgweb.nodes.SVGDOMTextNode;
  31. import org.svgweb.events.SVGEvent;
  32. import org.svgweb.utils.SVGUnits;
  33. import org.svgweb.utils.SVGColors;
  34. import flash.display.Sprite;
  35. import flash.display.StageScaleMode;
  36. import flash.display.StageAlign;
  37. import flash.display.LoaderInfo;
  38. import flash.events.Event;
  39. import flash.events.EventDispatcher;
  40. import flash.events.MouseEvent;
  41. import flash.events.IOErrorEvent;
  42. import flash.events.SecurityErrorEvent;
  43. import flash.events.ContextMenuEvent;
  44. import flash.external.ExternalInterface;
  45. import flash.geom.Matrix;
  46. import flash.geom.Point;
  47. import flash.net.URLLoader;
  48. import flash.net.URLRequest;
  49. import flash.text.TextField;
  50. import flash.ui.ContextMenu;
  51. import flash.ui.ContextMenuItem;
  52. import flash.utils.setTimeout;
  53. [SWF(frameRate="40", width="2048", height="1024")]
  54. /**
  55. * Web container for the SVG Renderer
  56. **/
  57. public class SVGViewerWeb extends SVGViewer
  58. {
  59. private var js_handler:String = '';
  60. private var js_uniqueId:String = '';
  61. private var js_guidLookup:Object = {};
  62. protected var svgIdParam:String = "";
  63. public var clipModeParam:String = "useNone";
  64. protected var scriptSentToJS:Boolean = false;
  65. protected var xmlString:String;
  66. protected var renderStartTime:Number;
  67. protected var debugEnabled:Boolean = true;
  68. protected var objectWidth:Number;
  69. protected var objectHeight:Number;
  70. // The delimiter we use when passing arguments between Flash and
  71. // JavaScript; performance testing showed this to be an important
  72. // bottleneck, so we do various tricks to make it fast
  73. protected var DELIMITER:String = '__SVG__DELIMIT';
  74. // The delimiter between each method that we have bunched up for later
  75. // execution during a suspendRedraw session; used for unsuspendRedrawAll
  76. protected var METHOD_DELIMITER:String = '__SVG__METHOD__DELIMIT';
  77. // If we send over a DocumentFragment, we represent these internally
  78. // using the tag <__document__fragment> followed by all of the nested
  79. // DocumentFragment children
  80. protected var DOCUMENT_FRAGMENT:String = '__document__fragment';
  81. public function SVGViewerWeb():void {
  82. this.setupJavaScriptInterface();
  83. //this.debug('SVGViewerWeb constructor');
  84. this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  85. stage.addEventListener(Event.RESIZE, handleResize);
  86. super();
  87. }
  88. protected function onAddedToStage(event:Event = null):void {
  89. this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
  90. this.stage.align = StageAlign.TOP_LEFT;
  91. this.stage.scaleMode = StageScaleMode.NO_SCALE;
  92. this.processHTMLParameters();
  93. }
  94. /**
  95. * process html <object> parameters
  96. **/
  97. protected function processHTMLParameters():void {
  98. var paramsObj:Object = LoaderInfo(this.root.loaderInfo).parameters;
  99. var svgURLParam:String = "";
  100. var sourceTypeParam:String = "";
  101. var item:String;
  102. for (item in paramsObj) {
  103. if (item == "clipMode") {
  104. this.clipModeParam = paramsObj[item];
  105. }
  106. if (item == "sourceType") {
  107. sourceTypeParam = paramsObj[item];
  108. }
  109. if (item == "svgURL") {
  110. svgURLParam = paramsObj[item];
  111. }
  112. if (item == "svgId") {
  113. this.svgIdParam = paramsObj[item];
  114. }
  115. }
  116. // notify browser javascript that we are loaded
  117. try {
  118. var result:Object = ExternalInterface.call(
  119. this.js_handler + "onMessage",
  120. this.msgToString(
  121. { type: 'event',
  122. eventType: 'onFlashLoaded',
  123. uniqueId: this.js_uniqueId
  124. }
  125. ));
  126. }
  127. catch(error:SecurityError) {
  128. var debugstr:String = "Security Error on ExternalInterface.call(...). ";
  129. if (this.root.loaderInfo.loaderURL.substring(0,4) == "file") {
  130. debugstr += "This is expected when loaded from a local file.";
  131. }
  132. this.debug(debugstr);
  133. }
  134. }
  135. /**
  136. * Load methods.
  137. **/
  138. protected function setSVGString(xmlStringParam:String, objectURL:String = '', pageURL:String = '',
  139. ignoreWhiteSpace:Boolean = false):void {
  140. //start('setSVGString');
  141. // FIXME: TODO: Respect the ignoreWhiteSpace setting
  142. this.xmlString = SVGViewerWeb.expandEntities(xmlStringParam);
  143. var dataXML:XML = new XML(this.xmlString);
  144. while(this.numChildren) {
  145. this.removeChildAt(0);
  146. }
  147. svgRoot = new SVGSVGNode(null, dataXML, null, this, objectURL, pageURL);
  148. // See comment in handleRootSVGLoad()
  149. if ( (this.xmlString.indexOf("<animate") != -1)
  150. || (this.xmlString.indexOf("<set") != -1) ) {
  151. this.visible = false;
  152. }
  153. this.addActionListener(SVGEvent.SVGLoad, svgRoot);
  154. this.addChild(svgRoot.topSprite);
  155. //end('setSVGString');
  156. }
  157. public static function expandEntities(xmlString:String):String {
  158. var entityMap:Object = {};
  159. for each(var myMatch:String in xmlString.match(/.*<!ENTITY\s+(\S+)\s+"([^"]*)"\s*>/mg) ) {
  160. var parts:Array = myMatch.match(/.*<!ENTITY\s+(\S+)\s+"([^"]*)"\s*>/m);
  161. entityMap[parts[1]]=parts[2];
  162. }
  163. for (var myEntity:String in entityMap) {
  164. xmlString = xmlString.split("&" + myEntity + ";").join(entityMap[myEntity]);
  165. }
  166. return xmlString;
  167. }
  168. /**
  169. * JavaScript interface setup
  170. **/
  171. protected function setupJavaScriptInterface():void {
  172. var paramsObj:Object = LoaderInfo(this.root.loaderInfo).parameters;
  173. // process the parameters to get the unique id
  174. var item:String;
  175. for (item in paramsObj) {
  176. if (item == "uniqueId") {
  177. this.js_uniqueId = paramsObj[item];
  178. this.js_handler = 'svgweb.handlers["' + this.js_uniqueId
  179. + '"].';
  180. }
  181. if (item == "debug") {
  182. if (paramsObj[item] == 'true') {
  183. this.debugEnabled = true;
  184. }
  185. else {
  186. this.debugEnabled = false;
  187. }
  188. }
  189. }
  190. try {
  191. // Performance testing found that Flash/JS communication is a
  192. // primary bottleneck; two things were found to make this
  193. // faster when calling from JS to Flash:
  194. // 1) pass giant strings instead of complex objects
  195. // over the boundry, and 2) minimize our own custom
  196. // marshaling code on both sides. Having separate methods for
  197. // each exposed method makes our marshaling code much simpler,
  198. // aiding performance.
  199. // When calling from Flash back to JS, we turn all things
  200. // into Strings instead of Objects as well; however, we
  201. // route everything on that side through an onMessage() method
  202. // on the JS side since we found we don't need to optimize that
  203. // into separate methods yet.
  204. ExternalInterface.addCallback("jsHandleLoad", js_handleLoad);
  205. ExternalInterface.addCallback("jsHandleResize", js_handleResize);
  206. ExternalInterface.addCallback("jsSuspendRedraw", js_suspendRedraw);
  207. ExternalInterface.addCallback("jsUnsuspendRedrawAll", js_unsuspendRedrawAll);
  208. ExternalInterface.addCallback("jsInsertBefore", js_insertBefore);
  209. ExternalInterface.addCallback("jsAddChildAt", js_addChildAt);
  210. ExternalInterface.addCallback("jsRemoveChild", js_removeChild);
  211. ExternalInterface.addCallback("jsAddEventListener", js_addEventListener);
  212. ExternalInterface.addCallback("jsSetText", js_setText);
  213. ExternalInterface.addCallback("jsSetAttribute", js_setAttribute);
  214. ExternalInterface.addCallback("jsGetAttribute", js_getAttribute);
  215. ExternalInterface.addCallback("jsAppendChild", js_appendChild);
  216. ExternalInterface.addCallback("jsGetScreenCTM", js_getScreenCTM);
  217. ExternalInterface.addCallback("jsMatrixInvert", js_matrixInvert);
  218. ExternalInterface.addCallback("jsSetCurrentScale", js_setCurrentScale);
  219. ExternalInterface.addCallback("jsSetCurrentTranslate", js_setCurrentTranslate);
  220. ExternalInterface.addCallback("jsRemoveAttribute", js_removeAttribute);
  221. }
  222. catch(error:SecurityError) {
  223. var debugstr:String = "Security Error on ExternalInterface.addCallback(...). ";
  224. if (this.root.loaderInfo.loaderURL.substring(0,4) == "file") {
  225. debugstr += "This is expected when loaded from a local file.";
  226. }
  227. this.debug(debugstr);
  228. }
  229. }
  230. override public function customizeContextMenu():void {
  231. var outerthis:SVGViewerWeb = this;
  232. super.customizeContextMenu();
  233. // View Source Custom Menu Item
  234. var itemViewSource:ContextMenuItem = new ContextMenuItem("View SVG Source");
  235. itemViewSource.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, viewSource);
  236. this.contextMenu.customItems.push(itemViewSource);
  237. function viewSource():void {
  238. try {
  239. ExternalInterface.call(outerthis.js_handler + "onMessage",
  240. outerthis.msgToString({ type: 'viewsource' })
  241. );
  242. }
  243. catch(error:SecurityError) {
  244. }
  245. }
  246. // View Source Dynamic Custom Menu Item
  247. var itemViewSourceDynamic:ContextMenuItem = new ContextMenuItem("View SVG Source - Dynamic");
  248. itemViewSourceDynamic.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, viewSourceDynamic);
  249. this.contextMenu.customItems.push(itemViewSourceDynamic);
  250. function viewSourceDynamic():void {
  251. try {
  252. var dynamicXMLString:String = outerthis.svgRoot.getXMLTree(0, true);
  253. // backslashes seem to interfere with flash to javascript interface. escape them.
  254. dynamicXMLString=dynamicXMLString.replace(/\\/g, '&amp;#92;');
  255. ExternalInterface.call(outerthis.js_handler + "onMessage",
  256. outerthis.msgToString(
  257. { type: 'viewsourceDynamic',
  258. source: dynamicXMLString
  259. }
  260. ));
  261. }
  262. catch(error:SecurityError) {
  263. }
  264. }
  265. }
  266. /**
  267. * Stringifies the object we send back and forth between Flash and JavaScript.
  268. * Performance testing found that sending strings between Flash and JS is
  269. * about twice as fast as sending objects.
  270. **/
  271. protected function msgToString(msg:Object):String {
  272. var result:Array = [];
  273. for (var i:String in msg) {
  274. result.push(i + ':' + msg[i]);
  275. }
  276. // we use a custom delimiter (__SVG__DELIMIT) instead of commas, since
  277. // the message might have an XML payload or commas already
  278. return result.join('__SVG__DELIMIT');
  279. }
  280. /**
  281. * Event handlers from SVG Nodes
  282. **/
  283. protected function handleRootSVGLoad():void {
  284. //this.debug("render time for " + this.js_uniqueId + ": " + ( (new Date()).valueOf() - this.renderStartTime) + "ms");
  285. // FIXME: Hack. If we are hidden due to the presence of animations,
  286. // then we do not unhide until 200ms into the document time. This
  287. // provides enough time for the first frame of rendering caused
  288. // by animations to occur. Currently, the entire tree is first
  289. // parsed and progressively rendered, without animation effects,
  290. // because animations may not be parsed yet. Once animations are
  291. // parsed, they cause new frames to be rendered. These frames
  292. // should be rendered before anything is visible because the
  293. // animation effects may have been scheduled to begin at time zero
  294. // and rendering without them could be bad, like when objects are
  295. // rendered which would be invisible if animation was working.
  296. // Since it is difficult to determine exactly when the first
  297. // "correct" frames have rendered, we just wait a "long time".
  298. // A better solution than the 200ms delay is to figure out all the
  299. // elements that should be rendered with animation effects at frame
  300. // zero, and unhide exactly when all of these elements have
  301. // completed rendering initially with animation effects.
  302. // We would start that tracking here, because the SVGLoad event
  303. // signals the end of parsing.
  304. var outerthis:SVGViewer=this;
  305. setTimeout(function ():void { outerthis.visible = true }, 200);
  306. var onLoadHandler:String = '';
  307. if (this.svgRoot.xml.@onload) {
  308. onLoadHandler = this.svgRoot.xml.@onload;
  309. }
  310. try {
  311. ExternalInterface.call(this.js_handler + "onMessage",
  312. this.msgToString(
  313. { type: 'event',
  314. eventType: "onRenderingFinished",
  315. width: this.svgRoot.getWidth(),
  316. height: this.svgRoot.getHeight(),
  317. uniqueId: this.js_uniqueId,
  318. onLoad: onLoadHandler
  319. }
  320. ));
  321. }
  322. catch(error:SecurityError) {
  323. }
  324. //var counts:Array = this.svgRoot.countTree();
  325. //this.debug(" Nodes: " + counts[0] +
  326. // " DOMText Nodes: " + counts[2] +
  327. // " Sprites: " + counts[1] +
  328. // " Avg Sprites: " + counts[1]/counts[0]);
  329. }
  330. override public function handleScript(script:String):void {
  331. // TODO: FIXME: Scripts should be batched up in the order they
  332. // are in the document and sent over all at once. This will become
  333. // more important as we support external scripts which load
  334. // asynchronously; we don't want these to arrive at different times
  335. // and get executed in the wrong order.
  336. if (!this.scriptSentToJS) {
  337. // strip off starting SCRIPT cruft; example: <script><![CDATA
  338. script = script.replace(/<[A-Za-z\-_0-9]*:?script[^>]*>(<\!\[CDATA\[)?/, '');
  339. // strip off ending SCRIPT scruft; example: ]]></svg:script>
  340. script = script.replace(/(]]>)?<\/[A-Za-z\-_0-9]*:?script>$/, '');
  341. try {
  342. ExternalInterface.call(this.js_handler + "onMessage",
  343. this.msgToString(
  344. { type: 'script',
  345. uniqueId: this.js_uniqueId,
  346. script: script
  347. }
  348. ));
  349. }
  350. catch(error:SecurityError) {
  351. }
  352. this.scriptSentToJS=true;
  353. }
  354. }
  355. /**
  356. * JavaScript interface handlers
  357. **/
  358. public function js_handleLoad(msg:String):void {
  359. this.renderStartTime = (new Date()).valueOf();
  360. // msg is a string delimited by __SVG__DELIMIT with fields in
  361. // the following order: objectURL, pageURL, objectWidth,
  362. // objectHeight, ignoreWhiteSpace (boolean), svgString
  363. var args:Array = msg.split(DELIMITER);
  364. var objectURL:String = args[0];
  365. var pageURL:String = args[1];
  366. // Flash/JS bridge transforms nulls/undefined into '' empty strings
  367. this.objectWidth = SVGUnits.cleanNumber(args[2]);
  368. this.objectHeight = SVGUnits.cleanNumber(args[3]);
  369. var ignoreWhiteSpace:Boolean = (args[4] === 'true') ? true : false;
  370. var svgString:String = this.decodeFlashData(args[5]);
  371. this.setSVGString(svgString, objectURL, pageURL, ignoreWhiteSpace);
  372. //this.debug("js_handleLoad: object size: " + objectWidth + "," + objectHeight);
  373. //this.debug("js_handleLoad: stage size: " +
  374. // this.stage.stageWidth + "," + this.stage.stageHeight);
  375. this.scaleX = (this.stage.stageWidth/this.objectWidth)
  376. * (this.objectWidth / this.getWidth());
  377. this.scaleY = (this.stage.stageHeight/this.objectHeight)
  378. * (this.objectHeight / this.getHeight());
  379. }
  380. /**
  381. **/
  382. public function js_handleResize(msg:String):void {
  383. // msg is a string delimited by __SVG__DELIMIT with fields in
  384. // the following order: objectWidth, objectHeight
  385. var args:Array = msg.split(DELIMITER);
  386. this.objectWidth = SVGUnits.cleanNumber(args[0]);
  387. this.objectHeight = SVGUnits.cleanNumber(args[1]);
  388. //this.debug("js_handlResize: object size: " + objectWidth + "," + objectHeight);
  389. this.scaleX = (this.stage.stageWidth/this.objectWidth)
  390. * (this.objectWidth / this.getWidth());
  391. this.scaleY = (this.stage.stageHeight/this.objectHeight)
  392. * (this.objectHeight / this.getHeight());
  393. if (this.svgRoot) {
  394. this.svgRoot.applyViewBox();
  395. this.svgRoot.applyDefaultMask();
  396. }
  397. }
  398. public function js_removeChild(msg:String):void {
  399. //this.debug('js_removeChild, msg='+msg);
  400. // msg has one argument, the elementGUID
  401. var elementGUID:String = msg;
  402. // Removes the element
  403. var element:SVGNode = this.svgRoot.getNodeByGUID(elementGUID);
  404. if (!element) {
  405. this.error('removeChild: GUID not found: '
  406. + elementGUID);
  407. }
  408. element.svgParent.removeSVGChild(element);
  409. }
  410. public function js_addChildAt(msg:String):void {
  411. //this.debug('js_addChildAt, msg='+msg);
  412. // msg is a string delimited by __SVG__DELIMIT with fields in
  413. // the following order: parentGUID, position, childXML
  414. var args:Array = msg.split(DELIMITER);
  415. var parentGUID:String = args[0];
  416. var position:Number = new Number(args[1]);
  417. var childXML:String = args[2];
  418. // Get the parent
  419. var parent:SVGNode = this.svgRoot.getNodeByGUID(parentGUID);
  420. if (!parent) {
  421. this.error('addChildAt: parent with GUID '
  422. + parentGUID + ' not found');
  423. }
  424. // If this is a DocumentFragment parse into individual children
  425. var isFragment:Boolean = (childXML.indexOf(DOCUMENT_FRAGMENT) != -1);
  426. var addMe:Array = this.parseXMLChildren(childXML);
  427. // freeze redraws while adding DocumentFragment children
  428. var suspendedBefore:Boolean = this.isSuspended;
  429. if (isFragment) {
  430. this.isSuspended = true;
  431. }
  432. for (var i:int = 0; i < addMe.length; i++) {
  433. // parse the newly appended element into an SVGNode and
  434. // all of its children as well
  435. var element:SVGNode = parent.parseNode(addMe[i]);
  436. element.forceParse();
  437. // append things now
  438. parent.addSVGChildAt(element, position + i);
  439. }
  440. // unsuspend the display; however, keep the display suspended if
  441. // it was suspended before we touched it, such as by a developer
  442. // calling suspendRedraw.
  443. if (isFragment && !suspendedBefore) {
  444. this.isSuspended = false;
  445. parent.invalidateDisplay();
  446. }
  447. }
  448. public function js_insertBefore(msg:String):void {
  449. //this.debug('js_insertBefore, msg='+msg);
  450. // msg is a string delimited by __SVG__DELIMIT with fields in
  451. // the following order: refChildGUID, parentGUID, position,
  452. // childXML
  453. var args:Array = msg.split(DELIMITER);
  454. var refChildGUID:String = args[0];
  455. var parentGUID:String = args[1];
  456. var position:Number = new Number(args[2]);
  457. var childXML:String = args[3];
  458. // Inserts newChild before refChild
  459. // TODO: Test this to see if its working correctly with
  460. // XML Mixed Content (i.e. content of the form
  461. // TEXT<element>foo</element>TEXT)
  462. // get the refChild and the parent
  463. var refChild:SVGNode = this.svgRoot.getNodeByGUID(refChildGUID);
  464. if (!refChild) {
  465. this.error("error:insertBefore: refChildGUID not found: " + refChildGUID);
  466. }
  467. var parent:SVGNode = this.svgRoot.getNodeByGUID(parentGUID);
  468. if (!parent) {
  469. this.error("error:insertBefore: parentGUID not found: " + parentGUID);
  470. }
  471. // If this is a DocumentFragment parse into individual children
  472. var isFragment:Boolean = (childXML.indexOf(DOCUMENT_FRAGMENT) != -1);
  473. var addMe:Array = this.parseXMLChildren(childXML);
  474. // freeze redraws while adding DocumentFragment children
  475. var suspendedBefore:Boolean = this.isSuspended;
  476. if (isFragment) {
  477. this.isSuspended = true;
  478. }
  479. for (var i:int = 0; i < addMe.length; i++) {
  480. // parse the newly appended elements into SVGNode and
  481. // all of its children as well
  482. var element:SVGNode = parent.parseNode(addMe[i]);
  483. element.forceParse();
  484. // now insert the element
  485. parent.insertSVGBefore(position, element, refChild);
  486. }
  487. // unsuspend the display; however, keep the display suspended if
  488. // it was suspended before we touched it, such as by a developer
  489. // calling suspendRedraw.
  490. if (isFragment && !suspendedBefore) {
  491. this.isSuspended = false;
  492. }
  493. }
  494. public function js_setAttribute(msg:String):void {
  495. //this.debug('js_setAttribute, msg='+msg);
  496. // msg is a string delimited by __SVG__DELIMIT with fields in
  497. // the following order: elementGUID, applyToStyle (boolean),
  498. // attrNamespace, attrName, attrValue
  499. var args:Array = msg.split(DELIMITER);
  500. var elementGUID:String = args[0];
  501. var applyToStyle:Boolean = (args[1] === 'true') ? true : false;
  502. // Flash/JS bridge transforms nulls/undefined into '' empty strings
  503. var attrNamespace:String = (args[2] !== '') ? args[2] : null;
  504. var attrName:String = args[3];
  505. var attrValue:String = decodeFlashData(args[4]);
  506. var element:SVGNode = this.svgRoot.getNodeByGUID(elementGUID);
  507. if (!element) {
  508. this.error('setAttribute: GUID not found: ' + elementGUID);
  509. }
  510. if (attrName == 'id') {
  511. this.svgRoot.unregisterID(element);
  512. }
  513. if (applyToStyle) {
  514. element.setStyle(attrName, attrValue);
  515. }
  516. else if (attrNamespace != null) {
  517. // namespaced attribute, such as xlink:href
  518. var ns:Namespace = new Namespace(attrNamespace);
  519. element.xml.@ns::[attrName] = attrValue.toString();
  520. } else {
  521. element.setAttribute(attrName, attrValue.toString());
  522. }
  523. if (attrName == 'id') {
  524. this.svgRoot.registerID(element);
  525. }
  526. }
  527. public function js_removeAttribute(msg:String):void {
  528. //this.debug('js_removeAttribute, msg='+msg);
  529. // msg is a string delimited by __SVG__DELIMIT with fields in
  530. // the following order: elementGUID, namespace, localName
  531. var args:Array = msg.split(DELIMITER);
  532. var elementGUID:String = args[0];
  533. // Flash/JS bridge transforms nulls/undefined into '' empty strings
  534. var ns:String = (args[1] !== '') ? args[1] : null;
  535. var localName:String = args[2];
  536. var element:SVGNode = this.svgRoot.getNodeByGUID(elementGUID);
  537. if (!element) {
  538. this.error('setAttribute: GUID not found: ' + elementGUID);
  539. }
  540. if (ns == null && localName == 'id') {
  541. this.svgRoot.unregisterID(element);
  542. }
  543. // ActionScript's XML implementation does not implement
  544. // removeAttribute or removeAttributeNS, so we have to use
  545. // this workaround
  546. if (ns) {
  547. var nsObj:Namespace = new Namespace(ns);
  548. delete element.xml.@nsObj::[localName]
  549. } else {
  550. delete element.xml.@[localName];
  551. }
  552. }
  553. public function js_addEventListener(msg:String):void {
  554. //this.debug('js_addEventListener, msg='+msg);
  555. // msg is a string delimited by __SVG__DELIMIT with fields in
  556. // the following order: elementGUID, eventType
  557. var args:Array = msg.split(DELIMITER);
  558. var elementGUID:String = args[0];
  559. var eventType:String = args[1];
  560. // Get the element to add the event listener to
  561. var element:SVGNode = this.svgRoot.getNodeByGUID(elementGUID);
  562. if (element) {
  563. if (eventType == 'click') {
  564. element.topSprite.addEventListener(MouseEvent.CLICK, handleAction);
  565. }
  566. if (eventType == 'mouseup') {
  567. element.topSprite.addEventListener(MouseEvent.MOUSE_UP, handleAction);
  568. }
  569. if (eventType == 'mousedown') {
  570. element.topSprite.addEventListener(MouseEvent.MOUSE_DOWN, handleAction);
  571. }
  572. if (eventType == 'mousemove') {
  573. element.topSprite.addEventListener(MouseEvent.MOUSE_MOVE, handleAction);
  574. }
  575. if (eventType == 'mouseover') {
  576. element.topSprite.addEventListener(MouseEvent.MOUSE_OVER, handleAction);
  577. }
  578. if (eventType == 'mouseout') {
  579. element.topSprite.addEventListener(MouseEvent.MOUSE_OUT, handleAction);
  580. }
  581. }
  582. else {
  583. this.error("addEventListener: GUID not found: "
  584. + elementGUID);
  585. }
  586. }
  587. public function js_setText(msg:String):void {
  588. //this.debug('js_setText, msg='+msg);
  589. // msg is a string delimited by __SVG__DELIMIT with fields in
  590. // the following order: parentGUID, elementGUID, text
  591. var args:Array = msg.split(DELIMITER);
  592. var parentGUID:String = args[0];
  593. var elementGUID:String = args[1];
  594. var textValue:String = decodeFlashData(args[2]);
  595. // Get the parent
  596. var parent:SVGNode = this.svgRoot.getNodeByGUID(parentGUID);
  597. if (!parent) {
  598. this.error("error:setText: parent with GUID not found: " + parentGUID);
  599. }
  600. // Get the fake text node element
  601. var element:SVGNode = this.svgRoot.getNodeByGUID(elementGUID);
  602. if (!element) {
  603. this.error("error:setText: element with GUID not found: " + elementGUID);
  604. }
  605. var textNode:SVGDOMTextNode = element as SVGDOMTextNode;
  606. textNode.nodeValue = textValue;
  607. // Tell its parent that its text value has changed
  608. if (parent.hasText()) {
  609. parent.setText(textValue);
  610. parent.invalidateDisplay();
  611. }
  612. }
  613. public function js_appendChild(msg:String):void {
  614. //this.debug('js_appendChild, msg='+msg);
  615. // msg is a string delimited by __SVG__DELIMIT with fields in
  616. // the following order: parentGUID, childXML
  617. var args:Array = msg.split(DELIMITER);
  618. var parentGUID:String = args[0];
  619. var childXML:String = decodeFlashData(args[1]);
  620. // Get the parent node
  621. var parent:SVGNode = this.svgRoot.getNodeByGUID(parentGUID);
  622. if (!parent) {
  623. this.error('appendChild: parent with GUID '
  624. + parentGUID + ' not found');
  625. }
  626. // If this is a DocumentFragment parse into individual children
  627. var isFragment:Boolean = (childXML.indexOf(DOCUMENT_FRAGMENT) != -1);
  628. var addMe:Array = this.parseXMLChildren(childXML);
  629. // freeze redraws while adding DocumentFragment children
  630. var suspendedBefore:Boolean = this.isSuspended;
  631. if (isFragment) {
  632. this.isSuspended = true;
  633. }
  634. for (var i:int = 0; i < addMe.length; i++) {
  635. // parse the newly appended elements into SVGNode and
  636. // all of its children as well
  637. var element:SVGNode = parent.parseNode(addMe[i]);
  638. element.forceParse();
  639. // now actually append the element to our display
  640. parent.appendSVGChild(element);
  641. }
  642. // unsuspend the display; however, keep the display suspended if
  643. // it was suspended before we touched it, such as by a developer
  644. // calling suspendRedraw.
  645. if (isFragment && !suspendedBefore) {
  646. this.isSuspended = false;
  647. }
  648. }
  649. public function js_getAttribute(msg:String):String {
  650. //this.debug('js_getAttribute, msg='+msg);
  651. // msg is a string delimited by __SVG__DELIMIT with fields in
  652. // the following order: elementGUID, getFromStyle (boolean),
  653. // applyAnimations (boolean), attrNamespace, attrName,
  654. // onlyUseXML (boolean)
  655. var args:Array = msg.split(DELIMITER);
  656. var elementGUID:String = args[0];
  657. var getFromStyle:Boolean = (args[1] === 'true') ? true : false;
  658. var applyAnimations:Boolean = (args[2] === 'true') ? true : false;
  659. // Flash/JS bridge transforms nulls/undefined into '' empty strings
  660. var attrNamespace:String = (args[3] !== '') ? args[3] : null;
  661. var attrName:String = args[4];
  662. var onlyUseXML:Boolean = (args[5] === 'true') ? true : false;
  663. var element:SVGNode = this.svgRoot.getNodeByGUID(elementGUID);
  664. if (!element) {
  665. this.error('getAttribute: GUID not found: '
  666. + elementGUID);
  667. }
  668. var attrValue:String;
  669. var ns:Namespace;
  670. if (attrNamespace) {
  671. ns = new Namespace(attrNamespace);
  672. attrValue = element.xml.@ns::[attrName];
  673. } else {
  674. attrValue = element.xml.@[attrName];
  675. }
  676. if (onlyUseXML) {
  677. return attrValue;
  678. }
  679. if (typeof(attrValue) != 'undefined' && attrValue != null) {
  680. if (getFromStyle) {
  681. attrValue = element.getStyle(attrName, null, false);
  682. // Firefox and Safari both return '' for
  683. // default inherited styles (i.e. if I check
  684. // someNode.style.display, I get an empty string
  685. // rather than 'inline'), so only get
  686. // explicitly set styles on this node
  687. if (attrValue == null) {
  688. attrValue = '';
  689. }
  690. }
  691. else {
  692. attrValue = element.getAttribute(attrName, null,
  693. false, applyAnimations, false);
  694. }
  695. }
  696. return attrValue;
  697. }
  698. public function js_suspendRedraw():void {
  699. this.isSuspended = true;
  700. }
  701. public function js_unsuspendRedrawAll(msg:String):void {
  702. //this.debug('js_unsuspendRedrawAll, msg='+msg);
  703. // msg is delimited a bit differently than the other js_* methods;
  704. // each portion is a Flash method name, followed by a colon,
  705. // followed by the string message to send to that method name. Each
  706. // of these portions is separated by the delimiter
  707. // __SVG__METHOD__DELIMITER
  708. if (msg != '') {
  709. var methods:Array = msg.split(this.METHOD_DELIMITER);
  710. for (var i:int = 0; i < methods.length; i++) {
  711. var colonAt:int = methods[i].indexOf(':');
  712. var invoke:String = methods[i].substring(0, colonAt);
  713. var message:String = methods[i].substring(colonAt + 1);
  714. switch (invoke) {
  715. case 'jsInsertBefore':
  716. this.js_insertBefore(message);
  717. break;
  718. case 'jsAddChildAt':
  719. this.js_addChildAt(message);
  720. break;
  721. case 'jsRemoveChild':
  722. this.js_removeChild(message);
  723. break;
  724. case 'jsAddEventListener':
  725. this.js_addEventListener(message);
  726. break;
  727. case 'jsSetText':
  728. this.js_setText(message);
  729. break;
  730. case 'jsSetAttribute':
  731. this.js_setAttribute(message);
  732. break;
  733. case 'jsAppendChild':
  734. this.js_appendChild(message);
  735. break;
  736. case 'jsHandleResize':
  737. this.js_handleResize(message);
  738. break;
  739. case 'jsSetCurrentScale':
  740. this.js_setCurrentScale(message);
  741. break;
  742. case 'jsSetCurrentTranslate':
  743. this.js_setCurrentTranslate(message);
  744. break;
  745. case 'jsRemoveAttribute':
  746. this.js_removeAttribute(message);
  747. break;
  748. default:
  749. this.error('Unknown unsuspendRedrawAll method: ' + invoke);
  750. break;
  751. }
  752. }
  753. }
  754. this.isSuspended = false;
  755. }
  756. public function js_getScreenCTM(msg:String):String {
  757. // msg is a string delimited by __SVG__DELIMIT with fields in
  758. // the following order: elementGUID
  759. var args:Array = msg.split(DELIMITER);
  760. var elementGUID:String = args[0];
  761. // Get the element to add the event listener to
  762. var element:SVGNode = this.svgRoot.getNodeByGUID(elementGUID);
  763. if (element) {
  764. var m:Matrix = element.viewBoxSprite.transform.concatenatedMatrix.clone();
  765. // screenCTM counts the object position as part of its
  766. // transform, but flash does not, so we have to correct that.
  767. m.invert();
  768. var xStr:String = element.getStyleOrAttr('x');
  769. var yStr:String = element.getStyleOrAttr('y');
  770. m.translate(SVGColors.cleanNumber2(xStr, element.getWidth()),
  771. SVGColors.cleanNumber2(yStr, element.getHeight()));
  772. m.invert();
  773. // native getScreenCTM ignores zoom and so shall we.
  774. var rootMatrix:Matrix = this.svgRoot.topSprite.transform.concatenatedMatrix.clone();
  775. rootMatrix.invert();
  776. m.concat(rootMatrix);
  777. return this.msgToString({ type: 'matrix',
  778. a: m.a, b: m.b,
  779. c: m.c, d: m.d,
  780. e: m.tx, f: m.ty
  781. });
  782. return retVal;
  783. }
  784. else {
  785. this.error("getScreenCTM: GUID not found: "
  786. + elementGUID);
  787. }
  788. return null;
  789. }
  790. public function js_matrixInvert(msg:String):String {
  791. // msg is a string delimited by __SVG__DELIMIT with fields in
  792. // the following order: a,b,c,d,e,f
  793. var args:Array = msg.split(DELIMITER);
  794. var a:Number = Number(args[0]);
  795. var b:Number = Number(args[1]);
  796. var c:Number = Number(args[2]);
  797. var d:Number = Number(args[3]);
  798. var e:Number = Number(args[4]);
  799. var f:Number = Number(args[5]);
  800. var m:Matrix = new Matrix(a,b,c,d,e,f);
  801. m.invert();
  802. return this.msgToString({ type: 'matrix',
  803. a: m.a, b: m.b,
  804. c: m.c, d: m.d,
  805. e: m.tx, f: m.ty
  806. });
  807. }
  808. public function js_setCurrentScale(msg:String):void {
  809. // msg is a string with only one value, the new scale
  810. var newValue:Number = Number(msg);
  811. this.svgRoot.currentScale = newValue;
  812. this.svgRoot.zoomAndPan();
  813. }
  814. public function js_setCurrentTranslate(msg:String):void {
  815. // msg is a string delimited by __SVG__DELIMIT with fields in
  816. // the following order: the string 'x', 'y', or 'xy' on which to set,
  817. // followed by the new value. If 'xy' then followed by another
  818. // value.
  819. var args:Array = msg.split(DELIMITER);
  820. var setMe:String = args[0];
  821. var newValue:Number = Number(args[1]);
  822. var newValue2:Number = Number(args[2]);
  823. if (setMe == 'xy') {
  824. this.svgRoot.currentTranslate.x = newValue;
  825. this.svgRoot.currentTranslate.y = newValue2;
  826. } else if (setMe == 'x') {
  827. this.svgRoot.currentTranslate.x = newValue;
  828. } else {
  829. this.svgRoot.currentTranslate.y = newValue;
  830. }
  831. this.svgRoot.zoomAndPan();
  832. }
  833. override public function addActionListener(eventType:String, target:EventDispatcher):void {
  834. if (!target.hasEventListener(eventType)) {
  835. target.addEventListener(eventType, handleAction);
  836. }
  837. }
  838. override public function removeActionListener(eventType:String, target:EventDispatcher):void {
  839. if (target.hasEventListener(eventType)) {
  840. target.removeEventListener(eventType, handleAction);
  841. }
  842. }
  843. protected function handleAction(event:Event):void {
  844. switch(event.type) {
  845. case SVGEvent.SVGLoad:
  846. handleRootSVGLoad();
  847. break;
  848. case MouseEvent.CLICK:
  849. case MouseEvent.MOUSE_DOWN:
  850. case MouseEvent.MOUSE_MOVE:
  851. case MouseEvent.MOUSE_OUT:
  852. case MouseEvent.MOUSE_OVER:
  853. case MouseEvent.MOUSE_UP:
  854. js_sendMouseEvent(MouseEvent(event));
  855. break;
  856. default:
  857. trace("handleAction: Event not found");
  858. }
  859. }
  860. protected function handleResize(event:Event):void {
  861. this.scaleX = (this.stage.stageWidth/this.objectWidth) * (this.objectWidth / this.getWidth());
  862. this.scaleY = (this.stage.stageHeight/this.objectHeight) * (this.objectHeight / this.getHeight());
  863. }
  864. public function js_sendMouseEvent(event:MouseEvent):void {
  865. if ( ( event.target is SVGSprite || event.target is TextField )
  866. && ( event.currentTarget is SVGSprite ) ) {
  867. if (event.target is SVGSprite ) {
  868. var targetNode:SVGNode = SVGSprite(event.target).svgNode;
  869. }
  870. else {
  871. targetNode = SVGSprite(event.currentTarget).svgNode;
  872. }
  873. var currentTargetNode:SVGNode = SVGSprite(event.currentTarget).svgNode;
  874. var scriptCode:String;
  875. if ( (targetNode != null) &&
  876. (currentTargetNode != null) ) {
  877. switch(event.type) {
  878. case MouseEvent.CLICK:
  879. scriptCode = targetNode.getAttribute('onclick');
  880. break;
  881. case MouseEvent.MOUSE_DOWN:
  882. scriptCode = targetNode.getAttribute('onmousedown');
  883. break;
  884. case MouseEvent.MOUSE_MOVE:
  885. scriptCode = targetNode.getAttribute('onmousemove');
  886. break;
  887. case MouseEvent.MOUSE_OUT:
  888. scriptCode = targetNode.getAttribute('onmouseout');
  889. break;
  890. case MouseEvent.MOUSE_OVER:
  891. scriptCode = targetNode.getAttribute('onmouseover');
  892. break;
  893. case MouseEvent.MOUSE_UP:
  894. scriptCode = targetNode.getAttribute('onmouseup');
  895. break;
  896. }
  897. var mousePoint:Point = new Point(event.stageX, event.stageY);
  898. // native getScreenCTM ignores zoom and so shall we.
  899. var rootMatrix:Matrix = this.svgRoot.topSprite.transform.concatenatedMatrix.clone();
  900. rootMatrix.invert();
  901. mousePoint = rootMatrix.transformPoint(mousePoint);
  902. try {
  903. ExternalInterface.call(this.js_handler + "onMessage",
  904. this.msgToString(
  905. { type: 'event',
  906. uniqueId: this.js_uniqueId,
  907. targetGUID: targetNode.guid,
  908. currentTargetGUID: currentTargetNode.guid,
  909. eventType: event.type.toLowerCase(),
  910. localX: event.localX,
  911. localY: event.localY,
  912. stageX: mousePoint.x,
  913. stageY: mousePoint.y,
  914. altKey: event.altKey,
  915. ctrlKey: event.ctrlKey,
  916. shiftKey: event.shiftKey,
  917. scriptCode: scriptCode
  918. }
  919. )
  920. );
  921. }
  922. catch(error:SecurityError) {
  923. }
  924. }
  925. }
  926. }
  927. override public function getWidth():Number {
  928. return this.objectWidth;
  929. }
  930. override public function getHeight():Number {
  931. return this.objectHeight;
  932. }
  933. public function getClipMode():String {
  934. return clipModeParam;
  935. }
  936. override public function debug(debugMessage:String):void {
  937. if (this.debugEnabled) {
  938. if (debugMessage.indexOf(this.DELIMITER) != -1) {
  939. debugMessage = debugMessage.replace(new RegExp(this.DELIMITER, 'g'), ',');
  940. }
  941. try {
  942. ExternalInterface.call(this.js_handler + 'onMessage',
  943. this.msgToString(
  944. { type: 'log',
  945. uniqueId: this.js_uniqueId,
  946. logString: debugMessage
  947. }
  948. ));
  949. }
  950. catch(error:SecurityError) {
  951. }
  952. }
  953. }
  954. override public function error(message:String):void {
  955. if (this.debugEnabled) {
  956. if (message.indexOf(this.DELIMITER) != -1) {
  957. message = message.replace(new RegExp(this.DELIMITER, 'g'), ',');
  958. }
  959. try {
  960. ExternalInterface.call(this.js_handler + 'onMessage',
  961. this.msgToString(
  962. { type: 'error',
  963. uniqueId: this.js_uniqueId,
  964. logString: message
  965. }
  966. ));
  967. }
  968. catch(error:SecurityError) {
  969. }
  970. }
  971. }
  972. /** Functions for profiling. */
  973. override public function start(subject:String, subjectStarted:String = null):void {
  974. ExternalInterface.call('start', subject, subjectStarted);
  975. }
  976. /** Functions for profiling. */
  977. override public function end(subject:String, subjectStarted:String = null):void {
  978. ExternalInterface.call('end', subject, subjectStarted);
  979. }
  980. /** Functions for profiling. */