/** * @class Ext.draw.engine.Svg * @extends Ext.draw.Surface * * SVG engine. */Ext.define('Ext.draw.engine.Svg', { extend: 'Ext.draw.Surface', requires: ['Ext.draw.engine.SvgContext'], isSVG: true, config: { /** * @cfg {Boolean} highPrecision * Nothing needs to be done in high precision mode. */ highPrecision: false }, getElementConfig: function () { return { reference: 'element', style: { position: 'absolute' }, children: [ { reference: 'bodyElement', style: { width: '100%', height: '100%', position: 'relative' }, children: [ { tag: 'svg', reference: 'svgElement', namespace: "http://www.w3.org/2000/svg", width: '100%', height: '100%', version: 1.1 } ] } ] }; }, constructor: function (config) { var me = this; me.callParent([config]); me.mainGroup = me.createSvgNode("g"); me.defsElement = me.createSvgNode("defs"); // me.svgElement is assigned in element creation of Ext.Component. me.svgElement.appendChild(me.mainGroup); me.svgElement.appendChild(me.defsElement); me.ctx = new Ext.draw.engine.SvgContext(me); }, /** * Creates a DOM element under the SVG namespace of the given type. * @param {String} type The type of the SVG DOM element. * @return {*} The created element. */ createSvgNode: function (type) { var node = document.createElementNS("http://www.w3.org/2000/svg", type); return Ext.get(node); }, /** * @private * Returns the SVG DOM element at the given position. * If it does not already exist or is a different element tag, * it will be created and inserted into the DOM. * @param {Ext.dom.Element} group The parent DOM element. * @param {String} tag The SVG element tag. * @param {Number} position The position of the element in the DOM. * @return {Ext.dom.Element} The SVG element. */ getSvgElement: function (group, tag, position) { var childNodes = group.dom.childNodes, length = childNodes.length, element; if (position < length) { element = childNodes[position]; if (element.tagName === tag) { return Ext.get(element); } else { Ext.destroy(element); } } else if (position > length) { Ext.raise("Invalid position."); } element = Ext.get(this.createSvgNode(tag)); if (position === 0) { group.insertFirst(element); } else { element.insertAfter(Ext.fly(childNodes[position - 1])); } element.cache = {}; return element; }, /** * @private * Applies attributes to the given element. * @param {Ext.dom.Element} element The DOM element to be applied. * @param {Object} attributes The attributes to apply to the element. */ setElementAttributes: function (element, attributes) { var dom = element.dom, cache = element.cache, name, value; for (name in attributes) { value = attributes[name]; if (cache[name] !== value) { cache[name] = value; dom.setAttribute(name, value); } } }, /** * @private * Gets the next reference element under the SVG 'defs' tag. * @param {String} tagName The type of reference element. * @return {Ext.dom.Element} The reference element. */ getNextDef: function (tagName) { return this.getSvgElement(this.defsElement, tagName, this.defsPosition++); }, /** * @inheritdoc */ clearTransform: function () { var me = this; me.mainGroup.set({transform: me.matrix.toSvg()}); }, /** * @inheritdoc */ clear: function () { this.ctx.clear(); this.removeSurplusDefs(); this.defsPosition = 0; }, removeSurplusDefs: function () { var defsElement = this.defsElement, defs = defsElement.dom.childNodes, ln = defs.length, i; for (i = ln - 1; i > this.defsPosition; i--) { defsElement.removeChild(defs[i]); } }, /** * @inheritdoc */ renderSprite: function (sprite) { var me = this, rect = me.getRect(), ctx = me.ctx; // This check is simplistic, but should result in a better performance // compared to !sprite.isVisible() when most surface sprites are visible. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) { // Create an empty group for each hidden sprite, // so that when these sprites do become visible, // they don't need groups to be created and don't // mess up the previous order of elements in the // document, i.e. sprites rendered in the next // frame reuse the same elements they used in the // previous frame. ctx.save(); ctx.restore(); return; } // Each sprite is rendered in its own group ('g' element), // returned by the `ctx.save` method. // Essentially, the group _is_ the sprite. sprite.element = ctx.save(); sprite.preRender(this); sprite.useAttributes(ctx, rect); if (false === sprite.render(this, ctx, [0, 0, rect[2], rect[3]])) { return false; } sprite.setDirty(false); ctx.restore(); }, /** * @private */ toSVG: function (size, surfaces) { var className = Ext.getClassName(this), svg, surface, rect, i; svg = '<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"' + ' width="' + size.width + '"' + ' height="' + size.height + '">'; for (i = 0; i < surfaces.length; i++) { surface = surfaces[i]; if (Ext.getClassName(surface) !== className) { continue; } rect = surface.getRect(); svg += '<g transform="translate(' + rect[0] + ',' + rect[1] + ')">'; svg += this.serializeNode(surface.svgElement.dom); svg += '</g>'; } svg += '</svg>'; return svg; }, b64EncodeUnicode: function (str) { // Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa // on a Unicode string will cause a Character Out Of Range exception if a character // exceeds the range of a 8-bit ASCII-encoded character. More information: // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { return String.fromCharCode('0x' + p1); })); }, flatten: function (size, surfaces) { var svg = '<?xml version="1.0" standalone="yes"?>'; svg += this.toSVG(size, surfaces); return { data: 'data:image/svg+xml;base64,' + this.b64EncodeUnicode(svg), type: 'svg' }; }, /** * @private * Serializes an SVG DOM element and its children recursively into a string. * @param {Object} node DOM element to serialize. * @return {String} */ serializeNode: function (node) { var result = '', i, n, attr, child; if (node.nodeType === document.TEXT_NODE) { return node.nodeValue; } result += '<' + node.nodeName; if (node.attributes.length) { for (i = 0, n = node.attributes.length; i < n; i++) { attr = node.attributes[i]; result += ' ' + attr.name + '="' + Ext.String.htmlEncode(attr.value) + '"'; } } result += '>'; if (node.childNodes && node.childNodes.length) { for (i = 0, n = node.childNodes.length; i < n; i++) { child = node.childNodes[i]; result += this.serializeNode(child); } } result += '</' + node.nodeName + '>'; return result; }, /** * Destroys the Canvas element and prepares it for Garbage Collection. */ destroy: function () { var me = this; me.ctx.destroy(); me.mainGroup.destroy(); me.defsElement.destroy(); delete me.mainGroup; delete me.defsElement; delete me.ctx; me.callParent(); }, remove: function (sprite, destroySprite) { if (sprite && sprite.element) { // If sprite has an associated SVG element, remove it from the surface. sprite.element.destroy(); sprite.element = null; } this.callParent(arguments); }});