/* * $Id: PDFDocument.java,v 1.4 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package gnu.jpdf; import java.awt.FontFormatException; import java.awt.Image; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.Vector; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** *

* This class is the base of the PDF generator. A PDFDocument class is created * for a document, and each page, object, annotation, etc is added to the * document. Once complete, the document can be written to an OutputStream, and * the PDF document's internal structures are kept in sync.

* *

* Note that most programmers using this package will NEVER access one of these * objects directly. Most everything can be done using PDFJob and * PDFGraphics, so you don't need to directly instantiate a * PDFDocument

* *

* ezb - 20011115 - Wondering if the constructors should even be public. When * would someone want to make one of these and manipulate it outside the context * of a job and graphics object?

* * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.4 $, $Date: 2007/09/22 12:58:40 $ */ public class PDFDocument implements Serializable { /* * NOTE: This class originated in uk.org.retep.pdf, by Peter T. Mount, and it * has been modified by Eric Z. Beard, ericzbeard@hotmail.com. The * package name was changed to gnu.jpdf and several inner classes were * moved out into their own files. */ /** * This is used to allocate objects a unique serial number in the document. */ protected int objser; /** * This vector contains each indirect object within the document. */ protected Vector objects; /** * This is the Catalog object, which is required by each PDF Document */ private PDFCatalog catalog; /** * This is the info object. Although this is an optional object, we include * it. */ private PDFInfo info; /** * This is the Pages object, which is required by each PDF Document */ private PDFPageList pdfPageList; /** * This is the Outline object, which is optional */ private PDFOutline outline; /** * This holds a PDFObject describing the default border for annotations. * It's only used when the document is being written. */ protected PDFObject defaultOutlineBorder; //JPEXS: cache for already used images private final Map usedImages = new HashMap(); public boolean isImageCached(Image image, boolean interpolate) { return usedImages.containsKey(new ImageInterpolate(image, interpolate)); } public PDFImage getCachedImage(Image image, boolean interpolate) { if (!isImageCached(image, interpolate)) { return null; } return usedImages.get(new ImageInterpolate(image, interpolate)); } public void cacheImage(Image image, PDFImage pdfImage) { usedImages.put(new ImageInterpolate(image, pdfImage.isInterpolate()), pdfImage); } /** *

* This page mode indicates that the document should be opened just with the * page visible. This is the default

*/ public static final int USENONE = 0; /** *

* This page mode indicates that the Outlines should also be displayed when * the document is opened.

*/ public static final int USEOUTLINES = 1; /** *

* This page mode indicates that the Thumbnails should be visible when the * document first opens.

*/ public static final int USETHUMBS = 2; /** *

* This page mode indicates that when the document is opened, it is * displayed in full-screen-mode. There is no menu bar, window controls nor * any other window present.

*/ public static final int FULLSCREEN = 3; /** *

* These map the page modes just defined to the pagemodes setting of PDF. *

*/ public static final String PDF_PAGE_MODES[] = { "/UseNone", "/UseOutlines", "/UseThumbs", "/FullScreen" }; /** * This is used to provide a unique name for a font */ private int fontid = 0; /** *

* This is used to provide a unique name for an image

*/ private int imageid = 0; /** * This holds the current fonts */ private Vector fonts; /** *

* This creates a PDF document with the default pagemode

*/ public PDFDocument() { this(USENONE); } /** *

* This creates a PDF document

* * @param pagemode an int, determines how the document will present itself * to the viewer when it first opens. */ public PDFDocument(int pagemode) { objser = 1; objects = new Vector(); fonts = new Vector(); // Now create some standard objects add(pdfPageList = new PDFPageList()); add(catalog = new PDFCatalog(pdfPageList, pagemode)); add(info = new PDFInfo()); // Acroread on linux seems to die if there is no root outline add(getOutline()); } /** * This adds a top level object to the document. * *

* Once added, it is allocated a unique serial number. * *

* Note: Not all object are added directly using this method. Some * objects which have Kids (in PDF sub-objects or children are called Kids) * will have their own add() method, which will call this one internally. * * @param obj The PDFObject to add to the document * @return the unique serial number for this object. */ public synchronized int add(PDFObject obj) { objects.addElement(obj); obj.objser = objser++; // create a new serial number obj.pdfDocument = this; // so they can find the document they belong to // If its a page, then add it to the pages collection if (obj instanceof PDFPage) { pdfPageList.add((PDFPage) obj); } return obj.objser; } /** *

* This returns a specific page. It's used mainly when using a Serialized * template file.

* * ?? How does a serialized template file work ??? * * @param page page number to return * @return PDFPage at that position */ public PDFPage getPage(int page) { return pdfPageList.getPage(page); } /** * @return the root outline */ public PDFOutline getOutline() { if (outline == null) { outline = new PDFOutline(); catalog.setOutline(outline); } return outline; } /** * This returns a font of the specified type and font. If the font has not * been defined, it creates a new font in the PDF document, and returns it. * * @param type PDF Font Type - usually "/Type1" * @param font Java font name * @param style java.awt.Font style (NORMAL, BOLD etc) * @return PDFFont defining this font */ public PDFFont getFont(String type, String font, int style) { for (PDFFont ft : fonts) { if (ft.equals(type, font, style)) { return ft; } } // the font wasn't found, so create it fontid++; PDFFont ft = new PDFFont("/F" + fontid, type, font, style); add(ft); fonts.addElement(ft); return ft; } public PDFFont getEmbeddedFont(String font, int style, File file) throws FileNotFoundException, IOException { for (PDFFont ft : fonts) { if (ft.equals("/TrueType", font, style)) { return ft; } } PDFStream fontFile2 = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Length1 ".getBytes("UTF-8")); os.write(Integer.toString(buf.size()).getBytes("UTF-8")); os.write("\n".getBytes("UTF-8")); writeStream(os); } }; fontFile2.setDeflate(true); OutputStream ff2Os = fontFile2.getOutputStream(); InputStream is = new FileInputStream(file); byte buf[] = new byte[1024]; int cnt = 0; while ((cnt = is.read(buf)) > 0) { ff2Os.write(buf, 0, cnt); } is.close(); //ff2Os.write("AHOJ".getBytes("UTF-8")); add(fontFile2); // the font wasn't found, so create it fontid++; final TtfParser par = new TtfParser(); try { par.loadFromTTF(file); } catch (FontFormatException ex) { Logger.getLogger(PDFDocument.class.getName()).log(Level.SEVERE, null, ex); } PDFStream cidToGidMap = new PDFStream(); cidToGidMap.getOutputStream().write(par.getCidtogidmap()); cidToGidMap.setDeflate(true); add(cidToGidMap); //ToUnicode map for Identity-H stream String uniIdentityH = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n/WMode 0 def\n1 begincodespacerange\n<0000> \nendcodespacerange\n100 beginbfrange\n<0000> <00ff> <0000>\n<0100> <01ff> <0100>\n<0200> <02ff> <0200>\n<0300> <03ff> <0300>\n<0400> <04ff> <0400>\n<0500> <05ff> <0500>\n<0600> <06ff> <0600>\n<0700> <07ff> <0700>\n<0800> <08ff> <0800>\n<0900> <09ff> <0900>\n<0a00> <0aff> <0a00>\n<0b00> <0bff> <0b00>\n<0c00> <0cff> <0c00>\n<0d00> <0dff> <0d00>\n<0e00> <0eff> <0e00>\n<0f00> <0fff> <0f00>\n<1000> <10ff> <1000>\n<1100> <11ff> <1100>\n<1200> <12ff> <1200>\n<1300> <13ff> <1300>\n<1400> <14ff> <1400>\n<1500> <15ff> <1500>\n<1600> <16ff> <1600>\n<1700> <17ff> <1700>\n<1800> <18ff> <1800>\n<1900> <19ff> <1900>\n<1a00> <1aff> <1a00>\n<1b00> <1bff> <1b00>\n<1c00> <1cff> <1c00>\n<1d00> <1dff> <1d00>\n<1e00> <1eff> <1e00>\n<1f00> <1fff> <1f00>\n<2000> <20ff> <2000>\n<2100> <21ff> <2100>\n<2200> <22ff> <2200>\n<2300> <23ff> <2300>\n<2400> <24ff> <2400>\n<2500> <25ff> <2500>\n<2600> <26ff> <2600>\n<2700> <27ff> <2700>\n<2800> <28ff> <2800>\n<2900> <29ff> <2900>\n<2a00> <2aff> <2a00>\n<2b00> <2bff> <2b00>\n<2c00> <2cff> <2c00>\n<2d00> <2dff> <2d00>\n<2e00> <2eff> <2e00>\n<2f00> <2fff> <2f00>\n<3000> <30ff> <3000>\n<3100> <31ff> <3100>\n<3200> <32ff> <3200>\n<3300> <33ff> <3300>\n<3400> <34ff> <3400>\n<3500> <35ff> <3500>\n<3600> <36ff> <3600>\n<3700> <37ff> <3700>\n<3800> <38ff> <3800>\n<3900> <39ff> <3900>\n<3a00> <3aff> <3a00>\n<3b00> <3bff> <3b00>\n<3c00> <3cff> <3c00>\n<3d00> <3dff> <3d00>\n<3e00> <3eff> <3e00>\n<3f00> <3fff> <3f00>\n<4000> <40ff> <4000>\n<4100> <41ff> <4100>\n<4200> <42ff> <4200>\n<4300> <43ff> <4300>\n<4400> <44ff> <4400>\n<4500> <45ff> <4500>\n<4600> <46ff> <4600>\n<4700> <47ff> <4700>\n<4800> <48ff> <4800>\n<4900> <49ff> <4900>\n<4a00> <4aff> <4a00>\n<4b00> <4bff> <4b00>\n<4c00> <4cff> <4c00>\n<4d00> <4dff> <4d00>\n<4e00> <4eff> <4e00>\n<4f00> <4fff> <4f00>\n<5000> <50ff> <5000>\n<5100> <51ff> <5100>\n<5200> <52ff> <5200>\n<5300> <53ff> <5300>\n<5400> <54ff> <5400>\n<5500> <55ff> <5500>\n<5600> <56ff> <5600>\n<5700> <57ff> <5700>\n<5800> <58ff> <5800>\n<5900> <59ff> <5900>\n<5a00> <5aff> <5a00>\n<5b00> <5bff> <5b00>\n<5c00> <5cff> <5c00>\n<5d00> <5dff> <5d00>\n<5e00> <5eff> <5e00>\n<5f00> <5fff> <5f00>\n<6000> <60ff> <6000>\n<6100> <61ff> <6100>\n<6200> <62ff> <6200>\n<6300> <63ff> <6300>\nendbfrange\n100 beginbfrange\n<6400> <64ff> <6400>\n<6500> <65ff> <6500>\n<6600> <66ff> <6600>\n<6700> <67ff> <6700>\n<6800> <68ff> <6800>\n<6900> <69ff> <6900>\n<6a00> <6aff> <6a00>\n<6b00> <6bff> <6b00>\n<6c00> <6cff> <6c00>\n<6d00> <6dff> <6d00>\n<6e00> <6eff> <6e00>\n<6f00> <6fff> <6f00>\n<7000> <70ff> <7000>\n<7100> <71ff> <7100>\n<7200> <72ff> <7200>\n<7300> <73ff> <7300>\n<7400> <74ff> <7400>\n<7500> <75ff> <7500>\n<7600> <76ff> <7600>\n<7700> <77ff> <7700>\n<7800> <78ff> <7800>\n<7900> <79ff> <7900>\n<7a00> <7aff> <7a00>\n<7b00> <7bff> <7b00>\n<7c00> <7cff> <7c00>\n<7d00> <7dff> <7d00>\n<7e00> <7eff> <7e00>\n<7f00> <7fff> <7f00>\n<8000> <80ff> <8000>\n<8100> <81ff> <8100>\n<8200> <82ff> <8200>\n<8300> <83ff> <8300>\n<8400> <84ff> <8400>\n<8500> <85ff> <8500>\n<8600> <86ff> <8600>\n<8700> <87ff> <8700>\n<8800> <88ff> <8800>\n<8900> <89ff> <8900>\n<8a00> <8aff> <8a00>\n<8b00> <8bff> <8b00>\n<8c00> <8cff> <8c00>\n<8d00> <8dff> <8d00>\n<8e00> <8eff> <8e00>\n<8f00> <8fff> <8f00>\n<9000> <90ff> <9000>\n<9100> <91ff> <9100>\n<9200> <92ff> <9200>\n<9300> <93ff> <9300>\n<9400> <94ff> <9400>\n<9500> <95ff> <9500>\n<9600> <96ff> <9600>\n<9700> <97ff> <9700>\n<9800> <98ff> <9800>\n<9900> <99ff> <9900>\n<9a00> <9aff> <9a00>\n<9b00> <9bff> <9b00>\n<9c00> <9cff> <9c00>\n<9d00> <9dff> <9d00>\n<9e00> <9eff> <9e00>\n<9f00> <9fff> <9f00>\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \nendbfrange\n56 beginbfrange\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"; PDFStream toUnicode = new PDFStream(); toUnicode.getOutputStream().write(uniIdentityH.getBytes("UTF-8")); toUnicode.setDeflate(true); add(toUnicode); PDFObject fontDescriptor = new PDFObject("/FontDescriptor") { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FontName " + font + "\n").getBytes("UTF-8")); os.write(("/Flags " + par.getFlags() + "\n").getBytes("UTF-8")); os.write(("/FontBBox [" + par.getBbox().xMin + " " + par.getBbox().yMin + " " + par.getBbox().xMax + " " + par.getBbox().yMax + "]\n").getBytes("UTF-8")); os.write(("/ItalicAngle 0" + par.getItalicAngle() + "\n").getBytes("UTF-8")); os.write(("/Ascent " + par.getAscent() + "\n").getBytes("UTF-8")); os.write(("/Descent " + par.getDescent() + "\n").getBytes("UTF-8")); os.write(("/Leading " + par.getLeading() + "\n").getBytes("UTF-8")); os.write(("/CapHeight " + par.getCapHeight() + "\n").getBytes("UTF-8")); os.write(("/XHeight " + par.getxHeight() + "\n").getBytes("UTF-8")); os.write(("/StemV " + par.getStemV() + "\n").getBytes("UTF-8")); os.write(("/StemH " + par.getStemH() + "\n").getBytes("UTF-8")); os.write(("/AvgWidth " + par.getAvgWidth() + "\n").getBytes("UTF-8")); os.write(("/MaxWidth " + par.getMaxWidth() + "\n").getBytes("UTF-8")); os.write(("/MissingWidth " + par.getMissingWidth() + "\n").getBytes("UTF-8")); os.write(("/FontFile2 " + fontFile2.getSerialID() + " 0 R\n").getBytes("UTF-8")); writeEnd(os); } }; add(fontDescriptor); PDFObject descendantFont = new PDFObject("/Font") { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(" /Subtype /CIDFontType2".getBytes("UTF-8")); os.write((" /BaseFont " + font).getBytes("UTF-8")); String cidinfo = "/Registry " + PDFStringHelper.makePDFString("Adobe"); cidinfo += " /Ordering " + PDFStringHelper.makePDFString("Identity"); cidinfo += " /Supplement 0"; os.write((" /CIDSystemInfo << " + cidinfo + " >>").getBytes("UTF-8")); os.write((" /FontDescriptor " + fontDescriptor.getSerialID() + " 0 R").getBytes("UTF-8")); os.write((" /DW " + par.getDw() + "\n").getBytes("UTF-8")); // default width os.write((getFontWidths(par.getCw()) + "\n").getBytes("UTF-8")); //if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) { os.write(("/CIDToGIDMap " + cidToGidMap.getSerialID() + " 0 R").getBytes("UTF-8")); //}*/ writeEnd(os); } }; add(descendantFont); PDFFont ft = new PDFEmbeddedFont("/F" + fontid, font, style, descendantFont.getSerialID() + " 0 R", toUnicode.getSerialID() + " 0 R"); add(ft); fonts.addElement(ft); return ft; } public static String getFontWidths(Map cw) { StringBuilder widths = new StringBuilder(); int prevC = -5; boolean first = true; for (int c : cw.keySet()) { int w = cw.get(c); if (first) { widths.append("" + c + " [" + w); first = false; } else if (c == prevC + 1) { widths.append(" " + w); } else { widths.append("]"); widths.append(" " + c + " [" + w); } prevC = c; } if (!first) { widths.append("]"); } return "/W [" + widths.toString() + " ]"; } /** * Sets a unique name to a PDFImage * * @param img PDFImage to set the name of * @return the name given to the image */ public String setImageName(PDFImage img) { imageid++; img.setName("/Image" + imageid); return img.getName(); } /** *

* Set the PDFInfo object, which contains author, title, keywords, etc

* * @param info a PDFInof object */ public void setPDFInfo(PDFInfo info) { this.info = info; } /** *

* Get the PDFInfo object, which contains author, title, keywords, etc

* * @return the PDFInfo object for this document. */ public PDFInfo getPDFInfo() { return this.info; } /** * This writes the document to an OutputStream. * *

* Note: You can call this as many times as you wish, as long as the * calls are not running at the same time. * *

* Also, objects can be added or amended between these calls. * *

* Also, the OutputStream is not closed, but will be flushed on completion. * It is up to the caller to close the stream. * * @param os OutputStream to write the document to * @exception IOException on error */ public void write(OutputStream os) throws IOException { PDFOutput pos = new PDFOutput(os); // Write each object to the OutputStream. We call via the output // as that builds the xref table for (PDFObject o : objects) { pos.write(o); } // Finally close the output, which writes the xref table. pos.close(); // and flush the output stream to ensure everything is written. os.flush(); } } // end class PDFDocument