4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-draw-engine-Vml'>/**
19 </span> * @class Ext.draw.engine.Vml
20 * @extends Ext.draw.Surface
21 * Provides specific methods to draw with VML.
24 Ext.define('Ext.draw.engine.Vml', {
26 /* Begin Definitions */
28 extend: 'Ext.draw.Surface',
30 requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
36 map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
37 bitesRe: /([clmz]),?([^clmz]*)/gi,
39 fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
40 pathlike: /^(path|rect)$/,
41 NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
42 partialPathRe: /[clmz]/g,
43 fontFamilyRe: /^['"]+|['"]+$/g,
44 baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
45 vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
46 spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
47 measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
53 // Convert an SVG standard path into a VML path
54 path2vml: function (path) {
56 nonVML = me.NonVmlPathRe,
61 command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
62 res, pa, p, r, i, ii, j, jj;
63 if (String(path).match(nonVML)) {
64 command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
65 } else if (!String(path).match(me.partialPathRe)) {
66 res = String(path).replace(bites, function (all, command, args) {
68 isMove = command.toLowerCase() == "m",
70 args.replace(val, function (value) {
71 if (isMove && vals[length] == 2) {
72 res += vals + map[command == "m" ? "l" : "L"];
75 vals.push(Math.round(value * zoom));
83 for (i = 0, ii = pa.length; i < ii; i++) {
85 r = pa[i][0].toLowerCase();
86 if (r == "z") {
89 for (j = 1, jj = p.length; j < jj; j++) {
90 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
94 return res.join(" ");
97 // @private - set of attributes which need to be translated from the sprite API to the native browser API
99 radius: "r",
100 radiusX: "rx",
101 radiusY: "ry",
102 lineWidth: "stroke-width",
103 fillOpacity: "fill-opacity",
104 strokeOpacity: "stroke-opacity",
105 strokeLinejoin: "stroke-linejoin"
108 // @private - Minimun set of defaults for different types of sprites.
111 fill: "none",
113 "stroke-width": null,
115 "fill-opacity": null,
116 "stroke-opacity": null
123 fill: "none",
125 "stroke-width": null,
127 "fill-opacity": null,
128 "stroke-opacity": null
137 fill: "none",
139 "stroke-width": null,
141 "fill-opacity": null,
142 "stroke-opacity": null
147 "text-anchor": "start",
148 font: '10px "Arial"',
149 fill: "#000",
151 "stroke-width": null,
153 "fill-opacity": null,
154 "stroke-opacity": null
158 fill: "none",
160 "stroke-width": null,
162 "fill-opacity": null,
163 "stroke-opacity": null
170 preserveAspectRatio: "none",
176 onMouseEnter: function(e) {
177 this.fireEvent("mouseenter", e);
181 onMouseLeave: function(e) {
182 this.fireEvent("mouseleave", e);
185 // @private - Normalize a delegated single event from the main container to each sprite and sprite group
186 processEvent: function(name, e) {
187 var target = e.getTarget(),
188 surface = this.surface,
190 this.fireEvent(name, e);
191 sprite = this.items.get(target.id);
193 sprite.fireEvent(name, sprite, e);
197 // Create the VML element/elements and append them to the DOM
198 createSpriteElement: function(sprite) {
203 vml = sprite.vml || (sprite.vml = {}),
205 el = (type === 'image') ? me.createNode('image') : me.createNode('shape'),
206 path, skew, textPath;
208 el.coordsize = zoom + ' ' + zoom;
209 el.coordorigin = attr.coordorigin || "0 0";
210 Ext.get(el).addCls(me.spriteCls);
211 if (type == "text") {
212 vml.path = path = me.createNode("path");
213 path.textpathok = true;
214 vml.textpath = textPath = me.createNode("textpath");
216 el.appendChild(textPath);
217 el.appendChild(path);
220 sprite.el = Ext.get(el);
221 me.el.appendChild(el);
222 if (type !== 'image') {
223 skew = me.createNode("skew");
225 el.appendChild(skew);
228 sprite.matrix = Ext.create('Ext.draw.Matrix');
233 sprite.fireEvent("render", sprite);
237 // @private - Get bounding box for the sprite. The Sprite itself has the public method.
238 getBBox: function (sprite, isWithoutTransform) {
239 var realPath = this["getPath" + sprite.type](sprite);
240 if (isWithoutTransform) {
241 sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
242 return sprite.bbox.plain;
244 sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
245 return sprite.bbox.transform;
248 getBBoxText: function (sprite) {
249 var vml = sprite.vml;
251 x: vml.X + (vml.bbx || 0) - vml.W / 2,
252 y: vml.Y - vml.H / 2,
258 applyAttrs: function (sprite) {
261 group = sprite.group,
262 spriteAttr = sprite.attr,
265 style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
268 groups = [].concat(group);
270 for (i = 0; i < ln; i++) {
272 me.getGroup(group).add(sprite);
276 scrubbedAttrs = me.scrubAttrs(sprite) || {};
278 if (sprite.zIndexDirty) {
279 me.setZIndex(sprite);
282 // Apply minimum default attributes
283 Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
285 if (sprite.type == 'image') {
286 Ext.apply(sprite.attr, {
289 width: scrubbedAttrs.width,
290 height: scrubbedAttrs.height
292 bbox = sprite.getBBox();
294 width: bbox.width + 'px',
295 height: bbox.height + 'px'
297 dom.src = scrubbedAttrs.src;
301 dom.href = scrubbedAttrs.href;
304 dom.title = scrubbedAttrs.title;
307 dom.target = scrubbedAttrs.target;
310 dom.cursor = scrubbedAttrs.cursor;
314 if (sprite.dirtyHidden) {
315 (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
316 sprite.dirtyHidden = false;
320 if (sprite.dirtyPath) {
321 if (sprite.type == "circle" || sprite.type == "ellipse") {
322 var cx = scrubbedAttrs.x,
323 cy = scrubbedAttrs.y,
324 rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
325 ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
326 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
327 Math.round((cx - rx) * me.zoom),
328 Math.round((cy - ry) * me.zoom),
329 Math.round((cx + rx) * me.zoom),
330 Math.round((cy + ry) * me.zoom),
331 Math.round(cx * me.zoom));
332 sprite.dirtyPath = false;
334 else if (sprite.type !== "text" && sprite.type !== 'image') {
335 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
336 dom.path = me.path2vml(scrubbedAttrs.path);
337 sprite.dirtyPath = false;
342 if ("clip-rect" in scrubbedAttrs) {
343 me.setClip(sprite, scrubbedAttrs);
346 // Handle text (special handling required)
347 if (sprite.type == "text") {
348 me.setTextAttributes(sprite, scrubbedAttrs);
351 // Handle fill and opacity
352 if (scrubbedAttrs.opacity || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
353 me.setFill(sprite, scrubbedAttrs);
356 // Handle stroke (all fills require a stroke element)
357 if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
358 me.setStroke(sprite, scrubbedAttrs);
362 style = spriteAttr.style;
367 sprite.dirty = false;
370 setZIndex: function(sprite) {
372 if (sprite.attr.zIndex != undefined) {
373 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
375 sprite.zIndexDirty = false;
379 // Normalize all virtualized types into paths.
380 setPaths: function(sprite, params) {
381 var spriteAttr = sprite.attr;
383 sprite.bbox.plain = null;
384 sprite.bbox.transform = null;
385 if (sprite.type == 'circle') {
386 spriteAttr.rx = spriteAttr.ry = params.r;
387 return Ext.draw.Draw.ellipsePath(sprite);
389 else if (sprite.type == 'ellipse') {
390 spriteAttr.rx = params.rx;
391 spriteAttr.ry = params.ry;
392 return Ext.draw.Draw.ellipsePath(sprite);
394 else if (sprite.type == 'rect') {
395 spriteAttr.rx = spriteAttr.ry = params.r;
396 return Ext.draw.Draw.rectPath(sprite);
398 else if (sprite.type == 'path' && spriteAttr.path) {
399 return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
404 setFill: function(sprite, params) {
409 opacity, gradient, fillUrl, rotation, angle;
412 // NOT an expando (but it sure looks like one)...
413 fillEl = el.fill = me.createNode("fill");
416 if (Ext.isArray(params.fill)) {
417 params.fill = params.fill[0];
419 if (params.fill == "none") {
423 if (typeof params.opacity == "number") {
424 fillEl.opacity = params.opacity;
426 if (typeof params["fill-opacity"] == "number") {
427 fillEl.opacity = params["fill-opacity"];
430 if (typeof params.fill == "string") {
431 fillUrl = params.fill.match(me.fillUrlRe);
433 fillUrl = fillUrl[1];
434 // If the URL matches one of the registered gradients, render that gradient
435 if (fillUrl.charAt(0) == "#") {
436 gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
439 // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
440 rotation = params.rotation;
441 angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
442 // IE will flip the angle at 0 degrees...
446 fillEl.angle = angle;
447 fillEl.type = "gradient";
448 fillEl.method = "sigma";
449 fillEl.colors.value = gradient.colors;
451 // Otherwise treat it as an image
453 fillEl.src = fillUrl;
454 fillEl.type = "tile";
458 fillEl.color = Ext.draw.Color.toHex(params.fill);
459 fillEl.src = "";
460 fillEl.type = "solid";
465 el.appendChild(fillEl);
469 setStroke: function(sprite, params) {
472 strokeEl = sprite.strokeEl,
477 strokeEl = sprite.strokeEl = me.createNode("stroke");
480 if (Ext.isArray(params.stroke)) {
481 params.stroke = params.stroke[0];
483 if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
488 if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
489 // VML does NOT support a gradient stroke :(
490 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
492 strokeEl.joinstyle = params["stroke-linejoin"];
493 strokeEl.endcap = params["stroke-linecap"] || "round";
494 strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
495 width = parseFloat(params["stroke-width"] || 1) * 0.75;
496 opacity = params["stroke-opacity"] || 1;
497 // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
498 if (Ext.isNumber(width) && width < 1) {
500 strokeEl.opacity = opacity * width;
503 strokeEl.weight = width;
504 strokeEl.opacity = opacity;
508 el.appendChild(strokeEl);
512 setClip: function(sprite, params) {
515 clipEl = sprite.clipEl,
516 rect = String(params["clip-rect"]).split(me.separatorRe);
518 clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
519 clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
521 if (rect.length == 4) {
522 rect[2] = +rect[2] + (+rect[0]);
523 rect[3] = +rect[3] + (+rect[1]);
524 clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
525 clipEl.setSize(me.el.width, me.el.height);
528 clipEl.setStyle("clip", "");
532 setTextAttributes: function(sprite, params) {
535 textStyle = vml.textpath.style,
536 spanCacheStyle = me.span.style,
540 fontSize: "font-size",
541 fontWeight: "font-weight",
542 fontStyle: "font-style"
546 if (sprite.dirtyFont) {
548 textStyle.font = spanCacheStyle.font = params.font;
550 if (params["font-family"]) {
551 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
552 spanCacheStyle.fontFamily = params["font-family"];
555 for (fontProp in fontObj) {
556 paramProp = params[fontObj[fontProp]];
558 textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
562 me.setText(sprite, params.text);
564 if (vml.textpath.string) {
565 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
567 vml.W = me.span.offsetWidth;
568 vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
570 // text-anchor emulation
571 if (params["text-anchor"] == "middle") {
572 textStyle["v-text-align"] = "center";
574 else if (params["text-anchor"] == "end") {
575 textStyle["v-text-align"] = "right";
576 vml.bbx = -Math.round(vml.W / 2);
579 textStyle["v-text-align"] = "left";
580 vml.bbx = Math.round(vml.W / 2);
585 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);
587 sprite.bbox.plain = null;
588 sprite.bbox.transform = null;
589 sprite.dirtyFont = false;
592 setText: function(sprite, text) {
593 sprite.vml.textpath.string = Ext.htmlDecode(text);
604 hidePrim: function(sprite) {
605 sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
608 showPrim: function(sprite) {
609 sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
612 setSize: function(width, height) {
614 width = width || me.width;
615 height = height || me.height;
621 if (width != undefined) {
622 me.el.setWidth(width);
624 if (height != undefined) {
625 me.el.setHeight(height);
628 // Handle viewBox sizing
631 me.callParent(arguments);
635 setViewBox: function(x, y, width, height) {
636 this.callParent(arguments);
646 <span id='Ext-draw-engine-Vml-method-applyViewBox'> /**
647 </span> * @private Using the current viewBox property and the surface's width and height, calculate the
648 * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
650 applyViewBox: function() {
652 viewBox = me.viewBox,
655 viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
656 relativeHeight, relativeWidth, size;
658 if (viewBox && (width || height)) {
659 viewBoxX = viewBox.x;
660 viewBoxY = viewBox.y;
661 viewBoxWidth = viewBox.width;
662 viewBoxHeight = viewBox.height;
663 relativeHeight = height / viewBoxHeight;
664 relativeWidth = width / viewBoxWidth;
666 if (viewBoxWidth * relativeHeight < width) {
667 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
669 if (viewBoxHeight * relativeWidth < height) {
670 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
673 size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
680 me.items.each(function(item) {
686 onAdd: function(item) {
687 this.callParent(arguments);
689 this.renderItem(item);
693 onRemove: function(sprite) {
698 this.callParent(arguments);
701 render: function (container) {
703 doc = Ext.getDoc().dom;
704 // VML Node factory method (createNode)
705 if (!me.createNode) {
707 if (!doc.namespaces.rvml) {
708 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
710 me.createNode = function (tagName) {
711 return doc.createElement("<rvml:" + tagName + ' class="rvml">');
714 me.createNode = function (tagName) {
715 return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
721 var el = doc.createElement("div");
723 me.el.addCls(me.baseVmlCls);
725 // Measuring span (offscrren)
726 me.span = doc.createElement("span");
727 Ext.get(me.span).addCls(me.measureSpanCls);
728 el.appendChild(me.span);
729 me.el.setSize(me.width || 10, me.height || 10);
730 container.appendChild(el);
733 mouseup: me.onMouseUp,
734 mousedown: me.onMouseDown,
735 mouseover: me.onMouseOver,
736 mouseout: me.onMouseOut,
737 mousemove: me.onMouseMove,
738 mouseenter: me.onMouseEnter,
739 mouseleave: me.onMouseLeave,
746 renderAll: function() {
747 this.items.each(this.renderItem, this);
750 redraw: function(sprite) {
752 this.renderItem(sprite);
755 renderItem: function (sprite) {
756 // Does the surface element exist?
761 // Create sprite element if necessary
763 this.createSpriteElement(sprite);
767 this.applyAttrs(sprite);
768 if (sprite.dirtyTransform) {
769 this.applyTransformations(sprite);
774 rotationCompensation: function (deg, dx, dy) {
775 var matrix = Ext.create('Ext.draw.Matrix');
776 matrix.rotate(-deg, 0.5, 0.5);
783 transform: function(sprite) {
785 matrix = Ext.create('Ext.draw.Matrix'),
786 transforms = sprite.transformations,
787 transformsLength = transforms.length,
795 domStyle = dom.style,
798 deltaX, deltaY, transform, type, compensate, y, fill, newAngle,zoomScaleX, zoomScaleY, newOrigin;
800 for (; i < transformsLength; i++) {
801 transform = transforms[i];
802 type = transform.type;
803 if (type == "translate") {
804 matrix.translate(transform.x, transform.y);
806 else if (type == "rotate") {
807 matrix.rotate(transform.degrees, transform.x, transform.y);
808 deltaDegrees += transform.degrees;
810 else if (type == "scale") {
811 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
812 deltaScaleX *= transform.x;
813 deltaScaleY *= transform.y;
817 if (me.viewBoxShift) {
818 matrix.scale(me.viewBoxShift.scale, me.viewBoxShift.scale, -1, -1);
819 matrix.add(1, 0, 0, 1, me.viewBoxShift.dx, me.viewBoxShift.dy);
822 sprite.matrix = matrix;
825 // Hide element while we transform
827 if (sprite.type != "image" && skew) {
828 // matrix transform via VML skew
829 skew.matrix = matrix.toString();
830 skew.offset = matrix.offset();
833 deltaX = matrix.matrix[0][2];
834 deltaY = matrix.matrix[1][2];
835 // Scale via coordsize property
836 zoomScaleX = zoom / deltaScaleX;
837 zoomScaleY = zoom / deltaScaleY;
839 dom.coordsize = Math.abs(zoomScaleX) + " " + Math.abs(zoomScaleY);
841 // Rotate via rotation property
842 newAngle = deltaDegrees * (deltaScaleX * ((deltaScaleY < 0) ? -1 : 1));
843 if (newAngle != domStyle.rotation && !(newAngle === 0 && !domStyle.rotation)) {
844 domStyle.rotation = newAngle;
847 // Compensate x/y position due to rotation
848 compensate = me.rotationCompensation(deltaDegrees, deltaX, deltaY);
849 deltaX = compensate.x;
850 deltaY = compensate.y;
853 // Handle negative scaling via flipping
854 if (deltaScaleX < 0) {
855 flip += "x";
857 if (deltaScaleY < 0) {
858 flip += " y";
861 if (flip != "" && !dom.style.flip) {
862 domStyle.flip = flip;
865 // Translate via coordorigin property
866 newOrigin = (deltaX * -zoomScaleX) + " " + (deltaY * -zoomScaleY);
867 if (newOrigin != dom.coordorigin) {
868 dom.coordorigin = (deltaX * -zoomScaleX) + " " + (deltaY * -zoomScaleY);
873 createItem: function (config) {
874 return Ext.create('Ext.draw.Sprite', config);
877 getRegion: function() {
878 return this.el.getRegion();
881 addCls: function(sprite, className) {
882 if (sprite && sprite.el) {
883 sprite.el.addCls(className);
887 removeCls: function(sprite, className) {
888 if (sprite && sprite.el) {
889 sprite.el.removeCls(className);
893 <span id='Ext-draw-engine-Vml-method-addGradient'> /**
894 </span> * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
895 * to its corresponding VML attributes and store it for later use by individual sprites.
896 * @param {Object} gradient
898 addGradient: function(gradient) {
899 var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
901 stops = Ext.create('Ext.util.MixedCollection');
903 // Build colors string
904 stops.addAll(gradient.stops);
905 stops.sortByKey("ASC", function(a, b) {
908 return a > b ? 1 : (a < b ? -1 : 0);
910 stops.eachKey(function(k, v) {
911 colors.push(k + "% " + v.color);
914 gradients.add(gradient.id, {
915 colors: colors.join(","),
916 angle: gradient.angle
920 destroy: function() {
923 me.callParent(arguments);