001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.text.pdf; 020 021import java.io.IOException; 022import java.io.OutputStream; 023import java.io.StringWriter; 024import java.util.ArrayList; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import lucee.commons.io.IOUtil; 032import lucee.commons.io.res.Resource; 033import lucee.commons.lang.ExceptionUtil; 034import lucee.commons.lang.StringUtil; 035import lucee.runtime.PageContext; 036import lucee.runtime.exp.ApplicationException; 037import lucee.runtime.exp.CasterException; 038import lucee.runtime.exp.PageException; 039import lucee.runtime.img.Image; 040import lucee.runtime.op.Caster; 041import lucee.runtime.op.Constants; 042import lucee.runtime.op.Decision; 043 044import org.pdfbox.exceptions.CryptographyException; 045import org.pdfbox.exceptions.InvalidPasswordException; 046import org.pdfbox.pdmodel.PDDocument; 047import org.pdfbox.util.PDFText2HTML; 048 049import com.lowagie.text.Document; 050import com.lowagie.text.DocumentException; 051import com.lowagie.text.pdf.PRAcroForm; 052import com.lowagie.text.pdf.PdfCopy; 053import com.lowagie.text.pdf.PdfImportedPage; 054import com.lowagie.text.pdf.PdfReader; 055import com.lowagie.text.pdf.PdfWriter; 056import com.lowagie.text.pdf.SimpleBookmark; 057 058public class PDFUtil { 059 060 061 public static final int ENCRYPT_RC4_40 = PdfWriter.STANDARD_ENCRYPTION_40; 062 public static final int ENCRYPT_RC4_128 = PdfWriter.STANDARD_ENCRYPTION_128; 063 public static final int ENCRYPT_RC4_128M = PdfWriter.STANDARD_ENCRYPTION_128; 064 public static final int ENCRYPT_AES_128 = PdfWriter.ENCRYPTION_AES_128; 065 public static final int ENCRYPT_NONE = -1; 066 067 private static final int PERMISSION_ALL = 068 PdfWriter.ALLOW_ASSEMBLY+ 069 PdfWriter.ALLOW_COPY+ 070 PdfWriter.ALLOW_DEGRADED_PRINTING+ 071 PdfWriter.ALLOW_FILL_IN+ 072 PdfWriter.ALLOW_MODIFY_ANNOTATIONS+ 073 PdfWriter.ALLOW_MODIFY_CONTENTS+ 074 PdfWriter.ALLOW_PRINTING+ 075 PdfWriter.ALLOW_SCREENREADERS+PdfWriter.ALLOW_COPY;// muss 2 mal sein, keine ahnung wieso 076 077 /** 078 * convert a string list of permission 079 * @param strPermissions 080 * @return 081 * @throws PageException 082 */ 083 public static int toPermissions(String strPermissions) throws PageException { 084 if(strPermissions==null) return 0; 085 int permissions=0; 086 strPermissions=strPermissions.trim(); 087 088 String[] arr = lucee.runtime.type.util.ListUtil.toStringArray(lucee.runtime.type.util.ListUtil.listToArrayRemoveEmpty(strPermissions, ',')); 089 for(int i=0;i<arr.length;i++) { 090 permissions=add(permissions,toPermission(arr[i])); 091 } 092 return permissions; 093 } 094 095 /** 096 * convert a string defintion of a permision in a integer Constant (PdfWriter.ALLOW_XXX) 097 * @param strPermission 098 * @return 099 * @throws ApplicationException 100 */ 101 public static int toPermission(String strPermission) throws ApplicationException { 102 strPermission=strPermission.trim().toLowerCase(); 103 if("allowassembly".equals(strPermission)) return PdfWriter.ALLOW_ASSEMBLY; 104 else if("none".equals(strPermission)) return 0; 105 else if("all".equals(strPermission)) return PERMISSION_ALL; 106 else if("assembly".equals(strPermission)) return PdfWriter.ALLOW_ASSEMBLY; 107 else if("documentassembly".equals(strPermission)) return PdfWriter.ALLOW_ASSEMBLY; 108 else if("allowdegradedprinting".equals(strPermission)) return PdfWriter.ALLOW_DEGRADED_PRINTING; 109 else if("degradedprinting".equals(strPermission)) return PdfWriter.ALLOW_DEGRADED_PRINTING; 110 else if("printing".equals(strPermission)) return PdfWriter.ALLOW_DEGRADED_PRINTING; 111 else if("allowfillin".equals(strPermission)) return PdfWriter.ALLOW_FILL_IN; 112 else if("fillin".equals(strPermission)) return PdfWriter.ALLOW_FILL_IN; 113 else if("fillingform".equals(strPermission)) return PdfWriter.ALLOW_FILL_IN; 114 else if("allowmodifyannotations".equals(strPermission)) return PdfWriter.ALLOW_MODIFY_ANNOTATIONS; 115 else if("modifyannotations".equals(strPermission)) return PdfWriter.ALLOW_MODIFY_ANNOTATIONS; 116 else if("allowmodifycontents".equals(strPermission)) return PdfWriter.ALLOW_MODIFY_CONTENTS; 117 else if("modifycontents".equals(strPermission)) return PdfWriter.ALLOW_MODIFY_CONTENTS; 118 else if("allowcopy".equals(strPermission)) return PdfWriter.ALLOW_COPY; 119 else if("copy".equals(strPermission)) return PdfWriter.ALLOW_COPY; 120 else if("copycontent".equals(strPermission)) return PdfWriter.ALLOW_COPY; 121 else if("allowprinting".equals(strPermission)) return PdfWriter.ALLOW_PRINTING; 122 else if("printing".equals(strPermission)) return PdfWriter.ALLOW_PRINTING; 123 else if("allowscreenreaders".equals(strPermission)) return PdfWriter.ALLOW_SCREENREADERS; 124 else if("screenreaders".equals(strPermission)) return PdfWriter.ALLOW_SCREENREADERS; 125 126 else throw new ApplicationException("invalid permission ["+strPermission+"], valid permission values are [AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations, AllowFillIn, AllowScreenReaders, AllowAssembly, AllowDegradedPrinting]"); 127 } 128 129 130 private static int add(int permissions, int permission) { 131 if(permission==0 || (permissions&permission)>0)return permissions; 132 return permissions+permission; 133 } 134 135 136 /** 137 * @param docs 138 * @param os 139 * @param removePages if true, pages defined in PDFDocument will be removed, otherwise all other pages will be removed 140 * @param version 141 * @throws PageException 142 * @throws IOException 143 * @throws DocumentException 144 */ 145 public static void concat(PDFDocument[] docs,OutputStream os, boolean keepBookmark,boolean removePages, boolean stopOnError, char version) throws PageException, IOException, DocumentException { 146 Document document = null; 147 PdfCopy writer = null; 148 PdfReader reader; 149 Set pages; 150 boolean isInit=false; 151 PdfImportedPage page; 152 try { 153 int pageOffset = 0; 154 ArrayList master = new ArrayList(); 155 156 for(int i=0;i<docs.length;i++) { 157 // we create a reader for a certain document 158 pages = docs[i].getPages(); 159 try { 160 reader = docs[i].getPdfReader(); 161 } 162 catch(Throwable t) { 163 ExceptionUtil.rethrowIfNecessary(t); 164 if(!stopOnError)continue; 165 throw Caster.toPageException(t); 166 } 167 reader.consolidateNamedDestinations(); 168 169 // we retrieve the total number of pages 170 int n = reader.getNumberOfPages(); 171 List bookmarks = keepBookmark?SimpleBookmark.getBookmark(reader):null; 172 if (bookmarks != null) { 173 removeBookmarks(bookmarks,pages,removePages); 174 if (pageOffset != 0) SimpleBookmark.shiftPageNumbers(bookmarks, pageOffset, null); 175 master.addAll(bookmarks); 176 } 177 178 if (!isInit) { 179 isInit=true; 180 document = new Document(reader.getPageSizeWithRotation(1)); 181 writer = new PdfCopy(document, os); 182 183 if(version!=0)writer.setPdfVersion(version); 184 185 186 document.open(); 187 } 188 189 190 for (int y = 1; y <= n; y++) { 191 if(pages!=null && removePages==pages.contains(Integer.valueOf(y))){ 192 continue; 193 } 194 pageOffset++; 195 page = writer.getImportedPage(reader, y); 196 writer.addPage(page); 197 } 198 PRAcroForm form = reader.getAcroForm(); 199 if (form != null) 200 writer.copyAcroForm(reader); 201 } 202 if (master.size() > 0) 203 writer.setOutlines(master); 204 205 } 206 finally { 207 IOUtil.closeEL(document); 208 } 209 } 210 211 212 private static void removeBookmarks(List bookmarks,Set pages, boolean removePages) { 213 int size = bookmarks.size(); 214 for(int i=size-1;i>=0;i--) { 215 if(removeBookmarks((Map) bookmarks.get(i),pages, removePages)) 216 bookmarks.remove(i); 217 } 218 } 219 220 private static boolean removeBookmarks(Map bookmark, Set pages, boolean removePages) { 221 List kids=(List) bookmark.get("Kids"); 222 if(kids!=null)removeBookmarks(kids,pages,removePages); 223 Integer page=Caster.toInteger(lucee.runtime.type.util.ListUtil.first((String) bookmark.get("Page")," ",true),Constants.INTEGER_MINUS_ONE); 224 return removePages==(pages!=null && pages.contains(page)); 225 } 226 227 public static Set parsePageDefinition(String strPages) throws PageException { 228 if(StringUtil.isEmpty(strPages)) return null; 229 HashSet<Integer> set=new HashSet<Integer>(); 230 parsePageDefinition(set, strPages); 231 return set; 232 } 233 public static void parsePageDefinition(Set<Integer> pages, String strPages) throws PageException { 234 if(StringUtil.isEmpty(strPages)) return; 235 String[] arr = lucee.runtime.type.util.ListUtil.toStringArrayTrim(lucee.runtime.type.util.ListUtil.listToArrayRemoveEmpty(strPages, ',')); 236 int index,from,to; 237 for(int i=0;i<arr.length;i++){ 238 index=arr[i].indexOf('-'); 239 if(index==-1)pages.add(Caster.toInteger(arr[i].trim())); 240 else { 241 from=Caster.toIntValue(arr[i].substring(0,index).trim()); 242 to=Caster.toIntValue(arr[i].substring(index+1).trim()); 243 for(int y=from;y<=to;y++){ 244 pages.add(Integer.valueOf(y)); 245 } 246 } 247 } 248 } 249 250 251 252 253 254 public static void encrypt(PDFDocument doc, OutputStream os, String newUserPassword, String newOwnerPassword, int permissions, int encryption) throws ApplicationException, DocumentException, IOException { 255 byte[] user = newUserPassword==null?null:newUserPassword.getBytes(); 256 byte[] owner = newOwnerPassword==null?null:newOwnerPassword.getBytes(); 257 258 PdfReader pr = doc.getPdfReader(); 259 List bookmarks = SimpleBookmark.getBookmark(pr); 260 int n = pr.getNumberOfPages(); 261 262 Document document = new Document(pr.getPageSizeWithRotation(1)); 263 PdfCopy writer = new PdfCopy(document, os); 264 if(encryption!=ENCRYPT_NONE)writer.setEncryption(user, owner, permissions, encryption); 265 document.open(); 266 267 268 PdfImportedPage page; 269 for (int i = 1; i <= n; i++) { 270 page = writer.getImportedPage(pr, i); 271 writer.addPage(page); 272 } 273 PRAcroForm form = pr.getAcroForm(); 274 if (form != null)writer.copyAcroForm(pr); 275 if (bookmarks!=null)writer.setOutlines(bookmarks); 276 document.close(); 277 } 278 279 public static HashMap generateGoToBookMark(String title,int page) { 280 return generateGoToBookMark(title,page, 0, 731); 281 } 282 283 public static HashMap generateGoToBookMark(String title,int page, int x, int y) { 284 HashMap map=new HashMap(); 285 map.put("Title", title); 286 map.put("Action", "GoTo"); 287 map.put("Page", page+" XYZ "+x+" "+y+" null"); 288 289 return map; 290 } 291 292 public static void setChildBookmarks(Map parent, List children) { 293 Object kids = parent.get("Kids"); 294 if(kids instanceof List){ 295 ((List)kids).addAll(children); 296 } 297 else parent.put("Kids", children); 298 } 299 300 public static PdfReader toPdfReader(PageContext pc,Object value, String password) throws IOException, PageException { 301 if(value instanceof PdfReader) return (PdfReader) value; 302 if(value instanceof PDFDocument) return ((PDFDocument) value).getPdfReader(); 303 if(Decision.isBinary(value)){ 304 if(password!=null)return new PdfReader(Caster.toBinary(value),password.getBytes()); 305 return new PdfReader(Caster.toBinary(value)); 306 } 307 if(value instanceof Resource) { 308 if(password!=null)return new PdfReader(IOUtil.toBytes((Resource)value),password.getBytes()); 309 return new PdfReader(IOUtil.toBytes((Resource)value)); 310 } 311 if(value instanceof String) { 312 if(password!=null)return new PdfReader(IOUtil.toBytes(Caster.toResource(pc,value,true)),password.getBytes()); 313 return new PdfReader(IOUtil.toBytes((Resource)value)); 314 } 315 throw new CasterException(value,PdfReader.class); 316 } 317 318 319 /*public static void main(String[] args) throws IOException { 320 321 322 323 PdfReader pr = new PdfReader("/Users/mic/Projects/Lucee/webroot/jm/test/tags/pdf/Parallels.pdf"); 324 List bm = SimpleBookmark.getBookmark(pr); 325 print.out(bm); 326 ByteArrayOutputStream os = new ByteArrayOutputStream(); 327 try { 328 SimpleBookmark.exportToXML(bm, os, "UTF-8",false); 329 } 330 finally { 331 IOUtil.closeEL(os); 332 } 333 print.out("*********************************"); 334 print.out(IOUtil.toString(os.toByteArray(), "UTF-8")); 335 }*/ 336 337 public static Image toImage(byte[] input,int page) throws PageException, IOException { 338 return PDF2Image.getInstance().toImage(input, page); 339 } 340 341 public static void writeImages(byte[] input,Set pages,Resource outputDirectory, String prefix, 342 String format, int scale, boolean overwrite, boolean goodQuality,boolean transparent) throws PageException, IOException { 343 PDF2Image.getInstance().writeImages(input, pages, outputDirectory, prefix, format, scale, overwrite, goodQuality, transparent); 344 } 345 346 public static Object extractText(PDFDocument doc, Set<Integer> pageNumbers) throws IOException, CryptographyException, InvalidPasswordException { 347 PDDocument pdDoc = doc.toPDDocument(); 348 //PDPageNode pages = pdDoc.getDocumentCatalog().getPages(); 349 //pages. 350 //pdDoc.getDocumentCatalog(). 351 352 /*Iterator<Integer> it = pageNumbers.iterator(); 353 int p; 354 while(it.hasNext()){ 355 p=it.next().intValue(); 356 357 pdDoc.getDocumentCatalog().getPages() 358 } 359 */ 360 361 //print.o(pages); 362 363 364 365 //pdDoc. 366 367 368 //PDFTextStripperByArea stripper = new PDFTextStripperByArea(); 369 //PDFHighlighter stripper = new PDFHighlighter(); 370 PDFText2HTML stripper = new PDFText2HTML(); 371 //PDFTextStripper stripper = new PDFTextStripper(); 372 StringWriter writer = new StringWriter(); 373 stripper.writeText(pdDoc, writer); 374 375 376 return writer.toString(); 377 } 378}