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