001/* 002 * $Id: FdfWriter.java 4784 2011-03-15 08:33:00Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044package com.itextpdf.text.pdf; 045 046import java.io.IOException; 047import java.io.OutputStream; 048import java.util.ArrayList; 049import java.util.HashMap; 050import java.util.Map; 051import java.util.StringTokenizer; 052 053import com.itextpdf.text.DocWriter; 054import com.itextpdf.text.pdf.AcroFields.Item; 055 056/** Writes an FDF form. 057 * @author Paulo Soares 058 */ 059public class FdfWriter { 060 private static final byte[] HEADER_FDF = DocWriter.getISOBytes("%FDF-1.4\n%\u00e2\u00e3\u00cf\u00d3\n"); 061 HashMap<String, Object> fields = new HashMap<String, Object>(); 062 063 /** The PDF file associated with the FDF. */ 064 private String file; 065 066 /** Creates a new FdfWriter. */ 067 public FdfWriter() { 068 } 069 070 /** Writes the content to a stream. 071 * @param os the stream 072 * @throws IOException on error 073 */ 074 public void writeTo(OutputStream os) throws IOException { 075 Wrt wrt = new Wrt(os, this); 076 wrt.writeTo(); 077 } 078 079 @SuppressWarnings("unchecked") 080 boolean setField(String field, PdfObject value) { 081 HashMap<String, Object> map = fields; 082 StringTokenizer tk = new StringTokenizer(field, "."); 083 if (!tk.hasMoreTokens()) 084 return false; 085 while (true) { 086 String s = tk.nextToken(); 087 Object obj = map.get(s); 088 if (tk.hasMoreTokens()) { 089 if (obj == null) { 090 obj = new HashMap<String, Object>(); 091 map.put(s, obj); 092 map = (HashMap<String, Object>)obj; 093 continue; 094 } 095 else if (obj instanceof HashMap) 096 map = (HashMap<String, Object>)obj; 097 else 098 return false; 099 } 100 else { 101 if (!(obj instanceof HashMap)) { 102 map.put(s, value); 103 return true; 104 } 105 else 106 return false; 107 } 108 } 109 } 110 111 @SuppressWarnings("unchecked") 112 void iterateFields(HashMap<String, Object> values, HashMap<String, Object> map, String name) { 113 for (Map.Entry<String, Object> entry: map.entrySet()) { 114 String s = entry.getKey(); 115 Object obj = entry.getValue(); 116 if (obj instanceof HashMap) 117 iterateFields(values, (HashMap<String, Object>)obj, name + "." + s); 118 else 119 values.put((name + "." + s).substring(1), obj); 120 } 121 } 122 123 /** Removes the field value. 124 * @param field the field name 125 * @return <CODE>true</CODE> if the field was found and removed, 126 * <CODE>false</CODE> otherwise 127 */ 128 @SuppressWarnings("unchecked") 129 public boolean removeField(String field) { 130 HashMap<String, Object> map = fields; 131 StringTokenizer tk = new StringTokenizer(field, "."); 132 if (!tk.hasMoreTokens()) 133 return false; 134 ArrayList<Object> hist = new ArrayList<Object>(); 135 while (true) { 136 String s = tk.nextToken(); 137 Object obj = map.get(s); 138 if (obj == null) 139 return false; 140 hist.add(map); 141 hist.add(s); 142 if (tk.hasMoreTokens()) { 143 if (obj instanceof HashMap) 144 map = (HashMap<String, Object>)obj; 145 else 146 return false; 147 } 148 else { 149 if (obj instanceof HashMap) 150 return false; 151 else 152 break; 153 } 154 } 155 for (int k = hist.size() - 2; k >= 0; k -= 2) { 156 map = (HashMap<String, Object>)hist.get(k); 157 String s = (String)hist.get(k + 1); 158 map.remove(s); 159 if (!map.isEmpty()) 160 break; 161 } 162 return true; 163 } 164 165 /** Gets all the fields. The map is keyed by the fully qualified 166 * field name and the values are <CODE>PdfObject</CODE>. 167 * @return a map with all the fields 168 */ 169 public HashMap<String, Object> getFields() { 170 HashMap<String, Object> values = new HashMap<String, Object>(); 171 iterateFields(values, fields, ""); 172 return values; 173 } 174 175 /** Gets the field value. 176 * @param field the field name 177 * @return the field value or <CODE>null</CODE> if not found 178 */ 179 @SuppressWarnings("unchecked") 180 public String getField(String field) { 181 HashMap<String, Object> map = fields; 182 StringTokenizer tk = new StringTokenizer(field, "."); 183 if (!tk.hasMoreTokens()) 184 return null; 185 while (true) { 186 String s = tk.nextToken(); 187 Object obj = map.get(s); 188 if (obj == null) 189 return null; 190 if (tk.hasMoreTokens()) { 191 if (obj instanceof HashMap) 192 map = (HashMap<String, Object>)obj; 193 else 194 return null; 195 } 196 else { 197 if (obj instanceof HashMap) 198 return null; 199 else { 200 if (((PdfObject)obj).isString()) 201 return ((PdfString)obj).toUnicodeString(); 202 else 203 return PdfName.decodeName(obj.toString()); 204 } 205 } 206 } 207 } 208 209 /** Sets the field value as a name. 210 * @param field the fully qualified field name 211 * @param value the value 212 * @return <CODE>true</CODE> if the value was inserted, 213 * <CODE>false</CODE> if the name is incompatible with 214 * an existing field 215 */ 216 public boolean setFieldAsName(String field, String value) { 217 return setField(field, new PdfName(value)); 218 } 219 220 /** Sets the field value as a string. 221 * @param field the fully qualified field name 222 * @param value the value 223 * @return <CODE>true</CODE> if the value was inserted, 224 * <CODE>false</CODE> if the name is incompatible with 225 * an existing field 226 */ 227 public boolean setFieldAsString(String field, String value) { 228 return setField(field, new PdfString(value, PdfObject.TEXT_UNICODE)); 229 } 230 231 /** 232 * Sets the field value as a <CODE>PDFAction</CODE>. 233 * For example, this method allows setting a form submit button action using {@link PdfAction#createSubmitForm(String, Object[], int)}. 234 * This method creates an <CODE>A</CODE> entry for the specified field in the underlying FDF file. 235 * Method contributed by Philippe Laflamme (plaflamme) 236 * @param field the fully qualified field name 237 * @param action the field's action 238 * @return <CODE>true</CODE> if the value was inserted, 239 * <CODE>false</CODE> if the name is incompatible with 240 * an existing field 241 * @since 2.1.5 242 */ 243 public boolean setFieldAsAction(String field, PdfAction action) { 244 return setField(field, action); 245 } 246 247 /** Sets all the fields from this <CODE>FdfReader</CODE> 248 * @param fdf the <CODE>FdfReader</CODE> 249 */ 250 public void setFields(FdfReader fdf) { 251 HashMap<String, PdfDictionary> map = fdf.getFields(); 252 for (Map.Entry<String, PdfDictionary> entry: map.entrySet()) { 253 String key = entry.getKey(); 254 PdfDictionary dic = entry.getValue(); 255 PdfObject v = dic.get(PdfName.V); 256 if (v != null) { 257 setField(key, v); 258 } 259 v = dic.get(PdfName.A); // (plaflamme) 260 if (v != null) { 261 setField(key, v); 262 } 263 } 264 } 265 266 /** Sets all the fields from this <CODE>PdfReader</CODE> 267 * @param pdf the <CODE>PdfReader</CODE> 268 */ 269 public void setFields(PdfReader pdf) { 270 setFields(pdf.getAcroFields()); 271 } 272 273 /** Sets all the fields from this <CODE>AcroFields</CODE> 274 * @param af the <CODE>AcroFields</CODE> 275 */ 276 public void setFields(AcroFields af) { 277 for (Map.Entry<String, Item> entry: af.getFields().entrySet()) { 278 String fn = entry.getKey(); 279 AcroFields.Item item = entry.getValue(); 280 PdfDictionary dic = item.getMerged(0); 281 PdfObject v = PdfReader.getPdfObjectRelease(dic.get(PdfName.V)); 282 if (v == null) 283 continue; 284 PdfObject ft = PdfReader.getPdfObjectRelease(dic.get(PdfName.FT)); 285 if (ft == null || PdfName.SIG.equals(ft)) 286 continue; 287 setField(fn, v); 288 } 289 } 290 291 /** Gets the PDF file name associated with the FDF. 292 * @return the PDF file name associated with the FDF 293 */ 294 public String getFile() { 295 return this.file; 296 } 297 298 /** Sets the PDF file name associated with the FDF. 299 * @param file the PDF file name associated with the FDF 300 * 301 */ 302 public void setFile(String file) { 303 this.file = file; 304 } 305 306 static class Wrt extends PdfWriter { 307 private FdfWriter fdf; 308 309 Wrt(OutputStream os, FdfWriter fdf) throws IOException { 310 super(new PdfDocument(), os); 311 this.fdf = fdf; 312 this.os.write(HEADER_FDF); 313 body = new PdfBody(this); 314 } 315 316 void writeTo() throws IOException { 317 PdfDictionary dic = new PdfDictionary(); 318 dic.put(PdfName.FIELDS, calculate(fdf.fields)); 319 if (fdf.file != null) 320 dic.put(PdfName.F, new PdfString(fdf.file, PdfObject.TEXT_UNICODE)); 321 PdfDictionary fd = new PdfDictionary(); 322 fd.put(PdfName.FDF, dic); 323 PdfIndirectReference ref = addToBody(fd).getIndirectReference(); 324 os.write(getISOBytes("trailer\n")); 325 PdfDictionary trailer = new PdfDictionary(); 326 trailer.put(PdfName.ROOT, ref); 327 trailer.toPdf(null, os); 328 os.write(getISOBytes("\n%%EOF\n")); 329 os.close(); 330 } 331 332 333 @SuppressWarnings("unchecked") 334 PdfArray calculate(HashMap<String, Object> map) throws IOException { 335 PdfArray ar = new PdfArray(); 336 for (Map.Entry<String, Object> entry: map.entrySet()) { 337 String key = entry.getKey(); 338 Object v = entry.getValue(); 339 PdfDictionary dic = new PdfDictionary(); 340 dic.put(PdfName.T, new PdfString(key, PdfObject.TEXT_UNICODE)); 341 if (v instanceof HashMap) { 342 dic.put(PdfName.KIDS, calculate((HashMap<String, Object>)v)); 343 } 344 else if(v instanceof PdfAction) { // (plaflamme) 345 dic.put(PdfName.A, (PdfAction)v); 346 } 347 else { 348 dic.put(PdfName.V, (PdfObject)v); 349 } 350 ar.add(dic); 351 } 352 return ar; 353 } 354 } 355}