/* * $Id: PDFShapeCmd.java,v 1.3 2009-01-16 16:26:15 tomoke Exp $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.sun.pdfview; import java.awt.BasicStroke; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; /** * Encapsulates a path. Also contains extra fields and logic to check * for consecutive abutting anti-aliased regions. We stroke the shared * line between these regions again with a 1-pixel wide line so that * the background doesn't show through between them. * * @author Mike Wessler */ public class PDFShapeCmd extends PDFCmd { /** stroke the outline of the path with the stroke paint */ public static final int STROKE = 1; /** fill the path with the fill paint */ public static final int FILL = 2; /** perform both stroke and fill */ public static final int BOTH = 3; /** set the clip region to the path */ public static final int CLIP = 4; /** base path */ private GeneralPath gp; /** the style */ private int style; /** the bounding box of the path */ private Rectangle2D bounds; /** the stroke style for the anti-antialias stroke */ BasicStroke againstroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); /** * create a new PDFShapeCmd and check it against the previous one * to find any shared edges. * @param gp the path * @param style the style: an OR of STROKE, FILL, or CLIP. As a * convenience, BOTH = STROKE | FILL. */ public PDFShapeCmd(GeneralPath gp, int style) { this.gp = new GeneralPath(gp); this.style = style; bounds = gp.getBounds2D(); } /** * perform the stroke and record the dirty region */ public Rectangle2D execute(PDFRenderer state) { Rectangle2D rect = null; if ((style & FILL) != 0) { rect = state.fill(gp); GeneralPath strokeagain = checkOverlap(state); if (strokeagain != null) { state.draw(strokeagain, againstroke); } if (gp != null) { state.setLastShape(gp); } } if ((style & STROKE) != 0) { Rectangle2D strokeRect = state.stroke(gp); if (rect == null) { rect = strokeRect; } else { rect = rect.createUnion(strokeRect); } } if ((style & CLIP) != 0) { state.clip(gp); } return rect; } /** * Check for overlap with the previous shape to make anti-aliased shapes * that are near each other look good */ private GeneralPath checkOverlap(PDFRenderer state) { if (style == FILL && gp != null && state.getLastShape() != null) { float mypoints[] = new float[16]; float prevpoints[] = new float[16]; int mycount = getPoints(gp, mypoints); int prevcount = getPoints(state.getLastShape(), prevpoints); // now check mypoints against prevpoints for opposite pairs: if (mypoints != null && prevpoints != null) { for (int i = 0; i < prevcount; i += 4) { for (int j = 0; j < mycount; j += 4) { if ((Math.abs(mypoints[j + 2] - prevpoints[i]) < 0.01 && Math.abs(mypoints[j + 3] - prevpoints[i + 1]) < 0.01 && Math.abs(mypoints[j] - prevpoints[i + 2]) < 0.01 && Math.abs(mypoints[j + 1] - prevpoints[i + 3]) < 0.01)) { GeneralPath strokeagain = new GeneralPath(); strokeagain.moveTo(mypoints[j], mypoints[j + 1]); strokeagain.lineTo(mypoints[j + 2], mypoints[j + 3]); return strokeagain; } } } } } // no issues return null; } /** * Get an array of 16 points from a path * @return the number of points we actually got */ private int getPoints(GeneralPath path, float[] mypoints) { int count = 0; float x = 0; float y = 0; float startx = 0; float starty = 0; float[] coords = new float[6]; PathIterator pi = path.getPathIterator(new AffineTransform()); while (!pi.isDone()) { if (count >= mypoints.length) { mypoints = null; break; } int pathtype = pi.currentSegment(coords); switch (pathtype) { case PathIterator.SEG_MOVETO: startx = x = coords[0]; starty = y = coords[1]; break; case PathIterator.SEG_LINETO: mypoints[count++] = x; mypoints[count++] = y; x = mypoints[count++] = coords[0]; y = mypoints[count++] = coords[1]; break; case PathIterator.SEG_QUADTO: x = coords[2]; y = coords[3]; break; case PathIterator.SEG_CUBICTO: x = mypoints[4]; y = mypoints[5]; break; case PathIterator.SEG_CLOSE: mypoints[count++] = x; mypoints[count++] = y; x = mypoints[count++] = startx; y = mypoints[count++] = starty; break; } pi.next(); } return count; } /** Get detailed information about this shape */ @Override public String getDetails() { StringBuffer sb = new StringBuffer(); Rectangle2D b = gp.getBounds2D(); sb.append("ShapeCommand at: " + b.getX() + ", " + b.getY() + "\n"); sb.append("Size: " + b.getWidth() + " x " + b.getHeight() + "\n"); sb.append("Mode: "); if ((style & FILL) != 0) { sb.append("FILL "); } if ((style & STROKE) != 0) { sb.append("STROKE "); } if ((style & CLIP) != 0) { sb.append("CLIP"); } return sb.toString(); } }