/** * pdfXtk - PDF Extraction Toolkit * Copyright (c) by the authors/contributors. All rights reserved. * This project includes code from PDFBox and TouchGraph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the names pdfXtk or PDF Extraction Toolkit; nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * http://pdfxtk.sourceforge.net * */ package at.ac.tuwien.dbai.pdfwrap.model.document; import at.ac.tuwien.dbai.pdfwrap.utils.SegmentUtils; import at.ac.tuwien.dbai.pdfwrap.utils.Utils; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.w3c.dom.Document; import org.w3c.dom.Element; import java.awt.*; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * Base class to represent a generic segment * on the page * * @author Tamir Hassan, pdfanalyser@tamirhassan.com * @version PDF Analyser 0.9 */ public class GenericSegment implements Cloneable, IXmillumSegment, Serializable { private static final long serialVersionUID = 1L; protected float x1, x2, y1, y2; boolean zeroSize = false; public GenericSegment(){ } /** * Constructor. * * @param x1 * The x1 coordinate of the segment. * @param x2 * The x2 coordinate of the segment. * @param y1 * The y1 coordinate of the segment. * @param y2 * The y2 coordinate of the segment. */ public GenericSegment(float x1, float x2, float y1, float y2) { this(); this.x1 = x1; this.x2 = x2; this.y1 = y1; this.y2 = y2; } public GenericSegment(float[] boundingBox) { this(); this.setBoundingBox(boundingBox); } public GenericSegment(PDRectangle bBox) { this(); this.setBoundingBox(bBox); } /** * This will get the x1 position of the segment. * * @return The x1 coordinate of the segment. */ public float getX1() { return x1; } /** * @param scale * The x1 to set. */ public void setX1(float x1) { this.x1 = x1; } /** * This will get the x2 position of the segment. * * @return The x2 coordinate of the segment. */ public float getX2() { return x2; } /** * @param scale * The x2 to set. */ public void setX2(float x2) { this.x2 = x2; } /** * This will get the y1 position of the segment. * * @return The y1 coordinate of the segment. */ public float getY1() { return y1; } /** * @param scale * The y1 to set. */ public void setY1(float y1) { this.y1 = y1; } /** * This will get the y2 position of the segment. * * @return The y2 coordinate of the segment. */ public float getY2() { return y2; } /** * @param scale * The y2 to set. */ public void setY2(float y2) { this.y2 = y2; } public float getXmid() { return (x1 + x2) / 2; } public float getYmid() { return (y1 + y2) / 2; } /** * This will get the width of the segment. * * @return The width of the segment. */ public float getWidth() { return (x2 - x1); } /** * This will get the height of the segment. * * @return The height of the segment. */ public float getHeight() { return (y2 - y1); } /** * This will return the area of the segment. * * @return The area of the segment (in pt^2). */ public float getArea() { return getHeight() * getWidth(); } // note: these methods assume no negative widths! // TODO: implement all Allen relations @Override public String toString() { return getAttributes().toString(); } /** * Merges this segment with the input segment. * Make sure to include everything you want your segments to share within the body of this or inherited methods. * * @param seg A segment you want to merge with */ public void mergeSegment(GenericSegment seg) { //TODO: better to create new objects? this is dangerous //Enlarges the bounding rectangle to fit the biggest bounds x1 = (seg.x1 < x1) ? seg.x1 : x1; x2 = (seg.x2 > x2) ? seg.x2 : x2; y1 = (seg.y1 < y1) ? seg.y1 : y1; y2 = (seg.y2 > y2) ? seg.y2 : y2; } /** * Getter method for attributes * * @return returns a list of all attributes that this segments contains together with the segment type */ public List<AttributeTuple> getAttributes() { ArrayList<AttributeTuple> attributeList = new ArrayList<AttributeTuple>(); //Add segment type for later recognition purposes attributeList.add(new AttributeTuple("type", tagName())); //Add new attributes here attributeList.add(new AttributeTuple("x1", x1)); attributeList.add(new AttributeTuple("y1", y1)); attributeList.add(new AttributeTuple("x2", x2)); attributeList.add(new AttributeTuple("y2", y2)); return attributeList; } public void setElementAttributes(Document resultDocument, Element newSegmentElement, GenericSegment pageDim, float resolution) { float pageHeight = pageDim.getHeight(); //float pageHeight = pageDim.getY2() + pageDim.getY1(); newSegmentElement.setAttribute("x", Float.toString(this.getX1() * resolution / Utils.PDF_POINT_RESOLUTION)); newSegmentElement.setAttribute("y", Float.toString((pageHeight - this.getY1() - this.getHeight()) * resolution / Utils.PDF_POINT_RESOLUTION)); newSegmentElement.setAttribute("w", Float.toString(this.getWidth() * resolution / Utils.PDF_POINT_RESOLUTION)); newSegmentElement.setAttribute("h", Float.toString(this.getHeight() * resolution / Utils.PDF_POINT_RESOLUTION)); } public void correctNegativeDimensions() { if (x1 > x2) { float temp = x1; x1 = x2; x2 = temp; } if (y1 > y2) { float temp = y1; y1 = y2; y2 = temp; } } public void enlargeCoordinates(float amount) { x1 = x1 - amount; x2 = x2 + amount; y1 = y1 - amount; y2 = y2 + amount; } public void scaleCoordinates(float multiple) { x1 = x1 * multiple; x2 = x2 * multiple; y1 = y1 * multiple; y2 = y2 * multiple; } public void invertYCoordinates(GenericSegment page) { float pageHeight = page.getHeight(); y1 = pageHeight - y1; y2 = pageHeight - y2; } public void rotate(float pointX, float pointY, int amount) { // if (amount == null) return; float px1 = x1 - pointX, px2 = x2 - pointX; float py1 = y1 - pointY, py2 = y2 - pointY; if (amount == 90 || amount == -270) { x1 = pointX - py2; x2 = pointX - py1; y1 = pointY + px1; y2 = pointY + px2; } else if (amount == 270 || amount == -90) { x1 = pointX + py1; x2 = pointX + py2; y1 = pointY - px2; y2 = pointY - px1; } } public void rotate(PDPage page) { GenericSegment mediaBox = new GenericSegment(page.getMediaBox()); if (page.getRotation() != null) { rotate(mediaBox.getX1(), mediaBox.getY1(), page.getRotation()); if (page.getRotation() == 90 || page.getRotation() == -270) { x1 = x1 + mediaBox.getHeight(); x2 = x2 + mediaBox.getHeight(); } else if (page.getRotation() == 270 || page.getRotation() == -90) { y1 = y1 + mediaBox.getWidth(); y2 = y2 + mediaBox.getWidth(); } } } /* * 22.01.07 * added to make processing faster when recalculating * bbox of a composite segment after only one element * has been added (clustering) * */ public void growBoundingBox(GenericSegment thisSegment) { if (thisSegment.getX1() < x1) x1 = thisSegment.getX1(); if (thisSegment.getY1() < y1) y1 = thisSegment.getY1(); if (thisSegment.getX2() > x2) x2 = thisSegment.getX2(); if (thisSegment.getY2() > y2) y2 = thisSegment.getY2(); } public void shrinkBoundingBox(GenericSegment thisSegment) { if (SegmentUtils.intersects(this, thisSegment)) { if (thisSegment.getX1() > x1) x1 = thisSegment.getX1(); if (thisSegment.getY1() > y1) y1 = thisSegment.getY1(); if (thisSegment.getX2() < x2) x2 = thisSegment.getX2(); if (thisSegment.getY2() < y2) y2 = thisSegment.getY2(); } else this.setZeroSize(true); // intersection is 0 } public boolean isZeroSize() { return zeroSize; } public void setZeroSize(boolean zeroSize) { this.zeroSize = zeroSize; } public Rectangle getBoundingRectangle() { int x = (int) x1; int y = (int) y1; int w = (int) this.getWidth(); int h = (int) this.getHeight(); return new Rectangle(x, y, w, h); } public void setBoundingBox(float[] bBox) { // TODO: not sure if we should bother // with exception stuff? x1 = bBox[0]; x2 = bBox[1]; y1 = bBox[2]; y2 = bBox[3]; } public void setBoundingBox(PDRectangle bBox) { x1 = bBox.getLowerLeftX(); x2 = bBox.getUpperRightX(); y1 = bBox.getLowerLeftY(); y2 = bBox.getUpperRightY(); } public float[] getBoundingBox() { float[] bBox = { x1, x2, y1, y2 }; return bBox; } public PDRectangle toPDRectangle() { PDRectangle retVal = new PDRectangle(); retVal.setLowerLeftX(x1); retVal.setLowerLeftY(y1); retVal.setUpperRightX(x2); retVal.setUpperRightY(y2); return retVal; } /** * @return Returns a clone of this segment, i.e. * the co-ordinates and level * TODO: implement for sub-objects */ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { // This should never happen throw new InternalError(e.toString()); } } public String tagName() { // if (classification == C_WRAPPING_INSTANCE) // return "wrapping-instance"; // else if (classification == C_SUB_INSTANCE) // return "sub-instance"; //String className = this.getClass().getSimpleName(); String className = Utils.stripClassName(this.getClass().getName()); StringBuffer retVal = new StringBuffer(className.length()); boolean afterHyphen = true; for (int n = 0; n < className.length(); n ++) { String chr = className.substring(n, n + 1); if (chr.toLowerCase().equals(chr)) { // lower case; just add retVal.append(chr); afterHyphen = false; } else { if (!afterHyphen) { retVal.append("-"); retVal.append(chr.toLowerCase()); afterHyphen = true; } else { retVal.append(chr.toLowerCase()); afterHyphen = true; } } } return retVal.toString(); } public void addAsXmillum(Document resultDocument, Element parent, GenericSegment pageDim, float resolution) { //System.out.println(this); Element newSegmentElement = resultDocument .createElement(tagName()); this.setElementAttributes(resultDocument, newSegmentElement, pageDim, resolution); parent.appendChild(newSegmentElement); } }