You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-dev@xmlgraphics.apache.org by vh...@apache.org on 2001/02/06 08:05:06 UTC

cvs commit: xml-batik/sources/org/apache/batik/gvt MarkerShapePainter.java

vhardy      01/02/05 23:05:05

  Added:       sources/org/apache/batik/gvt MarkerShapePainter.java
  Log:
  Forgot to add this previously.
  
  Revision  Changes    Path
  1.1                  xml-batik/sources/org/apache/batik/gvt/MarkerShapePainter.java
  
  Index: MarkerShapePainter.java
  ===================================================================
  /*****************************************************************************
   * Copyright (C) The Apache Software Foundation. All rights reserved.        *
   * ------------------------------------------------------------------------- *
   * This software is published under the terms of the Apache Software License *
   * version 1.1, a copy of which has been included with this distribution in  *
   * the LICENSE file.                                                         *
   *****************************************************************************/
  
  package org.apache.batik.gvt;
  
  import java.awt.Shape;
  import java.awt.Graphics2D;
  import java.awt.geom.AffineTransform;
  import java.awt.geom.Point2D;
  import java.awt.geom.Rectangle2D;
  import java.awt.geom.PathIterator;
  
  import java.util.List;
  import java.util.Vector;
  
  /**
   * A shape painter that can be used to paint markers on a shape.
   *
   * @author <a href="vincent.hardy@eng.sun.com">Vincent Hardy</a>
   * @version $Id: MarkerShapePainter.java,v 1.1 2001/02/06 07:05:04 vhardy Exp $
   */
  public class MarkerShapePainter implements ShapePainter {
      /** The Shape to be painted */
      protected Shape shape;
  
      /**
       * Constructs a new <tt>FillShapePainter</tt> that can be used to fill
       * a <tt>Shape</tt>.
       *
       * @param shape Shape to be painted by this painter. Should not be null
       */
      public MarkerShapePainter(Shape shape) {
          if(shape == null){
              throw new IllegalArgumentException();
          }
  
          this.shape = shape;
      }
  
      /**
       * Start Marker
       */
      private Marker startMarker;
      
      /**
       * Start Marker Proxy
       */
      private ProxyGraphicsNode startMarkerProxy;
  
      /**
       * Middle Marker
       */
      private Marker middleMarker;
  
      /**
       * Middle Marker Proxy
       */
      private ProxyGraphicsNode middleMarkerProxies[];
  
      /**
       * End Marker
       */
      private Marker endMarker;
  
      /**
       * End Marker Proxy
       */
      private ProxyGraphicsNode endMarkerProxy;
  
      /**
       * Internal Cache: Primitive bounds
       */
      private Rectangle2D dPrimitiveBounds;
  
      /**
       * Internal Cache: Geometry bounds
       */
      private Rectangle2D dGeometryBounds;
  
      /**
       * Contains the various marker proxies
       */
      private CompositeGraphicsNode markerGroup = new CompositeGraphicsNode();
  
      public void setStartMarker(Marker startMarker){
          this.startMarker = startMarker;
          this.startMarkerProxy = null;
          buildMarkerGroup();
      }
  
      public void setMiddleMarker(Marker middleMarker){
          this.middleMarker = middleMarker;
          this.middleMarkerProxies = null;
          buildMarkerGroup();
      }
  
      public void setEndMarker(Marker endMarker){
          this.endMarker = endMarker;
          this.endMarkerProxy = null;
          buildMarkerGroup();
      }
  
      public Marker getStartMarker(){
          return startMarker;
      }
  
      public Marker getMiddleMarker(){
          return middleMarker;
      }
  
      public Marker getEndMarker(){
          return endMarker;
      }
  
      /**
       * Builds a new marker group with the current set of 
       * markers
       */
      private void buildMarkerGroup(){
          if(startMarker != null && startMarkerProxy == null){
              startMarkerProxy = buildStartMarkerProxy();
          }
  
          if(middleMarker != null && middleMarkerProxies == null){
              middleMarkerProxies = buildMiddleMarkerProxies();
          }
  
          if(endMarker != null && endMarkerProxy == null){
              endMarkerProxy = buildEndMarkerProxy();
          }
  
          CompositeGraphicsNode group = new CompositeGraphicsNode();
          List children = group.getChildren();
          if(startMarkerProxy != null){
              children.add(startMarkerProxy);
          }
  
          if(middleMarkerProxies != null){
              for(int i=0; i<middleMarkerProxies.length; i++){
                  children.add(middleMarkerProxies[i]);
              }
          }
  
          if(endMarkerProxy != null){
              children.add(endMarkerProxy);
          }
  
          markerGroup = group;
      }
  
      /**
       * Builds a proxy <tt>GraphicsNode</tt> for the input 
       * <tt>Marker</tt> to be drawn at the start position
       */
      public ProxyGraphicsNode buildStartMarkerProxy(){
          PathIterator iter = getShape().getPathIterator(null);
  
          // Get initial point on the path
          double coords[] = new double[6];
          int segType = 0;
  
          if(iter.isDone()){
              return null;
          }
  
          segType = iter.currentSegment(coords);
          if(segType != iter.SEG_MOVETO){
              return null;
          }
          iter.next();
  
          Point2D markerPosition 
              = new Point2D.Double(coords[0],
                                   coords[1]);
  
          // If the marker's orient property is NaN,
          // the slope needs to be computed
          double rotation = startMarker.getOrient();
          if(Double.isNaN(rotation)){
              if(!iter.isDone()){
                  double next[] = new double[6];
                  int nextSegType = 0;
                  nextSegType = iter.currentSegment(next);
                  if(nextSegType == PathIterator.SEG_CLOSE){
                      nextSegType = PathIterator.SEG_LINETO;
                      next[0] = coords[0];
                      next[1] = coords[1];
                  }
                  rotation = computeRotation((double[])null, 0,  // no previous seg.
                                             coords, segType,    // segment ending on start point
                                             next, nextSegType); // segment out of start point
                  
              }
          }
          
          // Now, compute the marker's proxy transform
          AffineTransform markerTxf =
              computeMarkerTransform(startMarker,
                                     markerPosition,
                                     rotation);
                                     
          ProxyGraphicsNode gn 
              = new ProxyGraphicsNode();
  
          gn.setSource(startMarker.getMarkerNode());
          gn.setTransform(markerTxf);
  
          return gn;
      }
  
      /**
       * Builds a proxy <tt>GraphicsNode</tt> for the input 
       * <tt>Marker</tt> to be drawn at the end position
       */
      public ProxyGraphicsNode buildEndMarkerProxy(){
          PathIterator iter = getShape().getPathIterator(null);
  
          int nPoints = 0;
  
          // Get first point, in case the last segment on the
          // path is a close
          if(iter.isDone()){
              return null;
          }
  
          double coords[] = new double[6];
          double moveTo[] = new double[2];
          int segType = 0;
          segType = iter.currentSegment(coords);
          if(segType != iter.SEG_MOVETO){
              return null;
          }
          nPoints++;
          moveTo[0] = coords[0];
          moveTo[1] = coords[1];
  
          iter.next();
          
          // Now, get the last two points on the path
          double[] lastButOne = new double[6];
          double[] last = {coords[0], coords[1], coords[2],
                           coords[3], coords[4], coords[5] }, tmp = null;
          int lastSegType = segType;
          int lastButOneSegType = 0;
          while(!iter.isDone()){
              tmp = lastButOne;
              lastButOne = last;
              last = tmp;
              lastButOneSegType = lastSegType;
  
              lastSegType = iter.currentSegment(last);
  
              if(lastSegType == PathIterator.SEG_MOVETO){
                  moveTo[0] = last[0];
                  moveTo[1] = last[1];
              } else if(lastSegType == PathIterator.SEG_CLOSE){
                  lastSegType = PathIterator.SEG_LINETO;
                  last[0] = moveTo[0];
                  last[1] = moveTo[1];
              }
  
              iter.next();
              nPoints++;
          }
  
          if (nPoints < 2){
              return null;
          }
  
          // Turn the last segment into a position
          Point2D markerPosition = 
              getSegmentTerminatingPoint(last, lastSegType);
  
          // If the marker's orient property is NaN,
          // the slope needs to be computed
          double rotation = endMarker.getOrient();
          if(Double.isNaN(rotation)){
              rotation = computeRotation(lastButOne, 
                                         lastButOneSegType, 
                                         last, lastSegType,
                                         null, 0);
          }
  
          // Now, compute the marker's proxy transform
          AffineTransform markerTxf =
              computeMarkerTransform(endMarker,
                                     markerPosition,
                                     rotation);
                                     
          ProxyGraphicsNode gn 
              = new ProxyGraphicsNode();
  
          gn.setSource(endMarker.getMarkerNode());
          gn.setTransform(markerTxf);
  
          return gn;
      }
  
      /**
       * Extracts the terminating point, depending on the segment type.
       */
      private final Point2D getSegmentTerminatingPoint(double coords[], int segType){
          switch(segType){
          case PathIterator.SEG_CUBICTO:
              return new Point2D.Double(coords[4], coords[5]);
          case PathIterator.SEG_LINETO:
              return new Point2D.Double(coords[0], coords[1]);
          case PathIterator.SEG_MOVETO:
              return new Point2D.Double(coords[0], coords[1]);
          case PathIterator.SEG_QUADTO:
              return new Point2D.Double(coords[2], coords[3]);
          case PathIterator.SEG_CLOSE:
          default:
              throw new Error(); 
              // Should never happen: close segments are 
              // replaced with lineTo
          }
      }
  
      /**
       * Builds a proxy <tt>GraphicsNode</tt> for the input 
       * <tt>Marker</tt> to be drawn at the middle positions
       */
      public ProxyGraphicsNode[] buildMiddleMarkerProxies(){
          PathIterator iter = getShape().getPathIterator(null);
  
          double[] prev = new double[6];
          double[] cur = new double[6];
          double[] next = new double[6], tmp = null;
          int prevSegType = 0, curSegType = 0, nextSegType = 0;
  
          // Get the first three points on the path
          if(iter.isDone()){
              return null;
          }
  
          prevSegType = iter.currentSegment(prev);
  
          double[] moveTo = new double[2];
  
          if(prevSegType != PathIterator.SEG_MOVETO){
              return null;
          }
  
          moveTo[0] = prev[0];
          moveTo[1] = prev[1];
  
          iter.next();
  
          if(iter.isDone()){
              return null;
          }
  
          curSegType = iter.currentSegment(cur);
  
          if(curSegType == PathIterator.SEG_MOVETO){
              moveTo[0] = cur[0];
              moveTo[1] = cur[1];
          } else if(curSegType == PathIterator.SEG_CLOSE){
              curSegType = PathIterator.SEG_LINETO;
              cur[0] = moveTo[0];
              cur[1] = moveTo[1];
          }
  
          iter.next();
  
          Vector proxies = new Vector();
          while(!iter.isDone()){
              nextSegType = iter.currentSegment(next);
  
              if(nextSegType == PathIterator.SEG_MOVETO){
                  moveTo[0] = next[0];
                  moveTo[1] = next[1];
              } else if(nextSegType == PathIterator.SEG_CLOSE){
                  nextSegType = PathIterator.SEG_LINETO;
                  next[0] = moveTo[0];
                  next[1] = moveTo[1];
              }
  
              proxies.addElement(createMiddleMarker(prev, prevSegType,
                                                    cur, curSegType,
                                                    next, nextSegType));
              
              tmp = prev;
              prev = cur;
              prevSegType = curSegType;
              cur = next;
              curSegType = nextSegType;
              next = tmp;
              
              iter.next();
          }
  
          ProxyGraphicsNode gn[]
              = new ProxyGraphicsNode[proxies.size()];
  
          proxies.copyInto(gn);
  
          return gn;
      }
  
      private ProxyGraphicsNode createMiddleMarker(double[] prev,
                                                   int prevSegType,
                                                   double[] cur,
                                                   int curSegType,
                                                   double[] next,
                                                   int nextSegType){
  
          // Turn the cur segment into a position
          Point2D markerPosition = getSegmentTerminatingPoint(cur, curSegType);
  
          // If the marker's orient property is NaN,
          // the slope needs to be computed
          double rotation = middleMarker.getOrient();
          if(Double.isNaN(rotation)){
              rotation = computeRotation(prev, prevSegType,
                                         cur, curSegType,
                                         next, nextSegType);
          }
  
          // Now, compute the marker's proxy transform
          AffineTransform markerTxf =
              computeMarkerTransform(middleMarker,
                                     markerPosition,
                                     rotation);
                                     
          ProxyGraphicsNode gn 
              = new ProxyGraphicsNode();
  
          gn.setSource(middleMarker.getMarkerNode());
          gn.setTransform(markerTxf);
  
          return gn;
      }
  
      private double computeRotation(double[] prev,
                                     int prevSegType,
                                     double[] cur,
                                     int curSegType,
                                     double[] next,
                                     int nextSegType){
          // Compute in slope, i.e., the slope of the segment
          // going into the current point
          double[] inSlope = computeInSlope(prev, prevSegType, 
                                            cur, curSegType);
  
          // Compute out slope, i.e., the slope of the segment
          // going out of the current point
          double[] outSlope = computeOutSlope(cur, curSegType, 
                                              next, nextSegType);
  
          if(inSlope == null){
              inSlope = outSlope;
          }
  
          if(outSlope == null){
              outSlope = inSlope;
          }
  
          if(inSlope == null){
              return 0;
          }
  
         
          double rotationIn  = Math.atan2(inSlope[1], inSlope[0])*180./Math.PI;
          double rotationOut = Math.atan2(outSlope[1], outSlope[0])*180./Math.PI;
          double rotation = (rotationIn + rotationOut)/2;
  
          return rotation;
      }
  
      /**
       * @return dx/dy for the in slope
       */
      private double[] computeInSlope(double[] prev,
                                      int prevSegType,
                                      double[] cur,
                                      int curSegType){
          // Compute point into which the slope runs
          Point2D curEndPoint = getSegmentTerminatingPoint(cur, curSegType);
  
          double dx = 0, dy = 0;
          switch(curSegType){
          case PathIterator.SEG_QUADTO:
              // If the current segment is a line, quad or cubic curve. 
              // the slope is about equal to that of the
              // line from the last control point and the curEndPoint
              dx = curEndPoint.getX() - cur[0];
              dy = curEndPoint.getY() - cur[1];
              break;
          case PathIterator.SEG_LINETO:
              //
              // This is equivalent to a line from the previous
              // segment's terminating point and the current end
              // point.
              Point2D prevEndPoint = getSegmentTerminatingPoint(prev, prevSegType);
                  
              dx = curEndPoint.getX() - prevEndPoint.getX();
              dy = curEndPoint.getY() - prevEndPoint.getY();
              break;
          case PathIterator.SEG_CUBICTO:
              // If the current segment is a line, quad or cubic curve. 
              // the slope is about equal to that of the
              // line from the last control point and the curEndPoint
              dx = curEndPoint.getX() - cur[2];
              dy = curEndPoint.getY() - cur[3];
              break;
          case PathIterator.SEG_CLOSE:
              // Should not have any close at this point
              throw new Error();
          case PathIterator.SEG_MOVETO:
              // Cannot compute the slope
          default:
              return null;
          }
  
          if(dx == 0 && dy == 0){
              return null;
          }
  
          return new double[] { dx, dy };
      }
  
      /**
       * @return dx/dy for the out slope
       */
      private double[] computeOutSlope(double[] cur,
                                       int curSegType,
                                       double[] next,
                                       int nextSegType){
          Point2D curEndPoint = getSegmentTerminatingPoint(cur, curSegType);
          
          double dx = 0, dy = 0;
  
          switch(nextSegType){
          case PathIterator.SEG_CLOSE:
              // Should not happen at this point, because all close
              // segments have been replaced by lineTo segments.
              break;
          case PathIterator.SEG_CUBICTO:
          case PathIterator.SEG_LINETO:
          case PathIterator.SEG_QUADTO:
              // If the next segment is a line, quad or cubic curve. 
              // the slope is about equal to that of the
              // line from curEndPoint and the first control
              // point
              dx = next[0] - curEndPoint.getX();
              dy = next[1] - curEndPoint.getY();
              break;
          case PathIterator.SEG_MOVETO:
              // Cannot compute the out slope
          default:
              return null;
          }
  
          if(dx == 0 && dy == 0){
              return null;
          }
  
          return new double[] { dx, dy };
      }
  
      /**
       * Computes the transform for the input marker, so that
       * it is positioned at the given position with the specified
       * rotation
       */
      private AffineTransform 
          computeMarkerTransform(Marker marker,
                                 Point2D markerPosition,
                                 double rotation){
          Point2D ref = marker.getRef();
          /*AffineTransform txf = 
              AffineTransform.getTranslateInstance(markerPosition.getX()
                                                   - ref.getX(),
                                                   markerPosition.getY()
                                                   - ref.getY());*/
          AffineTransform txf = new AffineTransform();
  
          txf.translate(markerPosition.getX()
                        - ref.getX(),
                        markerPosition.getY()
                        - ref.getY());
  
          if(!Double.isNaN(rotation)){
              txf.rotate(rotation*Math.PI/180., 
                         ref.getX(),
                         ref.getY());
          }
  
          return txf;
      }
  
      /**
       * Paints the specified shape using the specified Graphics2D and context.
       *
       * @param shape the shape to paint
       * @param g2d the Graphics2D to use
       * @param ctx the render context to use
       */
  
       public void paint(Graphics2D g2d,
                         GraphicsNodeRenderContext ctx) {
          try{
              if(markerGroup.getChildren().size() > 0){
                  markerGroup.paint(g2d, ctx);
              }
          }catch(InterruptedException e){
              // ????????? Should we really have interrupted exceptions?
          }
      }
  
      /**
       * Returns the area painted by this painter
       */
      public Shape getPaintedArea(GraphicsNodeRenderContext rc){
          return markerGroup.getBounds(rc);
      }
  
      /**
       * Sets the Shape this painter is associated with.
       * @param shape new shape this painter should be associated with.
       *        should not be null.
       */
      public void setShape(Shape shape){
          this.shape = shape;
          this.startMarkerProxy = null;
          this.middleMarkerProxies = null;
          this.endMarkerProxy = null;
          buildMarkerGroup();
      }
  
      /**
       * Gets the Shape this painter is associated with.
       *
       * @return shape associated with this Painter.
       */
      public Shape getShape(){
          return shape;
      }
  }