Package com.mxgraph.canvas

Source Code of com.mxgraph.canvas.mxGraphicsCanvas2D$CanvasState

package com.mxgraph.canvas;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.AttributedString;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack;

import javax.swing.CellRendererPane;
import javax.swing.JLabel;

import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxLightweightLabel;
import com.mxgraph.util.mxUtils;

/**
* Used for exporting images. To render to an image from a given XML string,
* graph size and background color, the following code is used:
*
* <code>
* BufferedImage image = mxUtils.createBufferedImage(width, height, background);
* Graphics2D g2 = image.createGraphics();
* mxUtils.setAntiAlias(g2, true, true);
* XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
* reader.setContentHandler(new mxSaxOutputHandler(new mxGraphicsCanvas2D(g2)));
* reader.parse(new InputSource(new StringReader(xml)));
* </code>
*
* Text rendering is available for plain text and HTML markup, the latter with optional
* word wrapping. CSS support is limited to the following:
* http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
*/
public class mxGraphicsCanvas2D implements mxICanvas2D
{

  /**
   * Specifies the image scaling quality. Default is Image.SCALE_SMOOTH.
   * See {@link #scaleImage(Image, int, int)}
   */
  public static int IMAGE_SCALING = Image.SCALE_SMOOTH;

  /**
   * Specifies the additional pixels when computing the text width for HTML labels.
   * Default is 2.
   */
  public static int JAVA_TEXT_WIDTH_DELTA = 2;

  /**
   * Specifies the size of the cache used to store parsed colors
   */
  public static int COLOR_CACHE_SIZE = 100;

  /**
   * Reference to the graphics instance for painting.
   */
  protected Graphics2D graphics;

  /**
   * Specifies if text output should be rendered. Default is true.
   */
  protected boolean textEnabled = true;

  /**
   * Represents the current state of the canvas.
   */
  protected transient CanvasState state = new CanvasState();

  /**
   * Stack of states for save/restore.
   */
  protected transient Stack<CanvasState> stack = new Stack<CanvasState>();

  /**
   * Holds the current path.
   */
  protected transient GeneralPath currentPath;

  /**
   * Optional renderer pane to be used for HTML label rendering.
   */
  protected CellRendererPane rendererPane;

  /**
   * Font caching.
   */
  protected transient Font lastFont = null;

  /**
   * Font caching.
   */
  protected transient int lastFontStyle = 0;

  /**
   * Font caching.
   */
  protected transient int lastFontSize = 0;

  /**
   * Font caching.
   */
  protected transient String lastFontFamily = "";

  /**
   * Stroke caching.
   */
  protected transient Stroke lastStroke = null;

  /**
   * Stroke caching.
   */
  protected transient float lastStrokeWidth = 0;

  /**
   * Stroke caching.
   */
  protected transient int lastCap = 0;

  /**
   * Stroke caching.
   */
  protected transient int lastJoin = 0;

  /**
   * Stroke caching.
   */
  protected transient float lastMiterLimit = 0;

  /**
   * Stroke caching.
   */
  protected transient boolean lastDashed = false;

  /**
   * Stroke caching.
   */
  protected transient Object lastDashPattern = "";

  /**
   * Caches parsed colors.
   */
  @SuppressWarnings("serial")
  protected transient LinkedHashMap<String, Color> colorCache = new LinkedHashMap<String, Color>()
  {
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, Color> eldest)
    {
      return size() > COLOR_CACHE_SIZE;
    }
  };

  /**
   * Constructs a new graphics export canvas.
   */
  public mxGraphicsCanvas2D(Graphics2D g)
  {
    setGraphics(g);
    state.g = g;

    // Initializes the cell renderer pane for drawing HTML markup
    try
    {
      rendererPane = new CellRendererPane();
    }
    catch (Exception e)
    {
      // ignore
    }
  }

  /**
   * Sets the graphics instance.
   */
  public void setGraphics(Graphics2D value)
  {
    graphics = value;
  }

  /**
   * Returns the graphics instance.
   */
  public Graphics2D getGraphics()
  {
    return graphics;
  }

  /**
   * Returns true if text should be rendered.
   */
  public boolean isTextEnabled()
  {
    return textEnabled;
  }

  /**
   * Disables or enables text rendering.
   */
  public void setTextEnabled(boolean value)
  {
    textEnabled = value;
  }

  /**
   * Saves the current canvas state.
   */
  public void save()
  {
    stack.push(state);
    state = cloneState(state);
    state.g = (Graphics2D) state.g.create();
  }

  /**
   * Restores the last canvas state.
   */
  public void restore()
  {
    state = stack.pop();
  }

  /**
   * Returns a clone of thec given state.
   */
  protected CanvasState cloneState(CanvasState state)
  {
    try
    {
      return (CanvasState) state.clone();
    }
    catch (CloneNotSupportedException e)
    {
      e.printStackTrace();
    }

    return null;
  }

  /**
   *
   */
  public void scale(double value)
  {
    // This implementation uses custom scale/translate and built-in rotation
    state.scale = state.scale * value;
  }

  /**
   *
   */
  public void translate(double dx, double dy)
  {
    // This implementation uses custom scale/translate and built-in rotation
    state.dx += dx;
    state.dy += dy;
  }

  /**
   *
   */
  public void rotate(double theta, boolean flipH, boolean flipV, double cx, double cy)
  {
    cx += state.dx;
    cy += state.dy;
    cx *= state.scale;
    cy *= state.scale;
    state.g.rotate(Math.toRadians(theta), cx, cy);

    // This implementation uses custom scale/translate and built-in rotation
    // Rotation state is part of the AffineTransform in state.transform
    if (flipH && flipV)
    {
      theta += 180;
    }
    else if (flipH ^ flipV)
    {
      double tx = (flipH) ? cx : 0;
      int sx = (flipH) ? -1 : 1;

      double ty = (flipV) ? cy : 0;
      int sy = (flipV) ? -1 : 1;

      state.g.translate(tx, ty);
      state.g.scale(sx, sy);
      state.g.translate(-tx, -ty);
    }

    state.theta = theta;
    state.rotationCx = cx;
    state.rotationCy = cy;
    state.flipH = flipH;
    state.flipV = flipV;
  }

  /**
   *
   */
  public void setStrokeWidth(double value)
  {
    // Lazy and cached instantiation strategy for all stroke properties
    if (value != state.strokeWidth)
    {
      state.strokeWidth = value;
    }
  }

  /**
   * Caches color conversion as it is expensive.
   */
  public void setStrokeColor(String value)
  {
    // Lazy and cached instantiation strategy for all stroke properties
    if (state.strokeColorValue == null || !state.strokeColorValue.equals(value))
    {
      state.strokeColorValue = value;
      state.strokeColor = null;
    }
  }

  /**
   *
   */
  public void setDashed(boolean value)
  {
    // Lazy and cached instantiation strategy for all stroke properties
    if (value != state.dashed)
    {
      state.dashed = value;
    }
  }

  /**
   *
   */
  public void setDashPattern(String value)
  {
    if (value != null && value.length() > 0)
    {
      String[] tokens = value.split(" ");
      float[] dashpattern = new float[tokens.length];

      for (int i = 0; i < tokens.length; i++)
      {
        dashpattern[i] = (float) (Float.parseFloat(tokens[i]));
      }

      state.dashPattern = dashpattern;
    }
  }

  /**
   *
   */
  public void setLineCap(String value)
  {
    if (!state.lineCap.equals(value))
    {
      state.lineCap = value;
    }
  }

  /**
   *
   */
  public void setLineJoin(String value)
  {
    if (!state.lineJoin.equals(value))
    {
      state.lineJoin = value;
    }
  }

  /**
   *
   */
  public void setMiterLimit(double value)
  {
    if (value != state.miterLimit)
    {
      state.miterLimit = value;
    }
  }

  /**
   *
   */
  public void setFontSize(double value)
  {
    if (value != state.fontSize)
    {
      state.fontSize = value;
    }
  }

  /**
   *
   */
  public void setFontColor(String value)
  {
    if (state.fontColorValue == null || !state.fontColorValue.equals(value))
    {
      state.fontColorValue = value;
      state.fontColor = null;
    }
  }

  /**
   *
   */
  public void setFontBackgroundColor(String value)
  {
    if (state.fontBackgroundColorValue == null || !state.fontBackgroundColorValue.equals(value))
    {
      state.fontBackgroundColorValue = value;
      state.fontBackgroundColor = null;
    }
  }

  /**
   *
   */
  public void setFontBorderColor(String value)
  {
    if (state.fontBorderColorValue == null || !state.fontBorderColorValue.equals(value))
    {
      state.fontBorderColorValue = value;
      state.fontBorderColor = null;
    }
  }

  /**
   *
   */
  public void setFontFamily(String value)
  {
    if (!state.fontFamily.equals(value))
    {
      state.fontFamily = value;
    }
  }

  /**
   *
   */
  public void setFontStyle(int value)
  {
    if (value != state.fontStyle)
    {
      state.fontStyle = value;
    }
  }

  /**
   *
   */
  public void setAlpha(double value)
  {
    if (state.alpha != value)
    {
      state.g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) (value)));
      state.alpha = value;
    }
  }

  /**
   *
   */
  public void setFillColor(String value)
  {
    if (state.fillColorValue == null || !state.fillColorValue.equals(value))
    {
      state.fillColorValue = value;
      state.fillColor = null;

      // Setting fill color resets gradient paint
      state.gradientPaint = null;
    }
  }

  /**
   *
   */
  public void setGradient(String color1, String color2, double x, double y, double w, double h, String direction, double alpha1,
      double alpha2)
  {
    // LATER: Add lazy instantiation and check if paint already created
    float x1 = (float) ((state.dx + x) * state.scale);
    float y1 = (float) ((state.dy + y) * state.scale);
    float x2 = (float) x1;
    float y2 = (float) y1;
    h *= state.scale;
    w *= state.scale;

    if (direction == null || direction.length() == 0 || direction.equals(mxConstants.DIRECTION_SOUTH))
    {
      y2 = (float) (y1 + h);
    }
    else if (direction.equals(mxConstants.DIRECTION_EAST))
    {
      x2 = (float) (x1 + w);
    }
    else if (direction.equals(mxConstants.DIRECTION_NORTH))
    {
      y1 = (float) (y1 + h);
    }
    else if (direction.equals(mxConstants.DIRECTION_WEST))
    {
      x1 = (float) (x1 + w);
    }

    Color c1 = parseColor(color1);

    if (alpha1 != 1)
    {
      c1 = new Color(c1.getRed(), c1.getGreen(), c1.getBlue(), (int) (alpha1 * 255));
    }

    Color c2 = parseColor(color2);

    if (alpha2 != 1)
    {
      c2 = new Color(c2.getRed(), c2.getGreen(), c2.getBlue(), (int) (alpha2 * 255));
    }

    state.gradientPaint = new GradientPaint(x1, y1, c1, x2, y2, c2, true);
   
    // Resets fill color
    state.fillColorValue = null;
  }

  /**
   * Helper method that uses {@link mxUtils#parseColor(String)}.
   */
  protected Color parseColor(String hex)
  {
    Color result = colorCache.get(hex);

    if (result == null)
    {
      result = mxUtils.parseColor(hex);
      colorCache.put(hex, result);
    }

    return result;
  }

  /**
   *
   */
  public void rect(double x, double y, double w, double h)
  {
    currentPath = new GeneralPath();
    currentPath.append(new Rectangle2D.Double((state.dx + x) * state.scale, (state.dy + y) * state.scale, w * state.scale, h
        * state.scale), false);
  }

  /**
   * Implements a rounded rectangle using a path.
   */
  public void roundrect(double x, double y, double w, double h, double dx, double dy)
  {
    // LATER: Use arc here or quad in VML/SVG for exact match
    begin();
    moveTo(x + dx, y);
    lineTo(x + w - dx, y);
    quadTo(x + w, y, x + w, y + dy);
    lineTo(x + w, y + h - dy);
    quadTo(x + w, y + h, x + w - dx, y + h);
    lineTo(x + dx, y + h);
    quadTo(x, y + h, x, y + h - dy);
    lineTo(x, y + dy);
    quadTo(x, y, x + dx, y);
  }

  /**
   *
   */
  public void ellipse(double x, double y, double w, double h)
  {
    currentPath = new GeneralPath();
    currentPath.append(new Ellipse2D.Double((state.dx + x) * state.scale, (state.dy + y) * state.scale, w * state.scale, h
        * state.scale), false);
  }

  /**
   *
   */
  public void image(double x, double y, double w, double h, String src, boolean aspect, boolean flipH, boolean flipV)
  {
    if (src != null && w > 0 && h > 0)
    {
      Image img = loadImage(src);

      if (img != null)
      {
        Rectangle bounds = getImageBounds(img, x, y, w, h, aspect);
        img = scaleImage(img, bounds.width, bounds.height);

        if (img != null)
        {
          drawImage(createImageGraphics(bounds.x, bounds.y, bounds.width, bounds.height, flipH, flipV), img, bounds.x, bounds.y);
        }
      }
    }
  }

  /**
   *
   */
  protected void drawImage(Graphics2D graphics, Image image, int x, int y)
  {
    graphics.drawImage(image, x, y, null);
  }

  /**
   * Hook for image caching.
   */
  protected Image loadImage(String src)
  {
    return mxUtils.loadImage(src);
  }

  /**
   *
   */
  protected final Rectangle getImageBounds(Image img, double x, double y, double w, double h, boolean aspect)
  {
    x = (state.dx + x) * state.scale;
    y = (state.dy + y) * state.scale;
    w *= state.scale;
    h *= state.scale;

    if (aspect)
    {
      Dimension size = getImageSize(img);
      double s = Math.min(w / size.width, h / size.height);
      int sw = (int) Math.round(size.width * s);
      int sh = (int) Math.round(size.height * s);
      x += (w - sw) / 2;
      y += (h - sh) / 2;
      w = sw;
      h = sh;
    }
    else
    {
      w = Math.round(w);
      h = Math.round(h);
    }

    return new Rectangle((int) x, (int) y, (int) w, (int) h);
  }

  /**
   * Returns the size for the given image.
   */
  protected Dimension getImageSize(Image image)
  {
    return new Dimension(image.getWidth(null), image.getHeight(null));
  }

  /**
   * Uses {@link #IMAGE_SCALING} to scale the given image.
   */
  protected Image scaleImage(Image img, int w, int h)
  {
    Dimension size = getImageSize(img);

    if (w == size.width && h == size.height)
    {
      return img;
    }
    else
    {
      return img.getScaledInstance(w, h, IMAGE_SCALING);
    }
  }

  /**
   * Creates a graphic instance for rendering an image.
   */
  protected final Graphics2D createImageGraphics(double x, double y, double w, double h, boolean flipH, boolean flipV)
  {
    Graphics2D g2 = state.g;

    if (flipH || flipV)
    {
      g2 = (Graphics2D) g2.create();
     
      if (flipV && flipH)
      {
        g2.rotate(Math.toRadians(180), x + w / 2, y + h / 2);
      }
      else
      {
        int sx = 1;
        int sy = 1;
        int dx = 0;
        int dy = 0;

        if (flipH)
        {
          sx = -1;
          dx = (int) (-w - 2 * x);
        }
 
        if (flipV)
        {
          sy = -1;
          dy = (int) (-h - 2 * y);
        }
 
        g2.scale(sx, sy);
        g2.translate(dx, dy);
      }
    }

    return g2;
  }

  /**
   * Creates a HTML document around the given markup.
   */
  protected String createHtmlDocument(String text, String align, String valign, int w, int h, boolean wrap, String overflow, boolean clip)
  {
    StringBuffer css = new StringBuffer();
    css.append("display:inline;");
    css.append("font-family:" + state.fontFamily + ";");
    css.append("font-size:" + Math.round(state.fontSize) + " pt;");
    css.append("color:" + state.fontColorValue + ";");
    // KNOWN: Line-height ignored in JLabel
    css.append("line-height:" + ((mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(state.fontSize * mxConstants.LINE_HEIGHT) + " pt" : mxConstants.LINE_HEIGHT) + ";");
   
    boolean setWidth = false;
   
    if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
    {
      css.append("font-weight:bold;");
    }

    if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
    {
      css.append("font-style:italic;");
    }

    if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
    {
      css.append("text-decoration:underline;");
    }

    if (align != null)
    {
      if (align.equals(mxConstants.ALIGN_CENTER))
      {
        css.append("text-align:center;");
      }
      else if (align.equals(mxConstants.ALIGN_RIGHT))
      {
        css.append("text-align:right;");
      }
    }

    if (state.fontBackgroundColorValue != null)
    {
      css.append("background-color:" + state.fontBackgroundColorValue + ";");
    }

    // KNOWN: Border ignored in JLabel
    if (state.fontBorderColorValue != null)
    {
      css.append("border:1pt solid " + state.fontBorderColorValue + ";");
    }

    // KNOWN: max-width/-height ignored in JLabel
    if (clip)
    {
      css.append("overflow:hidden;");
      setWidth = true;
    }
    else if (overflow != null)
    {
      if (overflow.equals("fill"))
      {
        css.append("height:" + Math.round(h) + "pt;");
        setWidth = true;
      }
      else if (overflow.equals("width"))
      {
        setWidth = true;
 
        if (h > 0)
        {
          css.append("height:" + Math.round(h) + "pt;");
        }
      }
    }

    if (wrap)
    {
      if (!clip)
      {
        // NOTE: Max-width not available in Java
        setWidth = true;
      }

      css.append("white-space:normal;");
    }
    else
    {
      css.append("white-space:nowrap;");
    }

    if (setWidth && w > 0)
    {
      css.append("width:" + Math.round(w) + "pt;");
    }
   
    return "<html><div style=\"" + css.toString() + "\">" + text + "</div></html>";
  }

  /**
   * Hook to return the renderer for HTML formatted text. This implementation returns
   * the shared instance of mxLighweightLabel.
   */
  protected JLabel getTextRenderer()
  {
    return mxLightweightLabel.getSharedInstance();
  }

  /**
   *
   */
  protected Point2D getMargin(String align, String valign)
  {
    double dx = 0;
    double dy = 0;

    if (align != null)
    {
      if (align.equals(mxConstants.ALIGN_CENTER))
      {
        dx = -0.5;
      }
      else if (align.equals(mxConstants.ALIGN_RIGHT))
      {
        dx = -1;
      }
    }

    if (valign != null)
    {
      if (valign.equals(mxConstants.ALIGN_MIDDLE))
      {
        dy = -0.5;
      }
      else if (valign.equals(mxConstants.ALIGN_BOTTOM))
      {
        dy = -1;
      }
    }

    return new Point2D.Double(dx, dy);
  }

  /**
   * Draws the given HTML text.
   */
  protected void htmlText(double x, double y, double w, double h, String str, String align, String valign, boolean wrap, String format,
      String overflow, boolean clip, double rotation)
  {
    x += state.dx;
    y += state.dy;

    JLabel textRenderer = getTextRenderer();

    if (textRenderer != null && rendererPane != null)
    {
      // Use native scaling for HTML
      AffineTransform previous = state.g.getTransform();
      state.g.scale(state.scale, state.scale);
      double rad = rotation * (Math.PI / 180);
      state.g.rotate(rad, x, y);

      // Renders the scaled text with a correction factor of
      // PX_PER_PIXEL for px in HTML vs pixels in the bitmap
      boolean widthFill = false;
      boolean fill = false;
     
      String original = str;
     
      if (overflow != null)
      {
        widthFill = overflow.equals("width");
        fill = overflow.equals("fill");
      }
     
      str = createHtmlDocument(str, align, valign, (widthFill || fill) ?
          (int) Math.round(w) : 0, (fill) ?
          (int) Math.round(h) : 0, wrap, overflow, clip);
      textRenderer.setText(str);
      Dimension pref = textRenderer.getPreferredSize();
      int prefWidth = pref.width;
      int prefHeight = pref.height;
      
      // Poor man's max-width
      // TODO: Is this still needed?
      if (((clip || wrap) && prefWidth > w && w > 0) || (clip && prefHeight > h && h > 0))
      {
        // TextWidthDelta is workaround for inconsistent word wrapping in Java
        int cw = (int) Math.round(w + ((wrap) ? JAVA_TEXT_WIDTH_DELTA : 0));
        int ch = (int) Math.round(h);
        str = createHtmlDocument(original, align, valign, cw, ch, wrap, overflow, clip);
        textRenderer.setText(str);

        pref = textRenderer.getPreferredSize();
        prefWidth = pref.width;
        prefHeight = pref.height + 2;
      }

      // Matches HTML output
      if (clip && w > 0 && h > 0)
      {
        prefWidth = Math.min(pref.width, (int) w);
        prefHeight = Math.min(prefHeight, (int) h);
        h = prefHeight;
      }
      else if (!clip && wrap && w > 0 && h > 0)
      {
        prefWidth = pref.width;
        w = Math.max(pref.width, (int) w);
        h = prefHeight;
        prefHeight = Math.max(prefHeight, (int) h);
      }
      else if (!clip && !wrap)
      {
        if (w > 0 && w < prefWidth)
        {
          w = prefWidth;
        }
       
        if (h > 0 && h < prefHeight)
        {
          h = prefHeight;
        }
      }
     
      Point2D margin = getMargin(align, valign);
      x += margin.getX() * prefWidth;
      y += margin.getY() * prefHeight;

      if (w == 0)
      {
        w = prefWidth;
      }

      if (h == 0)
      {
        h = prefHeight;
      }

      rendererPane.paintComponent(state.g, textRenderer, rendererPane, (int) Math.round(x), (int) Math.round(y), (int) Math.round(w),
          (int) Math.round(h), true);
     
      state.g.setTransform(previous);
    }
  }

  /**
   * Draws the given text.
   */
  public void text(double x, double y, double w, double h, String str, String align, String valign, boolean wrap, String format,
      String overflow, boolean clip, double rotation)
  {
    if (format != null && format.equals("html"))
    {
      htmlText(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
    }
    else
    {
      plainText(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
    }
  }

  /**
   * Draws the given text.
   */
  public void plainText(double x, double y, double w, double h, String str, String align, String valign, boolean wrap, String format,
      String overflow, boolean clip, double rotation)
  {
    if (state.fontColor == null)
    {
      state.fontColor = parseColor(state.fontColorValue);
    }
   
    if (state.fontColor != null)
    {
      x = (state.dx + x) * state.scale;
      y = (state.dy + y) * state.scale;
      w *= state.scale;
      h *= state.scale;

      // Font-metrics needed below this line
      Graphics2D g2 = createTextGraphics(x, y, w, h, rotation, clip, align, valign);
      FontMetrics fm = g2.getFontMetrics();
      String[] lines = str.split("\n");
     
      int[] stringWidths = new int[lines.length];
      int textWidth = 0;
     
      for (int i = 0; i < lines.length; i++)
      {
        stringWidths[i] = fm.stringWidth(lines[i]);
        textWidth = Math.max(textWidth, stringWidths[i]);
      }

      int textHeight = (int) Math.round(lines.length * (fm.getFont().getSize() * mxConstants.LINE_HEIGHT));
     
      if (clip && textHeight > h && h > 0)
      {
        textHeight = (int) h;
      }
     
      Point2D margin = getMargin(align, valign);
      x += margin.getX() * textWidth;
      y += margin.getY() * textHeight;

      if (state.fontBackgroundColorValue != null)
      {
        if (state.fontBackgroundColor == null)
        {
          state.fontBackgroundColor = parseColor(state.fontBackgroundColorValue);
        }
       
        if (state.fontBackgroundColor != null)
        {
          g2.setColor(state.fontBackgroundColor);
          g2.fillRect((int) Math.round(x), (int) Math.round(y - 1), textWidth + 1, textHeight + 2);
        }
      }
     
      if (state.fontBorderColorValue != null)
      {
        if (state.fontBorderColor == null)
        {
          state.fontBorderColor = parseColor(state.fontBorderColorValue);
        }
       
        if (state.fontBorderColor != null)
        {
          g2.setColor(state.fontBorderColor);
          g2.drawRect((int) Math.round(x), (int) Math.round(y - 1), textWidth + 1, textHeight + 2);
        }
      }
     
      g2.setColor(state.fontColor);
      y += fm.getHeight() - fm.getDescent() - (margin.getY() + 0.5);

      for (int i = 0; i < lines.length; i++)
      {
        double dx = 0;

        if (align != null)
        {
          if (align.equals(mxConstants.ALIGN_CENTER))
          {
            dx = (textWidth - stringWidths[i]) / 2;
          }
          else if (align.equals(mxConstants.ALIGN_RIGHT))
          {
            dx = textWidth - stringWidths[i];
          }
        }

        // Adds support for underlined text via attributed character iterator
        if (!lines[i].isEmpty())
        {
          if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
          {
            AttributedString as = new AttributedString(lines[i]);
            as.addAttribute(TextAttribute.FONT, g2.getFont());
            as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
 
            g2.drawString(as.getIterator(), (int) Math.round(x + dx), (int) Math.round(y));
          }
          else
          {
            g2.drawString(lines[i], (int) Math.round(x + dx), (int) Math.round(y));
          }
        }

        y += (int) Math.round(fm.getFont().getSize() * mxConstants.LINE_HEIGHT);
      }
    }
  }

  /**
   * Returns a new graphics instance with the correct color and font for
   * text rendering.
   */
  protected final Graphics2D createTextGraphics(double x, double y, double w, double h, double rotation, boolean clip, String align, String valign)
  {
    Graphics2D g2 = state.g;
    updateFont();

    if (rotation != 0)
    {
      g2 = (Graphics2D) state.g.create();

      double rad = rotation * (Math.PI / 180);
      g2.rotate(rad, x, y);
    }
   
    if (clip && w > 0 && h > 0)
    {
      if (g2 == state.g)
      {
        g2 = (Graphics2D) state.g.create();
      }
     
      Point2D margin = getMargin(align, valign);
      x += margin.getX() * w;
      y += margin.getY() * h;
     
      g2.clip(new Rectangle2D.Double(x, y, w, h));
    }

    return g2;
  }

  /**
   *
   */
  public void begin()
  {
    currentPath = new GeneralPath();
  }

  /**
   *
   */
  public void moveTo(double x, double y)
  {
    if (currentPath != null)
    {
      currentPath.moveTo((float) ((state.dx + x) * state.scale), (float) ((state.dy + y) * state.scale));
    }
  }

  /**
   *
   */
  public void lineTo(double x, double y)
  {
    if (currentPath != null)
    {
      currentPath.lineTo((float) ((state.dx + x) * state.scale), (float) ((state.dy + y) * state.scale));
    }
  }

  /**
   *
   */
  public void quadTo(double x1, double y1, double x2, double y2)
  {
    if (currentPath != null)
    {
      currentPath.quadTo((float) ((state.dx + x1) * state.scale), (float) ((state.dy + y1) * state.scale),
          (float) ((state.dx + x2) * state.scale), (float) ((state.dy + y2) * state.scale));
    }
  }

  /**
   *
   */
  public void curveTo(double x1, double y1, double x2, double y2, double x3, double y3)
  {
    if (currentPath != null)
    {
      currentPath.curveTo((float) ((state.dx + x1) * state.scale), (float) ((state.dy + y1) * state.scale),
          (float) ((state.dx + x2) * state.scale), (float) ((state.dy + y2) * state.scale),
          (float) ((state.dx + x3) * state.scale), (float) ((state.dy + y3) * state.scale));
    }
  }

  /**
   * Closes the current path.
   */
  public void close()
  {
    if (currentPath != null)
    {
      currentPath.closePath();
    }
  }

  /**
   *
   */
  public void stroke()
  {
    paintCurrentPath(false, true);
  }

  /**
   *
   */
  public void fill()
  {
    paintCurrentPath(true, false);
  }

  /**
   *
   */
  public void fillAndStroke()
  {
    paintCurrentPath(true, true);
  }

  /**
   *
   */
  protected void paintCurrentPath(boolean filled, boolean stroked)
  {
    if (currentPath != null)
    {
      if (stroked)
      {
        if (state.strokeColor == null)
        {
          state.strokeColor = parseColor(state.strokeColorValue);
        }

        if (state.strokeColor != null)
        {
          updateStroke();
        }
      }

      if (filled)
      {
        if (state.gradientPaint == null && state.fillColor == null)
        {
          state.fillColor = parseColor(state.fillColorValue);
        }
      }

      if (state.shadow)
      {
        paintShadow(filled, stroked);
      }

      if (filled)
      {
        if (state.gradientPaint != null)
        {
          state.g.setPaint(state.gradientPaint);
          state.g.fill(currentPath);
        }
        else
        {
          if (state.fillColor == null)
          {
            state.fillColor = parseColor(state.fillColorValue);
          }

          if (state.fillColor != null)
          {
            state.g.setColor(state.fillColor);
            state.g.setPaint(null);
            state.g.fill(currentPath);
          }
        }
      }

      if (stroked && state.strokeColor != null)
      {
        state.g.setColor(state.strokeColor);
        state.g.draw(currentPath);
      }
    }
  }
 
  /**
   *
   */
  protected void paintShadow(boolean filled, boolean stroked)
  {
    if (state.shadowColor == null)
    {
      state.shadowColor = parseColor(state.shadowColorValue);
    }

    if (state.shadowColor != null)
    {
      double rad = -state.theta * (Math.PI / 180);
      double cos = Math.cos(rad);
      double sin = Math.sin(rad);

      double dx = state.shadowOffsetX * state.scale;
      double dy = state.shadowOffsetY * state.scale;

      if (state.flipH)
      {
        dx *= -1;
      }

      if (state.flipV)
      {
        dy *= -1;
      }

      double tx = dx * cos - dy * sin;
      double ty = dx * sin + dy * cos;

      state.g.setColor(state.shadowColor);
      state.g.translate(tx, ty);

      double alpha = state.alpha * state.shadowAlpha;

      Composite comp = state.g.getComposite();
      state.g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) (alpha)));

      if (filled && (state.gradientPaint != null || state.fillColor != null))
      {
        state.g.fill(currentPath);
      }

      // FIXME: Overlaps with fill in composide mode
      if (stroked && state.strokeColor != null)
      {
        state.g.draw(currentPath);
      }

      state.g.translate(-tx, -ty);
      state.g.setComposite(comp);
    }
  }

  /**
   *
   */
  public void setShadow(boolean value)
  {
    state.shadow = value;
  }

  /**
   *
   */
  public void setShadowColor(String value)
  {
    state.shadowColorValue = value;
  }

  /**
   *
   */
  public void setShadowAlpha(double value)
  {
    state.shadowAlpha = value;
  }

  /**
   *
   */
  public void setShadowOffset(double dx, double dy)
  {
    state.shadowOffsetX = dx;
    state.shadowOffsetY = dy;
  }

  /**
   *
   */
  protected void updateFont()
  {
    int size = (int) Math.round(state.fontSize * state.scale);
    int style = ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) ? Font.BOLD : Font.PLAIN;
    style += ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) ? Font.ITALIC : Font.PLAIN;

    if (lastFont == null || !lastFontFamily.equals(state.fontFamily) || size != lastFontSize || style != lastFontStyle)
    {
      lastFont = createFont(state.fontFamily, style, size);
      lastFontFamily = state.fontFamily;
      lastFontStyle = style;
      lastFontSize = size;
    }

    state.g.setFont(lastFont);
  }

  /**
   * Hook for subclassers to implement font caching.
   */
  protected Font createFont(String family, int style, int size)
  {
    return new Font(getFontName(family), style, size);
  }

  /**
   * Returns a font name for the given CSS values for font-family.
   * This implementation returns the first entry for comma-separated
   * lists of entries.
   */
  protected String getFontName(String family)
  {
    if (family != null)
    {
      int comma = family.indexOf(',');

      if (comma >= 0)
      {
        family = family.substring(0, comma);
      }
    }

    return family;
  }

  /**
   *
   */
  protected void updateStroke()
  {
    float sw = (float) Math.max(1, state.strokeWidth * state.scale);
    int cap = BasicStroke.CAP_BUTT;

    if (state.lineCap.equals("round"))
    {
      cap = BasicStroke.CAP_ROUND;
    }
    else if (state.lineCap.equals("square"))
    {
      cap = BasicStroke.CAP_SQUARE;
    }

    int join = BasicStroke.JOIN_MITER;

    if (state.lineJoin.equals("round"))
    {
      join = BasicStroke.JOIN_ROUND;
    }
    else if (state.lineJoin.equals("bevel"))
    {
      join = BasicStroke.JOIN_BEVEL;
    }

    float miterlimit = (float) state.miterLimit;

    if (lastStroke == null || lastStrokeWidth != sw || lastCap != cap || lastJoin != join || lastMiterLimit != miterlimit
        || lastDashed != state.dashed || (state.dashed && lastDashPattern != state.dashPattern))
    {
      float[] dash = null;

      if (state.dashed)
      {
        dash = new float[state.dashPattern.length];

        for (int i = 0; i < dash.length; i++)
        {
          dash[i] = (float) (state.dashPattern[i] * sw);
        }
      }

      lastStroke = new BasicStroke(sw, cap, join, miterlimit, dash, 0);
      lastStrokeWidth = sw;
      lastCap = cap;
      lastJoin = join;
      lastMiterLimit = miterlimit;
      lastDashed = state.dashed;
      lastDashPattern = state.dashPattern;
    }

    state.g.setStroke(lastStroke);
  }

  /**
   *
   */
  protected class CanvasState implements Cloneable
  {
    /**
     *
     */
    protected double alpha = 1;

    /**
     *
     */
    protected double scale = 1;

    /**
     *
     */
    protected double dx = 0;

    /**
     *
     */
    protected double dy = 0;

    /**
     *
     */
    protected double theta = 0;

    /**
     *
     */
    protected double rotationCx = 0;

    /**
     *
     */
    protected double rotationCy = 0;

    /**
     *
     */
    protected boolean flipV = false;

    /**
     *
     */
    protected boolean flipH = false;

    /**
     *
     */
    protected double miterLimit = 10;

    /**
     *
     */
    protected int fontStyle = 0;

    /**
     *
     */
    protected double fontSize = mxConstants.DEFAULT_FONTSIZE;

    /**
     *
     */
    protected String fontFamily = mxConstants.DEFAULT_FONTFAMILIES;

    /**
     *
     */
    protected String fontColorValue = "#000000";

    /**
     *
     */
    protected Color fontColor;

    /**
     *
     */
    protected String fontBackgroundColorValue;

    /**
     *
     */
    protected Color fontBackgroundColor;

    /**
     *
     */
    protected String fontBorderColorValue;

    /**
     *
     */
    protected Color fontBorderColor;

    /**
     *
     */
    protected String lineCap = "flat";

    /**
     *
     */
    protected String lineJoin = "miter";

    /**
     *
     */
    protected double strokeWidth = 1;

    /**
     *
     */
    protected String strokeColorValue;

    /**
     *
     */
    protected Color strokeColor;

    /**
     *
     */
    protected String fillColorValue;

    /**
     *
     */
    protected Color fillColor;

    /**
     *
     */
    protected Paint gradientPaint;

    /**
     *
     */
    protected boolean dashed = false;

    /**
     *
     */
    protected float[] dashPattern = { 3, 3 };

    /**
     *
     */
    protected boolean shadow = false;

    /**
     *
     */
    protected String shadowColorValue = mxConstants.W3C_SHADOWCOLOR;

    /**
     *
     */
    protected Color shadowColor;

    /**
     *
     */
    protected double shadowAlpha = 1;

    /**
     *
     */
    protected double shadowOffsetX = mxConstants.SHADOW_OFFSETX;

    /**
     *
     */
    protected double shadowOffsetY = mxConstants.SHADOW_OFFSETY;

    /**
     * Stores the actual state.
     */
    protected transient Graphics2D g;

    /**
     *
     */
    public Object clone() throws CloneNotSupportedException
    {
      return super.clone();
    }

  }

}
TOP

Related Classes of com.mxgraph.canvas.mxGraphicsCanvas2D$CanvasState

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.