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