--- /dev/null
+<!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-draw.engine.Svg'>/**
+</span> * @class Ext.draw.engine.Svg
+ * @extends Ext.draw.Surface
+ * Provides specific methods to draw with SVG.
+ */
+Ext.define('Ext.draw.engine.Svg', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.draw.Surface',
+
+ requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
+
+ /* End Definitions */
+
+ engine: 'Svg',
+
+ trimRe: /^\s+|\s+$/g,
+ spacesRe: /\s+/,
+ xlink: "http:/" + "/www.w3.org/1999/xlink",
+
+ translateAttrs: {
+ radius: "r",
+ radiusX: "rx",
+ radiusY: "ry",
+ path: "d",
+ lineWidth: "stroke-width",
+ fillOpacity: "fill-opacity",
+ strokeOpacity: "stroke-opacity",
+ strokeLinejoin: "stroke-linejoin"
+ },
+
+ minDefaults: {
+ circle: {
+ cx: 0,
+ cy: 0,
+ r: 0,
+ fill: "none",
+ stroke: null,
+ "stroke-width": null,
+ opacity: null,
+ "fill-opacity": null,
+ "stroke-opacity": null
+ },
+ ellipse: {
+ cx: 0,
+ cy: 0,
+ rx: 0,
+ ry: 0,
+ fill: "none",
+ stroke: null,
+ "stroke-width": null,
+ opacity: null,
+ "fill-opacity": null,
+ "stroke-opacity": null
+ },
+ rect: {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ rx: 0,
+ ry: 0,
+ fill: "none",
+ stroke: null,
+ "stroke-width": null,
+ opacity: null,
+ "fill-opacity": null,
+ "stroke-opacity": null
+ },
+ text: {
+ x: 0,
+ y: 0,
+ "text-anchor": "start",
+ "font-family": null,
+ "font-size": null,
+ "font-weight": null,
+ "font-style": null,
+ fill: "#000",
+ stroke: null,
+ "stroke-width": null,
+ opacity: null,
+ "fill-opacity": null,
+ "stroke-opacity": null
+ },
+ path: {
+ d: "M0,0",
+ fill: "none",
+ stroke: null,
+ "stroke-width": null,
+ opacity: null,
+ "fill-opacity": null,
+ "stroke-opacity": null
+ },
+ image: {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ preserveAspectRatio: "none",
+ opacity: null
+ }
+ },
+
+ createSvgElement: function(type, attrs) {
+ var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
+ key;
+ if (attrs) {
+ for (key in attrs) {
+ el.setAttribute(key, String(attrs[key]));
+ }
+ }
+ return el;
+ },
+
+ createSpriteElement: function(sprite) {
+ // Create svg element and append to the DOM.
+ var el = this.createSvgElement(sprite.type);
+ el.id = sprite.id;
+ if (el.style) {
+ el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
+ }
+ sprite.el = Ext.get(el);
+ this.applyZIndex(sprite); //performs the insertion
+ sprite.matrix = Ext.create('Ext.draw.Matrix');
+ sprite.bbox = {
+ plain: 0,
+ transform: 0
+ };
+ sprite.fireEvent("render", sprite);
+ return el;
+ },
+
+ getBBox: function (sprite, isWithoutTransform) {
+ var realPath = this["getPath" + sprite.type](sprite);
+ if (isWithoutTransform) {
+ sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
+ return sprite.bbox.plain;
+ }
+ sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
+ return sprite.bbox.transform;
+ },
+
+ getBBoxText: function (sprite) {
+ var bbox = {},
+ bb, height, width, i, ln, el;
+
+ if (sprite && sprite.el) {
+ el = sprite.el.dom;
+ try {
+ bbox = el.getBBox();
+ return bbox;
+ } catch(e) {
+ // Firefox 3.0.x plays badly here
+ }
+ bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
+ ln = el.getNumberOfChars();
+ for (i = 0; i < ln; i++) {
+ bb = el.getExtentOfChar(i);
+ bbox.y = Math.min(bb.y, bbox.y);
+ height = bb.y + bb.height - bbox.y;
+ bbox.height = Math.max(bbox.height, height);
+ width = bb.x + bb.width - bbox.x;
+ bbox.width = Math.max(bbox.width, width);
+ }
+ return bbox;
+ }
+ },
+
+ hide: function() {
+ Ext.get(this.el).hide();
+ },
+
+ show: function() {
+ Ext.get(this.el).show();
+ },
+
+ hidePrim: function(sprite) {
+ this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
+ },
+
+ showPrim: function(sprite) {
+ this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
+ },
+
+ getDefs: function() {
+ return this._defs || (this._defs = this.createSvgElement("defs"));
+ },
+
+ transform: function(sprite) {
+ var me = this,
+ matrix = Ext.create('Ext.draw.Matrix'),
+ transforms = sprite.transformations,
+ transformsLength = transforms.length,
+ i = 0,
+ transform, type;
+
+ for (; i < transformsLength; i++) {
+ transform = transforms[i];
+ type = transform.type;
+ if (type == "translate") {
+ matrix.translate(transform.x, transform.y);
+ }
+ else if (type == "rotate") {
+ matrix.rotate(transform.degrees, transform.x, transform.y);
+ }
+ else if (type == "scale") {
+ matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
+ }
+ }
+ sprite.matrix = matrix;
+ sprite.el.set({transform: matrix.toSvg()});
+ },
+
+ setSize: function(w, h) {
+ var me = this,
+ el = me.el;
+
+ w = +w || me.width;
+ h = +h || me.height;
+ me.width = w;
+ me.height = h;
+
+ el.setSize(w, h);
+ el.set({
+ width: w,
+ height: h
+ });
+ me.callParent([w, h]);
+ },
+
+<span id='Ext-draw.engine.Svg-method-getRegion'> /**
+</span> * Get the region for the surface's canvas area
+ * @returns {Ext.util.Region}
+ */
+ getRegion: function() {
+ // Mozilla requires using the background rect because the svg element returns an
+ // incorrect region. Webkit gives no region for the rect and must use the svg element.
+ var svgXY = this.el.getXY(),
+ rectXY = this.bgRect.getXY(),
+ max = Math.max,
+ x = max(svgXY[0], rectXY[0]),
+ y = max(svgXY[1], rectXY[1]);
+ return {
+ left: x,
+ top: y,
+ right: x + this.width,
+ bottom: y + this.height
+ };
+ },
+
+ onRemove: function(sprite) {
+ if (sprite.el) {
+ sprite.el.remove();
+ delete sprite.el;
+ }
+ this.callParent(arguments);
+ },
+
+ setViewBox: function(x, y, width, height) {
+ if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
+ this.callParent(arguments);
+ this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
+ }
+ },
+
+ render: function (container) {
+ var me = this;
+ if (!me.el) {
+ var width = me.width || 10,
+ height = me.height || 10,
+ el = me.createSvgElement('svg', {
+ xmlns: "http:/" + "/www.w3.org/2000/svg",
+ version: 1.1,
+ width: width,
+ height: height
+ }),
+ defs = me.getDefs(),
+
+ // Create a rect that is always the same size as the svg root; this serves 2 purposes:
+ // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
+ // use it rather than the svg element for retrieving the correct client rect of the
+ // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
+ bgRect = me.createSvgElement("rect", {
+ width: "100%",
+ height: "100%",
+ fill: "#000",
+ stroke: "none",
+ opacity: 0
+ }),
+ webkitRect;
+
+ if (Ext.isSafari3) {
+ // Rect that we will show/hide to fix old WebKit bug with rendering issues.
+ webkitRect = me.createSvgElement("rect", {
+ x: -10,
+ y: -10,
+ width: "110%",
+ height: "110%",
+ fill: "none",
+ stroke: "#000"
+ });
+ }
+ el.appendChild(defs);
+ if (Ext.isSafari3) {
+ el.appendChild(webkitRect);
+ }
+ el.appendChild(bgRect);
+ container.appendChild(el);
+ me.el = Ext.get(el);
+ me.bgRect = Ext.get(bgRect);
+ if (Ext.isSafari3) {
+ me.webkitRect = Ext.get(webkitRect);
+ me.webkitRect.hide();
+ }
+ me.el.on({
+ scope: me,
+ mouseup: me.onMouseUp,
+ mousedown: me.onMouseDown,
+ mouseover: me.onMouseOver,
+ mouseout: me.onMouseOut,
+ mousemove: me.onMouseMove,
+ mouseenter: me.onMouseEnter,
+ mouseleave: me.onMouseLeave,
+ click: me.onClick
+ });
+ }
+ me.renderAll();
+ },
+
+ // private
+ onMouseEnter: function(e) {
+ if (this.el.parent().getRegion().contains(e.getPoint())) {
+ this.fireEvent('mouseenter', e);
+ }
+ },
+
+ // private
+ onMouseLeave: function(e) {
+ if (!this.el.parent().getRegion().contains(e.getPoint())) {
+ this.fireEvent('mouseleave', e);
+ }
+ },
+ // @private - Normalize a delegated single event from the main container to each sprite and sprite group
+ processEvent: function(name, e) {
+ var target = e.getTarget(),
+ surface = this.surface,
+ sprite;
+
+ this.fireEvent(name, e);
+ // We wrap text types in a tspan, sprite is the parent.
+ if (target.nodeName == "tspan" && target.parentNode) {
+ target = target.parentNode;
+ }
+ sprite = this.items.get(target.id);
+ if (sprite) {
+ sprite.fireEvent(name, sprite, e);
+ }
+ },
+
+ /* @private - Wrap SVG text inside a tspan to allow for line wrapping. In addition this normallizes
+ * the baseline for text the vertical middle of the text to be the same as VML.
+ */
+ tuneText: function (sprite, attrs) {
+ var el = sprite.el.dom,
+ tspans = [],
+ height, tspan, text, i, ln, texts, factor;
+
+ if (attrs.hasOwnProperty("text")) {
+ tspans = this.setText(sprite, attrs.text);
+ }
+ // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
+ if (tspans.length) {
+ height = this.getBBoxText(sprite).height;
+ for (i = 0, ln = tspans.length; i < ln; i++) {
+ // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
+ // so we are going to normalize that here
+ factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
+ tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
+ }
+ sprite.dirty = true;
+ }
+ },
+
+ setText: function(sprite, textString) {
+ var me = this,
+ el = sprite.el.dom,
+ x = el.getAttribute("x"),
+ tspans = [],
+ height, tspan, text, i, ln, texts;
+
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+ // Wrap each row into tspan to emulate rows
+ texts = String(textString).split("\n");
+ for (i = 0, ln = texts.length; i < ln; i++) {
+ text = texts[i];
+ if (text) {
+ tspan = me.createSvgElement("tspan");
+ tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
+ tspan.setAttribute("x", x);
+ el.appendChild(tspan);
+ tspans[i] = tspan;
+ }
+ }
+ return tspans;
+ },
+
+ renderAll: function() {
+ this.items.each(this.renderItem, this);
+ },
+
+ renderItem: function (sprite) {
+ if (!this.el) {
+ return;
+ }
+ if (!sprite.el) {
+ this.createSpriteElement(sprite);
+ }
+ if (sprite.zIndexDirty) {
+ this.applyZIndex(sprite);
+ }
+ if (sprite.dirty) {
+ this.applyAttrs(sprite);
+ this.applyTransformations(sprite);
+ }
+ },
+
+ redraw: function(sprite) {
+ sprite.dirty = sprite.zIndexDirty = true;
+ this.renderItem(sprite);
+ },
+
+ applyAttrs: function (sprite) {
+ var me = this,
+ el = sprite.el,
+ group = sprite.group,
+ sattr = sprite.attr,
+ groups, i, ln, attrs, font, key, style, name, rect;
+
+ if (group) {
+ groups = [].concat(group);
+ ln = groups.length;
+ for (i = 0; i < ln; i++) {
+ group = groups[i];
+ me.getGroup(group).add(sprite);
+ }
+ delete sprite.group;
+ }
+ attrs = me.scrubAttrs(sprite) || {};
+
+ // if (sprite.dirtyPath) {
+ sprite.bbox.plain = 0;
+ sprite.bbox.transform = 0;
+ if (sprite.type == "circle" || sprite.type == "ellipse") {
+ attrs.cx = attrs.cx || attrs.x;
+ attrs.cy = attrs.cy || attrs.y;
+ }
+ else if (sprite.type == "rect") {
+ attrs.rx = attrs.ry = attrs.r;
+ }
+ else if (sprite.type == "path" && attrs.d) {
+ attrs.d = Ext.draw.Draw.pathToAbsolute(attrs.d);
+ }
+ sprite.dirtyPath = false;
+ // }
+
+ if (attrs['clip-rect']) {
+ me.setClip(sprite, attrs);
+ delete attrs['clip-rect'];
+ }
+ if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
+ el.set({ style: "font: " + attrs.font});
+ sprite.dirtyFont = false;
+ }
+ if (sprite.type == "image") {
+ el.dom.setAttributeNS(me.xlink, "href", attrs.src);
+ }
+ Ext.applyIf(attrs, me.minDefaults[sprite.type]);
+
+ if (sprite.dirtyHidden) {
+ (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
+ sprite.dirtyHidden = false;
+ }
+ for (key in attrs) {
+ if (attrs.hasOwnProperty(key) && attrs[key] != null) {
+ el.dom.setAttribute(key, String(attrs[key]));
+ }
+ }
+ if (sprite.type == 'text') {
+ me.tuneText(sprite, attrs);
+ }
+
+ //set styles
+ style = sattr.style;
+ if (style) {
+ el.setStyle(style);
+ }
+
+ sprite.dirty = false;
+
+ if (Ext.isSafari3) {
+ // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
+ me.webkitRect.show();
+ setTimeout(function () {
+ me.webkitRect.hide();
+ });
+ }
+ },
+
+ setClip: function(sprite, params) {
+ var me = this,
+ rect = params["clip-rect"],
+ clipEl, clipPath;
+ if (rect) {
+ if (sprite.clip) {
+ sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
+ }
+ clipEl = me.createSvgElement('clipPath');
+ clipPath = me.createSvgElement('rect');
+ clipEl.id = Ext.id(null, 'ext-clip-');
+ clipPath.setAttribute("x", rect.x);
+ clipPath.setAttribute("y", rect.y);
+ clipPath.setAttribute("width", rect.width);
+ clipPath.setAttribute("height", rect.height);
+ clipEl.appendChild(clipPath);
+ me.getDefs().appendChild(clipEl);
+ sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
+ sprite.clip = clipPath;
+ }
+ // if (!attrs[key]) {
+ // var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
+ // clip && clip.parentNode.removeChild(clip);
+ // sprite.el.setAttribute("clip-path", "");
+ // delete attrss.clip;
+ // }
+ },
+
+<span id='Ext-draw.engine.Svg-method-applyZIndex'> /**
+</span> * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
+ * @param {Ext.draw.Sprite} sprite
+ */
+ applyZIndex: function(sprite) {
+ var idx = this.normalizeSpriteCollection(sprite),
+ el = sprite.el,
+ prevEl;
+ if (this.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
+ if (idx > 0) {
+ // Find the first previous sprite which has its DOM element created already
+ do {
+ prevEl = this.items.getAt(--idx).el;
+ } while (!prevEl && idx > 0);
+ }
+ el.insertAfter(prevEl || this.bgRect);
+ }
+ sprite.zIndexDirty = false;
+ },
+
+ createItem: function (config) {
+ var sprite = Ext.create('Ext.draw.Sprite', config);
+ sprite.surface = this;
+ return sprite;
+ },
+
+ addGradient: function(gradient) {
+ gradient = Ext.draw.Draw.parseGradient(gradient);
+ var ln = gradient.stops.length,
+ vector = gradient.vector,
+ gradientEl,
+ stop,
+ stopEl,
+ i;
+ if (gradient.type == "linear") {
+ gradientEl = this.createSvgElement("linearGradient");
+ gradientEl.setAttribute("x1", vector[0]);
+ gradientEl.setAttribute("y1", vector[1]);
+ gradientEl.setAttribute("x2", vector[2]);
+ gradientEl.setAttribute("y2", vector[3]);
+ }
+ else {
+ gradientEl = this.createSvgElement("radialGradient");
+ gradientEl.setAttribute("cx", gradient.centerX);
+ gradientEl.setAttribute("cy", gradient.centerY);
+ gradientEl.setAttribute("r", gradient.radius);
+ if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
+ gradientEl.setAttribute("fx", gradient.focalX);
+ gradientEl.setAttribute("fy", gradient.focalY);
+ }
+ }
+ gradientEl.id = gradient.id;
+ this.getDefs().appendChild(gradientEl);
+
+ for (i = 0; i < ln; i++) {
+ stop = gradient.stops[i];
+ stopEl = this.createSvgElement("stop");
+ stopEl.setAttribute("offset", stop.offset + "%");
+ stopEl.setAttribute("stop-color", stop.color);
+ stopEl.setAttribute("stop-opacity",stop.opacity);
+ gradientEl.appendChild(stopEl);
+ }
+ },
+
+<span id='Ext-draw.engine.Svg-method-hasCls'> /**
+</span> * Checks if the specified CSS class exists on this element's DOM node.
+ * @param {String} className The CSS class to check for
+ * @return {Boolean} True if the class exists, else false
+ */
+ hasCls: function(sprite, className) {
+ return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
+ },
+
+ addCls: function(sprite, className) {
+ var el = sprite.el,
+ i,
+ len,
+ v,
+ cls = [],
+ curCls = el.getAttribute('class') || '';
+ // Separate case is for speed
+ if (!Ext.isArray(className)) {
+ if (typeof className == 'string' && !this.hasCls(sprite, className)) {
+ el.set({ 'class': curCls + ' ' + className });
+ }
+ }
+ else {
+ for (i = 0, len = className.length; i < len; i++) {
+ v = className[i];
+ if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
+ cls.push(v);
+ }
+ }
+ if (cls.length) {
+ el.set({ 'class': ' ' + cls.join(' ') });
+ }
+ }
+ },
+
+ removeCls: function(sprite, className) {
+ var me = this,
+ el = sprite.el,
+ curCls = el.getAttribute('class') || '',
+ i, idx, len, cls, elClasses;
+ if (!Ext.isArray(className)){
+ className = [className];
+ }
+ if (curCls) {
+ elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
+ for (i = 0, len = className.length; i < len; i++) {
+ cls = className[i];
+ if (typeof cls == 'string') {
+ cls = cls.replace(me.trimRe, '');
+ idx = Ext.Array.indexOf(elClasses, cls);
+ if (idx != -1) {
+ elClasses.splice(idx, 1);
+ }
+ }
+ }
+ el.set({ 'class': elClasses.join(' ') });
+ }
+ },
+
+ destroy: function() {
+ var me = this;
+
+ me.callParent();
+ if (me.el) {
+ me.el.remove();
+ }
+ delete me.el;
+ }
+});</pre></pre></body></html>
\ No newline at end of file