EplanPrinter.PDFPrint.java Source code

Java tutorial

Introduction

Here is the source code for EplanPrinter.PDFPrint.java

Source

//     * This file is part of the EplanPrinter project.
//     * Copyright (c) 2016 e-PlanSoft
//     * Authors: e-PlanSoft Team
//     * Website: http://www.eplansoft.com/
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License as
//    published by the Free Software Foundation, either version 3 of the
//    License, or (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//    * For more information, please contact us at info@eplansoft.com

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package EplanPrinter;

import com.itextpdf.text.Annotation;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Image;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfGState;
import com.itextpdf.text.pdf.PdfImportedPage;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.SimpleBookmark;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PrivateKeySignature;
import java.io.FileOutputStream;
import java.io.IOException;
import java.awt.Color;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.security.cert.Certificate;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import borg.bouncycastle.jce.provider.BouncyCastleProvider;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.PdfAnnotation;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author KRIMMER
 */
public class PDFPrint {
    //Test paths

    private static String INPUTFILE = "E:\\Projects\\eplansoft\\trunk\\EPC\\drawings\\uploads\\1182 Main Street Unit B\\Submitted\\20140715A-T02-Multipage.pdf";
    private static String INPUTFILE2 = "C:/dev/Projects/Owen/trunk/icc20/drawings/uploads/test/20110620A-20110420A-M123456789abcdefghijklmnopqrstuvwxyz123456789.pdf.pdf                                                                             ";
    private static String OUTPUTFILE = "E:\\Projects\\eplansoft\\trunk\\EPC\\drawings\\uploads\\1182 Main Street Unit B/OutTest.pdf";
    private static String OUTPUTFILE2 = "E:\\Projects\\eplansoft\\eaereview\\trunk\\icc20\\drawings\\cache\\003807-003914\\print/2_signed.pdf";
    //The image path for the comment image
    private String commentIMGPath = "";
    //The image rotation
    private int rot = 0;
    //The size of the incoming PDF
    private Rectangle r[];
    //The stamper used to build the page
    private PdfStamper pds;
    //The size ratio between the PDF and Plan Review
    //private float ratio;
    //The number of pages in the pdf
    private int totalPages;
    //The width and height of the altered PDF in Plan Review
    private float oWidth;
    private float oHeight;
    private float masterImgWidth;
    private float masterImgHeight;
    private int heightScalar = 0;
    private int widthScalar = 0;

    private MediaBox mediabox[];
    private int rotation[];
    protected int fontSize = 10; //default font size 10 pts

    /* Small list of pre-defined regex for common HTML markups */
    final static String REGEX_B = "\\<b*?>(.+?)\\<\\/b*?>";
    final static String REGEX_I = "\\<i*?>(.+?)\\<\\/i*?>";
    final static String REGEX_EM = "\\<em*?>(.+?)\\<\\/em*?>";
    final static String REGEX_STRONG = "\\<strong*?>(.+?)\\<\\/strong*?>";
    final static String REGEX_LI = "\\<li*?>(.+?)\\<\\/li*?>";
    final static String REGEX_DT = "\\<dt*?>(.+?)\\<\\/dt*?>";
    final static String REGEX_DD = "\\<dd*?>(.+?)\\<\\/dd*?>";

    public PDFPrint() {
    }

    //Sets the rotation and comment path.
    public String setPath(int rotate, String st, String imgSize, int masterWidth, int masterHeight) {
        commentIMGPath = st;
        rot = rotate;

        if (!imgSize.equals("")) {
            /*
             String[] mod = imgSize.split(",");
             System.out.println(mod.length);
             oWidth = 1450;
                
             int n = Integer.parseInt(mod[1]);
             int m = Integer.parseInt(mod[0]);
                
             if(m > 7500) m = 7500;
             if(n > 7500) n = 7500;
                
             float f = (1450/m) * n;
                
             oHeight = f;
             */
            String[] imageDimensions = imgSize.split(",");
            oWidth = Float.parseFloat(imageDimensions[0]);
            oHeight = Float.parseFloat(imageDimensions[1]);
        } else {
            oWidth = 1450;
            oHeight = 1450;
        }

        masterImgWidth = masterWidth;
        masterImgHeight = masterHeight;

        return "";
    }

    //Does the math to figure out the proper page ratio
    private void setRatio() {
        float width;

        width = masterImgWidth;
        if (masterImgWidth > masterImgHeight && rot > 0) {
            width = masterImgHeight;
        }

        //ratio = r.getWidth() / width * 4.0f;
    }

    //Copies the old PDF over and begins construction of the sheet.
    public String createPDF(String input, String output)
            throws FileNotFoundException, DocumentException, IOException {
        heightScalar = 0;
        //Addition. linh 07.21.2016 bypass owner password
        PdfReader.unethicalreading = true;
        PdfReader reader = new PdfReader(input);

        //Sets up the stamper, which is what adds all the content to the page.
        pds = new PdfStamper(reader, new FileOutputStream(output));
        PdfImportedPage page;
        page = pds.getImportedPage(reader, 1);
        PdfContentByte bg;
        totalPages = reader.getNumberOfPages();

        r = new Rectangle[totalPages];
        rotation = new int[totalPages];
        for (int x = 0; x < totalPages; x++) {
            r[x] = reader.getPageSizeWithRotation(x + 1);
            rotation[x] = reader.getPageRotation(x + 1);
        }

        mediabox = getDocumentMediaBox(reader, totalPages);

        //r = reader.getPageSizeWithRotation(1);
        //setRatio();
        bg = pds.getUnderContent(1);
        heightScalar = 0;
        return "";
    }

    /* Addition. ftorres - 7/21/2015 - Added to account for rotated pages 
     *   with the origin (0,0) not set to the bottom-left of the page. [1400] */
    private MediaBox[] getDocumentMediaBox(PdfReader reader, int pages) {
        MediaBox mbox[] = new MediaBox[pages];
        Rectangle rect;

        for (int x = 0; x < totalPages; x++) {
            mbox[x] = new MediaBox();
            rect = reader.getPageSize(x + 1);
            mbox[x].top = rect.getTop();
            mbox[x].bottom = rect.getBottom();
            mbox[x].left = rect.getLeft();
            mbox[x].right = rect.getRight();
        }
        return mbox;
    }

    //Close the PDF, which fully writes and saves it.
    public String closePDF() throws DocumentException, IOException {
        pds.close();
        return "";
    }

    //Returns the ratio using dimensions for the given page
    private float getRatio(int masterHeight, int masterWidth, int pageNum) {
        float width;

        width = masterWidth;
        if (masterWidth > masterHeight && rot > 0) {
            width = masterHeight;
        }

        return (float) (r[pageNum - 1].getWidth() / width * 4.0f);
    }

    //Inserts a single stamp on the page.
    //Modification. linh 06.14.2016 include Proj_no beneath stamps
    public String insertStamp(String loc, float x, float y, int width, int height, int set, String date,
            int pageNum, int masterHeight, int masterWidth, String projNo)
            throws BadElementException, MalformedURLException, IOException, DocumentException {
        Image image = Image.getInstance(loc);
        float[] scalar = scale(x, y, width, height, masterHeight, masterWidth, pageNum);
        float[] trans = translate(x, y, r[pageNum - 1].getHeight(), r[pageNum - 1].getWidth(), masterHeight,
                masterWidth, pageNum);
        float[] f = commentTrans(x, y, masterHeight, masterWidth, pageNum);

        float shift = 0;

        /* Addition. ftorres - 7/22/2015 - Added to account for rotated pages 
         *   with the origin (0,0) not set to the bottom-left of the page. [1400] */
        trans = translateRotation(trans[0], trans[1], pageNum);

        if (set == 1) {
            float m = image.getPlainHeight();
            float pageChunk = r[pageNum - 1].getHeight() / 10;
            shift = r[pageNum - 1].getHeight() / 100;
            scalar[1] = (int) (pageChunk);
            scalar[0] = (int) (image.getPlainWidth() * pageChunk) / image.getPlainHeight();
            trans[0] = (int) (0 + (pageChunk * widthScalar));
            trans[1] = (int) (r[pageNum - 1].getHeight() - (pageChunk * heightScalar)
                    - (shift * (heightScalar + 2)));
            heightScalar = heightScalar + 1;
            if (heightScalar == 8) {
                heightScalar = 0;
                widthScalar = widthScalar + 2;
            }
        }

        if (set == 1) {
            image.setAbsolutePosition(trans[0] + shift, trans[1] - scalar[1]);
            image.scaleAbsoluteHeight(scalar[1]);
            image.scaleAbsoluteWidth(scalar[0]);
            image.setRotationDegrees(rot);
        } else {
            //swap stamp dimensions for rotated drawings
            if (rot > 0) {
                image.setAbsolutePosition(trans[0] - 2 * scalar[1], trans[1] - 2 * scalar[0]);
            } else {
                image.setAbsolutePosition(trans[0], trans[1] - 2 * scalar[1]);
            }
            image.scaleAbsoluteHeight(scalar[1] * 2);
            image.scaleAbsoluteWidth(scalar[0] * 2);
            image.setRotationDegrees(rot);
        }

        PdfContentByte fg = pds.getOverContent(pageNum);
        fg.addImage(image);

        //Added by tmittelstadt on 09/21/2012 for ticket #698
        //draws a box around the date
        fg.setColorFill(BaseColor.WHITE);
        fg.setLineWidth(0f);

        //dmoody removed box.  ticket 900
        /*fg.moveTo(trans[0], trans[1] - scalar[1] * 2);
           fg.lineTo(trans[0], trans[1] - scalar[1] * 2 - 10);
           fg.lineTo(trans[0] + scalar[0] * 2, trans[1] - scalar[1] * 2 - 10);
           fg.lineTo(trans[0] + scalar[0] * 2, trans[1] - scalar[1] * 2);
           fg.lineTo(trans[0], trans[1] - scalar[1] * 2);*/

        fg.closePathFillStroke();
        fg.fill();

        //adds the date the stamp was added to the document to the pdf
        Phrase p = new Phrase(date);
        p.getFont().setColor(BaseColor.BLACK);
        //Modification zreeve 10/11/2012.  set font size to 11.
        p.getFont().setSize(9f);
        //p.getChunks().get(0).setAnnotation(PdfAnnotation.createText(pds.getWriter(), new Rectangle(trans[0],trans[1], trans[0]+5f, trans[1]+5f), id, comment, true, id));
        ColumnText.showTextAligned(fg, Element.ALIGN_CENTER, p, (float) (trans[0]),
                (float) (trans[1] - scalar[1] * 2 - 12), 0);
        Phrase pn = new Phrase("#[" + projNo + "]");
        pn.getFont().setColor(BaseColor.BLACK);
        pn.getFont().setSize(10f);
        pn.getFont().setStyle("bold");
        ColumnText.showTextAligned(fg, Element.ALIGN_CENTER, pn, (float) (trans[0]),
                (float) (trans[1] - scalar[1] * 2 - 22), 0);

        return "";
    }

    //Inserts a single Sketch on the page.
    public String insertSketch(String points, String color, String opac, int pageNum, int masterHeight,
            int masterWidth, int lineWeight, String lineStyle) throws DocumentException, IOException {
        float f = Float.valueOf(opac);
        PdfGState gs1 = new PdfGState();

        int t = points.indexOf("POLYGON");
        gs1.setFillOpacity(f);

        PdfContentByte fg = pds.getOverContent(pageNum);
        fg.setGState(gs1);
        float[] pointsSt = shatterSketches(points);
        color = "0x" + color.substring(1);
        Color c = Color.decode(color);

        fg.setLineWidth(lineWeight * getRatio(masterHeight, masterWidth, pageNum));
        if (lineStyle.equals("dash")) {
            fg.setLineDash(lineWeight * 4f, lineWeight);
        } else {
            fg.setLineDash(0);
        }

        //fg.setLineWidth(5);
        fg.setColorStroke(new BaseColor(c.getRGB()));
        fg.setColorFill(new BaseColor(c.getRGB()));
        float[] prefl = scaleShape(pointsSt, masterHeight, masterWidth, pageNum);
        float[] fl = sketchTrans(prefl, pageNum);

        /* Addition. ftorres - 7/22/2015 - Added to account for rotated pages 
         *   with the origin (0,0) not set to the bottom-left of the page. [1400] */
        float flTrans[] = translateRotation(fl[0], fl[1], pageNum);

        if (points.indexOf("LINEPOINT") != -1) {
            fg.circle(flTrans[0], flTrans[1], 5);
            fg.fillStroke();
            if (t == -1) {
                gs1.setFillOpacity(0f);
            } else {
                gs1.setFillOpacity(f);
            }
            fg.setGState(gs1);
        }
        fg.moveTo(flTrans[0], flTrans[1]);
        for (int i = 2; i < pointsSt.length; i = i + 2) {
            flTrans = translateRotation(fl[i], fl[i + 1], pageNum);
            fg.lineTo(flTrans[0], flTrans[1]);
        }

        if (t != -1) {
            fg.closePathFillStroke();
            fg.fill();
        } else {
            fg.stroke();
        }

        return "";
    }

    public String insertSignature(String src, String dest, String Keystore, String name, String password,
            int masterHeight, int masterWidth, int pageNum)
            throws GeneralSecurityException, IOException, DocumentException {

        float[] scalar = scale(1, 1, 120, 32, masterHeight, masterWidth, pageNum);
        float[] trans = translate(1, 1, r[pageNum - 1].getHeight(), r[pageNum - 1].getWidth(), masterHeight,
                masterWidth, pageNum);
        float[] f = commentTrans(1, 1, masterHeight, masterWidth, pageNum);

        float shift = 0;

        float pageChunk = r[pageNum - 1].getHeight() / 10;
        shift = r[pageNum - 1].getHeight() / 100;
        scalar[1] = (int) (pageChunk);
        scalar[0] = (int) (120 * pageChunk) / 32;
        trans[0] = (int) (0 + (pageChunk * widthScalar));
        trans[1] = (int) (r[pageNum - 1].getHeight() - (pageChunk * heightScalar) - (shift * (heightScalar + 2)));
        heightScalar = heightScalar + 1;
        if (heightScalar == 8) {
            heightScalar = 0;
            widthScalar = widthScalar + 2;
        }

        /* Addition. ftorres - 7/22/2015 - Added to account for rotated pages 
         *   with the origin (0,0) not set to the bottom-left of the page. [1400] */
        trans = translateRotation(trans[0], trans[1], pageNum);

        Rectangle cropBox = pds.getReader().getCropBox(1);
        Rectangle rectangle = new Rectangle(trans[0] + shift, trans[1] - scalar[1], trans[0] + shift + 120,
                trans[1] - scalar[1] + 32);
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(new FileInputStream(Keystore), password.toCharArray());
        String alias = (String) ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, password.toCharArray());
        Certificate[] chain = ks.getCertificateChain(alias);
        // Creating the reader and the stamper
        PdfReader reader = new PdfReader(src);
        FileOutputStream os = new FileOutputStream(dest);
        PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
        // Creating the appearance
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setVisibleSignature(rectangle, 1, name);
        // Creating the signature
        ExternalSignature pks = new PrivateKeySignature(pk, "SHA-256", "BC");
        ExternalDigest digest = new BouncyCastleDigest();
        MakeSignature.signDetached(appearance, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);

        return "";
    }

    //Inserts a comment tag on the page.
    public String insertComment(String sx, String sy, String id, String deptValue, String userInit, String comment,
            int pageNum, int masterHeight, int masterWidth, int pinned, int customFontSize)
            throws DocumentException, IOException {
        float ratio = getRatio(masterHeight, masterWidth, pageNum);
        float x = Float.parseFloat(sx);
        float y = Float.parseFloat(sy);
        float[] f = commentTrans(x, y, masterHeight, masterWidth, pageNum);
        PdfGState gs1 = new PdfGState();
        gs1.setFillOpacity(1);
        if (customFontSize > 0)
            fontSize = customFontSize;

        PdfContentByte fg = pds.getOverContent(pageNum);
        fg.setGState(gs1);
        BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
        float[] trans = translate(x, y, r[pageNum - 1].getHeight(), r[pageNum - 1].getWidth(), masterHeight,
                masterWidth, pageNum);
        // comment tag image (width=35pts, height=8pts)
        float[] scalar = scale(trans[0], trans[1], 35, 8, masterHeight, masterWidth, pageNum);

        /* Addition. ftorres - 7/21/2015 - Added to account for rotated pages 
         *   with the origin (0,0) not set to the bottom-left of the page. [1400] */
        float coords[] = translateRotation(trans[0], trans[1], pageNum);
        coords = checkBounds(coords[0], coords[1], pageNum);

        /* Addition. ftorres - 10/20/2015 - If the comment was pinned in EPC, then
         *   render the comment inside a comment box. */
        if (pinned == 1) {
            insertPinnedComment(coords[0], coords[1], id + " - " + deptValue + " (" + userInit + ")", comment,
                    pageNum, masterHeight, masterWidth);

            // Add the pinned comment text to page annotation -Jon Changkachith 11/24/2015
            Rectangle rect = new Rectangle(0, 0, 0, 0);
            PdfAnnotation annotation = PdfAnnotation.createText(pds.getWriter(), rect,
                    id + " - " + deptValue + " (" + userInit + ")", cleanupComment(comment), true, id);
            pds.addAnnotation(annotation, pds.getWriter().getCurrentPageNumber());
        } else {
            Image image = Image.getInstance(commentIMGPath);
            image.setAbsolutePosition(coords[0] + (scalar[0] * (id.length() / 5f)), coords[1]);

            /*
             * Commented out by Jon Changkachith 12/09/2015 because it was throwing
             * DocumentException with the message "The image must have absolute positioning."
            image.scaleAbsoluteHeight(1);
            image.scaleAbsoluteWidth(1);
            */
            image.scalePercent(ratio); //Added to fix DocumentException "The image must have absolute positioning." Jon Changkachith 12/09/2015
            image.setAnnotation(new Annotation(id + " - " + deptValue + " (" + userInit + ")",
                    cleanupComment(comment), 0, 0, 0, 0));
            fg.addImage(image);
        }

        fg.setLineWidth(.5f * ratio);
        fg.setColorStroke(new BaseColor(Color.decode("0x6E2405").getRGB()));
        fg.setColorFill(new BaseColor(Color.decode("0x6E2405").getRGB()));

        float tHeight = scalar[1];
        float tWidth = 0;
        if (id.length() > 3) {
            tWidth = (scalar[0] * (id.length() / 5f));
        } else {
            tWidth = (scalar[0]);
        }

        fg.moveTo(coords[0], coords[1]);
        fg.lineTo(coords[0] + (10f * ratio), coords[1] - (tHeight / 2));
        fg.lineTo(coords[0] + tWidth, coords[1] - (tHeight / 2));
        fg.lineTo(coords[0] + tWidth, coords[1] + (tHeight / 2));
        fg.lineTo(coords[0] + (10f * ratio), coords[1] + (tHeight / 2));

        fg.lineTo(coords[0], coords[1]);
        fg.closePathFillStroke();
        fg.fill();

        // Comment number that goes on the comment tag image
        Phrase p = new Phrase(id);
        p.getFont().setColor(BaseColor.WHITE);
        p.getFont().setSize(8f * ratio); //comment number font size = 8f
        //p.getChunks().get(0).setAnnotation(PdfAnnotation.createText(pds.getWriter(), new Rectangle(trans[0],trans[1], trans[0]+5f, trans[1]+5f), id, comment, true, id));

        float fs[] = translateRotation(f[0], f[1], pageNum);
        fs = checkBounds(fs[0], fs[1], pageNum);
        ColumnText.showTextAligned(fg, Element.ALIGN_LEFT, p, (float) (fs[0] + (9 * ratio)),
                (float) (fs[1] - (3 * ratio)), 0);

        return "";
    }

    private float[] pinnedCoords(float x, float y, int width, int height) {
        float coords[] = { x, y };

        coords[0] = x + width;
        coords[1] = y - height;

        return coords;
    }

    /* Addition. ftorres - 10/20/2015 - Will parse newlines out of comment lines
     * and create a comment line just for them. [1373] */
    private List<String> parseNewlines(String line) {
        List<String> newlines = new ArrayList<String>();
        int pos;
        String next;

        while (line.contains("\n")) {
            pos = line.lastIndexOf("\n");
            next = line.substring(pos + 1);
            line = line.substring(0, pos);
            newlines.add(0, next);
        }
        newlines.add(0, line);

        return newlines;
    }

    /* Addition. ftorres - 10/20/2015 - Breaks a comment into lines, each no longer
     * than LINELENGTH. [1373] */
    private List<String> createComment(String comment) {
        int LINELENGTH = 46;

        String words[] = comment.split(" ");
        List<String> lines = new ArrayList<String>();
        List<String> tempLines;
        String currentLine = words[0];

        for (int i = 1; i < words.length; i++) {
            if (currentLine.length() + words[i].length() + 1 <= LINELENGTH) {
                currentLine += " " + words[i];
            } else {
                tempLines = parseNewlines(currentLine);

                for (int z = 0; z < tempLines.size() - 1; z++) {
                    lines.add(tempLines.get(z));
                }

                if (tempLines.get(tempLines.size() - 1).length() + words[i].length() + 1 <= LINELENGTH)
                    currentLine = tempLines.get(tempLines.size() - 1) + " " + words[i];
                else {
                    lines.add(tempLines.get(tempLines.size() - 1));
                    currentLine = words[i];
                }
            }
        }

        if (currentLine.length() > 0) {
            tempLines = parseNewlines(currentLine);
            for (int i = 0; i < tempLines.size(); i++) {
                lines.add(tempLines.get(i));
            }
        }

        return lines;
    }

    /* Addition. ftorres - 10/19/2015 - Added to print 'pinned' comments. [1373]
    */
    private void insertPinnedComment(float xCoord, float yCoord, String id, String comment, int pageNum,
            int masterHeight, int masterWidth) throws DocumentException, IOException {

        int standardHeight = 38;
        int standardWidth = 130;
        int lineHeight = 7;

        if (fontSize == 8) {
            standardHeight = 48;
            standardWidth = 180;
            lineHeight = 10;
        } else if (fontSize == 10) {
            standardHeight = 70;
            standardWidth = 210;
            lineHeight = 12;
        }

        // comment box position defines lower-left corner
        xCoord += 41; // x-coordinate of comment text box
        yCoord -= 7; // lower offset moves box up

        float f = (float) 0.4;
        PdfGState gs1 = new PdfGState();
        gs1.setFillOpacity(f);

        PdfContentByte fg = pds.getOverContent(pageNum);
        fg.setGState(gs1);
        fg.setLineWidth(.5f * getRatio(masterHeight, masterWidth, pageNum));

        // Calculate additional needed height for the comment, based on length of comment.
        List<String> lines = createComment(cleanupComment(comment));
        int numLines = lines.size();
        int commentHeight = (numLines - 1) * lineHeight;

        // Draw the comment box
        String color = "0xF2F3E4";
        Color c = Color.decode(color);
        color = "0xFAFAF4";
        Color c2 = Color.decode(color);
        fg.setLineWidth(1f);
        fg.setColorStroke(new BaseColor(c.getRGB()));
        fg.setColorFill(new BaseColor(c2.getRGB()));
        float newCoords[] = pinnedCoords(xCoord, yCoord, standardWidth, standardHeight + commentHeight);
        fg.roundRectangle(xCoord, newCoords[1], standardWidth, standardHeight + commentHeight, 2f);
        fg.fillStroke();

        //BaseFont bf = BaseFont.createFont();
        BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, true);
        BaseFont bfb = BaseFont.createFont(BaseFont.HELVETICA_BOLD, BaseFont.CP1252, true);
        fg.setColorFill(BaseColor.RED);
        gs1.setFillOpacity(0.8f);
        fg.setGState(gs1);

        // Adding comment title text
        Phrase phrase = new Phrase("Comment", new Font(bfb, fontSize));
        ColumnText.showTextAligned(fg, Element.ALIGN_LEFT, phrase, xCoord + 3, yCoord - 7, 0);

        // Adding comment number
        phrase = new Phrase(id, new Font(bf, fontSize));
        float offset = xCoord + 35;
        if (fontSize == 8)
            offset = xCoord + 45;
        else if (fontSize == 10)
            offset = xCoord + 55;
        ColumnText.showTextAligned(fg, Element.ALIGN_LEFT, phrase, offset, yCoord - 7, 0);

        /*  Remove 10/27/2015 Jon Changkachith
        // Adding 'comment' label text
        phrase = new Phrase("Comment", new Font(bfb, fontSize));
        ColumnText.showTextAligned(fg, Element.ALIGN_LEFT, phrase, xCoord + 3, yCoord - 20f, 0);
        */

        // Adding comment text
        int commentYOffset = 32;
        for (int i = 0; i < lines.size(); i++) {
            //phrase = new Phrase(lines.get(i), new Font(bf, fontSize));
            phrase = composePhrase(lines.get(i), bf, bfb);
            ColumnText.showTextAligned(fg, Element.ALIGN_LEFT, phrase, xCoord + 3, yCoord - commentYOffset, 0);
            commentYOffset += lineHeight;
        }

    }

    // composes a phrase where html encoded characters are translated back to their respective elements
    public Phrase composePhrase(String str, BaseFont normalFont, BaseFont boldFont)
            throws DocumentException, IOException {
        ArrayList<Chunk> chunks = new ArrayList();

        // unescape all HTML encode i.e &lt; = <
        str = HtmlDecoder.unescapeHTML(str);

        // replace all <li> with bullet symbol
        str = Pattern.compile("\\<li*?\\>", Pattern.CASE_INSENSITIVE).matcher(str)
                .replaceAll(new Character('\u2022') + " ");

        String words[] = str.split(" ");
        for (int i = 0; i < words.length; i++) {
            String noHtml = words[i];
            BaseFont font = boldFont;
            if (hasHtmlTag(noHtml, REGEX_B)) {
                noHtml = removeHtmlTag(words[i]);
            } else if (hasHtmlTag(noHtml, REGEX_I)) {
                noHtml = removeHtmlTag(words[i]);
            } else if (hasHtmlTag(noHtml, REGEX_EM)) {
                noHtml = removeHtmlTag(words[i]);
            } else if (hasHtmlTag(noHtml, REGEX_STRONG)) {
                noHtml = removeHtmlTag(words[i]);
            } else if (hasHtmlTag(noHtml, REGEX_LI)) {
                noHtml = new Character('\u2022') + " " + removeHtmlTag(words[i]);
                font = normalFont;
            } else if (hasHtmlTag(noHtml, REGEX_DT)) {
                noHtml = new Character('\u2022') + " " + removeHtmlTag(words[i]);
                font = normalFont;
            } else if (hasHtmlTag(noHtml, REGEX_DD)) {
                noHtml = new Character('\u2022') + " " + removeHtmlTag(words[i]);
                font = normalFont;
            } else {
                noHtml = removeHtmlTag(words[i]);
                font = normalFont;
            }

            chunks.add(new Chunk(noHtml + " ", new Font(font, fontSize)));
        }

        Phrase phrase = new Phrase();
        for (Chunk chunk : chunks) {
            phrase.add(chunk);
        }

        return phrase;
    }

    public boolean hasHtmlTag(String str, String REGEX) {
        final Pattern RegexPattern = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE);
        final Matcher matcher = RegexPattern.matcher(str);
        if (matcher.find())
            return true;
        return false;
    }

    /*
     * Removes all HTML tags from string
    */
    public String removeHtmlTag(String str) {
        return str.replaceAll("\\<.*?>", "");
    }

    /*
     * Formats text to be use in pdf annotation
     * remove empty lines
     * convert html tag li, dd, dt into dash
     * remove all other html tags
    */
    public String cleanupComment(String str) {
        // unescape all HTML encode i.e &lt; = <
        str = HtmlDecoder.unescapeHTML(str);

        // replace all <li> with bullets
        str = Pattern.compile("\\<li*?\\>", Pattern.CASE_INSENSITIVE).matcher(str)
                .replaceAll(new Character('\u2022') + " ");

        // replace all <dt> with dash
        str = Pattern.compile("\\<dt*?\\>", Pattern.CASE_INSENSITIVE).matcher(str).replaceAll("\n- ");

        // replace all <dd> with dash
        str = Pattern.compile("\\<dd*?\\>", Pattern.CASE_INSENSITIVE).matcher(str).replaceAll("\n - ");

        // remove all other html tags.
        str = str.replaceAll("\\<.*?>", "");

        // replace all whitespaces except newlines
        str = str.replaceAll("^\\s+|\\s+$|\\s*(\n)\\s*|(\\s)\\s*", "$1$2").replace("\t", " ");

        return str;
    }

    /* Addition. ftorres - 7/21/2015 - Added to account for rotated pages 
     *   with the origin (0,0) not set to the bottom-left of the page. [1400] */
    public float[] translateRotation(float x, float y, int page) {
        float newX = x, newY = y;

        if (rotation[page - 1] == 90 || rotation[page - 1] == -270) {
            newX = mediabox[page - 1].bottom + x;
        } else if (rotation[page - 1] == 180 || rotation[page - 1] == -180) {
            newX = mediabox[page - 1].left + x;
            newY = mediabox[page - 1].bottom + y;
        } else if (rotation[page - 1] == 270 || rotation[page - 1] == -90) {
            newX = mediabox[page - 1].bottom + x;
        }

        return new float[] { (float) newX, (float) newY };
    }

    /* Addition. ftorres - 7/21/2015 - Checks if the coordinates are outside the
     *   bounds of the page, and if so moves them to the edge. [1400] */
    public float[] checkBounds(float x, float y, int page) {
        float newX = x, newY = y;

        if (rotation[page - 1] == 0 || rotation[page - 1] == 180 || rotation[page - 1] == -180) {
            if (x < mediabox[page - 1].left)
                newX = mediabox[page - 1].left;
            else if (x > mediabox[page - 1].right)
                newX = mediabox[page - 1].right - 50;

            if (y < mediabox[page - 1].bottom)
                newY = mediabox[page - 1].bottom + 25;
            else if (y > mediabox[page - 1].top)
                newY = mediabox[page - 1].top - 25;
        } else if (rotation[page - 1] == 90 || rotation[page - 1] == -270 || rotation[page - 1] == 270
                || rotation[page - 1] == -90) {
            if (x < mediabox[page - 1].bottom)
                newX = mediabox[page - 1].bottom;
            else if (x > mediabox[page - 1].top)
                newX = mediabox[page - 1].top - 50;

            if (y < mediabox[page - 1].left)
                newY = mediabox[page - 1].left + 25;
            else if (y > mediabox[page - 1].right)
                newY = mediabox[page - 1].right - 25;
        }

        return new float[] { (float) newX, (float) newY };
    }

    //Scales comment tags and stamps
    public float[] scale(float x, float y, int ow, int oh, int masterHeight, int masterWidth, int pageNum) {
        float[] scale = new float[4];
        float ratio = getRatio(masterHeight, masterWidth, pageNum);

        scale[0] = (ow * ratio);
        scale[1] = (oh * ratio);
        scale[2] = (x * ratio);
        scale[3] = (y * ratio);

        return scale;
    }

    //Scales a sketch
    public float[] scaleShape(float[] points, int masterHeight, int masterWidth, int pageNum) {
        float[] ret = new float[points.length];

        for (int i = 0; i < points.length; i++) {
            ret[i] = points[i] * getRatio(masterHeight, masterWidth, pageNum);
        }

        return ret;
    }

    //Addition eannis 03/27/2012 Rotates a point with respect to the origin.
    public float[] rotatePoint(float point[], int rotation) {
        double angle = Math.toRadians(rotation);

        //2 dimensional rotation formula
        //for a point (x,y), the corresponding point (x',y') under a rotation by an angle is given by
        //x'=x*cos(angle)-y*sin(angle), y'=x*sin(angle)+y*cos(angle)
        double xRotated = point[0] * Math.cos(angle) - point[1] * Math.sin(angle);
        double yRotated = point[0] * Math.sin(angle) + point[1] * Math.cos(angle);

        return new float[] { (float) xRotated, (float) yRotated };
    }

    //Translates a comment tag.
    public float[] commentTrans(float x, float y, int masterHeight, int masterWidth, int pageNum) {
        float[] f = new float[2];
        float ratio = getRatio(masterHeight, masterWidth, pageNum);

        float newW = r[pageNum - 1].getWidth() / 2f;
        float newH = r[pageNum - 1].getHeight() / 2f;

        float[] point = new float[] { x, y };
        if (rot > 0) {
            point = rotatePoint(point, rot);
        }

        f[0] = (point[0] * ratio) + newW;
        f[1] = (point[1] * ratio) + newH;

        return f;
    }

    //Translates a sketch
    public float[] sketchTrans(float[] points, int pageNum) {
        float[] f = new float[points.length];
        float newW = r[pageNum - 1].getWidth() / 2f;
        float newH = r[pageNum - 1].getHeight() / 2f;

        for (int i = 0; i < points.length; i = i + 2) {
            float[] point = new float[] { points[i], points[i + 1] };
            if (rot > 0) {
                point = rotatePoint(point, rot);
            }

            f[i] = point[0] + newW;
            f[i + 1] = point[1] + newH;
        }

        return f;
    }

    //Translates a stamp
    public float[] translate(float x, float y, float height, float width, int masterHeight, int masterWidth,
            int pageNum) {
        float[] ret = new float[2];
        float newW = width / 2f;
        float newH = height / 2f;
        float ratio = getRatio(masterHeight, masterWidth, pageNum);

        float[] point = new float[] { x, y };
        if (rot > 0) {
            point = rotatePoint(point, rot);
        }

        ret[0] = ((point[0] * ratio) + newW);
        ret[1] = ((point[1] * ratio) + newH);

        return ret;
    }

    //Breaks apart and deciphers the Open Layers format for shapes, and
    //converts into a usable format.
    public float[] shatterSketches(String input) {
        int t = input.indexOf("POLYGON");
        String subIn = "";
        if (t != -1) {
            subIn = input.substring(input.indexOf("((") + 2, input.indexOf("))"));
        } else {
            subIn = input.substring(input.indexOf("(") + 1, input.indexOf(")"));
        }
        String[] points = subIn.split(",");
        float[] st = new float[points.length * 2];

        int j = 0;

        for (int i = 0; i < points.length; i++) {
            String[] subHold = points[i].split(" ");
            st[j] = Float.parseFloat(subHold[0]);
            st[j + 1] = Float.parseFloat(subHold[1]);
            j = j + 2;
        }

        return st;
    }

    public String blankPDF(String loc, String message) throws DocumentException, FileNotFoundException {
        Rectangle size = new Rectangle(1700f, 1200f);
        Document doc = new Document(size);
        PdfWriter.getInstance(doc, new FileOutputStream(loc));

        doc.open();
        doc.add(new Paragraph("PDF could not be found: " + message));
        doc.close();

        return "";
    }

    public String pdfFusion(String pdfList) {

        return "";
    }

    //RTF Functions
    public String rtfDoc() throws FileNotFoundException, DocumentException {
        Document document = new Document();
        //RtfWriter2.getInstance(document,new FileOutputStream("testRTFdocument.rtf"));
        document.open();
        document.add(new Paragraph("Hello World!"));
        document.close();

        return "";
    }

    /* Added. ftorres 8/13/2014 - Following functions added to load bookmarks 
     * from a PDF document.
    **/
    private int parsePageNumber(String page) {
        if (page == null)
            return -1;
        String regex = " ";
        String[] tokens = page.split(regex);
        return (Integer.parseInt(tokens[0]));
    }

    public List<BookmarkData> parseBookmark(List<HashMap<String, Object>> bookmarks) {
        BookmarkData current;
        HashMap bm;

        List<BookmarkData> results = new ArrayList<BookmarkData>();

        if (bookmarks == null)
            return results;

        for (int x = 0; x < bookmarks.size(); x++) {
            current = new BookmarkData();
            bm = bookmarks.get(x);

            current.label = (String) bm.get("Title");
            current.pageNumber = parsePageNumber((String) bm.get("Page"));

            if (bm.get("Kids") != null) {
                current.hasChildren = true;
                current.children = parseBookmark((List) bm.get("Kids"));
            }
            results.add(current);
        }

        return results;
    }

    public List<BookmarkData> getBookmarks(String filePath) {
        try {
            PdfReader file = new PdfReader(filePath);
            List<HashMap<String, Object>> bookmarks = SimpleBookmark.getBookmark(file);

            List<BookmarkData> results = new ArrayList<BookmarkData>();
            results = parseBookmark(bookmarks);

            return results;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getSerializedBookmarks(String pdfPath) {
        List<BookmarkData> bookmarks = getBookmarks(pdfPath);
        return (serialize(bookmarks));
    }

    public String serializeBookmark(List<BookmarkData> bookmarks, String tab) {
        String result = "";
        if (bookmarks != null) {
            for (int x = 0; x < bookmarks.size(); x++) {
                BookmarkData current = bookmarks.get(x);

                result += tab + "{\n" + tab + "\t\"label\": \"" + current.label + "\",\n";
                result += tab + "\t\"pageNumber\": \"" + current.pageNumber + "\",\n";
                result += tab + "\t\"hasChildren\": \"" + current.hasChildren + "\",\n";
                result += tab + "\t\"children\": [";

                // Check and serialize children bookmarks
                if (current.hasChildren) {
                    result += "\n";
                    String subResults = serializeBookmark(current.children, tab + "\t\t");
                    result += subResults + tab + "\t]";
                } else {
                    result += "]";
                }

                if (x + 1 < bookmarks.size())
                    result += "\n" + tab + "},\n";
                else
                    result += "\n" + tab + "}\n";
            }
        }
        return result;
    }

    public String serialize(final List<BookmarkData> bookmarks) {
        String result;

        if (bookmarks.size() == 0)
            result = "No Bookmarks";
        else
            result = "[\n" + serializeBookmark(bookmarks, "\t") + "]";
        return result;
    }

    //A main method for testing purposes.
    public static void main(String[] args)
            throws FileNotFoundException, DocumentException, IOException, GeneralSecurityException {
        /*PDFPrint d = new PDFPrint();
        d.setPath(0, "E:\\Projects\\eplansoft\\trunk\\EPC/images/planCollab/commentTag.png", "", 2200, 1700);
        String[] test = new String[5];
            
        String stamp = "E:\\Projects\\eplansoft\\trunk\\EPC\\drawings\\uploads\\stamps\\1404157676276_MyStamp.png";
            
        d.createPDF(INPUTFILE, OUTPUTFILE);
            
        d.insertStamp(stamp, 0, 300, 10, 10, 0, "today", 1);
        d.insertStamp(stamp, 0, 300, 10, 10, 0, "today", 2);
        d.insertStamp(stamp, 0, 300, 10, 10, 0, "today", 3);*/
        //d.insertStamp("C:/dev/Projects/Owen/trunk/icc20/drawings/uploads/stamps/1303423107856_thumb.png", -1, -1, 1, 1, 1);
        /* d.insertStamp(stamp, -1, -1, 1, 1, 1);
         d.insertStamp(stamp, -1, -1, 1, 1, 1);
         d.insertStamp(stamp, -1, -1, 1, 1, 1);
         d.insertStamp(stamp, -1, -1, 1, 1, 1);
         d.insertStamp(stamp, -1, -1, 1, 1, 1);
         d.insertStamp(stamp, -1, -1, 1, 1, 1);
         d.insertStamp(stamp, -1, -1, 1, 1, 1);
            
         d.insertStamp(stamp, -1, -1, 1, 1, 1);
         d.insertStamp(stamp, -1, -1, 1, 1, 1);
         d.insertStamp(stamp, -1, -1, 1, 1, 1);*/
        /*d.closePDF();
        d.createPDF(OUTPUTFILE, OUTPUTFILE2);
        d.closePDF();
        d.insertSignature(OUTPUTFILE, OUTPUTFILE2, "E:\\Projects\\eplansoft\\eaereview\\trunk\\icc20\\includes/ks.ks", "SignatureField", "reason", "Location");
        d.rtfDoc();*/

    }
}