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

org.jpedal.render.output.OutputShape 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


 *
 * ---------------
 * OutputShape.java
 * ---------------
 */

package org.jpedal.render.output;

import java.awt.BasicStroke;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.PdfPageData;
import org.jpedal.parser.Cmd;

public class OutputShape {
	private static final boolean ENABLE_CROPPING = true;

	// track if shape not closed in FXML
	protected boolean shapeIsOpen = false;

	boolean hasMoveTo = false;

	protected int shapeCount = 0;

	double minXcoord = 999999, minYcoord = 999999;

	protected List pathCommands; // generics not supported in java ME
	protected int currentColor;
	protected Rectangle cropBox;
	private Point2D midPoint;

	private double[] lastVisiblePoint;
	private double[] lastInvisiblePoint;
	private double[] previousPoint;
	private boolean isPathSegmentVisible;
	protected double[] entryPoint;

	boolean includeClip = false;

	private double[] exitPoint;

	// For rectangle larger than cropBox
	private List largeBox;
	private boolean isLargeBox;
	private boolean largeBoxSideAlternation; // Flag to check that lines are alternating between horizontal and vertical.

	static int debug = 0;

	Rectangle clipBox = null;

	private int pageRotation = 0;

	// applied to the whole page. Default= 1
	protected float scaling;

	private boolean debugPath = false;

	int pathCommand;

	private float[][] ctm;

	private Shape currentShape;
	private int minX, minY;

	// Cmd.F or Cmd.S or Cmd.B for type of shape
	protected int cmd = -1;

	int adjustY = 0;

	public OutputShape(int cmd, float scaling, Shape currentShape, GraphicsState gs, AffineTransform scalingTransform, Point2D midPoint,
			Rectangle cropBox, int currentColor, int dpCount, int pageRotation, PdfPageData pageData, int pageNumber, boolean includeClip) {

		// used by HTML to flag if we clip with clip or in our code
		this.includeClip = includeClip;

		// adjust for rounding
		if (currentShape.getBounds2D().getHeight() > 0.6f && currentShape.getBounds2D().getHeight() < 1.0f
				&& currentShape.getBounds2D().getWidth() > 1f) {

			double diff = currentShape.getBounds2D().getMinY() - currentShape.getBounds().y;
			if (diff > 0.7) {
				this.adjustY = 1;//
			}
		}

		this.cmd = cmd;

		if (cmd == Cmd.Tj) {
			if (cropBox != null) {
				this.minX = (int) cropBox.getMinX();
				this.minY = (int) cropBox.getMinY();
			}
		}
		else {
			this.minX = pageData.getCropBoxX(pageNumber);
			this.minY = pageData.getCropBoxY(pageNumber);
		}

		if (this.debugPath) System.out.println("raw shape=" + currentShape.getBounds() + " minx=" + this.minX + ' ' + this.minY);

		// get any Clip (only rectangle outline)
		Area clip = gs.getClippingShape();
		if (clip != null) {
			this.clipBox = clip.getBounds();
		}
		else this.clipBox = null;

		// adjust for offsets
		if ((this.minX != 0 || this.minY != 0) && this.clipBox != null && cropBox != null) {
			this.clipBox.translate(-this.minX, -this.minY);
		}

		this.currentShape = currentShape;
		this.ctm = gs.CTM;
		this.scaling = scaling;
		this.currentColor = currentColor;
		this.cropBox = cropBox;
		this.midPoint = midPoint;

		this.pageRotation = pageRotation;

		this.isPathSegmentVisible = true;
		this.lastVisiblePoint = new double[2];
		this.lastInvisiblePoint = new double[2];
		this.previousPoint = new double[2];
		this.exitPoint = new double[2];
		this.entryPoint = new double[2];

		this.pathCommands = new ArrayList();
		this.largeBox = new ArrayList();
		this.isLargeBox = true; // Prove wrong
	}

	boolean isFilled = false, evenStroke = true;

	protected void generateShapeFromG2Data(GraphicsState gs, AffineTransform scalingTransform, Rectangle cropBox) {

		/** Start of fix for pixel perfect lines. Ask Leon if confused **/
		this.isFilled = gs.getFillType() == GraphicsState.FILL;
		int strokeWidth = (int) ((gs.getCTMAdjustedLineWidth() * (this.scaling)) + 0.99); // It is possible it may be worth dividing the scaling by
																							// 1.33f (To get same thickness as PDF @ 100%)
		this.evenStroke = strokeWidth != 0 && strokeWidth % 2 == 0;
		gs.setOutputLineWidth(strokeWidth);
		/** End of fix for pixel perfect lines **/

		PathIterator it = this.currentShape.getPathIterator(scalingTransform);

		// debugPath=cmd==Cmd.n && currentShape.getBounds().x==0 && currentShape.getBounds().y==44;

		this.shapeIsOpen = true;

		beginShape();

		boolean firstDrawCommand = true && ENABLE_CROPPING;

		if (this.debugPath) {
			System.out.println("About to generate commands for shape with bounds" + this.currentShape.getBounds());
			System.out.println("------------------------------------------------");
			System.out.println("crop bounds=" + cropBox.getBounds());
			System.out.println("shape bounds=" + this.currentShape.getBounds());
			System.out.println("minX=" + this.minX + " minY=" + this.minY);
			if (this.clipBox != null) System.out.println("clip bounds=" + this.clipBox.getBounds());
		}

		int segCount = 0;

		double[] lastPt;

		while (!it.isDone()) {
			double[] coords = { 0, 0, 0, 0, 0, 0 };

			this.pathCommand = it.currentSegment(coords);
			segCount++;

			if (cropBox != null) {
				for (int ii = 0; ii < 3; ii++) {
					coords[ii * 2] = coords[ii * 2] - this.minX;
					coords[(ii * 2) + 1] = coords[(ii * 2) + 1] - this.minY;
				}
			}

			if (this.debugPath) System.out.println("\n=======Get pathCommand segment " + (int) coords[0] + ' ' + (int) coords[1] + ' '
					+ (int) coords[2] + ' ' + (int) coords[3] + ' ' + (int) coords[4] + ' ' + (int) coords[5]);

			boolean isCropBoxDrawn = false;
			if (this.cmd != Cmd.n) isCropBoxDrawn = checkLargeBox(coords, this.pathCommand);

			// isCropBoxDrawn=false;
			// avoid duplicate lineto if box drawn (ie sample_pdfs_html/general/test22.pdf page 1
			if (isCropBoxDrawn) {
				it.next();
				continue;
			}

			if (firstDrawCommand) { // see if starts visible
				int dx = (int) coords[getCoordOffset(this.pathCommand)];
				int dy = (int) coords[getCoordOffset(this.pathCommand) + 1];

				// use CropBox or clip (whichever is smaller)
				Rectangle cropBoxtoTest = cropBox;

				if (this.clipBox != null) {
					cropBoxtoTest = this.clipBox;
				}

				// avoid rounding error in comparison
				if (cropBoxtoTest != null && cropBoxtoTest.width > 1) {
					if (dx == cropBoxtoTest.x) dx = dx + 1;
					else
						if (dx == (cropBoxtoTest.x + cropBoxtoTest.width)) dx = dx - 1;
				}

				this.isPathSegmentVisible = cropBoxtoTest == null || cropBoxtoTest.contains(dx, dy);
				firstDrawCommand = false;

				if (this.debugPath) System.out.println("isPathSegmentVisible=" + this.isPathSegmentVisible);

			}

			if (this.pathCommand == PathIterator.SEG_CUBICTO) {

				boolean isPointVisible = testDrawLimits(coords, PathIterator.SEG_CUBICTO);
				if (this.debugPath) System.out.println("PathIterator.SEG_CUBICTO isPointVisible=" + isPointVisible);

				if (isPointVisible && this.isPathSegmentVisible) bezierCurveTo(coords);

				// flag incase next item is a line
				this.isPathSegmentVisible = isPointVisible;

			}
			else
				if (this.pathCommand == PathIterator.SEG_LINETO) {

					lastPt = new double[] { this.previousPoint[0], this.previousPoint[1] };

					boolean isPointVisible = testDrawLimits(coords, PathIterator.SEG_LINETO);

					if (this.debugPath) System.out.println("PathIterator.SEG_LINETO isPointVisible=" + isPointVisible + " isPathSegmentVisible="
							+ this.isPathSegmentVisible + ' ' + (int) coords[0] + ' ' + (int) coords[1]);

					if (isPointVisible && this.isPathSegmentVisible) {

						if (this.debugPath) System.out.println("pdf.lineTo(" + coordsToStringParam(coords, 2) + ')');

						lineTo(coords);

					}
					else
						if (isPointVisible != this.isPathSegmentVisible) {

							if (!this.isPathSegmentVisible) {

								if (gs.getFillType() != GraphicsState.FILL) {
									moveTo(this.entryPoint);
									this.hasMoveTo = true;

									if (this.debugPath) System.out.println("pdf.moveTo(" + coordsToStringParam(coords, 2));
								}
								else {
									// if it has not entered box yet, work out which corner to start at
									if (!this.hasMoveTo && !cropBox.contains(lastPt[0], lastPt[1])) {

										double cx = cropBox.x + cropBox.width;
										double cy = cropBox.y + cropBox.height;

										if (cx > this.lastInvisiblePoint[0]) cx = cropBox.x;

										if (this.lastInvisiblePoint[1] < cropBox.y) cy = cropBox.y;
										else
											if (cy < this.lastInvisiblePoint[1]) cy = cropBox.y;

										double[] cornerEntryPt = new double[] { cx, cy };
										this.hasMoveTo = true;
										moveTo(cornerEntryPt);

										if (this.debugPath) System.out.println("pdf.moveTo(" + coordsToStringParam(cornerEntryPt, 2));

									}

									lineTo(this.entryPoint);
								}

								lineTo(coords);

								if (this.debugPath) System.out.println("pdf.lineTo(" + coordsToStringParam(coords, 2) + ");");

								this.isPathSegmentVisible = true;
							}
							else {

								lineTo(this.exitPoint);

								this.isPathSegmentVisible = false;

								if (this.debugPath) System.out.println("pdf.lineTo(" + coordsToStringParam(this.exitPoint, 2) + ");");
							}
						}
				}
				else
					if (this.pathCommand == PathIterator.SEG_QUADTO) {

						if (this.debugPath) System.out.println("PathIterator.SEG_QUADTO");

						if (testDrawLimits(coords, PathIterator.SEG_QUADTO)) {

							if (this.debugPath) System.out.println("pdf.quadraticCurveTo(" + coordsToStringParam(coords, 4));

							quadraticCurveTo(coords);
							this.isPathSegmentVisible = true;

						}
						else {
							this.isPathSegmentVisible = false;
						}
					}
					else
						if (this.pathCommand == PathIterator.SEG_MOVETO) {
							if (this.debugPath) System.out.println("PathIterator.SEG_MOVETO");
							if (testDrawLimits(coords, PathIterator.SEG_MOVETO)) {
								this.isPathSegmentVisible = true;
								if (this.debugPath) System.out.println("pdf.moveTo(" + coordsToStringParam(coords, 2) + ");");
								this.hasMoveTo = true;
								moveTo(coords);

							}
							else {
								this.isPathSegmentVisible = false;
							}
						}
						else
							if (this.pathCommand == PathIterator.SEG_CLOSE) {
								if (this.debugPath) System.out.println("pdf.closePath();");
								closePath();
							}
			it.next();
		}

		if (this.pathCommands.size() == 1) {
			// No commands made it through
			this.pathCommands.clear();
		}
		else {

			if (this.cmd == Cmd.Tj || (!this.isPathSegmentVisible && gs.getFillType() != GraphicsState.FILL)) {

				this.shapeIsOpen = false;

				finishShape();

				if (this.debugPath) System.out.println("pdf.closePath();");
			}
			applyGraphicsStateToPath(gs);
		}

		// ignore rect if totally outside clip
		if (this.cmd == Cmd.S && segCount == 6 && this.clipBox != null && this.currentShape.getBounds().width > 10
				&& this.currentShape.getBounds().height > 10 && this.clipBox.x >= this.currentShape.getBounds().x
				&& this.clipBox.x + this.clipBox.width <= this.currentShape.getBounds().x + this.currentShape.getBounds().width
				&& this.clipBox.y >= this.currentShape.getBounds().y
				&& this.clipBox.y + this.clipBox.height <= this.currentShape.getBounds().y + this.currentShape.getBounds().height) {
			this.pathCommands.clear();
			if (this.debugPath) System.out.println("shape removed");
		}

		// only does something on FXML
		checkShapeClosed();
		this.isFilled = false;
	}

	public void checkShapeClosed() {
		// only does something on FXML which overrides this
	}

	// empty version
	protected void moveTo(double[] coords) {}

	// empty version
	protected void quadraticCurveTo(double[] coords) {}

	// empty version
	protected void lineTo(double[] coords) {}

	// empty version
	protected void closePath() {}

	// empty version
	protected void finishShape() {}

	// empty version
	protected void beginShape() {}

	// empty version
	protected void bezierCurveTo(double[] coords) {}

	/**
	 * trim if needed
	 * 
	 * @param i
	 * @return
	 * 
	 *         (note duplicate in HTMLDisplay)
	 */
	private String setPrecision(double i, boolean isX) {

		// New pixel perfect version
		String value = "";

		if (this.isFilled || this.evenStroke) {
			value = "" + (int) (i);
		}
		else {
			int num = (int) i;
			value += num + ".5";
		}

		// track corner so we can reuse if needed for shading
		if (isX && i < this.minXcoord) this.minXcoord = i;
		else
			if (!isX && i < this.minYcoord) this.minYcoord = i;

		return value;
	}

	/**
	 * Extracts information out of graphics state to use in HTML - empty version
	 */
	protected void applyGraphicsStateToPath(GraphicsState gs) {}

	/**
	 * Extract line cap attribute
	 */
	protected static String determineLineCap(BasicStroke stroke) {
		// attribute DOMString lineCap; // "butt", "round", "square" (default "butt")
		String attribute;

		switch (stroke.getEndCap()) {
			case (BasicStroke.CAP_ROUND):
				attribute = "round";
				break;
			case (BasicStroke.CAP_SQUARE):
				attribute = "square";
				break;
			default:
				attribute = "butt";
				break;
		}
		return attribute;
	}

	/**
	 * Extract line join attribute
	 */
	protected static String determineLineJoin(BasicStroke stroke) {
		// attribute DOMString lineJoin; // "round", "bevel", "miter" (default "miter")
		String attribute;
		switch (stroke.getLineJoin()) {
			case (BasicStroke.JOIN_ROUND):
				attribute = "round";
				break;
			case (BasicStroke.JOIN_BEVEL):
				attribute = "bevel";
				break;
			default:
				attribute = "miter";
				break;
		}
		return attribute;
	}

	/**
	 * @return true if coordinates lie with visible area
	 */
	private boolean testDrawLimits(double[] coords, int pathCommand) {

		// html uses clipping so we do not alter shapes
		if (this.includeClip) return true;

		if (this.debugPath) System.out.println("testDrawLimits coords[0] + coords[1]=" + (int) coords[0] + ' ' + (int) coords[1]);

		if (!ENABLE_CROPPING) {
			return true;
		}

		int offset = getCoordOffset(pathCommand);

		// assume not visible by default
		boolean isCurrentPointVisible;

		if (this.debugPath) System.out.println("crop=" + this.cropBox + " clip=" + this.clipBox);

		// use CropBox or clip (whichever is smaller)
		Rectangle cropBox = this.cropBox;

		if (this.clipBox != null) {
			cropBox = cropBox.intersection(this.clipBox);

			if (this.debugPath) System.out.println("merged crop=" + cropBox);
		}

		/**
		 * turn co-ords into a Point (the 1s are to allow for rounding errors)
		 */
		double x = coords[offset];
		if (x > cropBox.x + 1) x = x - 1;
		double y = coords[offset + 1];
		if (y > cropBox.y + 1) y = y - 1;
		double[] point = { x, y };

		if (cropBox.contains(point[0], point[1])) {
			this.lastVisiblePoint = point;
			isCurrentPointVisible = true;

			if (this.debugPath) System.out.println("Point visible in cropBox or clip");

		}
		else { // this point outside visible area
			this.lastInvisiblePoint = point;
			isCurrentPointVisible = false;

			if (this.debugPath) System.out.println("Point invisible " + (int) point[0] + ' ' + (int) point[1] + " crop=" + cropBox.getBounds());
		}

		if (!isCurrentPointVisible && this.isPathSegmentVisible) {

			if (this.debugPath) System.out.println("Case1 this point " + (int) x + ',' + (int) y + " invisible and isPathSegmentVisible");

			findSwitchPoint(point, true);
		}
		else
			if (isCurrentPointVisible && !this.isPathSegmentVisible) {

				if (this.debugPath) System.out.println("Case2 this point " + (int) x + ',' + (int) y + " visible and isPathSegment invisible");

				findSwitchPoint(point, false);

			}
			else {

				if (this.debugPath) System.out.println("Case3 NOT COVERED isCurrentPointVisible=" + isCurrentPointVisible + " isPathSegmentVisible"
						+ this.isPathSegmentVisible);
			}

		// Check whether this point and last point cross crop box.
		if (!isCurrentPointVisible && (!cropBox.contains(this.previousPoint[0], this.previousPoint[1])) && pathCommand == PathIterator.SEG_LINETO) {

			if (this.debugPath) System.out.println("checkTraversalPoints");

			checkTraversalPoints(point, this.previousPoint, cropBox);

		}

		this.previousPoint = point;

		return isCurrentPointVisible;
	}

	/**
	 * Figure out where to draw a line if the given points do not lie within the cropbox but should draw a line because they pass over it.
	 */
	private void checkTraversalPoints(double[] startPoint, double[] endPoint, Rectangle cropBox) {
		boolean xTraversal = (endPoint[0] < cropBox.x && startPoint[0] > (cropBox.x + cropBox.width))
				|| (startPoint[0] < cropBox.x && endPoint[0] > (cropBox.x + cropBox.width));
		boolean yTraversal = (endPoint[1] < cropBox.y && startPoint[1] > (cropBox.y + cropBox.height))
				|| (startPoint[1] < cropBox.y && endPoint[1] > (cropBox.y + cropBox.height));
		boolean completeCropBoxMiss = isCompleteCropBoxMiss(startPoint, endPoint, cropBox);

		if (this.debugPath) {

			System.out.println("checkTraversalPoints xtrav=" + xTraversal + " yTrav=" + yTraversal + " completeCropBoxMiss=" + completeCropBoxMiss
					+ ' ' + (endPoint[0] < cropBox.x && startPoint[0] > (cropBox.x + cropBox.width)));
			System.out.println("start=" + (int) startPoint[0] + ' ' + (int) startPoint[1] + "  end=" + (int) endPoint[0] + ' ' + (int) endPoint[1]);
			// System.out.println("cropBox="+cropBox.x+" "+cropBox.y+" "+cropBox.width+" "+cropBox.height);
		}

		if (!xTraversal && !yTraversal) {
			return;
		}

		// double xSide = startPoint[0] - endPoint[0];
		// double ySide = startPoint[1] - endPoint[1];

		// System.out.println((int)startPoint[0]+" "+(int)startPoint[1]+" end="+(int)endPoint[0]+" "+(int)endPoint[1]+" "+cropBox);

		// work out gradient of line
		double m = (endPoint[1] - startPoint[1]) / (endPoint[0] - startPoint[0]);
		double rawM = m;

		// allow for vertical line
		if (Math.abs(endPoint[0] - startPoint[0]) < 1) m = 0;

		if (m < 0.001) m = 0;

		// and workout y
		if (m == 0) {

			if (rawM < 0 && endPoint[0] < 0 && (endPoint[0] < startPoint[0])) {
				this.exitPoint = calcCrossoverPoint(endPoint, cropBox, m, startPoint);
				this.entryPoint = calcCrossoverPoint(startPoint, cropBox, m, endPoint);
			}
			else {
				this.entryPoint = calcCrossoverPoint(endPoint, cropBox, m, startPoint);
				this.exitPoint = calcCrossoverPoint(startPoint, cropBox, m, endPoint);
			}
		}
		else {
			this.exitPoint = calcCrossoverPoint(endPoint, cropBox, m, startPoint);
			this.entryPoint = calcCrossoverPoint(startPoint, cropBox, m, endPoint);
		}

		if (this.debugPath) {
			System.out.println("entry=" + this.entryPoint[0] + ' ' + this.entryPoint[1] + " exit=" + this.exitPoint[0] + ' ' + this.exitPoint[1]);
			System.out.println("XXpdf.lineTo(" + coordsToStringParam(this.entryPoint, 2) + ");");
			System.out.println("XXpdf.lineTo(" + coordsToStringParam(this.exitPoint, 2) + "); hasMoveTo=" + this.hasMoveTo);
		}

		// if it has not entered box yet, work out which corner to start at (see awjune p13)
		if (!this.hasMoveTo && endPoint[0] < 0 && startPoint[0] > this.entryPoint[0] && this.entryPoint[0] > this.exitPoint[0]) {

			double cx = cropBox.x + cropBox.width;
			double cy = cropBox.y + cropBox.height;
			if (cx > this.lastVisiblePoint[0]) cx = cropBox.x;
			if (cy < this.lastVisiblePoint[1]) cy = cropBox.y;

			double[] cornerEntryPt = new double[] { cx, cy };
			this.hasMoveTo = true;
			moveTo(cornerEntryPt);

			if (this.debugPath) System.out.println("pdf.moveTo(" + coordsToStringParam(cornerEntryPt, 2));
			lineTo(this.exitPoint);
			lineTo(this.entryPoint);
		}
		else {
			lineTo(this.entryPoint);
			lineTo(this.exitPoint);
		}
	}

	private static double[] calcCrossoverPoint(double[] pt, Rectangle cropBox, double m, double[] otherPt) {

		double x1 = 0, y1 = 0;

		/**
		 * case 1/2 and -above top of of cropBox or below
		 */
		// (use cropBox vertical line as Y and check if in bounds to make sure not a side line)
		if (pt[1] < cropBox.y || pt[1] > cropBox.y + cropBox.height) {

			// choose y to match
			if (pt[1] < cropBox.y) y1 = cropBox.y;
			else y1 = cropBox.y + cropBox.height;

			if (m == 0) x1 = pt[0];
			else x1 = ((y1 - pt[1]) / m) + pt[0];

			// check it crosses top line and not one of sides
			if (x1 < cropBox.x || x1 > cropBox.x + cropBox.width) { // actually it crosses as side line
				// choose left or right side
				if (x1 < cropBox.x) x1 = cropBox.x;
				else x1 = cropBox.x + cropBox.width;

				// and workout y
				y1 = (m * (x1 - pt[0])) + pt[1];
			}

			/**
			 * case 3/4 and -to left or right of cropBox
			 */
			// (use cropBox horizontal line as X and check if in bounds to make sure not a vertical)
		}
		else
			if (pt[0] < cropBox.x || pt[0] > cropBox.x + cropBox.width) {

				// choose left or right side
				if (pt[0] < cropBox.x) x1 = cropBox.x;
				else x1 = cropBox.x + cropBox.width;

				y1 = (m * (x1 - pt[0])) + pt[1];

				// check it crosses side line and not one of verticals
				if (y1 < cropBox.y || y1 > cropBox.y + cropBox.height) { // actually it crosses as side line
					// choose y to match
					if (pt[1] < cropBox.y) y1 = cropBox.y;
					else y1 = cropBox.y + cropBox.height;

					x1 = ((y1 - pt[1]) / m) + pt[0];

				}
			}

		return new double[] { x1, y1 };
	}

	/**
	 * Figure out where the line disappears or reappears off the cropbox boundary.
	 * 
	 * @param point
	 *            The current point
	 * @param exit
	 *            true if you wish to find an exit point (one that marks a disappearance)
	 */
	private void findSwitchPoint(double[] point, boolean exit) {

		double[] lastPoint;
		double[] switchPoint = new double[2];

		lastPoint = exit ? this.lastVisiblePoint : this.lastInvisiblePoint;

		if (this.debugPath) {
			if (exit) System.out.println("Find point of exit lastPoint=" + (int) lastPoint[0] + ' ' + (int) lastPoint[1] + " current="
					+ (int) point[0] + ' ' + (int) point[1]);
			else System.out.println("Find point of entry lastPoint=" + (int) lastPoint[0] + ' ' + (int) lastPoint[1] + " current=" + (int) point[0]
					+ ' ' + (int) point[1]);
		}

		if (!exit) {
			double[] tmp = point;
			point = lastPoint;
			lastPoint = tmp;
		}

		double xSide = point[0] - lastPoint[0];
		double ySide = point[1] - lastPoint[1];

		// To indicate whether a coordinate has been found
		boolean xFound = false;
		boolean yFound = false;

		if (this.clipBox != null && point[0] >= this.clipBox.width + this.clipBox.x) {
			switchPoint[0] = this.clipBox.width + this.clipBox.x;
			xFound = true;
		}
		else
			if (point[0] >= this.cropBox.width + this.cropBox.x) {
				switchPoint[0] = this.cropBox.width + this.cropBox.x;
				xFound = true;
			}
			else
				if (this.clipBox != null && point[0] < this.clipBox.x) {
					switchPoint[0] = this.clipBox.x;
					xFound = true;
				}
				else
					if (point[0] < this.cropBox.x) {
						switchPoint[0] = this.cropBox.x;
						xFound = true;
					}

		if (this.clipBox != null && point[1] > this.clipBox.height + this.clipBox.y) {
			switchPoint[1] = this.clipBox.height + this.clipBox.y;
			yFound = true;
		}
		else
			if (point[1] > this.cropBox.height + this.cropBox.y) {
				switchPoint[1] = this.cropBox.height + this.cropBox.y;
				yFound = true;
			}
			else
				if (this.clipBox != null && point[1] < this.clipBox.y) {
					switchPoint[1] = this.clipBox.y;
					yFound = true;
				}
				else
					if (point[1] < this.cropBox.y) {
						switchPoint[1] = this.cropBox.y;
						yFound = true;
					}

		if (yFound) {
			if (xSide == 0) {
				switchPoint[0] = point[0];
			}
			else {
				double tan = xSide / ySide;
				if (ySide == 0) tan = 1;
				double distanceToCropY = switchPoint[1] - point[1];
				switchPoint[0] = point[0] + (tan * distanceToCropY);
			}
		}
		if (xFound) {
			if (ySide == 0) {
				switchPoint[1] = point[1];
			}
			else {
				double tan = ySide / xSide;
				double distanceToCropX = switchPoint[0] - point[0];
				switchPoint[1] = point[1] + (tan * distanceToCropX);
			}
		}

		if (exit) {
			this.exitPoint = switchPoint;
			if (this.debugPath) System.out.println("returns exit=" + (int) switchPoint[0] + ' ' + (int) switchPoint[1]);

		}
		else {
			this.entryPoint = switchPoint;
			if (this.debugPath) System.out.println("returns entry=" + (int) switchPoint[0] + ' ' + (int) switchPoint[1]);
		}
	}

	/**
	 * Add the coords to the large box list if it might possibly be part of a rectangle.
	 * 
	 * @param coords
	 * @param pathCommand
	 */
	private boolean checkLargeBox(double[] coords, int pathCommand) {

		boolean isDrawn = false;

		if (this.debugPath) System.out.println("check large " + (int) coords[0] + ' ' + (int) coords[1]);

		if (!this.isLargeBox && (pathCommand != PathIterator.SEG_LINETO || pathCommand != PathIterator.SEG_MOVETO)) {
			return false;
		}

		double px = coords[getCoordOffset(pathCommand)], py = coords[getCoordOffset(pathCommand) + 1];
		double[] adjustedCords = this.correctCoords(new double[] { px, py });
		px = adjustedCords[0];
		py = adjustedCords[1];

		Point point = new Point((int) px, (int) py);

		if (this.largeBox.isEmpty()) {
			this.largeBox.add(point);
		}
		else {
			Point2D last = this.largeBox.get(this.largeBox.size() - 1);
			double xSide = last.getX() - point.getX();
			double ySide = last.getY() - point.getY();

			// First time we can compare so check here and ignore below.
			if (this.largeBox.size() == 1) {

				if (ySide != 0) // allow for div by zero
				this.largeBoxSideAlternation = xSide / ySide != 0;
				else this.largeBoxSideAlternation = true;
			}

			// If this point and the last point do not form a horizontal or vertical line its not part of a large rectangular shape.
			if (xSide / ySide == 0 || ySide / xSide == 0) {

				boolean currentSide = xSide / ySide == 0;

				// Ignore if its part of a continous line. The continous line could be going back on it self but currently not accounted for.
				if (this.largeBox.size() > 1 || (currentSide != this.largeBoxSideAlternation)) {
					this.largeBox.add(point);
					this.largeBoxSideAlternation = xSide / ySide == 0;
				}
			}
			else
				if (!point.equals(this.largeBox.get(this.largeBox.size() - 1))) {
					this.isLargeBox = false;
					return false;
				}

			// Check if we have enough point to see if it is larger than the whole page.
			if (this.largeBox.size() >= 4 && isLargerThanCropBox()) {
				drawCropBox();
				isDrawn = true;
			}
		}

		return isDrawn;
	}

	/**
	 * return true if the coordinates in this path specify a box larger than the cropbox.
	 */
	private boolean isLargerThanCropBox() {
		if (!this.isLargeBox || this.cropBox == null) {
			return false;
		}

		Point2D x = this.largeBox.get(this.largeBox.size() - 4);
		Point2D y = this.largeBox.get(this.largeBox.size() - 3);
		Point2D z = this.largeBox.get(this.largeBox.size() - 2);
		Point2D w = this.largeBox.get(this.largeBox.size() - 1);

		int shortestSide = this.cropBox.width < this.cropBox.height ? this.cropBox.width : this.cropBox.height;
		// Can not cover the page if this is true.
		if (x.distance(y) < shortestSide || y.distance(z) < shortestSide || z.distance(w) < shortestSide) {
			return false;
		}

		int outsideCount = 0;

		if (!this.cropBox.contains(x.getX(), x.getY())) outsideCount++;
		if (!this.cropBox.contains(y.getX(), y.getY())) outsideCount++;
		if (!this.cropBox.contains(z.getX(), z.getY())) outsideCount++;
		if (!this.cropBox.contains(w.getX(), w.getY())) outsideCount++;

		if (outsideCount <= 2) {
			return false;
		}

		Set points = new HashSet();
		points.add(x);
		points.add(y);
		points.add(z);
		points.add(w);

		outsideCount = 0;

		// Test that points lie in correct areas to justify a box covering the page.
		for (int hOffset = -1; hOffset <= 1; hOffset++) {
			for (int wOffset = -1; wOffset <= 1; wOffset++) {
				if (hOffset == 0 && wOffset == 0) { // Would mean the test rectangle is same as cropbox so ignore
					continue;
				}
				Rectangle outside = new Rectangle(this.cropBox);
				outside.translate(wOffset * this.cropBox.width, hOffset * this.cropBox.height);
				if (doesPointSetCollide(points, outside)) outsideCount++;
			}
		}

		return outsideCount >= 3;
	}

	/**
	 * return true if any of the given points are contained in the given rectangle
	 */
	private static boolean doesPointSetCollide(Set points, Rectangle rect) {
		// converted so java ME compilies
		for (Object point : points) {
			Point2D pt = (Point2D) point;
			if (rect.contains(pt)) {
				return true;
			}
		}
		return false;
	}

	protected void drawCropBox() {}

	private double getClosestCropEdgeX(double x) {
		return x < this.cropBox.x + (this.cropBox.width / 2) ? this.cropBox.x : this.cropBox.x + this.cropBox.width;
	}

	private double getClosestCropEdgeY(double y) {
		return y < this.cropBox.y + (this.cropBox.height / 2) ? this.cropBox.y : this.cropBox.y + this.cropBox.height;
	}

	/**
	 * Convert from PDF coords to java coords.
	 */
	private double[] correctCoords(double[] coords) {

		if (this.cropBox != null) {
			int offset;

			switch (this.pathCommand) {
				case (PathIterator.SEG_CUBICTO):
					offset = 4;
					break;
				case (PathIterator.SEG_QUADTO):
					offset = 2;
					break;
				default:
					offset = 0;
					break;
			}

			// ensure fits
			if (offset > coords.length) offset = coords.length - 2;

			for (int i = 0; i < offset + 2; i += 2) {

				// adjust for rounding error
				coords[i + 1] = coords[i + 1] + this.adjustY;

				coords[i] = coords[i] - this.midPoint.getX() + this.minX;
				coords[i] += this.cropBox.width / 2;

				coords[i + 1] = coords[i + 1] - this.midPoint.getY() + this.minY;
				coords[i + 1] = 0 - coords[i + 1];
				coords[i + 1] += this.cropBox.height / 2;

			}
		}

		return coords;
	}

	/**
	 * Return the index of the start coordinate
	 */
	private static int getCoordOffset(int pathCommand) {
		int offset;

		switch (pathCommand) {
			case (PathIterator.SEG_CUBICTO):
				offset = 4;
				break;
			case (PathIterator.SEG_QUADTO):
				offset = 2;
				break;
			default:
				offset = 0;
				break;
		}
		return offset;
	}

	/**
	 * Tests whether the line between the two given points crosses over the crop box.
	 * 
	 * @return true if it misses completely.
	 */
	private static boolean isCompleteCropBoxMiss(double[] start, double[] end, Rectangle cropBox) {
		int xLimMin = cropBox.x;
		int xLimMax = xLimMin + cropBox.width;
		int yLimMin = cropBox.y;
		int yLimMax = xLimMax + cropBox.height;

		return ((start[0] < xLimMin && end[0] < xLimMin) || (start[0] > xLimMax && end[0] > xLimMax))
				&& ((start[1] < yLimMin && end[1] < yLimMin) || (start[1] > yLimMax && end[1] > yLimMax));
	}

	/**
	 * Coverts an array of numbers to a String for JavaScript parameters. Removes cropbox offset.
	 * 
	 * @param coords
	 *            Numbers to change
	 * @param count
	 *            Use up to count doubles from coords array
	 * @return String Bracketed stringified version of coords (note numbers rounded to nearest int to keep down filesize)
	 */
	protected String coordsToStringParam(double[] coords, int count) {
		// make copy factoring in size
		int size = coords.length;
		double[] copy = new double[size];
		System.arraycopy(coords, 0, copy, 0, size);

		coords = correctCoords(copy);

		return convertCoords(coords, count);
	}

	protected String convertCoords(double[] coords, int count) {

		String result = "";

		int cropBoxW = 0, cropBoxH = 0;
		if (this.cropBox != null) {
			cropBoxW = this.cropBox.width;
			cropBoxH = this.cropBox.height;
		}

		switch (this.pageRotation) {
			case 90:

				// for each set of coordinates, set value
				for (int i = 0; i < count; i = i + 2) {
					if (i != 0) {
						result += ",";
					}

					if (this.ctm[0][0] == 0 && this.ctm[1][1] == 0 && this.ctm[0][1] > 0 && (this.ctm[1][0] < 0 || Math.abs(this.ctm[1][0]) < 0.5)) {// adjust
																																						// to
																																						// take
																																						// account
																																						// negative
																																						// shape
																																						// offset
						result += setPrecision(((cropBoxH - coords[i + 1]) * this.scaling), false);
						result += ",";
						result += setPrecision(coords[i] * this.scaling, (i & 1) != 1);
					}
					else {

						result += setPrecision(((cropBoxH - coords[i + 1]) * this.scaling), false);

						result += ",";
						result += setPrecision(coords[i] * this.scaling, (i & 1) != 1);// - (i%2 == 1 ? cropBox.x : cropBox.y) );
					}
				}
				break;

			case 180: {

				// convert x and y values to output coords from PDF
				for (int i = 0; i < count; i = i + 2) {

					// handle x values
					if (i != 0) {
						result += ",";
					}
					result += setPrecision((cropBoxW - coords[i]) * this.scaling, true);// - (i%2 == 1 ? cropBox.x : cropBox.y) );

					// handle y values
					if (i + 1 != 0) {
						result += ",";
					}
					// System.out.println(cropBox.height+" "+coords[i+1]);
					result += setPrecision(((cropBoxH - coords[i + 1]) * this.scaling), false);// - (i%2 == 1 ? cropBox.x : cropBox.y) );
				}
			}
				break;

			case 270: {
				// for each set of coordinates, set value
				for (int i = 0; i < count; i = i + 2) {
					if (i != 0) {
						result += ",";
					}

					if (this.ctm[0][0] == 0 && this.ctm[1][1] == 0 && this.ctm[0][1] > 0 && (this.ctm[1][0] < 0 || Math.abs(this.ctm[1][0]) < 0.5)) {// adjust
																																						// to
																																						// take
																																						// account
																																						// negative
																																						// shape
																																						// offset
						result += setPrecision(((cropBoxH - coords[i + 1]) * this.scaling), false);
						result += ",";
						result += setPrecision(coords[i] * this.scaling, (i & 1) != 1);

					}
					else {

						result += setPrecision(((coords[i + 1]) * this.scaling), false);
						// result += setPrecision((cropBox.height-coords[i+1]-pageData.getCropBoxY(pageNumber))*scaling);

						result += ",";
						result += setPrecision((cropBoxW - coords[i]) * this.scaling, (i & 1) != 1);// - (i%2 == 1 ? cropBox.x : cropBox.y) );
						// result += setPrecision((coords[i]-(pageData.getCropBoxX(pageNumber)/2))*scaling);//- (i%2 == 1 ? cropBox.x : cropBox.y) );
					}
				}
			}
				break;

			default: {

				// convert x and y values to output coords from PDF
				for (int i = 0; i < count; i = i + 2) {

					// handle x values
					if (i != 0) {
						result += ",";
					}
					result += setPrecision(coords[i] * this.scaling, true);// - (i%2 == 1 ? cropBox.x : cropBox.y) );

					// handle y values
					if (i + 1 != 0) {
						result += ",";
					}
					result += setPrecision((coords[i + 1] * this.scaling), false);// - (i%2 == 1 ? cropBox.x : cropBox.y) );
				}
			}
		}
		return result;
	}

	/**
	 * Formats an int to CSS rgb(r,g,b) string
	 */
	public static String rgbToCSSColor(int raw) {
		int r = (raw >> 16) & 255;
		int g = (raw >> 8) & 255;
		int b = raw & 255;

		return "rgb(" + r + ',' + g + ',' + b + ')';
	}

	public String getContent() {
		int size = this.pathCommands.size();

		StringBuilder result = new StringBuilder(size * 15);

		for (int i = 0; i < size; i++) {

			// @TODO Hack to backspace out tab so as to not break test.
			if (i != 0) {
				result.append('\t');
			}
			result.append(this.pathCommands.get(i));
			if (i != (size - 1)) {
				result.append('\n');
			}
		}

		return result.toString();
	}

	public boolean isEmpty() {
		return this.pathCommands.isEmpty();
	}

	public int getShapeColor() {
		return this.currentColor;
	}

	public double getMinXcoord() {
		return this.minXcoord;
	}

	public double getMinYcoord() {
		return this.minYcoord;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy