/** * @author george michael brower / http://georgemichaelbrower.com/ */ function loadSVG(loc, success, fail) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4) { if (xmlhttp.status == 200) { if (xmlhttp.responseXML == null) { if (fail != undefined) fail.call(this, loc); } else { var node = xmlhttp.responseXML.getElementsByTagName("svg").item(0); var svg = new SVG(node); svg.filename = loc; success.call(this, svg, loc); } } else { if (fail != undefined) fail.call(this, loc); } } } xmlhttp.open("GET", loc, true); xmlhttp.send(null); } function createSVG(svgText) { var node = document.createElement("svg"); node.innerHTML = svgText; return new SVG(node); } var SVG = function(node) { this.filename = ""; this.children = []; // TODO interpret things other than pixels. var w = node.getAttribute("width"); var h = node.getAttribute("height"); this.width = w == null ? 0 : parseFloat(w.replace("px", "")); this.height = h == null ? 0 : parseFloat(h.replace("px", "")); for (var i = 0; i < node.childNodes.length; i++) { if (!node.childNodes.item(i).getAttribute) continue; var toAdd; if (node.childNodes.item(i).nodeName == "g") { toAdd = new SVG(node.childNodes.item(i)); } else { toAdd = new Path(node.childNodes.item(i)); } this.children.push(toAdd); } // Draws every path in this SVG to the specified context. this.draw = function(context) { for (var i = 0; i < this.children.length; i++) { this.children[i].draw(context); } }; } var Path = function(element) { this.element = element; this.commands = commands(element); this.lineWidth = parseLineWidth(element); this.strokeStyle = parseStrokeStyle(element); this.fillStyle = parseFillStyle(element); // for at this.totalLength = 0; this.lengths = []; this.tlengths = [] var turtle = function() { this.x; this.y; this.x1; this.y1; this.x2; this.y2; this.reset = function() { this.x = this.y = this.x1 = this.y1 = this.x2 = this.y2 = 0; } this.reset(); } // Draws this entire path to the specified context. this.draw = function(context) { this.style(context); context.beginPath(); this.shape(context); this.end(context); }; // Calls canvas shape methods such as moveTo(), lineTo(), bezierCurveTo() based on the commands in this path. this.shape = function(context) { for (var i = 0; i < this.commands.length; i++) { this.commands[i].shape(turtle, context); } }; this.lerp = function(a,b,c,d,t) { var t1 = 1.0 - t; return a*t1*t1*t1 + 3*b*t*t1*t1 + 3*c*t*t*t1 + d*t*t*t; }; this.at = function(t, c) { var rx, ry; if (this.lengths.length == 0) { this.calcLengths(c); } var tt = this.tlengths[0]; var i = 0; while (t > tt) { i++; tt += this.tlengths[i]; } pt = tt - this.tlengths[i]; var it = this.map(t, pt, tt, 0, 1); for (var j = 0; j <= i; j++) { this.commands[j].shape(turtle, c); } var px = turtle.x; var py = turtle.y; this.commands[i+1].shape(turtle, c); rx = this.lerp(px, turtle.x1, turtle.x2, turtle.x, it); ry = this.lerp(py, turtle.y1, turtle.y2, turtle.y, it); return {x:rx, y:ry}; }; this.map = function(v, i1, i2, o1, o2) { return o1 + (o2 - o1) * ((v - i1) / (i2 - i1)); } this.calcLengths = function(c) { var rx,ry; var prx, pry; // go through and get the length of the entire path // as well as the lengths of each indiv. path var curLength = 0; var lengthAccuracy = 0.001; this.commands[0].shape(turtle, c); var px = prx = turtle.x; var py = pry = turtle.y; for (var i = 1; i < this.commands.length; i++) { curLength = 0; px = turtle.x; py = turtle.y; this.commands[i].shape(turtle, c); for (var tt = 0; tt <=1; tt+= lengthAccuracy) { rx = this.lerp(px, turtle.x1, turtle.x2, turtle.x, tt); ry = this.lerp(py, turtle.y1, turtle.y2, turtle.y, tt); curLength += this.dist(rx, ry, prx, pry); prx = rx; pry = ry; } this.lengths.push(curLength); this.totalLength += curLength; } for (var j = 0; j < this.lengths.length; j++) { this.tlengths.push(this.lengths[j]/this.totalLength); } } this.dist = function (x, y, xx, yy) { return Math.sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy)); }; // Sets the drawing style of the canvas context based on the styles in this Path. this.style = function(context) { if (this.lineWidth != null) { context.lineWidth = this.lineWidth; } if (this.strokeStyle != null) { context.strokeStyle = this.strokeStyle; if (this.lineWidth == undefined) { context.lineWidth = 1; } } if (this.fillStyle != null) { context.fillStyle = this.fillStyle; } }; // Calls context.fill() and/or context.stroke() depending on the styles in this Path. this.end = function(context) { if (this.fillStyle != null) context.fill(); if (this.strokeStyle != null) context.stroke(); } } var parseLineWidth = function(element) { var a = element.attributes.getNamedItem("stroke-width"); return a == null ? null : parseFloat(a.nodeValue); } var parseStrokeStyle = function(element) { var a = element.attributes.getNamedItem("stroke"); return a == null ? null : a.nodeValue; } var parseFillStyle = function(element) { var a = element.attributes.getNamedItem("fill"); if (a == null) { var s = element.attributes.getNamedItem("stroke"); if (s != null) { return null; } else { return "#000000"; } } else { if (a.nodeValue == "none") return null; return a.nodeValue; } } var Command = function(type, data) { this.type = type; this.data = data; this.debug = false; // Calls context shape methods such as moveTo(), lineTo(), bezierCurveTo(), etc. this.shape = function(turtle, c) { var px = turtle.x; var py = turtle.y; if (this.type == "M") { turtle.x = this.data[0]; turtle.y = this.data[1]; if (c) c.moveTo(turtle.x, turtle.y); } else if (this.type == "C") { turtle.x = this.data[4]; turtle.y = this.data[5]; if (c) c.bezierCurveTo(turtle.x1 = this.data[0], turtle.y1 = this.data[1], turtle.x2 = this.data[2], turtle.y2 = this.data[3], turtle.x, turtle.y); } else if (this.type == "c") { if (c) c.bezierCurveTo(turtle.x1 = turtle.x+this.data[0], turtle.y1 = turtle.y+this.data[1], turtle.x2 = turtle.x+this.data[2], turtle.y2 = turtle.y+this.data[3], turtle.x += this.data[4], turtle.y += this.data[5]); } else if (this.type == "S") { turtle.x = this.data[2]; turtle.y = this.data[3]; var dx = turtle.x - turtle.x2; var dy = turtle.y - turtle.y2; if (c) c.bezierCurveTo(turtle.x1 = turtle.x+dx, turtle.y1 = turtle.y+dy, turtle.x2 = this.data[0], turtle.y2 = this.data[1], turtle.x, turtle.y); } else if (this.type == "s") { var dx = turtle.x - turtle.x2; var dy = turtle.y - turtle.y2; if (c) c.bezierCurveTo(turtle.x1 = turtle.x+dx, turtle.y1 = turtle.y+dy, turtle.x2 = turtle.x+this.data[0], turtle.y2 = turtle.y+this.data[1], turtle.x += this.data[2], turtle.y += this.data[3]); } else if (this.type == "L") { turtle.x1 = turtle.x; turtle.y1 = turtle.y; if (c) c.lineTo(turtle.x = this.data[0], turtle.y = this.data[1]); turtle.x2 = turtle.x; turtle.y2 = turtle.y; } else if (this.type == "l") { turtle.x1 = turtle.x; turtle.y1 = turtle.y; if (c) c.lineTo(turtle.x+=this.data[0], turtle.y+=this.data[1]); turtle.x2 = turtle.x; turtle.y2 = turtle.y; } else if (this.type == "H") { turtle.x1 = turtle.x; turtle.y1 = turtle.y; if (c) c.lineTo(turtle.x = this.data[0], turtle.y) turtle.x2 = turtle.x; turtle.y2 = turtle.y; } else if (this.type == "h") { turtle.x1 = turtle.x; turtle.y1 = turtle.y; if (c) c.lineTo(turtle.x += this.data[0], turtle.y) turtle.x2 = turtle.x; turtle.y2 = turtle.y; } else if (this.type == "V") { turtle.x1 = turtle.x; turtle.y1 = turtle.y; if (c) c.lineTo(turtle.x, turtle.y = this.data[0]); turtle.x2 = turtle.x; turtle.y2 = turtle.y; } else if (this.type == "v") { turtle.x1 = turtle.x; turtle.y1 = turtle.y; if (c) c.lineTo(turtle.x, turtle.y += this.data[0]); turtle.x2 = turtle.x; turtle.y2 = turtle.y; } else if (this.type == "z") { c.closePath(); } else { alert("unrecognized command " + this.type); } if (c){ c.strokeStyle = "#000000"; c.lineWidth = 1; if (this.debug) { c.strokeRect(turtle.x-1.5, turtle.y-1.5, 3, 3); c.beginPath(); c.moveTo(turtle.px, turtle.py); c.lineTo(turtle.x1, turtle.y1); c.closePath(); c.stroke(); } } } } // Utility functions var commands = function(element) { if (element.nodeName.toLowerCase() == "path") { return commandsFromD(element.getAttribute("d")); } if (element.nodeName.toLowerCase() == "polygon") { return commandsFromPoints(element.getAttribute("points")); } if (element.nodeName.toLowerCase() == "line") { return commandsFromLine(element); } if (element.nodeName.toLowerCase() == "rect") { return commandsFromRect(element); } return []; } // Returns an array of commands as interpreted by the "d" attribute of a path. var commandsFromD = function(d) { var toReturn = []; var commands = d.match(/[a-zA-Z][0-9\.\-\,]+/g); for (var i = 0; i < commands.length; i++) { var type = commands[i].charAt(0); // Dirty time. var commandData = commands[i].substr(1); commandData = commandData.replace(/\-/g, ",-") if (commandData.charAt(0) == ",") { commandData = commandData.substr(1); } commandData = commandData.split(","); for (var j = 0; j < commandData.length; j++) { commandData[j] = parseFloat(commandData[j]); } toReturn.push(new Command(type, commandData)); } return toReturn; } var commandsFromLine = function(element) { var toReturn = []; var x1 = parseFloat(element.getAttribute("x1")); var x2 = parseFloat(element.getAttribute("x2")); var y1 = parseFloat(element.getAttribute("y1")); var y2 = parseFloat(element.getAttribute("y2")); toReturn.push(new Command("M", [x1,y1])); toReturn.push(new Command("L", [x2,y2])); return toReturn; } // Returns an array of commands as interpreted by the "points" attribute of a polygon. var commandsFromPoints = function(pointAttribute) { //pointAttribute = pointAttribute.replace(/\,\-/g, "-"); var shouldBeComma = true; if (pointAttribute.indexOf(",") == -1) { for (var i = 0; i < pointAttribute.length; i++) { var c = pointAttribute.charAt(i); if (c == " ") { if (shouldBeComma) { pointAttribute = pointAttribute.setCharAt(i, ","); } shouldBeComma = !shouldBeComma; } } } pointAttribute = "M"+pointAttribute; pointAttribute = pointAttribute.replace(/ /g, "L") + "z"; var toReturn = commandsFromD(pointAttribute); return toReturn; } String.prototype.setCharAt = function(index,chr) { if(index > this.length-1) return str; return this.substr(0,index) + chr + this.substr(index+1); } var commandsFromRect = function(element) { var toReturn = []; var x = parseFloat(element.getAttribute("x")); var y = parseFloat(element.getAttribute("y")); var w = parseFloat(element.getAttribute("width")); var h = parseFloat(element.getAttribute("height")); toReturn.push(new Command("M", [x,y])); toReturn.push(new Command("h", [w])); toReturn.push(new Command("v", [h])); toReturn.push(new Command("h", [-w])); toReturn.push(new Command("v", [-h])); return toReturn; }