Package org.activiti.image.impl

Source Code of org.activiti.image.impl.DefaultProcessDiagramCanvas

/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.activiti.image.impl;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;

import org.activiti.bpmn.model.AssociationDirection;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.image.exception.ActivitiImageException;
import org.activiti.image.util.ReflectUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Represents a canvas on which BPMN 2.0 constructs can be drawn.
*
* Some of the icons used are licensed under a Creative Commons Attribution 2.5
* License, see http://www.famfamfam.com/lab/icons/silk/
*
* @see org.activiti.engine.impl.bpmn.diagram.DefaultProcessDiagramGenerator
* @author Joram Barrez
*/
public class DefaultProcessDiagramCanvas {

  protected static final Logger LOGGER = LoggerFactory.getLogger(DefaultProcessDiagramCanvas.class);
  public enum SHAPE_TYPE {Rectangle, Rhombus, Ellipse}

  // Predefined sized
  protected static final int ARROW_WIDTH = 5;
  protected static final int CONDITIONAL_INDICATOR_WIDTH = 16;
  protected static final int DEFAULT_INDICATOR_WIDTH = 10;
  protected static final int MARKER_WIDTH = 12;
  protected static final int FONT_SIZE = 11;
  protected static final int FONT_SPACING = 2;
  protected static final int TEXT_PADDING = 3;
  protected static final int ANNOTATION_TEXT_PADDING = 7;
  protected static final int LINE_HEIGHT = FONT_SIZE + FONT_SPACING;
 

  // Colors
  protected static Color TASK_BOX_COLOR = new Color(249, 249, 249);
  protected static Color SUBPROCESS_BOX_COLOR = new Color(255, 255, 255);
  protected static Color EVENT_COLOR = new Color(255, 255, 255);
  protected static Color CONNECTION_COLOR = new Color(88, 88, 88);
  protected static Color CONDITIONAL_INDICATOR_COLOR = new Color(255, 255, 255);
  protected static Color HIGHLIGHT_COLOR = Color.RED;
  protected static Color LABEL_COLOR = new Color(112, 146, 190);
  protected static Color TASK_BORDER_COLOR = new Color(187, 187, 187);
  protected static Color EVENT_BORDER_COLOR = new Color(88, 88, 88);
  protected static Color SUBPROCESS_BORDER_COLOR = new Color(0, 0, 0);
 
  // Fonts
  protected static Font LABEL_FONT = null;
  protected static Font ANNOTATION_FONT = new Font("Arial", Font.PLAIN, FONT_SIZE);
  protected static Font TASK_FONT = new Font("Arial", Font.PLAIN, FONT_SIZE);

  // Strokes
  protected static Stroke THICK_TASK_BORDER_STROKE = new BasicStroke(3.0f);
  protected static Stroke GATEWAY_TYPE_STROKE = new BasicStroke(3.0f);
  protected static Stroke END_EVENT_STROKE = new BasicStroke(3.0f);
  protected static Stroke MULTI_INSTANCE_STROKE = new BasicStroke(1.3f);
  protected static Stroke EVENT_SUBPROCESS_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0fnew float[] { 1.0f }, 0.0f);
  protected static Stroke NON_INTERRUPTING_EVENT_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0fnew float[] { 4.0f, 3.0f }, 0.0f);
  protected static Stroke HIGHLIGHT_FLOW_STROKE = new BasicStroke(1.3f);
  protected static Stroke ANNOTATION_STROKE = new BasicStroke(2.0f);
  protected static Stroke ASSOCIATION_STROKE = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0fnew float[] { 2.0f, 2.0f }, 0.0f);

  // icons
  protected static int ICON_PADDING = 5;
  protected static BufferedImage USERTASK_IMAGE;
  protected static BufferedImage SCRIPTTASK_IMAGE;
  protected static BufferedImage SERVICETASK_IMAGE;
  protected static BufferedImage RECEIVETASK_IMAGE;
  protected static BufferedImage SENDTASK_IMAGE;
  protected static BufferedImage MANUALTASK_IMAGE;
  protected static BufferedImage BUSINESS_RULE_TASK_IMAGE;
  protected static BufferedImage SHELL_TASK_IMAGE;
  protected static BufferedImage MULE_TASK_IMAGE;
  protected static BufferedImage CAMEL_TASK_IMAGE;
 
  protected static BufferedImage TIMER_IMAGE;
  protected static BufferedImage ERROR_THROW_IMAGE;
  protected static BufferedImage ERROR_CATCH_IMAGE;
  protected static BufferedImage MESSAGE_THROW_IMAGE;
  protected static BufferedImage MESSAGE_CATCH_IMAGE;
  protected static BufferedImage SIGNAL_CATCH_IMAGE;
  protected static BufferedImage SIGNAL_THROW_IMAGE;

  protected int canvasWidth = -1;
  protected int canvasHeight = -1;
  protected int minX = -1;
  protected int minY = -1;
  protected BufferedImage processDiagram;
  protected Graphics2D g;
  protected FontMetrics fontMetrics;
  protected boolean closed;
  protected ClassLoader customClassLoader;
  protected String activityFontName = "Arial";
  protected String labelFontName = "Arial";
 
  /**
   * Creates an empty canvas with given width and height.
   *
   * Allows to specify minimal boundaries on the left and upper side of the
   * canvas. This is useful for diagrams that have white space there.
   * Everything beneath these minimum values will be cropped.
   * It's also possible to pass a specific font name and a class loader for the icon images.
   *
   */
  public DefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType,
      String activityFontName, String labelFontName, ClassLoader customClassLoader) {
   
    this.canvasWidth = width;
    this.canvasHeight = height;
    this.minX = minX;
    this.minY = minY;
    if (activityFontName != null) {
      this.activityFontName = activityFontName;
    }
    if (labelFontName != null) {
      this.labelFontName = labelFontName;
    }
    this.customClassLoader = customClassLoader;
   
    initialize(imageType);
  }

  /**
   * Creates an empty canvas with given width and height.
   *
   * Allows to specify minimal boundaries on the left and upper side of the
   * canvas. This is useful for diagrams that have white space there (eg
   * Signavio). Everything beneath these minimum values will be cropped.
   *
   * @param minX
   *          Hint that will be used when generating the image. Parts that fall
   *          below minX on the horizontal scale will be cropped.
   * @param minY
   *          Hint that will be used when generating the image. Parts that fall
   *          below minX on the horizontal scale will be cropped.
   */
  public DefaultProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) {
    this.canvasWidth = width;
    this.canvasHeight = height;
    this.minX = minX;
    this.minY = minY;
   
    initialize(imageType);
  }
 
  public void initialize(String imageType) {
    if ("png".equalsIgnoreCase(imageType)) {
      this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
    } else {
      this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);
    }
   
    this.g = processDiagram.createGraphics();
    if ("png".equalsIgnoreCase(imageType) == false) {
      this.g.setBackground(new Color(255, 255, 255, 0));
      this.g.clearRect(0, 0, canvasWidth, canvasHeight);
    }

    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g.setPaint(Color.black);
   
    Font font = new Font(activityFontName, Font.BOLD, FONT_SIZE);
    g.setFont(font);
    this.fontMetrics = g.getFontMetrics();

    LABEL_FONT = new Font(labelFontName, Font.ITALIC, 10);
   
    try {
      USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/userTask.png", customClassLoader));
      SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/scriptTask.png", customClassLoader));
      SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/serviceTask.png", customClassLoader));
      RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/receiveTask.png", customClassLoader));
      SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/sendTask.png", customClassLoader));
      MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/manualTask.png", customClassLoader));
      BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/businessRuleTask.png", customClassLoader));
      SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/shellTask.png", customClassLoader));
      CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/camelTask.png", customClassLoader));
      MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/muleTask.png", customClassLoader));
     
      TIMER_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/timer.png", customClassLoader));
      ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/error-throw.png", customClassLoader));
      ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/error.png", customClassLoader));
      MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/message-throw.png", customClassLoader));
      MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/message.png", customClassLoader));
      SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/signal-throw.png", customClassLoader));
      SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResourceAsStream("org/activiti/icons/signal.png", customClassLoader));
    } catch (IOException e) {
      LOGGER.warn("Could not load image for process diagram creation: {}", e.getMessage());
    }
  }

  /**
   * Generates an image of what currently is drawn on the canvas.
   *
   * Throws an {@link ActivitiException} when {@link #close()} is already
   * called.
   */
  public InputStream generateImage(String imageType) {
    if (closed) {
      throw new ActivitiImageException("ProcessDiagramGenerator already closed");
    }

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
      // Try to remove white space
      minX = (minX <= 5) ? 5 : minX;
      minY = (minY <= 5) ? 5 : minY;
      BufferedImage imageToSerialize = processDiagram;
      if (minX >= 0 && minY >= 0) {
        imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5);
      }
      ImageIO.write(imageToSerialize, imageType, out);
     
    } catch (IOException e) {
      throw new ActivitiImageException("Error while generating process image", e);
    } finally {
      try {
        if (out != null) {
          out.close();
        }
      } catch(IOException ignore) {
        // Exception is silently ignored
      }
    }
    return new ByteArrayInputStream(out.toByteArray());
  }
 
  /**
   * Generates an image of what currently is drawn on the canvas.
   *
   * Throws an {@link ActivitiException} when {@link #close()} is already
   * called.
   */
  public BufferedImage generateBufferedImage(String imageType) {
    if (closed) {
      throw new ActivitiImageException("ProcessDiagramGenerator already closed");
    }

    // Try to remove white space
    minX = (minX <= 5) ? 5 : minX;
    minY = (minY <= 5) ? 5 : minY;
    BufferedImage imageToSerialize = processDiagram;
    if (minX >= 0 && minY >= 0) {
      imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5);
    }
    return imageToSerialize;
  }

  /**
   * Closes the canvas which dissallows further drawing and releases graphical
   * resources.
   */
  public void close() {
    g.dispose();
    closed = true;
  }

  public void drawNoneStartEvent(GraphicInfo graphicInfo) {
    drawStartEvent(graphicInfo, null, 1.0);
  }

  public void drawTimerStartEvent(GraphicInfo graphicInfo, double scaleFactor) {
    drawStartEvent(graphicInfo, TIMER_IMAGE, scaleFactor);
  }
 
  public void drawSignalStartEvent(GraphicInfo graphicInfo, double scaleFactor) {
    drawStartEvent(graphicInfo, SIGNAL_CATCH_IMAGE, scaleFactor);
  }

  public void drawStartEvent(GraphicInfo graphicInfo, BufferedImage image, double scaleFactor) {
    Paint originalPaint = g.getPaint();
    g.setPaint(EVENT_COLOR);
    Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(),
        graphicInfo.getWidth(), graphicInfo.getHeight());
    g.fill(circle);
    g.setPaint(EVENT_BORDER_COLOR);
    g.draw(circle);
    g.setPaint(originalPaint);
    if (image != null) {
      g.drawImage(image, (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4)),
          (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4)),
          (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), null);
    }

  }

  public void drawNoneEndEvent(GraphicInfo graphicInfo, double scaleFactor) {
    Paint originalPaint = g.getPaint();
    Stroke originalStroke = g.getStroke();
    g.setPaint(EVENT_COLOR);
    Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(),
        graphicInfo.getWidth(), graphicInfo.getHeight());
    g.fill(circle);
    g.setPaint(EVENT_BORDER_COLOR);
    if (scaleFactor == 1.0) {
      g.setStroke(END_EVENT_STROKE);
    } else {
      g.setStroke(new BasicStroke(2.0f));
    }
    g.draw(circle);
    g.setStroke(originalStroke);
    g.setPaint(originalPaint);
  }

  public void drawErrorEndEvent(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawErrorEndEvent(graphicInfo, scaleFactor);
    if (scaleFactor == 1.0) {
      drawLabel(name, graphicInfo);
    }
  }
 
  public void drawErrorEndEvent(GraphicInfo graphicInfo, double scaleFactor) {
    drawNoneEndEvent(graphicInfo, scaleFactor);
    g.drawImage(ERROR_THROW_IMAGE, (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4)),
        (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4)),
        (int) (ERROR_THROW_IMAGE.getWidth() / scaleFactor),
        (int) (ERROR_THROW_IMAGE.getHeight() / scaleFactor), null);
  }
 
  public void drawErrorStartEvent(GraphicInfo graphicInfo, double scaleFactor) {
    drawNoneStartEvent(graphicInfo);
    g.drawImage(ERROR_CATCH_IMAGE, (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4)),
        (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4)),
        (int) (ERROR_CATCH_IMAGE.getWidth() / scaleFactor),
        (int) (ERROR_CATCH_IMAGE.getHeight() / scaleFactor), null);
  }

  public void drawCatchingEvent(GraphicInfo graphicInfo, boolean isInterrupting,
      BufferedImage image, String eventType, double scaleFactor) {
   
    // event circles
    Ellipse2D outerCircle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(),
        graphicInfo.getWidth(), graphicInfo.getHeight());
    int innerCircleSize = (int) (4 / scaleFactor);
    if (innerCircleSize == 0) {
      innerCircleSize = 1;
    }
    int innerCircleX = (int) graphicInfo.getX() + innerCircleSize;
    int innerCircleY = (int) graphicInfo.getY() + innerCircleSize;
    int innerCircleWidth = (int) graphicInfo.getWidth() - (2 * innerCircleSize);
    int innerCircleHeight = (int) graphicInfo.getHeight() - (2 * innerCircleSize);
    Ellipse2D innerCircle = new Ellipse2D.Double(innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight);

    Paint originalPaint = g.getPaint();
    Stroke originalStroke = g.getStroke();
    g.setPaint(EVENT_COLOR);
    g.fill(outerCircle);

    g.setPaint(EVENT_BORDER_COLOR);
    if (isInterrupting == false)
      g.setStroke(NON_INTERRUPTING_EVENT_STROKE);
    g.draw(outerCircle);
    g.setStroke(originalStroke);
    g.setPaint(originalPaint);
    g.draw(innerCircle);

    if (image != null) {
      int imageX = (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4));
      int imageY = (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4));
      if (scaleFactor == 1.0 && "timer".equals(eventType)) {
        // move image one pixel to center timer image
        imageX--;
        imageY--;
      }
      g.drawImage(image, imageX, imageY, (int) (image.getWidth() / scaleFactor),
          (int) (image.getHeight() / scaleFactor), null);
    }
  }

  public void drawCatchingTimerEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
    drawCatchingTimerEvent(graphicInfo, isInterrupting, scaleFactor);
    drawLabel(name, graphicInfo);
  }

  public void drawCatchingTimerEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
    drawCatchingEvent(graphicInfo, isInterrupting, TIMER_IMAGE, "timer", scaleFactor);
  }

  public void drawCatchingErrorEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
    drawCatchingErrorEvent(graphicInfo, isInterrupting, scaleFactor);
    drawLabel(name, graphicInfo);
  }

  public void drawCatchingErrorEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
    drawCatchingEvent(graphicInfo, isInterrupting, ERROR_CATCH_IMAGE, "error", scaleFactor);
  }

  public void drawCatchingSignalEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
    drawCatchingSignalEvent(graphicInfo, isInterrupting, scaleFactor);
    drawLabel(name, graphicInfo);
  }

  public void drawCatchingSignalEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) {
    drawCatchingEvent(graphicInfo, isInterrupting, SIGNAL_CATCH_IMAGE, "signal", scaleFactor);
  }

  public void drawThrowingSignalEvent(GraphicInfo graphicInfo, double scaleFactor) {
    drawCatchingEvent(graphicInfo, true, SIGNAL_THROW_IMAGE, "signal", scaleFactor);
  }
 
  public void drawThrowingNoneEvent(GraphicInfo graphicInfo, double scaleFactor) {
    drawCatchingEvent(graphicInfo, true, null, "none", scaleFactor);
  }

  public void drawSequenceflow(int srcX, int srcY, int targetX, int targetY, boolean conditional, double scaleFactor) {
    drawSequenceflow(srcX, srcY, targetX, targetY, conditional, false, scaleFactor);
  }
 
  public void drawSequenceflow(int srcX, int srcY, int targetX, int targetY, boolean conditional, boolean highLighted, double scaleFactor) {
    Paint originalPaint = g.getPaint();
    if (highLighted)
      g.setPaint(HIGHLIGHT_COLOR);

    Line2D.Double line = new Line2D.Double(srcX, srcY, targetX, targetY);
    g.draw(line);
    drawArrowHead(line, scaleFactor);

    if (conditional) {
      drawConditionalSequenceFlowIndicator(line, scaleFactor);
    }

    if (highLighted)
      g.setPaint(originalPaint);
  }

  public void drawAssociation(int[] xPoints, int[] yPoints, AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
    boolean conditional = false, isDefault = false;
    drawConnection(xPoints, yPoints, conditional, isDefault, "association", associationDirection, highLighted, scaleFactor);
  }

  public void drawSequenceflow(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, boolean highLighted, double scaleFactor) {
    drawConnection(xPoints, yPoints, conditional, isDefault, "sequenceFlow", AssociationDirection.ONE, highLighted, scaleFactor);
  }
 
  public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType,
      AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
   
    Paint originalPaint = g.getPaint();
    Stroke originalStroke = g.getStroke();

    g.setPaint(CONNECTION_COLOR);
    if (connectionType.equals("association")) {
      g.setStroke(ASSOCIATION_STROKE);
    } else if (highLighted) {
      g.setPaint(HIGHLIGHT_COLOR);
      g.setStroke(HIGHLIGHT_FLOW_STROKE);
    }

    for (int i=1; i<xPoints.length; i++) {
      Integer sourceX = xPoints[i - 1];
      Integer sourceY = yPoints[i - 1];
      Integer targetX = xPoints[i];
      Integer targetY = yPoints[i];
      Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY);
      g.draw(line);
    }
 
    if (isDefault){
      Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
      drawDefaultSequenceFlowIndicator(line, scaleFactor);
    }

    if (conditional) {
      Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
      drawConditionalSequenceFlowIndicator(line, scaleFactor);
    }
 
    if (associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) {
      Line2D.Double line = new Line2D.Double(xPoints[xPoints.length-2], yPoints[xPoints.length-2], xPoints[xPoints.length-1], yPoints[xPoints.length-1]);
      drawArrowHead(line, scaleFactor);
    }
    if (associationDirection.equals(AssociationDirection.BOTH)) {
      Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]);
      drawArrowHead(line, scaleFactor);
    }
    g.setPaint(originalPaint);
    g.setStroke(originalStroke);
  }

  public void drawSequenceflowWithoutArrow(int srcX, int srcY, int targetX, int targetY, boolean conditional, double scaleFactor) {
    drawSequenceflowWithoutArrow(srcX, srcY, targetX, targetY, conditional, false, scaleFactor);
  }

  public void drawSequenceflowWithoutArrow(int srcX, int srcY, int targetX, int targetY, boolean conditional, boolean highLighted, double scaleFactor) {
    Paint originalPaint = g.getPaint();
    if (highLighted)
      g.setPaint(HIGHLIGHT_COLOR);

    Line2D.Double line = new Line2D.Double(srcX, srcY, targetX, targetY);
    g.draw(line);

    if (conditional) {
      drawConditionalSequenceFlowIndicator(line, scaleFactor);
    }

    if (highLighted)
      g.setPaint(originalPaint);
  }

  public void drawArrowHead(Line2D.Double line, double scaleFactor) {
    int doubleArrowWidth = (int) (2 * ARROW_WIDTH / scaleFactor);
    if (doubleArrowWidth == 0) {
      doubleArrowWidth = 2;
    }
    Polygon arrowHead = new Polygon();
    arrowHead.addPoint(0, 0);
    int arrowHeadPoint = (int) (-ARROW_WIDTH / scaleFactor);
    if (arrowHeadPoint == 0) {
      arrowHeadPoint = -1;
    }
    arrowHead.addPoint(arrowHeadPoint, -doubleArrowWidth);
    arrowHeadPoint = (int) (ARROW_WIDTH / scaleFactor);
    if (arrowHeadPoint == 0) {
      arrowHeadPoint = 1;
    }
    arrowHead.addPoint(arrowHeadPoint, -doubleArrowWidth);

    AffineTransform transformation = new AffineTransform();
    transformation.setToIdentity();
    double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
    transformation.translate(line.x2, line.y2);
    transformation.rotate((angle - Math.PI / 2d));

    AffineTransform originalTransformation = g.getTransform();
    g.setTransform(transformation);
    g.fill(arrowHead);
    g.setTransform(originalTransformation);
  }

  public void drawDefaultSequenceFlowIndicator(Line2D.Double line, double scaleFactor) {
    double length = DEFAULT_INDICATOR_WIDTH / scaleFactor, halfOfLength = length/2, f = 8;
    Line2D.Double defaultIndicator = new Line2D.Double(-halfOfLength, 0, halfOfLength, 0);

    double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
    double dx = f * Math.cos(angle), dy = f * Math.sin(angle),
         x1 = line.x1 + dx, y1 = line.y1 + dy;

    AffineTransform transformation = new AffineTransform();
    transformation.setToIdentity();
    transformation.translate(x1, y1);
    transformation.rotate((angle - 3 * Math.PI / 4));

    AffineTransform originalTransformation = g.getTransform();
    g.setTransform(transformation);
    g.draw(defaultIndicator);

    g.setTransform(originalTransformation);
  }

  public void drawConditionalSequenceFlowIndicator(Line2D.Double line, double scaleFactor) {
    if (scaleFactor > 1.0) return;
    int horizontal = (int) (CONDITIONAL_INDICATOR_WIDTH * 0.7);
    int halfOfHorizontal = horizontal / 2;
    int halfOfVertical = CONDITIONAL_INDICATOR_WIDTH / 2;

    Polygon conditionalIndicator = new Polygon();
    conditionalIndicator.addPoint(0, 0);
    conditionalIndicator.addPoint(-halfOfHorizontal, halfOfVertical);
    conditionalIndicator.addPoint(0, CONDITIONAL_INDICATOR_WIDTH);
    conditionalIndicator.addPoint(halfOfHorizontal, halfOfVertical);

    AffineTransform transformation = new AffineTransform();
    transformation.setToIdentity();
    double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
    transformation.translate(line.x1, line.y1);
    transformation.rotate((angle - Math.PI / 2d));

    AffineTransform originalTransformation = g.getTransform();
    g.setTransform(transformation);
    g.draw(conditionalIndicator);

    Paint originalPaint = g.getPaint();
    g.setPaint(CONDITIONAL_INDICATOR_COLOR);
    g.fill(conditionalIndicator);

    g.setPaint(originalPaint);
    g.setTransform(originalTransformation);
  }

  public void drawTask(BufferedImage icon, String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(name, graphicInfo);
    g.drawImage(icon, (int) (graphicInfo.getX() + ICON_PADDING / scaleFactor),
        (int) (graphicInfo.getY() + ICON_PADDING / scaleFactor),
        (int) (icon.getWidth() / scaleFactor), (int) (icon.getHeight() / scaleFactor), null);
  }

  public void drawTask(String name, GraphicInfo graphicInfo) {
    drawTask(name, graphicInfo, false);
  }
 
  public void drawPoolOrLane(String name, GraphicInfo graphicInfo) {
    int x = (int) graphicInfo.getX();
    int y = (int) graphicInfo.getY();
    int width = (int) graphicInfo.getWidth();
    int height = (int) graphicInfo.getHeight();
    g.drawRect(x, y, width, height);
   
    // Add the name as text, vertical
    if(name != null && name.length() > 0) {
      // Include some padding
      int availableTextSpace = height - 6;

      // Create rotation for derived font
      AffineTransform transformation = new AffineTransform();
      transformation.setToIdentity();
      transformation.rotate(270 * Math.PI/180);

      Font currentFont = g.getFont();
      Font theDerivedFont = currentFont.deriveFont(transformation);
      g.setFont(theDerivedFont);
     
      String truncated = fitTextToWidth(name, availableTextSpace);
      int realWidth = fontMetrics.stringWidth(truncated);
     
      g.drawString(truncated, x + 2 + fontMetrics.getHeight(), 3 + y + availableTextSpace - (availableTextSpace - realWidth) / 2);
      g.setFont(currentFont);
    }
  }

  protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder) {
    Paint originalPaint = g.getPaint();
    int x = (int) graphicInfo.getX();
    int y = (int) graphicInfo.getY();
    int width = (int) graphicInfo.getWidth();
    int height = (int) graphicInfo.getHeight();
   
    // Create a new gradient paint for every task box, gradient depends on x and y and is not relative
    g.setPaint(TASK_BOX_COLOR);

    int arcR = 6;
    if (thickBorder)
      arcR = 3;
   
    // shape
    RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcR, arcR);
    g.fill(rect);
    g.setPaint(TASK_BORDER_COLOR);

    if (thickBorder) {
      Stroke originalStroke = g.getStroke();
      g.setStroke(THICK_TASK_BORDER_STROKE);
      g.draw(rect);
      g.setStroke(originalStroke);
    } else {
      g.draw(rect);
    }

    g.setPaint(originalPaint);
    // text
    if (name != null && name.length() > 0) {
      int boxWidth = width - (2 * TEXT_PADDING);
      int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2;
      int boxX = x + width/2 - boxWidth/2;
      int boxY = y + height/2 - boxHeight/2 + ICON_PADDING + ICON_PADDING - 2 - 2;
     
      drawMultilineCentredText(name, boxX, boxY, boxWidth, boxHeight);
    }
  }
 
  protected void drawMultilineCentredText(String text, int x, int y, int boxWidth, int boxHeight) {
    drawMultilineText(text, x, y, boxWidth, boxHeight, true);
  }

  protected void drawMultilineAnnotationText(String text, int x, int y, int boxWidth, int boxHeight) {
    drawMultilineText(text, x, y, boxWidth, boxHeight, false);
  }
 
  protected void drawMultilineText(String text, int x, int y, int boxWidth, int boxHeight, boolean centered) {
    // Create an attributed string based in input text
    AttributedString attributedString = new AttributedString(text);
    attributedString.addAttribute(TextAttribute.FONT, g.getFont());
    attributedString.addAttribute(TextAttribute.FOREGROUND, Color.black);
   
    AttributedCharacterIterator characterIterator = attributedString.getIterator();
   
    int currentHeight = 0;
    // Prepare a list of lines of text we'll be drawing
    List<TextLayout> layouts = new ArrayList<TextLayout>();
    String lastLine = null;
   
    LineBreakMeasurer measurer = new LineBreakMeasurer(characterIterator, g.getFontRenderContext());
   
    TextLayout layout = null;
    while (measurer.getPosition() < characterIterator.getEndIndex() && currentHeight <= boxHeight) {
      
      int previousPosition = measurer.getPosition();
     
      // Request next layout
      layout = measurer.nextLayout(boxWidth);
     
      int height = ((Float)(layout.getDescent() + layout.getAscent() + layout.getLeading())).intValue();
     
      if(currentHeight + height > boxHeight) {
        // The line we're about to add should NOT be added anymore, append three dots to previous one instead
        // to indicate more text is truncated
        if (layouts.size() > 0) {
          layouts.remove(layouts.size() - 1);
         
          if(lastLine.length() >= 4) {
            lastLine = lastLine.substring(0, lastLine.length() - 4) + "...";
          }
          layouts.add(new TextLayout(lastLine, g.getFont(), g.getFontRenderContext()));
        }
      } else {
        layouts.add(layout);
        lastLine = text.substring(previousPosition, measurer.getPosition());
        currentHeight += height;
      }
    }
   
   
    int currentY = y + (centered ? ((boxHeight - currentHeight) /2) : 0);
    int currentX = 0;
   
    // Actually draw the lines
    for(TextLayout textLayout : layouts) {
     
      currentY += textLayout.getAscent();
      currentX = x + (centered ? ((boxWidth - ((Double)textLayout.getBounds().getWidth()).intValue()) /2) : 0);
     
      textLayout.draw(g, currentX, currentY);
      currentY += textLayout.getDescent() + textLayout.getLeading();
    }
   
  }
 

  protected String fitTextToWidth(String original, int width) {
    String text = original;

    // remove length for "..."
    int maxWidth = width - 10;

    while (fontMetrics.stringWidth(text + "...") > maxWidth && text.length() > 0) {
      text = text.substring(0, text.length() - 1);
    }

    if (!text.equals(original)) {
      text = text + "...";
    }

    return text;
  }

  public void drawUserTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(USERTASK_IMAGE, name, graphicInfo, scaleFactor);
  }

  public void drawScriptTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(SCRIPTTASK_IMAGE, name, graphicInfo, scaleFactor);
  }

  public void drawServiceTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(SERVICETASK_IMAGE, name, graphicInfo, scaleFactor);
  }

  public void drawReceiveTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(RECEIVETASK_IMAGE, name, graphicInfo, scaleFactor);
  }

  public void drawSendTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(SENDTASK_IMAGE, name, graphicInfo, scaleFactor);
  }

  public void drawManualTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(MANUALTASK_IMAGE, name, graphicInfo, scaleFactor);
  }
 
  public void drawBusinessRuleTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(BUSINESS_RULE_TASK_IMAGE, name, graphicInfo, scaleFactor);
  }
 
  public void drawCamelTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(CAMEL_TASK_IMAGE, name, graphicInfo, scaleFactor);
  }
 
  public void drawMuleTask(String name, GraphicInfo graphicInfo, double scaleFactor) {
    drawTask(MULE_TASK_IMAGE, name, graphicInfo, scaleFactor);
  }

  public void drawExpandedSubProcess(String name, GraphicInfo graphicInfo, Boolean isTriggeredByEvent, double scaleFactor) {
    RoundRectangle2D rect = new RoundRectangle2D.Double(graphicInfo.getX(), graphicInfo.getY(),
        graphicInfo.getWidth(), graphicInfo.getHeight(), 8, 8);
   
    // Use different stroke (dashed)
    if (isTriggeredByEvent) {
      Stroke originalStroke = g.getStroke();
      g.setStroke(EVENT_SUBPROCESS_STROKE);
      g.draw(rect);
      g.setStroke(originalStroke);
    } else {
      Paint originalPaint = g.getPaint();
      g.setPaint(SUBPROCESS_BOX_COLOR);
      g.fill(rect);
      g.setPaint(SUBPROCESS_BORDER_COLOR);
      g.draw(rect);
      g.setPaint(originalPaint);
    }

    if (scaleFactor == 1.0 && name != null && !name.isEmpty()) {
      String text = fitTextToWidth(name, (int) graphicInfo.getWidth());
      g.drawString(text, (int) graphicInfo.getX() + 10, (int) graphicInfo.getY() + 15);
    }
  }

  public void drawCollapsedSubProcess(String name, GraphicInfo graphicInfo, Boolean isTriggeredByEvent) {
    drawCollapsedTask(name, graphicInfo, false);
  }

  public void drawCollapsedCallActivity(String name, GraphicInfo graphicInfo) {
    drawCollapsedTask(name, graphicInfo, true);
  }

  protected void drawCollapsedTask(String name, GraphicInfo graphicInfo, boolean thickBorder) {
    // The collapsed marker is now visualized separately
    drawTask(name, graphicInfo, thickBorder);
  }

  public void drawCollapsedMarker(int x, int y, int width, int height) {
    // rectangle
    int rectangleWidth = MARKER_WIDTH;
    int rectangleHeight = MARKER_WIDTH;
    Rectangle rect = new Rectangle(x + (width - rectangleWidth) / 2, y + height - rectangleHeight - 3, rectangleWidth, rectangleHeight);
    g.draw(rect);

    // plus inside rectangle
    Line2D.Double line = new Line2D.Double(rect.getCenterX(), rect.getY() + 2, rect.getCenterX(), rect.getMaxY() - 2);
    g.draw(line);
    line = new Line2D.Double(rect.getMinX() + 2, rect.getCenterY(), rect.getMaxX() - 2, rect.getCenterY());
    g.draw(line);
  }

  public void drawActivityMarkers(int x, int y, int width, int height, boolean multiInstanceSequential, boolean multiInstanceParallel, boolean collapsed) {
    if (collapsed) {
      if (!multiInstanceSequential && !multiInstanceParallel) {
        drawCollapsedMarker(x, y, width, height);
      } else {
        drawCollapsedMarker(x - MARKER_WIDTH / 2 - 2, y, width, height);
        if (multiInstanceSequential) {
          drawMultiInstanceMarker(true, x + MARKER_WIDTH / 2 + 2, y, width, height);
        } else {
          drawMultiInstanceMarker(false, x + MARKER_WIDTH / 2 + 2, y, width, height);
        }
      }
    } else {
      if (multiInstanceSequential) {
        drawMultiInstanceMarker(true, x, y, width, height);
      } else if (multiInstanceParallel) {
        drawMultiInstanceMarker(false, x, y, width, height);
      }
    }
  }

  public void drawGateway(GraphicInfo graphicInfo) {
    Polygon rhombus = new Polygon();
    int x = (int) graphicInfo.getX();
    int y = (int) graphicInfo.getY();
    int width = (int) graphicInfo.getWidth();
    int height = (int) graphicInfo.getHeight();
   
    rhombus.addPoint(x, y + (height / 2));
    rhombus.addPoint(x + (width / 2), y + height);
    rhombus.addPoint(x + width, y + (height / 2));
    rhombus.addPoint(x + (width / 2), y);
    g.draw(rhombus);
  }

  public void drawParallelGateway(GraphicInfo graphicInfo, double scaleFactor) {
    // rhombus
    drawGateway(graphicInfo);
    int x = (int) graphicInfo.getX();
    int y = (int) graphicInfo.getY();
    int width = (int) graphicInfo.getWidth();
    int height = (int) graphicInfo.getHeight();

    if (scaleFactor == 1.0) {
      // plus inside rhombus
      Stroke orginalStroke = g.getStroke();
      g.setStroke(GATEWAY_TYPE_STROKE);
      Line2D.Double line = new Line2D.Double(x + 10, y + height / 2, x + width - 10, y + height / 2); // horizontal
      g.draw(line);
      line = new Line2D.Double(x + width / 2, y + height - 10, x + width / 2, y + 10); // vertical
      g.draw(line);
      g.setStroke(orginalStroke);
    }
  }

  public void drawExclusiveGateway(GraphicInfo graphicInfo, double scaleFactor) {
    // rhombus
    drawGateway(graphicInfo);
    int x = (int) graphicInfo.getX();
    int y = (int) graphicInfo.getY();
    int width = (int) graphicInfo.getWidth();
    int height = (int) graphicInfo.getHeight();

    int quarterWidth = width / 4;
    int quarterHeight = height / 4;

    if (scaleFactor == 1.0) {
      // X inside rhombus
      Stroke orginalStroke = g.getStroke();
      g.setStroke(GATEWAY_TYPE_STROKE);
      Line2D.Double line = new Line2D.Double(x + quarterWidth + 3, y + quarterHeight + 3, x + 3 * quarterWidth - 3, y + 3 * quarterHeight - 3);
      g.draw(line);
      line = new Line2D.Double(x + quarterWidth + 3, y + 3 * quarterHeight - 3, x + 3 * quarterWidth - 3, y + quarterHeight + 3);
      g.draw(line);
      g.setStroke(orginalStroke);
    }
  }

  public void drawInclusiveGateway(GraphicInfo graphicInfo, double scaleFactor) {
    // rhombus
    drawGateway(graphicInfo);
    int x = (int) graphicInfo.getX();
    int y = (int) graphicInfo.getY();
    int width = (int) graphicInfo.getWidth();
    int height = (int) graphicInfo.getHeight();

    int diameter = width / 2;

    if (scaleFactor == 1.0) {
      // circle inside rhombus
      Stroke orginalStroke = g.getStroke();
      g.setStroke(GATEWAY_TYPE_STROKE);
      Ellipse2D.Double circle = new Ellipse2D.Double(((width - diameter) / 2) + x, ((height - diameter) / 2) + y, diameter, diameter);
      g.draw(circle);
      g.setStroke(orginalStroke);
    }
  }
 
  public void drawEventBasedGateway(GraphicInfo graphicInfo, double scaleFactor) {
    // rhombus
    drawGateway(graphicInfo);
   
    if (scaleFactor == 1.0) {
      int x = (int) graphicInfo.getX();
      int y = (int) graphicInfo.getY();
      int width = (int) graphicInfo.getWidth();
      int height = (int) graphicInfo.getHeight();
     
      double scale = .6;
     
      GraphicInfo eventInfo = new GraphicInfo();
      eventInfo.setX(x + width*(1-scale)/2);
      eventInfo.setY(y + height*(1-scale)/2);
      eventInfo.setWidth(width*scale);
      eventInfo.setHeight(height*scale);
      drawCatchingEvent(eventInfo, true, null, "eventGateway", scaleFactor);
     
      double r = width / 6.;
     
      // create pentagon (coords with respect to center)
      int topX = (int)(.95 * r); // top right corner
      int topY = (int)(-.31 * r);
      int bottomX = (int)(.59 * r); // bottom right corner
      int bottomY = (int)(.81 * r);
     
      int[] xPoints = new int[]{ 0, topX, bottomX, -bottomX, -topX };
      int[] yPoints = new int[]{ -(int)r, topY, bottomY, bottomY, topY };
      Polygon pentagon = new Polygon(xPoints, yPoints, 5);
      pentagon.translate(x+width/2, y+width/2);
 
      // draw
      g.drawPolygon(pentagon);
    }
  }

  public void drawMultiInstanceMarker(boolean sequential, int x, int y, int width, int height) {
    int rectangleWidth = MARKER_WIDTH;
    int rectangleHeight = MARKER_WIDTH;
    int lineX = x + (width - rectangleWidth) / 2;
    int lineY = y + height - rectangleHeight - 3;

    Stroke orginalStroke = g.getStroke();
    g.setStroke(MULTI_INSTANCE_STROKE);

    if (sequential) {
      g.draw(new Line2D.Double(lineX, lineY, lineX + rectangleWidth, lineY));
      g.draw(new Line2D.Double(lineX, lineY + rectangleHeight / 2, lineX + rectangleWidth, lineY + rectangleHeight / 2));
      g.draw(new Line2D.Double(lineX, lineY + rectangleHeight, lineX + rectangleWidth, lineY + rectangleHeight));
    } else {
      g.draw(new Line2D.Double(lineX, lineY, lineX, lineY + rectangleHeight));
      g.draw(new Line2D.Double(lineX + rectangleWidth / 2, lineY, lineX + rectangleWidth / 2, lineY + rectangleHeight));
      g.draw(new Line2D.Double(lineX + rectangleWidth, lineY, lineX + rectangleWidth, lineY + rectangleHeight));
    }

    g.setStroke(orginalStroke);
  }

  public void drawHighLight(int x, int y, int width, int height) {
    Paint originalPaint = g.getPaint();
    Stroke originalStroke = g.getStroke();

    g.setPaint(HIGHLIGHT_COLOR);
    g.setStroke(THICK_TASK_BORDER_STROKE);

    RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20);
    g.draw(rect);

    g.setPaint(originalPaint);
    g.setStroke(originalStroke);
  }

  public void drawTextAnnotation(String text, GraphicInfo graphicInfo) {
    int x = (int) graphicInfo.getX();
    int y = (int) graphicInfo.getY();
    int width = (int) graphicInfo.getWidth();
    int height = (int) graphicInfo.getHeight();
   
    Font originalFont = g.getFont();
    Stroke originalStroke = g.getStroke();
   
    g.setFont(ANNOTATION_FONT);
   
    Path2D path = new Path2D.Double();
    x += .5;
    int lineLength = 18;
    path.moveTo(x + lineLength, y);
    path.lineTo(x, y);
    path.lineTo(x, y + height);
    path.lineTo(x + lineLength, y + height);
   
    path.lineTo(x + lineLength, y + height -1);
    path.lineTo(x + 1, y + height -1);
    path.lineTo(x + 1, y + 1);
    path.lineTo(x + lineLength, y + 1);
    path.closePath();
   
    g.draw(path);
   
    int boxWidth = width - (2 * ANNOTATION_TEXT_PADDING);
    int boxHeight = height - (2 * ANNOTATION_TEXT_PADDING);
    int boxX = x + width/2 - boxWidth/2;
    int boxY = y + height/2 - boxHeight/2;
   
    if (text != null && text.isEmpty() == false) {
      drawMultilineAnnotationText(text, boxX, boxY, boxWidth, boxHeight);
    }
   
    // restore originals
    g.setFont(originalFont);
    g.setStroke(originalStroke);
  }
 
  public void drawLabel(String text, GraphicInfo graphicInfo){
    drawLabel(text, graphicInfo, true);
  }
  public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered){
  float interline = 1.0f;
 
    // text
    if (text != null && text.length()>0) {
      Paint originalPaint = g.getPaint();
      Font originalFont = g.getFont();

      g.setPaint(LABEL_COLOR);
      g.setFont(LABEL_FONT);

      int wrapWidth = 100;
      int textY = (int) (graphicInfo.getY() + graphicInfo.getHeight());
     
      // TODO: use drawMultilineText()
      AttributedString as = new AttributedString(text);
      as.addAttribute(TextAttribute.FOREGROUND, g.getPaint());
      as.addAttribute(TextAttribute.FONT, g.getFont());
      AttributedCharacterIterator aci = as.getIterator();
      FontRenderContext frc = new FontRenderContext(null, true, false);
      LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc);
     
      while (lbm.getPosition() < text.length()) {
        TextLayout tl = lbm.nextLayout(wrapWidth);
        textY += tl.getAscent();
        Rectangle2D bb = tl.getBounds();
        int tX = (int) graphicInfo.getX();
        if (centered)
            tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
        tl.draw(g, tX, textY);
        textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
      }
 
      // restore originals
      g.setFont(originalFont);
      g.setPaint(originalPaint);
    }
  }

  /**
   * This method makes coordinates of connection flow better.
   * @param sourceShapeType
   * @param targetShapeType
   * @param sourceGraphicInfo
   * @param targetGraphicInfo
   * @param graphicInfoList
   *
   */
  public List<GraphicInfo> connectionPerfectionizer(SHAPE_TYPE sourceShapeType, SHAPE_TYPE targetShapeType, GraphicInfo sourceGraphicInfo, GraphicInfo targetGraphicInfo, List<GraphicInfo> graphicInfoList) {
    Shape shapeFirst = createShape(sourceShapeType, sourceGraphicInfo);
    Shape shapeLast = createShape(targetShapeType, targetGraphicInfo);

    GraphicInfo graphicInfoFirst = graphicInfoList.get(0);
    GraphicInfo graphicInfoLast = graphicInfoList.get(graphicInfoList.size()-1);
    if (shapeFirst != null) {
      graphicInfoFirst.setX(shapeFirst.getBounds2D().getCenterX());
      graphicInfoFirst.setY(shapeFirst.getBounds2D().getCenterY());
    }
    if (shapeLast != null) {
      graphicInfoLast.setX(shapeLast.getBounds2D().getCenterX());
      graphicInfoLast.setY(shapeLast.getBounds2D().getCenterY());
    }

    Point p = null;
   
    if (shapeFirst != null) {
      Line2D.Double lineFirst = new Line2D.Double(graphicInfoFirst.getX(), graphicInfoFirst.getY(), graphicInfoList.get(1).getX(), graphicInfoList.get(1).getY());
      p = getIntersection(shapeFirst, lineFirst);
      if (p != null) {
        graphicInfoFirst.setX(p.getX());
        graphicInfoFirst.setY(p.getY());
      }
    }

    if (shapeLast != null) {
      Line2D.Double lineLast = new Line2D.Double(graphicInfoLast.getX(), graphicInfoLast.getY(), graphicInfoList.get(graphicInfoList.size()-2).getX(), graphicInfoList.get(graphicInfoList.size()-2).getY());
      p = getIntersection(shapeLast, lineLast);
      if (p != null) {
        graphicInfoLast.setX(p.getX());
        graphicInfoLast.setY(p.getY());
      }
    }

    return graphicInfoList;
  }

  /**
   * This method creates shape by type and coordinates.
   * @param shapeType
   * @param graphicInfo
   * @return Shape
   */
  private static Shape createShape(SHAPE_TYPE shapeType, GraphicInfo graphicInfo) {
    if (SHAPE_TYPE.Rectangle.equals(shapeType)) {
      // source is rectangle
      return new Rectangle2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight());      
    } else if (SHAPE_TYPE.Rhombus.equals(shapeType)) {
      // source is rhombus
      Path2D.Double rhombus = new Path2D.Double();
      rhombus.moveTo(graphicInfo.getX(), graphicInfo.getY() + graphicInfo.getHeight() / 2);
      rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth() / 2, graphicInfo.getY() + graphicInfo.getHeight());
      rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth(), graphicInfo.getY() + graphicInfo.getHeight() / 2);
      rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth() / 2, graphicInfo.getY());
      rhombus.lineTo(graphicInfo.getX(), graphicInfo.getY() + graphicInfo.getHeight() / 2);
      rhombus.closePath();
      return rhombus;
    } else if (SHAPE_TYPE.Ellipse.equals(shapeType)) {
      // source is ellipse
      return new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight());
    } else {
      // unknown source element, just do not correct coordinates
    }
    return null;
  }

  /**
   * This method returns intersection point of shape border and line.
   *
   * @param shape
   * @param line
   * @return Point
   */
  private static Point getIntersection(Shape shape, Line2D.Double line) {
    if (shape instanceof Ellipse2D) {
      return getEllipseIntersection(shape, line);
    } else if (shape instanceof Rectangle2D || shape instanceof Path2D) {
      return getShapeIntersection(shape, line);
    } else {
      // something strange
      return null;
    }
  }

    /**
     * This method calculates ellipse intersection with line
     * @param shape
     *                  Bounds of this shape used to calculate parameters of inscribed into this bounds ellipse.
     * @param line
     * @return Intersection point
     */
  private static Point getEllipseIntersection(Shape shape, Line2D.Double line) {
    double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
    double x = shape.getBounds2D().getWidth()/2 * Math.cos(angle) + shape.getBounds2D().getCenterX();
    double y = shape.getBounds2D().getHeight()/2 * Math.sin(angle) + shape.getBounds2D().getCenterY();
    Point p = new Point();
    p.setLocation(x, y);
    return p;
  }

  /**
   * This method calculates shape intersection with line.
   *
   * @param shape
   * @param line
   * @return Intersection point
   */
  private static Point getShapeIntersection(Shape shape, Line2D.Double line) {
    PathIterator it = shape.getPathIterator(null);
    double[] coords = new double[6];
    double[] pos = new double[2];
    Line2D.Double l = new Line2D.Double();
    while (!it.isDone()) {
      int type = it.currentSegment(coords);
      switch (type) {
      case PathIterator.SEG_MOVETO:
        pos[0] = coords[0];
        pos[1] = coords[1];
        break;
      case PathIterator.SEG_LINETO:
        l = new Line2D.Double(pos[0], pos[1], coords[0], coords[1]);
        if (line.intersectsLine(l)) {
          return getLinesIntersection(line, l);
        }
        pos[0] = coords[0];
        pos[1] = coords[1];
        break;
      case PathIterator.SEG_CLOSE:
        break;
      default:
        // whatever
      }
      it.next();
    }
    return null;
  }

  /**
   * This method calculates intersections of two lines.
   * @param a Line 1
   * @param b Line 2
   * @return Intersection point
   */
  private static Point getLinesIntersection(Line2D a, Line2D b) {
    double d  = (a.getX1()-a.getX2())*(b.getY2()-b.getY1()) - (a.getY1()-a.getY2())*(b.getX2()-b.getX1());
    double da = (a.getX1()-b.getX1())*(b.getY2()-b.getY1()) - (a.getY1()-b.getY1())*(b.getX2()-b.getX1());
    // double db = (a.getX1()-a.getX2())*(a.getY1()-b.getY1()) - (a.getY1()-a.getY2())*(a.getX1()-b.getX1());
    double ta = da/d;
    // double tb = db/d;
    Point p = new Point();
    p.setLocation(a.getX1()+ta*(a.getX2()-a.getX1()), a.getY1()+ta*(a.getY2()-a.getY1()));
    return p;
  }
}
TOP

Related Classes of org.activiti.image.impl.DefaultProcessDiagramCanvas

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.