| /* BasicStroke.java -- |
| Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package java.awt; |
| |
| import gnu.java.awt.java2d.CubicSegment; |
| import gnu.java.awt.java2d.LineSegment; |
| import gnu.java.awt.java2d.QuadSegment; |
| import gnu.java.awt.java2d.Segment; |
| |
| import java.awt.geom.GeneralPath; |
| import java.awt.geom.PathIterator; |
| import java.awt.geom.Point2D; |
| import java.util.Arrays; |
| |
| /** |
| * A general purpose {@link Stroke} implementation that can represent a wide |
| * variety of line styles for use with subclasses of {@link Graphics2D}. |
| * <p> |
| * The line cap and join styles can be set using the options illustrated |
| * here: |
| * <p> |
| * <img src="doc-files/capjoin.png" width="350" height="180" |
| * alt="Illustration of line cap and join styles" /> |
| * <p> |
| * A dash array can be used to specify lines with alternating opaque and |
| * transparent sections. |
| */ |
| public class BasicStroke implements Stroke |
| { |
| /** |
| * Indicates a mitered line join style. See the class overview for an |
| * illustration. |
| */ |
| public static final int JOIN_MITER = 0; |
| |
| /** |
| * Indicates a rounded line join style. See the class overview for an |
| * illustration. |
| */ |
| public static final int JOIN_ROUND = 1; |
| |
| /** |
| * Indicates a bevelled line join style. See the class overview for an |
| * illustration. |
| */ |
| public static final int JOIN_BEVEL = 2; |
| |
| /** |
| * Indicates a flat line cap style. See the class overview for an |
| * illustration. |
| */ |
| public static final int CAP_BUTT = 0; |
| |
| /** |
| * Indicates a rounded line cap style. See the class overview for an |
| * illustration. |
| */ |
| public static final int CAP_ROUND = 1; |
| |
| /** |
| * Indicates a square line cap style. See the class overview for an |
| * illustration. |
| */ |
| public static final int CAP_SQUARE = 2; |
| |
| /** The stroke width. */ |
| private final float width; |
| |
| /** The line cap style. */ |
| private final int cap; |
| |
| /** The line join style. */ |
| private final int join; |
| |
| /** The miter limit. */ |
| private final float limit; |
| |
| /** The dash array. */ |
| private final float[] dash; |
| |
| /** The dash phase. */ |
| private final float phase; |
| |
| // The inner and outer paths of the stroke |
| private Segment start, end; |
| |
| /** |
| * Creates a new <code>BasicStroke</code> instance with the given attributes. |
| * |
| * @param width the line width (>= 0.0f). |
| * @param cap the line cap style (one of {@link #CAP_BUTT}, |
| * {@link #CAP_ROUND} or {@link #CAP_SQUARE}). |
| * @param join the line join style (one of {@link #JOIN_ROUND}, |
| * {@link #JOIN_BEVEL}, or {@link #JOIN_MITER}). |
| * @param miterlimit the limit to trim the miter join. The miterlimit must be |
| * greater than or equal to 1.0f. |
| * @param dash The array representing the dashing pattern. There must be at |
| * least one non-zero entry. |
| * @param dashPhase is negative and dash is not null. |
| * |
| * @throws IllegalArgumentException If one input parameter doesn't meet |
| * its needs. |
| */ |
| public BasicStroke(float width, int cap, int join, float miterlimit, |
| float[] dash, float dashPhase) |
| { |
| if (width < 0.0f ) |
| throw new IllegalArgumentException("width " + width + " < 0"); |
| else if (cap < CAP_BUTT || cap > CAP_SQUARE) |
| throw new IllegalArgumentException("cap " + cap + " out of range [" |
| + CAP_BUTT + ".." + CAP_SQUARE + "]"); |
| else if (miterlimit < 1.0f && join == JOIN_MITER) |
| throw new IllegalArgumentException("miterlimit " + miterlimit |
| + " < 1.0f while join == JOIN_MITER"); |
| else if (join < JOIN_MITER || join > JOIN_BEVEL) |
| throw new IllegalArgumentException("join " + join + " out of range [" |
| + JOIN_MITER + ".." + JOIN_BEVEL |
| + "]"); |
| else if (dashPhase < 0.0f && dash != null) |
| throw new IllegalArgumentException("dashPhase " + dashPhase |
| + " < 0.0f while dash != null"); |
| else if (dash != null) |
| if (dash.length == 0) |
| throw new IllegalArgumentException("dash.length is 0"); |
| else |
| { |
| boolean allZero = true; |
| |
| for ( int i = 0; i < dash.length; ++i) |
| { |
| if (dash[i] != 0.0f) |
| { |
| allZero = false; |
| break; |
| } |
| } |
| |
| if (allZero) |
| throw new IllegalArgumentException("all dashes are 0.0f"); |
| } |
| |
| this.width = width; |
| this.cap = cap; |
| this.join = join; |
| limit = miterlimit; |
| this.dash = dash == null ? null : (float[]) dash.clone(); |
| phase = dashPhase; |
| } |
| |
| /** |
| * Creates a new <code>BasicStroke</code> instance with the given attributes. |
| * |
| * @param width the line width (>= 0.0f). |
| * @param cap the line cap style (one of {@link #CAP_BUTT}, |
| * {@link #CAP_ROUND} or {@link #CAP_SQUARE}). |
| * @param join the line join style (one of {@link #JOIN_ROUND}, |
| * {@link #JOIN_BEVEL}, or {@link #JOIN_MITER}). |
| * @param miterlimit the limit to trim the miter join. The miterlimit must be |
| * greater than or equal to 1.0f. |
| * |
| * @throws IllegalArgumentException If one input parameter doesn't meet |
| * its needs. |
| */ |
| public BasicStroke(float width, int cap, int join, float miterlimit) |
| { |
| this(width, cap, join, miterlimit, null, 0); |
| } |
| |
| /** |
| * Creates a new <code>BasicStroke</code> instance with the given attributes. |
| * The miter limit defaults to <code>10.0</code>. |
| * |
| * @param width the line width (>= 0.0f). |
| * @param cap the line cap style (one of {@link #CAP_BUTT}, |
| * {@link #CAP_ROUND} or {@link #CAP_SQUARE}). |
| * @param join the line join style (one of {@link #JOIN_ROUND}, |
| * {@link #JOIN_BEVEL}, or {@link #JOIN_MITER}). |
| * |
| * @throws IllegalArgumentException If one input parameter doesn't meet |
| * its needs. |
| */ |
| public BasicStroke(float width, int cap, int join) |
| { |
| this(width, cap, join, 10, null, 0); |
| } |
| |
| /** |
| * Creates a new <code>BasicStroke</code> instance with the given line |
| * width. The default values are: |
| * <ul> |
| * <li>line cap style: {@link #CAP_SQUARE};</li> |
| * <li>line join style: {@link #JOIN_MITER};</li> |
| * <li>miter limit: <code>10.0f</code>. |
| * </ul> |
| * |
| * @param width the line width (>= 0.0f). |
| * |
| * @throws IllegalArgumentException If <code>width</code> is negative. |
| */ |
| public BasicStroke(float width) |
| { |
| this(width, CAP_SQUARE, JOIN_MITER, 10, null, 0); |
| } |
| |
| /** |
| * Creates a new <code>BasicStroke</code> instance. The default values are: |
| * <ul> |
| * <li>line width: <code>1.0f</code>;</li> |
| * <li>line cap style: {@link #CAP_SQUARE};</li> |
| * <li>line join style: {@link #JOIN_MITER};</li> |
| * <li>miter limit: <code>10.0f</code>. |
| * </ul> |
| */ |
| public BasicStroke() |
| { |
| this(1, CAP_SQUARE, JOIN_MITER, 10, null, 0); |
| } |
| |
| /** |
| * Creates a shape representing the stroked outline of the given shape. |
| * THIS METHOD IS NOT YET IMPLEMENTED. |
| * |
| * @param s the shape. |
| */ |
| public Shape createStrokedShape(Shape s) |
| { |
| PathIterator pi = s.getPathIterator(null); |
| |
| if( dash == null ) |
| return solidStroke( pi ); |
| |
| return dashedStroke( pi ); |
| } |
| |
| /** |
| * Returns the line width. |
| * |
| * @return The line width. |
| */ |
| public float getLineWidth() |
| { |
| return width; |
| } |
| |
| /** |
| * Returns a code indicating the line cap style (one of {@link #CAP_BUTT}, |
| * {@link #CAP_ROUND}, {@link #CAP_SQUARE}). |
| * |
| * @return A code indicating the line cap style. |
| */ |
| public int getEndCap() |
| { |
| return cap; |
| } |
| |
| /** |
| * Returns a code indicating the line join style (one of {@link #JOIN_BEVEL}, |
| * {@link #JOIN_MITER} or {@link #JOIN_ROUND}). |
| * |
| * @return A code indicating the line join style. |
| */ |
| public int getLineJoin() |
| { |
| return join; |
| } |
| |
| /** |
| * Returns the miter limit. |
| * |
| * @return The miter limit. |
| */ |
| public float getMiterLimit() |
| { |
| return limit; |
| } |
| |
| /** |
| * Returns the dash array, which defines the length of alternate opaque and |
| * transparent sections in lines drawn with this stroke. If |
| * <code>null</code>, a continuous line will be drawn. |
| * |
| * @return The dash array (possibly <code>null</code>). |
| */ |
| public float[] getDashArray() |
| { |
| return dash; |
| } |
| |
| /** |
| * Returns the dash phase for the stroke. This is the offset from the start |
| * of a path at which the pattern defined by {@link #getDashArray()} is |
| * rendered. |
| * |
| * @return The dash phase. |
| */ |
| public float getDashPhase() |
| { |
| return phase; |
| } |
| |
| /** |
| * Returns the hash code for this object. The hash is calculated by |
| * xoring the hash, cap, join, limit, dash array and phase values |
| * (converted to <code>int</code> first with |
| * <code>Float.floatToIntBits()</code> if the value is a |
| * <code>float</code>). |
| * |
| * @return The hash code. |
| */ |
| public int hashCode() |
| { |
| int hash = Float.floatToIntBits(width); |
| hash ^= cap; |
| hash ^= join; |
| hash ^= Float.floatToIntBits(limit); |
| |
| if (dash != null) |
| for (int i = 0; i < dash.length; i++) |
| hash ^= Float.floatToIntBits(dash[i]); |
| |
| hash ^= Float.floatToIntBits(phase); |
| |
| return hash; |
| } |
| |
| /** |
| * Compares this <code>BasicStroke</code> for equality with an arbitrary |
| * object. This method returns <code>true</code> if and only if: |
| * <ul> |
| * <li><code>o</code> is an instanceof <code>BasicStroke</code>;</li> |
| * <li>this object has the same width, line cap style, line join style, |
| * miter limit, dash array and dash phase as <code>o</code>.</li> |
| * </ul> |
| * |
| * @param o the object (<code>null</code> permitted). |
| * |
| * @return <code>true</code> if this stroke is equal to <code>o</code> and |
| * <code>false</code> otherwise. |
| */ |
| public boolean equals(Object o) |
| { |
| if (! (o instanceof BasicStroke)) |
| return false; |
| BasicStroke s = (BasicStroke) o; |
| return width == s.width && cap == s.cap && join == s.join |
| && limit == s.limit && Arrays.equals(dash, s.dash) && phase == s.phase; |
| } |
| |
| private Shape solidStroke(PathIterator pi) |
| { |
| double[] coords = new double[6]; |
| double x, y, x0, y0; |
| boolean pathOpen = false; |
| GeneralPath output = new GeneralPath( ); |
| Segment[] p; |
| x = x0 = y = y0 = 0; |
| |
| while( !pi.isDone() ) |
| { |
| switch( pi.currentSegment(coords) ) |
| { |
| case PathIterator.SEG_MOVETO: |
| x0 = x = coords[0]; |
| y0 = y = coords[1]; |
| if( pathOpen ) |
| { |
| capEnds(); |
| convertPath(output, start); |
| start = end = null; |
| pathOpen = false; |
| } |
| break; |
| |
| case PathIterator.SEG_LINETO: |
| p = (new LineSegment(x, y, coords[0], coords[1])). |
| getDisplacedSegments(width/2.0); |
| if( !pathOpen ) |
| { |
| start = p[0]; |
| end = p[1]; |
| pathOpen = true; |
| } |
| else |
| addSegments(p); |
| |
| x = coords[0]; |
| y = coords[1]; |
| break; |
| |
| case PathIterator.SEG_QUADTO: |
| p = (new QuadSegment(x, y, coords[0], coords[1], coords[2], |
| coords[3])).getDisplacedSegments(width/2.0); |
| if( !pathOpen ) |
| { |
| start = p[0]; |
| end = p[1]; |
| pathOpen = true; |
| } |
| else |
| addSegments(p); |
| |
| x = coords[2]; |
| y = coords[3]; |
| break; |
| |
| case PathIterator.SEG_CUBICTO: |
| p = new CubicSegment(x, y, coords[0], coords[1], |
| coords[2], coords[3], |
| coords[4], coords[5]).getDisplacedSegments(width/2.0); |
| if( !pathOpen ) |
| { |
| start = p[0]; |
| end = p[1]; |
| pathOpen = true; |
| } |
| else |
| addSegments(p); |
| |
| x = coords[4]; |
| y = coords[5]; |
| break; |
| |
| case PathIterator.SEG_CLOSE: |
| if (x == x0 && y == y0) |
| { |
| joinSegments(new Segment[] { start.first, end.first }); |
| } |
| else |
| { |
| p = (new LineSegment(x, y, x0, y0)).getDisplacedSegments(width / 2.0); |
| addSegments(p); |
| } |
| convertPath(output, start); |
| convertPath(output, end); |
| start = end = null; |
| pathOpen = false; |
| output.setWindingRule(GeneralPath.WIND_EVEN_ODD); |
| break; |
| } |
| pi.next(); |
| } |
| |
| if( pathOpen ) |
| { |
| capEnds(); |
| convertPath(output, start); |
| } |
| return output; |
| } |
| |
| private Shape dashedStroke(PathIterator pi) |
| { |
| GeneralPath out = new GeneralPath(); |
| return out; |
| } |
| |
| /** |
| * Cap the ends of the path (joining the start and end list of segments) |
| */ |
| private void capEnds() |
| { |
| Segment returnPath = end.last; |
| |
| end.reverseAll(); // reverse the path. |
| end = null; |
| capEnd(start, returnPath); |
| start.last = returnPath.last; |
| end = null; |
| |
| capEnd(start, start); |
| } |
| |
| /** |
| * Append the Segments in s to the GeneralPath p |
| */ |
| private void convertPath(GeneralPath p, Segment s) |
| { |
| Segment v = s; |
| p.moveTo((float)s.P1.getX(), (float)s.P1.getY()); |
| |
| do |
| { |
| if(v instanceof LineSegment) |
| p.lineTo((float)v.P2.getX(), (float)v.P2.getY()); |
| else if(v instanceof QuadSegment) |
| p.quadTo((float)((QuadSegment)v).cp.getX(), |
| (float)((QuadSegment)v).cp.getY(), |
| (float)v.P2.getX(), |
| (float)v.P2.getY()); |
| else if(v instanceof CubicSegment) |
| p.curveTo((float)((CubicSegment)v).cp1.getX(), |
| (float)((CubicSegment)v).cp1.getY(), |
| (float)((CubicSegment)v).cp2.getX(), |
| (float)((CubicSegment)v).cp2.getY(), |
| (float)v.P2.getX(), |
| (float)v.P2.getY()); |
| v = v.next; |
| } while(v != s && v != null); |
| |
| p.closePath(); |
| } |
| |
| /** |
| * Add the segments to start and end (the inner and outer edges of the stroke) |
| */ |
| private void addSegments(Segment[] segments) |
| { |
| joinSegments(segments); |
| start.add(segments[0]); |
| end.add(segments[1]); |
| } |
| |
| private void joinSegments(Segment[] segments) |
| { |
| double[] p0 = start.last.cp2(); |
| double[] p1 = new double[]{start.last.P2.getX(), start.last.P2.getY()}; |
| double[] p2 = new double[]{segments[0].first.P1.getX(), segments[0].first.P1.getY()}; |
| double[] p3 = segments[0].cp1(); |
| Point2D p; |
| |
| p = lineIntersection(p0[0],p0[1],p1[0],p1[1], |
| p2[0],p2[1],p3[0],p3[1], false); |
| |
| double det = (p1[0] - p0[0])*(p3[1] - p2[1]) - |
| (p3[0] - p2[0])*(p1[1] - p0[1]); |
| |
| if( det > 0 ) |
| { |
| // start and segment[0] form the 'inner' part of a join, |
| // connect the overlapping segments |
| joinInnerSegments(start, segments[0], p); |
| joinOuterSegments(end, segments[1], p); |
| } |
| else |
| { |
| // end and segment[1] form the 'inner' part |
| joinInnerSegments(end, segments[1], p); |
| joinOuterSegments(start, segments[0], p); |
| } |
| } |
| |
| /** |
| * Make a cap between a and b segments, |
| * where a-->b is the direction of iteration. |
| */ |
| private void capEnd(Segment a, Segment b) |
| { |
| double[] p0, p1; |
| double dx, dy, l; |
| Point2D c1,c2; |
| |
| switch( cap ) |
| { |
| case CAP_BUTT: |
| a.add(new LineSegment(a.last.P2, b.P1)); |
| break; |
| |
| case CAP_SQUARE: |
| p0 = a.last.cp2(); |
| p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()}; |
| dx = p1[0] - p0[0]; |
| dy = p1[1] - p0[1]; |
| l = Math.sqrt(dx * dx + dy * dy); |
| dx = 0.5*width*dx/l; |
| dy = 0.5*width*dy/l; |
| c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy); |
| c2 = new Point2D.Double(b.P1.getX() + dx, b.P1.getY() + dy); |
| a.add(new LineSegment(a.last.P2, c1)); |
| a.add(new LineSegment(c1, c2)); |
| a.add(new LineSegment(c2, b.P1)); |
| break; |
| |
| case CAP_ROUND: |
| p0 = a.last.cp2(); |
| p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()}; |
| dx = p1[0] - p0[0]; |
| dy = p1[1] - p0[1]; |
| l = Math.sqrt(dx * dx + dy * dy); |
| dx = (2.0/3.0)*width*dx/l; |
| dy = (2.0/3.0)*width*dy/l; |
| c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy); |
| c2 = new Point2D.Double(b.P1.getX() + dx, b.P1.getY() + dy); |
| a.add(new CubicSegment(a.last.P2, c1, c2, b.P1)); |
| break; |
| } |
| a.add(b); |
| } |
| |
| /** |
| * Returns the intersection of two lines, or null if there isn't one. |
| * @param infinite - true if the lines should be regarded as infinite, false |
| * if the intersection must be within the given segments. |
| * @return a Point2D or null. |
| */ |
| private Point2D lineIntersection(double X1, double Y1, |
| double X2, double Y2, |
| double X3, double Y3, |
| double X4, double Y4, |
| boolean infinite) |
| { |
| double x1 = X1; |
| double y1 = Y1; |
| double rx = X2 - x1; |
| double ry = Y2 - y1; |
| |
| double x2 = X3; |
| double y2 = Y3; |
| double sx = X4 - x2; |
| double sy = Y4 - y2; |
| |
| double determinant = sx * ry - sy * rx; |
| double nom = (sx * (y2 - y1) + sy * (x1 - x2)); |
| |
| // lines can be considered parallel. |
| if (Math.abs(determinant) < 1E-6) |
| return null; |
| |
| nom = nom / determinant; |
| |
| // check if lines are within the bounds |
| if(!infinite && (nom > 1.0 || nom < 0.0)) |
| return null; |
| |
| return new Point2D.Double(x1 + nom * rx, y1 + nom * ry); |
| } |
| |
| /** |
| * Join a and b segments, where a-->b is the direction of iteration. |
| * |
| * insideP is the inside intersection point of the join, needed for |
| * calculating miter lengths. |
| */ |
| private void joinOuterSegments(Segment a, Segment b, Point2D insideP) |
| { |
| double[] p0, p1; |
| double dx, dy, l; |
| Point2D c1,c2; |
| |
| switch( join ) |
| { |
| case JOIN_MITER: |
| p0 = a.last.cp2(); |
| p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()}; |
| double[] p2 = new double[]{b.P1.getX(), b.P1.getY()}; |
| double[] p3 = b.cp1(); |
| Point2D p = lineIntersection(p0[0],p0[1],p1[0],p1[1],p2[0],p2[1],p3[0],p3[1], true); |
| if( p == null || insideP == null ) |
| a.add(new LineSegment(a.last.P2, b.P1)); |
| else if((p.distance(insideP)/width) < limit) |
| { |
| a.add(new LineSegment(a.last.P2, p)); |
| a.add(new LineSegment(p, b.P1)); |
| } |
| else |
| { |
| // outside miter limit, do a bevel join. |
| a.add(new LineSegment(a.last.P2, b.P1)); |
| } |
| break; |
| |
| case JOIN_ROUND: |
| p0 = a.last.cp2(); |
| p1 = new double[]{a.last.P2.getX(), a.last.P2.getY()}; |
| dx = p1[0] - p0[0]; |
| dy = p1[1] - p0[1]; |
| l = Math.sqrt(dx * dx + dy * dy); |
| dx = 0.5*width*dx/l; |
| dy = 0.5*width*dy/l; |
| c1 = new Point2D.Double(p1[0] + dx, p1[1] + dy); |
| |
| p0 = new double[]{b.P1.getX(), b.P1.getY()}; |
| p1 = b.cp1(); |
| |
| dx = p0[0] - p1[0]; // backwards direction. |
| dy = p0[1] - p1[1]; |
| l = Math.sqrt(dx * dx + dy * dy); |
| dx = 0.5*width*dx/l; |
| dy = 0.5*width*dy/l; |
| c2 = new Point2D.Double(p0[0] + dx, p0[1] + dy); |
| a.add(new CubicSegment(a.last.P2, c1, c2, b.P1)); |
| break; |
| |
| case JOIN_BEVEL: |
| a.add(new LineSegment(a.last.P2, b.P1)); |
| break; |
| } |
| } |
| |
| /** |
| * Join a and b segments, removing any overlap |
| */ |
| private void joinInnerSegments(Segment a, Segment b, Point2D p) |
| { |
| double[] p0 = a.last.cp2(); |
| double[] p1 = new double[] { a.last.P2.getX(), a.last.P2.getY() }; |
| double[] p2 = new double[] { b.P1.getX(), b.P1.getY() }; |
| double[] p3 = b.cp1(); |
| |
| if (p == null) |
| { |
| // Dodgy. |
| a.add(new LineSegment(a.last.P2, b.P1)); |
| p = new Point2D.Double((b.P1.getX() + a.last.P2.getX()) / 2.0, |
| (b.P1.getY() + a.last.P2.getY()) / 2.0); |
| } |
| else |
| // This assumes segments a and b are single segments, which is |
| // incorrect - if they are a linked list of segments (ie, passed in |
| // from a flattening operation), this produces strange results!! |
| a.last.P2 = b.P1 = p; |
| } |
| } |