content: package com.ruoyi.flowable.flow;
  
  import org.flowable.bpmn.model.AssociationDirection;
  import org.flowable.bpmn.model.GraphicInfo;
  import org.flowable.image.impl.DefaultProcessDiagramCanvas;
  import org.flowable.image.util.ReflectUtil;
  
  import javax.imageio.ImageIO;
  import java.awt.*;
  import java.awt.font.FontRenderContext;
  import java.awt.font.LineBreakMeasurer;
  import java.awt.font.TextAttribute;
  import java.awt.font.TextLayout;
  import java.awt.geom.Ellipse2D;
  import java.awt.geom.Rectangle2D;
  import java.awt.geom.RoundRectangle2D;
  import java.awt.image.BufferedImage;
  import java.io.IOException;
  import java.text.AttributedCharacterIterator;
  import java.text.AttributedString;
  
  /**
   * @author XuanXuan
   * @date 2021/4/4 23:58
   */
  public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas {
      //定义走过流程连线颜色为绿色
      protected static Color HIGHLIGHT_SequenceFlow_COLOR = Color.GREEN;
      //设置未走过流程的连接线颜色
      protected static Color CONNECTION_COLOR = Color.BLACK;
      //设置flows连接线字体颜色red
      protected static Color LABEL_COLOR = new Color(0, 0, 0);
      //高亮显示task框颜色
      protected static Color HIGHLIGHT_COLOR = Color.GREEN;
      protected static Color HIGHLIGHT_COLOR1 = Color.RED;
  
      public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) {
          super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader);
          this.initialize(imageType);
      }
  
      /**
       * 重写绘制连线的方式,设置绘制颜色
       * @param xPoints
       * @param yPoints
       * @param conditional
       * @param isDefault
       * @param connectionType
       * @param associationDirection
       * @param highLighted
       * @param scaleFactor
       */
      @Override
      public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType, AssociationDirection associationDirection, boolean highLighted, double scaleFactor) {
          Paint originalPaint = this.g.getPaint();
          Stroke originalStroke = this.g.getStroke();
          this.g.setPaint(CONNECTION_COLOR);
          if (connectionType.equals("association")) {
              this.g.setStroke(ASSOCIATION_STROKE);
          } else if (highLighted) {
              this.g.setPaint(HIGHLIGHT_SequenceFlow_COLOR);
              this.g.setStroke(HIGHLIGHT_FLOW_STROKE);
          }
  
          for (int i = 1; i < xPoints.length; ++i) {
              int sourceX = xPoints[i - 1];
              int sourceY = yPoints[i - 1];
              int targetX = xPoints[i];
              int targetY = yPoints[i];
              java.awt.geom.Line2D.Double line = new java.awt.geom.Line2D.Double((double) sourceX, (double) sourceY, (double) targetX, (double) targetY);
              this.g.draw(line);
          }
  
          java.awt.geom.Line2D.Double line;
          if (isDefault) {
              line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], (double) xPoints[1], (double) yPoints[1]);
              this.drawDefaultSequenceFlowIndicator(line, scaleFactor);
          }
  
          if (conditional) {
              line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], (double) xPoints[1], (double) yPoints[1]);
              this.drawConditionalSequenceFlowIndicator(line, scaleFactor);
          }
  
          if (associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) {
              line = new java.awt.geom.Line2D.Double((double) xPoints[xPoints.length - 2], (double) yPoints[xPoints.length - 2], (double) xPoints[xPoints.length - 1], (double) yPoints[xPoints.length - 1]);
              this.drawArrowHead(line, scaleFactor);
          }
  
          if (associationDirection.equals(AssociationDirection.BOTH)) {
              line = new java.awt.geom.Line2D.Double((double) xPoints[1], (double) yPoints[1], (double) xPoints[0], (double) yPoints[0]);
              this.drawArrowHead(line, scaleFactor);
          }
  
          this.g.setPaint(originalPaint);
          this.g.setStroke(originalStroke);
      }
  
      /**
       * 设置字体大小图标颜色
       * @param imageType
       */
      @Override
      public void initialize(String imageType) {
          if ("png".equalsIgnoreCase(imageType)) {
              this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 2);
          } else {
              this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 1);
          }
  
          this.g = this.processDiagram.createGraphics();
          if (!"png".equalsIgnoreCase(imageType)) {
              this.g.setBackground(new Color(255, 255, 255, 0));
              this.g.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
          }
  
          this.g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
          //修改图标颜色，修改图标字体大小
          this.g.setPaint(Color.black);
          Font font = new Font(this.activityFontName, 10, 14);
          this.g.setFont(font);
          this.fontMetrics = this.g.getFontMetrics();
          //修改连接线字体大小
          LABEL_FONT = new Font(this.labelFontName, 10, 15);
          ANNOTATION_FONT = new Font(this.annotationFontName, 0, 11);
  
          try {
              USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/userTask.png", this.customClassLoader));
              SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png", this.customClassLoader));
              SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png", this.customClassLoader));
              RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png", this.customClassLoader));
              SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/sendTask.png", this.customClassLoader));
              MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/manualTask.png", this.customClassLoader));
              BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png", this.customClassLoader));
              SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/shellTask.png", this.customClassLoader));
              DMN_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/dmnTask.png", this.customClassLoader));
              CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/camelTask.png", this.customClassLoader));
              MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/muleTask.png", this.customClassLoader));
              HTTP_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/httpTask.png", this.customClassLoader));
              TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", this.customClassLoader));
              COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png", this.customClassLoader));
              COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/compensate.png", this.customClassLoader));
              ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error-throw.png", this.customClassLoader));
              ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/error.png", this.customClassLoader));
              MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", this.customClassLoader));
              MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message.png", this.customClassLoader));
              SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", this.customClassLoader));
              SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal.png", this.customClassLoader));
          } catch (IOException var4) {
              LOGGER.warn("Could not load image for process diagram creation: {}", var4.getMessage());
          }
  
      }
  
      /**
       * 设置连接线字体
       * @param text
       * @param graphicInfo
       * @param centered
       */
      @Override
      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();
  
              // 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();
                  double tX = graphicInfo.getX();
  
                  if (centered) {
                      tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2);
                  }
                  tl.draw(g, (float) tX, textY);
                  textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent();
              }
  
              // restore originals
              g.setFont(originalFont);
              g.setPaint(originalPaint);
          }
      }
  
      /**
       * 高亮显示task框完成的
       * @param x
       * @param y
       * @param width
       * @param height
       */
      @Override
      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);
      }
  
      /**
       * 自定义task框当前的位置
       * @param x
       * @param y
       * @param width
       * @param height
       */
      public void drawHighLightNow(int x, int y, int width, int height) {
          Paint originalPaint = g.getPaint();
          Stroke originalStroke = g.getStroke();
  
          g.setPaint(HIGHLIGHT_COLOR1);
          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);
      }
  
      /**
       * 自定义结束节点
       * @param x
       * @param y
       * @param width
       * @param height
       */
      public void drawHighLightEnd(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);
      }
  
      /**
       * task框自定义文字
       * @param name
       * @param graphicInfo
       * @param thickBorder
       * @param scaleFactor
       */
      @Override
      protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder, double scaleFactor) {
  
          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 (scaleFactor == 1.0 && 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 static Color EVENT_COLOR = new Color(255, 255, 255);
  
      /**
       * 重写开始事件
       * @param graphicInfo
       * @param image
       * @param scaleFactor
       */
      @Override
      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) {
              // calculate coordinates to center image
              int imageX = (int) Math.round(graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (image.getWidth() / (2 * scaleFactor)));
              int imageY = (int) Math.round(graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (image.getHeight() / (2 * scaleFactor)));
              g.drawImage(image, imageX, imageY,
                      (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), null);
          }
  
      }
  
      /**
       * 重写结束事件
       * @param graphicInfo
       * @param scaleFactor
       */
      @Override
      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);
  //        g.setPaint(HIGHLIGHT_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);
      }
  }
  