1 <!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.Vml'>/**
2 </span> * @class Ext.draw.engine.Vml
3 * @extends Ext.draw.Surface
4 * Provides specific methods to draw with VML.
7 Ext.define('Ext.draw.engine.Vml', {
9 /* Begin Definitions */
11 extend: 'Ext.draw.Surface',
13 requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
19 map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
20 bitesRe: /([clmz]),?([^clmz]*)/gi,
22 fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
23 pathlike: /^(path|rect)$/,
24 NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
25 partialPathRe: /[clmz]/g,
26 fontFamilyRe: /^['"]+|['"]+$/g,
27 baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
28 vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
29 spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
30 measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
36 // Convert an SVG standard path into a VML path
37 path2vml: function (path) {
39 nonVML = me.NonVmlPathRe,
44 command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
45 res, pa, p, r, i, ii, j, jj;
46 if (String(path).match(nonVML)) {
47 command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
48 } else if (!String(path).match(me.partialPathRe)) {
49 res = String(path).replace(bites, function (all, command, args) {
51 isMove = command.toLowerCase() == "m",
53 args.replace(val, function (value) {
54 if (isMove && vals[length] == 2) {
55 res += vals + map[command == "m" ? "l" : "L"];
58 vals.push(Math.round(value * zoom));
66 for (i = 0, ii = pa.length; i < ii; i++) {
68 r = pa[i][0].toLowerCase();
69 if (r == "z") {
72 for (j = 1, jj = p.length; j < jj; j++) {
73 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
77 return res.join(" ");
80 // @private - set of attributes which need to be translated from the sprite API to the native browser API
82 radius: "r",
83 radiusX: "rx",
84 radiusY: "ry",
85 lineWidth: "stroke-width",
86 fillOpacity: "fill-opacity",
87 strokeOpacity: "stroke-opacity",
88 strokeLinejoin: "stroke-linejoin"
91 // @private - Minimun set of defaults for different types of sprites.
94 fill: "none",
96 "stroke-width": null,
98 "fill-opacity": null,
99 "stroke-opacity": null
106 fill: "none",
108 "stroke-width": null,
110 "fill-opacity": null,
111 "stroke-opacity": null
120 fill: "none",
122 "stroke-width": null,
124 "fill-opacity": null,
125 "stroke-opacity": null
130 "text-anchor": "start",
131 font: '10px "Arial"',
132 fill: "#000",
134 "stroke-width": null,
136 "fill-opacity": null,
137 "stroke-opacity": null
141 fill: "none",
143 "stroke-width": null,
145 "fill-opacity": null,
146 "stroke-opacity": null
153 preserveAspectRatio: "none",
159 onMouseEnter: function(e) {
160 this.fireEvent("mouseenter", e);
164 onMouseLeave: function(e) {
165 this.fireEvent("mouseleave", e);
168 // @private - Normalize a delegated single event from the main container to each sprite and sprite group
169 processEvent: function(name, e) {
170 var target = e.getTarget(),
171 surface = this.surface,
173 this.fireEvent(name, e);
174 sprite = this.items.get(target.id);
176 sprite.fireEvent(name, sprite, e);
180 // Create the VML element/elements and append them to the DOM
181 createSpriteElement: function(sprite) {
186 vml = sprite.vml || (sprite.vml = {}),
188 el = (type === 'image') ? me.createNode('image') : me.createNode('shape'),
189 path, skew, textPath;
191 el.coordsize = zoom + ' ' + zoom;
192 el.coordorigin = attr.coordorigin || "0 0";
193 Ext.get(el).addCls(me.spriteCls);
194 if (type == "text") {
195 vml.path = path = me.createNode("path");
196 path.textpathok = true;
197 vml.textpath = textPath = me.createNode("textpath");
199 el.appendChild(textPath);
200 el.appendChild(path);
203 sprite.el = Ext.get(el);
204 me.el.appendChild(el);
205 if (type !== 'image') {
206 skew = me.createNode("skew");
208 el.appendChild(skew);
211 sprite.matrix = Ext.create('Ext.draw.Matrix');
216 sprite.fireEvent("render", sprite);
220 // @private - Get bounding box for the sprite. The Sprite itself has the public method.
221 getBBox: function (sprite, isWithoutTransform) {
222 var realPath = this["getPath" + sprite.type](sprite);
223 if (isWithoutTransform) {
224 sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
225 return sprite.bbox.plain;
227 sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
228 return sprite.bbox.transform;
231 getBBoxText: function (sprite) {
232 var vml = sprite.vml;
234 x: vml.X + (vml.bbx || 0) - vml.W / 2,
235 y: vml.Y - vml.H / 2,
241 applyAttrs: function (sprite) {
244 group = sprite.group,
245 spriteAttr = sprite.attr,
248 style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
251 groups = [].concat(group);
253 for (i = 0; i < ln; i++) {
255 me.getGroup(group).add(sprite);
259 scrubbedAttrs = me.scrubAttrs(sprite) || {};
261 if (sprite.zIndexDirty) {
262 me.setZIndex(sprite);
265 // Apply minimum default attributes
266 Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
268 if (sprite.type == 'image') {
269 Ext.apply(sprite.attr, {
272 width: scrubbedAttrs.width,
273 height: scrubbedAttrs.height
275 bbox = sprite.getBBox();
277 width: bbox.width + 'px',
278 height: bbox.height + 'px'
280 dom.src = scrubbedAttrs.src;
284 dom.href = scrubbedAttrs.href;
287 dom.title = scrubbedAttrs.title;
290 dom.target = scrubbedAttrs.target;
293 dom.cursor = scrubbedAttrs.cursor;
297 if (sprite.dirtyHidden) {
298 (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
299 sprite.dirtyHidden = false;
303 if (sprite.dirtyPath) {
304 if (sprite.type == "circle" || sprite.type == "ellipse") {
305 var cx = scrubbedAttrs.x,
306 cy = scrubbedAttrs.y,
307 rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
308 ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
309 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
310 Math.round((cx - rx) * me.zoom),
311 Math.round((cy - ry) * me.zoom),
312 Math.round((cx + rx) * me.zoom),
313 Math.round((cy + ry) * me.zoom),
314 Math.round(cx * me.zoom));
315 sprite.dirtyPath = false;
317 else if (sprite.type !== "text" && sprite.type !== 'image') {
318 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
319 dom.path = me.path2vml(scrubbedAttrs.path);
320 sprite.dirtyPath = false;
325 if ("clip-rect" in scrubbedAttrs) {
326 me.setClip(sprite, scrubbedAttrs);
329 // Handle text (special handling required)
330 if (sprite.type == "text") {
331 me.setTextAttributes(sprite, scrubbedAttrs);
334 // Handle fill and opacity
335 if (scrubbedAttrs.opacity || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
336 me.setFill(sprite, scrubbedAttrs);
339 // Handle stroke (all fills require a stroke element)
340 if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
341 me.setStroke(sprite, scrubbedAttrs);
345 style = spriteAttr.style;
350 sprite.dirty = false;
353 setZIndex: function(sprite) {
355 if (sprite.attr.zIndex != undefined) {
356 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
358 sprite.zIndexDirty = false;
362 // Normalize all virtualized types into paths.
363 setPaths: function(sprite, params) {
364 var spriteAttr = sprite.attr;
366 sprite.bbox.plain = null;
367 sprite.bbox.transform = null;
368 if (sprite.type == 'circle') {
369 spriteAttr.rx = spriteAttr.ry = params.r;
370 return Ext.draw.Draw.ellipsePath(sprite);
372 else if (sprite.type == 'ellipse') {
373 spriteAttr.rx = params.rx;
374 spriteAttr.ry = params.ry;
375 return Ext.draw.Draw.ellipsePath(sprite);
377 else if (sprite.type == 'rect') {
378 spriteAttr.rx = spriteAttr.ry = params.r;
379 return Ext.draw.Draw.rectPath(sprite);
381 else if (sprite.type == 'path' && spriteAttr.path) {
382 return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
387 setFill: function(sprite, params) {
392 opacity, gradient, fillUrl, rotation, angle;
395 // NOT an expando (but it sure looks like one)...
396 fillEl = el.fill = me.createNode("fill");
399 if (Ext.isArray(params.fill)) {
400 params.fill = params.fill[0];
402 if (params.fill == "none") {
406 if (typeof params.opacity == "number") {
407 fillEl.opacity = params.opacity;
409 if (typeof params["fill-opacity"] == "number") {
410 fillEl.opacity = params["fill-opacity"];
413 if (typeof params.fill == "string") {
414 fillUrl = params.fill.match(me.fillUrlRe);
416 fillUrl = fillUrl[1];
417 // If the URL matches one of the registered gradients, render that gradient
418 if (fillUrl.charAt(0) == "#") {
419 gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
422 // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
423 rotation = params.rotation;
424 angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
425 // IE will flip the angle at 0 degrees...
429 fillEl.angle = angle;
430 fillEl.type = "gradient";
431 fillEl.method = "sigma";
432 fillEl.colors.value = gradient.colors;
434 // Otherwise treat it as an image
436 fillEl.src = fillUrl;
437 fillEl.type = "tile";
441 fillEl.color = Ext.draw.Color.toHex(params.fill);
442 fillEl.src = "";
443 fillEl.type = "solid";
448 el.appendChild(fillEl);
452 setStroke: function(sprite, params) {
455 strokeEl = sprite.strokeEl,
460 strokeEl = sprite.strokeEl = me.createNode("stroke");
463 if (Ext.isArray(params.stroke)) {
464 params.stroke = params.stroke[0];
466 if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
471 if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
472 // VML does NOT support a gradient stroke :(
473 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
475 strokeEl.joinstyle = params["stroke-linejoin"];
476 strokeEl.endcap = params["stroke-linecap"] || "round";
477 strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
478 width = parseFloat(params["stroke-width"] || 1) * 0.75;
479 opacity = params["stroke-opacity"] || 1;
480 // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
481 if (Ext.isNumber(width) && width < 1) {
483 strokeEl.opacity = opacity * width;
486 strokeEl.weight = width;
487 strokeEl.opacity = opacity;
491 el.appendChild(strokeEl);
495 setClip: function(sprite, params) {
498 clipEl = sprite.clipEl,
499 rect = String(params["clip-rect"]).split(me.separatorRe);
501 clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
502 clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
504 if (rect.length == 4) {
505 rect[2] = +rect[2] + (+rect[0]);
506 rect[3] = +rect[3] + (+rect[1]);
507 clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
508 clipEl.setSize(me.el.width, me.el.height);
511 clipEl.setStyle("clip", "");
515 setTextAttributes: function(sprite, params) {
518 textStyle = vml.textpath.style,
519 spanCacheStyle = me.span.style,
523 fontSize: "font-size",
524 fontWeight: "font-weight",
525 fontStyle: "font-style"
529 if (sprite.dirtyFont) {
531 textStyle.font = spanCacheStyle.font = params.font;
533 if (params["font-family"]) {
534 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
535 spanCacheStyle.fontFamily = params["font-family"];
538 for (fontProp in fontObj) {
539 paramProp = params[fontObj[fontProp]];
541 textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
545 me.setText(sprite, params.text);
547 if (vml.textpath.string) {
548 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
550 vml.W = me.span.offsetWidth;
551 vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
553 // text-anchor emulation
554 if (params["text-anchor"] == "middle") {
555 textStyle["v-text-align"] = "center";
557 else if (params["text-anchor"] == "end") {
558 textStyle["v-text-align"] = "right";
559 vml.bbx = -Math.round(vml.W / 2);
562 textStyle["v-text-align"] = "left";
563 vml.bbx = Math.round(vml.W / 2);
568 vml.path.v = Ext.String.format("m{0},{1}l{2},{1}", Math.round(vml.X * zoom), Math.round(vml.Y * zoom), Math.round(vml.X * zoom) + 1);
570 sprite.bbox.plain = null;
571 sprite.bbox.transform = null;
572 sprite.dirtyFont = false;
575 setText: function(sprite, text) {
576 sprite.vml.textpath.string = Ext.htmlDecode(text);
587 hidePrim: function(sprite) {
588 sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
591 showPrim: function(sprite) {
592 sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
595 setSize: function(width, height) {
597 viewBox = me.viewBox,
598 scaleX, scaleY, items, i, len;
599 width = width || me.width;
600 height = height || me.height;
609 if (width != undefined) {
610 me.el.setWidth(width);
612 if (height != undefined) {
613 me.el.setHeight(height);
616 // Handle viewBox sizing
617 if (viewBox && (width || height)) {
618 var viewBoxX = viewBox.x,
619 viewBoxY = viewBox.y,
620 viewBoxWidth = viewBox.width,
621 viewBoxHeight = viewBox.height,
622 relativeHeight = height / viewBoxHeight,
623 relativeWidth = width / viewBoxWidth,
625 if (viewBoxWidth * relativeHeight < width) {
626 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
628 if (viewBoxHeight * relativeWidth < height) {
629 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
631 size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
632 // Scale and translate group
638 items = me.items.items;
639 for (i = 0, len = items.length; i < len; i++) {
640 me.transform(items[i]);
643 this.callParent(arguments);
646 setViewBox: function(x, y, width, height) {
647 this.callParent(arguments);
656 onAdd: function(item) {
657 this.callParent(arguments);
659 this.renderItem(item);
663 onRemove: function(sprite) {
668 this.callParent(arguments);
671 render: function (container) {
673 doc = Ext.getDoc().dom;
674 // VML Node factory method (createNode)
675 if (!me.createNode) {
677 if (!doc.namespaces.rvml) {
678 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
680 me.createNode = function (tagName) {
681 return doc.createElement("<rvml:" + tagName + ' class="rvml">');
684 me.createNode = function (tagName) {
685 return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
691 var el = doc.createElement("div");
693 me.el.addCls(me.baseVmlCls);
695 // Measuring span (offscrren)
696 me.span = doc.createElement("span");
697 Ext.get(me.span).addCls(me.measureSpanCls);
698 el.appendChild(me.span);
699 me.el.setSize(me.width || 10, me.height || 10);
700 container.appendChild(el);
703 mouseup: me.onMouseUp,
704 mousedown: me.onMouseDown,
705 mouseover: me.onMouseOver,
706 mouseout: me.onMouseOut,
707 mousemove: me.onMouseMove,
708 mouseenter: me.onMouseEnter,
709 mouseleave: me.onMouseLeave,
716 renderAll: function() {
717 this.items.each(this.renderItem, this);
720 redraw: function(sprite) {
722 this.renderItem(sprite);
725 renderItem: function (sprite) {
726 // Does the surface element exist?
731 // Create sprite element if necessary
733 this.createSpriteElement(sprite);
737 this.applyAttrs(sprite);
738 if (sprite.dirtyTransform) {
739 this.applyTransformations(sprite);
744 rotationCompensation: function (deg, dx, dy) {
745 var matrix = Ext.create('Ext.draw.Matrix');
746 matrix.rotate(-deg, 0.5, 0.5);
753 transform: function(sprite) {
755 matrix = Ext.create('Ext.draw.Matrix'),
756 transforms = sprite.transformations,
757 transformsLength = transforms.length,
765 domStyle = dom.style,
768 deltaX, deltaY, transform, type, compensate, y, fill, newAngle,zoomScaleX, zoomScaleY, newOrigin;
770 for (; i < transformsLength; i++) {
771 transform = transforms[i];
772 type = transform.type;
773 if (type == "translate") {
774 matrix.translate(transform.x, transform.y);
776 else if (type == "rotate") {
777 matrix.rotate(transform.degrees, transform.x, transform.y);
778 deltaDegrees += transform.degrees;
780 else if (type == "scale") {
781 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
782 deltaScaleX *= transform.x;
783 deltaScaleY *= transform.y;
787 if (me.viewBoxShift) {
788 matrix.scale(me.viewBoxShift.scale, me.viewBoxShift.scale, -1, -1);
789 matrix.add(1, 0, 0, 1, me.viewBoxShift.dx, me.viewBoxShift.dy);
792 sprite.matrix = matrix;
795 // Hide element while we transform
797 if (sprite.type != "image" && skew) {
798 // matrix transform via VML skew
799 skew.matrix = matrix.toString();
800 skew.offset = matrix.offset();
803 deltaX = matrix.matrix[0][2];
804 deltaY = matrix.matrix[1][2];
805 // Scale via coordsize property
806 zoomScaleX = zoom / deltaScaleX;
807 zoomScaleY = zoom / deltaScaleY;
809 dom.coordsize = Math.abs(zoomScaleX) + " " + Math.abs(zoomScaleY);
811 // Rotate via rotation property
812 newAngle = deltaDegrees * (deltaScaleX * ((deltaScaleY < 0) ? -1 : 1));
813 if (newAngle != domStyle.rotation && !(newAngle === 0 && !domStyle.rotation)) {
814 domStyle.rotation = newAngle;
817 // Compensate x/y position due to rotation
818 compensate = me.rotationCompensation(deltaDegrees, deltaX, deltaY);
819 deltaX = compensate.x;
820 deltaY = compensate.y;
823 // Handle negative scaling via flipping
824 if (deltaScaleX < 0) {
825 flip += "x";
827 if (deltaScaleY < 0) {
828 flip += " y";
831 if (flip != "" && !dom.style.flip) {
832 domStyle.flip = flip;
835 // Translate via coordorigin property
836 newOrigin = (deltaX * -zoomScaleX) + " " + (deltaY * -zoomScaleY);
837 if (newOrigin != dom.coordorigin) {
838 dom.coordorigin = (deltaX * -zoomScaleX) + " " + (deltaY * -zoomScaleY);
843 createItem: function (config) {
844 return Ext.create('Ext.draw.Sprite', config);
847 getRegion: function() {
848 return this.el.getRegion();
851 addCls: function(sprite, className) {
852 if (sprite && sprite.el) {
853 sprite.el.addCls(className);
857 removeCls: function(sprite, className) {
858 if (sprite && sprite.el) {
859 sprite.el.removeCls(className);
863 <span id='Ext-draw.engine.Vml-method-addGradient'> /**
864 </span> * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
865 * to its corresponding VML attributes and store it for later use by individual sprites.
866 * @param {Object} gradient
868 addGradient: function(gradient) {
869 var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
871 stops = Ext.create('Ext.util.MixedCollection');
873 // Build colors string
874 stops.addAll(gradient.stops);
875 stops.sortByKey("ASC", function(a, b) {
878 return a > b ? 1 : (a < b ? -1 : 0);
880 stops.eachKey(function(k, v) {
881 colors.push(k + "% " + v.color);
884 gradients.add(gradient.id, {
885 colors: colors.join(","),
886 angle: gradient.angle
890 destroy: function() {
893 me.callParent(arguments);
900 </pre></pre></body></html>