Example #1
0
  /**
   * Returns a path which is cappedPath at the ends, to prevent it from drawing under the end caps.
   */
  protected BezierPath getCappedPath() {
    if (cappedPath == null) {
      cappedPath = path.clone();
      if (isClosed()) {
        cappedPath.setClosed(true);
      } else {
        if (cappedPath.size() > 1) {
          if (get(START_DECORATION) != null) {
            BezierPath.Node p0 = cappedPath.get(0);
            BezierPath.Node p1 = cappedPath.get(1);
            Point2D.Double pp;
            if ((p0.getMask() & BezierPath.C2_MASK) != 0) {
              pp = p0.getControlPoint(2);
            } else if ((p1.getMask() & BezierPath.C1_MASK) != 0) {
              pp = p1.getControlPoint(1);
            } else {
              pp = p1.getControlPoint(0);
            }
            double radius = get(START_DECORATION).getDecorationRadius(this);
            double lineLength = Geom.length(p0.getControlPoint(0), pp);
            cappedPath.set(
                0, 0, Geom.cap(pp, p0.getControlPoint(0), -Math.min(radius, lineLength)));
          }
          if (get(END_DECORATION) != null) {
            BezierPath.Node p0 = cappedPath.get(cappedPath.size() - 1);
            BezierPath.Node p1 = cappedPath.get(cappedPath.size() - 2);

            Point2D.Double pp;
            if ((p0.getMask() & BezierPath.C1_MASK) != 0) {
              pp = p0.getControlPoint(1);
            } else if ((p1.getMask() & BezierPath.C2_MASK) != 0) {
              pp = p1.getControlPoint(2);
            } else {
              pp = p1.getControlPoint(0);
            }

            double radius = get(END_DECORATION).getDecorationRadius(this);
            double lineLength = Geom.length(p0.getControlPoint(0), pp);
            cappedPath.set(
                cappedPath.size() - 1,
                0,
                Geom.cap(pp, p0.getControlPoint(0), -Math.min(radius, lineLength)));
          }
          cappedPath.invalidatePath();
        }
      }
    }
    return cappedPath;
  }
Example #2
0
 /**
  * Finds a control point index. Returns -1 if no control point could be found. FIXME - Move this
  * to BezierPath
  */
 public int findNode(Point2D.Double p) {
   BezierPath tp = path;
   for (int i = 0; i < tp.size(); i++) {
     BezierPath.Node p2 = tp.get(i);
     if (p2.x[0] == p.x && p2.y[0] == p.y) {
       return i;
     }
   }
   return -1;
 }
Example #3
0
 @Override
 public boolean contains(Point2D.Double p) {
   double tolerance = Math.max(2f, AttributeKeys.getStrokeTotalWidth(this) / 2d);
   if (isClosed() || get(FILL_COLOR) != null && get(UNCLOSED_PATH_FILLED)) {
     if (path.contains(p)) {
       return true;
     }
     double grow = AttributeKeys.getPerpendicularHitGrowth(this) * 2d;
     GrowStroke gs =
         new GrowStroke(grow, AttributeKeys.getStrokeTotalWidth(this) * get(STROKE_MITER_LIMIT));
     if (gs.createStrokedShape(path).contains(p)) {
       return true;
     } else {
       if (isClosed()) {
         return false;
       }
     }
   }
   if (!isClosed()) {
     if (getCappedPath().outlineContains(p, tolerance)) {
       return true;
     }
     if (get(START_DECORATION) != null) {
       BezierPath cp = getCappedPath();
       Point2D.Double p1 = path.get(0, 0);
       Point2D.Double p2 = cp.get(0, 0);
       // FIXME - Check here, if caps path contains the point
       if (Geom.lineContainsPoint(p1.x, p1.y, p2.x, p2.y, p.x, p.y, tolerance)) {
         return true;
       }
     }
     if (get(END_DECORATION) != null) {
       BezierPath cp = getCappedPath();
       Point2D.Double p1 = path.get(path.size() - 1, 0);
       Point2D.Double p2 = cp.get(path.size() - 1, 0);
       // FIXME - Check here, if caps path contains the point
       if (Geom.lineContainsPoint(p1.x, p1.y, p2.x, p2.y, p.x, p.y, tolerance)) {
         return true;
       }
     }
   }
   return false;
 }
Example #4
0
 protected void drawCaps(Graphics2D g) {
   if (getNodeCount() > 1) {
     if (get(START_DECORATION) != null) {
       BezierPath cp = getCappedPath();
       Point2D.Double p1 = path.get(0, 0);
       Point2D.Double p2 = cp.get(0, 0);
       if (p2.equals(p1)) {
         p2 = path.get(1, 0);
       }
       get(START_DECORATION).draw(g, this, p1, p2);
     }
     if (get(END_DECORATION) != null) {
       BezierPath cp = getCappedPath();
       Point2D.Double p1 = path.get(path.size() - 1, 0);
       Point2D.Double p2 = cp.get(path.size() - 1, 0);
       if (p2.equals(p1)) {
         p2 = path.get(path.size() - 2, 0);
       }
       get(END_DECORATION).draw(g, this, p1, p2);
     }
   }
 }
Example #5
0
 @Override
 public Collection<Handle> createHandles(int detailLevel) {
   LinkedList<Handle> handles = new LinkedList<Handle>();
   switch (detailLevel % 2) {
     case -1: // Mouse hover handles
       handles.add(new BezierOutlineHandle(this, true));
       break;
     case 0:
       handles.add(new BezierOutlineHandle(this));
       for (int i = 0, n = path.size(); i < n; i++) {
         handles.add(new BezierNodeHandle(this, i));
       }
       break;
     case 1:
       TransformHandleKit.addTransformHandles(this, handles);
       handles.add(new BezierScaleHandle(this));
       break;
   }
   return handles;
 }
Example #6
0
  public static List<BezierPath> fromPathData(String str) throws IOException {
    LinkedList<BezierPath> paths = new LinkedList<BezierPath>();

    BezierPath path = null;
    Point2D.Double p = new Point2D.Double();
    Point2D.Double c1 = new Point2D.Double();
    Point2D.Double c2 = new Point2D.Double();
    StreamTokenizer tt = new StreamTokenizer(new StringReader(str));
    tt.resetSyntax();
    tt.parseNumbers();
    tt.whitespaceChars(0, ' ');
    tt.whitespaceChars(',', ',');

    char nextCommand = 'M';
    char command = 'M';
    while (tt.nextToken() != StreamTokenizer.TT_EOF) {
      if (tt.ttype > 0) {
        command = (char) tt.ttype;
      } else {
        command = nextCommand;
        tt.pushBack();
      }

      BezierPath.Node node;
      switch (command) {
          // moveto
        case 'M':
          if (path != null) {
            paths.add(path);
          }
          path = new BezierPath();

          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y = tt.nval;
          path.moveTo(p.x, p.y);
          nextCommand = 'L';
          break;
        case 'm':
          if (path != null) {
            paths.add(path);
          }
          path = new BezierPath();

          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x += tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y += tt.nval;
          path.moveTo(p.x, p.y);
          nextCommand = 'l';

          // close path
          break;
        case 'Z':
        case 'z':
          p.x = path.get(0).x[0];
          p.y = path.get(0).y[0];
          path.setClosed(true);

          // lineto
          break;
        case 'L':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y = tt.nval;
          path.lineTo(p.x, p.y);
          nextCommand = 'L';

          break;
        case 'l':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x += tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y += tt.nval;
          path.lineTo(p.x, p.y);
          nextCommand = 'l';

          break;
        case 'H':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x = tt.nval;
          path.lineTo(p.x, p.y);
          nextCommand = 'H';

          break;
        case 'h':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x += tt.nval;
          path.lineTo(p.x, p.y);
          nextCommand = 'h';

          break;
        case 'V':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y = tt.nval;
          path.lineTo(p.x, p.y);
          nextCommand = 'V';

          break;
        case 'v':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y += tt.nval;
          path.lineTo(p.x, p.y);
          nextCommand = 'v';

          // curveto
          break;
        case 'C':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c1.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c1.y = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c2.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c2.y = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y = tt.nval;
          path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
          nextCommand = 'C';

          break;
        case 'c':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c1.x = p.x + tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c1.y = p.y + tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c2.x = p.x + tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c2.y = p.y + tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x += tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y += tt.nval;
          path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
          nextCommand = 'c';

          break;
        case 'S':
          node = path.get(path.size() - 1);
          c1.x = node.x[0] * 2d - node.x[1];
          c1.y = node.y[0] * 2d - node.y[1];
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c2.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c2.y = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y = tt.nval;
          path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
          nextCommand = 'S';

          break;
        case 's':
          node = path.get(path.size() - 1);
          c1.x = node.x[0] * 2d - node.x[1];
          c1.y = node.y[0] * 2d - node.y[1];
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c2.x = p.x + tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c2.y = p.y + tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x += tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y += tt.nval;
          path.curveTo(c1.x, c1.y, c2.x, c2.y, p.x, p.y);
          nextCommand = 's';

          // quadto
          break;
        case 'Q':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c1.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c1.y = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y = tt.nval;
          path.quadTo(c1.x, c1.y, p.x, p.y);
          nextCommand = 'Q';

          break;
        case 'q':
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c1.x = p.x + tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          c1.y = p.y + tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x += tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y += tt.nval;
          path.quadTo(c1.x, c1.y, p.x, p.y);
          nextCommand = 'q';

          break;
        case 'T':
          node = path.get(path.size() - 1);
          c1.x = node.x[0] * 2d - node.x[1];
          c1.y = node.y[0] * 2d - node.y[1];
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x = tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y = tt.nval;
          path.quadTo(c1.x, c1.y, p.x, p.y);
          nextCommand = 'T';

          break;
        case 't':
          node = path.get(path.size() - 1);
          c1.x = node.x[0] * 2d - node.x[1];
          c1.y = node.y[0] * 2d - node.y[1];
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.x += tt.nval;
          if (tt.nextToken() != StreamTokenizer.TT_NUMBER) throw new IOException("Number expected");
          p.y += tt.nval;
          path.quadTo(c1.x, c1.y, p.x, p.y);
          nextCommand = 's';

          break;
        default:
          throw new IOException("Illegal command: " + command);
      }
    }
    if (path != null) {
      paths.add(path);
    }
    return paths;
  }
Example #7
0
  public static String toPathData(BezierPath path) {
    StringBuilder buf = new StringBuilder();

    if (path.size() == 0) {
      // nothing to do
    } else if (path.size() == 1) {
      BezierPath.Node current = path.get(0);
      buf.append("M ");
      buf.append(current.x[0]);
      buf.append(' ');
      buf.append(current.y[0]);
      buf.append(" L ");
      buf.append(current.x[0]);
      buf.append(' ');
      buf.append(current.y[0] + 1);
    } else {
      BezierPath.Node previous;
      BezierPath.Node current;

      previous = current = path.get(0);
      buf.append("M ");
      buf.append(current.x[0]);
      buf.append(' ');
      buf.append(current.y[0]);
      for (int i = 1, n = path.size(); i < n; i++) {
        previous = current;
        current = path.get(i);

        if ((previous.mask & BezierPath.C2_MASK) == 0) {
          if ((current.mask & BezierPath.C1_MASK) == 0) {
            buf.append(" L ");
            buf.append(current.x[0]);
            buf.append(' ');
            buf.append(current.y[0]);
          } else {
            buf.append(" Q ");
            buf.append(current.x[1]);
            buf.append(' ');
            buf.append(current.y[1]);
            buf.append(' ');
            buf.append(current.x[0]);
            buf.append(' ');
            buf.append(current.y[0]);
          }
        } else {
          if ((current.mask & BezierPath.C1_MASK) == 0) {
            buf.append(" Q ");
            buf.append(current.x[2]);
            buf.append(' ');
            buf.append(current.y[2]);
            buf.append(' ');
            buf.append(current.x[0]);
            buf.append(' ');
            buf.append(current.y[0]);
          } else {
            buf.append(" C ");
            buf.append(previous.x[2]);
            buf.append(' ');
            buf.append(previous.y[2]);
            buf.append(' ');
            buf.append(current.x[1]);
            buf.append(' ');
            buf.append(current.y[1]);
            buf.append(' ');
            buf.append(current.x[0]);
            buf.append(' ');
            buf.append(current.y[0]);
          }
        }
      }
      if (path.isClosed()) {
        if (path.size() > 1) {
          previous = path.get(path.size() - 1);
          current = path.get(0);

          if ((previous.mask & BezierPath.C2_MASK) == 0) {
            if ((current.mask & BezierPath.C1_MASK) == 0) {
              buf.append(" L ");
              buf.append(current.x[0]);
              buf.append(' ');
              buf.append(current.y[0]);
            } else {
              buf.append(" Q ");
              buf.append(current.x[1]);
              buf.append(' ');
              buf.append(current.y[1]);
              buf.append(' ');
              buf.append(current.x[0]);
              buf.append(' ');
              buf.append(current.y[0]);
            }
          } else {
            if ((current.mask & BezierPath.C1_MASK) == 0) {
              buf.append(" Q ");
              buf.append(previous.x[2]);
              buf.append(' ');
              buf.append(previous.y[2]);
              buf.append(' ');
              buf.append(current.x[0]);
              buf.append(' ');
              buf.append(current.y[0]);
            } else {
              buf.append(" C ");
              buf.append(previous.x[2]);
              buf.append(' ');
              buf.append(previous.y[2]);
              buf.append(' ');
              buf.append(current.x[1]);
              buf.append(' ');
              buf.append(current.y[1]);
              buf.append(' ');
              buf.append(current.x[0]);
              buf.append(' ');
              buf.append(current.y[0]);
            }
          }
        }
        buf.append(" Z");
      }
    }
    return buf.toString();
  }
Example #8
0
 /** Gets the node count. */
 public int getNodeCount() {
   return path.size();
 }
  public void lineout(ConnectionFigure figure) {
    BezierPath path = ((LineConnectionFigure) figure).getBezierPath();
    Connector start = figure.getStartConnector();
    Connector end = figure.getEndConnector();
    if (start == null || end == null || path == null) {
      return;
    }

    // Special treatment if the connection connects the same figure
    if (figure.getStartFigure() == figure.getEndFigure()) {
      // Ensure path has exactly 4 nodes
      while (path.size() < 4) {
        path.add(1, new BezierPath.Node(0, 0));
      }
      while (path.size() > 4) {
        path.remove(1);
      }
      Point2D.Double sp = start.findStart(figure);
      Point2D.Double ep = end.findEnd(figure);
      Rectangle2D.Double sb = start.getBounds();
      Rectangle2D.Double eb = end.getBounds();
      int soutcode = sb.outcode(sp);
      if (soutcode == 0) {
        soutcode = Geom.outcode(sb, eb);
      }
      int eoutcode = eb.outcode(ep);
      if (eoutcode == 0) {
        eoutcode = Geom.outcode(sb, eb);
      }

      path.get(0).moveTo(sp);
      path.get(path.size() - 1).moveTo(ep);

      switch (soutcode) {
        case Geom.OUT_TOP:
          eoutcode = Geom.OUT_LEFT;
          break;
        case Geom.OUT_RIGHT:
          eoutcode = Geom.OUT_TOP;
          break;
        case Geom.OUT_BOTTOM:
          eoutcode = Geom.OUT_RIGHT;
          break;
        case Geom.OUT_LEFT:
          eoutcode = Geom.OUT_BOTTOM;
          break;
        default:
          eoutcode = Geom.OUT_TOP;
          soutcode = Geom.OUT_RIGHT;
          break;
      }
      // path.get(0).moveTo(sp.x + shoulderSize, sp.y);
      path.get(0).mask = BezierPath.C2_MASK;
      if ((soutcode & Geom.OUT_RIGHT) != 0) {
        path.get(0).x[2] = sp.x + shoulderSize;
        path.get(0).y[2] = sp.y;
      } else if ((soutcode & Geom.OUT_LEFT) != 0) {
        path.get(0).x[2] = sp.x - shoulderSize;
        path.get(0).y[2] = sp.y;
      } else if ((soutcode & Geom.OUT_BOTTOM) != 0) {
        path.get(0).x[2] = sp.x;
        path.get(0).y[2] = sp.y + shoulderSize;
      } else {
        path.get(0).x[2] = sp.x;
        path.get(0).y[2] = sp.y - shoulderSize;
      }
      path.get(1).mask = BezierPath.C2_MASK;
      path.get(1).moveTo(sp.x + shoulderSize, (sp.y + ep.y) / 2);
      path.get(1).x[2] = sp.x + shoulderSize;
      path.get(1).y[2] = ep.y - shoulderSize;
      path.get(2).mask = BezierPath.C1_MASK;
      path.get(2).moveTo((sp.x + ep.x) / 2, ep.y - shoulderSize);
      path.get(2).x[1] = sp.x + shoulderSize;
      path.get(2).y[1] = ep.y - shoulderSize;
      path.get(3).mask = BezierPath.C1_MASK;
      if ((eoutcode & Geom.OUT_RIGHT) != 0) {
        path.get(3).x[1] = ep.x + shoulderSize;
        path.get(3).y[1] = ep.y;
      } else if ((eoutcode & Geom.OUT_LEFT) != 0) {
        path.get(3).x[1] = ep.x - shoulderSize;
        path.get(3).y[1] = ep.y;
      } else if ((eoutcode & Geom.OUT_BOTTOM) != 0) {
        path.get(3).x[1] = ep.x;
        path.get(3).y[1] = ep.y + shoulderSize;
      } else {
        path.get(3).x[1] = ep.x;
        path.get(3).y[1] = ep.y - shoulderSize;
      }
    } else {
      Point2D.Double sp = start.findStart(figure);
      Point2D.Double ep = end.findEnd(figure);

      path.clear();

      if (sp.x == ep.x || sp.y == ep.y) {
        path.add(new BezierPath.Node(ep.x, ep.y));
      } else {
        Rectangle2D.Double sb = start.getBounds();
        sb.x += 5d;
        sb.y += 5d;
        sb.width -= 10d;
        sb.height -= 10d;
        Rectangle2D.Double eb = end.getBounds();
        eb.x += 5d;
        eb.y += 5d;
        eb.width -= 10d;
        eb.height -= 10d;

        int soutcode = sb.outcode(sp);
        if (soutcode == 0) {
          soutcode = Geom.outcode(sb, eb);
        }
        int eoutcode = eb.outcode(ep);
        if (eoutcode == 0) {
          eoutcode = Geom.outcode(eb, sb);
        }

        if ((soutcode & (Geom.OUT_TOP | Geom.OUT_BOTTOM)) != 0
            && (eoutcode & (Geom.OUT_TOP | Geom.OUT_BOTTOM)) != 0) {
          path.add(
              new BezierPath.Node(
                  BezierPath.C2_MASK, sp.x, sp.y, sp.x, sp.y, sp.x, (sp.y + ep.y) / 2));
          path.add(
              new BezierPath.Node(
                  BezierPath.C1_MASK, ep.x, ep.y, ep.x, (sp.y + ep.y) / 2, ep.x, ep.y));
        } else if ((soutcode & (Geom.OUT_LEFT | Geom.OUT_RIGHT)) != 0
            && (eoutcode & (Geom.OUT_LEFT | Geom.OUT_RIGHT)) != 0) {
          path.add(
              new BezierPath.Node(
                  BezierPath.C2_MASK, sp.x, sp.y, sp.x, sp.y, (sp.x + ep.x) / 2, sp.y));
          path.add(
              new BezierPath.Node(
                  BezierPath.C1_MASK, ep.x, ep.y, (sp.x + ep.x) / 2, ep.y, ep.x, ep.y));
        } else if (soutcode == Geom.OUT_BOTTOM || soutcode == Geom.OUT_TOP) {
          path.add(new BezierPath.Node(BezierPath.C2_MASK, sp.x, sp.y, sp.x, sp.y, sp.x, ep.y));
          path.add(new BezierPath.Node(ep.x, ep.y));
        } else {
          path.add(new BezierPath.Node(BezierPath.C2_MASK, sp.x, sp.y, sp.x, sp.y, ep.x, sp.y));
          path.add(new BezierPath.Node(ep.x, ep.y));
        }
      }
    }

    path.invalidatePath();
  }