1 /*
2  * $Id: PDFShapeCmd.java,v 1.3 2009-01-16 16:26:15 tomoke Exp $
3  *
4  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5  * Santa Clara, California 95054, U.S.A. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 package com.sun.pdfview;
22 
23 import java.awt.BasicStroke;
24 import java.awt.geom.AffineTransform;
25 import java.awt.geom.GeneralPath;
26 import java.awt.geom.PathIterator;
27 import java.awt.geom.Rectangle2D;
28 
29 /**
30  * Encapsulates a path.  Also contains extra fields and logic to check
31  * for consecutive abutting anti-aliased regions.  We stroke the shared
32  * line between these regions again with a 1-pixel wide line so that
33  * the background doesn't show through between them.
34  *
35  * @author Mike Wessler
36  */
37 public class PDFShapeCmd extends PDFCmd {
38 
39     /** stroke the outline of the path with the stroke paint */
40     public static final int STROKE = 1;
41     /** fill the path with the fill paint */
42     public static final int FILL = 2;
43     /** perform both stroke and fill */
44     public static final int BOTH = 3;
45     /** set the clip region to the path */
46     public static final int CLIP = 4;
47     /** base path */
48     private GeneralPath gp;
49     /** the style */
50     private int style;
51     /** the bounding box of the path */
52     private Rectangle2D bounds;
53     /** the stroke style for the anti-antialias stroke */
54     BasicStroke againstroke =
55             new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
56 
57     /**
58      * create a new PDFShapeCmd and check it against the previous one
59      * to find any shared edges.
60      * @param gp the path
61      * @param style the style: an OR of STROKE, FILL, or CLIP.  As a
62      * convenience, BOTH = STROKE | FILL.
63      */
PDFShapeCmd(GeneralPath gp, int style)64     public PDFShapeCmd(GeneralPath gp, int style) {
65         this.gp = new GeneralPath(gp);
66         this.style = style;
67         bounds = gp.getBounds2D();
68     }
69 
70     /**
71      * perform the stroke and record the dirty region
72      */
execute(PDFRenderer state)73     public Rectangle2D execute(PDFRenderer state) {
74         Rectangle2D rect = null;
75 
76         if ((style & FILL) != 0) {
77             rect = state.fill(gp);
78 
79             GeneralPath strokeagain = checkOverlap(state);
80             if (strokeagain != null) {
81                 state.draw(strokeagain, againstroke);
82             }
83 
84             if (gp != null) {
85                 state.setLastShape(gp);
86             }
87         }
88         if ((style & STROKE) != 0) {
89             Rectangle2D strokeRect = state.stroke(gp);
90             if (rect == null) {
91                 rect = strokeRect;
92             } else {
93                 rect = rect.createUnion(strokeRect);
94             }
95         }
96         if ((style & CLIP) != 0) {
97             state.clip(gp);
98         }
99 
100         return rect;
101     }
102 
103     /**
104      * Check for overlap with the previous shape to make anti-aliased shapes
105      * that are near each other look good
106      */
checkOverlap(PDFRenderer state)107     private GeneralPath checkOverlap(PDFRenderer state) {
108         if (style == FILL && gp != null && state.getLastShape() != null) {
109             float mypoints[] = new float[16];
110             float prevpoints[] = new float[16];
111 
112             int mycount = getPoints(gp, mypoints);
113             int prevcount = getPoints(state.getLastShape(), prevpoints);
114 
115             // now check mypoints against prevpoints for opposite pairs:
116             if (mypoints != null && prevpoints != null) {
117                 for (int i = 0; i < prevcount; i += 4) {
118                     for (int j = 0; j < mycount; j += 4) {
119                         if ((Math.abs(mypoints[j + 2] - prevpoints[i]) < 0.01 &&
120                                 Math.abs(mypoints[j + 3] - prevpoints[i + 1]) < 0.01 &&
121                                 Math.abs(mypoints[j] - prevpoints[i + 2]) < 0.01 &&
122                                 Math.abs(mypoints[j + 1] - prevpoints[i + 3]) < 0.01)) {
123                             GeneralPath strokeagain = new GeneralPath();
124                             strokeagain.moveTo(mypoints[j], mypoints[j + 1]);
125                             strokeagain.lineTo(mypoints[j + 2], mypoints[j + 3]);
126                             return strokeagain;
127                         }
128                     }
129                 }
130             }
131         }
132 
133         // no issues
134         return null;
135     }
136 
137     /**
138      * Get an array of 16 points from a path
139      * @return the number of points we actually got
140      */
getPoints(GeneralPath path, float[] mypoints)141     private int getPoints(GeneralPath path, float[] mypoints) {
142         int count = 0;
143         float x = 0;
144         float y = 0;
145         float startx = 0;
146         float starty = 0;
147         float[] coords = new float[6];
148 
149         PathIterator pi = path.getPathIterator(new AffineTransform());
150         while (!pi.isDone()) {
151             if (count >= mypoints.length) {
152                 mypoints = null;
153                 break;
154             }
155 
156             int pathtype = pi.currentSegment(coords);
157             switch (pathtype) {
158                 case PathIterator.SEG_MOVETO:
159                     startx = x = coords[0];
160                     starty = y = coords[1];
161                     break;
162                 case PathIterator.SEG_LINETO:
163                     mypoints[count++] = x;
164                     mypoints[count++] = y;
165                     x = mypoints[count++] = coords[0];
166                     y = mypoints[count++] = coords[1];
167                     break;
168                 case PathIterator.SEG_QUADTO:
169                     x = coords[2];
170                     y = coords[3];
171                     break;
172                 case PathIterator.SEG_CUBICTO:
173                     x = mypoints[4];
174                     y = mypoints[5];
175                     break;
176                 case PathIterator.SEG_CLOSE:
177                     mypoints[count++] = x;
178                     mypoints[count++] = y;
179                     x = mypoints[count++] = startx;
180                     y = mypoints[count++] = starty;
181                     break;
182             }
183 
184             pi.next();
185         }
186 
187         return count;
188     }
189 
190     /** Get detailed information about this shape
191      */
192     @Override
getDetails()193     public String getDetails() {
194         StringBuffer sb = new StringBuffer();
195 
196         Rectangle2D b = gp.getBounds2D();
197         sb.append("ShapeCommand at: " + b.getX() + ", " + b.getY() + "\n");
198         sb.append("Size: " + b.getWidth() + " x " + b.getHeight() + "\n");
199 
200         sb.append("Mode: ");
201         if ((style & FILL) != 0) {
202             sb.append("FILL ");
203         }
204         if ((style & STROKE) != 0) {
205             sb.append("STROKE ");
206         }
207         if ((style & CLIP) != 0) {
208             sb.append("CLIP");
209         }
210 
211         return sb.toString();
212     }
213 }
214