public class X1Comparator implements Comparator<GenericSegment>
{
	public int compare(GenericSegment obj1, GenericSegment obj2)
	{
		// sorts in x order
		double x1 = obj1.getX1();
		double x2 = obj2.getX1();

//			causes a contract violation (rounding?)
//			return (int) (x1 - x2);
		
		if (x2 > x1) return -1;
		else if (x2 == x1) return 0;
		else return 1;
	}

	public boolean equals(Object obj)
	{
		return obj.equals(this);
	}
} public class CharSegment extends TextSegment
{
	boolean overprint = false;
	protected OpTuple sourceOp;
	
    /**
     * 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.
     * @param text The textual contents of the segment.
     * @param font The (main) font of the segment.
     * @param fontSize The (main) font size in the segment.
     */
    public CharSegment(
        float x1,
        float x2,
        float y1,
        float y2,
        String text,
        String fontName,
        float fontSize
        )
    {
		super(x1, x2, y1, y2, text, fontName, fontSize);
    }
    
    public CharSegment(
            float x1,
            float x2,
            float y1,
            float y2,
            String text,
            PDFont font,
            float fontSize,
            OpTuple sourceOp
            )
        {
    		super(x1, x2, y1, y2, text, Utils.findFontName(font), fontSize);
    		this.sourceOp = sourceOp;
        }
    
    public CharSegment(
        float x1,
        float x2,
        float y1,
        float y2
        )
    {
		super(x1, x2, y1, y2);
    }

    // not in current use (I think) -- WRONG
    // now by default sets level to zero (primitive)
    public CharSegment(TextPosition tPos) {
        super(tPos.getX(),
                tPos.getX() + (tPos.getWidth()),
                tPos.getY(),
                tPos.getY() + (tPos.getFontSize() * tPos.getYScale()),
//              tPos.getCharacter(),
                tPos.getUnicode(),
//              tPos.getFont().getBaseFont(),
                Utils.findFontName(tPos.getFont()),
                tPos.getFontSize() * tPos.getYScale());


        // todo: trim the name of the font
//      String fontName = tPos.getFont().getBaseFont();
        String fontName = tPos.getFont().getFontDescriptor().getFontName();
    }

	/**
     * This will create a TextFragment object from a TextPosition object.
     * As of PDFBox 0.7.2, this is the method currently in use, which
	 * converts co-ordinates back to the original system.
	 *
     * @param tPos - the TextPosition object; pageDim - page dimensions in order to
	 * convert co-ordinates
	 * @return The new TextFragment object
     */
    public CharSegment(TextPosition tPos, GenericSegment pageDim) 
    {
        super(tPos.getX(),
                tPos.getX() + tPos.getWidth(),
                pageDim.getY2() - tPos.getY(),
                pageDim.getY2() - tPos.getY() + (tPos.getFontSize() * tPos.getYScale()),
//              tPos.getCharacter(),
                tPos.getUnicode(),
//              tPos.getFont().getBaseFont(),
                Utils.findFontName(tPos.getFont()),
                tPos.getFontSize() * tPos.getYScale());
    }
    
    @Override
    public List<AttributeTuple> getAttributes() {

    	List<AttributeTuple> attributeList = super.getAttributes();

    	if (sourceOp != null)
    	{
    		attributeList.add(new AttributeTuple("opindex", sourceOp.getOpIndex()));
    		attributeList.add(new AttributeTuple("argindex", sourceOp.getArgIndex()));
    	}
    	
    	return attributeList;
    }
    
	public boolean isOverprint() {
		return overprint;
	}

	public void setOverprint(boolean overprint) {
		this.overprint = overprint;
	}

	public OpTuple getSourceOp() {
		return sourceOp;
	}

	public void setSourceOp(OpTuple sourceOp) {
		this.sourceOp = sourceOp;
	}
} public class CompositeSegment<T extends GenericSegment> extends TextSegment 
	implements Cloneable
{
	// TODO: multiple inheritance (sort of); create a
	// CompoundSegment.
	protected List<T> items;
	
//	boolean preserveLineBreaks = false;
	boolean preserveLineBreaks = true;
//	double threshMult = 0.15;
//	double threshMult = 0.25;
	double threshMult = 0.15;

//	protected boolean constantFont = true;
//	protected boolean constantFontSize = true;
	
	/**
	 * 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.
	 * @param text
	 *            The textual contents of the segment.
	 * @param font
	 *            The (main) font of the segment.
	 * @param fontSize
	 *            The (main) font size in the segment.
	 */

	public CompositeSegment(float x1, float x2, float y1, float y2,
			String text, String fontName, float fontSize)
	{
		super(x1, x2, y1, y2, text, fontName, fontSize);
		this.items = new ArrayList<T>();
	}
	

	public CompositeSegment(float x1, float x2, float y1, float y2)
	{
		super(x1, x2, y1, y2);
		this.items = new ArrayList<T>();
	}

	public CompositeSegment(float x1, float x2, float y1, float y2,
			String text, String fontName, float fontSize, List<T> items)
	{
		super(x1, x2, y1, y2, text, fontName, fontSize);
		this.items = items;
	}

	public CompositeSegment(float x1, float x2, float y1, float y2,
			List<T> items)
	{
		super(x1, x2, y1, y2);
		this.items = items;
	}

	public CompositeSegment(List<T> items)
	{
		super();
		this.items = items;
	}

	public CompositeSegment()
	{
		// most common method if initialization now
		// the fields are filled once all the items have
		// been added...
		super();
		this.items = new ArrayList<T>();
	}

	public List<T> getItems() {
		return items;
	}

	public T getFirstItem() {
		return items.get(0);
	}
	
	public T getLastItem() {
		return items.get(items.size() - 1);
	}

	public void setItems(List<T> items) {
		this.items = items;
	}


	/**
	 * @return Returns a clone of this segment, i.e.
	 * the co-ordinates and other attributes and a
	 * _shallowly cloned_ list sub-objects
	 */
	public Object clone()
	{
		CompositeSegment retVal = (CompositeSegment)super.clone();
		
		// 2011-01-24 List is not cloneable
		List<T> cloneList = new ArrayList<T>();
		cloneList.addAll(this.items);
		
		retVal.items = cloneList;
		
		return retVal;
	}

	// overrides super with number of items
	public String toString()
	{
		return tagName() + " no. items: " + items.size() + " - " + getAttributes(); 
	}
	
	public void printSubItems()
    {
		System.out.println(this);
		printSubItems(0);
    }
	
	public void printSubItems(int indent)
    {
//    	System.out.println(this);
    	for (GenericSegment gs : items)
    	{
    		for (int n = 0; n < indent; n ++)
				System.out.print("    ");
    		System.out.println(gs);
    		if (gs instanceof CompositeSegment<?>)
    		{
    			CompositeSegment<?> cs = (CompositeSegment<?>)gs;
    			cs.printSubItems(indent + 1);
    		}
    	}
    }
	
	/**
	 * returns string representation including sub-items
	 */
	public String toExtendedString()
	{
		StringBuffer sb = new StringBuffer(toString() + "\nSub-items:\n");
		for (GenericSegment gs : items)
		{
			sb.append(gs.toString() + "\n");
		}
		sb.append("\n");
		
		return(sb.toString());
	}
	
	public void setCalculatedFields()
	{
		findBoundingBox();
		findText();
		findFontName();
		findFontSize();
	}
	
	public void setCalculatedFields(TextSegment ts)
	{
		this.setBoundingBox(ts.getBoundingBox());
		this.setText(ts.getText());
		this.setFontName(ts.getFontName());
		this.setFontSize(ts.getFontSize());
	}
	
	// TODO: remove following method? or move to utils? count mode etc.
	
	public void findFontName()
	{
		HashMap fontHash = new HashMap();
		ArrayList numList = new ArrayList(items.size() + 1);
		for (int n = 0; n < (items.size() + 1); n ++)
		{
			//numList.add(new GenericSegment(0, 0, 0, 0));
			numList.add(null);
			// dummy value; null doesn't work (doesn't increase the size)
		}
		
		for (GenericSegment thisSegment : items)
		{
			if (thisSegment instanceof TextSegment &&
				!(thisSegment instanceof IBlankSegment))
			{
				TextSegment thisTextSegment = 
					(TextSegment)thisSegment;
				if (fontHash.containsKey(thisTextSegment.getFontName()))
				{
					int count = 
						((Integer)fontHash.get(thisTextSegment.getFontName()))
						.intValue();
					count ++;
					fontHash.put(thisTextSegment.getFontName(), new Integer(count));
					
					if (numList.get(count) == null)//(numList.get(count) instanceof GenericSegment)
						numList.set(count, thisTextSegment.getFontName());
				}
				else
				{
					fontHash.put(thisTextSegment.getFontName(), new Integer(1));
					if (numList.get(1) == null)//(numList.get(1) instanceof GenericSegment)
						numList.set(1, thisTextSegment.getFontName());
				}
			}
		}
		// loop through items in the hash and find the modal frequency
		Collection counts = fontHash.values();
		int maxCount = 0;
		Iterator cIter = counts.iterator();
		while(cIter.hasNext())
		{
			Integer countObj = (Integer)cIter.next();
			int count = countObj.intValue();
			if (count > maxCount)
				maxCount = count;
		}
		
		// now find the first font object with this frequency and set that as the font
		if (maxCount > 0) // otherwise no font!
		{
			this.fontName = (String)numList.get(maxCount);
		}
	}
	
	public void findBoundingBox()
	{
		boolean first = true;

		int noItems = 0;
		double fontSizeTotal = 0.0;

		for (GenericSegment thisSegment : items)
		{
			if (!(thisSegment instanceof IBlankSegment))
			{
				if (thisSegment instanceof TextSegment)
				{
					noItems++;
					fontSizeTotal += ((TextSegment) thisSegment).getFontSize();
				}
	
				if (first)
				{
					x1 = thisSegment.getX1();
					x2 = thisSegment.getX2();
					y1 = thisSegment.getY1();
					y2 = thisSegment.getY2();
					first = false;
				} else
				{
					growBoundingBox(thisSegment);
				}
			}
		}
		if (noItems >= 0)
		{
			fontSize = (float) (fontSizeTotal / noItems);
		} else
		{
			fontSize = -1.0f;
		}
	}

	public void findText()
	{
		text = "";
		
		TextSegment prevSeg = null;
		
		for (GenericSegment gs : items)
		{
			if (gs instanceof TextSegment)
			{
				TextSegment ts = (TextSegment)gs;
				
				// add space between line objects
				if (ts.getText() == "")	
				{
					text += " ";
				}
				else 
				{
					// check for word space
					if (prevSeg != null)
					{
						if (SegmentUtils.vertCentresIntersect(prevSeg, ts) )
						{
							// is on same line
							if (Utils.sameFontSize(prevSeg, ts))
							{
								double thresh = Utils.avg(prevSeg.getFontSize(), ts.getFontSize()) * threshMult;
								
								if (ts.getX1() > prevSeg.getX2() + thresh)
								{
									text += " ";
								}
							}
						}
						else
						{
							// new line
							if (preserveLineBreaks)
							{
								text += "\n";
							}
							else
							{
								text += " ";
							}
						}
					}
						
					
					
				}
				
				text += ts.getText();
				
				prevSeg = ts;
			}
		}
		
		/*
		System.out.println("after findText: " + text);
		ListUtils.printList(items);
		System.out.println();
		*/
	}

	// TODO: replace in TextBlock to count modal size
	// at character level
	public void findFontSize()
	{
		// what if no text seg?  avg is screwed up...
		int size = getItems().size();
		float sum = 0;
		for (GenericSegment gs : items)
		{
			if (gs instanceof TextSegment)
			{
				TextSegment ts = (TextSegment)gs;
				sum += ts.getFontSize();
			}
		}
		setFontSize(sum/size);
	}
} public class GenericSegment implements Cloneable, IXMLSegment, 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, int id)
	{
		if (id >= 0)
			newSegmentElement.setAttribute("id", Integer.toString(id));
		
		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() != 0)
		{
			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 addAsXML(Document resultDocument, Element parent,
		GenericSegment pageDim, float resolution, int id)
	{
		Element newSegmentElement = resultDocument
			.createElement(tagName());
		
		this.setElementAttributes(resultDocument, newSegmentElement, pageDim, 
				resolution, id);
		parent.appendChild(newSegmentElement);
	}
} public class ImageSegment extends GenericSegment 
		implements IXHTMLSegment, IXMLSegment
{

//  protected byte[] imageData;

	protected String suffix;
	
	PDImage pdImage;
	
    protected UUID uuid = UUID.randomUUID();

    public ImageSegment(float x1, float x2, float y1, float y2) {
        this(x1, x2, y1, y2, null);
    }


    /**
     * Constructor.
     *
     * @param x1 The x1 coordinate of the segments bounding box.
     * @param x2 The x2 coordinate of the segments bounding box.
     * @param y1 The y1 coordinate of the segments bounding box.
     * @param y2 The y2 coordinate of the segments bounding box.
     */
    public ImageSegment(
        float x1,
        float x2,
        float y1,
        float y2,
        PDImage pdImage
        )
    {
		super(x1, x2, y1, y2);
        this.pdImage = pdImage;
    }

    public PDImage getPDImage() {
		return pdImage;
	}


	public void setPDImage(PDImage pdImage) {
		this.pdImage = pdImage;
	}


	public UUID getUuid() {
        return uuid;
    }

    public String getSuffix() {
		return suffix;
	}


	public void setSuffix(String suffix) {
		this.suffix = suffix;
	}


	@Override
    public void addAsXHTML(Document resultDocument, Element parent) 
	{
        Element newImgElement = resultDocument.createElement("img");
        newImgElement.setAttribute( "src", "img-" + uuid.toString() + "." + suffix);
        newImgElement.setAttribute( "width", "" + getWidth());
        newImgElement.setAttribute( "height", "" + getHeight());
    }
	
	@Override
    public void setElementAttributes(Document resultDocument, Element newImgElement,
    		GenericSegment pageDim, float resolution, int id)
	{
        newImgElement.setAttribute( "src", "img-" + uuid.toString() + "." + suffix);
        newImgElement.setAttribute( "px-width", "" + getWidth());
        newImgElement.setAttribute( "px-height", "" + getHeight());
    }
} public class LineSegment extends GenericSegment
{
    public static final int DIR_HORIZ = 1;
    public static final int DIR_VERT = 2;
    public static final int DIR_OTHER = 0;
    
    protected Color color;
    boolean isCurve = false;
    
    protected int direction;
    
	/**
     * 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 LineSegment(
        float x1,
        float x2,
        float y1,
        float y2
        )
    {
		super(x1, x2, y1, y2);
        
        // TODO: variance is hard-coded as 1pt -- make it relative to something...
        
        if (Utils.within(y1, y2, 1.0f)) // horizontal
        {
            direction = DIR_HORIZ;
//            l1 = x1;
//            l2 = x2;
//            t = (y1 + y2) / 2;
        }
        else if (Utils.within(x1, x2, 1.0f)) // vertical
        {
            direction = DIR_VERT;
//            l1 = y1;
//            l2 = y2;
//            t = (x1 + x2) / 2;
        }
        else // angled
        {
            // TODO: throw exception here, if an attempt is made to
            // access the co-ordinates?
            direction = DIR_OTHER;
//            l1 = -1;
//            l2 = -1;
//            t = -1;
        }
    }

    public String getNodeText()
    {
    	return "[Line]";
    }
    

    /**
     * @return Returns the direction.
     */
    public int getDirection() {
        return direction;
    }
    

    /**
     * @param direction The direction to set.
     */
    public void setDirection(int direction) {
        this.direction = direction;
    }
    

    /**
     * @return Returns the l1.
     */
    public float getL1() {
    	if (direction == DIR_HORIZ) // horizontal
        {
        	return x1;
        }
        else if (direction == DIR_VERT) // vertical
        {
        	return y1;
        }
        else // angled
        {
        	return -1;
        }
    }
    

    /**
     * @return Returns the l2.
     */
    public float getL2() {
    	if (direction == DIR_HORIZ) // horizontal
        {
        	return x2;
        }
        else if (direction == DIR_VERT) // vertical
        {
        	return y2;
        }
        else // angled
        {
            // TODO: throw exception here, if an attempt is made to
            // access the co-ordinates?
        	return -1;
        }
    }
    

    /**
     * @return Returns the t.
     */
    public float getT() {
    	if (direction == DIR_HORIZ) // horizontal
        {
    		return (y1 + y2) / 2;
        }
        else if (direction == DIR_VERT) // vertical
        {
        	return (x1 + x2) / 2;
        }
        else // angled
        {
            // TODO: throw exception here, if an attempt is made to
            // access the co-ordinates?
        	return -1;
        }
    }
    

	public Color getColor()
	{
		return color;
	}

	public void setColor(Color color)
	{
		this.color = color;
	}

	public boolean isCurve() {
		return isCurve;
	}

	public void setCurve(boolean isCurve) {
		this.isCurve = isCurve;
	}
} public class TextLine extends CompositeSegment<TextFragment>
{
	public TextLine()
	{
		super();
		this.items = new ArrayList<TextFragment>();
	}
	
	/**
	 * Creates a new line with one fragment
	 * 
	 * @param tf
	 */
	public TextLine(TextFragment tf)
	{
		super();
		this.items.add(tf);
		this.setCalculatedFields(tf);
	}
	
	/**
	 * creates a shallow copy of the line object pointing
	 * to the same text fragment objects
	 * 
	 * @return
	 */
	public TextLine(TextLine lineToCopy)
	{
		super();
		this.items.addAll(lineToCopy.getItems());
		this.setCalculatedFields(lineToCopy);
	}
	
    public TextLine(
        float x1,
        float x2,
        float y1,
        float y2,
        String text,
        String fontName,
        float fontSize
        )
    {
		super(x1, x2, y1, y2, text, fontName, fontSize);
		this.items = new ArrayList<TextFragment>();
    }
    
    public TextLine(
        float x1,
        float x2,
        float y1,
        float y2
        )
    {
		super(x1, x2, y1, y2);
		this.items = new ArrayList<TextFragment>();
    }

    public TextLine(
        float x1,
        float x2,
        float y1,
        float y2,
        String text,
        String fontName,
        float fontSize,
		List<TextFragment> items
        )
    {
    	super(x1, x2, y1, y2, text, fontName, fontSize);
    	this.items = items;
    }
    
    public TextLine(
        float x1,
        float x2,
        float y1,
        float y2,
		List<TextFragment> items
        )
    {
    	super(x1, x2, y1, y2);
    	this.items = items;
    }
    
    public boolean addSegment(TextFragment newSegment)
    {
    	return addSegment(newSegment, false);
    }
    
    public boolean addSegment(TextFragment newSegment, boolean middle)
	{
    	return addSegment(newSegment, middle, false);
	}
    
	// TODO: calculated fields:
    // find fontsize -> if variable then -1; if empty then 0 (constants)
    // find font
    // find average word spacing
    // find average character spacing
    // super/subscript and accidentals no effect
    // object for "multiple font" (no majority font)
    //   e.g. by font change halfway through line
    //   currently only a string - set to "undef" constant;
    //   later encapsulated font object (RTFont containing PDFont)
    // variance measure of baseline (accidentals ignored)
    //
    // TODO: 
    // accidentals affect demerits
    // multiple font object affects demerits
    // wobbly baseline affects demerits
    // wobbly spacing affects demerits
    
    public boolean addSegment(TextFragment newSegment, boolean middle, boolean fontsize)
	{
    	// TODO: maybe undefined fontsize e.g. if there are only 2 items with different sizes???
    	// (although good edge ordering should avoid this!)
    	
    	float retVal = 1.0f;
    	boolean addToRight = true;
    	boolean addSpace = false;
    	
    	System.out.println("in addSegment");
    	
    	if (items.size() == 0)
    	{
    		// nothing to check
    	}
    	else
    	{
        	// check vertical overlap
    		// TODO: verify that this works; better gaussian tailoff
    		if (!SegmentUtils.vertCentresIntersect(this, newSegment))
    			retVal -= 0.6f;
        	
    		// check if baseline/fontsize match (sameLine method)
    		if (fontsize && !Utils.sameFontSize(this, newSegment)) retVal -= 0.2f;
    		if (!Utils.within(this.getY1(), newSegment.getY1(), 0.1f)) retVal -= 0.2f;
    		// TODO: gaussian tailoffs
    		
    		// if not, check if super, subscript or other "accidental"
    		// TODO!
    		
        	// accidental leads to a demerit
        	
    		// check if should be added to left or right
    		// maximum allowed overlap = 0.25 * lineheight
    		
    		float tolerance = this.getHeight() * 0.25f;
    		float distance = newSegment.getX1() - this.getX2();
    		
    		// assume adding to right
    		if (newSegment.getX2() <= this.getX1() + tolerance)
    		{
    			// adding to left
    			addToRight = false;
    			distance = this.getX1() - newSegment.getX2();
    		}
    		else if (newSegment.getX1() < this.getX2() - tolerance)
    		{
    			// too much overlap; add to middle or exit!
    			if (middle)
    			{
    				// currently just add and sort by x coordinate
    				// don't check for overlapping/overprinting chars
    				// as there might be legitimate reasons to do this

    				// do not penalize retVal
    				
    				// avoid distance penalty
    				distance = 0;
    			}
    			else
    			{
    				retVal -= 0.6f;
    			}
    		}
    		
        	// also check distance - demerit based on gaussian function
    		// can be negative, but only up to 0.25 * lineheight
    		float relDistance = distance / this.getHeight();
    		
    		System.out.println("reld: " + relDistance);
    		
    		if (relDistance > 0.05f)
    		{
    			System.out.println("setting addSpace to true");
    			addSpace = true;
    		}
    		
    		// floor at 0.5
    		float gaussMultiplicator = 0.5f + (0.5f * Utils.normgaussian(relDistance, 0, 2));
    		retVal *= gaussMultiplicator;
    	}
    	
		if (retVal > 0.5)
		{
			if (addToRight)
			{
				if (addSpace)
				{
					items.add(new BlankSpace());	
				}
				items.add(newSegment);
			}
			else
			{
				items.add(0, newSegment);
			}
			
			// TODO: desired for super/subscript?
			this.growBoundingBox(newSegment);
			
			Collections.sort(items, new X1Comparator());
			
			// TODO: deal with font size!
			
			return true;
		}
		else
		{
			return false;
		}
	}
    
    // TODO: penalize lines with small numbers of words (e.g. 1) in later eval method?
    
    public float evalSpacing()
    {
    	// pre: items are sorted
    	
    	float retVal;
    	
    	if (items.size() == 0)
    	{
    		// nothing to check
    		retVal = -1.0f;
    	}
    	else if (items.size() == 1)
    	{
    		retVal = 1.0f;
    	}
    	else
    	{
    		float maxSpace = 0;
    		TextFragment prevFrag = null;
    		for (TextFragment thisFrag : items)
    		{
    			if (prevFrag != null)
    			{
    				float space = thisFrag.getX1() - prevFrag.getX2();
    				
    				if (space > maxSpace)
    					maxSpace = space;
    			}
    			prevFrag = thisFrag;
    		}
    		
    		// also check distance - demerit based on gaussian function
    		// can be negative, but only up to 0.25 * lineheight
    		float relDistance = maxSpace / this.getHeight();
    		
    		// floor at 0.5
    		float gaussMultiplicator = 0.5f + (0.5f * Utils.normgaussian(relDistance, 0, 2));
    		retVal = gaussMultiplicator;
    	}
    	
    	return retVal;
    }
    
    
    /*
    public boolean addSegment(TextFragment newSegment, 
			TextFragment existingSegment)
	{
		// see if it overlaps an existing line (vertically)
		
		// see if, for this overlap, baseline/fontsize match (sameLine method)
		
		// if not, see if super, subscript or other "accidental"
		
		// return true if 
		return false;
	}
	*/
    
    // IXMillumSegment
	public void setElementAttributes(Document resultDocument, 
    	Element newSegmentElement, GenericSegment pageDim, float resolution, int id)
    {
        super.setElementAttributes(resultDocument, newSegmentElement, pageDim, 
        		resolution, id);
        
        // TODO: HACK -- the below lines refer to the this.getText() method, as the
        // text currently is not stored.  But this is due to change when the
        // line-finding is integrated.
        newSegmentElement.setAttribute
            ("font-size", Float.toString(this.getFontSize()));
//	        newSegmentElement.setAttribute
//	            ("text-ratio", Float.toString(this.getTextRatio()));
//	        newSegmentElement.setAttribute
//	        	("info", getInfoString());
        
        String type = "unknown";
        switch(classification)
        {
        	case PARAGRAPH:
        		type = "paragraph"; break;
        	case HEADING:
        		type = "heading"; break;
        	case OTHER_TEXT:
        		type = "other-text"; break;
        	case CELL:
        		type = "cell"; break;
        	default:
        		type = "error";
        }
        
        newSegmentElement.setAttribute("type", type);
        
        // System.out.println("creating text node: " + this.getText());
        // done in super!
        //newSegmentElement.appendChild
            //(resultDocument.createTextNode(this.getText()));
    }

	public void addAsXHTML(Document resultDocument, Element parent)//, GenericSegment pageDim)
    {
        Element newParagraphElement, newTextElement, tempElement = null;
        
        if (classification == HEADING || classification == HEADING_3)
            newParagraphElement = resultDocument.createElement("h3");
        else if (classification == HEADING_2)
            newParagraphElement = resultDocument.createElement("h2");
        else if (classification == HEADING_1)
            newParagraphElement = resultDocument.createElement("h1");
        else if (classification == UNORDERED_LIST_ITEM)
        {
//        	tempElement = resultDocument.createElement("ul");
//        	newParagraphElement = resultDocument.createElement("li");
        	// 22.01.2011 <ul>s are now separate objects
        	newParagraphElement = resultDocument.createElement("li");
        }
        else
            newParagraphElement = resultDocument.createElement("p");

        // HEADING_1 to HEADING_3 in str mode
        if (classification >= 40 && classification < 60)
        {
        	boolean bold = false;
        	boolean italic = false;
        	boolean underlined = false;
        	int superSubscript = 0;
        	String textToAdd = "";
        	float prevX2 = -1.0f;
        	
        	Iterator iter1 = items.iterator();
        	while(iter1.hasNext())
        	{
        		TextLine tl1 = (TextLine)iter1.next();
//        		System.out.println("tl1: " + tl1);
        		if (tl1.getX1() < prevX2)
				{
					// new line: do we insert a carriage return?
					if (prevX2 < strXPosNewline)
						textToAdd = textToAdd + ("\n");
				}
				prevX2 = tl1.getX2();
				
        		Iterator iter2 = tl1.getItems().iterator();
        		while(iter2.hasNext())
        		{
        			TextLine tl2 = (TextLine)iter2.next();
//        			System.out.println("tl2: " + tl2);
        			TextFragment prevFrag = null;
        			Iterator iter3 = tl2.getItems().iterator();
        			while(iter3.hasNext())
        			{
        				TextFragment tf = (TextFragment)iter3.next();
//        				System.out.println("tf: " + tf);
//        				System.out.println("tf is superSubscript: " + tf.isStrIsUnderlined());
        				
        				// if neither matches the whitespace character
        				// and horiz gap > 0.25(afs)
        				if (prevFrag != null)
        				{
        					float horizGap = tf.getX1() - prevFrag.getX2();
        					float afs = (tf.getFontSize() + prevFrag.getFontSize()) / 2.0f;
        					if (!(tf.getText().trim().matches("[\\s]") || prevFrag.getText().trim().matches("[\\s]")) &&
        						horizGap > afs * 0.15f)
        					{
        						textToAdd = textToAdd + " ";
        					}
        				}	
        				
        				if (tf.isBold() == bold && tf.isItalic() == italic && 
        					tf.isUnderlined() == underlined && tf.getSuperSubscript() == superSubscript)
        				{
        					// same style as previous character
//        					textToAdd.concat(tf.getText()); // doesn't work?!?
        					textToAdd = textToAdd + (tf.getText());
        				}
        				else
        				{
        					// add text
//        					if (textToAdd.length() > 0)
        					if (textToAdd.trim().length() > 0)
        					{
        						if (superSubscript == 1)
        						{
	        						if (underlined)
	        						{
	//        							System.out.println("underlined with textToAdd: " + textToAdd);
	//	        		        		System.out.println("textToAdd.length: " + textToAdd.trim().length());
	        							if (bold && italic)
	    	        		        	{
	        								Element newTextElement4 = resultDocument.createElement("sup");
	        								newParagraphElement.appendChild(newTextElement4);
	        								Element newTextElement3 = resultDocument.createElement("u");
	    	        		        		newTextElement4.appendChild(newTextElement3);
	    	        		            	Element newTextElement2 = resultDocument.createElement("b");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (bold)
	    	        		        	{
	    	        		        		Element newTextElement3 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement3);
	    	        		        		Element newTextElement2 = resultDocument.createElement("u");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("b");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (italic)
	    	        		        	{
	    	        		        		Element newTextElement3 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement3);
	    	        		        		Element newTextElement2 = resultDocument.createElement("u");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("u");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	        						}
	        						else
	        						{
	        							if (bold && italic)
	    	        		        	{
	        								Element newTextElement3 = resultDocument.createElement("sup");
	        								newParagraphElement.appendChild(newTextElement3);
	    	        		            	Element newTextElement2 = resultDocument.createElement("b");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (bold)
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("b");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (italic)
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else
	    	        		        	{
	    	        		        		newTextElement = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement);
	    	        		        	}
	        						}
        						}
        						else if (superSubscript == -1)
        						{
//        							System.out.println("outputting subscript");
        							if (underlined)
	        						{
	//        							System.out.println("underlined with textToAdd: " + textToAdd);
	//	        		        		System.out.println("textToAdd.length: " + textToAdd.trim().length());
	        							if (bold && italic)
	    	        		        	{
	        								Element newTextElement4 = resultDocument.createElement("sub");
	        								newParagraphElement.appendChild(newTextElement4);
	        								Element newTextElement3 = resultDocument.createElement("u");
	    	        		        		newTextElement4.appendChild(newTextElement3);
	    	        		            	Element newTextElement2 = resultDocument.createElement("b");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (bold)
	    	        		        	{
	    	        		        		Element newTextElement3 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement3);
	    	        		        		Element newTextElement2 = resultDocument.createElement("u");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("b");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (italic)
	    	        		        	{
	    	        		        		Element newTextElement3 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement3);
	    	        		        		Element newTextElement2 = resultDocument.createElement("u");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("u");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	        						}
	        						else
	        						{
	        							if (bold && italic)
	    	        		        	{
	        								Element newTextElement3 = resultDocument.createElement("sub");
	        								newParagraphElement.appendChild(newTextElement3);
	    	        		            	Element newTextElement2 = resultDocument.createElement("b");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (bold)
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("b");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (italic)
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else
	    	        		        	{
	    	        		        		newTextElement = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement);
	    	        		        	}
	        						}
        						}
        						else // normal text (required in order to initialize newTextElement)
        						{
        							if (underlined)
            						{
//            							System.out.println("underlined with textToAdd: " + textToAdd);
//    	        		        		System.out.println("textToAdd.length: " + textToAdd.trim().length());
            							if (bold && italic)
        	        		        	{
            								Element newTextElement3 = resultDocument.createElement("u");
        	        		        		newParagraphElement.appendChild(newTextElement3);
        	        		            	Element newTextElement2 = resultDocument.createElement("b");
        	        		        		newTextElement3.appendChild(newTextElement2);
        	        		        		newTextElement = resultDocument.createElement("i");
        	        		        		newTextElement2.appendChild(newTextElement);
        	        		        	}
        	        		        	else if (bold)
        	        		        	{
        	        		        		Element newTextElement2 = resultDocument.createElement("u");
        	        		        		newParagraphElement.appendChild(newTextElement2);
        	        		        		newTextElement = resultDocument.createElement("b");
        	        		        		newTextElement2.appendChild(newTextElement);
        	        		        	}
        	        		        	else if (italic)
        	        		        	{
        	        		        		Element newTextElement2 = resultDocument.createElement("u");
        	        		        		newParagraphElement.appendChild(newTextElement2);
        	        		        		newTextElement = resultDocument.createElement("i");
        	        		        		newTextElement2.appendChild(newTextElement);
        	        		        	}
        	        		        	else
        	        		        	{
        	        		        		newTextElement = resultDocument.createElement("u");
        	        		        		newParagraphElement.appendChild(newTextElement);
        	        		        	}
            						}
            						else
            						{
            							if (bold && italic)
        	        		        	{
        	        		            	Element newTextElement2 = resultDocument.createElement("b");
        	        		        		newParagraphElement.appendChild(newTextElement2);
        	        		        		newTextElement = resultDocument.createElement("i");
        	        		        		newTextElement2.appendChild(newTextElement);
        	        		        	}
        	        		        	else if (bold)
        	        		        	{
//        	        		        		System.out.println("bold with textToAdd: " + textToAdd);
//        	        		        		System.out.println("textToAdd.length: " + textToAdd.trim().length());
        	        		        		newTextElement = resultDocument.createElement("b");
        	        		        		newParagraphElement.appendChild(newTextElement);
        	        		        	}
        	        		        	else if (italic)
        	        		        	{
        	        		        		newTextElement = resultDocument.createElement("i");
        	        		        		newParagraphElement.appendChild(newTextElement);
        	        		        	}
        	        		        	else
        	        		        	{
        	        		            	newTextElement = newParagraphElement;
        	        		        	}
            						}
        						}
	        		            
	        		            // the following lines would just add the string
	        		            // without <br/>s
	        		            //newColumnElement.appendChild
	        		    		//(resultDocument.createTextNode(theText));
	        		            String textSection = new String();
	        		            
	        		            for (int n = 0; n < textToAdd.length(); n ++)
	        		            {
	        		            	String thisChar = textToAdd.substring(n, n + 1);
	        		            	if (thisChar.equals("\n"))
	        		            	{
	        		            		newTextElement.appendChild
	        		            			(resultDocument.createTextNode(textSection));
	        		                    newTextElement.appendChild
	        		                    	(resultDocument.createElement("br"));
	        		                    textSection = "";
	        		            	}
	        		            	else
	        		            	{
	        		            		textSection = textSection.concat(thisChar);
	        		            	}
	        		            }
	        		            
	        		            if (textSection.length() > 0)
	        		            	
	        		            	newTextElement.appendChild
	        		            	(resultDocument.createTextNode(textSection));
        					
        					}
        					
        					// update bold and italic
        		            bold = tf.isBold();
        		            italic = tf.isItalic();
        		            underlined = tf.isUnderlined();
        		            textToAdd = "";
        		            textToAdd = textToAdd + (tf.getText());
        		            superSubscript = tf.getSuperSubscript();
        				}
        				prevFrag = tf;
        			}
        		}
        	}
        	
        	// if remaining text
        	if(textToAdd.trim().length() > 0)
        	{
        		if (bold && italic)
	        	{
	            	Element newTextElement2 = resultDocument.createElement("b");
	        		newParagraphElement.appendChild(newTextElement2);
	        		newTextElement = resultDocument.createElement("i");
	        		newTextElement2.appendChild(newTextElement);
	        	}
	        	else if (bold)
	        	{
	        		newTextElement = resultDocument.createElement("b");
	        		newParagraphElement.appendChild(newTextElement);
	        	}
	        	else if (italic)
	        	{
	        		newTextElement = resultDocument.createElement("i");
	        		newParagraphElement.appendChild(newTextElement);
	        	}
	        	else
	        	{
	            	newTextElement = newParagraphElement;
	        	}
	            
	            // the following lines would just add the string
	            // without <br/>s
	            //newColumnElement.appendChild
	    		//(resultDocument.createTextNode(theText));
	            String textSection = new String();
	            
	            for (int n = 0; n < textToAdd.length(); n ++)
	            {
	            	String thisChar = textToAdd.substring(n, n + 1);
	            	if (thisChar.equals("\n"))
	            	{
	            		newTextElement.appendChild
	            			(resultDocument.createTextNode(textSection));
	                    newTextElement.appendChild
	                    	(resultDocument.createElement("br"));
	                    textSection = "";
	            	}
	            	else
	            	{
	            		textSection = textSection.concat(thisChar);
	            	}
	            }
	            
	            if (textSection.length() > 0)
	            	
	            	newTextElement.appendChild
	            	(resultDocument.createTextNode(textSection));

        	}
        	
        }
        else // normal mode
        {
        	if (isBold() && isItalic())
        	{
            	Element newTextElement2 = resultDocument.createElement("b");
        		newParagraphElement.appendChild(newTextElement2);
        		newTextElement = resultDocument.createElement("i");
        		newTextElement2.appendChild(newTextElement);
        	}
        	else if (isBold())
        	{
        		newTextElement = resultDocument.createElement("b");
        		newParagraphElement.appendChild(newTextElement);
        	}
        	else if (isItalic())
        	{
        		newTextElement = resultDocument.createElement("i");
        		newParagraphElement.appendChild(newTextElement);
        	}
        	else
        	{
            	newTextElement = newParagraphElement;
        	}
            
            String theText = this.getText();
            // the following lines would just add the string
            // without <br/>s
            //newColumnElement.appendChild
    		//(resultDocument.createTextNode(theText));
            String textSection = new String();
            
            for (int n = 0; n < theText.length(); n ++)
            {
            	String thisChar = theText.substring(n, n + 1);
            	if (thisChar.equals("\n"))
            	{
            		newTextElement.appendChild
            			(resultDocument.createTextNode(textSection));
                    newTextElement.appendChild
                    	(resultDocument.createElement("br"));
                    textSection = "";
            	}
            	else
            	{
            		textSection = textSection.concat(thisChar);
            	}
            }
            
            if (textSection.length() > 0)
            	
            	newTextElement.appendChild
            	(resultDocument.createTextNode(textSection));
        }

        /*
        newParagraphElement.appendChild
            (resultDocument.createTextNode(this.getText()));
        */
        
        // 22.01.2011 <ul>s are now separate objects
        /*
        if (classification == UNORDERED_LIST_ITEM)
        {
        	tempElement.appendChild(newParagraphElement);
        	parent.appendChild(tempElement);
        }
        else
        */
        	parent.appendChild(newParagraphElement);
    }
} public class RectSegment extends 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.
     */
	
	protected Color fillColor;
	protected Color lineColor;
	protected boolean isFilled;
	
    public RectSegment(
        float x1,
        float x2,
        float y1,
        float y2
        )
    {
		super(x1, x2, y1, y2);
		this.isFilled = false;
    }
    
    public List<LineSegment> toLines()
    {
    	ArrayList<LineSegment> retVal = new ArrayList<LineSegment>();
    	
    	// if this rectangle is just a THICK LINE
    	if (this.getWidth() < 3.0f || this.getHeight() < 3.0f)
    	{
    		// the constructor of Line automatically
    		// determines its direction.
            retVal.add(new LineSegment(this.getX1(), this.getX2(), 
                    this.getY1(), this.getY2()));
            return retVal;
    	}
    	else
    	{
    		// add top line
    		retVal.add(new LineSegment(x1, x2, y2, y2));
    		// add bottom line
    		retVal.add(new LineSegment(x1, x2, y1, y1));
    		// add left line
    		retVal.add(new LineSegment(x1, x1, y1, y2));
    		// add right line
    		retVal.add(new LineSegment(x2, x2, y1, y2));
    		return retVal;
    	}
    }

	public Color getFillColor()
	{
		return fillColor;
	}

	public void setFillColor(Color fillColor)
	{
		this.fillColor = fillColor;
	}

	public boolean isFilled()
	{
		return isFilled;
	}

	public void setFilled(boolean isFilled)
	{
		this.isFilled = isFilled;
	}

	public Color getLineColor()
	{
		return lineColor;
	}

	public void setLineColor(Color lineColor)
	{
		this.lineColor = lineColor;
	}
	
	public String tagName()
	{
		if (isFilled)
			return "filled-rect";
		else return super.tagName();
	}
} public class TextBlock extends CompositeSegment<TextLine> //implements ResultSegment
	implements IXHTMLSegment
{
	protected float rejectDistance = Float.MAX_VALUE;
	protected boolean branchMerged = false;
	protected boolean branchSplit = false; // add edge or distance?
	
//	Other ideas: firstChild/secondChild (hierarchical)
//	             lastGoodSegmentation
//	Merge history allows linear search
	
//	List<List<CandidateBlock>> mergeHistory = new ArrayList<List<CandidateBlock>>();
	
	final public static int ALIGN_UNSET = -1;
	final public static int ALIGN_LEFT = 0;
	final public static int ALIGN_CENTRE = 1;
	//final public static int ALIGN_CENTRE_KNUTH = 11;
	final public static int ALIGN_JUSTIFY = 2;
	final public static int ALIGN_RIGHT = 3;
	final public static int ALIGN_FORCE_JUSTIFY = 4;
	
	int alignment = -1;
	
	final public static int LBL_UNSET = -1;
	final public static int LBL_PARA = 10;
	final public static int LBL_CELL = 20;
	final public static int LBL_TD = 21;
	final public static int LBL_TH = 22;
	final public static int LBL_CELLS = 25;
	final public static int LBL_LIST = 30;
	final public static int LBL_LIST_LABEL = 31;
	final public static int LBL_LIST_TEXT = 32;
	
//	int label = -1;
	
	float evalPara = -1.0f;
	float evalCell = -1.0f;
	float evalBullet = -1.0f;
	
	float lineSpacing = -1.0f;
	
	int colSpan; // TODO: move elsewhere perhaps
	
	
	public TextBlock()
    {
        super();
    }
	
	/**
	 * Creates candidate block with single line item
	 * and sets its calculated fields
	 * 
	 * @param firstSegment
	 */
	public TextBlock(TextFragment firstSegment)
    {
    	super();
    	
    	this.items.add(new TextLine(firstSegment));
    	this.setCalculatedFields(firstSegment);
    }
	
	/*
	public boolean addSegmentHorizontally(TextFragment newSegment)
	{
		// try adding as superscript to existing line
		
		// 
		
		return false;
	}
	*/
	
	/**
	 * creates a copy with new line objects pointing
	 * to the same text fragment objects
	 * 
	 * @return
	 */
	public TextBlock(TextBlock blockToCopy)
	{
		super();
		
		for (TextLine tl : blockToCopy.items)
			this.items.add(new TextLine(tl));
		this.setCalculatedFields(blockToCopy);
		this.rejectDistance = blockToCopy.rejectDistance;
		this.branchMerged = blockToCopy.branchMerged;
		this.branchSplit = blockToCopy.branchSplit;
		
		this.alignment = blockToCopy.alignment;
//		this.label = blockToCopy.label;
		
		this.evalPara = blockToCopy.evalPara;
		this.evalCell = blockToCopy.evalCell;
		this.evalBullet = blockToCopy.evalBullet;
	}
	
	
	// inherited: List<RTTextLine> items;
	// when adding, this list should be kept 
	// sorted in descending Y order (top to bottom)
	
	public float getRejectDistance() {
		return rejectDistance;
	}

	public void setRejectDistance(float rejectDistance) {
		this.rejectDistance = rejectDistance;
	}

	public boolean isBranchMerged() {
		return branchMerged;
	}

	public void setBranchMerged(boolean branchMerged) {
		this.branchMerged = branchMerged;
	}

	public boolean isBranchSplit() {
		return branchSplit;
	}

	public void setBranchSplit(boolean branchSplit) {
		this.branchSplit = branchSplit;
	}

	public int getAlignment() {
		return alignment;
	}

	public void setAlignment(int alignment) {
		this.alignment = alignment;
	}

	public float getLineSpacing() {
		return lineSpacing;
	}

	public void setLineSpacing(float lineSpacing) {
		this.lineSpacing = lineSpacing;
	}

	/*
	public int getLabel() {
		return label;
	}

	public void setLabel(int label) {
		this.label = label;
	}
	 */
	
	public float getEvalPara() {
		return evalPara;
	}

	public void setEvalPara(float evalPara) {
		this.evalPara = evalPara;
	}

	public float getEvalCell() {
		return evalCell;
	}

	public void setEvalCell(float evalCell) {
		this.evalCell = evalCell;
	}

	public float getEvalBullet() {
		return evalBullet;
	}

	public void setEvalBullet(float evalBullet) {
		this.evalBullet = evalBullet;
	}

	public int getColSpan() {
		return colSpan;
	}

	public void setColSpan(int colSpan) {
		this.colSpan = colSpan;
	}

	/**
	 * Generates a list of text fragments by recursing the line objects
	 */
	public List<TextFragment> fragments()
	{
		List<TextFragment> retVal = new ArrayList<TextFragment>();
		
		for(TextLine l : items)
			retVal.addAll(l.getItems());
		
		return retVal;
	}
	
	/**
	 * Returns the line containing the fragment
	 */
	public TextLine getLineContainingFragment(TextFragment tf)
	{
		for (TextLine l : items)
		{
			if (l.getItems().contains(tf))
				return l;
		}
		return null;
	}
	
	/**
	 * sets average line spacing (baseline distance) in points
	 * (not as multiple of fontsize)
	 * 
	 * pre: lines must be sorted from top to bottom
	 * 
	 */
	public void findLineSpacing()
	{
		if (items.size() >= 2)
		{
			float lineSum = 0.0f;
			TextLine prevLine = null;
			for (TextLine l : items)
			{
				if (prevLine != null)
				{
					lineSum += (prevLine.getY1() - l.getY1());
				}
				prevLine = l;
			}
			lineSpacing = lineSum / (float)(items.size() - 1);
		}
		else
		{
			lineSpacing = -1.0f;
		}
	}
	
	// demerits a la Knuth(-Plass)
	//
	// penalties: 0 - irrelevant; +1000 prohibited -1000 compulsory
	//
	// adjustment ratio: +ve until 1 (max stretchability) or -ve until -1
	//   i.e. relative space with 1 = loose -1 = tight; >1 too loose; <1 too tight
	// 
	// badness: 100 * (|adj ratio|)^3
	//
	// demerits: (1 + badness + penalty)^2 + additional line penalty
	//   (always positive, from 1 to the thousands)
	//
	// formula quite arbitrary
	//
	// adding 1 to the badness instead of using badness by itself will
	// minimize the total number of lines in cases where there are breaks
	// with approximately zero badness
	
	// TODO: calculated fields
	// likelihood of left alignment
	// likelihood of centre "
	// "             justified "
	// "             force justified "
	//
	// if indentations present,
	//   likelihood of 1st line indentation
	//   likelihood fo several paragraphs with indentations
	
	
	
	
	
	// regular paragraph (i.e. without forced line breaks)
	// funny shape or fit, but syntactically correct
	// possibilities for left, centre and right alignment (max deviation gaussian evaluation)
	// constant line spacing (max deviation gaussian evaluation)
	
	// ORDER
	// regular paras first
	// then add superscripts
	// non-aligned paragraphs if not preferred by aligned
	// L-shaped and funny shaped if not preferred by something else
	
	
	/**
	 * @return Likelihood that the candidate block is a bullet
	 */
	public float evalAsBullet()
	{
		if (items.size() > 1) return 0.0f;
		
		float val = Utils.normgaussian(text.length(), 1.0f, 1.0f);
		
		return (val * 0.9f) + 0.1f;
	}
	
	/**
	 * @return Likelihood that the candidate block is a paragraph
	 */
	public float evalAsParagraph()
	{
		float relWidth = getWidth() / fontSize;
		
		float val = Utils.normgaussian(relWidth, 28.0f, 12.0f);
		
//		return (val * 0.75f) + 0.25f;
		return (val * 0.5f) + 0.5f;
		
		/*
		if (true)
//		if (val < 28.0f)
		{
			return (val * 0.75f) + 0.25f;
		}
		else
		{
			// ensure higher score than cell for unusually long lines
			return (val * 0.5f) + 0.5f;
		}
		*/
	}
	
	/**
	 * @return Likelihood that the candidate block is a cell
	 */
	public float evalAsCell()
	{
		float relWidth = getWidth() / fontSize;
		
//		float val = Utils.normgaussian(relWidth, 4.0f, 2.0f);
		float val = Utils.normgaussian(relWidth, 4.0f, 4.0f);
				
//		return (val * 0.5f) + 0.5f;
		return (val * 0.6f) + 0.4f;
	}
	
	/**
	 * @return Likelihood that the candidate block is a table column
	 */
	public float evalAsCol()
	{
		float relWidth = getWidth() / fontSize;
		
//		float val = Utils.normgaussian(relWidth, 4.0f, 2.0f);
		float val = Utils.normgaussian(relWidth, 4.0f, 4.0f);
		
		// prefer longer columns
		float recipItems = 1.0f / items.size();
		float multiplicator = 1.0f - (recipItems * 0.5f);
		
		// TODO: look at alignment
		
//		return (val * 0.5f) + 0.5f;
//		return (val * 0.6f) + 0.4f;
		return (val * multiplicator * 0.6f) + 0.4f;
	}
	
	/**
	 * @return Likelihood that the candidate block is segmented correctly
	 * Uses a heuristic that calls the other eval methods
	 */
	public float evalBlock()
	{
		float retVal = evalAlignment() * evalLineSpacing() * evalNumLines() * evalWhitespace();
		
		if (!evalOverlappingLines()) retVal = retVal * 0.25f;
		
		return retVal;
	}
	
	
	/**
	 * prefers larger numbers of lines
	 * (asymptotic function)
	 * 
	 * @return
	 */
	public float evalNumLines()
	{
		if (this.items.size() < 1) return 0.0f;
		
		/*
		float numLines = this.items.size();
		
		float retVal = 1.0f - (0.50f * (1.0f / numLines));
		
		// for     0.25
		// returns 0.75        for 1 line
		//         0.875       for 2 lines
		//         0.91666 ... for 3 lines
		//         etc
		
		return retVal;
		*/
		
		else if (this.items.size() == 1) return 0.99f;
		else return 1.0f;
	}
	
	
	/**
	 * returns true if no overlapping lines found (OK)
	 * otherwise false
	 * pre: lines correctly found and sorted in vertical order
	 * 
	 * @return
	 */
	public boolean evalOverlappingLines()
	{
		// check that no line vertically overlaps the centre of another
		for (TextLine l1 : items)
		{
			for (TextLine l2 : items)
			{
				if (l1 != l2)
				{
					if (SegmentUtils.vertCentresIntersect(l1, l2))
						return false;
				}
			}
		}
		
		// check that all sequential pairs of lines intersect one another horizontally
		TextLine prevLine = null;
		for (TextLine thisLine : items)
		{
			if (prevLine != null)
			{
				if (!SegmentUtils.horizIntersect(thisLine, prevLine))
					return false;
			}
			prevLine = thisLine;
		}
		
		return true;
	}
	
	/**
	 * Returns a value corresponding to the evenness of the line spacing
	 * higher = better/more even
	 * max is 1
	 * 
	 * @return
	 */
	public float evalLineSpacing()
	{
		if (items.size() < 2)
			return 1;
	
		// 2019-03-20 changed from distance to als
		// to min/max calculations, so they don't cancel each other out
		
		// calculate existing line spacing average
    	float als = calcAvgLineSpacing();
		
		// assume sorted vertically
//		Collections.sort(blockFrom.getItems(), new YComparator());
		
//    	float maxDeviation = 0.0f;
		float maxLS = Float.MIN_VALUE;
		float minLS = Float.MAX_VALUE;
		TextSegment prevSeg = null;
		for (TextLine thisSeg : items)
		{
			if (prevSeg == null)
			{
				// do nothing
			}
			else
			{
				float lineSpacing = prevSeg.getY1() - thisSeg.getY1();
				/*
				float deviation = lineSpacing - als;
				if (deviation < 0) deviation = 0.0f - deviation;
				
				if (deviation > maxDeviation)
					maxDeviation = deviation;
				*/
				if (lineSpacing > maxLS) maxLS = lineSpacing;
				if (lineSpacing < minLS) minLS = lineSpacing;
			}
			prevSeg = thisSeg;
		}
		
		// OLD: calculate related deviation to fontsize
		// float relMaxDiff = maxDeviation / calcAvgFontSize();
		
		// calculate related deviation to average
//		float relMaxDiff = maxDeviation / als;
		float deviation = maxLS - minLS;
		float relMaxDiff = deviation / als;
		
		return Utils.normgaussian(relMaxDiff, 0, 0.20f);
	}

	
	public float evalWordSpacing()
	{
		// returns minimum value for all lines
		
		if (items.size() == 0)
			return -1.0f;
    	
		float minVal = Float.MAX_VALUE;
		for (TextLine l : items)
		{
			float val = l.evalSpacing();
			if (val < minVal)
				minVal = val;
		}
		
    	return minVal;
	}
	
	/*
	public float evalBlock()
	{
		return evalAlignment() * evalWhitespace();
	}
	*/
	
	/**
	 * 
	 * returns the max alignment score of left/right/cen/jus
	 * multiplied by wordspacing eval
	 * 
	 * and sets alignment variable
	 *  
	 * @return
	 */
	public float evalAlignment()
	{
		// return the max alignment score of left/right/cen/jus
		// multiplied by wordspacing eval
		
		float evalLeft = evalLeftAlign();
		float evalCentre = evalCentreAlign();
		float evalRight = evalRightAlign();
		float evalJustify = evalJustify();
		
		float evalWordSpacing = evalWordSpacing();
		
		// for justify ignore wordspacing eval
		
		float retVal = 0.0f;
		
		if (evalLeft * evalWordSpacing > retVal)
		{
			retVal = evalLeft * evalWordSpacing;
			alignment = ALIGN_LEFT;
		}
		
		if (evalJustify > retVal)
		{
			retVal = evalJustify;
			alignment = ALIGN_JUSTIFY;
		}
		
		if (evalCentre * evalWordSpacing > retVal)
		{
			retVal = evalCentre * evalWordSpacing;
			alignment = ALIGN_CENTRE;
		}
		
		if (evalRight * evalWordSpacing > retVal)
		{
			retVal = evalRight * evalWordSpacing;
			alignment = ALIGN_RIGHT;
		}
		
		return retVal;
	}
	
	public float evalWhitespace()
	{
		return evalWhitespace(0.5f);
	}
	
	/**
	 * returns false if whitespace gap found with
	 * gapWidth on two consecutive lines
	 * or 1/2 gapWidth on more than two consecutive lines
	 * 
	 * @param gapWidth
	 * @return
	 */
	public float evalWhitespace(float gapWidth)
	{
		float retVal = 1.0f;
		
		List<WhitespaceGap> finalGaps = new ArrayList<WhitespaceGap>();
		
		List<WhitespaceGap> currGaps = new ArrayList<WhitespaceGap>();
		for (TextLine l : items)
		{
			List<WhitespaceGap> thisLineGaps = new ArrayList<WhitespaceGap>();
			
			TextSegment prevSeg = null;
			for (TextSegment thisSeg : l.getItems())
			{
				if (prevSeg != null && thisSeg.getX1() > prevSeg.getX2())
				{
					thisLineGaps.add(new WhitespaceGap
							(prevSeg.getX2(), thisSeg.getX1(), l.getY1(), l.getY2(),
									l.getFontSize()));
				}
				
				prevSeg = thisSeg;
			}
			
			List<WhitespaceGap> usedCurrGaps = new ArrayList<WhitespaceGap>();
			List<WhitespaceGap> newCurrGaps = new ArrayList<WhitespaceGap>();
			List<WhitespaceGap> matchedLineGaps = new ArrayList<WhitespaceGap>();
			
			// create lengthened gaps from currGaps by seeing whether they intersect
			// with this line's gaps
			for (WhitespaceGap wgc : currGaps)
			{
				for (WhitespaceGap wgl : thisLineGaps)
				{
					if (SegmentUtils.horizIntersect(wgc, wgl))
					{
						// create new wgc and shrink its horizontal extent if necessary
						// for n+1 lines
						
						WhitespaceGap wgc1 = new WhitespaceGap(wgc);
						wgc1.numLines ++;
						
						if (wgl.getX1() > wgc.getX1()) wgc1.setX1(wgl.getX1());
						if (wgl.getX2() < wgc.getX2()) wgc1.setX2(wgl.getX2());
						
						// set fontsize to the larger of the two
						if (wgl.fontSize > wgc.fontSize)
							wgc1.fontSize = wgl.fontSize;
						
						// mark the old wgc as "used"
						// this will stop it being removed from currGaps later
						usedCurrGaps.add(wgc);
						newCurrGaps.add(wgc1);
						
//						matchedLineGaps.add(wgl);
						
						// create new wgc for this line only for 1 line
						
						/*
						wgc.numLines ++;
						if (wgl.fontSize > wgc.fontSize)
							wgc.fontSize = wgl.fontSize;
						usedCurrGaps.add(wgc);
						matchedLineGaps.add(wgl);
						break;
						*/
						
						// do not break as there may be further gaps to enlarge
					}
				}
			}
			
			
			// move all gaps from currGaps, which did not intersect this line's gaps
			// and therefore cannot be extended any more, to finalGaps
			List<WhitespaceGap> unusedCurrGaps = new ArrayList<WhitespaceGap>();
			for (WhitespaceGap wgc : currGaps)
				if (!usedCurrGaps.contains(wgc))
					unusedCurrGaps.add(wgc);
			finalGaps.addAll(unusedCurrGaps);
			
			// currGaps will now contain the newly extended gaps and the single-liners
			// from the current line
			currGaps.clear();
			currGaps.addAll(newCurrGaps);
			currGaps.addAll(thisLineGaps);
		}
		
		finalGaps.addAll(currGaps);
		
		
		
		for (WhitespaceGap wgf : finalGaps)
		{
			if (wgf.numLines == 2)
			{
				if (wgf.getWidth() > wgf.fontSize * gapWidth)
				{
					retVal = 0;
				}
			}
			if (wgf.numLines > 2)
			{
				if (wgf.getWidth() > wgf.fontSize * gapWidth * 0.5)
				{
					retVal = 0;
				}
			}
		}
		
		return retVal;
	}
	
	// 2019-05-06
	public float evalWhitespaceOld()
	{
		float retVal = 1.0f;
		
		List<WhitespaceGap> finalGaps = new ArrayList<WhitespaceGap>();
		
		List<WhitespaceGap> currGaps = new ArrayList<WhitespaceGap>();
		for (TextLine l : items)
		{
			List<WhitespaceGap> thisLineGaps = new ArrayList<WhitespaceGap>();
			
			TextSegment prevSeg = null;
			for (TextSegment thisSeg : l.getItems())
			{
				if (prevSeg != null && thisSeg.getX1() > prevSeg.getX2())
				{
					thisLineGaps.add(new WhitespaceGap
							(prevSeg.getX2(), thisSeg.getX1(), l.getY1(), l.getY2(),
									l.getFontSize()));
				}
				
				prevSeg = thisSeg;
			}
			
			List<WhitespaceGap> usedCurrGaps = new ArrayList<WhitespaceGap>();
			List<WhitespaceGap> matchedLineGaps = new ArrayList<WhitespaceGap>();
			
			for (WhitespaceGap wgc : currGaps)
			{
				for (WhitespaceGap wgl : thisLineGaps)
				{
					if (SegmentUtils.horizIntersect(wgc, wgl))
					{
						wgc.numLines ++;
						if (wgl.fontSize > wgc.fontSize)
							wgc.fontSize = wgl.fontSize;
						usedCurrGaps.add(wgc);
						matchedLineGaps.add(wgl);
						break;
					}
					// TODO: if doesn't intersect 100%, reduce with
				}
			}
			
			// TODO: if gap is shrunk (or later grown) horizontally, add twice!
			
			// move all gaps from currGaps which are not in usedCurrGaps to finalGaps
			List<WhitespaceGap> unusedCurrGaps = new ArrayList<WhitespaceGap>();
			for (WhitespaceGap wgc : currGaps)
				if (!usedCurrGaps.contains(wgc))
					unusedCurrGaps.add(wgc);
			currGaps.removeAll(unusedCurrGaps);
			finalGaps.addAll(unusedCurrGaps);
			
			// add all unmatched thisLineGaps to currGaps
			for (WhitespaceGap wgl : thisLineGaps)
				if (!matchedLineGaps.contains(wgl))
					currGaps.add(wgl);
		}
		
		finalGaps.addAll(currGaps);
		
		
		
		for (WhitespaceGap wgf : finalGaps)
		{
			if (wgf.numLines == 2)
			{
				if (wgf.getWidth() > wgf.fontSize * 0.5)
				{
					retVal = 0;
				}
			}
			if (wgf.numLines > 2)
			{
				if (wgf.getWidth() > wgf.fontSize * 0.25)
				{
					retVal = 0;
				}
			}
		}
		
		return retVal;
	}
	
	// TODO: for alignment, also depend slightly on segment width?
	
	public float evalLeftAlign()
	{
		if (items.size() < 1)
			return -1;
		
		// calculate average leftmost coordinate
		float sumX1 = 0.0f;
    	int numValues = 0;
		for (TextLine thisSeg : items)
		{
			sumX1 += thisSeg.getX1();
			numValues ++;
		}
		
		float avgX1 = sumX1 / numValues;
		
		float maxDiff = 0;
		
		for (TextLine thisSeg : items)
		{
			float x1Diff = thisSeg.getX1() - avgX1;
			if (x1Diff < 0) x1Diff = 0.0f - x1Diff;
			
			if (x1Diff > maxDiff) maxDiff = x1Diff;
		}
		
		// calculate related deviation to fontsize
		float relMaxDiff = maxDiff / calcAvgFontSize();
		
		// TODO: add 10% of width of block to standard deviation
		return Utils.normgaussian(relMaxDiff, 0, 0.5f);
	}
	
	public float evalCentreAlign()
	{
		if (items.size() < 1)
			return -1;
		
		// calculate average centre coordinate
		float sumXmid = 0.0f;
    	int numValues = 0;
		for (TextLine thisSeg : items)
		{
			sumXmid += thisSeg.getXmid();
			numValues ++;
		}
		
		float avgXmid = sumXmid / numValues;
		
		float maxDiff = 0;
		
		for (TextLine thisSeg : items)
		{
			float xMidDiff = thisSeg.getXmid() - avgXmid;
			if (xMidDiff < 0) xMidDiff = 0.0f - xMidDiff;
			
			if (xMidDiff > maxDiff) maxDiff = xMidDiff;
		}
		
		// calculate related deviation to fontsize
		float relMaxDiff = maxDiff / calcAvgFontSize();
		
		// TODO: add 10% of width of block to standard deviation
		return Utils.normgaussian(relMaxDiff, 0, 0.5f);
	}
	
	public float evalRightAlign(boolean lastLine)
	{
		if (items.size() < 1)
			return -1;
		
		if (!lastLine && items.size() < 2)
			return -1;
		
		// calculate average rightmost coordinate
		float sumX2 = 0.0f;
    	int numValues = 0;
		for (TextLine thisSeg : items)
		{
			if (lastLine || thisSeg != items.get(items.size() - 1))
			{
				sumX2 += thisSeg.getX2();
				numValues ++;
			}
		}
		
		float avgX2 = sumX2 / numValues;
		
		float maxDiff = 0;
		
		for (TextLine thisSeg : items)
		{
			if (lastLine || thisSeg != items.get(items.size() - 1))
			{
				float x2Diff = thisSeg.getX2() - avgX2;
				if (x2Diff < 0) x2Diff = 0.0f - x2Diff;
				
				if (x2Diff > maxDiff) maxDiff = x2Diff;
			}
		}
		
		// calculate related deviation to fontsize
		float relMaxDiff = maxDiff / calcAvgFontSize();
		
		// TODO: add 10% of width of block to standard deviation
		return Utils.normgaussian(relMaxDiff, 0, 0.5f);
	}
	
	public float evalRightAlign()
	{
		return evalRightAlign(true);
	}
	
	public float evalBlockAlign()
	{
		return Utils.minimum(evalLeftAlign(), evalRightAlign());
	}
	
	public float evalJustify()
	{
		if (items.size() < 2) // currently returns -1 and not 1 if no check possible
			return evalLeftAlign();
		else
			return Utils.minimum(evalLeftAlign(), evalRightAlign(false));
	}
	
	public float calcAvgLineSpacing()
    {
    	// calculate existing line spacing average
    	
		// assume sorted vertically
//		Collections.sort(blockFrom.getItems(), new YComparator());
		
    	float sumLineSpaces = 0.0f;
    	int numValues = 0;
		TextSegment prevSeg = null;
		for (TextLine thisSeg : items)
		{
			if (prevSeg == null)
			{
				// do nothing
			}
			else
			{
				float lineSpacing = prevSeg.getY1() - thisSeg.getY1();
				sumLineSpaces += lineSpacing;
				numValues ++;
			}
			prevSeg = thisSeg;
		}
		
		return(sumLineSpaces / numValues);
    }
	
	// potentially useful later for "paragraphs" with variable
	// fontsize/line heights
	public float calcAvgLineGaps()
    {
    	// calculate existing line spacing average
    	
		// assume sorted vertically
//		Collections.sort(blockFrom.getItems(), new YComparator());
		
    	float sumLineGaps = 0.0f;
    	int numValues = 0;
		TextSegment prevSeg = null;
		for (TextLine thisSeg : items)
		{
			if (prevSeg == null)
			{
				// do nothing
			}
			else
			{
				float lineGap = prevSeg.getY1() - thisSeg.getY2();
				sumLineGaps += lineGap;
				numValues ++;
			}
			prevSeg = thisSeg;
		}
		
		return(sumLineGaps / numValues);
    }
	
	public float calcAvgFontSize()
    {
    	// calculate existing font size average
    	
		// assume sorted vertically
//		Collections.sort(blockFrom.getItems(), new YComparator());
		
    	float sumLineSpacings = 0.0f;
    	int numValues = 0;
		for (TextLine thisSeg : items)
		{
			if (thisSeg.getFontSize() <= 0)
			{
//				System.err.println("invalid fontsize of line");
			}
			else
			{
				sumLineSpacings += thisSeg.getFontSize();
				numValues ++;
			}
		}
		
		return(sumLineSpacings / numValues);
    }
	
    // IXMillumSegment
	public void setElementAttributes(Document resultDocument, 
    	Element newSegmentElement, GenericSegment pageDim, float resolution, int id)
    {
        super.setElementAttributes(resultDocument, newSegmentElement, pageDim, 
        		resolution, id);
        
        // TODO: HACK -- the below lines refer to the this.getText() method, as the
        // text currently is not stored.  But this is due to change when the
        // line-finding is integrated.
        newSegmentElement.setAttribute
            ("font-size", Float.toString(this.getFontSize()));
//	        newSegmentElement.setAttribute
//	            ("text-ratio", Float.toString(this.getTextRatio()));
//	        newSegmentElement.setAttribute
//	        	("info", getInfoString());
        
        String type = "unknown";
        switch(classification)
        {
        	case PARAGRAPH:
        		type = "paragraph"; break;
        	case HEADING:
        		type = "heading"; break;
        	case OTHER_TEXT:
        		type = "other-text"; break;
        	case CELL:
        		type = "cell"; break;
        	case ORDERED_LIST_ITEM:
        		type = "ol-item"; break;
        	case UNORDERED_LIST_ITEM:
        		type = "ul-item"; break;
        	case HEADER:
        		type = "header"; break;
        	case FOOTER:
        		type = "footer"; break;
        	case TABLE_HEADING:
        		type = "table-heading"; break;
        	case TABLE_SUBTITLE:
        		type = "table-subtitle"; break;
        	default:
        		type = "other";
        }
        
        newSegmentElement.setAttribute("type", type);
        
        // System.out.println("creating text node: " + this.getText());
        // done in super!
        //newSegmentElement.appendChild
            //(resultDocument.createTextNode(this.getText()));
    }

	public void addAsXHTML(Document resultDocument, Element parent)//, GenericSegment pageDim)
    {
        Element newParagraphElement, newTextElement, tempElement = null;
        
        if (classification == HEADING || classification == HEADING_3)
            newParagraphElement = resultDocument.createElement("h3");
        else if (classification == HEADING_2)
            newParagraphElement = resultDocument.createElement("h2");
        else if (classification == HEADING_1)
            newParagraphElement = resultDocument.createElement("h1");
        else if (classification == UNORDERED_LIST_ITEM)
        {
//        	tempElement = resultDocument.createElement("ul");
//        	newParagraphElement = resultDocument.createElement("li");
        	// 22.01.2011 <ul>s are now separate objects
        	newParagraphElement = resultDocument.createElement("li");
        }
        else
            newParagraphElement = resultDocument.createElement("p");

        // HEADING_1 to HEADING_3 in str mode
        if (classification >= 40 && classification < 60)
        {
        	boolean bold = false;
        	boolean italic = false;
        	boolean underlined = false;
        	int superSubscript = 0;
        	String textToAdd = "";
        	float prevX2 = -1.0f;
        	
        	Iterator iter1 = items.iterator();
        	while(iter1.hasNext())
        	{
        		TextLine tl1 = (TextLine)iter1.next();
//        		System.out.println("tl1: " + tl1);
        		if (tl1.getX1() < prevX2)
				{
					// new line: do we insert a carriage return?
					if (prevX2 < strXPosNewline)
						textToAdd = textToAdd + ("\n");
				}
				prevX2 = tl1.getX2();
				
        		Iterator iter2 = tl1.getItems().iterator();
        		while(iter2.hasNext())
        		{
        			TextLine tl2 = (TextLine)iter2.next();
//        			System.out.println("tl2: " + tl2);
        			TextFragment prevFrag = null;
        			Iterator iter3 = tl2.getItems().iterator();
        			while(iter3.hasNext())
        			{
        				TextFragment tf = (TextFragment)iter3.next();
//        				System.out.println("tf: " + tf);
//        				System.out.println("tf is superSubscript: " + tf.isStrIsUnderlined());
        				
        				// if neither matches the whitespace character
        				// and horiz gap > 0.25(afs)
        				if (prevFrag != null)
        				{
        					float horizGap = tf.getX1() - prevFrag.getX2();
        					float afs = (tf.getFontSize() + prevFrag.getFontSize()) / 2.0f;
        					if (!(tf.getText().trim().matches("[\\s]") || prevFrag.getText().trim().matches("[\\s]")) &&
        						horizGap > afs * 0.15f)
        					{
        						textToAdd = textToAdd + " ";
        					}
        				}	
        				
        				if (tf.isBold() == bold && tf.isItalic() == italic && 
        					tf.isUnderlined() == underlined && tf.getSuperSubscript() == superSubscript)
        				{
        					// same style as previous character
//        					textToAdd.concat(tf.getText()); // doesn't work?!?
        					textToAdd = textToAdd + (tf.getText());
        				}
        				else
        				{
        					// add text
//        					if (textToAdd.length() > 0)
        					if (textToAdd.trim().length() > 0)
        					{
        						if (superSubscript == 1)
        						{
	        						if (underlined)
	        						{
	//        							System.out.println("underlined with textToAdd: " + textToAdd);
	//	        		        		System.out.println("textToAdd.length: " + textToAdd.trim().length());
	        							if (bold && italic)
	    	        		        	{
	        								Element newTextElement4 = resultDocument.createElement("sup");
	        								newParagraphElement.appendChild(newTextElement4);
	        								Element newTextElement3 = resultDocument.createElement("u");
	    	        		        		newTextElement4.appendChild(newTextElement3);
	    	        		            	Element newTextElement2 = resultDocument.createElement("b");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (bold)
	    	        		        	{
	    	        		        		Element newTextElement3 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement3);
	    	        		        		Element newTextElement2 = resultDocument.createElement("u");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("b");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (italic)
	    	        		        	{
	    	        		        		Element newTextElement3 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement3);
	    	        		        		Element newTextElement2 = resultDocument.createElement("u");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("u");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	        						}
	        						else
	        						{
	        							if (bold && italic)
	    	        		        	{
	        								Element newTextElement3 = resultDocument.createElement("sup");
	        								newParagraphElement.appendChild(newTextElement3);
	    	        		            	Element newTextElement2 = resultDocument.createElement("b");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (bold)
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("b");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (italic)
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else
	    	        		        	{
	    	        		        		newTextElement = resultDocument.createElement("sup");
	    	        		        		newParagraphElement.appendChild(newTextElement);
	    	        		        	}
	        						}
        						}
        						else if (superSubscript == -1)
        						{
//        							System.out.println("outputting subscript");
        							if (underlined)
	        						{
	//        							System.out.println("underlined with textToAdd: " + textToAdd);
	//	        		        		System.out.println("textToAdd.length: " + textToAdd.trim().length());
	        							if (bold && italic)
	    	        		        	{
	        								Element newTextElement4 = resultDocument.createElement("sub");
	        								newParagraphElement.appendChild(newTextElement4);
	        								Element newTextElement3 = resultDocument.createElement("u");
	    	        		        		newTextElement4.appendChild(newTextElement3);
	    	        		            	Element newTextElement2 = resultDocument.createElement("b");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (bold)
	    	        		        	{
	    	        		        		Element newTextElement3 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement3);
	    	        		        		Element newTextElement2 = resultDocument.createElement("u");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("b");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (italic)
	    	        		        	{
	    	        		        		Element newTextElement3 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement3);
	    	        		        		Element newTextElement2 = resultDocument.createElement("u");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("u");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	        						}
	        						else
	        						{
	        							if (bold && italic)
	    	        		        	{
	        								Element newTextElement3 = resultDocument.createElement("sub");
	        								newParagraphElement.appendChild(newTextElement3);
	    	        		            	Element newTextElement2 = resultDocument.createElement("b");
	    	        		        		newTextElement3.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (bold)
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("b");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else if (italic)
	    	        		        	{
	    	        		        		Element newTextElement2 = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement2);
	    	        		        		newTextElement = resultDocument.createElement("i");
	    	        		        		newTextElement2.appendChild(newTextElement);
	    	        		        	}
	    	        		        	else
	    	        		        	{
	    	        		        		newTextElement = resultDocument.createElement("sub");
	    	        		        		newParagraphElement.appendChild(newTextElement);
	    	        		        	}
	        						}
        						}
        						else // normal text (required in order to initialize newTextElement)
        						{
        							if (underlined)
            						{
//            							System.out.println("underlined with textToAdd: " + textToAdd);
//    	        		        		System.out.println("textToAdd.length: " + textToAdd.trim().length());
            							if (bold && italic)
        	        		        	{
            								Element newTextElement3 = resultDocument.createElement("u");
        	        		        		newParagraphElement.appendChild(newTextElement3);
        	        		            	Element newTextElement2 = resultDocument.createElement("b");
        	        		        		newTextElement3.appendChild(newTextElement2);
        	        		        		newTextElement = resultDocument.createElement("i");
        	        		        		newTextElement2.appendChild(newTextElement);
        	        		        	}
        	        		        	else if (bold)
        	        		        	{
        	        		        		Element newTextElement2 = resultDocument.createElement("u");
        	        		        		newParagraphElement.appendChild(newTextElement2);
        	        		        		newTextElement = resultDocument.createElement("b");
        	        		        		newTextElement2.appendChild(newTextElement);
        	        		        	}
        	        		        	else if (italic)
        	        		        	{
        	        		        		Element newTextElement2 = resultDocument.createElement("u");
        	        		        		newParagraphElement.appendChild(newTextElement2);
        	        		        		newTextElement = resultDocument.createElement("i");
        	        		        		newTextElement2.appendChild(newTextElement);
        	        		        	}
        	        		        	else
        	        		        	{
        	        		        		newTextElement = resultDocument.createElement("u");
        	        		        		newParagraphElement.appendChild(newTextElement);
        	        		        	}
            						}
            						else
            						{
            							if (bold && italic)
        	        		        	{
        	        		            	Element newTextElement2 = resultDocument.createElement("b");
        	        		        		newParagraphElement.appendChild(newTextElement2);
        	        		        		newTextElement = resultDocument.createElement("i");
        	        		        		newTextElement2.appendChild(newTextElement);
        	        		        	}
        	        		        	else if (bold)
        	        		        	{
//        	        		        		System.out.println("bold with textToAdd: " + textToAdd);
//        	        		        		System.out.println("textToAdd.length: " + textToAdd.trim().length());
        	        		        		newTextElement = resultDocument.createElement("b");
        	        		        		newParagraphElement.appendChild(newTextElement);
        	        		        	}
        	        		        	else if (italic)
        	        		        	{
        	        		        		newTextElement = resultDocument.createElement("i");
        	        		        		newParagraphElement.appendChild(newTextElement);
        	        		        	}
        	        		        	else
        	        		        	{
        	        		            	newTextElement = newParagraphElement;
        	        		        	}
            						}
        						}
	        		            
	        		            // the following lines would just add the string
	        		            // without <br/>s
	        		            //newColumnElement.appendChild
	        		    		//(resultDocument.createTextNode(theText));
	        		            String textSection = new String();
	        		            
	        		            for (int n = 0; n < textToAdd.length(); n ++)
	        		            {
	        		            	String thisChar = textToAdd.substring(n, n + 1);
	        		            	if (thisChar.equals("\n"))
	        		            	{
	        		            		newTextElement.appendChild
	        		            			(resultDocument.createTextNode(textSection));
	        		                    newTextElement.appendChild
	        		                    	(resultDocument.createElement("br"));
	        		                    textSection = "";
	        		            	}
	        		            	else
	        		            	{
	        		            		textSection = textSection.concat(thisChar);
	        		            	}
	        		            }
	        		            
	        		            if (textSection.length() > 0)
	        		            	
	        		            	newTextElement.appendChild
	        		            	(resultDocument.createTextNode(textSection));
        					
        					}
        					
        					// update bold and italic
        		            bold = tf.isBold();
        		            italic = tf.isItalic();
        		            underlined = tf.isUnderlined();
        		            textToAdd = "";
        		            textToAdd = textToAdd + (tf.getText());
        		            superSubscript = tf.getSuperSubscript();
        				}
        				prevFrag = tf;
        			}
        		}
        	}
        	
        	// if remaining text
        	if(textToAdd.trim().length() > 0)
        	{
        		if (bold && italic)
	        	{
	            	Element newTextElement2 = resultDocument.createElement("b");
	        		newParagraphElement.appendChild(newTextElement2);
	        		newTextElement = resultDocument.createElement("i");
	        		newTextElement2.appendChild(newTextElement);
	        	}
	        	else if (bold)
	        	{
	        		newTextElement = resultDocument.createElement("b");
	        		newParagraphElement.appendChild(newTextElement);
	        	}
	        	else if (italic)
	        	{
	        		newTextElement = resultDocument.createElement("i");
	        		newParagraphElement.appendChild(newTextElement);
	        	}
	        	else
	        	{
	            	newTextElement = newParagraphElement;
	        	}
	            
	            // the following lines would just add the string
	            // without <br/>s
	            //newColumnElement.appendChild
	    		//(resultDocument.createTextNode(theText));
	            String textSection = new String();
	            
	            for (int n = 0; n < textToAdd.length(); n ++)
	            {
	            	String thisChar = textToAdd.substring(n, n + 1);
	            	if (thisChar.equals("\n"))
	            	{
	            		newTextElement.appendChild
	            			(resultDocument.createTextNode(textSection));
	                    newTextElement.appendChild
	                    	(resultDocument.createElement("br"));
	                    textSection = "";
	            	}
	            	else
	            	{
	            		textSection = textSection.concat(thisChar);
	            	}
	            }
	            
	            if (textSection.length() > 0)
	            	
	            	newTextElement.appendChild
	            	(resultDocument.createTextNode(textSection));

        	}
        	
        }
        else // normal mode
        {
        	if (isBold() && isItalic())
        	{
            	Element newTextElement2 = resultDocument.createElement("b");
        		newParagraphElement.appendChild(newTextElement2);
        		newTextElement = resultDocument.createElement("i");
        		newTextElement2.appendChild(newTextElement);
        	}
        	else if (isBold())
        	{
        		newTextElement = resultDocument.createElement("b");
        		newParagraphElement.appendChild(newTextElement);
        	}
        	else if (isItalic())
        	{
        		newTextElement = resultDocument.createElement("i");
        		newParagraphElement.appendChild(newTextElement);
        	}
        	else
        	{
            	newTextElement = newParagraphElement;
        	}
            
            String theText = this.getText();
            // the following lines would just add the string
            // without <br/>s
            //newColumnElement.appendChild
    		//(resultDocument.createTextNode(theText));
            String textSection = new String();
            
            for (int n = 0; n < theText.length(); n ++)
            {
            	String thisChar = theText.substring(n, n + 1);
            	if (thisChar.equals("\n"))
            	{
            		newTextElement.appendChild
            			(resultDocument.createTextNode(textSection));
                    newTextElement.appendChild
                    	(resultDocument.createElement("br"));
                    textSection = "";
            	}
            	else
            	{
            		textSection = textSection.concat(thisChar);
            	}
            }
            
            if (textSection.length() > 0)
            	
            	newTextElement.appendChild
            	(resultDocument.createTextNode(textSection));
        }

        /*
        newParagraphElement.appendChild
            (resultDocument.createTextNode(this.getText()));
        */
        
        // 22.01.2011 <ul>s are now separate objects
        /*
        if (classification == UNORDERED_LIST_ITEM)
        {
        	tempElement.appendChild(newParagraphElement);
        	parent.appendChild(tempElement);
        }
        else
        */
        	parent.appendChild(newParagraphElement);
    }
	
	class WhitespaceGap extends GenericSegment
	{
		int numLines = 1;
		float fontSize = 0;
		
		public WhitespaceGap(float x1, float x2, float y1, float y2, float fontSize)
		{
			super(x1, x2, y1, y2);
			this.fontSize = fontSize;
		}
		
		// clones
		public WhitespaceGap(WhitespaceGap wgc)
		{
			super(wgc.x1, wgc.x2, wgc.y1, wgc.y2);
			this.fontSize = wgc.fontSize;
			this.numLines = wgc.numLines;
		}
	}
} public class TextFragment extends CompositeSegment<CharSegment>
{
	// TODO: what about rotated text/text with negative width/height?

	// should these variables be included even in the base class?
	/*
	float xScale;
	float yScale;
	float wordSpacing;
	float widthOfSpace;
	*/
	boolean overprint = false;

//	2011-01-24 unnecessary
//	protected String tagName = "text-fragment";
	
    /**
     * 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.
     * @param text The textual contents of the segment.
     * @param font The (main) font of the segment.
     * @param fontSize The (main) font size in the segment.
     */
    public TextFragment(
        float x1,
        float x2,
        float y1,
        float y2,
        String text,
        String fontName,
        float fontSize
        )
    {
		super(x1, x2, y1, y2, text, fontName, fontSize);
    }
    
    public TextFragment(
        float x1,
        float x2,
        float y1,
        float y2,
        String text,
        PDFont font,
        float fontSize
        )
    {
		super(x1, x2, y1, y2, text, Utils.findFontName(font), fontSize);
    }
    
    public TextFragment(
        float x1,
        float x2,
        float y1,
        float y2
        )
    {
		super(x1, x2, y1, y2);
    }
    
    public TextFragment()
    {
		super();
    }
    
    public TextFragment(CharSegment c)
    {
		super(c.getX1(), c.getX2(), c.getY1(), c.getY2());
		items.add(c);
		text = c.getText();
		fontName = c.getFontName();
		fontSize = c.getFontSize();
    }

	// not in current use (I think) -- WRONG
    // now by default sets level to zero (primitive)
    public TextFragment(TextPosition tPos)
    {
    	super(tPos.getX(),
                tPos.getX() + (tPos.getWidth()),
                tPos.getY(),
                tPos.getY() + (tPos.getFontSize() * tPos.getYScale()),
//              tPos.getCharacter(),
                tPos.getUnicode(),
//              tPos.getFont().getBaseFont(),
                Utils.findFontName(tPos.getFont()),
                tPos.getFontSize() * tPos.getYScale());
    }

	/**
     * This will create a TextFragment object from a TextPosition object.
     * As of PDFBox 0.7.2, this is the method currently in use, which
	 * converts co-ordinates back to the original system.
	 *
     * @param tPos - the TextPosition object; pageDim - page dimensions in order to
	 * convert co-ordinates
	 * @return The new TextFragment object
     */
    public TextFragment(TextPosition tPos, GenericSegment pageDim)
    {
        super(tPos.getX(),
                tPos.getX() + tPos.getWidth(),
                pageDim.getY2() - tPos.getY(),
                pageDim.getY2() - tPos.getY() + (tPos.getFontSize() * tPos.getYScale()),
//              tPos.getCharacter(),
                tPos.getUnicode(),
//              tPos.getFont().getBaseFont(),
                Utils.findFontName(tPos.getFont()),
                tPos.getFontSize() * tPos.getYScale());
    }
    
    public List<OpTuple> sourceOps()
    {
    	List<OpTuple> retVal = new ArrayList<OpTuple>();
    	
    	for (CharSegment cs : items)
    		retVal.add(cs.getSourceOp());
    	
    	ListUtils.removeDuplicates(retVal);
    	return retVal;
    }
    
	public boolean isOverprint() {
		return overprint;
	}

	public void setOverprint(boolean overprint) {
		this.overprint = overprint;
	}
    
    /*
     * with the tagName stuff this should not be necessary
     * 
    public void addAsXML(Document resultDocument, Element parent, GenericSegment pageDim,
    	float resolution)
    {
        //TODO: find a better name for this element?
        Element newSegmentElement = resultDocument.createElement("text-fragment");
        super.setElementAttributes(resultDocument, newSegmentElement, pageDim, resolution);
        parent.appendChild(newSegmentElement);
    }
    */
	
	// 2011-11-02 overrides method from CompositeSegment
	// to ignore whitespace characters (with sometimes fancy coordinates)
	public void findBoundingBox()
	{
		boolean first = true;

		int noItems = 0;
		double fontSizeTotal = 0.0;

		for (CharSegment thisSegment : items)
		{
//			System.out.println(thisSegment.getText());
			if (!(thisSegment instanceof IBlankSegment))// &&
//				thisSegment.getText() != null && // keeps from crashing if text missing due to e.g. missing unicode mapping
//				!(thisSegment.getText().equals(" ")))
			{
				if (thisSegment instanceof TextSegment)
				{
					noItems++;
					fontSizeTotal += ((TextSegment) thisSegment).getFontSize();
				}
	
				if (first)
				{
					x1 = thisSegment.getX1();
					x2 = thisSegment.getX2();
					y1 = thisSegment.getY1();
					y2 = thisSegment.getY2();
					first = false;
				} else
				{
					growBoundingBox(thisSegment);
				}
			}
		}
		if (noItems >= 0)
		{
			fontSize = (float) (fontSizeTotal / noItems);
		} else
		{
			fontSize = -1.0f;
		}
	}
} public class TextSegment extends GenericSegment
{
	// only for str conversions -- is not saved or represented in graph
	protected boolean isUnderlined;
	protected int superSubscript = 0; // -1 for subscript, 1 for superscript, 0 for normal
	
    protected String text;
//  protected PDFont segFont;
    protected float fontSize;
    protected String fontName;
    
    protected int classification;
    public final static int PARAGRAPH = 0;
    public final static int HEADING = 1;
    public final static int OTHER_TEXT = 9;
    public final static int CELL = 2;
    public final static int BODY_TEXT = 40;
    public final static int HEADING_1 = 141;
    public final static int HEADING_2 = 142;
    public final static int HEADING_3 = 143;
    public final static int ORDERED_LIST_ITEM = 151;
    public final static int UNORDERED_LIST_ITEM = 152;
    public final static int HEADER = 61;
    public final static int FOOTER = 62;
    public final static int TABLE_HEADING = 71;
    public final static int TABLE_SUBTITLE = 72;
    
    public float strXPosNewline = -1.0f;
    
    public String getFontName() {
		return fontName;
	}

	public void setFontName(String fontName) {
		this.fontName = fontName;
	}
    
    /**
     * 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.
     * @param text The textual contents of the segment.
     * @param font The (main) font of the segment.
     * @param fontSize The (main) font size in the segment.
     */
    public TextSegment(
        float x1,
        float x2,
        float y1,
        float y2,
        String text,
        String fontName,
        float fontSize
        )
    {
		super(x1, x2, y1, y2);
		this.text = text;
//		this.segFont = font;
		// TODO: remove subsetting preceding chars
		this.fontName = fontName;
		this.setFontSize(fontSize); // also sets node text font
    }
    
    public TextSegment(
        float x1,
        float x2,
        float y1,
        float y2
        )
    {
		super(x1, x2, y1, y2);
        this.text = Utils.EMPTY_STRING;
    }

    public TextSegment()
    {
        super();
        this.text = Utils.EMPTY_STRING;
    }
    
	/**
     * This will get the text of the segment.
     *
     * @return The text.
     */
    public String getText()
    {
        return text;
    }
    
    /**
     * @param scale The text to set.
     */
    public void setText(String text)
    {
        this.text = text;
    }

    /**
     * This will get the font size of the segment.
     *
     * @return The font size of the segment.
     */
    public float getFontSize()
    {
        return fontSize;
    }
    
    /**
     * @param scale The font size to set.
     */
    public void setFontSize(float fontSize)
    {
        this.fontSize = fontSize;
    }
    
    /**
     * This will return whether the text segment is 'empty', i.e.
	 * contains just an empty string or a string with only spaces
     *
     * @return TRUE if empty
     */
    public boolean isEmpty()
    {
        if (text != null)
            return (text.trim().length() == 0);
        else
            return false;
    }
    
    @Override
	public void mergeSegment(GenericSegment seg) {
		
		super.mergeSegment(seg);
		
		//Merge the text content of the two segments
		text = text.concat(" ").concat(((TextSegment)seg).getText());
	}
    
    @Override
    public List<AttributeTuple> getAttributes()
    {
    	List<AttributeTuple> attributeList = super.getAttributes();
    	
    	attributeList.add(new AttributeTuple("text", text));
    	attributeList.add(new AttributeTuple("fontsize", fontSize));
    	attributeList.add(new AttributeTuple("font", fontName));
    	attributeList.add(new AttributeTuple("bold", isBold()));
    	attributeList.add(new AttributeTuple("italic", isItalic()));
    	
    	return attributeList;
    }
    
    public boolean isBold()
    {
    	if (fontName == null) return false;
    	
    	if (Utils.containsSubstring(fontName, "Bold") ||
    		Utils.containsSubstring(fontName, "bold") ||
    		Utils.containsSubstring(fontName, "Black") ||
    		Utils.containsSubstring(fontName, "black") ||
    		Utils.containsSubstring(fontName, "Heavy") ||
    		Utils.containsSubstring(fontName, "heavy"))
    		return true;
    	else return false;
    }
    
    public boolean isItalic()
    {
    	if (fontName == null) return false;
    	
    	if (Utils.containsSubstring(fontName, "Italic") ||
    		Utils.containsSubstring(fontName, "italic") ||
    		Utils.containsSubstring(fontName, "Cursive") ||
    		Utils.containsSubstring(fontName, "cursive") ||
    		Utils.containsSubstring(fontName, "Kursiv") ||
    		Utils.containsSubstring(fontName, "kursiv"))
    		return true;
    	else return false;
    }
    
    public boolean isCapitals()
    {
    	// TODO: 22.01.2011 include regexp or remove this method! used anywhere?
    	// doesn't include lower-case A-Z
    	return false;
    }
    
    public boolean isNumeric()
    {
    	// TODO: 22.01.2011 include regexp or remove this method! used anywhere?
    	// doesn't include A-Z/a-z
    	return false;
    }
    
	/*
    public void addAsXML(Document resultDocument, Element parent, GenericSegment pageDim,
    	float resolution)
    {
    	// TODO: inelegant solution
    	//if (potentialTable) tagName = "potential-table-cell";
    	
        //TODO: find a better name for this element?
        Element newSegmentElement = resultDocument.createElement(getTagName());
        this.setElementAttributes(resultDocument, newSegmentElement, pageDim, resolution);
        parent.appendChild(newSegmentElement);
    }
    */
    
    public void setElementAttributes
        (Document resultDocument, Element newSegmentElement, GenericSegment pageDim,
        float resolution, int id)
    {
//    	float fontSizeRatio = (resolution / SCREEN_RESOLUTION) * 
//    		FONT_SIZE_RATIO;
//    	22.01.2011 why font size ratio?
    	
    	newSegmentElement.setAttribute
        	("font-name", this.getFontName());
        newSegmentElement.setAttribute
            ("font-size", Float.toString(this.getFontSize())); // * fontSizeRatio));
        newSegmentElement.setAttribute
        	("bold", Boolean.toString(isBold()));
        newSegmentElement.setAttribute
        	("italic", Boolean.toString(isItalic()));
        //newSegmentElement.setAttribute
        //    ("text-ratio", Float.toString(this.getTextRatio()));
        
        // XMIllum does not like 0-length text
        // this method will allow us to easily see when this might happen!
        //System.out.println("foobar");
        if (this.isEmpty())
        {
            if (this.getText().length() > 0)
            {
                newSegmentElement.appendChild
                    (resultDocument.createTextNode("[empty:spaces]"));   
            }
            else
            {
                newSegmentElement.appendChild
                    (resultDocument.createTextNode("[empty:empty]"));
            }
        }
        else
        {
        	//if (this.getClass() == TableCell.class)
        	//System.out.println("class: " + this.getClass());
        	//System.out.println("appending text: " + Utils.removeInvalidXMLCharacters(this.getSegText()));
            //resultDocument.removeTextNode();
        	newSegmentElement.appendChild
                (resultDocument.createTextNode(Utils.removeInvalidXMLCharacters(this.getText())));
        }
        super.setElementAttributes(resultDocument, newSegmentElement, pageDim, 
        		resolution, id);
    }

    public boolean isUnderlined() {
		return isUnderlined;
	}

	public void setUnderlined(boolean isUnderlined) {
		this.isUnderlined = isUnderlined;
	}

	public int getSuperSubscript() {
		return superSubscript;
	}

	public void setSuperSubscript(int superSubscript) {
		this.superSubscript = superSubscript;
	}

	public int getClassification() {
		return classification;
	}

	public void setClassification(int classification) {
		this.classification = classification;
	}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import com.tamirhassan.pdfxtk.comparators.X1Comparator;
import com.tamirhassan.pdfxtk.model.CharSegment;
import com.tamirhassan.pdfxtk.model.CompositeSegment;
import com.tamirhassan.pdfxtk.model.GenericSegment;
import com.tamirhassan.pdfxtk.model.ImageSegment;
import com.tamirhassan.pdfxtk.model.LineSegment;
import com.tamirhassan.pdfxtk.model.TextLine;
import com.tamirhassan.pdfxtk.model.RectSegment;
import com.tamirhassan.pdfxtk.model.TextBlock;
import com.tamirhassan.pdfxtk.model.TextFragment;
import com.tamirhassan.pdfxtk.model.TextSegment;
/**
 * 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 com.tamirhassan.pdfxtk.utils;



/**
 * Static utility methods for lists
 * 
 * @author Tamir Hassan, pdfanalyser@tamirhassan.com
 * @version PDF Analyser 0.9
 */
public class ListUtils
{
	// adpated from http://briankuhn.com/?p=47
	public static void removeDuplicates(List<?> l) {
	    Set set = new LinkedHashSet();
	    set.addAll(l);
	    l.clear();
	    l.addAll(set);
	}
	
	/*
	public static void test()
	{
		CompositeSegment ts = new CompositeSegment();
		ts.getItems().add(new GenericSegment());
		
		List<GenericSegment> genericSegments = new ArrayList<GenericSegment>();
		List<TextSegment> textSegments = new ArrayList<TextSegment>();
		
		getItemsByClass(genericSegments, TextSegment.class, textSegments);
	}
	*/
	
	public static void selectItemsByClass(List<? extends Object> inputList,
		Class c, List resultList)
	{
		for(Object gs : inputList)
		{
			if (gs.getClass() == c)
			{
				resultList.add(gs);
			}
		}
	}
	
	public static List<CharSegment> selectCharacters(List<GenericSegment> l)