extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/pdf/PdfUtil7.java

Code
Comments
Other
Rev Date Author Line
6058 17 Nov 20 nicklas 1 package net.sf.basedb.reggie.pdf;
6058 17 Nov 20 nicklas 2
6058 17 Nov 20 nicklas 3 import java.io.ByteArrayOutputStream;
6058 17 Nov 20 nicklas 4 import java.io.FileInputStream;
6058 17 Nov 20 nicklas 5 import java.io.IOException;
6058 17 Nov 20 nicklas 6 import java.io.InputStream;
6058 17 Nov 20 nicklas 7 import java.io.OutputStream;
6092 14 Dec 20 nicklas 8 import java.util.ArrayList;
6449 20 Oct 21 nicklas 9 import java.util.Date;
6081 25 Nov 20 nicklas 10 import java.util.HashMap;
6092 14 Dec 20 nicklas 11 import java.util.List;
6081 25 Nov 20 nicklas 12 import java.util.Map;
6058 17 Nov 20 nicklas 13
6061 18 Nov 20 nicklas 14 import com.itextpdf.barcodes.Barcode128;
6073 20 Nov 20 nicklas 15 import com.itextpdf.io.font.FontNames;
6058 17 Nov 20 nicklas 16 import com.itextpdf.io.font.PdfEncodings;
6058 17 Nov 20 nicklas 17 import com.itextpdf.io.image.ImageData;
6058 17 Nov 20 nicklas 18 import com.itextpdf.io.image.ImageDataFactory;
6060 17 Nov 20 nicklas 19 import com.itextpdf.io.image.ImageType;
6060 17 Nov 20 nicklas 20 import com.itextpdf.io.image.ImageTypeDetector;
6100 12 Jan 21 nicklas 21 import com.itextpdf.io.source.PdfTokenizer;
6100 12 Jan 21 nicklas 22 import com.itextpdf.io.source.RandomAccessFileOrArray;
6100 12 Jan 21 nicklas 23 import com.itextpdf.io.source.RandomAccessSourceFactory;
6058 17 Nov 20 nicklas 24 import com.itextpdf.kernel.colors.Color;
6058 17 Nov 20 nicklas 25 import com.itextpdf.kernel.colors.ColorConstants;
6058 17 Nov 20 nicklas 26 import com.itextpdf.kernel.font.PdfFont;
6058 17 Nov 20 nicklas 27 import com.itextpdf.kernel.font.PdfFontFactory;
6061 18 Nov 20 nicklas 28 import com.itextpdf.kernel.geom.AffineTransform;
6058 17 Nov 20 nicklas 29 import com.itextpdf.kernel.geom.PageSize;
6070 20 Nov 20 nicklas 30 import com.itextpdf.kernel.geom.Point;
6058 17 Nov 20 nicklas 31 import com.itextpdf.kernel.geom.Rectangle;
6058 17 Nov 20 nicklas 32 import com.itextpdf.kernel.pdf.EncryptionConstants;
6449 20 Oct 21 nicklas 33 import com.itextpdf.kernel.pdf.PdfDate;
6100 12 Jan 21 nicklas 34 import com.itextpdf.kernel.pdf.PdfDictionary;
6058 17 Nov 20 nicklas 35 import com.itextpdf.kernel.pdf.PdfDocument;
6058 17 Nov 20 nicklas 36 import com.itextpdf.kernel.pdf.PdfDocumentInfo;
6100 12 Jan 21 nicklas 37 import com.itextpdf.kernel.pdf.PdfLiteral;
6100 12 Jan 21 nicklas 38 import com.itextpdf.kernel.pdf.PdfName;
6100 12 Jan 21 nicklas 39 import com.itextpdf.kernel.pdf.PdfObject;
6058 17 Nov 20 nicklas 40 import com.itextpdf.kernel.pdf.PdfPage;
6058 17 Nov 20 nicklas 41 import com.itextpdf.kernel.pdf.PdfReader;
6100 12 Jan 21 nicklas 42 import com.itextpdf.kernel.pdf.PdfStream;
6058 17 Nov 20 nicklas 43 import com.itextpdf.kernel.pdf.PdfWriter;
6058 17 Nov 20 nicklas 44 import com.itextpdf.kernel.pdf.WriterProperties;
6058 17 Nov 20 nicklas 45 import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
6060 17 Nov 20 nicklas 46 import com.itextpdf.kernel.pdf.canvas.wmf.WmfImageData;
6060 17 Nov 20 nicklas 47 import com.itextpdf.kernel.pdf.canvas.wmf.WmfImageHelper;
6075 23 Nov 20 nicklas 48 import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
6058 17 Nov 20 nicklas 49 import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
6092 14 Dec 20 nicklas 50 import com.itextpdf.pdfcleanup.PdfCleanUpLocation;
6092 14 Dec 20 nicklas 51 import com.itextpdf.pdfcleanup.PdfCleanUpTool;
6092 14 Dec 20 nicklas 52 import com.itextpdf.pdfcleanup.autosweep.PdfAutoSweep;
6092 14 Dec 20 nicklas 53 import com.itextpdf.pdfcleanup.autosweep.RegexBasedCleanupStrategy;
6064 18 Nov 20 nicklas 54 import com.itextpdf.svg.converter.SvgConverter;
6058 17 Nov 20 nicklas 55
6058 17 Nov 20 nicklas 56 import net.sf.basedb.core.Application;
6058 17 Nov 20 nicklas 57 import net.sf.basedb.reggie.Reggie;
6058 17 Nov 20 nicklas 58 import net.sf.basedb.util.FileUtil;
6058 17 Nov 20 nicklas 59
6058 17 Nov 20 nicklas 60 /**
6058 17 Nov 20 nicklas 61   Utility class for working with PDF documents using iText PDF version 7. 
6058 17 Nov 20 nicklas 62   We always work with A4 documents (unless noted).
6058 17 Nov 20 nicklas 63   
6058 17 Nov 20 nicklas 64   @author nicklas
6058 17 Nov 20 nicklas 65   @since 4.28
6058 17 Nov 20 nicklas 66 */
6058 17 Nov 20 nicklas 67 public class PdfUtil7 
6058 17 Nov 20 nicklas 68 {
6058 17 Nov 20 nicklas 69   
6062 18 Nov 20 nicklas 70   /**
6062 18 Nov 20 nicklas 71     DPI value used in coordinate system for PDF.
6062 18 Nov 20 nicklas 72   */
6062 18 Nov 20 nicklas 73   public static final float DPI = 72;
6062 18 Nov 20 nicklas 74   
6062 18 Nov 20 nicklas 75   /**
6062 18 Nov 20 nicklas 76     Full with in points for an A4 PDF document (595).
6062 18 Nov 20 nicklas 77   */
6062 18 Nov 20 nicklas 78   public static final float FULL_WIDTH = PageSize.A4.getWidth();
6062 18 Nov 20 nicklas 79   
6062 18 Nov 20 nicklas 80   /**
6062 18 Nov 20 nicklas 81     Full height in points for an A4 PDF document (842).
6062 18 Nov 20 nicklas 82   */
6062 18 Nov 20 nicklas 83   public static final float FULL_HEIGHT =  PageSize.A4.getHeight();
6062 18 Nov 20 nicklas 84
6062 18 Nov 20 nicklas 85   
6060 17 Nov 20 nicklas 86   private String title;
6060 17 Nov 20 nicklas 87   private String creator;
6058 17 Nov 20 nicklas 88
6058 17 Nov 20 nicklas 89   private PdfWriter writer;
6058 17 Nov 20 nicklas 90   private PdfDocument pdf;
6058 17 Nov 20 nicklas 91   private PdfPage page;
6058 17 Nov 20 nicklas 92   private PdfCanvas canvas;
6060 17 Nov 20 nicklas 93
6060 17 Nov 20 nicklas 94   private PdfReader reader;
6058 17 Nov 20 nicklas 95   
6058 17 Nov 20 nicklas 96   private PdfFont defaultFont;
6058 17 Nov 20 nicklas 97   private PdfFont boldFont;
6073 20 Nov 20 nicklas 98   private PdfFont italicFont;
6073 20 Nov 20 nicklas 99   private PdfFont boldItalicFont;
6073 20 Nov 20 nicklas 100   
6058 17 Nov 20 nicklas 101   private Color currentColor = ColorConstants.BLACK;
6058 17 Nov 20 nicklas 102   private Color clearColor = ColorConstants.WHITE;
6075 23 Nov 20 nicklas 103   private float clearOpacity = Float.NaN;
6092 14 Dec 20 nicklas 104   private Color redactColor = ColorConstants.WHITE;
6100 12 Jan 21 nicklas 105   private float redactOpacity = Float.NaN;
6058 17 Nov 20 nicklas 106   
6092 14 Dec 20 nicklas 107   private Map<String, PdfDocument> docCache = new HashMap<>();
6092 14 Dec 20 nicklas 108   private List<PdfCleanUpLocation> redactLocations = new ArrayList<>();
6081 25 Nov 20 nicklas 109
6058 17 Nov 20 nicklas 110   /**
6058 17 Nov 20 nicklas 111     Create a new PDF document.
6058 17 Nov 20 nicklas 112   */
6058 17 Nov 20 nicklas 113   public PdfUtil7(String title, String creator)
6058 17 Nov 20 nicklas 114   {
6058 17 Nov 20 nicklas 115     this.title = title;
6058 17 Nov 20 nicklas 116     this.creator = creator;
6058 17 Nov 20 nicklas 117     this.defaultFont = getDefaultFont();
6058 17 Nov 20 nicklas 118   }
6058 17 Nov 20 nicklas 119
6058 17 Nov 20 nicklas 120   /**
6060 17 Nov 20 nicklas 121     Create a new PDF document that is a copy
6060 17 Nov 20 nicklas 122     of an existing PDF file.
6060 17 Nov 20 nicklas 123   */
6060 17 Nov 20 nicklas 124   public PdfUtil7(InputStream existing)
6060 17 Nov 20 nicklas 125     throws IOException
6060 17 Nov 20 nicklas 126   {
6060 17 Nov 20 nicklas 127     this.reader = new PdfReader(existing);
6060 17 Nov 20 nicklas 128     this.defaultFont = getDefaultFont();
6060 17 Nov 20 nicklas 129   }
6060 17 Nov 20 nicklas 130   
6060 17 Nov 20 nicklas 131   /**
6058 17 Nov 20 nicklas 132     Open the PDF document so that content can be added to it.
6058 17 Nov 20 nicklas 133     @param out The outputstream to write the PDF document to
6058 17 Nov 20 nicklas 134   */
6058 17 Nov 20 nicklas 135   public void open(OutputStream out)
6058 17 Nov 20 nicklas 136   {
6058 17 Nov 20 nicklas 137     open(out, null);
6058 17 Nov 20 nicklas 138   }
6058 17 Nov 20 nicklas 139
6058 17 Nov 20 nicklas 140   
6058 17 Nov 20 nicklas 141   /**
6058 17 Nov 20 nicklas 142     Open the PDF document so that content can be added to it. If a password
6058 17 Nov 20 nicklas 143     is specified the PDF is encrypted using AES-256 encryption. The password
6058 17 Nov 20 nicklas 144     is the owner password. A random string is generated for the user password.
6058 17 Nov 20 nicklas 145       
6058 17 Nov 20 nicklas 146     @param out The outputstream to write the PDF document to
6058 17 Nov 20 nicklas 147     @param password If not null, the pdf if password protected and encrypted
6058 17 Nov 20 nicklas 148       with AES 256.
6058 17 Nov 20 nicklas 149   */
6058 17 Nov 20 nicklas 150   public void open(OutputStream out, String password)
6058 17 Nov 20 nicklas 151   {
6058 17 Nov 20 nicklas 152     WriterProperties wp = new WriterProperties();
6058 17 Nov 20 nicklas 153     if (password != null)
6058 17 Nov 20 nicklas 154     {
6058 17 Nov 20 nicklas 155       wp.setStandardEncryption(Application.generateRandomId(16).getBytes(), password.getBytes(), 0, EncryptionConstants.ENCRYPTION_AES_256);
6058 17 Nov 20 nicklas 156     }
6060 17 Nov 20 nicklas 157     writer = new PdfWriter(out, wp);
6060 17 Nov 20 nicklas 158     if (reader != null)
6060 17 Nov 20 nicklas 159     {
6060 17 Nov 20 nicklas 160       pdf = new PdfDocument(reader, writer);
6060 17 Nov 20 nicklas 161       page = pdf.getFirstPage();
6060 17 Nov 20 nicklas 162       canvas = new PdfCanvas(page);
6060 17 Nov 20 nicklas 163     }
6060 17 Nov 20 nicklas 164     else
6060 17 Nov 20 nicklas 165     {
6060 17 Nov 20 nicklas 166       pdf = new PdfDocument(writer);
6060 17 Nov 20 nicklas 167       PdfDocumentInfo info = pdf.getDocumentInfo();
6060 17 Nov 20 nicklas 168       info.setCreator("Reggie " + Reggie.VERSION + (creator != null ? " ("+creator+")" : ""));
6060 17 Nov 20 nicklas 169       info.setTitle(title);
6060 17 Nov 20 nicklas 170       newPage();
6060 17 Nov 20 nicklas 171     }
6058 17 Nov 20 nicklas 172   }
6058 17 Nov 20 nicklas 173   
6058 17 Nov 20 nicklas 174   /**
6100 12 Jan 21 nicklas 175     Try to remove contents falling with the specified redact regions.
6100 12 Jan 21 nicklas 176     This code will check and parse the "content streams" on each page
6100 12 Jan 21 nicklas 177     and XObject streams. The code is not intentended for generic use
6100 12 Jan 21 nicklas 178     since it assumes a simple structure of the PDF document when it
6100 12 Jan 21 nicklas 179     comes to nesting and matrix transformations (eg. none!).
6100 12 Jan 21 nicklas 180     
6100 12 Jan 21 nicklas 181     It seems to work well with the current SCAN-B report but may break 
6100 12 Jan 21 nicklas 182     if we change anything in templates, iText libraries, etc.
6100 12 Jan 21 nicklas 183     
6100 12 Jan 21 nicklas 184     Regions that has been selected for redaction, can optionally be painted
6100 12 Jan 21 nicklas 185     with "redactColor". Use "false" or a transparant color to make sure that
6100 12 Jan 21 nicklas 186     nothing is left that was expected to be removed. As a last resort, paint
6100 12 Jan 21 nicklas 187     with solid white to hide contents but NOT if the data is sensitive.
6100 12 Jan 21 nicklas 188     @since 4.30
6100 12 Jan 21 nicklas 189   */
6100 12 Jan 21 nicklas 190   private void simpleRedact(boolean paintRegions)
6100 12 Jan 21 nicklas 191     throws IOException
6100 12 Jan 21 nicklas 192   {
6100 12 Jan 21 nicklas 193     
6100 12 Jan 21 nicklas 194     for (int pageNo = 1; pageNo <= pdf.getNumberOfPages(); pageNo++)
6100 12 Jan 21 nicklas 195     {
6100 12 Jan 21 nicklas 196       // Get regions on this page
6100 12 Jan 21 nicklas 197       List<PdfCleanUpLocation> redactRegions = new ArrayList<>();
6100 12 Jan 21 nicklas 198       for (PdfCleanUpLocation loc : redactLocations)
6100 12 Jan 21 nicklas 199       {
6100 12 Jan 21 nicklas 200         if (loc.getPage() == pageNo) redactRegions.add(loc);
6100 12 Jan 21 nicklas 201       }
6100 12 Jan 21 nicklas 202       if (redactRegions.size() == 0) continue;
6100 12 Jan 21 nicklas 203
6100 12 Jan 21 nicklas 204       PdfPage page = pdf.getPage(pageNo);
6100 12 Jan 21 nicklas 205       
6100 12 Jan 21 nicklas 206       // Check content streams
6100 12 Jan 21 nicklas 207       for (int streamNo = 0; streamNo < page.getContentStreamCount(); streamNo++)
6100 12 Jan 21 nicklas 208       {
6100 12 Jan 21 nicklas 209         //System.out.println("Page: " + pageNo + "; stream: " + streamNo);
6100 12 Jan 21 nicklas 210         cleanStream(page.getContentStream(streamNo), redactRegions);
6100 12 Jan 21 nicklas 211       }
6100 12 Jan 21 nicklas 212           
6100 12 Jan 21 nicklas 213       // Check xobject streams
6100 12 Jan 21 nicklas 214       // I hope it is safe to do this... since we have less control over them...
6100 12 Jan 21 nicklas 215       PdfDictionary resources = page.getPdfObject().getAsDictionary(PdfName.Resources);
6100 12 Jan 21 nicklas 216       if (resources != null)
6100 12 Jan 21 nicklas 217       {
6100 12 Jan 21 nicklas 218         PdfDictionary xobjects = resources.getAsDictionary(PdfName.XObject);
6100 12 Jan 21 nicklas 219         if (xobjects != null)
6100 12 Jan 21 nicklas 220         {
6100 12 Jan 21 nicklas 221           for (Map.Entry<PdfName, PdfObject> entry : xobjects.entrySet())
6100 12 Jan 21 nicklas 222           {
6100 12 Jan 21 nicklas 223             PdfObject obj = entry.getValue();
6100 12 Jan 21 nicklas 224             if (!obj.isStream()) continue;
6100 12 Jan 21 nicklas 225             
6100 12 Jan 21 nicklas 226             //System.out.println("Page: " + pageNo + "; stream: " + entry.getKey());
6100 12 Jan 21 nicklas 227             cleanStream((PdfStream)obj, redactRegions);
6100 12 Jan 21 nicklas 228           }
6100 12 Jan 21 nicklas 229         }
6100 12 Jan 21 nicklas 230       }
6100 12 Jan 21 nicklas 231       
6100 12 Jan 21 nicklas 232       // Paint regions
6100 12 Jan 21 nicklas 233       if (paintRegions)
6100 12 Jan 21 nicklas 234       {
6100 12 Jan 21 nicklas 235         PdfCanvas canvas = new PdfCanvas(page);
6100 12 Jan 21 nicklas 236         canvas.saveState();
6100 12 Jan 21 nicklas 237         if (!Float.isNaN(redactOpacity)) 
6100 12 Jan 21 nicklas 238         {
6100 12 Jan 21 nicklas 239           canvas.setExtGState(new PdfExtGState().setFillOpacity(redactOpacity));
6100 12 Jan 21 nicklas 240         }
6100 12 Jan 21 nicklas 241         for (PdfCleanUpLocation loc : redactRegions)
6100 12 Jan 21 nicklas 242         {
6100 12 Jan 21 nicklas 243           canvas.setFillColor(loc.getCleanUpColor());
6100 12 Jan 21 nicklas 244           canvas.rectangle(loc.getRegion());
6100 12 Jan 21 nicklas 245           canvas.fill();
6100 12 Jan 21 nicklas 246         }
6100 12 Jan 21 nicklas 247         canvas.restoreState();
6100 12 Jan 21 nicklas 248       }
6100 12 Jan 21 nicklas 249     }
6100 12 Jan 21 nicklas 250   }
6100 12 Jan 21 nicklas 251   
6100 12 Jan 21 nicklas 252   /**
6100 12 Jan 21 nicklas 253     A very simple cleaning procedure. We assume the PDF is structured with
6100 12 Jan 21 nicklas 254     a 'q' and 'Q' delimiting a single text or xobject. We check if we find a location
6100 12 Jan 21 nicklas 255     coordinate (either in 'Td' (for text) or 'cm' (for xobject) that falls withing one
6100 12 Jan 21 nicklas 256     of the redaction regions. If not, we copy the data between 'q' and 'Q' otherwise we 
6100 12 Jan 21 nicklas 257     simply skip it. Note that this will not remove the referenced xobjects.
6100 12 Jan 21 nicklas 258     
6100 12 Jan 21 nicklas 259     NOTE! This is not a generic removal procedure. It has been tested to work with the PDF
6100 12 Jan 21 nicklas 260     generated by this class. Changes to the implementation or structure may cause this code
6100 12 Jan 21 nicklas 261     to stop working.
6100 12 Jan 21 nicklas 262   */
6100 12 Jan 21 nicklas 263   private void cleanStream(PdfStream stream, List<PdfCleanUpLocation> redactRegions)
6100 12 Jan 21 nicklas 264     throws IOException
6100 12 Jan 21 nicklas 265   {
6100 12 Jan 21 nicklas 266     if (stream.getLength() <= 0) return;
6100 12 Jan 21 nicklas 267     
6100 12 Jan 21 nicklas 268     byte[] data = stream.getBytes();
6100 12 Jan 21 nicklas 269     byte[] out = new byte[data.length];
6100 12 Jan 21 nicklas 270     
6100 12 Jan 21 nicklas 271     PdfTokenizer tokenizer = new PdfTokenizer(new RandomAccessFileOrArray(new RandomAccessSourceFactory().createSource(data)));
6100 12 Jan 21 nicklas 272     
6100 12 Jan 21 nicklas 273     // Flags if we should copy and/or cut a range
6100 12 Jan 21 nicklas 274     boolean copy = true;
6100 12 Jan 21 nicklas 275     boolean cut = false;
6100 12 Jan 21 nicklas 276     // Current offsets in the in/out arrays
6100 12 Jan 21 nicklas 277     int offsetOut = 0;
6100 12 Jan 21 nicklas 278     int offsetData = 0;
6100 12 Jan 21 nicklas 279     // Operators the we need to use (eg. 'x' and 'y' coordinate of text or xobject)
6100 12 Jan 21 nicklas 280     String last1 = null;
6100 12 Jan 21 nicklas 281     String last2 = null;      
6100 12 Jan 21 nicklas 282
6100 12 Jan 21 nicklas 283     while (tokenizer.nextToken())
6100 12 Jan 21 nicklas 284     {
6100 12 Jan 21 nicklas 285       PdfLiteral literal = new PdfLiteral(tokenizer.getByteContent());
6100 12 Jan 21 nicklas 286       String op = literal.toString();
6100 12 Jan 21 nicklas 287       if ("q".equals(op))
6100 12 Jan 21 nicklas 288       {
6100 12 Jan 21 nicklas 289         // Stop copying if we see a 'q'. 
6100 12 Jan 21 nicklas 290         copy = false;
6100 12 Jan 21 nicklas 291       }
6100 12 Jan 21 nicklas 292       else if ("Q".equals(op))
6100 12 Jan 21 nicklas 293       {
6100 12 Jan 21 nicklas 294         if (cut)
6100 12 Jan 21 nicklas 295         {
6100 12 Jan 21 nicklas 296           // If cutting, we move the data offset to current location
6100 12 Jan 21 nicklas 297           // which means the copy will see 0 bytes
6100 12 Jan 21 nicklas 298           offsetData = (int)tokenizer.getPosition();
6100 12 Jan 21 nicklas 299           cut = false;
6100 12 Jan 21 nicklas 300         }
6100 12 Jan 21 nicklas 301         copy = true;
6100 12 Jan 21 nicklas 302       }
6100 12 Jan 21 nicklas 303       else if ("Td".equals(op) || "cm".equals(op))
6100 12 Jan 21 nicklas 304       {
6100 12 Jan 21 nicklas 305         // We get 'x y Td' or 'a b c d x y cm'
6100 12 Jan 21 nicklas 306         // In either case, the x and y coordinates are the previous two tokens
6100 12 Jan 21 nicklas 307         float x = Float.parseFloat(last2);
6100 12 Jan 21 nicklas 308         float y =  Float.parseFloat(last1);
6100 12 Jan 21 nicklas 309         Rectangle r = new Rectangle(x, y, 0, 0);
6100 12 Jan 21 nicklas 310         // System.out.println("Checking: " + op + ": " + x +"," + y);
6100 12 Jan 21 nicklas 311         for (PdfCleanUpLocation loc : redactRegions)
6100 12 Jan 21 nicklas 312         {
6100 12 Jan 21 nicklas 313           if (loc.getRegion().overlaps(r, -0.2f))
6100 12 Jan 21 nicklas 314           {
6100 12 Jan 21 nicklas 315             // System.out.println("Cutting: " + op + ": " + x +"," + y);
6100 12 Jan 21 nicklas 316             cut = true;
6100 12 Jan 21 nicklas 317             break;
6100 12 Jan 21 nicklas 318           }
6100 12 Jan 21 nicklas 319         }
6100 12 Jan 21 nicklas 320       }
6100 12 Jan 21 nicklas 321       // Keep track of the last two operators since they are the 'x' and 'y' coordinates when we see 'Td' or 'cm'
6100 12 Jan 21 nicklas 322       last2 = last1;
6100 12 Jan 21 nicklas 323       last1 = op;
6100 12 Jan 21 nicklas 324       
6100 12 Jan 21 nicklas 325       if (copy)
6100 12 Jan 21 nicklas 326       {
6100 12 Jan 21 nicklas 327         // Copy from data[] from stored offset to current location
6100 12 Jan 21 nicklas 328         int length = (int)tokenizer.getPosition() - offsetData;
6100 12 Jan 21 nicklas 329         // System.out.println("copy:" + op + "; " + offsetData + "->"+offsetOut + ": " + length);
6100 12 Jan 21 nicklas 330         if (length > 0)
6100 12 Jan 21 nicklas 331         {
6100 12 Jan 21 nicklas 332           System.arraycopy(data, offsetData, out, offsetOut, length);
6100 12 Jan 21 nicklas 333           offsetData += length;
6100 12 Jan 21 nicklas 334           offsetOut += length;
6100 12 Jan 21 nicklas 335         }
6100 12 Jan 21 nicklas 336       }
6100 12 Jan 21 nicklas 337     }
6100 12 Jan 21 nicklas 338     
6100 12 Jan 21 nicklas 339     // Repace the content with the redacted data
6100 12 Jan 21 nicklas 340     if (offsetOut < offsetData)
6100 12 Jan 21 nicklas 341     {
6100 12 Jan 21 nicklas 342       byte[] tmp = new byte[offsetOut];
6100 12 Jan 21 nicklas 343       System.arraycopy(out, 0, tmp, 0, offsetOut);
6100 12 Jan 21 nicklas 344       stream.setData(tmp);
6100 12 Jan 21 nicklas 345     }
6100 12 Jan 21 nicklas 346   }
6100 12 Jan 21 nicklas 347   
6100 12 Jan 21 nicklas 348   /**
6092 14 Dec 20 nicklas 349     Set the title of the PDF documen.
6092 14 Dec 20 nicklas 350     @since 4.29
6092 14 Dec 20 nicklas 351   */
6100 12 Jan 21 nicklas 352   public void setTitle(String title)
6092 14 Dec 20 nicklas 353   {
6092 14 Dec 20 nicklas 354     pdf.getDocumentInfo().setTitle(title);
6092 14 Dec 20 nicklas 355   }
6092 14 Dec 20 nicklas 356   
6449 20 Oct 21 nicklas 357   public Date getCreationDate()
6449 20 Oct 21 nicklas 358   {
6449 20 Oct 21 nicklas 359     String d = pdf.getDocumentInfo().getMoreInfo(PdfName.CreationDate.getValue());
6449 20 Oct 21 nicklas 360     return PdfDate.decode(d).getTime();
6449 20 Oct 21 nicklas 361   }
6449 20 Oct 21 nicklas 362   
6092 14 Dec 20 nicklas 363   /**
6058 17 Nov 20 nicklas 364     Create a new page in the existing document.
6058 17 Nov 20 nicklas 365   */
6058 17 Nov 20 nicklas 366   public void newPage()
6058 17 Nov 20 nicklas 367   {
6058 17 Nov 20 nicklas 368     if (canvas != null) canvas.release();
6058 17 Nov 20 nicklas 369     page = pdf.addNewPage(PageSize.A4);
6058 17 Nov 20 nicklas 370     canvas = new PdfCanvas(page);
6061 18 Nov 20 nicklas 371     setColor(currentColor);
6058 17 Nov 20 nicklas 372   }
6058 17 Nov 20 nicklas 373   
6092 14 Dec 20 nicklas 374   /**
6092 14 Dec 20 nicklas 375     Change the current page to the give page. If the page
6092 14 Dec 20 nicklas 376     doesn't exists, the current page is kept as it is.
6092 14 Dec 20 nicklas 377     @return TRUE if the page was change, FALSE if not
6092 14 Dec 20 nicklas 378     @since 4.29
6092 14 Dec 20 nicklas 379   */
6092 14 Dec 20 nicklas 380   public boolean setCurrentPage(int pageNo)
6092 14 Dec 20 nicklas 381   {
6092 14 Dec 20 nicklas 382     PdfPage tmp = pdf.getPage(pageNo);
6092 14 Dec 20 nicklas 383     if (tmp != null)
6092 14 Dec 20 nicklas 384     {
6092 14 Dec 20 nicklas 385       page = tmp;
6092 14 Dec 20 nicklas 386       if (canvas != null) canvas.release();
6092 14 Dec 20 nicklas 387       canvas = new PdfCanvas(page);
6092 14 Dec 20 nicklas 388       setColor(currentColor);
6092 14 Dec 20 nicklas 389     }
6092 14 Dec 20 nicklas 390     return tmp != null;
6092 14 Dec 20 nicklas 391   }
6092 14 Dec 20 nicklas 392   
6092 14 Dec 20 nicklas 393   /**
6092 14 Dec 20 nicklas 394     Get the page number of the current page.
6092 14 Dec 20 nicklas 395     @since 4.29
6092 14 Dec 20 nicklas 396   */
6092 14 Dec 20 nicklas 397   public int getCurrentPageNumber()
6092 14 Dec 20 nicklas 398   {
6092 14 Dec 20 nicklas 399     return pdf.getPageNumber(page);
6092 14 Dec 20 nicklas 400   }
6092 14 Dec 20 nicklas 401   
6092 14 Dec 20 nicklas 402   /**
6092 14 Dec 20 nicklas 403     Get the number of pages in the current PDF document.
6092 14 Dec 20 nicklas 404     @since 4.29
6092 14 Dec 20 nicklas 405   */
6092 14 Dec 20 nicklas 406   public int getNumPages()
6092 14 Dec 20 nicklas 407   {
6092 14 Dec 20 nicklas 408     return pdf.getNumberOfPages();
6092 14 Dec 20 nicklas 409   }
6092 14 Dec 20 nicklas 410   
6061 18 Nov 20 nicklas 411   public void removePage(int pageNo)
6061 18 Nov 20 nicklas 412   {
6061 18 Nov 20 nicklas 413     pdf.removePage(pageNo);
6061 18 Nov 20 nicklas 414   }
6061 18 Nov 20 nicklas 415   
6073 20 Nov 20 nicklas 416   public PdfFont loadFont(String path)
6073 20 Nov 20 nicklas 417   {
6073 20 Nov 20 nicklas 418     try
6073 20 Nov 20 nicklas 419     {
6073 20 Nov 20 nicklas 420       return PdfFontFactory.createFont(path, PdfEncodings.IDENTITY_H, true);
6073 20 Nov 20 nicklas 421     }
6073 20 Nov 20 nicklas 422     catch (IOException ex)
6073 20 Nov 20 nicklas 423     {
6073 20 Nov 20 nicklas 424       throw new RuntimeException(ex);
6073 20 Nov 20 nicklas 425     }
6073 20 Nov 20 nicklas 426   }
6073 20 Nov 20 nicklas 427   
6058 17 Nov 20 nicklas 428   /**
6073 20 Nov 20 nicklas 429     Get the default font for text elements.
6058 17 Nov 20 nicklas 430   */
6058 17 Nov 20 nicklas 431   public PdfFont getDefaultFont()
6058 17 Nov 20 nicklas 432   {
6073 20 Nov 20 nicklas 433     if (defaultFont == null) 
6058 17 Nov 20 nicklas 434     {
6073 20 Nov 20 nicklas 435       defaultFont = loadFont("/META-INF/fonts/OpenSans-Regular.ttf");
6058 17 Nov 20 nicklas 436     }
6058 17 Nov 20 nicklas 437     return defaultFont;
6058 17 Nov 20 nicklas 438   }
6058 17 Nov 20 nicklas 439
6058 17 Nov 20 nicklas 440   /**
6073 20 Nov 20 nicklas 441     Get the default bold font for text elements.
6058 17 Nov 20 nicklas 442   */
6058 17 Nov 20 nicklas 443   public PdfFont getBoldFont()
6058 17 Nov 20 nicklas 444   {
6058 17 Nov 20 nicklas 445     if (boldFont == null)
6058 17 Nov 20 nicklas 446     {
6073 20 Nov 20 nicklas 447       boldFont = loadFont("/META-INF/fonts/OpenSans-Bold.ttf");
6058 17 Nov 20 nicklas 448     }
6058 17 Nov 20 nicklas 449     return boldFont;
6058 17 Nov 20 nicklas 450   }
6058 17 Nov 20 nicklas 451   
6058 17 Nov 20 nicklas 452   /**
6073 20 Nov 20 nicklas 453     Get the default italic font for text elements.
6073 20 Nov 20 nicklas 454   */
6073 20 Nov 20 nicklas 455   public PdfFont getItalicFont()
6073 20 Nov 20 nicklas 456   {
6073 20 Nov 20 nicklas 457     if (italicFont == null)
6073 20 Nov 20 nicklas 458     {
6073 20 Nov 20 nicklas 459       italicFont = loadFont("/META-INF/fonts/OpenSans-Italic.ttf");
6073 20 Nov 20 nicklas 460     }
6073 20 Nov 20 nicklas 461     return italicFont;
6073 20 Nov 20 nicklas 462   }
6073 20 Nov 20 nicklas 463   
6073 20 Nov 20 nicklas 464   /**
6073 20 Nov 20 nicklas 465     Get the default bold and italic font for text elements.
6073 20 Nov 20 nicklas 466   */
6073 20 Nov 20 nicklas 467   public PdfFont getBoldItalicFont()
6073 20 Nov 20 nicklas 468   {
6073 20 Nov 20 nicklas 469     if (boldItalicFont == null)
6073 20 Nov 20 nicklas 470     {
6073 20 Nov 20 nicklas 471       boldItalicFont = loadFont("/META-INF/fonts/OpenSans-BoldItalic.ttf");
6073 20 Nov 20 nicklas 472     }
6073 20 Nov 20 nicklas 473     return boldItalicFont;
6073 20 Nov 20 nicklas 474   }
6073 20 Nov 20 nicklas 475
6073 20 Nov 20 nicklas 476   
6073 20 Nov 20 nicklas 477   /**
6058 17 Nov 20 nicklas 478     Close the PDF document.
6058 17 Nov 20 nicklas 479   */
6058 17 Nov 20 nicklas 480   public void close()
6058 17 Nov 20 nicklas 481   {
6058 17 Nov 20 nicklas 482     if (canvas != null) canvas.release();
6100 12 Jan 21 nicklas 483     FileUtil.close(pdf);
6100 12 Jan 21 nicklas 484     FileUtil.close(writer);
6100 12 Jan 21 nicklas 485     for (PdfDocument doc : docCache.values())
6100 12 Jan 21 nicklas 486     {
6100 12 Jan 21 nicklas 487       FileUtil.close(doc);
6100 12 Jan 21 nicklas 488     }
6100 12 Jan 21 nicklas 489   }
6100 12 Jan 21 nicklas 490
6100 12 Jan 21 nicklas 491   /**
6100 12 Jan 21 nicklas 492     Apply redactions and close the PDF document.
6100 12 Jan 21 nicklas 493     @param useSimpleRedact TRUE to use the simple redact, FALSE to use pdfSweep
6100 12 Jan 21 nicklas 494     @since 4.30
6100 12 Jan 21 nicklas 495   */
6100 12 Jan 21 nicklas 496   public void redactAndClose(boolean useSimpleRedact, boolean paintRegions)
6100 12 Jan 21 nicklas 497   {
6100 12 Jan 21 nicklas 498     if (canvas != null) 
6100 12 Jan 21 nicklas 499     {
6100 12 Jan 21 nicklas 500       canvas.release();
6100 12 Jan 21 nicklas 501       canvas = null;
6100 12 Jan 21 nicklas 502     }
6092 14 Dec 20 nicklas 503     if (redactLocations.size() > 0)
6092 14 Dec 20 nicklas 504     {
6092 14 Dec 20 nicklas 505       try
6092 14 Dec 20 nicklas 506       {
6100 12 Jan 21 nicklas 507         if (useSimpleRedact)
6100 12 Jan 21 nicklas 508         {
6100 12 Jan 21 nicklas 509           simpleRedact(paintRegions);
6100 12 Jan 21 nicklas 510         }
6100 12 Jan 21 nicklas 511         else
6100 12 Jan 21 nicklas 512         {
6100 12 Jan 21 nicklas 513           new PdfCleanUpTool(pdf, redactLocations).cleanUp();
6100 12 Jan 21 nicklas 514         }
6092 14 Dec 20 nicklas 515       }
6092 14 Dec 20 nicklas 516       catch (IOException ex)
6092 14 Dec 20 nicklas 517       {
6092 14 Dec 20 nicklas 518         throw new RuntimeException(ex);
6092 14 Dec 20 nicklas 519       }
6092 14 Dec 20 nicklas 520     }
6100 12 Jan 21 nicklas 521     close();
6058 17 Nov 20 nicklas 522   }
6058 17 Nov 20 nicklas 523
6100 12 Jan 21 nicklas 524   
6100 12 Jan 21 nicklas 525   
6058 17 Nov 20 nicklas 526   /**
6058 17 Nov 20 nicklas 527     Set the color to use for text and other affected elements.
6058 17 Nov 20 nicklas 528   */
6058 17 Nov 20 nicklas 529   public void setColor(Color color)
6058 17 Nov 20 nicklas 530   {
6058 17 Nov 20 nicklas 531     this.currentColor = color;
6058 17 Nov 20 nicklas 532     canvas.setFillColor(color);
6058 17 Nov 20 nicklas 533     canvas.setStrokeColor(color);
6058 17 Nov 20 nicklas 534   }
6058 17 Nov 20 nicklas 535   
6058 17 Nov 20 nicklas 536   /**
6092 14 Dec 20 nicklas 537     Set the color to use for marking redacted areas. The
6092 14 Dec 20 nicklas 538     default color is white.
6100 12 Jan 21 nicklas 539     @since 4.29, 4.30
6092 14 Dec 20 nicklas 540   */
6100 12 Jan 21 nicklas 541   public void setRedactColor(Color color, float opacity)
6092 14 Dec 20 nicklas 542   {
6092 14 Dec 20 nicklas 543     this.redactColor = color;
6100 12 Jan 21 nicklas 544     this.redactOpacity = opacity;
6092 14 Dec 20 nicklas 545   }
6092 14 Dec 20 nicklas 546   
6092 14 Dec 20 nicklas 547   /**
6069 19 Nov 20 nicklas 548     Add text to the document using default options.
6069 19 Nov 20 nicklas 549     
6069 19 Nov 20 nicklas 550     @param text The text to add
6069 19 Nov 20 nicklas 551     @param fontSize Font size to use
6069 19 Nov 20 nicklas 552     @param x X coordinate in points
6069 19 Nov 20 nicklas 553     @param y Y coordinate in points
6092 14 Dec 20 nicklas 554     @return The font-size used (can be changed due to {@link Options#maxTextWidth(float)} setting)
6069 19 Nov 20 nicklas 555   */
6092 14 Dec 20 nicklas 556   public float addText(String text, float fontSize, float x, float y)
6069 19 Nov 20 nicklas 557   {
6092 14 Dec 20 nicklas 558     return addText(text, fontSize, x, y, Options.DEFAULT);
6069 19 Nov 20 nicklas 559   }
6069 19 Nov 20 nicklas 560   
6069 19 Nov 20 nicklas 561   /**
6069 19 Nov 20 nicklas 562     Add text to the document using other options.
6069 19 Nov 20 nicklas 563     
6069 19 Nov 20 nicklas 564     @param text The text to add
6069 19 Nov 20 nicklas 565     @param fontSize Font size to use
6069 19 Nov 20 nicklas 566     @param x X coordinate in points
6069 19 Nov 20 nicklas 567     @param y Y coordinate in points
6069 19 Nov 20 nicklas 568     @param option Options for the text
6092 14 Dec 20 nicklas 569     @return The font-size used (can be changed due to {@link Options#maxTextWidth(float)} setting)
6069 19 Nov 20 nicklas 570   */
6092 14 Dec 20 nicklas 571   public float addText(String text, float fontSize, float x, float y, Options options)
6069 19 Nov 20 nicklas 572   {
6092 14 Dec 20 nicklas 573     if (text == null || text.equals("")) return fontSize;
6069 19 Nov 20 nicklas 574     if (options == null) options = Options.DEFAULT;
6069 19 Nov 20 nicklas 575
6073 20 Nov 20 nicklas 576     PdfFont font = options.font;
6073 20 Nov 20 nicklas 577     if (font == null)
6073 20 Nov 20 nicklas 578     {
6073 20 Nov 20 nicklas 579       if (options.italic)
6073 20 Nov 20 nicklas 580       {
6073 20 Nov 20 nicklas 581         font = options.bold ? getBoldItalicFont() : getItalicFont();
6073 20 Nov 20 nicklas 582       }
6073 20 Nov 20 nicklas 583       else
6073 20 Nov 20 nicklas 584       {
6073 20 Nov 20 nicklas 585         font = options.bold ? getBoldFont() : getDefaultFont();
6073 20 Nov 20 nicklas 586       }
6073 20 Nov 20 nicklas 587     }
6070 20 Nov 20 nicklas 588     
6078 24 Nov 20 nicklas 589     // Adjust fontSize and text if it is wider than maxTextWidth
6078 24 Nov 20 nicklas 590     if (!Float.isNaN(options.maxTextWidth))
6078 24 Nov 20 nicklas 591     {
6078 24 Nov 20 nicklas 592       float minFontSize = fontSize * options.minFontSizeFraction;
6078 24 Nov 20 nicklas 593       String suffix = null;
6078 24 Nov 20 nicklas 594       while (options.maxTextWidth < font.getWidth(text, fontSize))
6078 24 Nov 20 nicklas 595       {
6078 24 Nov 20 nicklas 596         if (fontSize > minFontSize)
6078 24 Nov 20 nicklas 597         {
6078 24 Nov 20 nicklas 598           // Decrease font size as long as it is larger than the min font size
6078 24 Nov 20 nicklas 599           fontSize -= 0.5;
6078 24 Nov 20 nicklas 600         }
6078 24 Nov 20 nicklas 601         else
6078 24 Nov 20 nicklas 602         {
6078 24 Nov 20 nicklas 603           // After that, we remove characters from the end of the text
6078 24 Nov 20 nicklas 604           text = text.substring(0, text.length()-1);
6078 24 Nov 20 nicklas 605           suffix = "…";
6078 24 Nov 20 nicklas 606         }
6078 24 Nov 20 nicklas 607       }
6078 24 Nov 20 nicklas 608       if (suffix != null) text += suffix;
6078 24 Nov 20 nicklas 609     }
6078 24 Nov 20 nicklas 610     
6070 20 Nov 20 nicklas 611     AffineTransform t = null;
6070 20 Nov 20 nicklas 612     if (!Float.isNaN(options.rotation))
6070 20 Nov 20 nicklas 613     {
6070 20 Nov 20 nicklas 614       float textWidth = font.getWidth(text, fontSize);
6070 20 Nov 20 nicklas 615       t = AffineTransform.getTranslateInstance(x, y);
6070 20 Nov 20 nicklas 616       t.rotate(options.rotation * Math.PI / 180);
6070 20 Nov 20 nicklas 617       t.translate(options.align.adjustX(0, textWidth), options.align.adjustY(0, fontSize));
6070 20 Nov 20 nicklas 618     }
6070 20 Nov 20 nicklas 619     else
6070 20 Nov 20 nicklas 620     {
6070 20 Nov 20 nicklas 621       x = options.align.adjustX(x, font, text, fontSize);
6070 20 Nov 20 nicklas 622       y = options.align.adjustY(y, fontSize);
6070 20 Nov 20 nicklas 623     }
6069 19 Nov 20 nicklas 624
6069 19 Nov 20 nicklas 625     canvas.saveState();
6069 19 Nov 20 nicklas 626     if (options.color != null) canvas.setFillColor(options.color);
6069 19 Nov 20 nicklas 627
6069 19 Nov 20 nicklas 628     if (options.underline)
6069 19 Nov 20 nicklas 629     {
6070 20 Nov 20 nicklas 630       float yOffset = -0.5f*(1+fontSize/12f);
6069 19 Nov 20 nicklas 631       float width = font.getWidth(text, fontSize);
6070 20 Nov 20 nicklas 632       Point p1, p2;
6070 20 Nov 20 nicklas 633       if (t != null)
6070 20 Nov 20 nicklas 634       {
6070 20 Nov 20 nicklas 635         p1 = t.transform(new Point(0, yOffset), null);
6070 20 Nov 20 nicklas 636         p2 = t.transform(new Point (width, yOffset), null);
6070 20 Nov 20 nicklas 637       }
6070 20 Nov 20 nicklas 638       else
6070 20 Nov 20 nicklas 639       {
6070 20 Nov 20 nicklas 640         p1 = new Point(x, y+yOffset);
6070 20 Nov 20 nicklas 641         p2 = new Point(x+width, p1.y);
6070 20 Nov 20 nicklas 642       }
6069 19 Nov 20 nicklas 643       if (options.color != null) canvas.setStrokeColor(options.color);
6069 19 Nov 20 nicklas 644       canvas.setLineWidth(Math.max(0.2f, (options.bold ? 0.85f : 0.5f) * fontSize / 12));
6070 20 Nov 20 nicklas 645       canvas.moveTo(p1.x, p1.y);
6070 20 Nov 20 nicklas 646       canvas.lineTo(p2.x, p2.y);
6069 19 Nov 20 nicklas 647       canvas.stroke();
6069 19 Nov 20 nicklas 648     }
6069 19 Nov 20 nicklas 649     
6069 19 Nov 20 nicklas 650     canvas.beginText();
6069 19 Nov 20 nicklas 651     canvas.setFontAndSize(font, fontSize);
6070 20 Nov 20 nicklas 652     if (t != null)
6070 20 Nov 20 nicklas 653     {
6070 20 Nov 20 nicklas 654       canvas.setTextMatrix(t);
6070 20 Nov 20 nicklas 655     }
6070 20 Nov 20 nicklas 656     else
6070 20 Nov 20 nicklas 657     {
6070 20 Nov 20 nicklas 658       canvas.moveText(x, y);
6070 20 Nov 20 nicklas 659     }
6069 19 Nov 20 nicklas 660     canvas.showText(text);
6069 19 Nov 20 nicklas 661     canvas.endText();
6069 19 Nov 20 nicklas 662     canvas.restoreState();
6092 14 Dec 20 nicklas 663     return fontSize;
6069 19 Nov 20 nicklas 664   }
6069 19 Nov 20 nicklas 665   
6069 19 Nov 20 nicklas 666   /**
6058 17 Nov 20 nicklas 667     Draw a circle that is centerd on the x and y coordinates with
6058 17 Nov 20 nicklas 668     radius r.
6058 17 Nov 20 nicklas 669   */
6058 17 Nov 20 nicklas 670   public void drawCircle(float x, float y, float r, Color fillColor)
6058 17 Nov 20 nicklas 671   {
6058 17 Nov 20 nicklas 672     canvas.saveState();
6075 23 Nov 20 nicklas 673     canvas.setLineWidth(0.7f);
6058 17 Nov 20 nicklas 674     canvas.setFillColor(fillColor);
6058 17 Nov 20 nicklas 675     canvas.circle(x, y, r);
6058 17 Nov 20 nicklas 676     canvas.fillStroke();
6058 17 Nov 20 nicklas 677     canvas.restoreState();
6058 17 Nov 20 nicklas 678   }
6058 17 Nov 20 nicklas 679
6058 17 Nov 20 nicklas 680   /**
6058 17 Nov 20 nicklas 681     Import an image into the PDF document and place it at the location specified 
6058 17 Nov 20 nicklas 682     by the x and y coordinates. The image is automatically scaled to fit within the
6058 17 Nov 20 nicklas 683     given width and height while preserving aspect ratio. It can be aligned either to
6058 17 Nov 20 nicklas 684     left/right or top/bottom.
6058 17 Nov 20 nicklas 685   */
6058 17 Nov 20 nicklas 686   public void importImage(InputStream image, float x, float y, float width, float height, Align align)
6058 17 Nov 20 nicklas 687     throws IOException
6058 17 Nov 20 nicklas 688   {
6058 17 Nov 20 nicklas 689     ByteArrayOutputStream bytes = new ByteArrayOutputStream(4096);
6058 17 Nov 20 nicklas 690     try
6058 17 Nov 20 nicklas 691     {
6058 17 Nov 20 nicklas 692       FileUtil.copy(image, bytes);
6058 17 Nov 20 nicklas 693       bytes.close();
6060 17 Nov 20 nicklas 694       byte[] data = bytes.toByteArray();
6060 17 Nov 20 nicklas 695       ImageType imgType = ImageTypeDetector.detectImageType(data);
6060 17 Nov 20 nicklas 696       ImageData img = null;
6060 17 Nov 20 nicklas 697       WmfImageHelper wmfHelper = null;
6060 17 Nov 20 nicklas 698       if (imgType == ImageType.WMF)
6060 17 Nov 20 nicklas 699       {
6060 17 Nov 20 nicklas 700         img = new WmfImageData(data);
6060 17 Nov 20 nicklas 701         wmfHelper = new WmfImageHelper(img);
6060 17 Nov 20 nicklas 702       }
6060 17 Nov 20 nicklas 703       else
6060 17 Nov 20 nicklas 704       {
6060 17 Nov 20 nicklas 705         img = ImageDataFactory.create(data);
6060 17 Nov 20 nicklas 706       }
6060 17 Nov 20 nicklas 707       
6058 17 Nov 20 nicklas 708       if (Float.isNaN(width) && Float.isNaN(height))
6058 17 Nov 20 nicklas 709       {
6058 17 Nov 20 nicklas 710         width = img.getWidth();
6058 17 Nov 20 nicklas 711         height = img.getHeight();
6058 17 Nov 20 nicklas 712       }
6058 17 Nov 20 nicklas 713       else if (Float.isNaN(height))
6058 17 Nov 20 nicklas 714       {
6058 17 Nov 20 nicklas 715         float scale = width / img.getWidth();
6058 17 Nov 20 nicklas 716         height = scale * img.getHeight();
6058 17 Nov 20 nicklas 717       }
6058 17 Nov 20 nicklas 718       else if (Float.isNaN(width))
6058 17 Nov 20 nicklas 719       {
6058 17 Nov 20 nicklas 720         float scale = height / img.getHeight();
6058 17 Nov 20 nicklas 721         width = scale * img.getWidth();
6058 17 Nov 20 nicklas 722       }
6058 17 Nov 20 nicklas 723       else
6058 17 Nov 20 nicklas 724       {
6058 17 Nov 20 nicklas 725         float scale = Math.min(height / img.getHeight(), width / img.getWidth());
6058 17 Nov 20 nicklas 726         height = scale * img.getHeight();
6058 17 Nov 20 nicklas 727         width = scale * img.getWidth();
6058 17 Nov 20 nicklas 728       }
6058 17 Nov 20 nicklas 729       if (align != null)
6058 17 Nov 20 nicklas 730       {
6058 17 Nov 20 nicklas 731         x = align.adjustX(x, width);
6058 17 Nov 20 nicklas 732         y = align.adjustY(y, height);
6058 17 Nov 20 nicklas 733       }
6060 17 Nov 20 nicklas 734       if (imgType == ImageType.WMF)
6060 17 Nov 20 nicklas 735       {
6060 17 Nov 20 nicklas 736         canvas.addXObjectFittedIntoRectangle(wmfHelper.createFormXObject(pdf), new Rectangle(x, y, width, height));
6060 17 Nov 20 nicklas 737       }
6060 17 Nov 20 nicklas 738       else
6060 17 Nov 20 nicklas 739       {
6060 17 Nov 20 nicklas 740         canvas.addImageFittedIntoRectangle(img, new Rectangle(x, y, width, height), false);
6060 17 Nov 20 nicklas 741       }
6058 17 Nov 20 nicklas 742     }
6058 17 Nov 20 nicklas 743     finally
6058 17 Nov 20 nicklas 744     {
6058 17 Nov 20 nicklas 745       FileUtil.close(bytes);
6058 17 Nov 20 nicklas 746       FileUtil.close(image);
6058 17 Nov 20 nicklas 747     }
6058 17 Nov 20 nicklas 748   }
6058 17 Nov 20 nicklas 749
6064 18 Nov 20 nicklas 750   /**
6064 18 Nov 20 nicklas 751     Import an SVG image into the PDF document and place it at the location specified 
6064 18 Nov 20 nicklas 752     by the x and y coordinates. The image is automatically scaled to fit within the
6064 18 Nov 20 nicklas 753     given width and height while preserving aspect ratio. It can be aligned either to
6064 18 Nov 20 nicklas 754     left/right or top/bottom.
6064 18 Nov 20 nicklas 755   */
6064 18 Nov 20 nicklas 756   public void importSvg(InputStream svg, float x, float y, float width, float height, Align align)
6064 18 Nov 20 nicklas 757     throws IOException
6064 18 Nov 20 nicklas 758   {
6064 18 Nov 20 nicklas 759     try
6064 18 Nov 20 nicklas 760     {
6064 18 Nov 20 nicklas 761       PdfFormXObject xo = SvgConverter.convertToXObject(svg, pdf);
6064 18 Nov 20 nicklas 762       if (Float.isNaN(width) && Float.isNaN(height))
6064 18 Nov 20 nicklas 763       {
6064 18 Nov 20 nicklas 764         width = xo.getWidth();
6064 18 Nov 20 nicklas 765         height = xo.getHeight();
6064 18 Nov 20 nicklas 766       }
6064 18 Nov 20 nicklas 767       else if (Float.isNaN(height))
6064 18 Nov 20 nicklas 768       {
6064 18 Nov 20 nicklas 769         float scale = width / xo.getWidth();
6064 18 Nov 20 nicklas 770         height = scale * xo.getHeight();
6064 18 Nov 20 nicklas 771       }
6064 18 Nov 20 nicklas 772       else if (Float.isNaN(width))
6064 18 Nov 20 nicklas 773       {
6064 18 Nov 20 nicklas 774         float scale = height / xo.getHeight();
6064 18 Nov 20 nicklas 775         width = scale * xo.getWidth();
6064 18 Nov 20 nicklas 776       }
6064 18 Nov 20 nicklas 777       else
6064 18 Nov 20 nicklas 778       {
6064 18 Nov 20 nicklas 779         float scale = Math.min(height / xo.getHeight(), width / xo.getWidth());
6064 18 Nov 20 nicklas 780         height = scale * xo.getHeight();
6064 18 Nov 20 nicklas 781         width = scale * xo.getWidth();
6064 18 Nov 20 nicklas 782       }
6064 18 Nov 20 nicklas 783       if (align != null)
6064 18 Nov 20 nicklas 784       {
6064 18 Nov 20 nicklas 785         x = align.adjustX(x, width);
6064 18 Nov 20 nicklas 786         y = align.adjustY(y, height);
6064 18 Nov 20 nicklas 787       }
6064 18 Nov 20 nicklas 788       canvas.addXObjectFittedIntoRectangle(xo, new Rectangle(x, y, width, height));
6064 18 Nov 20 nicklas 789     }
6064 18 Nov 20 nicklas 790     finally
6064 18 Nov 20 nicklas 791     {
6064 18 Nov 20 nicklas 792       FileUtil.close(svg);
6064 18 Nov 20 nicklas 793     }
6064 18 Nov 20 nicklas 794   }
6064 18 Nov 20 nicklas 795
6081 25 Nov 20 nicklas 796   /**
6081 25 Nov 20 nicklas 797     Load a PDF file specified by the given path on the real file system. 
6081 25 Nov 20 nicklas 798     The PdfDocument is cached. Calling this method multiple times with 
6081 25 Nov 20 nicklas 799     the same path will return the same PdfDocument instance. Used by
6081 25 Nov 20 nicklas 800     {@link #importPdf(TemplateFilePage, float, float, float, float, Align)}
6081 25 Nov 20 nicklas 801     to avoid having to reload the same PDF more than once even if multiple 
6081 25 Nov 20 nicklas 802     pages are imported.
6081 25 Nov 20 nicklas 803   */
6081 25 Nov 20 nicklas 804   public PdfDocument getDocument(String path)
6081 25 Nov 20 nicklas 805     throws IOException
6081 25 Nov 20 nicklas 806   {
6081 25 Nov 20 nicklas 807     PdfDocument doc = docCache.get(path);
6081 25 Nov 20 nicklas 808     if (doc == null)
6081 25 Nov 20 nicklas 809     {
6081 25 Nov 20 nicklas 810       PdfReader reader = new PdfReader(new FileInputStream(path));
6081 25 Nov 20 nicklas 811       doc = new PdfDocument(reader);
6081 25 Nov 20 nicklas 812       docCache.put(path, doc);
6081 25 Nov 20 nicklas 813     }
6081 25 Nov 20 nicklas 814     return doc;
6081 25 Nov 20 nicklas 815   }
6058 17 Nov 20 nicklas 816   
6058 17 Nov 20 nicklas 817   /**
6058 17 Nov 20 nicklas 818     Import a PDF template document.
6058 17 Nov 20 nicklas 819   */
6061 18 Nov 20 nicklas 820   public void importPdf(TemplateFilePage pdf, float x, float y, float width, float height, Align align)
6058 17 Nov 20 nicklas 821     throws IOException
6058 17 Nov 20 nicklas 822   {
6081 25 Nov 20 nicklas 823     PdfDocument doc = getDocument(pdf.getPath());
6081 25 Nov 20 nicklas 824     importPdf(doc, pdf.getPage(), x, y, width, height, align);
6058 17 Nov 20 nicklas 825   }
6058 17 Nov 20 nicklas 826
6058 17 Nov 20 nicklas 827   /**
6058 17 Nov 20 nicklas 828     Import another PDF document and place it at the location specified 
6058 17 Nov 20 nicklas 829     by the x and y coordinates.
6058 17 Nov 20 nicklas 830   */
6061 18 Nov 20 nicklas 831   public void importPdf(InputStream pdf, int pageNo, float x, float y, float width, float height, Align align)
6058 17 Nov 20 nicklas 832     throws IOException
6058 17 Nov 20 nicklas 833   {
6058 17 Nov 20 nicklas 834     // Load existing PDF
6081 25 Nov 20 nicklas 835     PdfReader reader = null;
6081 25 Nov 20 nicklas 836     PdfDocument other = null;
6081 25 Nov 20 nicklas 837     try
6081 25 Nov 20 nicklas 838     {
6081 25 Nov 20 nicklas 839       reader = new PdfReader(pdf);
6081 25 Nov 20 nicklas 840       other = new PdfDocument(reader);
6081 25 Nov 20 nicklas 841       importPdf(other, pageNo, x, y, width, height, align);
6081 25 Nov 20 nicklas 842     }
6081 25 Nov 20 nicklas 843     finally
6081 25 Nov 20 nicklas 844     {
6081 25 Nov 20 nicklas 845       FileUtil.close(other);
6081 25 Nov 20 nicklas 846       FileUtil.close(pdf);
6081 25 Nov 20 nicklas 847     }
6058 17 Nov 20 nicklas 848   }
6058 17 Nov 20 nicklas 849   
6061 18 Nov 20 nicklas 850   public void importPdf(PdfDocument from, int pageNo, float x, float y, float width, float height, Align align)
6058 17 Nov 20 nicklas 851   {
6058 17 Nov 20 nicklas 852     try
6058 17 Nov 20 nicklas 853     {
6060 17 Nov 20 nicklas 854       PdfFormXObject xo = from.getPage(pageNo).copyAsFormXObject(pdf);
6061 18 Nov 20 nicklas 855       if (Float.isNaN(width) && Float.isNaN(height))
6061 18 Nov 20 nicklas 856       {
6061 18 Nov 20 nicklas 857         width = xo.getWidth();
6061 18 Nov 20 nicklas 858         height = xo.getHeight();
6061 18 Nov 20 nicklas 859       }
6061 18 Nov 20 nicklas 860       else if (Float.isNaN(height))
6061 18 Nov 20 nicklas 861       {
6061 18 Nov 20 nicklas 862         float scale = width / xo.getWidth();
6061 18 Nov 20 nicklas 863         height = scale * xo.getHeight();
6061 18 Nov 20 nicklas 864       }
6061 18 Nov 20 nicklas 865       else if (Float.isNaN(width))
6061 18 Nov 20 nicklas 866       {
6061 18 Nov 20 nicklas 867         float scale = height / xo.getHeight();
6061 18 Nov 20 nicklas 868         width = scale * xo.getWidth();
6061 18 Nov 20 nicklas 869       }
6061 18 Nov 20 nicklas 870       else
6061 18 Nov 20 nicklas 871       {
6061 18 Nov 20 nicklas 872         float scale = Math.min(height / xo.getHeight(), width / xo.getWidth());
6061 18 Nov 20 nicklas 873         height = scale * xo.getHeight();
6061 18 Nov 20 nicklas 874         width = scale * xo.getWidth();
6061 18 Nov 20 nicklas 875       }
6061 18 Nov 20 nicklas 876       if (align != null)
6061 18 Nov 20 nicklas 877       {
6061 18 Nov 20 nicklas 878         x = align.adjustX(x, width);
6061 18 Nov 20 nicklas 879         y = align.adjustY(y, height);
6061 18 Nov 20 nicklas 880       }
6061 18 Nov 20 nicklas 881       
6061 18 Nov 20 nicklas 882       canvas.addXObjectFittedIntoRectangle(xo, new Rectangle(x, y, width, height));
6058 17 Nov 20 nicklas 883     }
6058 17 Nov 20 nicklas 884     catch (IOException ex)
6058 17 Nov 20 nicklas 885     {
6058 17 Nov 20 nicklas 886       throw new RuntimeException(ex);
6058 17 Nov 20 nicklas 887     }
6058 17 Nov 20 nicklas 888   }
6058 17 Nov 20 nicklas 889
6061 18 Nov 20 nicklas 890   
6058 17 Nov 20 nicklas 891   /**
6061 18 Nov 20 nicklas 892     Create a barcode image for the PDF document and place it at the location specified 
6061 18 Nov 20 nicklas 893     by the x and y coordinates. The image is stretched to fit within the given
6061 18 Nov 20 nicklas 894     width and height.
6061 18 Nov 20 nicklas 895   */
6061 18 Nov 20 nicklas 896   public void addBarcode(String text, float x, float y, float width, float height, float rotationDegrees)
6061 18 Nov 20 nicklas 897     throws IOException
6061 18 Nov 20 nicklas 898   {
6061 18 Nov 20 nicklas 899     Barcode128 code128 = new Barcode128(pdf, defaultFont);
6061 18 Nov 20 nicklas 900     code128.setBaseline(-1);
6061 18 Nov 20 nicklas 901     code128.setFont(null);
6061 18 Nov 20 nicklas 902     code128.setSize(12);
6061 18 Nov 20 nicklas 903     code128.setCode(text);
6061 18 Nov 20 nicklas 904     code128.setCodeType(Barcode128.CODE128);
6061 18 Nov 20 nicklas 905     Rectangle rect = code128.getBarcodeSize();
6061 18 Nov 20 nicklas 906     PdfFormXObject xo = code128.createFormXObject(pdf);
6061 18 Nov 20 nicklas 907     
6061 18 Nov 20 nicklas 908     if (rotationDegrees > 0)
6061 18 Nov 20 nicklas 909     {
6061 18 Nov 20 nicklas 910       AffineTransform t = AffineTransform.getRotateInstance(rotationDegrees * Math.PI / 180);
6061 18 Nov 20 nicklas 911       t.scale(width/xo.getWidth(), height/xo.getHeight());
6061 18 Nov 20 nicklas 912       canvas.addXObjectWithTransformationMatrix(xo, (float)t.getScaleX(), (float)t.getShearY(), (float)t.getShearX(), (float)t.getScaleY(), x+height, y);
6061 18 Nov 20 nicklas 913     }
6061 18 Nov 20 nicklas 914     else
6061 18 Nov 20 nicklas 915     {
6061 18 Nov 20 nicklas 916       canvas.addXObjectFittedIntoRectangle(xo, new Rectangle(x, y, width, height));
6061 18 Nov 20 nicklas 917     }
6061 18 Nov 20 nicklas 918   }
6061 18 Nov 20 nicklas 919
6061 18 Nov 20 nicklas 920   /**
6058 17 Nov 20 nicklas 921     Set the color to use for "cleaning" areas on the template.
6058 17 Nov 20 nicklas 922     @see #clearRect(float, float, float, float)
6058 17 Nov 20 nicklas 923   */
6075 23 Nov 20 nicklas 924   public void setClearColor(Color color, float opacity)
6058 17 Nov 20 nicklas 925   {
6058 17 Nov 20 nicklas 926     this.clearColor = color;
6075 23 Nov 20 nicklas 927     this.clearOpacity = opacity;
6058 17 Nov 20 nicklas 928   }
6058 17 Nov 20 nicklas 929
6058 17 Nov 20 nicklas 930   /**
6058 17 Nov 20 nicklas 931     Method for drawing a non-transparent white rectangle on the PDF 
6058 17 Nov 20 nicklas 932     in order to hide what is below. Note! This is a helper method for
6058 17 Nov 20 nicklas 933     development only where templates etc. are not yet finalized. This
6058 17 Nov 20 nicklas 934     should never be used in production.
6058 17 Nov 20 nicklas 935   */
6058 17 Nov 20 nicklas 936   public void clearRect(float lowerLeftX, float lowerLeftY, float upperRightX, float upperRightY)
6058 17 Nov 20 nicklas 937   {
6058 17 Nov 20 nicklas 938     Rectangle rect = new Rectangle(lowerLeftX, lowerLeftY, upperRightX-lowerLeftX, upperRightY-lowerLeftY);
6058 17 Nov 20 nicklas 939     canvas.saveState();
6058 17 Nov 20 nicklas 940     canvas.setFillColor(clearColor);
6075 23 Nov 20 nicklas 941     if (!Float.isNaN(clearOpacity)) 
6075 23 Nov 20 nicklas 942     {
6075 23 Nov 20 nicklas 943       canvas.setExtGState(new PdfExtGState().setFillOpacity(clearOpacity));
6075 23 Nov 20 nicklas 944     }
6058 17 Nov 20 nicklas 945     canvas.rectangle(rect);
6058 17 Nov 20 nicklas 946     canvas.fill();
6058 17 Nov 20 nicklas 947     canvas.restoreState();
6058 17 Nov 20 nicklas 948   }
6058 17 Nov 20 nicklas 949   
6058 17 Nov 20 nicklas 950   /**
6092 14 Dec 20 nicklas 951     Redact/erase the given text a page. NOTE! It is not possible to redact information 
6092 14 Dec 20 nicklas 952     at the same time as adding content to the PDF. If this is needed it must be two 
6092 14 Dec 20 nicklas 953     separate operations using a temporary PDF in-between.
6092 14 Dec 20 nicklas 954     @param pageNo The number of the page to erase the information from. If 0 or
6092 14 Dec 20 nicklas 955       negative, the text is redacted from all pages
6092 14 Dec 20 nicklas 956     @since 4.29
6092 14 Dec 20 nicklas 957   */
6092 14 Dec 20 nicklas 958   public void redactText(int pageNo, String text)
6092 14 Dec 20 nicklas 959   {
6092 14 Dec 20 nicklas 960     RegexBasedCleanupStrategy clean = new RegexBasedCleanupStrategy("\\Q"+text+"\\E");
6092 14 Dec 20 nicklas 961     clean.setRedactionColor(redactColor);
6092 14 Dec 20 nicklas 962     PdfPage page = pageNo > 0 ? pdf.getPage(pageNo) : null;
6092 14 Dec 20 nicklas 963     if (page == null)
6092 14 Dec 20 nicklas 964     {
6092 14 Dec 20 nicklas 965       redactLocations.addAll(new PdfAutoSweep(clean).getPdfCleanUpLocations(pdf));
6092 14 Dec 20 nicklas 966     }
6092 14 Dec 20 nicklas 967     else
6092 14 Dec 20 nicklas 968     {
6092 14 Dec 20 nicklas 969       redactLocations.addAll(new PdfAutoSweep(clean).getPdfCleanUpLocations(page));
6092 14 Dec 20 nicklas 970     }
6092 14 Dec 20 nicklas 971   }
6092 14 Dec 20 nicklas 972   
6092 14 Dec 20 nicklas 973   /**
6092 14 Dec 20 nicklas 974     Redact/erase information from a page that is located inside the given
6092 14 Dec 20 nicklas 975     rectangle. NOTE! It is not possible to redact information at the same time as
6092 14 Dec 20 nicklas 976     adding content to the PDF. If this is needed it must be two separate operations
6092 14 Dec 20 nicklas 977     using a temporary PDF in-between.
6092 14 Dec 20 nicklas 978     @param pageNo The number of the page to erase the information from. If 0 or negative,
6092 14 Dec 20 nicklas 979       the current page is used
6092 14 Dec 20 nicklas 980     @since 4.29
6092 14 Dec 20 nicklas 981   */
6092 14 Dec 20 nicklas 982   public void redactRect(int pageNo, float lowerLeftX, float lowerLeftY, float upperRightX, float upperRightY)
6092 14 Dec 20 nicklas 983   {
6092 14 Dec 20 nicklas 984     if (pageNo <= 0) pageNo = getCurrentPageNumber();
6092 14 Dec 20 nicklas 985     Rectangle rect = new Rectangle(lowerLeftX, lowerLeftY, upperRightX-lowerLeftX, upperRightY-lowerLeftY);
6092 14 Dec 20 nicklas 986     redactLocations.add(new PdfCleanUpLocation(pageNo, rect, redactColor));
6092 14 Dec 20 nicklas 987   }
6092 14 Dec 20 nicklas 988   
6092 14 Dec 20 nicklas 989   /**
6058 17 Nov 20 nicklas 990     Draw grid line around the page. Major grid is 50pt, with minor at every 10pt and
6058 17 Nov 20 nicklas 991     ticks at every 2pt.
6058 17 Nov 20 nicklas 992    */
6058 17 Nov 20 nicklas 993   public void drawGrid()
6058 17 Nov 20 nicklas 994   {
6058 17 Nov 20 nicklas 995     float pageHeight = page.getPageSize().getHeight();
6058 17 Nov 20 nicklas 996     float pageWidth = page.getPageSize().getWidth();
6058 17 Nov 20 nicklas 997     
6058 17 Nov 20 nicklas 998     // Along Y-axis
6058 17 Nov 20 nicklas 999     canvas.saveState();
6058 17 Nov 20 nicklas 1000     canvas.setLineWidth(0.25f);
6058 17 Nov 20 nicklas 1001     for (int y = 0; y < pageHeight; y += 2)
6058 17 Nov 20 nicklas 1002     {
6058 17 Nov 20 nicklas 1003       int lineLength = y % 50 == 0 ? 15 : (y % 10 == 0 ? 10 : 5);
6058 17 Nov 20 nicklas 1004       canvas.moveTo(0, y);
6058 17 Nov 20 nicklas 1005       canvas.lineTo(lineLength, y);
6058 17 Nov 20 nicklas 1006       canvas.moveTo(pageWidth-lineLength, y);
6058 17 Nov 20 nicklas 1007       canvas.lineTo(pageWidth, y);
6058 17 Nov 20 nicklas 1008     }
6058 17 Nov 20 nicklas 1009     
6058 17 Nov 20 nicklas 1010     // Along X axis
6058 17 Nov 20 nicklas 1011     for (int x = 0; x < pageWidth; x += 2)
6058 17 Nov 20 nicklas 1012     {
6058 17 Nov 20 nicklas 1013       int lineLength = x % 50 == 0 ? 15 : (x % 10 == 0 ? 10 : 5);
6058 17 Nov 20 nicklas 1014       canvas.moveTo(x, 0);
6058 17 Nov 20 nicklas 1015       canvas.lineTo(x, lineLength);
6058 17 Nov 20 nicklas 1016       canvas.moveTo(x, pageHeight-lineLength);
6058 17 Nov 20 nicklas 1017       canvas.lineTo(x, pageHeight);
6058 17 Nov 20 nicklas 1018     }
6058 17 Nov 20 nicklas 1019     
6058 17 Nov 20 nicklas 1020     canvas.stroke();
6058 17 Nov 20 nicklas 1021     canvas.restoreState();
6058 17 Nov 20 nicklas 1022   
6058 17 Nov 20 nicklas 1023     PdfFont gridFont = getDefaultFont();
6058 17 Nov 20 nicklas 1024     float gridFontSize = 8;
6058 17 Nov 20 nicklas 1025     canvas.setFontAndSize(gridFont, gridFontSize);
6058 17 Nov 20 nicklas 1026     canvas.beginText();
6058 17 Nov 20 nicklas 1027     // Put labels on major grid lines
6058 17 Nov 20 nicklas 1028     for (int y = 50; y < pageHeight; y += 50)
6058 17 Nov 20 nicklas 1029     {
6058 17 Nov 20 nicklas 1030       String lbl = Integer.toString(y);
6058 17 Nov 20 nicklas 1031       float lblWidth = gridFont.getWidth(lbl, gridFontSize);
6058 17 Nov 20 nicklas 1032       canvas.setTextMatrix(16, y-3);
6058 17 Nov 20 nicklas 1033       canvas.showText(lbl);
6058 17 Nov 20 nicklas 1034       canvas.setTextMatrix(pageWidth-16-lblWidth, y-3);
6058 17 Nov 20 nicklas 1035       canvas.showText(lbl);
6058 17 Nov 20 nicklas 1036     }
6058 17 Nov 20 nicklas 1037     for (int x = 50; x < pageWidth; x += 50)
6058 17 Nov 20 nicklas 1038     {
6058 17 Nov 20 nicklas 1039       String lbl = Integer.toString(x);
6058 17 Nov 20 nicklas 1040       float lblWidth = gridFont.getWidth(lbl, gridFontSize);
6058 17 Nov 20 nicklas 1041       canvas.setTextMatrix(0, 1, -1, 0, x+3, 16); // 90 degreese rotation
6058 17 Nov 20 nicklas 1042       canvas.showText(lbl);
6058 17 Nov 20 nicklas 1043       canvas.setTextMatrix(0, 1, -1, 0, x+3, pageHeight-16-lblWidth);
6058 17 Nov 20 nicklas 1044       canvas.showText(lbl);
6058 17 Nov 20 nicklas 1045     }
6058 17 Nov 20 nicklas 1046     canvas.endText(); 
6058 17 Nov 20 nicklas 1047   }
6058 17 Nov 20 nicklas 1048
6058 17 Nov 20 nicklas 1049   /**
6058 17 Nov 20 nicklas 1050     Alignment specifications for text, images, etc.
6058 17 Nov 20 nicklas 1051   */
6058 17 Nov 20 nicklas 1052   public enum Align
6058 17 Nov 20 nicklas 1053   {
6058 17 Nov 20 nicklas 1054     /**
6058 17 Nov 20 nicklas 1055       Align the left and bottom side to the specified coordinate.
6058 17 Nov 20 nicklas 1056     */
6058 17 Nov 20 nicklas 1057     LEFT,
6058 17 Nov 20 nicklas 1058     
6058 17 Nov 20 nicklas 1059     /**
6058 17 Nov 20 nicklas 1060       Align the center (and bottom) to the specified coordinate.
6058 17 Nov 20 nicklas 1061     */
6058 17 Nov 20 nicklas 1062     CENTER
6058 17 Nov 20 nicklas 1063     {
6058 17 Nov 20 nicklas 1064       @Override
6058 17 Nov 20 nicklas 1065       public float adjustX(float x, PdfFont font, String text, float size) 
6058 17 Nov 20 nicklas 1066       {
6058 17 Nov 20 nicklas 1067         return x-font.getWidth(text, size)/2;
6058 17 Nov 20 nicklas 1068       }
6058 17 Nov 20 nicklas 1069       @Override
6058 17 Nov 20 nicklas 1070       public float adjustX(float x, float width)
6058 17 Nov 20 nicklas 1071       {
6058 17 Nov 20 nicklas 1072         return x-width/2;
6058 17 Nov 20 nicklas 1073       }
6058 17 Nov 20 nicklas 1074     },
6058 17 Nov 20 nicklas 1075     
6058 17 Nov 20 nicklas 1076     /**
6058 17 Nov 20 nicklas 1077       Align the right (and bottom) to the specified coordinate.
6058 17 Nov 20 nicklas 1078     */
6058 17 Nov 20 nicklas 1079     RIGHT
6058 17 Nov 20 nicklas 1080     {
6058 17 Nov 20 nicklas 1081       @Override
6058 17 Nov 20 nicklas 1082       public float adjustX(float x, PdfFont font, String text, float size) 
6058 17 Nov 20 nicklas 1083       {
6058 17 Nov 20 nicklas 1084         return x-font.getWidth(text, size);
6058 17 Nov 20 nicklas 1085       }
6058 17 Nov 20 nicklas 1086       @Override
6058 17 Nov 20 nicklas 1087       public float adjustX(float x, float width)
6058 17 Nov 20 nicklas 1088       {
6058 17 Nov 20 nicklas 1089         return x-width;
6058 17 Nov 20 nicklas 1090       }
6058 17 Nov 20 nicklas 1091     },
6058 17 Nov 20 nicklas 1092     
6058 17 Nov 20 nicklas 1093     /**
6058 17 Nov 20 nicklas 1094       Align the left and top to the specified coordinate.
6058 17 Nov 20 nicklas 1095     */
6058 17 Nov 20 nicklas 1096     TOP_LEFT
6058 17 Nov 20 nicklas 1097     {
6058 17 Nov 20 nicklas 1098       @Override
6058 17 Nov 20 nicklas 1099       public float adjustY(float y, float height)
6058 17 Nov 20 nicklas 1100       {
6058 17 Nov 20 nicklas 1101         return y-height;
6058 17 Nov 20 nicklas 1102       }
6058 17 Nov 20 nicklas 1103       
6058 17 Nov 20 nicklas 1104     },
6058 17 Nov 20 nicklas 1105     
6058 17 Nov 20 nicklas 1106     /**
6058 17 Nov 20 nicklas 1107       Align the right and top to the specified coordinate.
6058 17 Nov 20 nicklas 1108     */
6058 17 Nov 20 nicklas 1109     TOP_RIGHT
6058 17 Nov 20 nicklas 1110     {
6058 17 Nov 20 nicklas 1111       @Override
6058 17 Nov 20 nicklas 1112       public float adjustX(float x, PdfFont font, String text, float size) 
6058 17 Nov 20 nicklas 1113       {
6058 17 Nov 20 nicklas 1114         return x-font.getWidth(text, size);
6058 17 Nov 20 nicklas 1115       }
6058 17 Nov 20 nicklas 1116       @Override
6058 17 Nov 20 nicklas 1117       public float adjustX(float x, float width)
6058 17 Nov 20 nicklas 1118       {
6058 17 Nov 20 nicklas 1119         return x-width;
6058 17 Nov 20 nicklas 1120       }
6058 17 Nov 20 nicklas 1121       @Override
6058 17 Nov 20 nicklas 1122       public float adjustY(float y, float height)
6058 17 Nov 20 nicklas 1123       {
6058 17 Nov 20 nicklas 1124         return y-height;
6058 17 Nov 20 nicklas 1125       }
6058 17 Nov 20 nicklas 1126     };
6058 17 Nov 20 nicklas 1127     
6058 17 Nov 20 nicklas 1128     public float adjustX(float x, PdfFont font, String text, float size)
6058 17 Nov 20 nicklas 1129     {
6058 17 Nov 20 nicklas 1130       return x;
6058 17 Nov 20 nicklas 1131     }
6058 17 Nov 20 nicklas 1132     public float adjustX(float x, float width)
6058 17 Nov 20 nicklas 1133     {
6058 17 Nov 20 nicklas 1134       return x;
6058 17 Nov 20 nicklas 1135     }
6058 17 Nov 20 nicklas 1136     public float adjustY(float y, float height)
6058 17 Nov 20 nicklas 1137     {
6058 17 Nov 20 nicklas 1138       return y;
6058 17 Nov 20 nicklas 1139     }
6058 17 Nov 20 nicklas 1140   }
6069 19 Nov 20 nicklas 1141   
6069 19 Nov 20 nicklas 1142   /**
6069 19 Nov 20 nicklas 1143     Options for text and images, etc. Note that all options doesn't 
6069 19 Nov 20 nicklas 1144     apply in all cases. 
6069 19 Nov 20 nicklas 1145   */
6069 19 Nov 20 nicklas 1146   public static class Options
6069 19 Nov 20 nicklas 1147     implements Cloneable
6069 19 Nov 20 nicklas 1148   {
6069 19 Nov 20 nicklas 1149     /**
6069 19 Nov 20 nicklas 1150       Default options.
6069 19 Nov 20 nicklas 1151     */
6069 19 Nov 20 nicklas 1152     public static final Options DEFAULT = new Options().lock();
6069 19 Nov 20 nicklas 1153     
6069 19 Nov 20 nicklas 1154     /**
6069 19 Nov 20 nicklas 1155       Default options, except that objects are aligned to the right.
6069 19 Nov 20 nicklas 1156     */
6069 19 Nov 20 nicklas 1157     public static final Options ALIGN_RIGHT = new Options(Align.RIGHT).lock();
6069 19 Nov 20 nicklas 1158     
6069 19 Nov 20 nicklas 1159     Align align = Align.LEFT;
6069 19 Nov 20 nicklas 1160     Color color = null;
6069 19 Nov 20 nicklas 1161     boolean underline = false;
6069 19 Nov 20 nicklas 1162     PdfFont font = null;
6069 19 Nov 20 nicklas 1163     boolean bold = false;
6073 20 Nov 20 nicklas 1164     boolean italic = false;
6070 20 Nov 20 nicklas 1165     float rotation = Float.NaN;
6078 24 Nov 20 nicklas 1166     float maxTextWidth = Float.NaN;
6078 24 Nov 20 nicklas 1167     float minFontSizeFraction = 0.75f;
6069 19 Nov 20 nicklas 1168     
6069 19 Nov 20 nicklas 1169     private boolean locked;
6069 19 Nov 20 nicklas 1170     
6069 19 Nov 20 nicklas 1171     /**
6069 19 Nov 20 nicklas 1172       Create a new options instance with all default settings.
6069 19 Nov 20 nicklas 1173     */
6069 19 Nov 20 nicklas 1174     public Options()
6069 19 Nov 20 nicklas 1175     {}
6069 19 Nov 20 nicklas 1176
6069 19 Nov 20 nicklas 1177     /**
6069 19 Nov 20 nicklas 1178       Create a new options instance with all default settings, except alignment.
6069 19 Nov 20 nicklas 1179     */
6069 19 Nov 20 nicklas 1180     public Options(Align align)
6069 19 Nov 20 nicklas 1181     {
6069 19 Nov 20 nicklas 1182       this.align = align;
6069 19 Nov 20 nicklas 1183     }
6069 19 Nov 20 nicklas 1184
6449 20 Oct 21 nicklas 1185     private Options checkLocked()
6069 19 Nov 20 nicklas 1186     {
6449 20 Oct 21 nicklas 1187       if (locked)
6449 20 Oct 21 nicklas 1188       {
6449 20 Oct 21 nicklas 1189         try
6449 20 Oct 21 nicklas 1190         {
6449 20 Oct 21 nicklas 1191           return this.clone();
6449 20 Oct 21 nicklas 1192         }
6449 20 Oct 21 nicklas 1193         catch (CloneNotSupportedException ex)
6449 20 Oct 21 nicklas 1194         {
6449 20 Oct 21 nicklas 1195           // Should never happen
6449 20 Oct 21 nicklas 1196           throw new IllegalStateException("Options is locked");
6449 20 Oct 21 nicklas 1197         }
6449 20 Oct 21 nicklas 1198       }
6449 20 Oct 21 nicklas 1199       return this;
6069 19 Nov 20 nicklas 1200     }
6069 19 Nov 20 nicklas 1201     
6069 19 Nov 20 nicklas 1202     /**
6069 19 Nov 20 nicklas 1203       Are the options locked?
6069 19 Nov 20 nicklas 1204     */
6069 19 Nov 20 nicklas 1205     public boolean isLocked()
6069 19 Nov 20 nicklas 1206     {
6069 19 Nov 20 nicklas 1207       return locked;
6069 19 Nov 20 nicklas 1208     }
6069 19 Nov 20 nicklas 1209     
6069 19 Nov 20 nicklas 1210     /**
6069 19 Nov 20 nicklas 1211       Clone the options and return an unlocked instance.
6069 19 Nov 20 nicklas 1212     */
6069 19 Nov 20 nicklas 1213     @Override
6449 20 Oct 21 nicklas 1214     public Options clone()
6069 19 Nov 20 nicklas 1215       throws CloneNotSupportedException 
6069 19 Nov 20 nicklas 1216     {
6069 19 Nov 20 nicklas 1217       Options clone = (Options)super.clone();
6069 19 Nov 20 nicklas 1218       clone.locked = false;
6069 19 Nov 20 nicklas 1219       return clone;
6069 19 Nov 20 nicklas 1220     }
6069 19 Nov 20 nicklas 1221
6069 19 Nov 20 nicklas 1222     /**
6069 19 Nov 20 nicklas 1223       Lock the options for modifications.
6069 19 Nov 20 nicklas 1224     */
6069 19 Nov 20 nicklas 1225     public Options lock()
6069 19 Nov 20 nicklas 1226     {
6069 19 Nov 20 nicklas 1227       this.locked = true;
6069 19 Nov 20 nicklas 1228       return this;
6069 19 Nov 20 nicklas 1229     }
6069 19 Nov 20 nicklas 1230     
6069 19 Nov 20 nicklas 1231     public Align getAlign()
6069 19 Nov 20 nicklas 1232     {
6069 19 Nov 20 nicklas 1233       return align;
6069 19 Nov 20 nicklas 1234     }
6069 19 Nov 20 nicklas 1235     
6069 19 Nov 20 nicklas 1236     /**
6069 19 Nov 20 nicklas 1237       Change alignment.
6069 19 Nov 20 nicklas 1238     */
6069 19 Nov 20 nicklas 1239     public Options align(Align align)
6069 19 Nov 20 nicklas 1240     {
6449 20 Oct 21 nicklas 1241       Options o = checkLocked();
6449 20 Oct 21 nicklas 1242       o.align = align;
6449 20 Oct 21 nicklas 1243       return o;
6069 19 Nov 20 nicklas 1244     }
6069 19 Nov 20 nicklas 1245     
6069 19 Nov 20 nicklas 1246     public Color getColor()
6069 19 Nov 20 nicklas 1247     {
6069 19 Nov 20 nicklas 1248       return color;
6069 19 Nov 20 nicklas 1249     }
6069 19 Nov 20 nicklas 1250     
6069 19 Nov 20 nicklas 1251     /**
6069 19 Nov 20 nicklas 1252       Change text color.
6069 19 Nov 20 nicklas 1253     */
6069 19 Nov 20 nicklas 1254     public Options color(Color color)
6069 19 Nov 20 nicklas 1255     {
6449 20 Oct 21 nicklas 1256       Options o = checkLocked();
6449 20 Oct 21 nicklas 1257       o.color = color;
6449 20 Oct 21 nicklas 1258       return o;
6069 19 Nov 20 nicklas 1259     }
6069 19 Nov 20 nicklas 1260     
6069 19 Nov 20 nicklas 1261     public boolean getUnderline()
6069 19 Nov 20 nicklas 1262     {
6069 19 Nov 20 nicklas 1263       return underline;
6069 19 Nov 20 nicklas 1264     }
6069 19 Nov 20 nicklas 1265     
6069 19 Nov 20 nicklas 1266     /**
6069 19 Nov 20 nicklas 1267       Enable underlined text.
6069 19 Nov 20 nicklas 1268     */
6069 19 Nov 20 nicklas 1269     public Options underline()
6069 19 Nov 20 nicklas 1270     {
6069 19 Nov 20 nicklas 1271       return underline(true);
6069 19 Nov 20 nicklas 1272     }
6069 19 Nov 20 nicklas 1273     
6069 19 Nov 20 nicklas 1274     /**
6069 19 Nov 20 nicklas 1275       Enable or disable underlined text.
6069 19 Nov 20 nicklas 1276     */
6069 19 Nov 20 nicklas 1277     public Options underline(boolean underline)
6069 19 Nov 20 nicklas 1278     {
6449 20 Oct 21 nicklas 1279       Options o = checkLocked();
6449 20 Oct 21 nicklas 1280       o.underline = underline;
6449 20 Oct 21 nicklas 1281       return o;
6069 19 Nov 20 nicklas 1282     }
6069 19 Nov 20 nicklas 1283     
6069 19 Nov 20 nicklas 1284     public PdfFont getFont()
6069 19 Nov 20 nicklas 1285     {
6069 19 Nov 20 nicklas 1286       return font;
6069 19 Nov 20 nicklas 1287     }
6069 19 Nov 20 nicklas 1288     
6069 19 Nov 20 nicklas 1289     /**
6069 19 Nov 20 nicklas 1290       Change text font.
6069 19 Nov 20 nicklas 1291     */
6069 19 Nov 20 nicklas 1292     public Options font(PdfFont font)
6069 19 Nov 20 nicklas 1293     {
6449 20 Oct 21 nicklas 1294       Options o = checkLocked();
6449 20 Oct 21 nicklas 1295       o.font = font;
6073 20 Nov 20 nicklas 1296       if (font != null) 
6073 20 Nov 20 nicklas 1297       {
6073 20 Nov 20 nicklas 1298         FontNames fn = font.getFontProgram().getFontNames();
6449 20 Oct 21 nicklas 1299         o.bold = fn.isBold();
6449 20 Oct 21 nicklas 1300         o.italic = fn.isItalic();
6073 20 Nov 20 nicklas 1301       }
6449 20 Oct 21 nicklas 1302       return o;
6069 19 Nov 20 nicklas 1303     }
6069 19 Nov 20 nicklas 1304     
6069 19 Nov 20 nicklas 1305     public boolean getBold()
6069 19 Nov 20 nicklas 1306     {
6069 19 Nov 20 nicklas 1307       return bold;
6069 19 Nov 20 nicklas 1308     }
6069 19 Nov 20 nicklas 1309     
6069 19 Nov 20 nicklas 1310     /**
6069 19 Nov 20 nicklas 1311       Enable bold text. This flag is automatically updated when a font is 
6069 19 Nov 20 nicklas 1312       specified by {@link #font(PdfFont)}. If no explicit font has been set
6069 19 Nov 20 nicklas 1313       and this flag is enabled the default bold font is selected.
6069 19 Nov 20 nicklas 1314     */
6069 19 Nov 20 nicklas 1315     public Options bold()
6069 19 Nov 20 nicklas 1316     {
6069 19 Nov 20 nicklas 1317       return bold(true);
6069 19 Nov 20 nicklas 1318     }
6069 19 Nov 20 nicklas 1319     
6069 19 Nov 20 nicklas 1320     /**
6069 19 Nov 20 nicklas 1321       Enable or disable bold text.
6069 19 Nov 20 nicklas 1322     */
6069 19 Nov 20 nicklas 1323     public Options bold(boolean bold)
6069 19 Nov 20 nicklas 1324     {
6449 20 Oct 21 nicklas 1325       Options o = checkLocked();
6449 20 Oct 21 nicklas 1326       o.bold = bold;
6449 20 Oct 21 nicklas 1327       return o;
6070 20 Nov 20 nicklas 1328     }
6070 20 Nov 20 nicklas 1329     
6073 20 Nov 20 nicklas 1330     public boolean getItalic()
6073 20 Nov 20 nicklas 1331     {
6073 20 Nov 20 nicklas 1332       return italic;
6073 20 Nov 20 nicklas 1333     }
6073 20 Nov 20 nicklas 1334     
6073 20 Nov 20 nicklas 1335     /**
6073 20 Nov 20 nicklas 1336       Enable italic text. This flag is automatically updated when a font is 
6073 20 Nov 20 nicklas 1337       specified by {@link #font(PdfFont)}. If no explicit font has been set
6073 20 Nov 20 nicklas 1338       and this flag is enabled the default italic font is selected.
6073 20 Nov 20 nicklas 1339     */
6073 20 Nov 20 nicklas 1340     public Options italic()
6073 20 Nov 20 nicklas 1341     {
6073 20 Nov 20 nicklas 1342       return italic(true);
6073 20 Nov 20 nicklas 1343     }
6073 20 Nov 20 nicklas 1344     
6073 20 Nov 20 nicklas 1345     /**
6073 20 Nov 20 nicklas 1346       Enable or disable italic text.
6073 20 Nov 20 nicklas 1347     */
6073 20 Nov 20 nicklas 1348     public Options italic(boolean italic)
6073 20 Nov 20 nicklas 1349     {
6449 20 Oct 21 nicklas 1350       Options o = checkLocked();
6449 20 Oct 21 nicklas 1351       o.italic = italic;
6449 20 Oct 21 nicklas 1352       return o;
6073 20 Nov 20 nicklas 1353     }
6073 20 Nov 20 nicklas 1354     
6070 20 Nov 20 nicklas 1355     public float getRotation()
6070 20 Nov 20 nicklas 1356     {
6070 20 Nov 20 nicklas 1357       return rotation;
6070 20 Nov 20 nicklas 1358     }
6070 20 Nov 20 nicklas 1359     
6070 20 Nov 20 nicklas 1360     /**
6070 20 Nov 20 nicklas 1361       Rotate text by the specified angle in degrees.
6070 20 Nov 20 nicklas 1362     */
6070 20 Nov 20 nicklas 1363     public Options rotation(float rotation)
6070 20 Nov 20 nicklas 1364     {
6449 20 Oct 21 nicklas 1365       Options o = checkLocked();
6449 20 Oct 21 nicklas 1366       o.rotation = rotation;
6449 20 Oct 21 nicklas 1367       return o;
6069 19 Nov 20 nicklas 1368     }
6069 19 Nov 20 nicklas 1369     
6078 24 Nov 20 nicklas 1370     public float getMaxTextWidth()
6078 24 Nov 20 nicklas 1371     {
6078 24 Nov 20 nicklas 1372       return maxTextWidth;
6078 24 Nov 20 nicklas 1373     }
6078 24 Nov 20 nicklas 1374     
6078 24 Nov 20 nicklas 1375     /**
6078 24 Nov 20 nicklas 1376       Set the max width of the text. If the text
6078 24 Nov 20 nicklas 1377       takes up more space, the font size will automatically
6078 24 Nov 20 nicklas 1378       be decreased in steps of 0.5 points down to 'minFontSizeFraction' 
6078 24 Nov 20 nicklas 1379       (default is 0.75) of the original font size. If the text still doesn't 
6078 24 Nov 20 nicklas 1380       fit, characters are removed from the end until it is short enough and
6078 24 Nov 20 nicklas 1381       an ellipsis is added.
6078 24 Nov 20 nicklas 1382     */
6078 24 Nov 20 nicklas 1383     public Options maxTextWidth(float maxTextWidth)
6078 24 Nov 20 nicklas 1384     {
6449 20 Oct 21 nicklas 1385       Options o = checkLocked();
6449 20 Oct 21 nicklas 1386       o.maxTextWidth = maxTextWidth;
6449 20 Oct 21 nicklas 1387       return o;
6078 24 Nov 20 nicklas 1388     }
6078 24 Nov 20 nicklas 1389
6078 24 Nov 20 nicklas 1390     public float getMinFontSizeFraction()
6078 24 Nov 20 nicklas 1391     {
6078 24 Nov 20 nicklas 1392       return minFontSizeFraction;
6078 24 Nov 20 nicklas 1393     }
6078 24 Nov 20 nicklas 1394     
6078 24 Nov 20 nicklas 1395     /**
6078 24 Nov 20 nicklas 1396       Set the lower limit of the font size that is acceptable if
6078 24 Nov 20 nicklas 1397       the text is using more space than specified by 'maxTextWidth'. 
6078 24 Nov 20 nicklas 1398       This font size is calculated as a fraction of the original
6078 24 Nov 20 nicklas 1399       font size. The default value is 0.75.
6078 24 Nov 20 nicklas 1400     */
6078 24 Nov 20 nicklas 1401     public Options minFontSizeFraction(float minFontSizeFraction)
6078 24 Nov 20 nicklas 1402     {
6449 20 Oct 21 nicklas 1403       Options o = checkLocked();
6449 20 Oct 21 nicklas 1404       o.minFontSizeFraction = minFontSizeFraction;
6449 20 Oct 21 nicklas 1405       return o;
6078 24 Nov 20 nicklas 1406     }
6078 24 Nov 20 nicklas 1407
6078 24 Nov 20 nicklas 1408     
6069 19 Nov 20 nicklas 1409   }
6069 19 Nov 20 nicklas 1410   
6058 17 Nov 20 nicklas 1411 }