Package com.sencha.gxt.chart.client.draw.engine

Source Code of com.sencha.gxt.chart.client.draw.engine.VML

/**
* Sencha GXT 3.0.0 - Sencha for GWT
* Copyright(c) 2007-2012, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
package com.sencha.gxt.chart.client.draw.engine;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.regexp.shared.SplitResult;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.sencha.gxt.chart.client.draw.Color;
import com.sencha.gxt.chart.client.draw.Gradient;
import com.sencha.gxt.chart.client.draw.Matrix;
import com.sencha.gxt.chart.client.draw.Rotation;
import com.sencha.gxt.chart.client.draw.Scaling;
import com.sencha.gxt.chart.client.draw.Stop;
import com.sencha.gxt.chart.client.draw.Translation;
import com.sencha.gxt.chart.client.draw.path.ClosePath;
import com.sencha.gxt.chart.client.draw.path.CurveTo;
import com.sencha.gxt.chart.client.draw.path.LineTo;
import com.sencha.gxt.chart.client.draw.path.MoveTo;
import com.sencha.gxt.chart.client.draw.path.PathCommand;
import com.sencha.gxt.chart.client.draw.path.PathSprite;
import com.sencha.gxt.chart.client.draw.sprite.CircleSprite;
import com.sencha.gxt.chart.client.draw.sprite.EllipseSprite;
import com.sencha.gxt.chart.client.draw.sprite.ImageSprite;
import com.sencha.gxt.chart.client.draw.sprite.RectangleSprite;
import com.sencha.gxt.chart.client.draw.sprite.Sprite;
import com.sencha.gxt.chart.client.draw.sprite.TextSprite;
import com.sencha.gxt.chart.client.draw.sprite.TextSprite.TextAnchor;
import com.sencha.gxt.chart.client.draw.sprite.TextSprite.TextBaseline;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.util.PrecisePoint;
import com.sencha.gxt.core.client.util.PreciseRectangle;
import com.sencha.gxt.core.client.util.Size;
import com.sencha.gxt.core.client.util.TextMetrics;

/**
* Provides specific methods to draw with VML.
*
*/
public class VML extends DomSurface {

  private final String baseVMLClass = "x-vml-base";
  private final String groupVMLClass = "x-vml-group";
  private final String hideClass = "x-hide-visibility";
  private final String spriteVMLClass = "x-vml-sprite";
  private final String measureSpanVMLClass = "x-vml-measure-span";
  private int zoom = 21600;
  private double coordsize = 1000;
  private Scaling viewBoxShift = null;
  private RegExp newLineRegExp = RegExp.compile("\n");
  private Map<Sprite, PreciseRectangle> textBBoxCache = new HashMap<Sprite, PreciseRectangle>();
  // store rendered text values to emulate SVG text bbox calls
  private Map<Sprite, PrecisePoint> textRenderedPoints = new HashMap<Sprite, PrecisePoint>();
  private Map<Sprite, TextBaseline> textRenderedBaseline = new HashMap<Sprite, TextBaseline>();
  protected boolean ignoreOptimizations = false;

  @Override
  public void addGradient(Gradient gradient) {
    this.gradients.add(gradient);
  }

  @Override
  public void deleteSprite(Sprite sprite) {
    if (sprite instanceof TextSprite) {
      textBBoxCache.remove(sprite);
      textRenderedPoints.remove(sprite);
      textRenderedBaseline.remove(sprite);
    }
    surfaceElement.removeChild(getElement(sprite));
  }

  @Override
  public void draw() {
    super.draw();
    if (surfaceElement == null) {
      surfaceElement = XElement.as(DOM.createDiv());
      surfaceElement.addClassName(baseVMLClass);
      surfaceElement.setSize(width, height);
      addNamespace("vml", "urn:schemas-microsoft-com:vml");
    }

    container.appendChild(surfaceElement);
    container.setSize(width, height);

    renderAll();
  }

  /**
   * Draws the surface ignoring whether or not sprites are dirty.
   */
  public void drawIgnoreOptimizations() {
    ignoreOptimizations = true;
    draw();
    ignoreOptimizations = false;
  }

  @Override
  public void renderSprite(Sprite sprite) {
    // Does the surface element exist?
    if (surfaceElement == null) {
      return;
    }

    if (!sprite.isDirty() && !ignoreOptimizations) {
      return;
    }
    applyAttributes(sprite);
    if (sprite.isTransformDirty() || ignoreOptimizations) {
      transform(sprite);
    }

    sprite.clearDirtyFlags();
  }

  @Override
  public void setCursor(Sprite sprite, String property) {
    XElement element = getElement(sprite);
    if (element != null) {
      element.getStyle().setProperty("cursor", property);
    }
  }

  @Override
  public void setViewBox(double x, double y, double width, double height) {
    // Handle viewbox sizing
    if (this.width > 0 && this.height > 0) {
      double relativeHeight = this.height / height;
      double relativeWidth = this.width / width;
      if (width * relativeHeight < this.width) {
        x -= (this.width - width * relativeHeight) / 2.0 / relativeHeight;
      }
      if (height * relativeWidth < this.height) {
        y -= (this.height - height * relativeWidth) / 2.0 / relativeWidth;
      }

      double size = 1.0 / Math.max(width / this.width, height / this.height);
      // Scale and translate group
      viewBoxShift = new Scaling(size, size, -x, -y);
      for (Sprite sprite : sprites) {
        transform(sprite);
      }
    }
  }

  @Override
  protected PreciseRectangle getBBoxText(TextSprite sprite) {
    XElement element = getElement(sprite);
    if (element == null) {
      return new PreciseRectangle();
    }
    PreciseRectangle bbox = textBBoxCache.get(sprite);
    if (bbox == null) {
      Element textPath = element.childElement("textPath").cast();
      if (textPath != null) {
        TextMetrics.get().bind(textPath);
      }

      SplitResult split = newLineRegExp.split(sprite.getText());
      bbox = new PreciseRectangle();

      for (int i = 0; i < split.length(); i++) {
        double width = TextMetrics.get().getWidth(split.get(i));
        bbox.setWidth(Math.max(bbox.getWidth(), width));
      }
      bbox.setHeight(sprite.getFontSize() * split.length());

      PrecisePoint point = textRenderedPoints.get(sprite);
      TextBaseline baseline = textRenderedBaseline.get(sprite);
      bbox.setX(point.getX());
      bbox.setY(point.getY());
      if (baseline == TextBaseline.MIDDLE) {
        bbox.setY(point.getY() - (bbox.getHeight() / 2.0));
      } else if (baseline == TextBaseline.BOTTOM) {
        bbox.setY(point.getY() - bbox.getHeight());
      }
      textBBoxCache.put(sprite, bbox);
    }
    return bbox;
  }

  /**
   * Adds the passed namespace and URN to the document.
   *
   * @param namespace the namespace to be added
   * @param urn the schema URN of the namespace
   */
  private native void addNamespace(String namespace, String urn) /*-{
                                                                 if (!$doc.namespaces[namespace]) {
                                                                 $doc.namespaces.add(namespace, urn);
                                                                 }
                                                                 }-*/;

  /**
   * Applies pending attributes to the DOM element of a {@link Sprite}.
   *
   * @param sprite the sprite in need of attributes to be set.
   */
  private void applyAttributes(Sprite sprite) {
    String vml = null;
    XElement element = getElement(sprite);

    // Create sprite element if necessary
    if (element == null) {
      element = createSpriteElement(sprite);
    }

    if (sprite instanceof EllipseSprite || sprite instanceof CircleSprite) {
      vml = ellipticalArc(sprite);
    } else if (sprite instanceof RectangleSprite) {
      RectangleSprite rect = (RectangleSprite) sprite;
      if (rect.isXDirty() || rect.isYDirty() || rect.isWidthDirty() || rect.isHeightDirty() || rect.isRadiusDirty()
          || ignoreOptimizations) {
        // faster conversion for rectangles without rounded corners
        if (Double.isNaN(rect.getRadius()) || rect.getRadius() == 0) {
          StringBuilder path = new StringBuilder();
          long x = Math.round(rect.getX() * zoom);
          long y = Math.round(rect.getY() * zoom);
          long width = Math.round((rect.getX() + rect.getWidth()) * zoom);
          long height = Math.round((rect.getY() + rect.getHeight()) * zoom);
          vml = path.append("m").append(x).append(",").append(y).append(" l").append(width).append(",").append(y).append(
              " l").append(width).append(",").append(height).append(" l").append(x).append(",").append(height).append(
              " x e").toString();
        } else {
          vml = path2vml(new PathSprite(rect));
        }
      }
    } else if (sprite instanceof PathSprite) {
      PathSprite pathSprite = (PathSprite) sprite;
      vml = path2vml(pathSprite);

    } else if (sprite instanceof TextSprite) {
      // Handle text (special handling required)
      setTextAttributes((TextSprite) sprite, element);
    } else if (sprite instanceof ImageSprite) {
      ImageSprite image = (ImageSprite) sprite;
      if (image.isXDirty() || ignoreOptimizations) {
        element.setLeft((int) Math.round(image.getX()));
      }
      if (image.isYDirty() || ignoreOptimizations) {
        element.setTop((int) Math.round(image.getY()));
      }
      if (image.isWidthDirty() || image.isHeightDirty() || ignoreOptimizations) {
        element.setSize(new Size((int) Math.round(image.getWidth()), (int) Math.round(image.getHeight())));
      }
      if (image.isResourceDirty() || ignoreOptimizations) {
        ImageResource resource = image.getResource();
        StringBuilder builder = new StringBuilder();
        builder.append("url(").append(image.getResource().getSafeUri().asString()).append(") ");
        builder.append(-1 * resource.getLeft()).append("px ");
        builder.append(-1 * resource.getTop()).append("px");
        element.getStyle().setProperty("background", builder.toString());
      }
    }

    if (vml != null) {
      element.setAttribute("path", vml);
    }

    if (sprite.isZIndexDirty() || ignoreOptimizations) {
      applyZIndex(sprite, element);
    }

    String id = spriteIds.get(sprite);
    if (id != null) {
      element.setId(id);
    }

    // Apply clip rectangle to the sprite
    if (sprite.getClipRectangle() != null) {
      applyClip(sprite);
    }

    // Handle fill and opacity
    if (sprite.isOpacityDirty() || sprite.isStrokeOpacityDirty() || sprite.isFillDirty() || ignoreOptimizations) {
      setFill(sprite, element);
    }

    // Handle stroke (all fills require a stroke element)
    if (sprite.isStrokeDirty() || sprite.isStrokeWidthDirty() || sprite.isStrokeOpacityDirty() || sprite.isFillDirty()
        || ignoreOptimizations) {
      setStroke(sprite, element);
    }

    // Hide or show the sprite
    if (sprite.isHiddenDirty() || ignoreOptimizations) {
      if (sprite.isHidden()) {
        element.addClassName(hideClass);
      } else {
        element.removeClassName(hideClass);
      }
    }
  }

  /**
   * Applies the clip rectangle of the given sprite to its DOM element.
   *
   * @param sprite the sprite to have its clip rectangle applied
   */
  private void applyClip(Sprite sprite) {
    XElement clipElement = getClipElement(sprite);
    if (clipElement == null) {
      clipElement = this.surfaceElement.insertFirst(Document.get().createDivElement()).cast();
      clipElement.addClassName(spriteVMLClass);
      setClipElement(sprite, clipElement);
    }
    PreciseRectangle rect = sprite.getClipRectangle();
    double width = rect.getX() + rect.getWidth();
    double height = rect.getY() + rect.getHeight();
    clipElement.getStyle().setProperty(
        "clip",
        new StringBuilder().append("rect(").append(rect.getY()).append("px ").append(width + "px ").append(height).append(
            "px ").append(rect.getX() + "px").toString());
  }

  /**
   * Applies the z-index of the given sprite to its DOM element.
   *
   * @param sprite the sprite to have its z-index applied
   */
  private void applyZIndex(Sprite sprite, XElement element) {
    if (element != null) {
      element.getStyle().setProperty("zIndex", String.valueOf(sprite.getZIndex()));
    }
  }

  /**
   * Determines whether a {@link PathSprite} contains a command not supported by
   * VML.
   *
   * @param sprite the sprite to be inspected
   * @return true if the sprite contains an unsupported command
   */
  private boolean containsNonVMLCommands(PathSprite sprite) {
    for (PathCommand command : sprite.getCommands()) {
      if (!(command instanceof MoveTo) && !(command instanceof CurveTo) && !(command instanceof LineTo)
          && !(command instanceof ClosePath)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Creates a VML DOM node.
   *
   * @param tagName the type of node to create.
   * @return the created VML DOM node.
   */
  private XElement createNode(String tagName) {
    Element node = Document.get().createElement("vml:" + tagName);
    node.setClassName("vml");
    return XElement.as(node);
  }

  /**
   * Creates the DOM element of the passed {@link Sprite}.
   *
   * @param sprite the sprite in need of element creation
   */
  private XElement createSpriteElement(Sprite sprite) {
    XElement element;

    if (sprite instanceof ImageSprite) {
      element = createNode("image");
    } else {
      element = createNode("shape");
      XElement skew = createNode("skew");
      skew.setPropertyBoolean("on", true);
      element.appendChild(skew);
      element.setPropertyJSO("skew", skew);
    }

    element.setPropertyString("coordsize", zoom + " " + zoom);
    element.setPropertyString("coordorigin", "0 0");
    element.addClassName(spriteVMLClass);

    if (sprite instanceof TextSprite) {
      XElement path = createNode("path");
      path.setPropertyBoolean("textpathok", true);
      XElement textPath = createNode("textpath");
      textPath.setPropertyBoolean("on", true);
      element.appendChild(textPath);
      element.appendChild(path);
      Style textStyle = textPath.getStyle();
      textStyle.setProperty("lineHeight", "normal");
      textStyle.setProperty("fontVariant", "normal");
      textRenderedPoints.put(sprite, new PrecisePoint());
    }

    this.surfaceElement.appendChild(element);
    setElement(sprite, element);
    return element;
  }

  /**
   * Generates a path sprite made up of an elliptical arc based on the given
   * {@link CircleSprite} or {@link EllipseSprite}. Returns null if not an
   * Circle
   *
   * @param sprite the circle or ellipse to be converted to an elliptical arc
   * @return the generated elliptical arc
   */
  private String ellipticalArc(Sprite sprite) {
    final double cx, cy, rx, ry;
    if (sprite instanceof EllipseSprite) {
      EllipseSprite ellipse = (EllipseSprite) sprite;
      if (!ellipse.isCenterXDirty() && !ellipse.isCenterYDirty() && !ellipse.isRadiusXDirty()
          && !ellipse.isRadiusYDirty() && !ignoreOptimizations) {
        return null;
      }
      cx = ellipse.getCenterX();
      cy = ellipse.getCenterY();
      rx = ellipse.getRadiusX();
      ry = ellipse.getRadiusY();
    } else if (sprite instanceof CircleSprite) {
      CircleSprite circle = (CircleSprite) sprite;
      if (!circle.isCenterXDirty() && !circle.isCenterYDirty() && !circle.isRadiusDirty() && !ignoreOptimizations) {
        return null;
      }
      cx = circle.getCenterX();
      cy = circle.getCenterY();
      rx = circle.getRadius();
      ry = circle.getRadius();
    } else {
      return null;
    }
    long centerX = Math.round(cx * zoom);
    long yShift = Math.round((cy - ry) * zoom);
    return new StringBuilder().append("ar").append(Math.round((cx - rx) * zoom)).append(",").append(yShift).append(",").append(
        Math.round((cx + rx) * zoom)).append(",").append(Math.round((cy + ry) * zoom)).append(",").append(centerX).append(
        ",").append(yShift).append(",").append(centerX).append(",").append(yShift).toString();
  }

  /**
   * Returns a string representing a VML path using the the passed
   * {@link PathSprite}.
   *
   * @param sprite the sprite to be converted
   * @return the converted path
   */
  private String path2vml(PathSprite sprite) {
    StringBuilder path = new StringBuilder();

    if (containsNonVMLCommands(sprite)) {
      sprite = sprite.copy().toCurve();
    } else {
      sprite = sprite.copy().toAbsolute();

    }

    for (PathCommand command : sprite.getCommands()) {
      if (command instanceof CurveTo) {
        CurveTo curveto = (CurveTo) command;
        path.append("c").append(Math.round(curveto.getX1() * zoom)).append(",").append(
            Math.round(curveto.getY1() * zoom)).append(",").append(Math.round(curveto.getX2() * zoom)).append(",").append(
            Math.round(curveto.getY2() * zoom)).append(",").append(Math.round(curveto.getX() * zoom)).append(",").append(
            Math.round(curveto.getY() * zoom)).append(" ");
      } else if (command instanceof MoveTo) {
        MoveTo moveto = (MoveTo) command;
        path.append("m").append(Math.round(moveto.getX() * zoom)).append(",").append(Math.round(moveto.getY() * zoom)).append(
            " ");
      } else if (command instanceof LineTo) {
        LineTo lineto = (LineTo) command;
        path.append("l").append(Math.round(lineto.getX() * zoom)).append(",").append(Math.round(lineto.getY() * zoom)).append(
            " ");
      } else if (command instanceof ClosePath) {
        path.append("x ");
      }
    }
    path.append("e");
    return path.toString();
  }

  /**
   * Applies the fill attribute of a sprite to its VML DOM element.
   *
   * @param sprite the sprite to have its fill set
   */
  private void setFill(Sprite sprite, Element element) {
    Element fill = element.getPropertyJSO("fill").cast();

    if (fill == null) {
      fill = createNode("fill");
      element.setPropertyJSO("fill", fill);
      element.appendChild(fill);
    }

    if (sprite.getFill() == null || sprite.getFill() == Color.NONE) {
      fill.setPropertyBoolean("on", false);
    } else {
      if (sprite.isFillDirty() || ignoreOptimizations) {
        fill.setPropertyBoolean("on", true);
        if (sprite.getFill() instanceof Gradient) {
          Gradient gradient = (Gradient) sprite.getFill();
          // VML angle is offset and inverted from standard, and must be
          // adjusted
          // to match rotation transform
          final double degrees;
          if (sprite.getRotation() != null) {
            degrees = sprite.getRotation().getDegrees();
          } else {
            degrees = 0;
          }

          double angle;
          angle = -(gradient.getAngle() + 270 + degrees) % 360.0;
          // IE will flip the angle at 0 degrees...
          if (angle == 0) {
            angle = 180;
          }
          fill.setPropertyDouble("angle", angle);
          fill.setPropertyString("type", "gradient");
          fill.setPropertyString("method", "sigma");
          StringBuilder stops = new StringBuilder();
          for (Stop stop : gradient.getStops()) {
            if (stops.length() > 0) {
              stops.append(", ");
            }
            stops.append(stop.getOffset()).append("% ").append(stop.getColor());
          }
          Element colors = fill.getPropertyJSO("colors").cast();
          colors.setPropertyString("value", stops.toString());
        } else {
          fill.setPropertyString("color", sprite.getFill().getColor());
          fill.setPropertyString("src", "");
          fill.setPropertyString("type", "solid");
        }
      }

      if (!Double.isNaN(sprite.getOpacity()) && (sprite.isOpacityDirty() || ignoreOptimizations)) {
        fill.setPropertyString("opacity", String.valueOf(sprite.getOpacity()));
      }
      if (!Double.isNaN(sprite.getFillOpacity()) && (sprite.isFillOpacityDirty() || ignoreOptimizations)) {
        fill.setPropertyString("opacity", String.valueOf(sprite.getFillOpacity()));
      }
    }
  }

  /**
   * Applies the stroke attribute of a sprite to its VML dom element.
   *
   * @param sprite the sprite to have its stroke set
   */
  private void setStroke(Sprite sprite, XElement element) {
    Element stroke = element.getPropertyJSO("stroke").cast();

    if (stroke == null) {
      stroke = createNode("stroke");
      element.setPropertyJSO("stroke", stroke);
      element.appendChild(stroke);
    }

    Color strokeColor = sprite.getStroke();
    if (strokeColor == null || strokeColor == Color.NONE || sprite.getStrokeWidth() == 0.0) {
      stroke.setPropertyBoolean("on", false);
    } else {
      stroke.setPropertyBoolean("on", true);

      if (strokeColor != null && !(strokeColor instanceof Gradient)) {
        // VML does NOT support a gradient stroke :(
        stroke.setPropertyString("color", strokeColor.getColor());
      }

      if (sprite instanceof PathSprite) {
        PathSprite path = (PathSprite) sprite;
        if (path.getStrokeLineCap() != null) {
          stroke.setPropertyString("endcap", path.getStrokeLineCap().getValue());
        }
        if (path.getStrokeLineJoin() != null) {
          stroke.setPropertyString("joinstyle", path.getStrokeLineJoin().getValue());
        }
        if (!Double.isNaN(path.getMiterLimit())) {
          stroke.setPropertyDouble("miterlimit", path.getMiterLimit());
        }
      }

      double width = sprite.getStrokeWidth();
      double opacity = sprite.getStrokeOpacity();
      if (Double.isNaN(width)) {
        width = 0.75;
      } else {
        width *= 0.75;
      }
      if (Double.isNaN(opacity)) {
        opacity = 1;
      }

      // VML Does not support stroke widths under 1, so we're going to fiddle
      // with stroke-opacity instead.
      if (width < 1) {
        opacity *= width;
        width = 1;
      }

      stroke.setPropertyDouble("weight", width);
      stroke.setPropertyDouble("opacity", opacity);
    }
  }

  /**
   * Sets the text alignment on the given {@link Style}.
   *
   * @param style the style
   * @param align the text alignment
   */
  private native void setTextAlign(Style style, String align) /*-{
                                                              style["v-text-align"] = align;
                                                              }-*/;

  /**
   * Applies the attributes of the passed {@link TextSprite} to its VML element.
   *
   * @param sprite the sprite whose attributes to use
   */
  private void setTextAttributes(TextSprite sprite, XElement element) {
    Element textPath = element.childElement("textPath").cast();
    Style textStyle = textPath.getStyle();
    textBBoxCache.remove(sprite);

    if (sprite.isFontSizeDirty() || ignoreOptimizations) {
      if (sprite.getFontSize() > 0) {
        textStyle.setFontSize(sprite.getFontSize(), Unit.PX);
      } else {
        textStyle.clearFontSize();
      }
    }
    if (sprite.isFontStyleDirty() || ignoreOptimizations) {
      if (sprite.getFontStyle() != null) {
        textStyle.setFontStyle(sprite.getFontStyle());
      } else {
        textStyle.clearFontStyle();
      }
    }
    if (sprite.isFontWeightDirty() || ignoreOptimizations) {
      if (sprite.getFontWeight() != null) {
        textStyle.setFontWeight(sprite.getFontWeight());
      } else {
        textStyle.clearFontWeight();
      }
    }
    if (sprite.isFontDirty() || ignoreOptimizations) {
      if (sprite.getFont() != null) {
        textStyle.setProperty("fontFamily", sprite.getFont());
      } else {
        textStyle.clearProperty("fontFamily");
      }
    }

    // text-anchor emulation
    if (sprite.isTextAnchorDirty() || ignoreOptimizations) {
      if (sprite.getTextAnchor() == TextAnchor.MIDDLE) {
        setTextAlign(textStyle, "center");
      } else if (sprite.getTextAnchor() == TextAnchor.END) {
        setTextAlign(textStyle, "right");
      } else {
        setTextAlign(textStyle, "left");
      }
    }

    if (sprite.isTextDirty() || ignoreOptimizations) {
      textPath.setPropertyString("string", sprite.getText());
    }

    if (sprite.isTextBaselineDirty() || sprite.isXDirty() || sprite.isYDirty() || ignoreOptimizations) {
      double height = sprite.getFontSize();
      if (sprite.getTextBaseline() == TextBaseline.MIDDLE) {
        height = 0;
      } else if (sprite.getTextBaseline() == TextBaseline.BOTTOM) {
        height *= -1;
      }
      Element path = element.childElement("path").cast();
      path.setPropertyString(
          "v",
          new StringBuilder().append("m").append(Math.round(sprite.getX() * zoom)).append(",").append(
              Math.round((sprite.getY() + (height / 2.0)) * zoom)).append(" l").append(
              Math.round(sprite.getX() * zoom) + 1).append(",").append(
              Math.round((sprite.getY() + (height / 2.0)) * zoom)).toString());
      textRenderedPoints.put(sprite, new PrecisePoint(sprite.getX(), sprite.getY()));
      textRenderedBaseline.put(sprite, sprite.getTextBaseline());
    }
  }

  /**
   * Returns the given number rounded to the given precision.
   *
   * @param number the number to be rounded
   * @param place the decimal place
   * @return the rounded number
   */
  private double toFixed(double number, int place) {
    double pow = Math.pow(10, place);
    return Math.round(number * pow) / pow;
  }

  /**
   * Applies transformation to passed sprite
   *
   * @param sprite the sprite to be transformed
   */
  private void transform(Sprite sprite) {
    double deltaDegrees = 0;
    double deltaScaleX = 1;
    double deltaScaleY = 1;
    Matrix matrix = new Matrix();
    Rotation rotation = sprite.getRotation();
    Scaling scaling = sprite.getScaling();
    Translation translation = sprite.getTranslation();
    XElement element = getElement(sprite);
    Style style = element.getStyle();
    Element skew = element.getPropertyJSO("skew").cast();

    if (rotation != null) {
      matrix.rotate(rotation.getDegrees(), rotation.getX(), rotation.getY());
      deltaDegrees += rotation.getDegrees();
    }
    if (scaling != null) {
      matrix.scale(scaling.getX(), scaling.getY(), scaling.getCenterX(), scaling.getCenterY());
      deltaScaleX *= scaling.getX();
      deltaScaleY *= scaling.getY();
    }
    if (translation != null) {
      matrix.translate(translation.getX(), translation.getY());
    }
    if (viewBoxShift != null) {
      matrix.scale(viewBoxShift.getX(), viewBoxShift.getY(), -1, -1);
      matrix.add(1, 0, 0, 1, viewBoxShift.getCenterX(), viewBoxShift.getCenterY());
    }

    if (!(sprite instanceof ImageSprite) && skew != null) {
      // matrix transform via VML skew
      skew.setPropertyString(
          "matrix",
          new StringBuilder().append(toFixed(matrix.get(0, 0), 4)).append(", ").append(toFixed(matrix.get(0, 1), 4)).append(
              ", ").append(toFixed(matrix.get(1, 0), 4)).append(", ").append(toFixed(matrix.get(1, 1), 4)).append(
              ", 0, 0").toString());
      skew.setPropertyString(
          "offset",
          new StringBuilder().append(toFixed(matrix.get(0, 2), 4)).append(", ").append(toFixed(matrix.get(1, 2), 4)).toString());
    } else {
      double deltaX = matrix.get(0, 2);
      double deltaY = matrix.get(1, 2);
      // Scale via coordsize property
      double zoomScaleX = zoom / deltaScaleX;
      double zoomScaleY = zoom / deltaScaleY;

      element.setPropertyString("coordsize", Math.abs(zoomScaleX) + " " + Math.abs(zoomScaleY));

      // Rotate via rotation property
      double newAngle = deltaDegrees * (deltaScaleX * ((deltaScaleY < 0) ? -1 : 1));
      if ((style.getProperty("rotation") == null && newAngle != 0)) {
        style.setProperty("rotation", String.valueOf(newAngle));
      } else if (style.getProperty("rotation") != null && newAngle != Double.valueOf(style.getProperty("rotation"))) {
        style.setProperty("rotation", String.valueOf(newAngle));
      }
      if (deltaDegrees != 0) {
        // Compensate x/y position due to rotation
        Matrix compMatrix = new Matrix();
        compMatrix.rotate(-deltaDegrees, deltaX, deltaY);
        deltaX = deltaX * compMatrix.get(0, 0) + deltaY * compMatrix.get(0, 1) + compMatrix.get(0, 2);
        deltaY = deltaX * compMatrix.get(1, 0) + deltaY * compMatrix.get(1, 1) + compMatrix.get(1, 2);
      }

      String flip = "";
      // Handle negative scaling via flipping
      if (deltaScaleX < 0) {
        flip += "x";
      }
      if (deltaScaleY < 0) {
        flip += " y";
      }
      if (flip != "") {
        style.setProperty("flip", flip);
      }

      // Translate via coordorigin property
      element.setPropertyString("coordorigin", (-zoomScaleX * (deltaX / ((ImageSprite) sprite).getWidth())) + " "
          + (-zoomScaleY * (deltaY / ((ImageSprite) sprite).getHeight())));
    }
  }

}
TOP

Related Classes of com.sencha.gxt.chart.client.draw.engine.VML

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.