All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rwt.graphics.VML.js Maven / Gradle / Ivy

Go to download

The Rich Ajax Platform lets you build rich, Ajax-enabled Web applications.

There is a newer version: 3.29.0
Show newest version
/*******************************************************************************
 * Copyright (c) 2010, 2012 EclipseSource and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    EclipseSource - initial API and implementation
 ******************************************************************************/

rwt.qx.Class.define( "rwt.graphics.VML", {

  statics : {

    init : function() {
      document.namespaces.add( "v", "urn:schemas-microsoft-com:vml");
      document.namespaces.add( "o", "urn:schemas-microsoft-com:office:office");
      var sheet = document.createStyleSheet();
      sheet.cssText = "v\\:* { behavior:url(#default#VML);display:inline-block; } "+
                      "o\\:* { behavior: url(#default#VML);}";
      this._vmlEnabled = true;
    },

    createCanvas : function() {
      var result = {};
      result.type = "vmlCanvas";
      var node = document.createElement( "div" );
      node.style.position = "absolute";
      node.style.width = "10px";
      node.style.height = "10px";
      node.style.top = "0";
      node.style.left = "0";
      node.style.fontSize = "0";
      node.style.lineHeight = "0";
      result.node = node;
      result.children = {};
      return result;
    },

    clearCanvas : function( canvas ) {
      for( var hash in canvas.children ) {
        canvas.node.removeChild( canvas.children[ hash ].node );
      }
      canvas.children = {};
    },

    getCanvasNode : function( canvas ) {
      return canvas.node;
    },

    handleAppear : function( canvas ) {
      var children = canvas.children;
      for( var hash in children ) {
       this._handleAppearShape( children[ hash ] );
      }
    },

    createShape : function( type ) {
      var result = null;
      switch( type ) {
        case "rect":
          result = this._createRect();
        break;
        case "roundrect":
        case "custom":
          result = this._createCustomShape();
          result.blurRadius = 0;
        break;
        case "image":
          result = this._createImage();
        break;
        default:
          throw "VML does not support shape " + type;
      }
      result.restoreData = { "fill" : {} };
      result.node.stroked = false;
      // TODO [tb] : test if stroke-node conflicts with stroke-properties on
      // the element-node when moved in dom.
      var fill = this._createNode( "fill" );
      fill.method = "sigma";
      result.node.appendChild( fill );
      result.fill = fill;
      this.setFillColor( result, null );
      return result;
    },

    addToCanvas : function( canvas, shape, beforeShape ) {
      var hash = rwt.qx.Object.toHashCode( shape );
      canvas.children[ hash ] = shape;
      //canvas.node.appendChild( shape.node );
      if( beforeShape ) {
        canvas.node.insertBefore( shape.node, beforeShape.node );
      } else {
        canvas.node.appendChild( shape.node );
      }
    },

    enableOverflow : function( canvas ) {
      // nothing to do
    },

    removeFromCanvas : function( canvas, shape ) {
      var hash = rwt.qx.Object.toHashCode( shape );
      delete canvas.children[ hash ];
      canvas.node.removeChild( shape.node );
    },

    setDisplay : function( shape, value ) {
      shape.node.style.display = value ? "" : "none";
    },

    getDisplay : function( shape) {
      var result = shape.node.style.display == "none" ? false : true;
      return result;
    },

    setRectBounds : function( shape, x, y, width, height ) {
      var node = shape.node;
      node.style.width = this._convertNumeric( width, false );
      node.style.height = this._convertNumeric( height, false );
      node.style.left = this._convertNumeric( x, true );
      node.style.top = this._convertNumeric( y, true );
    },

    /**
     * "crop" is an optional array [ top, right, bottom, left ]. The values have
     * to be between 0 and 1, representing percentage of the image-dimension.
     */
    setImageData : function( shape, src, x, y, width, height, crop ) {
      var node = shape.node;
      node.src = src;
      if( typeof crop != "undefined" ) {
        node.cropTop = crop[ 0 ];
        node.cropRight = crop[ 1 ];
        node.cropBottom =  crop[ 2 ];
        node.cropLeft = crop[ 3 ];
      }
      node.style.width = width;
      node.style.height = height;
      node.style.left = x;
      node.style.top = y;
    },

    setRoundRectLayout : function( shape, x, y, width, height, radii ) {
      var quarter = this._VMLDEGREE * 90;
      var maxRadius = Math.floor( Math.min( width, height ) / 2 );
      var radiusLeftTop = Math.min( radii[ 0 ], maxRadius );
      var radiusTopRight = Math.min( radii[ 1 ], maxRadius );
      var radiusRightBottom = Math.min( radii[ 2 ], maxRadius );
      var radiusBottomLeft = Math.min( radii[ 3 ], maxRadius );
      radiusLeftTop = this._convertNumeric( radiusLeftTop, false );
      radiusTopRight = this._convertNumeric( radiusTopRight, false );
      radiusRightBottom = this._convertNumeric( radiusRightBottom, false );
      radiusBottomLeft = this._convertNumeric( radiusBottomLeft, false );
      var bluroffsets = this._getBlurOffsets( shape.blurRadius );
      var rectLeft = this._convertNumeric( x - bluroffsets[ 1 ], true );
      var rectTop = this._convertNumeric( y - bluroffsets[ 1 ], true );
      var rectWidth = this._convertNumeric( width - bluroffsets[ 2 ], false );
      var rectHeight = this._convertNumeric( height - bluroffsets[ 2 ], false );
      var path = [];
      if( radiusLeftTop > 0 ) {
        path.push( "AL", rectLeft + radiusLeftTop, rectTop + radiusLeftTop );
        path.push( radiusLeftTop, radiusLeftTop, 2 * quarter, quarter );
      } else {
        path.push( "M", rectLeft, rectTop + radiusLeftTop );
      }
      if( radiusTopRight > 0 ) {
        path.push( "AE", rectLeft + rectWidth - radiusTopRight );
        path.push( rectTop + radiusTopRight );
        path.push( radiusTopRight, radiusTopRight, 3 * quarter, quarter );
      } else {
        path.push( "L", rectLeft + rectWidth, rectTop );
      }
      if( radiusRightBottom > 0 ) {
        path.push( "AE", rectLeft + rectWidth - radiusRightBottom );
        path.push( rectTop + rectHeight - radiusRightBottom );
        path.push( radiusRightBottom, radiusRightBottom, 0, quarter );
      } else {
        path.push( "L", rectLeft + rectWidth, rectTop + rectHeight );
      }
      if( radiusBottomLeft > 0 ) {
        path.push( "AE", rectLeft + radiusBottomLeft );
        path.push( rectTop + rectHeight - radiusBottomLeft );
        path.push( radiusBottomLeft, radiusBottomLeft, quarter, quarter );
      } else {
        path.push( "L", rectLeft, rectTop + rectHeight );
      }
      path.push( "X E" );
      shape.node.path = path.join( " " );
    },

    applyDrawingContext : function( shape, context, fill ) {
      var opacity = context.globalAlpha;
      if( opacity != 1 ) {
        this.setOpacity( shape, opacity );
      }
      if( fill ) {
        var fill = context.fillStyle;
        if( fill instanceof Array ) {
          this.setFillGradient( shape, context.fillStyle );
        } else {
          this.setFillColor( shape, context.fillStyle );
        }
        this.setStroke( shape, null, 0 );
      } else {
        this.setFillColor( shape, null );
        this.setStroke( shape, context.strokeStyle, context.lineWidth );
        var endCap = context.lineCap == "butt" ? "flat" : context.lineCap;
        var joinStyle = context.lineJoin;
        var miterLimit = context.miterLimit;
        this._setStrokeStyle( shape, joinStyle, miterLimit, endCap );
      }
      shape.node.path = this._convertPath( context._currentPath );
    },

    createShapeFromContext : function( context, fill ) {
      var shape = this.createShape( "custom" );
      this.applyDrawingContext( shape, context, fill );
      return shape;
    },

    setFillColor : function( shape, color ) {
      var fill = shape.fill;
      fill.type = "solid";
      if( color != null && color !== "transparent" && color !== "" ) {
        this._setFillEnabled( shape, true );
        fill.color = color;
        shape.restoreData.fill.color = color;
      } else {
        this._setFillEnabled( shape, false );
        delete shape.restoreData.fill.color;
      }
    },

    getFillColor : function( shape ) {
      var result = null;
      if( this.getFillType( shape ) == "color" ) {
        result = shape.restoreData.fill.color;
      }
      return result;
    },

    setFillGradient : function( shape, gradient ) {
      var fill = shape.fill;
      if( gradient != null ) {
        shape.node.removeChild( shape.fill );
        this._setFillEnabled( shape, true );
        delete shape.restoreData.fill.color;
        fill.type = "gradient";
        //the "color" attribute of fill is lost when the node
        //is removed from the dom. However, it can be overwritten
        //by a transition colors, so it doesn't matter
        var startColor = gradient[ 0 ][ 1 ];
        //fill.color = startColor;
        fill.color2 = gradient[ gradient.length - 1 ][ 1 ];
        fill.angle = gradient.horizontal ? 270 : 180;
        var transitionColors = "0% " + startColor;
        var lastColor = rwt.util.Colors.stringToRgb( startColor );
        var nextColor = null;
        var lastOffset = 0;
        var currentOffset = null;
        for( var colorPos = 1; colorPos < gradient.length; colorPos++ ) {
          var color = gradient[ colorPos ][ 1 ];
          nextColor = rwt.util.Colors.stringToRgb( color );
          var nextOffset = gradient[ colorPos ][ 0 ];
          transitionColors += ", ";
          transitionColors += this._transitionColors( lastColor,
                                                      nextColor,
                                                      lastOffset,
                                                      nextOffset,
                                                      3 );
          transitionColors += ", " + ( nextOffset * 100 ) + "% " + color;
          lastColor = nextColor;
          lastOffset = nextOffset;
        }
        fill.colors = transitionColors;
        shape.node.appendChild( fill );
      } else {
        this._setFillEnabled( shape, true );
      }
    },

    setFillPattern : function( shape, source, width, height ) {
      var fill = shape.fill;
      if( source != null ) {
        shape.node.removeChild( shape.fill );
        this._setFillEnabled( shape, true );
        fill.type = "tile";
        fill.src = source;
        // IE only accepts "pt" for the size:
        fill.size = ( width * 0.75 ) + "pt," + ( height * 0.75 ) + "pt";
        shape.node.appendChild( fill );
      } else {
        this._setFillEnabled( shape, false );
      }
    },

    getFillType : function( shape ) {
      var on = shape.fill.on;
      var result = !on ? null : shape.fill.type;
      if( result == "solid" ) {
        result = "color";
      }
      if( result == "tile" ) {
        result = "pattern";
      }
      return result;
    },

    // About VML-strokes and opacity:
    // There is a bug in the VML antialiasing, that can produce grey pixels
    // around vml elements if the css-opacity-filter is used on any of its
    // parents, including the widgets div or any of the parent-widgets divs.
    // However this ONLY happens if the element that the opacity is applied to,
    // does NOT have a background of its own!
    // If antialiasing is turned off, the effect is gone, but without
    // antaliasing the element looks just as ugly as with the glitch.
    setStroke : function( shape, color, width ) {
      if( width > 0 ) {
        shape.node.stroked = true;
        shape.node.strokecolor = color;
        shape.node.strokeweight = width + "px";
        shape.restoreData.strokecolor = color;
        shape.restoreData.strokeweight = width + "px";
        // TODO [tb] : joinstyle
      } else {
        shape.node.stroked = false;
        delete shape.restoreData.strokecolor;
        delete shape.restoreData.strokeweight;
      }
    },

    getStrokeWidth : function( shape ) {
      // IE returns strokeweight either as number (then its pt)
      // or as string with a "px" or "pt" postfix
      var result = false;
      if( shape.node.stroked ) {
        result = shape.node.strokeweight;
        var isPt = typeof result == "number" || result.search( "pt" ) != -1;
        result = parseFloat( result );
        result = isPt ? result / 0.75 : result;
      }
      return result;
    },

    getStrokeColor : function( shape ) {
      return shape.node.strokecolor.value;
    },

    setOpacity : function( shape, opacity ) {
      shape.opacity = opacity;
      this._renderFilter( shape );
      this._setAntiAlias( shape, opacity < 1 );
    },

    getOpacity : function( shape ) {
      var result = 1;
      if( typeof shape.opacity === "number" && shape.opacity < 1 ) {
        result = shape.opacity;
      }
      return result;
    },

    setBlur : function( shape, radius ) {
      // NOTE: IE shifts the shape to the bottom-right,
      // compensated ONLY in setRoundRectLayout
      shape.blurRadius = radius;
      this._renderFilter( shape );
    },

    getBlur : function( shape, radius ) {
      var result = 0;
      if( typeof shape.blurRadius === "number" && shape.blurRadius > 0 ) {
        result = shape.blurRadius;
      }
      return result;
    },

    _renderFilter : function( shape ) {
      var filterStr = [];
      var opacity = this.getOpacity( shape );
      var blurRadius = this.getBlur( shape );
      if( opacity < 1 ) {
        filterStr.push( "Alpha(opacity=" );
        filterStr.push( Math.round( opacity * 100 ) );
        filterStr.push( ")" );
      }
      if( blurRadius > 0 ) {
        filterStr.push( "progid:DXImageTransform.Microsoft.Blur(pixelradius=" );
        filterStr.push( this._getBlurOffsets( blurRadius )[ 0 ] );
        filterStr.push( ")" );
      }
      if( filterStr.length > 0 ) {
        shape.node.style.filter = filterStr.join( "" );
      } else {
        rwt.html.Style.removeCssFilter( shape.node );
      }
    },

    /////////
    // helper

    _VMLFACTOR : 10,
    _VMLDEGREE : -65535,
    _VMLRAD : -65535 * ( 180 / Math.PI ),

    _createNode : function( type ) {
      return document.createElement( "v:" + type );
    },

    _createRect : function() {
      var result = {};
      result.type = "vmlRect";
      var node = this._createNode( "rect" );
      node.style.position = "absolute";
      node.style.width = 0;
      node.style.height = 0;
      node.style.top = 0;
      node.style.left = 0;
      node.style.antialias = false;
      result.node = node;
      return result;
    },

    _createImage : function() {
      var result = {};
      result.type = "vmlImage";
      var node = this._createNode( "image" );
      node.style.position = "absolute";
      result.node = node;
      return result;
    },

    _createCustomShape : function() {
      var result = {};
      var node = this._createNode( "shape" );
      var coordsize = 100 * this._VMLFACTOR + ", " + 100 * this._VMLFACTOR;
      node.coordsize = coordsize;
      node.coordorigin = "0 0";
      node.style.position = "absolute";
      node.style.width = 100;
      node.style.height = 100;
      node.style.top = 0;
      node.style.left = 0;
      result.node = node;
      return result;
    },

    _setFillEnabled : function( shape, value ) {
      shape.fill.on = value;
      shape.restoreData.fill.on = value;
    },

    _ensureStrokeNode : function( shape ) {
      if( !shape.stroke ) {
        var stroke = this._createNode( "stroke" );
        shape.node.appendChild( stroke );
        shape.stroke = stroke;
      }
    },

    _setStrokeStyle : function( shape, joinStyle, miterLimit, endCap ) {
      this._ensureStrokeNode( shape );
      shape.stroke.joinstyle = joinStyle;
      shape.stroke.miterlimit = miterLimit;
      shape.stroke.endcap = endCap;
    },

    _transitionColors : function( color1, color2, start, stop, steps ) {
      var diff = stop-start;
      var stepwidth = diff / ( steps + 1 );
      var str =[];
      var color3 = [];
      var pos;
      for ( var i = 1; i <= steps; i++ ) {
        pos = i * ( 1 / ( steps + 1 ) );
        color3[ 0 ] = this._transitionColorPart( color1[ 0 ], color2[ 0 ], pos);
        color3[ 1 ] = this._transitionColorPart( color1[ 1 ], color2[ 1 ], pos);
        color3[ 2 ] = this._transitionColorPart( color1[ 2 ], color2[ 2 ], pos);
        str.push(   Math.round( ( ( start + ( i * stepwidth ) ) * 100 ) )
                  + "% RGB(" + color3.join()
                  + ")" );
      }
      return str.join(" ,");
    },

    _copyData : function( source, target ) {
      if( !source || !target ) {
        throw "VML._copyData: source or target missing.";
      }
      for( var key in source ) {
        var value = source[ key ];
        if( typeof value === "object" ) {
          try {
            this._copyData( value, target[ key ] );
          } catch( ex ) {
            throw new Error( "Could not copy " + key + ": " + ex );
          }
        } else {
          target[ key ] = value;
        }
      }
    },

    _handleAppearShape : function( shape ) {
      this._copyData( shape.restoreData, shape.node );
    },

    _transitionColorPart : function( color1, color2, pos ) {
      // TODO [tb] : color1 should always be a number, parseInt not needed?
      var part = parseInt( color1, 10 ) + ( ( color2 - color1 ) * pos );
      return Math.round( part );
    },

    _convertNumeric : function( value, fixOffset ) {
      var result;
      if( typeof value == "number" ) {
        result = ( fixOffset ? value - 0.5 : value ) * this._VMLFACTOR;
        result = Math.round( result );
      } else {
        result = value;
      }
      return  result;
    },

    _convertPath : function( path ) {
      var string = [];
      for( var i = 0; i < path.length; i++ ) {
        var item = path[ i ];
        switch( item.type ) {
          case "moveTo":
            string.push( "M" );
            string.push( this._convertNumeric( item.x, true ) );
            string.push( this._convertNumeric( item.y, true ) );
          break;
          case "lineTo":
            string.push( "L" );
            string.push( this._convertNumeric( item.x, true ) );
            string.push( this._convertNumeric( item.y, true ) );
          break;
          case "close":
            string.push( "X" );
            item = null;
          break;
          case "quadraticCurveTo":
            string.push( "QB" );
            string.push( this._convertNumeric( item.cp1x, true ) );
            string.push( this._convertNumeric( item.cp1y, true ) );
            string.push( "L" ); // a bug in VML requires this
            string.push( this._convertNumeric( item.x, true ) );
            string.push( this._convertNumeric( item.y, true ) );
          break;
          case "bezierCurveTo":
            string.push( "C" );
            string.push( this._convertNumeric( item.cp1x, true ) );
            string.push( this._convertNumeric( item.cp1y, true ) );
            string.push( this._convertNumeric( item.cp2x, true ) );
            string.push( this._convertNumeric( item.cp2y, true ) );
            string.push( this._convertNumeric( item.x, true ) );
            string.push( this._convertNumeric( item.y, true ) );
          break;
          case "arc":
            string.push( "AE" );
            var startAngle = Math.round( item.startAngle * this._VMLRAD );
            var endAngle = Math.round( item.endAngle * this._VMLRAD );
            string.push( this._convertNumeric( item.centerX, true ) );
            string.push( this._convertNumeric( item.centerY, true ) );
            string.push( this._convertNumeric( item.radiusX, false ) );
            string.push( this._convertNumeric( item.radiusY, false ) );
            string.push( startAngle );
            string.push( endAngle - startAngle );
          break;
        }
      }
      return string.join( " " );
    },

    _setAntiAlias : function( shape, value ) {
      shape.node.style.antialias = value;
    },

    _getBlurOffsets : function( blurradius ) {
      // returns [ blurradius, location-offset, dimension-offset ]
      var result;
      var offsets = this._BLUROFFSETS[ blurradius ];
      if( offsets !== undefined ) {
        result = offsets;
      } else {
        result = [ blurradius, blurradius, 1 ];
      }
      return result;
    },

    _BLUROFFSETS : [
      // NOTE: these values are chosen to resemble the blur-effect on css3-shadows
      // as closely as possible, but in doubt going for the stronger effect.
      [ 0, 0, 0 ],
      [ 2, 2, 1 ],
      [ 3, 3, 1 ],
      [ 4, 4, 1 ]
    ]
  }

} );




© 2015 - 2024 Weber Informatics LLC | Privacy Policy