canvg.js 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620
  1. /*
  2. * canvg.js - Javascript SVG parser and renderer on Canvas
  3. * MIT Licensed
  4. * Gabe Lerner (gabelerner@gmail.com)
  5. * http://code.google.com/p/canvg/
  6. *
  7. * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
  8. */
  9. if(!window.console) {
  10. window.console = {};
  11. window.console.log = function(str) {};
  12. window.console.dir = function(str) {};
  13. }
  14. if(!Array.prototype.indexOf){
  15. Array.prototype.indexOf = function(obj){
  16. for(var i=0; i<this.length; i++){
  17. if(this[i]==obj){
  18. return i;
  19. }
  20. }
  21. return -1;
  22. }
  23. }
  24. (function(){
  25. // canvg(target, s)
  26. // empty parameters: replace all 'svg' elements on page with 'canvas' elements
  27. // target: canvas element or the id of a canvas element
  28. // s: svg string, url to svg file, or xml document
  29. // opts: optional hash of options
  30. // ignoreMouse: true => ignore mouse events
  31. // ignoreAnimation: true => ignore animations
  32. // ignoreDimensions: true => does not try to resize canvas
  33. // ignoreClear: true => does not clear canvas
  34. // offsetX: int => draws at a x offset
  35. // offsetY: int => draws at a y offset
  36. // scaleWidth: int => scales horizontally to width
  37. // scaleHeight: int => scales vertically to height
  38. // renderCallback: function => will call the function after the first render is completed
  39. // forceRedraw: function => will call the function on every frame, if it returns true, will redraw
  40. this.canvg = function (target, s, opts) {
  41. // no parameters
  42. if (target == null && s == null && opts == null) {
  43. var svgTags = document.getElementsByTagName('svg');
  44. for (var i=0; i<svgTags.length; i++) {
  45. var svgTag = svgTags[i];
  46. var c = document.createElement('canvas');
  47. c.width = svgTag.clientWidth;
  48. c.height = svgTag.clientHeight;
  49. svgTag.parentNode.insertBefore(c, svgTag);
  50. svgTag.parentNode.removeChild(svgTag);
  51. var div = document.createElement('div');
  52. div.appendChild(svgTag);
  53. canvg(c, div.innerHTML);
  54. }
  55. return;
  56. }
  57. opts = opts || {};
  58. if (typeof target == 'string') {
  59. target = document.getElementById(target);
  60. }
  61. // reuse class per canvas
  62. var svg;
  63. if (target.svg == null) {
  64. svg = build();
  65. target.svg = svg;
  66. }
  67. else {
  68. svg = target.svg;
  69. svg.stop();
  70. }
  71. svg.opts = opts;
  72. var ctx = target.getContext('2d');
  73. if (typeof(s.documentElement) != 'undefined') {
  74. // load from xml doc
  75. svg.loadXmlDoc(ctx, s);
  76. }
  77. else if (s.substr(0,1) == '<') {
  78. // load from xml string
  79. svg.loadXml(ctx, s);
  80. }
  81. else {
  82. // load from url
  83. svg.load(ctx, s);
  84. }
  85. }
  86. function build() {
  87. var svg = { };
  88. svg.FRAMERATE = 30;
  89. svg.MAX_VIRTUAL_PIXELS = 30000;
  90. // globals
  91. svg.init = function(ctx) {
  92. svg.Definitions = {};
  93. svg.Styles = {};
  94. svg.Animations = [];
  95. svg.Images = [];
  96. svg.ctx = ctx;
  97. svg.ViewPort = new (function () {
  98. this.viewPorts = [];
  99. this.Clear = function() { this.viewPorts = []; }
  100. this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
  101. this.RemoveCurrent = function() { this.viewPorts.pop(); }
  102. this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
  103. this.width = function() { return this.Current().width; }
  104. this.height = function() { return this.Current().height; }
  105. this.ComputeSize = function(d) {
  106. if (d != null && typeof(d) == 'number') return d;
  107. if (d == 'x') return this.width();
  108. if (d == 'y') return this.height();
  109. return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);
  110. }
  111. });
  112. }
  113. svg.init();
  114. // images loaded
  115. svg.ImagesLoaded = function() {
  116. for (var i=0; i<svg.Images.length; i++) {
  117. if (!svg.Images[i].loaded) return false;
  118. }
  119. return true;
  120. }
  121. // trim
  122. svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
  123. // compress spaces
  124. svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
  125. // ajax
  126. svg.ajax = function(url) {
  127. var AJAX;
  128. if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
  129. else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
  130. if(AJAX){
  131. AJAX.open('GET',url,false);
  132. AJAX.send(null);
  133. return AJAX.responseText;
  134. }
  135. return null;
  136. }
  137. // parse xml
  138. svg.parseXml = function(xml) {
  139. if (window.DOMParser)
  140. {
  141. var parser = new DOMParser();
  142. return parser.parseFromString(xml, 'text/xml');
  143. }
  144. else
  145. {
  146. xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
  147. var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
  148. xmlDoc.async = 'false';
  149. xmlDoc.loadXML(xml);
  150. return xmlDoc;
  151. }
  152. }
  153. svg.Property = function(name, value) {
  154. this.name = name;
  155. this.value = value;
  156. this.hasValue = function() {
  157. return (this.value != null && this.value !== '');
  158. }
  159. // return the numerical value of the property
  160. this.numValue = function() {
  161. if (!this.hasValue()) return 0;
  162. var n = parseFloat(this.value);
  163. if ((this.value + '').match(/%$/)) {
  164. n = n / 100.0;
  165. }
  166. return n;
  167. }
  168. this.valueOrDefault = function(def) {
  169. if (this.hasValue()) return this.value;
  170. return def;
  171. }
  172. this.numValueOrDefault = function(def) {
  173. if (this.hasValue()) return this.numValue();
  174. return def;
  175. }
  176. /* EXTENSIONS */
  177. var that = this;
  178. // color extensions
  179. this.Color = {
  180. // augment the current color value with the opacity
  181. addOpacity: function(opacity) {
  182. var newValue = that.value;
  183. if (opacity != null && opacity != '') {
  184. var color = new RGBColor(that.value);
  185. if (color.ok) {
  186. newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
  187. }
  188. }
  189. return new svg.Property(that.name, newValue);
  190. }
  191. }
  192. // definition extensions
  193. this.Definition = {
  194. // get the definition from the definitions table
  195. getDefinition: function() {
  196. var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2');
  197. return svg.Definitions[name];
  198. },
  199. isUrl: function() {
  200. return that.value.indexOf('url(') == 0
  201. },
  202. getFillStyle: function(e) {
  203. var def = this.getDefinition();
  204. // gradient
  205. if (def != null && def.createGradient) {
  206. return def.createGradient(svg.ctx, e);
  207. }
  208. // pattern
  209. if (def != null && def.createPattern) {
  210. return def.createPattern(svg.ctx, e);
  211. }
  212. return null;
  213. }
  214. }
  215. // length extensions
  216. this.Length = {
  217. DPI: function(viewPort) {
  218. return 96.0; // TODO: compute?
  219. },
  220. EM: function(viewPort) {
  221. var em = 12;
  222. var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
  223. if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);
  224. return em;
  225. },
  226. // get the length as pixels
  227. toPixels: function(viewPort) {
  228. if (!that.hasValue()) return 0;
  229. var s = that.value+'';
  230. if (s.match(/em$/)) return that.numValue() * this.EM(viewPort);
  231. if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;
  232. if (s.match(/px$/)) return that.numValue();
  233. if (s.match(/pt$/)) return that.numValue() * 1.25;
  234. if (s.match(/pc$/)) return that.numValue() * 15;
  235. if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;
  236. if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;
  237. if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);
  238. if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);
  239. return that.numValue();
  240. }
  241. }
  242. // time extensions
  243. this.Time = {
  244. // get the time as milliseconds
  245. toMilliseconds: function() {
  246. if (!that.hasValue()) return 0;
  247. var s = that.value+'';
  248. if (s.match(/s$/)) return that.numValue() * 1000;
  249. if (s.match(/ms$/)) return that.numValue();
  250. return that.numValue();
  251. }
  252. }
  253. // angle extensions
  254. this.Angle = {
  255. // get the angle as radians
  256. toRadians: function() {
  257. if (!that.hasValue()) return 0;
  258. var s = that.value+'';
  259. if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);
  260. if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);
  261. if (s.match(/rad$/)) return that.numValue();
  262. return that.numValue() * (Math.PI / 180.0);
  263. }
  264. }
  265. }
  266. // fonts
  267. svg.Font = new (function() {
  268. this.Styles = ['normal','italic','oblique','inherit'];
  269. this.Variants = ['normal','small-caps','inherit'];
  270. this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];
  271. this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
  272. var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
  273. return {
  274. fontFamily: fontFamily || f.fontFamily,
  275. fontSize: fontSize || f.fontSize,
  276. fontStyle: fontStyle || f.fontStyle,
  277. fontWeight: fontWeight || f.fontWeight,
  278. fontVariant: fontVariant || f.fontVariant,
  279. toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') }
  280. }
  281. }
  282. var that = this;
  283. this.Parse = function(s) {
  284. var f = {};
  285. var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
  286. var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
  287. var ff = '';
  288. for (var i=0; i<d.length; i++) {
  289. if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
  290. else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true; }
  291. else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
  292. else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
  293. else { if (d[i] != 'inherit') ff += d[i]; }
  294. } if (ff != '') f.fontFamily = ff;
  295. return f;
  296. }
  297. });
  298. // points and paths
  299. svg.ToNumberArray = function(s) {
  300. var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
  301. for (var i=0; i<a.length; i++) {
  302. a[i] = parseFloat(a[i]);
  303. }
  304. return a;
  305. }
  306. svg.Point = function(x, y) {
  307. this.x = x;
  308. this.y = y;
  309. this.angleTo = function(p) {
  310. return Math.atan2(p.y - this.y, p.x - this.x);
  311. }
  312. this.applyTransform = function(v) {
  313. var xp = this.x * v[0] + this.y * v[2] + v[4];
  314. var yp = this.x * v[1] + this.y * v[3] + v[5];
  315. this.x = xp;
  316. this.y = yp;
  317. }
  318. }
  319. svg.CreatePoint = function(s) {
  320. var a = svg.ToNumberArray(s);
  321. return new svg.Point(a[0], a[1]);
  322. }
  323. svg.CreatePath = function(s) {
  324. var a = svg.ToNumberArray(s);
  325. var path = [];
  326. for (var i=0; i<a.length; i+=2) {
  327. path.push(new svg.Point(a[i], a[i+1]));
  328. }
  329. return path;
  330. }
  331. // bounding box
  332. svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
  333. this.x1 = Number.NaN;
  334. this.y1 = Number.NaN;
  335. this.x2 = Number.NaN;
  336. this.y2 = Number.NaN;
  337. this.x = function() { return this.x1; }
  338. this.y = function() { return this.y1; }
  339. this.width = function() { return this.x2 - this.x1; }
  340. this.height = function() { return this.y2 - this.y1; }
  341. this.addPoint = function(x, y) {
  342. if (x != null) {
  343. if (isNaN(this.x1) || isNaN(this.x2)) {
  344. this.x1 = x;
  345. this.x2 = x;
  346. }
  347. if (x < this.x1) this.x1 = x;
  348. if (x > this.x2) this.x2 = x;
  349. }
  350. if (y != null) {
  351. if (isNaN(this.y1) || isNaN(this.y2)) {
  352. this.y1 = y;
  353. this.y2 = y;
  354. }
  355. if (y < this.y1) this.y1 = y;
  356. if (y > this.y2) this.y2 = y;
  357. }
  358. }
  359. this.addX = function(x) { this.addPoint(x, null); }
  360. this.addY = function(y) { this.addPoint(null, y); }
  361. this.addBoundingBox = function(bb) {
  362. this.addPoint(bb.x1, bb.y1);
  363. this.addPoint(bb.x2, bb.y2);
  364. }
  365. this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
  366. var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
  367. var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
  368. var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
  369. var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
  370. this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
  371. }
  372. this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
  373. // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
  374. var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
  375. this.addPoint(p0[0], p0[1]);
  376. this.addPoint(p3[0], p3[1]);
  377. for (i=0; i<=1; i++) {
  378. var f = function(t) {
  379. return Math.pow(1-t, 3) * p0[i]
  380. + 3 * Math.pow(1-t, 2) * t * p1[i]
  381. + 3 * (1-t) * Math.pow(t, 2) * p2[i]
  382. + Math.pow(t, 3) * p3[i];
  383. }
  384. var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
  385. var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
  386. var c = 3 * p1[i] - 3 * p0[i];
  387. if (a == 0) {
  388. if (b == 0) continue;
  389. var t = -c / b;
  390. if (0 < t && t < 1) {
  391. if (i == 0) this.addX(f(t));
  392. if (i == 1) this.addY(f(t));
  393. }
  394. continue;
  395. }
  396. var b2ac = Math.pow(b, 2) - 4 * c * a;
  397. if (b2ac < 0) continue;
  398. var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
  399. if (0 < t1 && t1 < 1) {
  400. if (i == 0) this.addX(f(t1));
  401. if (i == 1) this.addY(f(t1));
  402. }
  403. var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
  404. if (0 < t2 && t2 < 1) {
  405. if (i == 0) this.addX(f(t2));
  406. if (i == 1) this.addY(f(t2));
  407. }
  408. }
  409. }
  410. this.isPointInBox = function(x, y) {
  411. return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
  412. }
  413. this.addPoint(x1, y1);
  414. this.addPoint(x2, y2);
  415. }
  416. // transforms
  417. svg.Transform = function(v) {
  418. var that = this;
  419. this.Type = {}
  420. // translate
  421. this.Type.translate = function(s) {
  422. this.p = svg.CreatePoint(s);
  423. this.apply = function(ctx) {
  424. ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
  425. }
  426. this.applyToPoint = function(p) {
  427. p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
  428. }
  429. }
  430. // rotate
  431. this.Type.rotate = function(s) {
  432. var a = svg.ToNumberArray(s);
  433. this.angle = new svg.Property('angle', a[0]);
  434. this.cx = a[1] || 0;
  435. this.cy = a[2] || 0;
  436. this.apply = function(ctx) {
  437. ctx.translate(this.cx, this.cy);
  438. ctx.rotate(this.angle.Angle.toRadians());
  439. ctx.translate(-this.cx, -this.cy);
  440. }
  441. this.applyToPoint = function(p) {
  442. var a = this.angle.Angle.toRadians();
  443. p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
  444. p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
  445. p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
  446. }
  447. }
  448. this.Type.scale = function(s) {
  449. this.p = svg.CreatePoint(s);
  450. this.apply = function(ctx) {
  451. ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
  452. }
  453. this.applyToPoint = function(p) {
  454. p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
  455. }
  456. }
  457. this.Type.matrix = function(s) {
  458. this.m = svg.ToNumberArray(s);
  459. this.apply = function(ctx) {
  460. ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
  461. }
  462. this.applyToPoint = function(p) {
  463. p.applyTransform(this.m);
  464. }
  465. }
  466. this.Type.SkewBase = function(s) {
  467. this.base = that.Type.matrix;
  468. this.base(s);
  469. this.angle = new svg.Property('angle', s);
  470. }
  471. this.Type.SkewBase.prototype = new this.Type.matrix;
  472. this.Type.skewX = function(s) {
  473. this.base = that.Type.SkewBase;
  474. this.base(s);
  475. this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];
  476. }
  477. this.Type.skewX.prototype = new this.Type.SkewBase;
  478. this.Type.skewY = function(s) {
  479. this.base = that.Type.SkewBase;
  480. this.base(s);
  481. this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];
  482. }
  483. this.Type.skewY.prototype = new this.Type.SkewBase;
  484. this.transforms = [];
  485. this.apply = function(ctx) {
  486. for (var i=0; i<this.transforms.length; i++) {
  487. this.transforms[i].apply(ctx);
  488. }
  489. }
  490. this.applyToPoint = function(p) {
  491. for (var i=0; i<this.transforms.length; i++) {
  492. this.transforms[i].applyToPoint(p);
  493. }
  494. }
  495. var data = svg.trim(svg.compressSpaces(v)).split(/\s(?=[a-z])/);
  496. for (var i=0; i<data.length; i++) {
  497. var type = data[i].split('(')[0];
  498. var s = data[i].split('(')[1].replace(')','');
  499. var transform = new this.Type[type](s);
  500. this.transforms.push(transform);
  501. }
  502. }
  503. // aspect ratio
  504. svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
  505. // aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
  506. aspectRatio = svg.compressSpaces(aspectRatio);
  507. aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
  508. var align = aspectRatio.split(' ')[0] || 'xMidYMid';
  509. var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';
  510. // calculate scale
  511. var scaleX = width / desiredWidth;
  512. var scaleY = height / desiredHeight;
  513. var scaleMin = Math.min(scaleX, scaleY);
  514. var scaleMax = Math.max(scaleX, scaleY);
  515. if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
  516. if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }
  517. refX = new svg.Property('refX', refX);
  518. refY = new svg.Property('refY', refY);
  519. if (refX.hasValue() && refY.hasValue()) {
  520. ctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));
  521. }
  522. else {
  523. // align
  524. if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);
  525. if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0);
  526. if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0);
  527. if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight);
  528. }
  529. // scale
  530. if (align == 'none') ctx.scale(scaleX, scaleY);
  531. else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin);
  532. else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);
  533. // translate
  534. ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);
  535. }
  536. // elements
  537. svg.Element = {}
  538. svg.Element.ElementBase = function(node) {
  539. this.attributes = {};
  540. this.styles = {};
  541. this.children = [];
  542. // get or create attribute
  543. this.attribute = function(name, createIfNotExists) {
  544. var a = this.attributes[name];
  545. if (a != null) return a;
  546. a = new svg.Property(name, '');
  547. if (createIfNotExists == true) this.attributes[name] = a;
  548. return a;
  549. }
  550. // get or create style, crawls up node tree
  551. this.style = function(name, createIfNotExists) {
  552. var s = this.styles[name];
  553. if (s != null) return s;
  554. var a = this.attribute(name);
  555. if (a != null && a.hasValue()) {
  556. return a;
  557. }
  558. var p = this.parent;
  559. if (p != null) {
  560. var ps = p.style(name);
  561. if (ps != null && ps.hasValue()) {
  562. return ps;
  563. }
  564. }
  565. s = new svg.Property(name, '');
  566. if (createIfNotExists == true) this.styles[name] = s;
  567. return s;
  568. }
  569. // base render
  570. this.render = function(ctx) {
  571. // don't render display=none
  572. if (this.style('display').value == 'none') return;
  573. // don't render visibility=hidden
  574. if (this.attribute('visibility').value == 'hidden') return;
  575. ctx.save();
  576. this.setContext(ctx);
  577. // mask
  578. if (this.attribute('mask').hasValue()) {
  579. var mask = this.attribute('mask').Definition.getDefinition();
  580. if (mask != null) mask.apply(ctx, this);
  581. }
  582. else if (this.style('filter').hasValue()) {
  583. var filter = this.style('filter').Definition.getDefinition();
  584. if (filter != null) filter.apply(ctx, this);
  585. }
  586. else this.renderChildren(ctx);
  587. this.clearContext(ctx);
  588. ctx.restore();
  589. }
  590. // base set context
  591. this.setContext = function(ctx) {
  592. // OVERRIDE ME!
  593. }
  594. // base clear context
  595. this.clearContext = function(ctx) {
  596. // OVERRIDE ME!
  597. }
  598. // base render children
  599. this.renderChildren = function(ctx) {
  600. for (var i=0; i<this.children.length; i++) {
  601. this.children[i].render(ctx);
  602. }
  603. }
  604. this.addChild = function(childNode, create) {
  605. var child = childNode;
  606. if (create) child = svg.CreateElement(childNode);
  607. child.parent = this;
  608. this.children.push(child);
  609. }
  610. if (node != null && node.nodeType == 1) { //ELEMENT_NODE
  611. // add children
  612. for (var i=0; i<node.childNodes.length; i++) {
  613. var childNode = node.childNodes[i];
  614. if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
  615. }
  616. // add attributes
  617. for (var i=0; i<node.attributes.length; i++) {
  618. var attribute = node.attributes[i];
  619. this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
  620. }
  621. // add tag styles
  622. var styles = svg.Styles[node.nodeName];
  623. if (styles != null) {
  624. for (var name in styles) {
  625. this.styles[name] = styles[name];
  626. }
  627. }
  628. // add class styles
  629. if (this.attribute('class').hasValue()) {
  630. var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
  631. for (var j=0; j<classes.length; j++) {
  632. styles = svg.Styles['.'+classes[j]];
  633. if (styles != null) {
  634. for (var name in styles) {
  635. this.styles[name] = styles[name];
  636. }
  637. }
  638. styles = svg.Styles[node.nodeName+'.'+classes[j]];
  639. if (styles != null) {
  640. for (var name in styles) {
  641. this.styles[name] = styles[name];
  642. }
  643. }
  644. }
  645. }
  646. // add inline styles
  647. if (this.attribute('style').hasValue()) {
  648. var styles = this.attribute('style').value.split(';');
  649. for (var i=0; i<styles.length; i++) {
  650. if (svg.trim(styles[i]) != '') {
  651. var style = styles[i].split(':');
  652. var name = svg.trim(style[0]);
  653. var value = svg.trim(style[1]);
  654. this.styles[name] = new svg.Property(name, value);
  655. }
  656. }
  657. }
  658. // add id
  659. if (this.attribute('id').hasValue()) {
  660. if (svg.Definitions[this.attribute('id').value] == null) {
  661. svg.Definitions[this.attribute('id').value] = this;
  662. }
  663. }
  664. }
  665. }
  666. svg.Element.RenderedElementBase = function(node) {
  667. this.base = svg.Element.ElementBase;
  668. this.base(node);
  669. this.setContext = function(ctx) {
  670. // fill
  671. if (this.style('fill').Definition.isUrl()) {
  672. var fs = this.style('fill').Definition.getFillStyle(this);
  673. if (fs != null) ctx.fillStyle = fs;
  674. }
  675. else if (this.style('fill').hasValue()) {
  676. var fillStyle = this.style('fill');
  677. if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
  678. ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
  679. }
  680. // stroke
  681. if (this.style('stroke').Definition.isUrl()) {
  682. var fs = this.style('stroke').Definition.getFillStyle(this);
  683. if (fs != null) ctx.strokeStyle = fs;
  684. }
  685. else if (this.style('stroke').hasValue()) {
  686. var strokeStyle = this.style('stroke');
  687. if (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);
  688. ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
  689. }
  690. if (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();
  691. if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
  692. if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
  693. if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
  694. // font
  695. if (typeof(ctx.font) != 'undefined') {
  696. ctx.font = svg.Font.CreateFont(
  697. this.style('font-style').value,
  698. this.style('font-variant').value,
  699. this.style('font-weight').value,
  700. this.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '',
  701. this.style('font-family').value).toString();
  702. }
  703. // transform
  704. if (this.attribute('transform').hasValue()) {
  705. var transform = new svg.Transform(this.attribute('transform').value);
  706. transform.apply(ctx);
  707. }
  708. // clip
  709. if (this.attribute('clip-path').hasValue()) {
  710. var clip = this.attribute('clip-path').Definition.getDefinition();
  711. if (clip != null) clip.apply(ctx);
  712. }
  713. // opacity
  714. if (this.style('opacity').hasValue()) {
  715. ctx.globalAlpha = this.style('opacity').numValue();
  716. }
  717. }
  718. }
  719. svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
  720. svg.Element.PathElementBase = function(node) {
  721. this.base = svg.Element.RenderedElementBase;
  722. this.base(node);
  723. this.path = function(ctx) {
  724. if (ctx != null) ctx.beginPath();
  725. return new svg.BoundingBox();
  726. }
  727. this.renderChildren = function(ctx) {
  728. this.path(ctx);
  729. svg.Mouse.checkPath(this, ctx);
  730. if (ctx.fillStyle != '') ctx.fill();
  731. if (ctx.strokeStyle != '') ctx.stroke();
  732. var markers = this.getMarkers();
  733. if (markers != null) {
  734. if (this.style('marker-start').Definition.isUrl()) {
  735. var marker = this.style('marker-start').Definition.getDefinition();
  736. marker.render(ctx, markers[0][0], markers[0][1]);
  737. }
  738. if (this.style('marker-mid').Definition.isUrl()) {
  739. var marker = this.style('marker-mid').Definition.getDefinition();
  740. for (var i=1;i<markers.length-1;i++) {
  741. marker.render(ctx, markers[i][0], markers[i][1]);
  742. }
  743. }
  744. if (this.style('marker-end').Definition.isUrl()) {
  745. var marker = this.style('marker-end').Definition.getDefinition();
  746. marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
  747. }
  748. }
  749. }
  750. this.getBoundingBox = function() {
  751. return this.path();
  752. }
  753. this.getMarkers = function() {
  754. return null;
  755. }
  756. }
  757. svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
  758. // svg element
  759. svg.Element.svg = function(node) {
  760. this.base = svg.Element.RenderedElementBase;
  761. this.base(node);
  762. this.baseClearContext = this.clearContext;
  763. this.clearContext = function(ctx) {
  764. this.baseClearContext(ctx);
  765. svg.ViewPort.RemoveCurrent();
  766. }
  767. this.baseSetContext = this.setContext;
  768. this.setContext = function(ctx) {
  769. // initial values
  770. ctx.strokeStyle = 'rgba(0,0,0,0)';
  771. ctx.lineCap = 'butt';
  772. ctx.lineJoin = 'miter';
  773. ctx.miterLimit = 4;
  774. this.baseSetContext(ctx);
  775. // create new view port
  776. if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
  777. ctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));
  778. }
  779. var width = svg.ViewPort.width();
  780. var height = svg.ViewPort.height();
  781. if (typeof(this.root) == 'undefined' && this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
  782. width = this.attribute('width').Length.toPixels('x');
  783. height = this.attribute('height').Length.toPixels('y');
  784. var x = 0;
  785. var y = 0;
  786. if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
  787. x = -this.attribute('refX').Length.toPixels('x');
  788. y = -this.attribute('refY').Length.toPixels('y');
  789. }
  790. ctx.beginPath();
  791. ctx.moveTo(x, y);
  792. ctx.lineTo(width, y);
  793. ctx.lineTo(width, height);
  794. ctx.lineTo(x, height);
  795. ctx.closePath();
  796. ctx.clip();
  797. }
  798. svg.ViewPort.SetCurrent(width, height);
  799. // viewbox
  800. if (this.attribute('viewBox').hasValue()) {
  801. var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
  802. var minX = viewBox[0];
  803. var minY = viewBox[1];
  804. width = viewBox[2];
  805. height = viewBox[3];
  806. svg.AspectRatio(ctx,
  807. this.attribute('preserveAspectRatio').value,
  808. svg.ViewPort.width(),
  809. width,
  810. svg.ViewPort.height(),
  811. height,
  812. minX,
  813. minY,
  814. this.attribute('refX').value,
  815. this.attribute('refY').value);
  816. svg.ViewPort.RemoveCurrent();
  817. svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
  818. }
  819. }
  820. }
  821. svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
  822. // rect element
  823. svg.Element.rect = function(node) {
  824. this.base = svg.Element.PathElementBase;
  825. this.base(node);
  826. this.path = function(ctx) {
  827. var x = this.attribute('x').Length.toPixels('x');
  828. var y = this.attribute('y').Length.toPixels('y');
  829. var width = this.attribute('width').Length.toPixels('x');
  830. var height = this.attribute('height').Length.toPixels('y');
  831. var rx = this.attribute('rx').Length.toPixels('x');
  832. var ry = this.attribute('ry').Length.toPixels('y');
  833. if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
  834. if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
  835. if (ctx != null) {
  836. ctx.beginPath();
  837. ctx.moveTo(x + rx, y);
  838. ctx.lineTo(x + width - rx, y);
  839. ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
  840. ctx.lineTo(x + width, y + height - ry);
  841. ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
  842. ctx.lineTo(x + rx, y + height);
  843. ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
  844. ctx.lineTo(x, y + ry);
  845. ctx.quadraticCurveTo(x, y, x + rx, y)
  846. ctx.closePath();
  847. }
  848. return new svg.BoundingBox(x, y, x + width, y + height);
  849. }
  850. }
  851. svg.Element.rect.prototype = new svg.Element.PathElementBase;
  852. // circle element
  853. svg.Element.circle = function(node) {
  854. this.base = svg.Element.PathElementBase;
  855. this.base(node);
  856. this.path = function(ctx) {
  857. var cx = this.attribute('cx').Length.toPixels('x');
  858. var cy = this.attribute('cy').Length.toPixels('y');
  859. var r = this.attribute('r').Length.toPixels();
  860. if (ctx != null) {
  861. ctx.beginPath();
  862. ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
  863. ctx.closePath();
  864. }
  865. return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
  866. }
  867. }
  868. svg.Element.circle.prototype = new svg.Element.PathElementBase;
  869. // ellipse element
  870. svg.Element.ellipse = function(node) {
  871. this.base = svg.Element.PathElementBase;
  872. this.base(node);
  873. this.path = function(ctx) {
  874. var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
  875. var rx = this.attribute('rx').Length.toPixels('x');
  876. var ry = this.attribute('ry').Length.toPixels('y');
  877. var cx = this.attribute('cx').Length.toPixels('x');
  878. var cy = this.attribute('cy').Length.toPixels('y');
  879. if (ctx != null) {
  880. ctx.beginPath();
  881. ctx.moveTo(cx, cy - ry);
  882. ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
  883. ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
  884. ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
  885. ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
  886. ctx.closePath();
  887. }
  888. return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
  889. }
  890. }
  891. svg.Element.ellipse.prototype = new svg.Element.PathElementBase;
  892. // line element
  893. svg.Element.line = function(node) {
  894. this.base = svg.Element.PathElementBase;
  895. this.base(node);
  896. this.getPoints = function() {
  897. return [
  898. new svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),
  899. new svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];
  900. }
  901. this.path = function(ctx) {
  902. var points = this.getPoints();
  903. if (ctx != null) {
  904. ctx.beginPath();
  905. ctx.moveTo(points[0].x, points[0].y);
  906. ctx.lineTo(points[1].x, points[1].y);
  907. }
  908. return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
  909. }
  910. this.getMarkers = function() {
  911. var points = this.getPoints();
  912. var a = points[0].angleTo(points[1]);
  913. return [[points[0], a], [points[1], a]];
  914. }
  915. }
  916. svg.Element.line.prototype = new svg.Element.PathElementBase;
  917. // polyline element
  918. svg.Element.polyline = function(node) {
  919. this.base = svg.Element.PathElementBase;
  920. this.base(node);
  921. this.points = svg.CreatePath(this.attribute('points').value);
  922. this.path = function(ctx) {
  923. var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
  924. if (ctx != null) {
  925. ctx.beginPath();
  926. ctx.moveTo(this.points[0].x, this.points[0].y);
  927. }
  928. for (var i=1; i<this.points.length; i++) {
  929. bb.addPoint(this.points[i].x, this.points[i].y);
  930. if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
  931. }
  932. return bb;
  933. }
  934. this.getMarkers = function() {
  935. var markers = [];
  936. for (var i=0; i<this.points.length - 1; i++) {
  937. markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
  938. }
  939. markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
  940. return markers;
  941. }
  942. }
  943. svg.Element.polyline.prototype = new svg.Element.PathElementBase;
  944. // polygon element
  945. svg.Element.polygon = function(node) {
  946. this.base = svg.Element.polyline;
  947. this.base(node);
  948. this.basePath = this.path;
  949. this.path = function(ctx) {
  950. var bb = this.basePath(ctx);
  951. if (ctx != null) {
  952. ctx.lineTo(this.points[0].x, this.points[0].y);
  953. ctx.closePath();
  954. }
  955. return bb;
  956. }
  957. }
  958. svg.Element.polygon.prototype = new svg.Element.polyline;
  959. // path element
  960. svg.Element.path = function(node) {
  961. this.base = svg.Element.PathElementBase;
  962. this.base(node);
  963. var d = this.attribute('d').value;
  964. // TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
  965. d = d.replace(/,/gm,' '); // get rid of all commas
  966. d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
  967. d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
  968. d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
  969. d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
  970. d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
  971. d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
  972. d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
  973. d = svg.compressSpaces(d); // compress multiple spaces
  974. d = svg.trim(d);
  975. this.PathParser = new (function(d) {
  976. this.tokens = d.split(' ');
  977. this.reset = function() {
  978. this.i = -1;
  979. this.command = '';
  980. this.previousCommand = '';
  981. this.start = new svg.Point(0, 0);
  982. this.control = new svg.Point(0, 0);
  983. this.current = new svg.Point(0, 0);
  984. this.points = [];
  985. this.angles = [];
  986. }
  987. this.isEnd = function() {
  988. return this.i >= this.tokens.length - 1;
  989. }
  990. this.isCommandOrEnd = function() {
  991. if (this.isEnd()) return true;
  992. return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;
  993. }
  994. this.isRelativeCommand = function() {
  995. return this.command == this.command.toLowerCase();
  996. }
  997. this.getToken = function() {
  998. this.i = this.i + 1;
  999. return this.tokens[this.i];
  1000. }
  1001. this.getScalar = function() {
  1002. return parseFloat(this.getToken());
  1003. }
  1004. this.nextCommand = function() {
  1005. this.previousCommand = this.command;
  1006. this.command = this.getToken();
  1007. }
  1008. this.getPoint = function() {
  1009. var p = new svg.Point(this.getScalar(), this.getScalar());
  1010. return this.makeAbsolute(p);
  1011. }
  1012. this.getAsControlPoint = function() {
  1013. var p = this.getPoint();
  1014. this.control = p;
  1015. return p;
  1016. }
  1017. this.getAsCurrentPoint = function() {
  1018. var p = this.getPoint();
  1019. this.current = p;
  1020. return p;
  1021. }
  1022. this.getReflectedControlPoint = function() {
  1023. if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {
  1024. return this.current;
  1025. }
  1026. // reflect point
  1027. var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);
  1028. return p;
  1029. }
  1030. this.makeAbsolute = function(p) {
  1031. if (this.isRelativeCommand()) {
  1032. p.x = this.current.x + p.x;
  1033. p.y = this.current.y + p.y;
  1034. }
  1035. return p;
  1036. }
  1037. this.addMarker = function(p, from, priorTo) {
  1038. // if the last angle isn't filled in because we didn't have this point yet ...
  1039. if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {
  1040. this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);
  1041. }
  1042. this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
  1043. }
  1044. this.addMarkerAngle = function(p, a) {
  1045. this.points.push(p);
  1046. this.angles.push(a);
  1047. }
  1048. this.getMarkerPoints = function() { return this.points; }
  1049. this.getMarkerAngles = function() {
  1050. for (var i=0; i<this.angles.length; i++) {
  1051. if (this.angles[i] == null) {
  1052. for (var j=i+1; j<this.angles.length; j++) {
  1053. if (this.angles[j] != null) {
  1054. this.angles[i] = this.angles[j];
  1055. break;
  1056. }
  1057. }
  1058. }
  1059. }
  1060. return this.angles;
  1061. }
  1062. })(d);
  1063. this.path = function(ctx) {
  1064. var pp = this.PathParser;
  1065. pp.reset();
  1066. var bb = new svg.BoundingBox();
  1067. if (ctx != null) ctx.beginPath();
  1068. while (!pp.isEnd()) {
  1069. pp.nextCommand();
  1070. switch (pp.command.toUpperCase()) {
  1071. case 'M':
  1072. var p = pp.getAsCurrentPoint();
  1073. pp.addMarker(p);
  1074. bb.addPoint(p.x, p.y);
  1075. if (ctx != null) ctx.moveTo(p.x, p.y);
  1076. pp.start = pp.current;
  1077. while (!pp.isCommandOrEnd()) {
  1078. var p = pp.getAsCurrentPoint();
  1079. pp.addMarker(p, pp.start);
  1080. bb.addPoint(p.x, p.y);
  1081. if (ctx != null) ctx.lineTo(p.x, p.y);
  1082. }
  1083. break;
  1084. case 'L':
  1085. while (!pp.isCommandOrEnd()) {
  1086. var c = pp.current;
  1087. var p = pp.getAsCurrentPoint();
  1088. pp.addMarker(p, c);
  1089. bb.addPoint(p.x, p.y);
  1090. if (ctx != null) ctx.lineTo(p.x, p.y);
  1091. }
  1092. break;
  1093. case 'H':
  1094. while (!pp.isCommandOrEnd()) {
  1095. var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
  1096. pp.addMarker(newP, pp.current);
  1097. pp.current = newP;
  1098. bb.addPoint(pp.current.x, pp.current.y);
  1099. if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
  1100. }
  1101. break;
  1102. case 'V':
  1103. while (!pp.isCommandOrEnd()) {
  1104. var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
  1105. pp.addMarker(newP, pp.current);
  1106. pp.current = newP;
  1107. bb.addPoint(pp.current.x, pp.current.y);
  1108. if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
  1109. }
  1110. break;
  1111. case 'C':
  1112. while (!pp.isCommandOrEnd()) {
  1113. var curr = pp.current;
  1114. var p1 = pp.getPoint();
  1115. var cntrl = pp.getAsControlPoint();
  1116. var cp = pp.getAsCurrentPoint();
  1117. pp.addMarker(cp, cntrl, p1);
  1118. bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
  1119. if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
  1120. }
  1121. break;
  1122. case 'S':
  1123. while (!pp.isCommandOrEnd()) {
  1124. var curr = pp.current;
  1125. var p1 = pp.getReflectedControlPoint();
  1126. var cntrl = pp.getAsControlPoint();
  1127. var cp = pp.getAsCurrentPoint();
  1128. pp.addMarker(cp, cntrl, p1);
  1129. bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
  1130. if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
  1131. }
  1132. break;
  1133. case 'Q':
  1134. while (!pp.isCommandOrEnd()) {
  1135. var curr = pp.current;
  1136. var cntrl = pp.getAsControlPoint();
  1137. var cp = pp.getAsCurrentPoint();
  1138. pp.addMarker(cp, cntrl, cntrl);
  1139. bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
  1140. if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
  1141. }
  1142. break;
  1143. case 'T':
  1144. while (!pp.isCommandOrEnd()) {
  1145. var curr = pp.current;
  1146. var cntrl = pp.getReflectedControlPoint();
  1147. pp.control = cntrl;
  1148. var cp = pp.getAsCurrentPoint();
  1149. pp.addMarker(cp, cntrl, cntrl);
  1150. bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
  1151. if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
  1152. }
  1153. break;
  1154. case 'A':
  1155. while (!pp.isCommandOrEnd()) {
  1156. var curr = pp.current;
  1157. var rx = pp.getScalar();
  1158. var ry = pp.getScalar();
  1159. var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
  1160. var largeArcFlag = pp.getScalar();
  1161. var sweepFlag = pp.getScalar();
  1162. var cp = pp.getAsCurrentPoint();
  1163. // Conversion from endpoint to center parameterization
  1164. // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
  1165. // x1', y1'
  1166. var currp = new svg.Point(
  1167. Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
  1168. -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
  1169. );
  1170. // adjust radii
  1171. var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
  1172. if (l > 1) {
  1173. rx *= Math.sqrt(l);
  1174. ry *= Math.sqrt(l);
  1175. }
  1176. // cx', cy'
  1177. var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
  1178. ((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /
  1179. (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
  1180. );
  1181. if (isNaN(s)) s = 0;
  1182. var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
  1183. // cx, cy
  1184. var centp = new svg.Point(
  1185. (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
  1186. (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
  1187. );
  1188. // vector magnitude
  1189. var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
  1190. // ratio between two vectors
  1191. var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
  1192. // angle between two vectors
  1193. var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
  1194. // initial angle
  1195. var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
  1196. // angle delta
  1197. var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
  1198. var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
  1199. var ad = a(u, v);
  1200. if (r(u,v) <= -1) ad = Math.PI;
  1201. if (r(u,v) >= 1) ad = 0;
  1202. if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;
  1203. if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;
  1204. // for markers
  1205. var halfWay = new svg.Point(
  1206. centp.x - rx * Math.cos((a1 + ad) / 2),
  1207. centp.y - ry * Math.sin((a1 + ad) / 2)
  1208. );
  1209. pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
  1210. pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
  1211. bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
  1212. if (ctx != null) {
  1213. var r = rx > ry ? rx : ry;
  1214. var sx = rx > ry ? 1 : rx / ry;
  1215. var sy = rx > ry ? ry / rx : 1;
  1216. ctx.translate(centp.x, centp.y);
  1217. ctx.rotate(xAxisRotation);
  1218. ctx.scale(sx, sy);
  1219. ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
  1220. ctx.scale(1/sx, 1/sy);
  1221. ctx.rotate(-xAxisRotation);
  1222. ctx.translate(-centp.x, -centp.y);
  1223. }
  1224. }
  1225. break;
  1226. case 'Z':
  1227. if (ctx != null) ctx.closePath();
  1228. pp.current = pp.start;
  1229. }
  1230. }
  1231. return bb;
  1232. }
  1233. this.getMarkers = function() {
  1234. var points = this.PathParser.getMarkerPoints();
  1235. var angles = this.PathParser.getMarkerAngles();
  1236. var markers = [];
  1237. for (var i=0; i<points.length; i++) {
  1238. markers.push([points[i], angles[i]]);
  1239. }
  1240. return markers;
  1241. }
  1242. }
  1243. svg.Element.path.prototype = new svg.Element.PathElementBase;
  1244. // pattern element
  1245. svg.Element.pattern = function(node) {
  1246. this.base = svg.Element.ElementBase;
  1247. this.base(node);
  1248. this.createPattern = function(ctx, element) {
  1249. // render me using a temporary svg element
  1250. var tempSvg = new svg.Element.svg();
  1251. tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
  1252. tempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);
  1253. tempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);
  1254. tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);
  1255. tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);
  1256. tempSvg.children = this.children;
  1257. var c = document.createElement('canvas');
  1258. c.width = this.attribute('width').Length.toPixels('x');
  1259. c.height = this.attribute('height').Length.toPixels('y');
  1260. tempSvg.render(c.getContext('2d'));
  1261. return ctx.createPattern(c, 'repeat');
  1262. }
  1263. }
  1264. svg.Element.pattern.prototype = new svg.Element.ElementBase;
  1265. // marker element
  1266. svg.Element.marker = function(node) {
  1267. this.base = svg.Element.ElementBase;
  1268. this.base(node);
  1269. this.baseRender = this.render;
  1270. this.render = function(ctx, point, angle) {
  1271. ctx.translate(point.x, point.y);
  1272. if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
  1273. if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
  1274. ctx.save();
  1275. // render me using a temporary svg element
  1276. var tempSvg = new svg.Element.svg();
  1277. tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
  1278. tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
  1279. tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
  1280. tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
  1281. tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
  1282. tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
  1283. tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
  1284. tempSvg.children = this.children;
  1285. tempSvg.render(ctx);
  1286. ctx.restore();
  1287. if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
  1288. if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
  1289. ctx.translate(-point.x, -point.y);
  1290. }
  1291. }
  1292. svg.Element.marker.prototype = new svg.Element.ElementBase;
  1293. // definitions element
  1294. svg.Element.defs = function(node) {
  1295. this.base = svg.Element.ElementBase;
  1296. this.base(node);
  1297. this.render = function(ctx) {
  1298. // NOOP
  1299. }
  1300. }
  1301. svg.Element.defs.prototype = new svg.Element.ElementBase;
  1302. // base for gradients
  1303. svg.Element.GradientBase = function(node) {
  1304. this.base = svg.Element.ElementBase;
  1305. this.base(node);
  1306. this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
  1307. this.stops = [];
  1308. for (var i=0; i<this.children.length; i++) {
  1309. var child = this.children[i];
  1310. this.stops.push(child);
  1311. }
  1312. this.getGradient = function() {
  1313. // OVERRIDE ME!
  1314. }
  1315. this.createGradient = function(ctx, element) {
  1316. var stopsContainer = this;
  1317. if (this.attribute('xlink:href').hasValue()) {
  1318. stopsContainer = this.attribute('xlink:href').Definition.getDefinition();
  1319. }
  1320. var g = this.getGradient(ctx, element);
  1321. for (var i=0; i<stopsContainer.stops.length; i++) {
  1322. g.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);
  1323. }
  1324. if (this.attribute('gradientTransform').hasValue()) {
  1325. // render as transformed pattern on temporary canvas
  1326. var rootView = svg.ViewPort.viewPorts[0];
  1327. var rect = new svg.Element.rect();
  1328. rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);
  1329. rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);
  1330. rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
  1331. rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
  1332. var group = new svg.Element.g();
  1333. group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);
  1334. group.children = [ rect ];
  1335. var tempSvg = new svg.Element.svg();
  1336. tempSvg.attributes['x'] = new svg.Property('x', 0);
  1337. tempSvg.attributes['y'] = new svg.Property('y', 0);
  1338. tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
  1339. tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
  1340. tempSvg.children = [ group ];
  1341. var c = document.createElement('canvas');
  1342. c.width = rootView.width;
  1343. c.height = rootView.height;
  1344. var tempCtx = c.getContext('2d');
  1345. tempCtx.fillStyle = g;
  1346. tempSvg.render(tempCtx);
  1347. return tempCtx.createPattern(c, 'no-repeat');
  1348. }
  1349. return g;
  1350. }
  1351. }
  1352. svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
  1353. // linear gradient element
  1354. svg.Element.linearGradient = function(node) {
  1355. this.base = svg.Element.GradientBase;
  1356. this.base(node);
  1357. this.getGradient = function(ctx, element) {
  1358. var bb = element.getBoundingBox();
  1359. var x1 = (this.gradientUnits == 'objectBoundingBox'
  1360. ? bb.x() + bb.width() * this.attribute('x1').numValue()
  1361. : this.attribute('x1').Length.toPixels('x'));
  1362. var y1 = (this.gradientUnits == 'objectBoundingBox'
  1363. ? bb.y() + bb.height() * this.attribute('y1').numValue()
  1364. : this.attribute('y1').Length.toPixels('y'));
  1365. var x2 = (this.gradientUnits == 'objectBoundingBox'
  1366. ? bb.x() + bb.width() * this.attribute('x2').numValue()
  1367. : this.attribute('x2').Length.toPixels('x'));
  1368. var y2 = (this.gradientUnits == 'objectBoundingBox'
  1369. ? bb.y() + bb.height() * this.attribute('y2').numValue()
  1370. : this.attribute('y2').Length.toPixels('y'));
  1371. return ctx.createLinearGradient(x1, y1, x2, y2);
  1372. }
  1373. }
  1374. svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
  1375. // radial gradient element
  1376. svg.Element.radialGradient = function(node) {
  1377. this.base = svg.Element.GradientBase;
  1378. this.base(node);
  1379. this.getGradient = function(ctx, element) {
  1380. var bb = element.getBoundingBox();
  1381. var cx = (this.gradientUnits == 'objectBoundingBox'
  1382. ? bb.x() + bb.width() * this.attribute('cx').numValue()
  1383. : this.attribute('cx').Length.toPixels('x'));
  1384. var cy = (this.gradientUnits == 'objectBoundingBox'
  1385. ? bb.y() + bb.height() * this.attribute('cy').numValue()
  1386. : this.attribute('cy').Length.toPixels('y'));
  1387. var fx = cx;
  1388. var fy = cy;
  1389. if (this.attribute('fx').hasValue()) {
  1390. fx = (this.gradientUnits == 'objectBoundingBox'
  1391. ? bb.x() + bb.width() * this.attribute('fx').numValue()
  1392. : this.attribute('fx').Length.toPixels('x'));
  1393. }
  1394. if (this.attribute('fy').hasValue()) {
  1395. fy = (this.gradientUnits == 'objectBoundingBox'
  1396. ? bb.y() + bb.height() * this.attribute('fy').numValue()
  1397. : this.attribute('fy').Length.toPixels('y'));
  1398. }
  1399. var r = (this.gradientUnits == 'objectBoundingBox'
  1400. ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
  1401. : this.attribute('r').Length.toPixels());
  1402. return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);
  1403. }
  1404. }
  1405. svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
  1406. // gradient stop element
  1407. svg.Element.stop = function(node) {
  1408. this.base = svg.Element.ElementBase;
  1409. this.base(node);
  1410. this.offset = this.attribute('offset').numValue();
  1411. var stopColor = this.style('stop-color');
  1412. if (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);
  1413. this.color = stopColor.value;
  1414. }
  1415. svg.Element.stop.prototype = new svg.Element.ElementBase;
  1416. // animation base element
  1417. svg.Element.AnimateBase = function(node) {
  1418. this.base = svg.Element.ElementBase;
  1419. this.base(node);
  1420. svg.Animations.push(this);
  1421. this.duration = 0.0;
  1422. this.begin = this.attribute('begin').Time.toMilliseconds();
  1423. this.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();
  1424. this.getProperty = function() {
  1425. var attributeType = this.attribute('attributeType').value;
  1426. var attributeName = this.attribute('attributeName').value;
  1427. if (attributeType == 'CSS') {
  1428. return this.parent.style(attributeName, true);
  1429. }
  1430. return this.parent.attribute(attributeName, true);
  1431. };
  1432. this.initialValue = null;
  1433. this.removed = false;
  1434. this.calcValue = function() {
  1435. // OVERRIDE ME!
  1436. return '';
  1437. }
  1438. this.update = function(delta) {
  1439. // set initial value
  1440. if (this.initialValue == null) {
  1441. this.initialValue = this.getProperty().value;
  1442. }
  1443. // if we're past the end time
  1444. if (this.duration > this.maxDuration) {
  1445. // loop for indefinitely repeating animations
  1446. if (this.attribute('repeatCount').value == 'indefinite') {
  1447. this.duration = 0.0
  1448. }
  1449. else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
  1450. this.removed = true;
  1451. this.getProperty().value = this.initialValue;
  1452. return true;
  1453. }
  1454. else {
  1455. return false; // no updates made
  1456. }
  1457. }
  1458. this.duration = this.duration + delta;
  1459. // if we're past the begin time
  1460. var updated = false;
  1461. if (this.begin < this.duration) {
  1462. var newValue = this.calcValue(); // tween
  1463. if (this.attribute('type').hasValue()) {
  1464. // for transform, etc.
  1465. var type = this.attribute('type').value;
  1466. newValue = type + '(' + newValue + ')';
  1467. }
  1468. this.getProperty().value = newValue;
  1469. updated = true;
  1470. }
  1471. return updated;
  1472. }
  1473. // fraction of duration we've covered
  1474. this.progress = function() {
  1475. return ((this.duration - this.begin) / (this.maxDuration - this.begin));
  1476. }
  1477. }
  1478. svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
  1479. // animate element
  1480. svg.Element.animate = function(node) {
  1481. this.base = svg.Element.AnimateBase;
  1482. this.base(node);
  1483. this.calcValue = function() {
  1484. var from = this.attribute('from').numValue();
  1485. var to = this.attribute('to').numValue();
  1486. // tween value linearly
  1487. return from + (to - from) * this.progress();
  1488. };
  1489. }
  1490. svg.Element.animate.prototype = new svg.Element.AnimateBase;
  1491. // animate color element
  1492. svg.Element.animateColor = function(node) {
  1493. this.base = svg.Element.AnimateBase;
  1494. this.base(node);
  1495. this.calcValue = function() {
  1496. var from = new RGBColor(this.attribute('from').value);
  1497. var to = new RGBColor(this.attribute('to').value);
  1498. if (from.ok && to.ok) {
  1499. // tween color linearly
  1500. var r = from.r + (to.r - from.r) * this.progress();
  1501. var g = from.g + (to.g - from.g) * this.progress();
  1502. var b = from.b + (to.b - from.b) * this.progress();
  1503. return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
  1504. }
  1505. return this.attribute('from').value;
  1506. };
  1507. }
  1508. svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
  1509. // animate transform element
  1510. svg.Element.animateTransform = function(node) {
  1511. this.base = svg.Element.animate;
  1512. this.base(node);
  1513. }
  1514. svg.Element.animateTransform.prototype = new svg.Element.animate;
  1515. // font element
  1516. svg.Element.font = function(node) {
  1517. this.base = svg.Element.ElementBase;
  1518. this.base(node);
  1519. this.horizAdvX = this.attribute('horiz-adv-x').numValue();
  1520. this.isRTL = false;
  1521. this.isArabic = false;
  1522. this.fontFace = null;
  1523. this.missingGlyph = null;
  1524. this.glyphs = [];
  1525. for (var i=0; i<this.children.length; i++) {
  1526. var child = this.children[i];
  1527. if (child.type == 'font-face') {
  1528. this.fontFace = child;
  1529. if (child.style('font-family').hasValue()) {
  1530. svg.Definitions[child.style('font-family').value] = this;
  1531. }
  1532. }
  1533. else if (child.type == 'missing-glyph') this.missingGlyph = child;
  1534. else if (child.type == 'glyph') {
  1535. if (child.arabicForm != '') {
  1536. this.isRTL = true;
  1537. this.isArabic = true;
  1538. if (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];
  1539. this.glyphs[child.unicode][child.arabicForm] = child;
  1540. }
  1541. else {
  1542. this.glyphs[child.unicode] = child;
  1543. }
  1544. }
  1545. }
  1546. }
  1547. svg.Element.font.prototype = new svg.Element.ElementBase;
  1548. // font-face element
  1549. svg.Element.fontface = function(node) {
  1550. this.base = svg.Element.ElementBase;
  1551. this.base(node);
  1552. this.ascent = this.attribute('ascent').value;
  1553. this.descent = this.attribute('descent').value;
  1554. this.unitsPerEm = this.attribute('units-per-em').numValue();
  1555. }
  1556. svg.Element.fontface.prototype = new svg.Element.ElementBase;
  1557. // missing-glyph element
  1558. svg.Element.missingglyph = function(node) {
  1559. this.base = svg.Element.path;
  1560. this.base(node);
  1561. this.horizAdvX = 0;
  1562. }
  1563. svg.Element.missingglyph.prototype = new svg.Element.path;
  1564. // glyph element
  1565. svg.Element.glyph = function(node) {
  1566. this.base = svg.Element.path;
  1567. this.base(node);
  1568. this.horizAdvX = this.attribute('horiz-adv-x').numValue();
  1569. this.unicode = this.attribute('unicode').value;
  1570. this.arabicForm = this.attribute('arabic-form').value;
  1571. }
  1572. svg.Element.glyph.prototype = new svg.Element.path;
  1573. // text element
  1574. svg.Element.text = function(node) {
  1575. this.base = svg.Element.RenderedElementBase;
  1576. this.base(node);
  1577. if (node != null) {
  1578. // add children
  1579. this.children = [];
  1580. for (var i=0; i<node.childNodes.length; i++) {
  1581. var childNode = node.childNodes[i];
  1582. if (childNode.nodeType == 1) { // capture tspan and tref nodes
  1583. this.addChild(childNode, true);
  1584. }
  1585. else if (childNode.nodeType == 3) { // capture text
  1586. this.addChild(new svg.Element.tspan(childNode), false);
  1587. }
  1588. }
  1589. }
  1590. this.baseSetContext = this.setContext;
  1591. this.setContext = function(ctx) {
  1592. this.baseSetContext(ctx);
  1593. if (this.style('dominant-baseline').hasValue()) ctx.textBaseline = this.style('dominant-baseline').value;
  1594. if (this.style('alignment-baseline').hasValue()) ctx.textBaseline = this.style('alignment-baseline').value;
  1595. }
  1596. this.renderChildren = function(ctx) {
  1597. var textAnchor = this.style('text-anchor').valueOrDefault('start');
  1598. var x = this.attribute('x').Length.toPixels('x');
  1599. var y = this.attribute('y').Length.toPixels('y');
  1600. for (var i=0; i<this.children.length; i++) {
  1601. var child = this.children[i];
  1602. if (child.attribute('x').hasValue()) {
  1603. child.x = child.attribute('x').Length.toPixels('x');
  1604. }
  1605. else {
  1606. if (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');
  1607. child.x = x;
  1608. }
  1609. var childLength = child.measureText(ctx);
  1610. if (textAnchor != 'start' && (i==0 || child.attribute('x').hasValue())) { // new group?
  1611. // loop through rest of children
  1612. var groupLength = childLength;
  1613. for (var j=i+1; j<this.children.length; j++) {
  1614. var childInGroup = this.children[j];
  1615. if (childInGroup.attribute('x').hasValue()) break; // new group
  1616. groupLength += childInGroup.measureText(ctx);
  1617. }
  1618. child.x -= (textAnchor == 'end' ? groupLength : groupLength / 2.0);
  1619. }
  1620. x = child.x + childLength;
  1621. if (child.attribute('y').hasValue()) {
  1622. child.y = child.attribute('y').Length.toPixels('y');
  1623. }
  1624. else {
  1625. if (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');
  1626. child.y = y;
  1627. }
  1628. y = child.y;
  1629. child.render(ctx);
  1630. }
  1631. }
  1632. }
  1633. svg.Element.text.prototype = new svg.Element.RenderedElementBase;
  1634. // text base
  1635. svg.Element.TextElementBase = function(node) {
  1636. this.base = svg.Element.RenderedElementBase;
  1637. this.base(node);
  1638. this.getGlyph = function(font, text, i) {
  1639. var c = text[i];
  1640. var glyph = null;
  1641. if (font.isArabic) {
  1642. var arabicForm = 'isolated';
  1643. if ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal';
  1644. if (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';
  1645. if (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';
  1646. if (typeof(font.glyphs[c]) != 'undefined') {
  1647. glyph = font.glyphs[c][arabicForm];
  1648. if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];
  1649. }
  1650. }
  1651. else {
  1652. glyph = font.glyphs[c];
  1653. }
  1654. if (glyph == null) glyph = font.missingGlyph;
  1655. return glyph;
  1656. }
  1657. this.renderChildren = function(ctx) {
  1658. var customFont = this.parent.style('font-family').Definition.getDefinition();
  1659. if (customFont != null) {
  1660. var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
  1661. var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
  1662. var text = this.getText();
  1663. if (customFont.isRTL) text = text.split("").reverse().join("");
  1664. var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
  1665. for (var i=0; i<text.length; i++) {
  1666. var glyph = this.getGlyph(customFont, text, i);
  1667. var scale = fontSize / customFont.fontFace.unitsPerEm;
  1668. ctx.translate(this.x, this.y);
  1669. ctx.scale(scale, -scale);
  1670. var lw = ctx.lineWidth;
  1671. ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;
  1672. if (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);
  1673. glyph.render(ctx);
  1674. if (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);
  1675. ctx.lineWidth = lw;
  1676. ctx.scale(1/scale, -1/scale);
  1677. ctx.translate(-this.x, -this.y);
  1678. this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;
  1679. if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
  1680. this.x += dx[i];
  1681. }
  1682. }
  1683. return;
  1684. }
  1685. if (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);
  1686. if (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
  1687. }
  1688. this.getText = function() {
  1689. // OVERRIDE ME
  1690. }
  1691. this.measureText = function(ctx) {
  1692. var customFont = this.parent.style('font-family').Definition.getDefinition();
  1693. if (customFont != null) {
  1694. var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
  1695. var measure = 0;
  1696. var text = this.getText();
  1697. if (customFont.isRTL) text = text.split("").reverse().join("");
  1698. var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
  1699. for (var i=0; i<text.length; i++) {
  1700. var glyph = this.getGlyph(customFont, text, i);
  1701. measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
  1702. if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
  1703. measure += dx[i];
  1704. }
  1705. }
  1706. return measure;
  1707. }
  1708. var textToMeasure = svg.compressSpaces(this.getText());
  1709. if (!ctx.measureText) return textToMeasure.length * 10;
  1710. ctx.save();
  1711. this.setContext(ctx);
  1712. var width = ctx.measureText(textToMeasure).width;
  1713. ctx.restore();
  1714. return width;
  1715. }
  1716. }
  1717. svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
  1718. // tspan
  1719. svg.Element.tspan = function(node) {
  1720. this.base = svg.Element.TextElementBase;
  1721. this.base(node);
  1722. this.text = node.nodeType == 3 ? node.nodeValue : // text
  1723. node.childNodes.length > 0 ? node.childNodes[0].nodeValue : // element
  1724. node.text;
  1725. this.getText = function() {
  1726. return this.text;
  1727. }
  1728. }
  1729. svg.Element.tspan.prototype = new svg.Element.TextElementBase;
  1730. // tref
  1731. svg.Element.tref = function(node) {
  1732. this.base = svg.Element.TextElementBase;
  1733. this.base(node);
  1734. this.getText = function() {
  1735. var element = this.attribute('xlink:href').Definition.getDefinition();
  1736. if (element != null) return element.children[0].getText();
  1737. }
  1738. }
  1739. svg.Element.tref.prototype = new svg.Element.TextElementBase;
  1740. // a element
  1741. svg.Element.a = function(node) {
  1742. this.base = svg.Element.TextElementBase;
  1743. this.base(node);
  1744. this.hasText = true;
  1745. for (var i=0; i<node.childNodes.length; i++) {
  1746. if (node.childNodes[i].nodeType != 3) this.hasText = false;
  1747. }
  1748. // this might contain text
  1749. this.text = this.hasText ? node.childNodes[0].nodeValue : '';
  1750. this.getText = function() {
  1751. return this.text;
  1752. }
  1753. this.baseRenderChildren = this.renderChildren;
  1754. this.renderChildren = function(ctx) {
  1755. if (this.hasText) {
  1756. // render as text element
  1757. this.baseRenderChildren(ctx);
  1758. var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
  1759. svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));
  1760. }
  1761. else {
  1762. // render as temporary group
  1763. var g = new svg.Element.g();
  1764. g.children = this.children;
  1765. g.parent = this;
  1766. g.render(ctx);
  1767. }
  1768. }
  1769. this.onclick = function() {
  1770. window.open(this.attribute('xlink:href').value);
  1771. }
  1772. this.onmousemove = function() {
  1773. svg.ctx.canvas.style.cursor = 'pointer';
  1774. }
  1775. }
  1776. svg.Element.a.prototype = new svg.Element.TextElementBase;
  1777. // image element
  1778. svg.Element.image = function(node) {
  1779. this.base = svg.Element.RenderedElementBase;
  1780. this.base(node);
  1781. svg.Images.push(this);
  1782. this.img = document.createElement('img');
  1783. this.loaded = false;
  1784. var that = this;
  1785. this.img.onload = function() { that.loaded = true; }
  1786. this.img.src = this.attribute('xlink:href').value;
  1787. this.renderChildren = function(ctx) {
  1788. var x = this.attribute('x').Length.toPixels('x');
  1789. var y = this.attribute('y').Length.toPixels('y');
  1790. var width = this.attribute('width').Length.toPixels('x');
  1791. var height = this.attribute('height').Length.toPixels('y');
  1792. if (width == 0 || height == 0) return;
  1793. ctx.save();
  1794. ctx.translate(x, y);
  1795. svg.AspectRatio(ctx,
  1796. this.attribute('preserveAspectRatio').value,
  1797. width,
  1798. this.img.width,
  1799. height,
  1800. this.img.height,
  1801. 0,
  1802. 0);
  1803. ctx.drawImage(this.img, 0, 0);
  1804. ctx.restore();
  1805. }
  1806. }
  1807. svg.Element.image.prototype = new svg.Element.RenderedElementBase;
  1808. // group element
  1809. svg.Element.g = function(node) {
  1810. this.base = svg.Element.RenderedElementBase;
  1811. this.base(node);
  1812. this.getBoundingBox = function() {
  1813. var bb = new svg.BoundingBox();
  1814. for (var i=0; i<this.children.length; i++) {
  1815. bb.addBoundingBox(this.children[i].getBoundingBox());
  1816. }
  1817. return bb;
  1818. };
  1819. }
  1820. svg.Element.g.prototype = new svg.Element.RenderedElementBase;
  1821. // symbol element
  1822. svg.Element.symbol = function(node) {
  1823. this.base = svg.Element.RenderedElementBase;
  1824. this.base(node);
  1825. this.baseSetContext = this.setContext;
  1826. this.setContext = function(ctx) {
  1827. this.baseSetContext(ctx);
  1828. // viewbox
  1829. if (this.attribute('viewBox').hasValue()) {
  1830. var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
  1831. var minX = viewBox[0];
  1832. var minY = viewBox[1];
  1833. width = viewBox[2];
  1834. height = viewBox[3];
  1835. svg.AspectRatio(ctx,
  1836. this.attribute('preserveAspectRatio').value,
  1837. this.attribute('width').Length.toPixels('x'),
  1838. width,
  1839. this.attribute('height').Length.toPixels('y'),
  1840. height,
  1841. minX,
  1842. minY);
  1843. svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
  1844. }
  1845. }
  1846. }
  1847. svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;
  1848. // style element
  1849. svg.Element.style = function(node) {
  1850. this.base = svg.Element.ElementBase;
  1851. this.base(node);
  1852. // text, or spaces then CDATA
  1853. var css = node.childNodes[0].nodeValue + (node.childNodes.length > 1 ? node.childNodes[1].nodeValue : '');
  1854. css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments
  1855. css = svg.compressSpaces(css); // replace whitespace
  1856. var cssDefs = css.split('}');
  1857. for (var i=0; i<cssDefs.length; i++) {
  1858. if (svg.trim(cssDefs[i]) != '') {
  1859. var cssDef = cssDefs[i].split('{');
  1860. var cssClasses = cssDef[0].split(',');
  1861. var cssProps = cssDef[1].split(';');
  1862. for (var j=0; j<cssClasses.length; j++) {
  1863. var cssClass = svg.trim(cssClasses[j]);
  1864. if (cssClass != '') {
  1865. var props = {};
  1866. for (var k=0; k<cssProps.length; k++) {
  1867. var prop = cssProps[k].indexOf(':');
  1868. var name = cssProps[k].substr(0, prop);
  1869. var value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);
  1870. if (name != null && value != null) {
  1871. props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
  1872. }
  1873. }
  1874. svg.Styles[cssClass] = props;
  1875. if (cssClass == '@font-face') {
  1876. var fontFamily = props['font-family'].value.replace(/"/g,'');
  1877. var srcs = props['src'].value.split(',');
  1878. for (var s=0; s<srcs.length; s++) {
  1879. if (srcs[s].indexOf('format("svg")') > 0) {
  1880. var urlStart = srcs[s].indexOf('url');
  1881. var urlEnd = srcs[s].indexOf(')', urlStart);
  1882. var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);
  1883. var doc = svg.parseXml(svg.ajax(url));
  1884. var fonts = doc.getElementsByTagName('font');
  1885. for (var f=0; f<fonts.length; f++) {
  1886. var font = svg.CreateElement(fonts[f]);
  1887. svg.Definitions[fontFamily] = font;
  1888. }
  1889. }
  1890. }
  1891. }
  1892. }
  1893. }
  1894. }
  1895. }
  1896. }
  1897. svg.Element.style.prototype = new svg.Element.ElementBase;
  1898. // use element
  1899. svg.Element.use = function(node) {
  1900. this.base = svg.Element.RenderedElementBase;
  1901. this.base(node);
  1902. this.baseSetContext = this.setContext;
  1903. this.setContext = function(ctx) {
  1904. this.baseSetContext(ctx);
  1905. if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);
  1906. if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));
  1907. }
  1908. this.getDefinition = function() {
  1909. var element = this.attribute('xlink:href').Definition.getDefinition();
  1910. if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
  1911. if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
  1912. return element;
  1913. }
  1914. this.path = function(ctx) {
  1915. var element = this.getDefinition();
  1916. if (element != null) element.path(ctx);
  1917. }
  1918. this.renderChildren = function(ctx) {
  1919. var element = this.getDefinition();
  1920. if (element != null) element.render(ctx);
  1921. }
  1922. }
  1923. svg.Element.use.prototype = new svg.Element.RenderedElementBase;
  1924. // mask element
  1925. svg.Element.mask = function(node) {
  1926. this.base = svg.Element.ElementBase;
  1927. this.base(node);
  1928. this.apply = function(ctx, element) {
  1929. // render as temp svg
  1930. var x = this.attribute('x').Length.toPixels('x');
  1931. var y = this.attribute('y').Length.toPixels('y');
  1932. var width = this.attribute('width').Length.toPixels('x');
  1933. var height = this.attribute('height').Length.toPixels('y');
  1934. // temporarily remove mask to avoid recursion
  1935. var mask = element.attribute('mask').value;
  1936. element.attribute('mask').value = '';
  1937. var cMask = document.createElement('canvas');
  1938. cMask.width = x + width;
  1939. cMask.height = y + height;
  1940. var maskCtx = cMask.getContext('2d');
  1941. this.renderChildren(maskCtx);
  1942. var c = document.createElement('canvas');
  1943. c.width = x + width;
  1944. c.height = y + height;
  1945. var tempCtx = c.getContext('2d');
  1946. element.render(tempCtx);
  1947. tempCtx.globalCompositeOperation = 'destination-in';
  1948. tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');
  1949. tempCtx.fillRect(0, 0, x + width, y + height);
  1950. ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');
  1951. ctx.fillRect(0, 0, x + width, y + height);
  1952. // reassign mask
  1953. element.attribute('mask').value = mask;
  1954. }
  1955. this.render = function(ctx) {
  1956. // NO RENDER
  1957. }
  1958. }
  1959. svg.Element.mask.prototype = new svg.Element.ElementBase;
  1960. // clip element
  1961. svg.Element.clipPath = function(node) {
  1962. this.base = svg.Element.ElementBase;
  1963. this.base(node);
  1964. this.apply = function(ctx) {
  1965. for (var i=0; i<this.children.length; i++) {
  1966. if (this.children[i].path) {
  1967. this.children[i].path(ctx);
  1968. ctx.clip();
  1969. }
  1970. }
  1971. }
  1972. this.render = function(ctx) {
  1973. // NO RENDER
  1974. }
  1975. }
  1976. svg.Element.clipPath.prototype = new svg.Element.ElementBase;
  1977. // filters
  1978. svg.Element.filter = function(node) {
  1979. this.base = svg.Element.ElementBase;
  1980. this.base(node);
  1981. this.apply = function(ctx, element) {
  1982. // render as temp svg
  1983. var bb = element.getBoundingBox();
  1984. var x = this.attribute('x').Length.toPixels('x');
  1985. var y = this.attribute('y').Length.toPixels('y');
  1986. if (x == 0 || y == 0) {
  1987. x = bb.x1;
  1988. y = bb.y1;
  1989. }
  1990. var width = this.attribute('width').Length.toPixels('x');
  1991. var height = this.attribute('height').Length.toPixels('y');
  1992. if (width == 0 || height == 0) {
  1993. width = bb.width();
  1994. height = bb.height();
  1995. }
  1996. // temporarily remove filter to avoid recursion
  1997. var filter = element.style('filter').value;
  1998. element.style('filter').value = '';
  1999. // max filter distance
  2000. var extraPercent = .20;
  2001. var px = extraPercent * width;
  2002. var py = extraPercent * height;
  2003. var c = document.createElement('canvas');
  2004. c.width = width + 2*px;
  2005. c.height = height + 2*py;
  2006. var tempCtx = c.getContext('2d');
  2007. tempCtx.translate(-x + px, -y + py);
  2008. element.render(tempCtx);
  2009. // apply filters
  2010. for (var i=0; i<this.children.length; i++) {
  2011. this.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);
  2012. }
  2013. // render on me
  2014. ctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);
  2015. // reassign filter
  2016. element.style('filter', true).value = filter;
  2017. };
  2018. this.render = function(ctx) {
  2019. // NO RENDER
  2020. };
  2021. }
  2022. svg.Element.filter.prototype = new svg.Element.ElementBase;
  2023. svg.Element.feGaussianBlur = function(node) {
  2024. this.base = svg.Element.ElementBase;
  2025. this.base(node);
  2026. function make_fgauss(sigma) {
  2027. sigma = Math.max(sigma, 0.01);
  2028. var len = Math.ceil(sigma * 4.0) + 1;
  2029. mask = [];
  2030. for (var i = 0; i < len; i++) {
  2031. mask[i] = Math.exp(-0.5 * (i / sigma) * (i / sigma));
  2032. }
  2033. return mask;
  2034. }
  2035. function normalize(mask) {
  2036. var sum = 0;
  2037. for (var i = 1; i < mask.length; i++) {
  2038. sum += Math.abs(mask[i]);
  2039. }
  2040. sum = 2 * sum + Math.abs(mask[0]);
  2041. for (var i = 0; i < mask.length; i++) {
  2042. mask[i] /= sum;
  2043. }
  2044. return mask;
  2045. }
  2046. function convolve_even(src, dst, mask, width, height) {
  2047. for (var y = 0; y < height; y++) {
  2048. for (var x = 0; x < width; x++) {
  2049. var a = imGet(src, x, y, width, height, 3)/255;
  2050. for (var rgba = 0; rgba < 4; rgba++) {
  2051. var sum = mask[0] * (a==0?255:imGet(src, x, y, width, height, rgba)) * (a==0||rgba==3?1:a);
  2052. for (var i = 1; i < mask.length; i++) {
  2053. var a1 = imGet(src, Math.max(x-i,0), y, width, height, 3)/255;
  2054. var a2 = imGet(src, Math.min(x+i, width-1), y, width, height, 3)/255;
  2055. sum += mask[i] *
  2056. ((a1==0?255:imGet(src, Math.max(x-i,0), y, width, height, rgba)) * (a1==0||rgba==3?1:a1) +
  2057. (a2==0?255:imGet(src, Math.min(x+i, width-1), y, width, height, rgba)) * (a2==0||rgba==3?1:a2));
  2058. }
  2059. imSet(dst, y, x, height, width, rgba, sum);
  2060. }
  2061. }
  2062. }
  2063. }
  2064. function imGet(img, x, y, width, height, rgba) {
  2065. return img[y*width*4 + x*4 + rgba];
  2066. }
  2067. function imSet(img, x, y, width, height, rgba, val) {
  2068. img[y*width*4 + x*4 + rgba] = val;
  2069. }
  2070. function blur(ctx, width, height, sigma)
  2071. {
  2072. var srcData = ctx.getImageData(0, 0, width, height);
  2073. var mask = make_fgauss(sigma);
  2074. mask = normalize(mask);
  2075. tmp = [];
  2076. convolve_even(srcData.data, tmp, mask, width, height);
  2077. convolve_even(tmp, srcData.data, mask, height, width);
  2078. ctx.clearRect(0, 0, width, height);
  2079. ctx.putImageData(srcData, 0, 0);
  2080. }
  2081. this.apply = function(ctx, x, y, width, height) {
  2082. // assuming x==0 && y==0 for now
  2083. blur(ctx, width, height, this.attribute('stdDeviation').numValue());
  2084. }
  2085. }
  2086. svg.Element.filter.prototype = new svg.Element.feGaussianBlur;
  2087. // title element, do nothing
  2088. svg.Element.title = function(node) {
  2089. }
  2090. svg.Element.title.prototype = new svg.Element.ElementBase;
  2091. // desc element, do nothing
  2092. svg.Element.desc = function(node) {
  2093. }
  2094. svg.Element.desc.prototype = new svg.Element.ElementBase;
  2095. svg.Element.MISSING = function(node) {
  2096. console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.');
  2097. }
  2098. svg.Element.MISSING.prototype = new svg.Element.ElementBase;
  2099. // element factory
  2100. svg.CreateElement = function(node) {
  2101. var className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace
  2102. className = className.replace(/\-/g,''); // remove dashes
  2103. var e = null;
  2104. if (typeof(svg.Element[className]) != 'undefined') {
  2105. e = new svg.Element[className](node);
  2106. }
  2107. else {
  2108. e = new svg.Element.MISSING(node);
  2109. }
  2110. e.type = node.nodeName;
  2111. return e;
  2112. }
  2113. // load from url
  2114. svg.load = function(ctx, url) {
  2115. svg.loadXml(ctx, svg.ajax(url));
  2116. }
  2117. // load from xml
  2118. svg.loadXml = function(ctx, xml) {
  2119. svg.loadXmlDoc(ctx, svg.parseXml(xml));
  2120. }
  2121. svg.loadXmlDoc = function(ctx, dom) {
  2122. svg.init(ctx);
  2123. var mapXY = function(p) {
  2124. var e = ctx.canvas;
  2125. while (e) {
  2126. p.x -= e.offsetLeft;
  2127. p.y -= e.offsetTop;
  2128. e = e.offsetParent;
  2129. }
  2130. if (window.scrollX) p.x += window.scrollX;
  2131. if (window.scrollY) p.y += window.scrollY;
  2132. return p;
  2133. }
  2134. // bind mouse
  2135. if (svg.opts['ignoreMouse'] != true) {
  2136. ctx.canvas.onclick = function(e) {
  2137. var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
  2138. svg.Mouse.onclick(p.x, p.y);
  2139. };
  2140. ctx.canvas.onmousemove = function(e) {
  2141. var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
  2142. svg.Mouse.onmousemove(p.x, p.y);
  2143. };
  2144. }
  2145. var e = svg.CreateElement(dom.documentElement);
  2146. e.root = true;
  2147. // render loop
  2148. var isFirstRender = true;
  2149. var draw = function() {
  2150. svg.ViewPort.Clear();
  2151. if (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);
  2152. if (svg.opts['ignoreDimensions'] != true) {
  2153. // set canvas size
  2154. if (e.style('width').hasValue()) {
  2155. ctx.canvas.width = e.style('width').Length.toPixels('x');
  2156. ctx.canvas.style.width = ctx.canvas.width + 'px';
  2157. }
  2158. if (e.style('height').hasValue()) {
  2159. ctx.canvas.height = e.style('height').Length.toPixels('y');
  2160. ctx.canvas.style.height = ctx.canvas.height + 'px';
  2161. }
  2162. }
  2163. var cWidth = ctx.canvas.clientWidth || ctx.canvas.width;
  2164. var cHeight = ctx.canvas.clientHeight || ctx.canvas.height;
  2165. svg.ViewPort.SetCurrent(cWidth, cHeight);
  2166. if (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
  2167. if (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
  2168. if (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
  2169. var xRatio = 1, yRatio = 1;
  2170. if (e.attribute('width').hasValue()) xRatio = e.attribute('width').Length.toPixels('x') / svg.opts['scaleWidth'];
  2171. if (e.attribute('height').hasValue()) yRatio = e.attribute('height').Length.toPixels('y') / svg.opts['scaleHeight'];
  2172. e.attribute('width', true).value = svg.opts['scaleWidth'];
  2173. e.attribute('height', true).value = svg.opts['scaleHeight'];
  2174. e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);
  2175. e.attribute('preserveAspectRatio', true).value = 'none';
  2176. }
  2177. // clear and render
  2178. if (svg.opts['ignoreClear'] != true) {
  2179. ctx.clearRect(0, 0, cWidth, cHeight);
  2180. }
  2181. e.render(ctx);
  2182. if (isFirstRender) {
  2183. isFirstRender = false;
  2184. if (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();
  2185. }
  2186. }
  2187. var waitingForImages = true;
  2188. if (svg.ImagesLoaded()) {
  2189. waitingForImages = false;
  2190. draw();
  2191. }
  2192. svg.intervalID = setInterval(function() {
  2193. var needUpdate = false;
  2194. if (waitingForImages && svg.ImagesLoaded()) {
  2195. waitingForImages = false;
  2196. needUpdate = true;
  2197. }
  2198. // need update from mouse events?
  2199. if (svg.opts['ignoreMouse'] != true) {
  2200. needUpdate = needUpdate | svg.Mouse.hasEvents();
  2201. }
  2202. // need update from animations?
  2203. if (svg.opts['ignoreAnimation'] != true) {
  2204. for (var i=0; i<svg.Animations.length; i++) {
  2205. needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
  2206. }
  2207. }
  2208. // need update from redraw?
  2209. if (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {
  2210. if (svg.opts['forceRedraw']() == true) needUpdate = true;
  2211. }
  2212. // render if needed
  2213. if (needUpdate) {
  2214. draw();
  2215. svg.Mouse.runEvents(); // run and clear our events
  2216. }
  2217. }, 1000 / svg.FRAMERATE);
  2218. }
  2219. svg.stop = function() {
  2220. if (svg.intervalID) {
  2221. clearInterval(svg.intervalID);
  2222. }
  2223. }
  2224. svg.Mouse = new (function() {
  2225. this.events = [];
  2226. this.hasEvents = function() { return this.events.length != 0; }
  2227. this.onclick = function(x, y) {
  2228. this.events.push({ type: 'onclick', x: x, y: y,
  2229. run: function(e) { if (e.onclick) e.onclick(); }
  2230. });
  2231. }
  2232. this.onmousemove = function(x, y) {
  2233. this.events.push({ type: 'onmousemove', x: x, y: y,
  2234. run: function(e) { if (e.onmousemove) e.onmousemove(); }
  2235. });
  2236. }
  2237. this.eventElements = [];
  2238. this.checkPath = function(element, ctx) {
  2239. for (var i=0; i<this.events.length; i++) {
  2240. var e = this.events[i];
  2241. if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
  2242. }
  2243. }
  2244. this.checkBoundingBox = function(element, bb) {
  2245. for (var i=0; i<this.events.length; i++) {
  2246. var e = this.events[i];
  2247. if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
  2248. }
  2249. }
  2250. this.runEvents = function() {
  2251. svg.ctx.canvas.style.cursor = '';
  2252. for (var i=0; i<this.events.length; i++) {
  2253. var e = this.events[i];
  2254. var element = this.eventElements[i];
  2255. while (element) {
  2256. e.run(element);
  2257. element = element.parent;
  2258. }
  2259. }
  2260. // done running, clear
  2261. this.events = [];
  2262. this.eventElements = [];
  2263. }
  2264. });
  2265. return svg;
  2266. }
  2267. })();
  2268. if (CanvasRenderingContext2D) {
  2269. CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
  2270. canvg(this.canvas, s, {
  2271. ignoreMouse: true,
  2272. ignoreAnimation: true,
  2273. ignoreDimensions: true,
  2274. ignoreClear: true,
  2275. offsetX: dx,
  2276. offsetY: dy,
  2277. scaleWidth: dw,
  2278. scaleHeight: dh
  2279. });
  2280. }
  2281. }