/* Copyright (c) 2004-2006, The Dojo Foundation All Rights Reserved. Licensed under the Academic Free License version 2.1 or above OR the modified BSD license. For more information on Dojo licensing, see: http://dojotoolkit.org/community/licensing.shtml */ dojo.provide("dojo.gfx.vml"); dojo.require("dojo.dom"); dojo.require("dojo.math"); dojo.require("dojo.lang.declare"); dojo.require("dojo.lang.extras"); dojo.require("dojo.string.*"); dojo.require("dojo.html.metrics"); dojo.require("dojo.gfx.color"); dojo.require("dojo.gfx.common"); dojo.require("dojo.gfx.shape"); dojo.require("dojo.gfx.path"); dojo.require("dojo.experimental"); dojo.experimental("dojo.gfx.vml"); // dojo.gfx.vml.xmlns: String: a VML's namespace dojo.gfx.vml.xmlns = "urn:schemas-microsoft-com:vml"; dojo.gfx.vml._parseFloat = function(str) { // summary: a helper function to parse VML-specific floating-point values // str: String: a representation of a floating-point number return str.match(/^\d+f$/i) ? parseInt(str) / 65536 : parseFloat(str); // Number }; // dojo.gfx.vml.cm_in_pt: Number: centimeters per inch dojo.gfx.vml.cm_in_pt = 72 / 2.54; // dojo.gfx.vml.mm_in_pt: Number: millimeters per inch dojo.gfx.vml.mm_in_pt = 7.2 / 2.54; dojo.gfx.vml.px_in_pt = function(){ // summary: returns a number of pixels per point return dojo.html.getCachedFontMeasurements()["12pt"] / 12; // Number }; dojo.gfx.vml.pt2px = function(len){ // summary: converts points to pixels // len: Number: a value in points return len * this.px_in_pt(); // Number }; dojo.gfx.vml.px2pt = function(len){ // summary: converts pixels to points // len: Number: a value in pixels return len / this.px_in_pt(); // Number }; dojo.gfx.vml.normalizedLength = function(len) { // summary: converts any length value to points // len: String: a length, e.g., "12pc" if(len.length == 0) return 0; if(len.length > 2){ var px_in_pt = this.px_in_pt(); var val = parseFloat(len); switch(len.slice(-2)){ case "px": return val; case "pt": return val * px_in_pt; case "in": return val * 72 * px_in_pt; case "pc": return val * 12 * px_in_pt; case "mm": return val / this.mm_in_pt * px_in_pt; case "cm": return val / this.cm_in_pt * px_in_pt; } } return parseFloat(len); // Number }; dojo.lang.extend(dojo.gfx.Shape, { // summary: VML-specific implementation of dojo.gfx.Shape methods setFill: function(fill){ // summary: sets a fill object (VML) // fill: Object: a fill object // (see dojo.gfx.defaultLinearGradient, // dojo.gfx.defaultRadialGradient, // dojo.gfx.defaultPattern, // or dojo.gfx.color.Color) if(!fill){ // don't fill this.fillStyle = null; this.rawNode.filled = false; return this; } if(typeof(fill) == "object" && "type" in fill){ // gradient switch(fill.type){ case "linear": var f = dojo.gfx.makeParameters(dojo.gfx.defaultLinearGradient, fill); this.fillStyle = f; var s = ""; for(var i = 0; i < f.colors.length; ++i){ f.colors[i].color = dojo.gfx.normalizeColor(f.colors[i].color); s += f.colors[i].offset.toFixed(8) + " " + f.colors[i].color.toHex() + ";"; } var fo = this.rawNode.fill; fo.colors.value = s; fo.method = "sigma"; fo.type = "gradient"; fo.angle = (dojo.math.radToDeg(Math.atan2(f.x2 - f.x1, f.y2 - f.y1)) + 180) % 360; fo.on = true; break; case "radial": var f = dojo.gfx.makeParameters(dojo.gfx.defaultRadialGradient, fill); this.fillStyle = f; var w = parseFloat(this.rawNode.style.width); var h = parseFloat(this.rawNode.style.height); var c = isNaN(w) ? 1 : 2 * f.r / w; var i = f.colors.length - 1; f.colors[i].color = dojo.gfx.normalizeColor(f.colors[i].color); var s = "0 " + f.colors[i].color.toHex(); for(; i >= 0; --i){ f.colors[i].color = dojo.gfx.normalizeColor(f.colors[i].color); s += (1 - c * f.colors[i].offset).toFixed(8) + " " + f.colors[i].color.toHex() + ";"; } var fo = this.rawNode.fill; fo.colors.value = s; fo.method = "sigma"; fo.type = "gradientradial"; if(isNaN(w) || isNaN(h)){ fo.focusposition = "0.5 0.5"; }else{ fo.focusposition = (f.cx / w).toFixed(8) + " " + (f.cy / h).toFixed(8); } fo.focussize = "0 0"; fo.on = true; break; case "pattern": var f = dojo.gfx.makeParameters(dojo.gfx.defaultPattern, fill); this.fillStyle = f; var fo = this.rawNode.fill; fo.type = "tile"; fo.src = f.src; if(f.width && f.height){ // in points fo.size.x = dojo.gfx.vml.px2pt(f.width); fo.size.y = dojo.gfx.vml.px2pt(f.height); } fo.alignShape = false; fo.position.x = 0; fo.position.y = 0; fo.origin.x = f.width ? f.x / f.width : 0; fo.origin.y = f.height ? f.y / f.height : 0; fo.on = true; break; } this.rawNode.fill.opacity = 1; return this; } // color object this.fillStyle = dojo.gfx.normalizeColor(fill); this.rawNode.fillcolor = this.fillStyle.toHex(); this.rawNode.fill.opacity = this.fillStyle.a; this.rawNode.filled = true; return this; // self }, setStroke: function(stroke){ // summary: sets a stroke object (VML) // stroke: Object: a stroke object // (see dojo.gfx.defaultStroke) if(!stroke){ // don't stroke this.strokeStyle = null; this.rawNode.stroked = false; return this; } // normalize the stroke this.strokeStyle = dojo.gfx.makeParameters(dojo.gfx.defaultStroke, stroke); this.strokeStyle.color = dojo.gfx.normalizeColor(this.strokeStyle.color); // generate attributes var s = this.strokeStyle; this.rawNode.stroked = true; this.rawNode.strokecolor = s.color.toCss(); this.rawNode.strokeweight = s.width + "px"; // TODO: should we assume that the width is always in pixels? if(this.rawNode.stroke) { this.rawNode.stroke.opacity = s.color.a; this.rawNode.stroke.endcap = this._translate(this._capMap, s.cap); if(typeof(s.join) == "number") { this.rawNode.stroke.joinstyle = "miter"; this.rawNode.stroke.miterlimit = s.join; }else{ this.rawNode.stroke.joinstyle = s.join; // this.rawNode.stroke.miterlimit = s.width; } } return this; // self }, _capMap: { butt: 'flat' }, _capMapReversed: { flat: 'butt' }, _translate: function(dict, value) { return (value in dict) ? dict[value] : value; }, _applyTransform: function() { var matrix = this._getRealMatrix(); if(!matrix) return this; var skew = this.rawNode.skew; if(typeof(skew) == "undefined"){ for(var i = 0; i < this.rawNode.childNodes.length; ++i){ if(this.rawNode.childNodes[i].tagName == "skew"){ skew = this.rawNode.childNodes[i]; break; } } } if(skew){ skew.on = false; var mt = matrix.xx.toFixed(8) + " " + matrix.xy.toFixed(8) + " " + matrix.yx.toFixed(8) + " " + matrix.yy.toFixed(8) + " 0 0"; var offset = Math.floor(matrix.dx).toFixed() + "px " + Math.floor(matrix.dy).toFixed() + "px"; var l = parseFloat(this.rawNode.style.left); var t = parseFloat(this.rawNode.style.top); var w = parseFloat(this.rawNode.style.width); var h = parseFloat(this.rawNode.style.height); if(isNaN(l)) l = 0; if(isNaN(t)) t = 0; if(isNaN(w)) w = 1; if(isNaN(h)) h = 1; var origin = (-l / w - 0.5).toFixed(8) + " " + (-t / h - 0.5).toFixed(8); skew.matrix = mt; skew.origin = origin; skew.offset = offset; skew.on = true; } return this; }, setRawNode: function(rawNode){ // summary: // assigns and clears the underlying node that will represent this // shape. Once set, transforms, gradients, etc, can be applied. // (no fill & stroke by default) rawNode.stroked = false; rawNode.filled = false; this.rawNode = rawNode; }, // Attach family attachFill: function(rawNode){ // summary: deduces a fill style from a Node. // rawNode: Node: an VML node var fillStyle = null; var fo = rawNode.fill; if(rawNode) { if(fo.on && fo.type == "gradient"){ var fillStyle = dojo.lang.shallowCopy(dojo.gfx.defaultLinearGradient, true); var rad = dojo.math.degToRad(fo.angle); fillStyle.x2 = Math.cos(rad); fillStyle.y2 = Math.sin(rad); fillStyle.colors = []; var stops = fo.colors.value.split(";"); for(var i = 0; i < stops.length; ++i){ var t = stops[i].match(/\S+/g); if(!t || t.length != 2) continue; fillStyle.colors.push({offset: dojo.gfx.vml._parseFloat(t[0]), color: new dojo.gfx.color.Color(t[1])}); } }else if(fo.on && fo.type == "gradientradial"){ var fillStyle = dojo.lang.shallowCopy(dojo.gfx.defaultRadialGradient, true); var w = parseFloat(rawNode.style.width); var h = parseFloat(rawNode.style.height); fillStyle.cx = isNaN(w) ? 0 : fo.focusposition.x * w; fillStyle.cy = isNaN(h) ? 0 : fo.focusposition.y * h; fillStyle.r = isNaN(w) ? 1 : w / 2; fillStyle.colors = []; var stops = fo.colors.value.split(";"); for(var i = stops.length - 1; i >= 0; --i){ var t = stops[i].match(/\S+/g); if(!t || t.length != 2) continue; fillStyle.colors.push({offset: dojo.gfx.vml._parseFloat(t[0]), color: new dojo.gfx.color.Color(t[1])}); } }else if(fo.on && fo.type == "tile"){ var fillStyle = dojo.lang.shallowCopy(dojo.gfx.defaultPattern, true); fillStyle.width = dojo.gfx.vml.pt2px(fo.size.x); // from pt fillStyle.height = dojo.gfx.vml.pt2px(fo.size.y); // from pt fillStyle.x = fo.origin.x * fillStyle.width; fillStyle.y = fo.origin.y * fillStyle.height; fillStyle.src = fo.src; }else if(fo.on && rawNode.fillcolor){ // a color object ! fillStyle = new dojo.gfx.color.Color(rawNode.fillcolor+""); fillStyle.a = fo.opacity; } } return fillStyle; // Object }, attachStroke: function(rawNode) { // summary: deduces a stroke style from a Node. // rawNode: Node: an VML node var strokeStyle = dojo.lang.shallowCopy(dojo.gfx.defaultStroke, true); if(rawNode && rawNode.stroked){ strokeStyle.color = new dojo.gfx.color.Color(rawNode.strokecolor.value); dojo.debug("We are expecting an .75pt here, instead of strokeweight = " + rawNode.strokeweight ); strokeStyle.width = dojo.gfx.vml.normalizedLength(rawNode.strokeweight+""); strokeStyle.color.a = rawNode.stroke.opacity; strokeStyle.cap = this._translate(this._capMapReversed, rawNode.stroke.endcap); strokeStyle.join = rawNode.stroke.joinstyle == "miter" ? rawNode.stroke.miterlimit : rawNode.stroke.joinstyle; }else{ return null; } return strokeStyle; // Object }, attachTransform: function(rawNode) { // summary: deduces a transformation matrix from a Node. // rawNode: Node: an VML node var matrix = {}; if(rawNode){ var s = rawNode.skew; matrix.xx = s.matrix.xtox; matrix.xy = s.matrix.ytox; matrix.yx = s.matrix.xtoy; matrix.yy = s.matrix.ytoy; matrix.dx = dojo.gfx.vml.pt2px(s.offset.x); matrix.dy = dojo.gfx.vml.pt2px(s.offset.y); } return dojo.gfx.matrix.normalize(matrix); // dojo.gfx.matrix.Matrix }, attach: function(rawNode){ // summary: reconstructs all shape parameters from a Node. // rawNode: Node: an VML node if(rawNode){ this.rawNode = rawNode; this.shape = this.attachShape(rawNode); this.fillStyle = this.attachFill(rawNode); this.strokeStyle = this.attachStroke(rawNode); this.matrix = this.attachTransform(rawNode); } } }); dojo.declare("dojo.gfx.Group", dojo.gfx.shape.VirtualGroup, { // summary: a group shape (VML), which can be used // to logically group shapes (e.g, to propagate matricies) add: function(shape){ // summary: adds a shape to a group/surface // shape: dojo.gfx.Shape: an VML shape object if(this != shape.getParent()){ this.rawNode.appendChild(shape.rawNode); dojo.gfx.Group.superclass.add.apply(this, arguments); } return this; // self }, remove: function(shape, silently){ // summary: remove a shape from a group/surface // shape: dojo.gfx.Shape: an VML shape object // silently: Boolean?: if true, regenerate a picture if(this == shape.getParent()){ if(this.rawNode == shape.rawNode.parentNode){ this.rawNode.removeChild(shape.rawNode); } dojo.gfx.Group.superclass.remove.apply(this, arguments); } return this; // self }, attach: function(rawNode){ // summary: reconstructs all group shape parameters from a Node (VML). // rawNode: Node: a node if(rawNode){ this.rawNode = rawNode; this.shape = null; this.fillStyle = null; this.strokeStyle = null; this.matrix = null; } } }); dojo.gfx.Group.nodeType = "group"; var zIndex = { moveToFront: function(){ // summary: moves a shape to front of its parent's list of shapes (VML) this.rawNode.parentNode.appendChild(this.rawNode); return this; }, moveToBack: function(){ // summary: moves a shape to back of its parent's list of shapes (VML) this.rawNode.parentNode.insertBefore(this.rawNode, this.rawNode.parentNode.firstChild); return this; } }; dojo.lang.extend(dojo.gfx.Shape, zIndex); dojo.lang.extend(dojo.gfx.Group, zIndex); delete zIndex; dojo.declare("dojo.gfx.Rect", dojo.gfx.shape.Rect, { // summary: a rectangle shape (VML) attachShape: function(rawNode){ // summary: builds a rectangle shape from a Node. // rawNode: Node: a VML node // a workaround for the VML's arcsize bug: cannot read arcsize of an instantiated node var arcsize = rawNode.outerHTML.match(/arcsize = \"(\d*\.?\d+[%f]?)\"/)[1]; arcsize = (arcsize.indexOf("%") >= 0) ? parseFloat(arcsize) / 100 : dojo.gfx.vml._parseFloat(arcsize); var style = rawNode.style; var width = parseFloat(style.width); var height = parseFloat(style.height); // make an object var o = dojo.gfx.makeParameters(dojo.gfx.defaultRect, { x: parseInt(style.left), y: parseInt(style.top), width: width, height: height, r: Math.min(width, height) * arcsize }); return o; // dojo.gfx.shape.Rect }, setShape: function(newShape){ // summary: sets a rectangle shape object (VML) // newShape: Object: a rectangle shape object var shape = this.shape = dojo.gfx.makeParameters(this.shape, newShape); this.bbox = null; var style = this.rawNode.style; style.left = shape.x.toFixed(); style.top = shape.y.toFixed(); style.width = (typeof(shape.width) == "string" && shape.width.indexOf("%") >= 0) ? shape.width : shape.width.toFixed(); style.height = (typeof(shape.width) == "string" && shape.height.indexOf("%") >= 0) ? shape.height : shape.height.toFixed(); var r = Math.min(1, (shape.r / Math.min(parseFloat(shape.width), parseFloat(shape.height)))).toFixed(8); // a workaround for the VML's arcsize bug: cannot read arcsize of an instantiated node var parent = this.rawNode.parentNode; var before = null; if(parent){ if(parent.lastChild != this.rawNode){ for(var i = 0; i < parent.childNodes.length; ++i){ if(parent.childNodes[i] == this.rawNode){ before = parent.childNodes[i+1]; break; } } } parent.removeChild(this.rawNode); } this.rawNode.arcsize = r; if(parent){ if(before){ parent.insertBefore(this.rawNode, before); }else{ parent.appendChild(this.rawNode); } } return this.setTransform(this.matrix); // self } }); dojo.gfx.Rect.nodeType = "roundrect"; // use a roundrect so the stroke join type is respected dojo.declare("dojo.gfx.Ellipse", dojo.gfx.shape.Ellipse, { // summary: an ellipse shape (VML) attachShape: function(rawNode){ // summary: builds an ellipse shape from a Node. // rawNode: Node: an VML node var style = this.rawNode.style; var rx = parseInt(style.width ) / 2; var ry = parseInt(style.height) / 2; var o = dojo.gfx.makeParameters(dojo.gfx.defaultEllipse, { cx: parseInt(style.left) + rx, cy: parseInt(style.top ) + ry, rx: rx, ry: ry }); return o; // dojo.gfx.shape.Ellipse }, setShape: function(newShape){ // summary: sets an ellipse shape object (VML) // newShape: Object: an ellipse shape object var shape = this.shape = dojo.gfx.makeParameters(this.shape, newShape); this.bbox = null; var style = this.rawNode.style; style.left = (shape.cx - shape.rx).toFixed(); style.top = (shape.cy - shape.ry).toFixed(); style.width = (shape.rx * 2).toFixed(); style.height = (shape.ry * 2).toFixed(); return this.setTransform(this.matrix); // self } }); dojo.gfx.Ellipse.nodeType = "oval"; dojo.declare("dojo.gfx.Circle", dojo.gfx.shape.Circle, { // summary: a circle shape (VML) attachShape: function(rawNode){ // summary: builds a circle shape from a Node. // rawNode: Node: an VML node var style = this.rawNode.style; var r = parseInt(style.width) / 2; var o = dojo.gfx.makeParameters(dojo.gfx.defaultCircle, { cx: parseInt(style.left) + r, cy: parseInt(style.top) + r, r: r }); return o; // dojo.gfx.shape.Circle }, setShape: function(newShape){ // summary: sets a circle shape object (VML) // newShape: Object: a circle shape object var shape = this.shape = dojo.gfx.makeParameters(this.shape, newShape); this.bbox = null; var style = this.rawNode.style; style.left = (shape.cx - shape.r).toFixed(); style.top = (shape.cy - shape.r).toFixed(); style.width = (shape.r * 2).toFixed(); style.height = (shape.r * 2).toFixed(); return this; // self } }); dojo.gfx.Circle.nodeType = "oval"; dojo.declare("dojo.gfx.Line", dojo.gfx.shape.Line, function(rawNode){ if(rawNode) rawNode.setAttribute("dojoGfxType", "line"); }, { // summary: a line shape (VML) attachShape: function(rawNode){ // summary: builds a line shape from a Node. // rawNode: Node: an VML node var p = rawNode.path.v.match(dojo.gfx.pathRegExp); var shape = {}; do{ if(p.length < 7 || p[0] != "m" || p[3] != "l" || p[6] != "e") break; shape.x1 = parseInt(p[1]); shape.y1 = parseInt(p[2]); shape.x2 = parseInt(p[4]); shape.y2 = parseInt(p[5]); }while(false); return dojo.gfx.makeParameters(dojo.gfx.defaultLine, shape); // dojo.gfx.shape.Line }, setShape: function(newShape){ // summary: sets a line shape object (VML) // newShape: Object: a line shape object var shape = this.shape = dojo.gfx.makeParameters(this.shape, newShape); this.bbox = null; this.rawNode.path.v = "m" + shape.x1.toFixed() + " " + shape.y1.toFixed() + "l" + shape.x2.toFixed() + " " + shape.y2.toFixed() + "e"; return this.setTransform(this.matrix); // self } }); dojo.gfx.Line.nodeType = "shape"; dojo.declare("dojo.gfx.Polyline", dojo.gfx.shape.Polyline, function(rawNode){ if(rawNode) rawNode.setAttribute("dojoGfxType", "polyline"); }, { // summary: a polyline/polygon shape (VML) attachShape: function(rawNode){ // summary: builds a polyline/polygon shape from a Node. // rawNode: Node: an VML node var shape = dojo.lang.shallowCopy(dojo.gfx.defaultPolyline, true); var p = rawNode.path.v.match(dojo.gfx.pathRegExp); do{ if(p.length < 3 || p[0] != "m") break; var x = parseInt(p[0]); var y = parseInt(p[1]); if(isNaN(x) || isNaN(y)) break; shape.points.push({x: x, y: y}); if(p.length < 6 || p[3] != "l") break; for(var i = 4; i < p.length; i += 2){ x = parseInt(p[i]); y = parseInt(p[i + 1]); if(isNaN(x) || isNaN(y)) break; shape.points.push({x: x, y: y}); } }while(false); return shape; // dojo.gfx.shape.Polyline }, setShape: function(points, closed){ // summary: sets a polyline/polygon shape object (SVG) // points: Object: a polyline/polygon shape object // closed: Boolean?: if true, close the polyline explicitely if(points && points instanceof Array){ // branch // points: Array: an array of points this.shape = dojo.gfx.makeParameters(this.shape, { points: points }); if(closed && this.shape.points.length) this.shape.points.push(this.shape.points[0]); }else{ this.shape = dojo.gfx.makeParameters(this.shape, points); } this.bbox = null; var attr = []; var p = this.shape.points; if(p.length > 0){ attr.push("m"); attr.push(p[0].x.toFixed()); attr.push(p[0].y.toFixed()); if(p.length > 1){ attr.push("l"); for(var i = 1; i < p.length; ++i){ attr.push(p[i].x.toFixed()); attr.push(p[i].y.toFixed()); } } } attr.push("e"); this.rawNode.path.v = attr.join(" "); return this.setTransform(this.matrix); // self } }); dojo.gfx.Polyline.nodeType = "shape"; dojo.declare("dojo.gfx.Image", dojo.gfx.shape.Image, { // summary: an image (VML) getEventSource: function() { // summary: returns a Node, which is used as // a source of events for this shape return this.rawNode ? this.rawNode.firstChild : null; // Node }, attachShape: function(rawNode){ // summary: builds an image shape from a Node. // rawNode: Node: an VML node var shape = dojo.lang.shallowCopy(dojo.gfx.defaultImage, true); shape.src = rawNode.firstChild.src; return shape; // dojo.gfx.shape.Image }, setShape: function(newShape){ // summary: sets an image shape object (VML) // newShape: Object: an image shape object var shape = this.shape = dojo.gfx.makeParameters(this.shape, newShape); this.bbox = null; var firstChild = this.rawNode.firstChild; firstChild.src = shape.src; if(shape.width || shape.height){ firstChild.style.width = shape.width; firstChild.style.height = shape.height; } return this.setTransform(this.matrix); // self }, setStroke: function(){ // summary: ignore setting a stroke style return this; // self }, setFill: function(){ // summary: ignore setting a fill style return this; // self }, attachStroke: function(rawNode){ // summary: ignore attaching a stroke style return null; }, attachFill: function(rawNode){ // summary: ignore attaching a fill style return null; }, attachTransform: function(rawNode) { // summary: deduces a transformation matrix from a Node. // rawNode: Node: an VML node var matrix = {}; if(rawNode){ var m = rawNode.filters["DXImageTransform.Microsoft.Matrix"]; matrix.xx = m.M11; matrix.xy = m.M12; matrix.yx = m.M21; matrix.yy = m.M22; matrix.dx = m.Dx; matrix.dy = m.Dy; } return dojo.gfx.matrix.normalize(matrix); // dojo.gfx.matrix.Matrix }, _applyTransform: function() { var matrix = this._getRealMatrix(); if(!matrix) return this; with(this.rawNode.filters["DXImageTransform.Microsoft.Matrix"]){ M11 = matrix.xx; M12 = matrix.xy; M21 = matrix.yx; M22 = matrix.yy; Dx = matrix.dx; Dy = matrix.dy; } return this; } }); dojo.gfx.Image.nodeType = "image"; dojo.gfx.path._calcArc = function(alpha){ var cosa = Math.cos(alpha); var sina = Math.sin(alpha); // return a start point, 1st and 2nd control points, and an end point var p2 = {x: cosa + (4 / 3) * (1 - cosa), y: sina - (4 / 3) * cosa * (1 - cosa) / sina}; return { s: {x: cosa, y: sina}, c1: p2, c2: {x: p2.x, y: -p2.y}, e: {x: cosa, y: -sina} }; }; dojo.declare("dojo.gfx.Path", dojo.gfx.path.Path, function(rawNode){ if(rawNode) rawNode.setAttribute("dojoGfxType", "path"); this.vmlPath = ""; this.lastControl = {}; }, { // summary: a path shape (VML) _updateWithSegment: function(segment){ // summary: updates the bounding box of path with new segment // segment: Object: a segment var last = dojo.lang.shallowCopy(this.last); dojo.gfx.Path.superclass._updateWithSegment.apply(this, arguments); // add a VML path segment var path = this[this.renderers[segment.action]](segment, last); if(typeof(this.vmlPath) == "string"){ this.vmlPath += path.join(""); }else{ this.vmlPath = this.vmlPath.concat(path); } if(typeof(this.vmlPath) == "string"){ this.rawNode.path.v = this.vmlPath + " e"; } }, attachShape: function(rawNode){ // summary: builds a path shape from a Node. // rawNode: Node: an VML node var shape = dojo.lang.shallowCopy(dojo.gfx.defaultPath, true); var p = rawNode.path.v.match(dojo.gfx.pathRegExp); var t = [], skip = false; for(var i = 0; i < p.length; ++p){ var s = p[i]; if(s in this._pathVmlToSvgMap) { skip = false; t.push(this._pathVmlToSvgMap[s]); } else if(!skip){ var n = parseInt(s); if(isNaN(n)){ skip = true; }else{ t.push(n); } } } if(t.length) shape.path = t.join(" "); return shape; // dojo.gfx.path.Path }, setShape: function(newShape){ // summary: forms a path using a shape (VML) // newShape: Object: an VML path string or a path object (see dojo.gfx.defaultPath) this.vmlPath = []; this.lastControl = {}; dojo.gfx.Path.superclass.setShape.apply(this, arguments); this.vmlPath = this.vmlPath.join(""); this.rawNode.path.v = this.vmlPath + " e"; return this; }, _pathVmlToSvgMap: {m: "M", l: "L", t: "m", r: "l", c: "C", v: "c", qb: "Q", x: "z", e: ""}, // VML-specific segment renderers renderers: { M: "_moveToA", m: "_moveToR", L: "_lineToA", l: "_lineToR", H: "_hLineToA", h: "_hLineToR", V: "_vLineToA", v: "_vLineToR", C: "_curveToA", c: "_curveToR", S: "_smoothCurveToA", s: "_smoothCurveToR", Q: "_qCurveToA", q: "_qCurveToR", T: "_qSmoothCurveToA", t: "_qSmoothCurveToR", A: "_arcTo", a: "_arcTo", Z: "_closePath", z: "_closePath" }, _addArgs: function(path, args, from, upto){ if(typeof(upto) == "undefined"){ upto = args.length; } if(typeof(from) == "undefined"){ from = 0; } for(var i = from; i < upto; ++i){ path.push(" "); path.push(args[i].toFixed()); } }, _addArgsAdjusted: function(path, last, args, from, upto){ if(typeof(upto) == "undefined"){ upto = args.length; } if(typeof(from) == "undefined"){ from = 0; } for(var i = from; i < upto; i += 2){ path.push(" "); path.push((last.x + args[i]).toFixed()); path.push(" "); path.push((last.y + args[i + 1]).toFixed()); } }, _moveToA: function(segment){ var p = [" m"]; var n = segment.args; var l = n.length; if(l == 2){ this._addArgs(p, n); }else{ this._addArgs(p, n, 0, 2); p.push(" l"); this._addArgs(p, n, 2); } this.lastControl = {}; return p; }, _moveToR: function(segment, last){ var p = ["x" in last ? " t" : " m"]; var n = segment.args; var l = n.length; if(l == 2){ this._addArgs(p, n); }else{ this._addArgs(p, n, 0, 2); p.push(" r"); this._addArgs(p, n, 2); } this.lastControl = {}; return p; }, _lineToA: function(segment){ var p = [" l"]; this._addArgs(p, segment.args); this.lastControl = {}; return p; }, _lineToR: function(segment){ var p = [" r"]; this._addArgs(p, segment.args); this.lastControl = {}; return p; }, _hLineToA: function(segment, last){ var p = [" l"]; var n = segment.args; var l = n.length; var y = " " + last.y.toFixed(); for(var i = 0; i < l; ++i){ p.push(" "); p.push(n[i].toFixed()); p.push(y); } this.lastControl = {}; return p; }, _hLineToR: function(segment){ var p = [" r"]; var n = segment.args; var l = n.length; for(var i = 0; i < l; ++i){ p.push(" "); p.push(n[i].toFixed()); p.push(" 0"); } this.lastControl = {}; return p; }, _vLineToA: function(segment, last){ var p = [" l"]; var n = segment.args; var l = n.length; var x = " " + last.x.toFixed(); for(var i = 0; i < l; ++i){ p.push(x); p.push(" "); p.push(n[i].toFixed()); } this.lastControl = {}; return p; }, _vLineToR: function(segment){ var p = [" r"]; var n = segment.args; var l = n.length; for(var i = 0; i < l; ++i){ p.push(" 0 "); p.push(n[i].toFixed()); } this.lastControl = {}; return p; }, _curveToA: function(segment){ var p = []; var n = segment.args; var l = n.length; for(var i = 0; i < l; i += 6){ p.push(" c"); this._addArgs(p, n, i, i + 6); } this.lastControl = {x: n[l - 4], y: n[l - 3], type: "C"}; return p; }, _curveToR: function(segment, last){ var p = []; var n = segment.args; var l = n.length; for(var i = 0; i < l; i += 6){ p.push(" v"); this._addArgs(p, n, i, i + 6); this.lastControl = {x: last.x + n[i + 2], y: last.y + n[i + 3]}; last.x += n[i + 4]; last.y += n[i + 5]; } this.lastControl.type = "C"; return p; }, _smoothCurveToA: function(segment, last){ var p = []; var n = segment.args; var l = n.length; for(var i = 0; i < l; i += 4){ p.push(" c"); if(this.lastControl.type == "C"){ this._addArgs(p, [ 2 * last.x - this.lastControl.x, 2 * last.y - this.lastControl.y ]); }else{ this._addArgs(p, [last.x, last.y]); } this._addArgs(p, n, i, i + 4); } this.lastControl = {x: n[l - 4], y: n[l - 3], type: "C"}; return p; }, _smoothCurveToR: function(segment, last){ var p = []; var n = segment.args; var l = n.length; for(var i = 0; i < l; i += 4){ p.push(" v"); if(this.lastControl.type == "C"){ this._addArgs(p, [ last.x - this.lastControl.x, last.y - this.lastControl.y ]); }else{ this._addArgs(p, [0, 0]); } this._addArgs(p, n, i, i + 4); this.lastControl = {x: last.x + n[i], y: last.y + n[i + 1]}; last.x += n[i + 2]; last.y += n[i + 3]; } this.lastControl.type = "C"; return p; }, _qCurveToA: function(segment){ var p = []; var n = segment.args; var l = n.length; for(var i = 0; i < l; i += 4){ p.push(" qb"); this._addArgs(p, n, i, i + 4); } this.lastControl = {x: n[l - 4], y: n[l - 3], type: "Q"}; return p; }, _qCurveToR: function(segment, last){ var p = []; var n = segment.args; var l = n.length; for(var i = 0; i < l; i += 4){ p.push(" qb"); this._addArgsAdjusted(p, last, n, i, i + 4); this.lastControl = {x: last.x + n[i], y: last.y + n[i + 1]}; last.x += n[i + 2]; last.y += n[i + 3]; } this.lastControl.type = "Q"; return p; }, _qSmoothCurveToA: function(segment, last){ var p = []; var n = segment.args; var l = n.length; for(var i = 0; i < l; i += 2){ p.push(" qb"); if(this.lastControl.type == "Q"){ this._addArgs(p, [ this.lastControl.x = 2 * last.x - this.lastControl.x, this.lastControl.y = 2 * last.y - this.lastControl.y ]); }else{ this._addArgs(p, [ this.lastControl.x = last.x, this.lastControl.y = last.y ]); } this._addArgs(p, n, i, i + 2); } this.lastControl.type = "Q"; return p; }, _qSmoothCurveToR: function(segment, last){ var p = []; var n = segment.args; var l = n.length; for(var i = 0; i < l; i += 2){ p.push(" qb"); if(this.lastControl.type == "Q"){ this._addArgs(p, [ this.lastControl.x = 2 * last.x - this.lastControl.x, this.lastControl.y = 2 * last.y - this.lastControl.y ]); }else{ this._addArgs(p, [ this.lastControl.x = last.x, this.lastControl.y = last.y ]); } this._addArgsAdjusted(p, last, n, i, i + 2); } this.lastControl.type = "Q"; return p; }, _PI4: Math.PI / 4, _curvePI4: dojo.gfx.path._calcArc(Math.PI / 8), _calcArcTo: function(path, last, rx, ry, xRotg, large, cw, x, y){ var m = dojo.gfx.matrix; // calculate parameters var xRot = -dojo.math.degToRad(xRotg); var rx2 = rx * rx; var ry2 = ry * ry; var pa = m.multiplyPoint( m.rotate(-xRot), {x: (last.x - x) / 2, y: (last.y - y) / 2} ); var pax2 = pa.x * pa.x; var pay2 = pa.y * pa.y; var c1 = Math.sqrt((rx2 * ry2 - rx2 * pay2 - ry2 * pax2) / (rx2 * pay2 + ry2 * pax2)); var ca = { x: c1 * rx * pa.y / ry, y: -c1 * ry * pa.x / rx }; if(large == cw){ ca = {x: -ca.x, y: -ca.y}; } // our center var c = m.multiplyPoint( [ m.translate( (last.x + x) / 2, (last.y + y) / 2 ), m.rotate(xRot) ], ca ); // start of our arc var startAngle = Math.atan2(c.y - last.y, last.x - c.x) - xRot; var endAngle = Math.atan2(c.y - y, x - c.x) - xRot; // size of our arc in radians var theta = cw ? startAngle - endAngle : endAngle - startAngle; if(theta < 0){ theta += this._2PI; }else if(theta > this._2PI){ theta = this._2PI; } // calculate our elliptic transformation var elliptic_transform = m.normalize([ m.translate(c.x, c.y), m.rotate(xRot), m.scale(rx, ry) ]); // draw curve chunks var alpha = this._PI4 / 2; var curve = this._curvePI4; var step = cw ? -alpha : alpha; for(var angle = theta; angle > 0; angle -= this._PI4){ if(angle < this._PI4){ alpha = angle / 2; curve = dojo.gfx.path._calcArc(alpha); step = cw ? -alpha : alpha; } var c1, c2, e; var M = m.normalize([elliptic_transform, m.rotate(startAngle + step)]); if(cw){ c1 = m.multiplyPoint(M, curve.c2); c2 = m.multiplyPoint(M, curve.c1); e = m.multiplyPoint(M, curve.s ); }else{ c1 = m.multiplyPoint(M, curve.c1); c2 = m.multiplyPoint(M, curve.c2); e = m.multiplyPoint(M, curve.e ); } // draw the curve path.push(" c"); this._addArgs(path, [c1.x, c1.y, c2.x, c2.y, e.x, e.y]); startAngle += 2 * step; } }, _arcTo: function(segment, last){ var p = []; var n = segment.args; var l = n.length; var relative = segment.action == "a"; for(var i = 0; i < l; i += 7){ var x1 = n[i + 5]; var y1 = n[i + 6]; if(relative){ x1 += last.x; y1 += last.y; } this._calcArcTo( p, last, n[i], n[i + 1], n[i + 2], n[i + 3] ? 1 : 0, n[i + 4] ? 1 : 0, x1, y1 ); last = {x: x1, y: y1}; } this.lastControl = {}; return p; }, _closePath: function(){ this.lastControl = {}; return ["x"]; } }); dojo.gfx.Path.nodeType = "shape"; dojo.gfx._creators = { // summary: VML shape creators createPath: function(path){ // summary: creates an SVG path shape // path: Object: a path object (see dojo.gfx.defaultPath) return this.createObject(dojo.gfx.Path, path, true); // dojo.gfx.Path }, createRect: function(rect){ // summary: creates an VML rectangle shape // rect: Object: a path object (see dojo.gfx.defaultRect) return this.createObject(dojo.gfx.Rect, rect); // dojo.gfx.Rect }, createCircle: function(circle){ // summary: creates an VML circle shape // circle: Object: a circle object (see dojo.gfx.defaultCircle) return this.createObject(dojo.gfx.Circle, circle); // dojo.gfx.Circle }, createEllipse: function(ellipse){ // summary: creates an VML ellipse shape // ellipse: Object: an ellipse object (see dojo.gfx.defaultEllipse) return this.createObject(dojo.gfx.Ellipse, ellipse); // dojo.gfx.Ellipse }, createLine: function(line){ // summary: creates an VML line shape // line: Object: a line object (see dojo.gfx.defaultLine) return this.createObject(dojo.gfx.Line, line, true); // dojo.gfx.Line }, createPolyline: function(points){ // summary: creates an VML polyline/polygon shape // points: Object: a points object (see dojo.gfx.defaultPolyline) // or an Array of points return this.createObject(dojo.gfx.Polyline, points, true); // dojo.gfx.Polyline }, createImage: function(image){ // summary: creates an VML image shape // image: Object: an image object (see dojo.gfx.defaultImage) if(!this.rawNode) return null; var shape = new dojo.gfx.Image(); var node = document.createElement('div'); node.style.position = "relative"; node.style.width = this.rawNode.style.width; node.style.height = this.rawNode.style.height; node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11=1, M12=0, M21=0, M22=1, Dx=0, Dy=0)"; var img = document.createElement('img'); node.appendChild(img); shape.setRawNode(node); this.rawNode.appendChild(node); shape.setShape(image); this.add(shape); return shape; // dojo.gfx.Image }, createGroup: function(){ // summary: creates an VML group shape return this.createObject(dojo.gfx.Group, null, true); // dojo.gfx.Group }, createObject: function(shapeType, rawShape, overrideSize) { // summary: creates an instance of the passed shapeType class // shapeType: Function: a class constructor to create an instance of // rawShape: Object: properties to be passed in to the classes "setShape" method if(!this.rawNode) return null; var shape = new shapeType(); var node = document.createElement('v:' + shapeType.nodeType); shape.setRawNode(node); this.rawNode.appendChild(node); if(overrideSize) this._overrideSize(node); shape.setShape(rawShape); this.add(shape); return shape; // dojo.gfx.Shape }, _overrideSize: function(node){ node.style.width = this.rawNode.style.width; node.style.height = this.rawNode.style.height; node.coordsize = parseFloat(node.style.width) + " " + parseFloat(node.style.height); } }; dojo.lang.extend(dojo.gfx.Group, dojo.gfx._creators); dojo.lang.extend(dojo.gfx.Surface, dojo.gfx._creators); delete dojo.gfx._creators; dojo.gfx.attachNode = function(node){ // summary: creates a shape from a Node // node: Node: an VML node if(!node) return null; var s = null; switch(node.tagName.toLowerCase()){ case dojo.gfx.Rect.nodeType: s = new dojo.gfx.Rect(); break; case dojo.gfx.Ellipse.nodeType: s = (node.style.width == node.style.height) ? new dojo.gfx.Circle() : new dojo.gfx.Ellipse(); break; case dojo.gfx.Path.nodeType: switch(node.getAttribute("dojoGfxType")){ case "line": s = new dojo.gfx.Line(); break; case "polyline": s = new dojo.gfx.Polyline(); break; case "path": s = new dojo.gfx.Path(); break; } break; case dojo.gfx.Image.nodeType: s = new dojo.gfx.Image(); break; default: dojo.debug("FATAL ERROR! tagName = " + node.tagName); } s.attach(node); return s; // dojo.gfx.Shape }; dojo.lang.extend(dojo.gfx.Surface, { // summary: a surface object to be used for drawings (VML) setDimensions: function(width, height){ // summary: sets the width and height of the rawNode // width: String: width of surface, e.g., "100px" // height: String: height of surface, e.g., "100px" if(!this.rawNode) return this; this.rawNode.style.width = width; this.rawNode.style.height = height; this.rawNode.coordsize = width + " " + height; return this; // self }, getDimensions: function(){ // summary: returns an object with properties "width" and "height" return this.rawNode ? { width: this.rawNode.style.width, height: this.rawNode.style.height } : null; // Object }, // group control add: function(shape){ // summary: adds a shape to a group/surface // shape: dojo.gfx.Shape: an VMLshape object var oldParent = shape.getParent(); if(this != oldParent){ this.rawNode.appendChild(shape.rawNode); if(oldParent){ oldParent.remove(shape, true); } shape._setParent(this, null); } return this; // self }, remove: function(shape, silently){ // summary: remove a shape from a group/surface // shape: dojo.gfx.Shape: an VML shape object // silently: Boolean?: if true, regenerate a picture if(this == shape.getParent()){ if(this.rawNode == shape.rawNode.parentNode){ this.rawNode.removeChild(shape.rawNode); } shape._setParent(null, null); } return this; // self } }); dojo.gfx.createSurface = function(parentNode, width, height){ // summary: creates a surface (VML) // parentNode: Node: a parent node // width: String: width of surface, e.g., "100px" // height: String: height of surface, e.g., "100px" var s = new dojo.gfx.Surface(); s.rawNode = document.createElement("v:group"); s.rawNode.style.width = width ? width : "100%"; s.rawNode.style.height = height ? height : "100%"; s.rawNode.coordsize = (width && height) ? (parseFloat(width) + " " + parseFloat(height)) : "100% 100%"; s.rawNode.coordorigin = "0 0"; dojo.byId(parentNode).appendChild(s.rawNode); return s; // dojo.gfx.Surface }; dojo.gfx.attachSurface = function(node){ // summary: creates a surface from a Node // node: Node: an VML node var s = new dojo.gfx.Surface(); s.rawNode = node; return s; // dojo.gfx.Surface };