All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jpedal.objects.PdfShape Maven / Gradle / Ivy

The newest version!
/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/java-pdf-library-support/
 *
 * (C) Copyright 1997-2013, IDRsolutions and Contributors.
 *
 * 	This file is part of JPedal
 *
     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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * PdfShape.java
 * ---------------
 */
package org.jpedal.objects;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.io.Serializable;

import org.jpedal.parser.Cmd;
import org.jpedal.render.DynamicVectorRenderer;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.repositories.Vector_Float;
import org.jpedal.utils.repositories.Vector_Int;

/**
 * 

* defines the current shape which is created by command stream *

*

* This class is NOT part of the API *

* . Shapes can be drawn onto pdf or used as a clip on other image/shape/text. Shape is built up by storing commands and then turning these commands * into a shape. Has to be done this way as Winding rule is not necessarily declared at start. */ public class PdfShape implements Serializable { private static final long serialVersionUID = -4877252327369495970L; /** * used to stop lots of huge, complex shapes. Note we DO NOT reset as we reuse this object and it stores cumulative count */ int complexClipCount = 0; /** we tell user we have not used some shapes only ONCE */ private Vector_Float shape_primitive_x2 = new Vector_Float(1000); private Vector_Float shape_primitive_y = new Vector_Float(1000); /** store shape currently being assembled */ private Vector_Int shape_primitives = new Vector_Int(1000); /** type of winding rule used to draw shape */ private int winding_rule = Path2D.WIND_NON_ZERO; private Vector_Float shape_primitive_x3 = new Vector_Float(1000); private Vector_Float shape_primitive_y3 = new Vector_Float(1000); /** used when trying to choose which shapes to use to test furniture */ private Vector_Float shape_primitive_y2 = new Vector_Float(1000); private Vector_Float shape_primitive_x = new Vector_Float(1000); private static final int H = 3; private static final int L = 2; private static final int V = 6; /** flags for commands used */ private static final int M = 1; private static final int Y = 4; private static final int C = 5; /** flag to show if image is for clip */ private boolean isClip = false; private boolean emulateEvenOddRule = false; // /////////////////////////////////////////////////////////////////////// /** * end a shape, storing info for later */ final public void closeShape() { this.shape_primitives.addElement(H); // add empty values this.shape_primitive_x.addElement(0); this.shape_primitive_y.addElement(0); this.shape_primitive_x2.addElement(0); this.shape_primitive_y2.addElement(0); this.shape_primitive_x3.addElement(0); this.shape_primitive_y3.addElement(0); } // //////////////////////////////////////////////////////////////////////// /** * add a curve to the shape */ final public void addBezierCurveC(float x, float y, float x2, float y2, float x3, float y3) { this.shape_primitives.addElement(C); this.shape_primitive_x.addElement(x); this.shape_primitive_y.addElement(y); // add empty values to keep in sync // add empty values this.shape_primitive_x2.addElement(x2); this.shape_primitive_y2.addElement(y2); this.shape_primitive_x3.addElement(x3); this.shape_primitive_y3.addElement(y3); } // //////////////////////////////////////////////////////////////////////// /** * set winding rule - non zero */ final public void setNONZEROWindingRule() { this.winding_rule = Path2D.WIND_NON_ZERO; } // //////////////////////////////////////////////////////////////////////// /** * add a line to the shape */ final public void lineTo(float x, float y) { this.shape_primitives.addElement(L); this.shape_primitive_x.addElement(x); this.shape_primitive_y.addElement(y); // add empty values to keep in sync // add empty values this.shape_primitive_x2.addElement(0); this.shape_primitive_y2.addElement(0); this.shape_primitive_x3.addElement(0); this.shape_primitive_y3.addElement(0); } // ///////////////////////////////////////////////////////////////////////// /** * add a curve to the shape */ final public void addBezierCurveV(float x2, float y2, float x3, float y3) { this.shape_primitives.addElement(V); this.shape_primitive_x.addElement(200); this.shape_primitive_y.addElement(200); // add empty values to keep in sync // add empty values this.shape_primitive_x2.addElement(x2); this.shape_primitive_y2.addElement(y2); this.shape_primitive_x3.addElement(x3); this.shape_primitive_y3.addElement(y3); } // //////////////////////////////////////////////////////////////////////// /** * turn shape commands into a Shape object, storing info for later. Has to be done this way because we need the winding rule to initialise the * shape in Java, and it could be set anywhere in the command stream */ final public Shape generateShapeFromPath(float[][] CTM, float thickness, int cmd, int type) { boolean is_clip = this.isClip; if (cmd == Cmd.n) is_clip = false; // create the shape - we have to do it this way // because we get the WINDING RULE last and we need it // to initialise the shape GeneralPath current_path = null; Area current_area = null; Shape current_shape; // init points float[] x = this.shape_primitive_x.get(); float[] y = this.shape_primitive_y.get(); float[] x2 = this.shape_primitive_x2.get(); float[] y2 = this.shape_primitive_y2.get(); float[] x3 = this.shape_primitive_x3.get(); float[] y3 = this.shape_primitive_y3.get(); int[] command = this.shape_primitives.get(); // float lx=0,ly=0; // float xs=0,ys=0; int end = this.shape_primitives.size() - 1; // used to debug final boolean show = false; final boolean debug = false; // If type is HTML & winding rule is EvenOdd & the shape has a loop, then the EvenOdd rule needs to be emulated. // (SVG has both rules, unsure about JavaFX. If not, just add && type == DynamicVectorRendered.CREATE_JAVAFX) // Segment count > 6 is an added optimisation. Unlikely to draw a shape that this would affect with < 6 segments. if (this.winding_rule == Path2D.WIND_EVEN_ODD && type == DynamicVectorRenderer.CREATE_HTML && getSegmentCount() > 6) { int i = 0; float xx = 0; float yy = 0; while (i < end && !this.emulateEvenOddRule) { // only used to create clips if (command[i] == H) { // H is Close path this.emulateEvenOddRule = true; } else if (command[i] == L) { // L is Line to x[i], y[i] if (xx == x[i] && yy == y[i]) this.emulateEvenOddRule = true; } else if (command[i] == M) { // M is Move to x[i], y[i] xx = x[i]; yy = y[i]; } else { // Curve to x3[i], y3[i] if (xx == x3[i] && yy == y3[i]) this.emulateEvenOddRule = true; } i++; } } // loop through commands and add to shape for (int i = 0; i < end; i++) { if (current_path == null) { current_path = new GeneralPath(this.winding_rule); current_path.moveTo(x[i], y[i]); // lx=x[i]; // ly=y[i]; // xs=lx; // ys=ly; if (show) LogWriter.writeLog("==START=" + x[i] + ' ' + y[i]); } // only used to create clips if (command[i] == H) { current_path.closePath(); if (is_clip) { // current_path.lineTo(xs,ys); // current_path.closePath(); if (show) LogWriter.writeLog("==H\n\n" + current_area + ' ' + current_path.getBounds2D() + ' ' + new Area(current_path).getBounds2D()); if (current_area == null) { current_area = new Area(current_path); // trap for apparent bug in Java where small paths create a 0 size Area if ((current_area.getBounds2D().getWidth() <= 0.0) || (current_area.getBounds2D().getHeight() <= 0.0)) current_area = new Area( current_path.getBounds2D()); } else current_area.add(new Area(current_path)); current_path = null; } else { if (show) LogWriter.writeLog("close shape " + command[i] + " i=" + i); } } if (command[i] == L) { current_path.lineTo(x[i], y[i]); // lx=x[i]; // ly=y[i]; if (show) LogWriter.writeLog("==L" + x[i] + ',' + y[i] + " "); } else if (command[i] == M) { current_path.moveTo(x[i], y[i]); // lx=x[i]; // ly=y[i]; if (show) LogWriter.writeLog("==M" + x[i] + ',' + y[i] + " "); } else { // cubic curves which use 2 control points if (command[i] == Y) { if (show) LogWriter.writeLog("==Y " + x[i] + ' ' + y[i] + ' ' + x3[i] + ' ' + y3[i] + ' ' + x3[i] + ' ' + y3[i]); current_path.curveTo(x[i], y[i], x3[i], y3[i], x3[i], y3[i]); // lx=x3[i]; // ly=y3[i]; } else if (command[i] == C) { if (show) LogWriter.writeLog("==C " + x[i] + ' ' + y[i] + ' ' + x2[i] + ' ' + y2[i] + ' ' + x3[i] + ' ' + y3[i]); current_path.curveTo(x[i], y[i], x2[i], y2[i], x3[i], y3[i]); // lx=x3[i]; // ly=y3[i]; } else if (command[i] == V) { float c_x = (float) current_path.getCurrentPoint().getX(); float c_y = (float) current_path.getCurrentPoint().getY(); if (show) LogWriter.writeLog("==v " + c_x + ',' + c_y + ',' + x2[i] + ',' + y2[i] + ',' + x3[i] + ',' + y3[i]); current_path.curveTo(c_x, c_y, x2[i], y2[i], x3[i], y3[i]); // lx=x3[i]; // ly=y3[i]; } } if (debug) { try { java.awt.image.BufferedImage img = new java.awt.image.BufferedImage(700, 700, java.awt.image.BufferedImage.TYPE_INT_ARGB); Graphics2D gg2 = img.createGraphics(); gg2.setPaint(Color.RED); gg2.translate(current_path.getBounds().width + 10, current_path.getBounds().height + 10); gg2.draw(current_path); org.jpedal.gui.ShowGUIMessage.showGUIMessage("path", img, "path " + current_path.getBounds()); } catch (Exception e) { // tell user and log if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage()); } } } // second part hack for artus file with thin line if ((current_path != null) && (current_path.getBounds().getHeight() == 0 || (thickness > 0.8 && thickness < 0.9 && current_path.getBounds2D().getHeight() < 0.1f))) { if (current_path.getBounds2D().getWidth() == 0 && current_path.getBounds2D().getHeight() == 0) { // ignore this case } else if (thickness > 1 && current_path.getBounds2D().getWidth() < 1) { current_path.moveTo(0, -thickness / 2); current_path.lineTo(0, thickness / 2); } else current_path.moveTo(0, 1); } if ((current_path != null) && (current_path.getBounds().getWidth() == 0)) current_path.moveTo(1, 0); // transform matrix only if needed if ((CTM[0][0] == (float) 1.0) && (CTM[1][0] == (float) 0.0) && (CTM[2][0] == (float) 0.0) && (CTM[0][1] == (float) 0.0) && (CTM[1][1] == (float) 1.0) && (CTM[2][1] == (float) 0.0) && (CTM[0][2] == (float) 0.0) && (CTM[1][2] == (float) 0.0) && (CTM[2][2] == (float) 1.0)) { // don't waste time if not needed } else { AffineTransform CTM_transform = new AffineTransform(CTM[0][0], CTM[0][1], CTM[1][0], CTM[1][1], CTM[2][0], CTM[2][1]); // apply CTM alterations if (current_path != null) { // transform current_path.transform(CTM_transform); // if(CTM[0][0]==0 && CTM[1][1]==0 && CTM[0][1]<0 && CTM[1][0]>0){ // current_path.transform(AffineTransform.getTranslateInstance(0,current_path.getBounds().height/CTM[0][1])); // System.out.println("transforms "+CTM_transform+" "+current_path.getBounds()); // } } else if (current_area != null) current_area.transform(CTM_transform); } // set to current or clip if (is_clip == false) { if (current_area == null) current_shape = current_path; else current_shape = current_area; } else current_shape = current_area; // track complex clips if (cmd == Cmd.n && getSegmentCount() > 2500) { this.complexClipCount++; } return current_shape; } // //////////////////////////////////////////////////////////////////////// /** * add a rectangle to set of shapes */ final public void appendRectangle(float x, float y, float w, float h) { moveTo(x, y); lineTo(x + w, y); lineTo(x + w, y + h); lineTo(x, y + h); lineTo(x, y); closeShape(); } // //////////////////////////////////////////////////////////////////////// /** * start a shape by creating a shape object */ final public void moveTo(float x, float y) { this.shape_primitives.addElement(M); this.shape_primitive_x.addElement(x); this.shape_primitive_y.addElement(y); // add empty values this.shape_primitive_x2.addElement(0); this.shape_primitive_y2.addElement(0); this.shape_primitive_x3.addElement(0); this.shape_primitive_y3.addElement(0); // delete lines for grouping over boxes } /** * add a curve to the shape */ final public void addBezierCurveY(float x, float y, float x3, float y3) { this.shape_primitives.addElement(Y); this.shape_primitive_x.addElement(x); this.shape_primitive_y.addElement(y); // add empty values to keep in sync // add empty values this.shape_primitive_x2.addElement(0); this.shape_primitive_y2.addElement(0); this.shape_primitive_x3.addElement(x3); this.shape_primitive_y3.addElement(y3); } /** * reset path to empty */ final public void resetPath() { // reset the store this.shape_primitives.clear(); this.shape_primitive_x.clear(); this.shape_primitive_y.clear(); this.shape_primitive_x2.clear(); this.shape_primitive_y2.clear(); this.shape_primitive_x3.clear(); this.shape_primitive_y3.clear(); // and reset winding rule this.winding_rule = Path2D.WIND_NON_ZERO; // and reset winding rule emulation flag this.emulateEvenOddRule = false; } // ///////////////////////////////////////////////////////////////////////// /** * set winding rule - even odd */ final public void setEVENODDWindingRule() { this.winding_rule = Path2D.WIND_EVEN_ODD; } /** * show the shape segments for debugging * * static final private void showShape( Shape current_shape ) { PathIterator xx = current_shape.getPathIterator( null ); double[] coords = new * double[6]; while( xx.isDone() == false ) { int type = xx.currentSegment( coords ); xx.next(); switch( type ) { case PathIterator.SEG_MOVETO: * LogWriter.writeLog( "MoveTo" + coords[0] + ' ' + coords[1] ); if( ( coords[0] == 0 ) & ( coords[1] == 0 ) ) LogWriter.writeLog( "xxx" ); break; * * case PathIterator.SEG_LINETO: LogWriter.writeLog( "LineTo" + coords[0] + ' ' + coords[1] ); if( ( coords[0] == 0 ) & ( coords[1] == 0 ) ) * LogWriter.writeLog( "xxx" ); break; * * case PathIterator.SEG_CLOSE: LogWriter.writeLog( "CLOSE" ); break; * * default: LogWriter.writeLog( "Other" + coords[0] + ' ' + coords[1] ); break; } } }/ **/ /** * number of segments in current shape (0 if no shape or none) */ public int getSegmentCount() { if (this.shape_primitives == null) return 0; else { return this.shape_primitives.size() - 1; } } public void setClip(boolean b) { this.isClip = b; } public boolean isClip() { return this.isClip; } public boolean requiresEvenOddEmulation() { return this.emulateEvenOddRule; } public int getComplexClipCount() { return this.complexClipCount; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy