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}