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.tag;
020
021import java.awt.Color;
022import java.awt.image.BufferedImage;
023import java.io.ByteArrayInputStream;
024import java.io.ByteArrayOutputStream;
025import java.io.IOException;
026import java.io.OutputStream;
027import java.util.ArrayList;
028import java.util.HashMap;
029import java.util.Iterator;
030import java.util.Map.Entry;
031import java.util.Set;
032
033import lucee.commons.io.IOUtil;
034import lucee.commons.io.res.Resource;
035import lucee.commons.io.res.filter.ExtensionResourceFilter;
036import lucee.commons.io.res.filter.ResourceFilter;
037import lucee.commons.io.res.util.ResourceUtil;
038import lucee.commons.io.res.util.WildCardFilter;
039import lucee.commons.lang.StringUtil;
040import lucee.runtime.exp.ApplicationException;
041import lucee.runtime.exp.CasterException;
042import lucee.runtime.exp.ExpressionException;
043import lucee.runtime.exp.PageException;
044import lucee.runtime.ext.tag.BodyTagImpl;
045import lucee.runtime.op.Caster;
046import lucee.runtime.op.Decision;
047import lucee.runtime.text.pdf.PDFDocument;
048import lucee.runtime.text.pdf.PDFUtil;
049import lucee.runtime.type.Array;
050import lucee.runtime.type.Collection.Key;
051import lucee.runtime.type.Struct;
052import lucee.runtime.type.util.KeyConstants;
053import lucee.runtime.type.util.ListUtil;
054
055import org.apache.oro.text.regex.MalformedPatternException;
056import org.pdfbox.exceptions.CryptographyException;
057import org.pdfbox.exceptions.InvalidPasswordException;
058
059import com.lowagie.text.DocumentException;
060import com.lowagie.text.Image;
061import com.lowagie.text.pdf.PdfContentByte;
062import com.lowagie.text.pdf.PdfGState;
063import com.lowagie.text.pdf.PdfImportedPage;
064import com.lowagie.text.pdf.PdfReader;
065import com.lowagie.text.pdf.PdfStamper;
066import com.lowagie.text.pdf.PdfWriter;
067import com.lowagie.text.pdf.SimpleBookmark;
068
069public class PDF extends BodyTagImpl  {
070
071        private static final int ACTION_ADD_WATERMARK = 0;
072        private static final int ACTION_DELETE_PAGES = 1;
073        private static final int ACTION_GET_INFO = 2;
074        private static final int ACTION_MERGE = 3;
075        private static final int ACTION_PROCESSDDX = 5;
076        private static final int ACTION_PROTECT = 5;
077        private static final int ACTION_READ = 6;
078        private static final int ACTION_REMOVE_WATERMARK = 7;
079        private static final int ACTION_SET_INFO = 8;
080        private static final int ACTION_THUMBNAIL = 9;
081        private static final int ACTION_WRITE = 10;
082        private static final int ACTION_EXTRACT_TEXT = 11;
083
084        
085        private static final String FORMAT_JPG ="jpg";
086        private static final String FORMAT_TIFF = "tiff";
087        private static final String FORMAT_PNG = "png";
088        
089        private static final int ORDER_TIME = 0;
090        private static final int ORDER_NAME = 1;
091        
092        private static final int RESOLUTION_HIGH = 0;
093        private static final int RESOLUTION_LOW = 1;
094
095        private static final int SAVE_OPTION_FULL = 0;
096        private static final int SAVE_OPTION_INCREMENTAL = 1;
097        private static final int SAVE_OPTION_LINEAR = 2;
098        
099        private static final int TYPE_STRING = 1;
100        private static final int TYPE_XML = 2;
101    
102        
103        private static final ExtensionResourceFilter PDF_FILTER = new ExtensionResourceFilter("pdf");
104        private static final int UNDEFINED = Integer.MIN_VALUE;
105        
106        
107        
108        private int action=ACTION_PROCESSDDX;
109        private boolean ascending =false;
110        private Object copyFrom=null;
111        private String ddxFile=null;
112        private Resource destination=null;
113        private Resource directory=null;
114        private int encrypt=PDFUtil.ENCRYPT_RC4_128;
115        private boolean flatten=false;
116        private boolean foreground=false;
117        private String format=FORMAT_JPG;
118        private Object image=null;
119        private Struct info=null;
120        private Struct inputFiles=null;
121        private Struct outputFiles=null;
122        private boolean isBase64=false;
123        private boolean keepBookmark=false;
124        private String name=null;
125        private String newOwnerPassword=null;
126        private String newUserPassword=null;
127        private float opacity=0.3F;
128        private int order=ORDER_TIME;
129        private boolean overwrite=false;
130        private String pages=null;
131        private String password=null;
132        private int permissions=0;
133        private String position=null;
134        private int resolution=RESOLUTION_HIGH;
135        private float rotation=0;
136        private int saveOption=SAVE_OPTION_FULL;
137        private int scale=25;
138        private boolean showOnPrint=false;
139        private Object source =null;
140        private boolean stopOnError=false;
141        private boolean transparent=false;
142        private char version=0;
143        private java.util.List<PDFParamBean> params;
144        private ResourceFilter filter=null;
145        private String imagePrefix=null;
146        private int type=TYPE_XML;
147        
148        @Override
149        public void release() {
150                super.release();
151                action=ACTION_PROCESSDDX;
152                ascending =false;
153                copyFrom=null;
154                ddxFile=null;
155                destination=null;
156                directory=null;
157                encrypt=PDFUtil.ENCRYPT_RC4_128;
158                flatten=false;
159                foreground=false;
160                format=FORMAT_JPG;
161                image=null;
162                info=null;
163                inputFiles=null;
164                outputFiles=null;
165                isBase64=false;
166                keepBookmark=false;
167                name=null;
168                newOwnerPassword=null;
169                newUserPassword=null;
170                opacity=0.3F;
171                order=ORDER_TIME;
172                overwrite=false;
173                pages=null;
174                password=null;
175                permissions=0;
176                position=null;
177                resolution=RESOLUTION_HIGH;
178                rotation=0;
179                saveOption=SAVE_OPTION_FULL;
180                scale=25;
181                showOnPrint=false;
182                source =null;
183                stopOnError=false;
184                transparent=false;
185                version=0;
186                params=null;
187                filter=null;
188                imagePrefix=null;
189                type=TYPE_XML;
190        }
191        
192        
193        
194        /**
195         * @param imagePrefix the imagePrefix to set
196         */
197        public void setImageprefix(String imagePrefix) {
198                this.imagePrefix = imagePrefix;
199        }
200
201
202
203        /**
204         * @param action the action to set
205         * @throws ApplicationException 
206         */
207        public void setAction(String strAction) throws ApplicationException {
208                
209                strAction=StringUtil.toLowerCase(strAction.trim());
210                if("addwatermark".equals(strAction))                            action=ACTION_ADD_WATERMARK;
211                else if("add-watermark".equals(strAction))                      action=ACTION_ADD_WATERMARK;
212                else if("add_watermark".equals(strAction))                      action=ACTION_ADD_WATERMARK;
213                else if("deletepages".equals(strAction))                        action=ACTION_DELETE_PAGES;
214                else if("delete-pages".equals(strAction))                       action=ACTION_DELETE_PAGES;
215                else if("delete_pages".equals(strAction))                       action=ACTION_DELETE_PAGES;
216                else if("deletepage".equals(strAction))                         action=ACTION_DELETE_PAGES;
217                else if("delete-page".equals(strAction))                        action=ACTION_DELETE_PAGES;
218                else if("delete_page".equals(strAction))                        action=ACTION_DELETE_PAGES;
219                else if("getinfo".equals(strAction))                            action=ACTION_GET_INFO;
220                else if("get-info".equals(strAction))                           action=ACTION_GET_INFO;
221                else if("get_info".equals(strAction))                           action=ACTION_GET_INFO;
222                else if("merge".equals(strAction))                                      action=ACTION_MERGE;
223                //else if("processddx".equals(strAction))                               action=ACTION_PROCESSDDX;
224                //else if("process-ddx".equals(strAction))                      action=ACTION_PROCESSDDX;
225                //else if("process_ddx".equals(strAction))                      action=ACTION_PROCESSDDX;
226                else if("protect".equals(strAction))                            action=ACTION_PROTECT;
227                else if("read".equals(strAction))                                       action=ACTION_READ;
228                else if("removewatermark".equals(strAction))            action=ACTION_REMOVE_WATERMARK;
229                else if("removewater-mark".equals(strAction))           action=ACTION_REMOVE_WATERMARK;
230                else if("removewater_mark".equals(strAction))           action=ACTION_REMOVE_WATERMARK;
231                else if("setinfo".equals(strAction))                            action=ACTION_SET_INFO;
232                else if("set-info".equals(strAction))                           action=ACTION_SET_INFO;
233                else if("set_info".equals(strAction))                           action=ACTION_SET_INFO;
234                else if("thumbnail".equals(strAction))                          action=ACTION_THUMBNAIL;
235                else if("write".equals(strAction))                                      action=ACTION_WRITE;
236                else if("extracttext".equals(strAction))                                        action=ACTION_EXTRACT_TEXT;
237                else if("extract-text".equals(strAction))                                       action=ACTION_EXTRACT_TEXT;
238                else if("extract_text".equals(strAction))                                       action=ACTION_EXTRACT_TEXT;
239                
240                else throw new ApplicationException("invalid action definition ["+strAction+"], valid actions definitions are " +
241                                "[addWatermark,deletePages,getInfo,merge,protect,read,removeWatermark,setInfo,thumbnail,write]");
242                
243        }
244        
245
246        public void setType(String strType) throws ApplicationException {
247                
248                strType=StringUtil.toLowerCase(strType.trim());
249                if("string".equals(strType))                            type=TYPE_STRING;
250                else if("text".equals(strType))                         type=TYPE_STRING;
251                else if("plain".equals(strType))                                type=TYPE_STRING;
252                else if("xml".equals(strType))                          type=TYPE_XML;
253                
254                else throw new ApplicationException("invalid type definition ["+strType+"], valid type definitions are " +
255                                "[string,xml]");
256                
257        }
258        
259        /** 
260        *  sets a filter pattern
261        * @param pattern
262         * @throws PageException 
263        **/
264        public void setFilter(String pattern) throws PageException      {
265            if(pattern.trim().length()>0) {
266            try {
267                this.filter=new WildCardFilter(pattern);
268            } 
269            catch (MalformedPatternException e) {
270                throw Caster.toPageException(e);
271            }
272        }
273        }
274        
275        /**
276         * @param ascending the ascending to set
277         */
278        public void setAscending(boolean ascending) {
279                this.ascending = ascending;
280        }
281        /**
282         * @param copyFrom the copyFrom to set
283         * @throws ExpressionException 
284         */
285        public void setCopyfrom(Object copyFrom) throws ExpressionException {
286                this.copyFrom = copyFrom;//ResourceUtil.toResourceExisting(pageContext, copyFrom);
287        }
288        /**
289         * @param ddxFile the ddxFile to set
290         */
291        public void setDdxfile(String ddxFile) {
292                this.ddxFile = ddxFile;// MUST
293        }
294        /**
295         * @param destination the destination to set
296         */
297        public void setDestination(String destination) {
298                this.destination = ResourceUtil.toResourceNotExisting(pageContext, destination);
299        }
300        /**
301         * @param directory the directory to set
302         * @throws ExpressionException 
303         */
304        public void setDirectory(String directory) throws ExpressionException {
305                this.directory = ResourceUtil.toResourceExisting(pageContext, directory);
306        }
307        /**
308         * @param encrypt the encrypt to set
309         * @throws ApplicationException 
310         */
311        public void setEncrypt(String strEncrypt) throws ApplicationException {
312
313                strEncrypt=StringUtil.toLowerCase(strEncrypt.trim());
314                if("aes128".equals(strEncrypt))                         encrypt=PDFUtil.ENCRYPT_AES_128;        
315                else if("aes-128".equals(strEncrypt))           encrypt=PDFUtil.ENCRYPT_AES_128;        
316                else if("aes_128".equals(strEncrypt))           encrypt=PDFUtil.ENCRYPT_AES_128;        
317                else if("none".equals(strEncrypt))                      encrypt=PDFUtil.ENCRYPT_NONE;           
318                else if("".equals(strEncrypt))                          encrypt=PDFUtil.ENCRYPT_NONE;           
319                else if("rc4128".equals(strEncrypt))            encrypt=PDFUtil.ENCRYPT_RC4_128;                
320                else if("rc4-128".equals(strEncrypt))           encrypt=PDFUtil.ENCRYPT_RC4_128;                
321                else if("rc4_128".equals(strEncrypt))           encrypt=PDFUtil.ENCRYPT_RC4_128;                
322                else if("rc4128m".equals(strEncrypt))           encrypt=PDFUtil.ENCRYPT_RC4_128M;               
323                else if("rc4-128m".equals(strEncrypt))          encrypt=PDFUtil.ENCRYPT_RC4_128M;               
324                else if("rc4_128m".equals(strEncrypt))          encrypt=PDFUtil.ENCRYPT_RC4_128M;               
325                else if("rc440".equals(strEncrypt))                     encrypt=PDFUtil.ENCRYPT_RC4_40;         
326                else if("rc4-40".equals(strEncrypt))            encrypt=PDFUtil.ENCRYPT_RC4_40;         
327                else if("rc4_40".equals(strEncrypt))            encrypt=PDFUtil.ENCRYPT_RC4_40;         
328                
329                
330                else throw new ApplicationException("invalid encrypt definition ["+strEncrypt+"], valid encrypt definitions are " +
331                                "[aes_128,none,rc4_128,rc4_128m,rc4_40]");
332        }
333        /**
334         * @param flatten the flatten to set
335         */
336        public void setFlatten(boolean flatten) {
337                this.flatten = flatten;
338        }
339        /**
340         * @param foreground the foreground to set
341         */
342        public void setForeground(boolean foreground) {
343                this.foreground = foreground;
344        }
345        /**
346         * @param format the format to set
347         * @throws ApplicationException 
348         */
349        public void setFormat(String strFormat) throws ApplicationException {
350                strFormat=StringUtil.toLowerCase(strFormat.trim());
351                if("jpg".equals(strFormat))                                     format=FORMAT_JPG;      
352                else if("jpeg".equals(strFormat))                       format=FORMAT_JPG;      
353                else if("jpe".equals(strFormat))                        format=FORMAT_JPG;      
354                else if("tiff".equals(strFormat))                       format=FORMAT_TIFF;
355                else if("tif".equals(strFormat))                        format=FORMAT_TIFF;     
356                else if("png".equals(strFormat))                        format=FORMAT_PNG;      
357                
358                else throw new ApplicationException("invalid format definition ["+strFormat+"], valid format definitions are " +
359                                "[jpg,tiff,png]");
360        }
361        /**
362         * @param image the image to set
363         */
364        public void setImage(Object image) {
365                this.image = image;// MUST
366        }
367        /**
368         * @param prefix the prefix to set
369         */
370        public void setPrefix(String prefix) {
371                this.imagePrefix = prefix;
372        }
373        /**
374         * @param info the info to set
375         */
376        public void setInfo(Struct info) {
377                this.info = info;
378        }
379        /**
380         * @param inputFiles the inputFiles to set
381         */
382        public void setInputfiles(Struct inputFiles) {
383                this.inputFiles = inputFiles;
384        }
385        /**
386         * @param outputFiles the outputFiles to set
387         */
388        public void setOutputfiles(Struct outputFiles) {
389                this.outputFiles = outputFiles;
390        }
391        /**
392         * @param isBase64 the isBase64 to set
393         */
394        public void setIsbase64(boolean isBase64) {
395                this.isBase64 = isBase64;
396        }
397        /**
398         * @param keepBookmark the keepBookmark to set
399         */
400        public void setKeepbookmark(boolean keepBookmark) {
401                this.keepBookmark = keepBookmark;
402        }
403        /**
404         * @param name the name to set
405         */
406        public void setName(String name) {
407                this.name = name;
408        }
409        /**
410         * @param newOwnerPassword the newOwnerPassword to set
411         */
412        public void setNewownerpassword(String newOwnerPassword) {
413                this.newOwnerPassword = newOwnerPassword;
414        }
415        /**
416         * @param newUserPassword the newUserPassword to set
417         */
418        public void setNewuserpassword(String newUserPassword) {
419                this.newUserPassword = newUserPassword;
420        }
421        /**
422         * @param opacity the opacity to set
423         * @throws ApplicationException 
424         */
425        public void setOpacity(double opacity) throws ApplicationException {
426                if(opacity<0 || opacity>10) 
427                        throw new ApplicationException("invalid opacity definition ["+Caster.toString(opacity)+"], value should be in range from 0 to 10");
428                this.opacity = (float) (opacity/10);
429        }
430        /**
431         * @param order the order to set
432         * @throws ApplicationException 
433         */
434        public void setOrder(String strOrder) throws ApplicationException {
435                strOrder=StringUtil.toLowerCase(strOrder.trim());
436                if("name".equals(strOrder))                                     order=ORDER_NAME;       
437                else if("time".equals(strOrder))                        order=ORDER_TIME;       
438                
439                else throw new ApplicationException("invalid order definition ["+strOrder+"], valid order definitions are " +
440                                "[name,time]");
441        }
442        /**
443         * @param overwrite the overwrite to set
444         */
445        public void setOverwrite(boolean overwrite) {
446                this.overwrite = overwrite;
447        }
448        /**
449         * @param pages the pages to set
450         */
451        public void setPages(String pages) {
452                this.pages = pages;
453        }
454        /**
455         * @param password the password to set
456         */
457        public void setPassword(String password) {
458                this.password = password;
459        }
460        /**
461         * @param permissions the permissions to set
462         * @throws PageException 
463         */
464        public void setPermissions(String strPermissions) throws PageException {
465                permissions=PDFUtil.toPermissions(strPermissions);
466        }       
467        /**
468         * @param position the position to set
469         */
470        public void setPosition(String position) {
471                this.position = position;//MUST
472        }
473        /**
474         * @param resolution the resolution to set
475         * @throws ApplicationException 
476         */
477        public void setResolution(String strResolution) throws ApplicationException {
478                strResolution=StringUtil.toLowerCase(strResolution.trim());
479                if("low".equals(strResolution))                 resolution=RESOLUTION_LOW;      
480                else if("high".equals(strResolution))   resolution=RESOLUTION_HIGH;     
481                
482                else throw new ApplicationException("invalid resolution definition ["+strResolution+"], valid resolution definitions are " +
483                                "[low,high]");
484        }
485        
486        /**
487         * @param rotation the rotation to set
488         */
489        public void setRotation(double rotation) {
490                rotation=rotation%360D;
491                //rotation=rotation/360*6.28318525;
492                
493                this.rotation = (float) rotation;
494        }
495        
496        /**
497         * @param saveOption the saveOption to set
498         * @throws ApplicationException 
499         */
500        public void setSaveoption(String strSaveOption) throws ApplicationException {
501                strSaveOption=StringUtil.toLowerCase(strSaveOption.trim());
502                if("full".equals(strSaveOption))                        saveOption=SAVE_OPTION_FULL;    
503                else if("incremental".equals(strSaveOption))saveOption=SAVE_OPTION_INCREMENTAL; 
504                else if("linear".equals(strSaveOption))         saveOption=SAVE_OPTION_LINEAR;  
505                
506                else throw new ApplicationException("invalid saveOption definition ["+strSaveOption+"], valid saveOption definitions are " +
507                                "[full,linear,incremental]");
508        }
509        
510        /**
511         * @param scale the scale to set
512         * @throws ApplicationException 
513         */
514        public void setScale(double scale) throws ApplicationException {
515                //if(scale<1 || scale>1000)  this check is now done inside PDF2IMage implementation
516                //      throw new ApplicationException("invalid scale definition ["+Caster.toString(scale)+"], value should be in range from 1 to 100");
517                this.scale = (int) scale;
518        }
519        
520        /**
521         * @param showOnPrint the showOnPrint to set
522         */
523        public void setShowonprint(boolean showOnPrint) {
524                this.showOnPrint = showOnPrint;
525        }
526        
527        /**
528         * @param source the source to set
529         */
530        public void setSource(Object source) {
531                this.source = source;
532        }
533        /**
534         * @param stopOnError the stopOnError to set
535         */
536        public void setStoponerror(boolean stopOnError) {
537                this.stopOnError = stopOnError;
538        }
539        /**
540         * @param transparent the transparent to set
541         */
542        public void setTransparent(boolean transparent) {
543                this.transparent = transparent;
544        }
545        /**
546         * @param version the version to set
547         * @throws ApplicationException 
548         */
549        public void setVersion(double version) throws ApplicationException {
550                if(1.1 == version)                      this.version='1';       
551                else if(1.2 == version)         this.version=PdfWriter.VERSION_1_2;     
552                else if(1.3 == version)         this.version=PdfWriter.VERSION_1_3;     
553                else if(1.4 == version)         this.version=PdfWriter.VERSION_1_4;     
554                else if(1.5 == version)         this.version=PdfWriter.VERSION_1_5;     
555                else if(1.6 == version)         this.version=PdfWriter.VERSION_1_6;     
556                
557                else throw new ApplicationException("invalid version definition ["+Caster.toString(version)+"], valid version definitions are " +
558                                "[1.1, 1.2, 1.3, 1.4, 1.5, 1.6]");
559        }
560        
561        @Override
562        public int doStartTag() throws PageException    {
563                // RR SerialNumber sn = pageContext.getConfig().getSerialNumber();
564            //if(sn.getVersion()==SerialNumber.VERSION_COMMUNITY)
565            //    throw new SecurityException("no access to this functionality with the "+sn.getStringVersion()+" version of lucee");
566            
567            return EVAL_BODY_BUFFERED;
568        }
569
570        @Override
571        public void doInitBody()        {
572                
573        }
574        
575        @Override
576        public int doAfterBody()        {
577                return SKIP_BODY;
578        }
579        
580        @Override
581        public int doEndTag() throws PageException {
582                try {
583
584                        if(ACTION_ADD_WATERMARK==action)                        doActionAddWatermark();
585                        else if(ACTION_REMOVE_WATERMARK==action)        doActionRemoveWatermark();
586                        else if(ACTION_READ==action)                            doActionRead();
587                        else if(ACTION_WRITE==action)                           doActionWrite();
588                        else if(ACTION_GET_INFO==action)                        doActionGetInfo();
589                        else if(ACTION_SET_INFO==action)                        doActionSetInfo();
590                        else if(ACTION_MERGE==action)                           doActionMerge();
591                        else if(ACTION_DELETE_PAGES==action)            doActionDeletePages();
592                        else if(ACTION_PROTECT==action)                         doActionProtect();
593                        else if(ACTION_THUMBNAIL==action)                       doActionThumbnail();
594                        else if(ACTION_EXTRACT_TEXT==action)            {
595                                if(true)throw new ApplicationException("not supported yet, see https://issues.jboss.org/browse/LUCEE-1559");
596                                doActionExtractText();
597                        }
598                        
599                        //else if(ACTION_PROCESSDDX==action)    throw new ApplicationException("action [processddx] not supported");
600                        
601                        
602                        
603                }
604                catch (Exception e) {
605                        throw Caster.toPageException(e);
606                }       
607                return EVAL_PAGE;
608        }
609        
610        
611        
612        
613        private void doActionWrite() throws PageException, IOException, DocumentException {
614                required("pdf", "write", "source", source);
615                required("pdf", "write", "destination", destination);
616                
617                if(destination.exists() && !overwrite)
618                        throw new ApplicationException("destination file ["+destination+"] already exists");
619                
620                PDFDocument doc = toPDFDocument(source, password, null);
621                //PdfReader pr = doc.getPdfReader();
622                // output
623                boolean destIsSource = doc.getResource()!=null && destination.equals(doc.getResource());
624                
625                OutputStream os=null;
626                if(destIsSource){
627                        os=new ByteArrayOutputStream();
628                }
629                else if(destination!=null) {
630                        os=destination.getOutputStream();
631                }
632                
633                try {   
634                        PDFUtil.concat(new PDFDocument[]{doc}, os, true, true, true,version);
635                }
636                finally {
637                        IOUtil.closeEL(os);
638                        if(os instanceof ByteArrayOutputStream) {
639                                if(destination!=null)IOUtil.copy(new ByteArrayInputStream(((ByteArrayOutputStream)os).toByteArray()), destination,true);// MUST overwrite
640                        }
641                }
642                
643/*      
644                // flatten = "yes|no"
645            // must saveOption = "linear|incremental|full"
646*/
647        }
648
649
650
651        private void doActionThumbnail() throws PageException, IOException, DocumentException {
652                required("pdf", "thumbnail", "source", source);
653
654                PDFDocument doc = toPDFDocument(source, password, null);
655                PdfReader pr = doc.getPdfReader();
656                boolean isEnc=pr.isEncrypted();
657                pr.close();
658                if(isEnc) {
659                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
660                        //PDFUtil.concat(new PDFDocument[]{doc}, baos, true, true, true, (char)0);
661                        PDFUtil.encrypt(doc,baos,null,null,0,PDFUtil.ENCRYPT_NONE);
662                        baos.close();
663                        doc = new PDFDocument(baos.toByteArray(),doc.getResource(),null);
664                }
665                
666                doc.setPages(pages);
667                
668                // scale
669                if(scale<1)
670                        throw new ApplicationException("value of attribute scale ["+scale+"] should be at least 1");
671                
672                // destination
673                if(destination==null)
674                        destination=ResourceUtil.toResourceNotExisting(pageContext, "thumbnails");
675                
676                // imagePrefix
677                if(imagePrefix==null){
678                        Resource res = doc.getResource();
679                        if(res!=null){
680                                String n = res.getName();
681                                int index=n.lastIndexOf('.');
682                                if(index!=-1)imagePrefix=n.substring(0,index);
683                                else imagePrefix=n;
684                        }
685                        else imagePrefix="memory";
686                }
687                
688                // MUST password
689                PDFUtil.writeImages(doc.getRaw(), doc.getPages(), destination, imagePrefix, format, scale,overwrite,
690                                resolution==RESOLUTION_HIGH,transparent);
691                
692                
693                
694        }
695        
696        
697        
698        
699        private void doActionAddWatermark() throws PageException, IOException, DocumentException {
700                required("pdf", "addWatermark", "source", source);
701                if(copyFrom==null && image==null)
702                        throw new ApplicationException("at least one of the following attributes must be defined " +
703                                        "[copyFrom,image]");
704                
705                if(destination!=null && destination.exists() && !overwrite)
706                        throw new ApplicationException("destination file ["+destination+"] already exists");
707                
708                
709                // image
710                Image img=null;
711                if(image!=null) {
712                        lucee.runtime.img.Image ri = lucee.runtime.img.Image.createImage(pageContext,image,false,false,true,null);
713                        img=Image.getInstance(ri.getBufferedImage(),null,false);
714                }
715                // copy From
716                else {
717                        byte[] barr;
718                        try{
719                                Resource res = Caster.toResource(pageContext, copyFrom, true);
720                                barr=IOUtil.toBytes(res);
721                        }
722                        catch(ExpressionException ee) {
723                                barr=Caster.toBinary(copyFrom);
724                        }
725                        img=Image.getInstance(PDFUtil.toImage(barr, 1).getBufferedImage(),null,false);
726                        
727                }
728                
729                // position
730                float x=UNDEFINED,y=UNDEFINED;
731                if(!StringUtil.isEmpty(position)) {
732                        int index=position.indexOf(',');
733                        if(index==-1)
734                                throw new ApplicationException("attribute [position] has an invalid value ["+position+"]," +
735                                                "value should follow one of the following pattern [40,50], [40,] or [,50]");
736                        String strX = position.substring(0,index).trim();
737                        String strY = position.substring(index+1).trim();
738                        if(!StringUtil.isEmpty(strX))x = Caster.toIntValue(strX);
739                        if(!StringUtil.isEmpty(strY))y = Caster.toIntValue(strY);
740                        
741                }
742                
743                
744                PDFDocument doc = toPDFDocument(source, password, null);
745                doc.setPages(pages);
746                PdfReader reader = doc.getPdfReader();
747                reader.consolidateNamedDestinations();
748                boolean destIsSource = destination!=null && doc.getResource()!=null && destination.equals(doc.getResource());
749                java.util.List bookmarks = SimpleBookmark.getBookmark(reader);
750            ArrayList master = new ArrayList(); 
751                if(bookmarks!=null)master.addAll(bookmarks);
752            
753                // output
754                OutputStream os=null;
755                if(!StringUtil.isEmpty(name) || destIsSource){
756                        os=new ByteArrayOutputStream();
757                }
758                else if(destination!=null) {
759                        os=destination.getOutputStream();
760                }
761                
762                try {
763                        
764                        int len = reader.getNumberOfPages();
765                    PdfStamper stamp = new PdfStamper(reader, os);
766                    
767                    if(len >0){
768                        if(x==UNDEFINED || y==UNDEFINED) {
769                                PdfImportedPage first = stamp.getImportedPage(reader, 1);
770                                if(y==UNDEFINED)y=(first.getHeight()-img.getHeight())/2;
771                                if(x==UNDEFINED)x=(first.getWidth()-img.getWidth())/2;          
772                        }
773                        img.setAbsolutePosition(x, y);
774                        //img.setAlignment(Image.ALIGN_JUSTIFIED); ration geht nicht anhand mitte
775                        
776                    }
777                    
778                        // rotation
779                        if(rotation!=0) {
780                                img.setRotationDegrees(rotation);
781                        }
782                        
783                        Set _pages = doc.getPages();
784                    for (int i=1;i<=len;i++) {
785                        if(_pages!=null && !_pages.contains(Integer.valueOf(i))) continue;
786                        PdfContentByte cb =foreground? stamp.getOverContent(i):stamp.getUnderContent(i);
787                        PdfGState gs1 = new PdfGState();
788                        //print.out("op:"+opacity);
789                        gs1.setFillOpacity(opacity);
790                        //gs1.setStrokeOpacity(opacity);
791                        cb.setGState(gs1);
792                        cb.addImage(img);
793                    }
794                    if(bookmarks!=null)stamp.setOutlines(master);
795                    stamp.close();
796                }
797                finally {
798                        IOUtil.closeEL(os);
799                        if(os instanceof ByteArrayOutputStream) {
800                                if(destination!=null)IOUtil.copy(new ByteArrayInputStream(((ByteArrayOutputStream)os).toByteArray()), destination,true);// MUST overwrite
801                                if(!StringUtil.isEmpty(name)){
802                                        pageContext.setVariable(name,new PDFDocument(((ByteArrayOutputStream)os).toByteArray(),password));
803                                }
804                        }
805                }
806        }
807        
808        private void doActionRemoveWatermark() throws PageException, IOException, DocumentException {
809                required("pdf", "removeWatermark", "source", source);
810                
811                if(destination!=null && destination.exists() && !overwrite)
812                        throw new ApplicationException("destination file ["+destination+"] already exists");
813                
814                lucee.runtime.img.Image ri = new lucee.runtime.img.Image(1,1,BufferedImage.TYPE_INT_RGB,Color.BLACK);
815                Image img = Image.getInstance(ri.getBufferedImage(),null,false);
816                img.setAbsolutePosition(1,1);
817                
818                
819                PDFDocument doc = toPDFDocument(source, password, null);
820                doc.setPages(pages);
821                PdfReader reader = doc.getPdfReader();
822
823                boolean destIsSource = destination!=null && doc.getResource()!=null && destination.equals(doc.getResource());
824                java.util.List bookmarks = SimpleBookmark.getBookmark(reader);
825            ArrayList master = new ArrayList(); 
826                if(bookmarks!=null)master.addAll(bookmarks);
827            
828                // output
829                OutputStream os=null;
830                if(!StringUtil.isEmpty(name) || destIsSource){
831                        os=new ByteArrayOutputStream();
832                }
833                else if(destination!=null) {
834                        os=destination.getOutputStream();
835                }
836                
837                try {
838                        int len = reader.getNumberOfPages();
839                    PdfStamper stamp = new PdfStamper(reader, os);
840                        
841                        Set _pages = doc.getPages();
842                    for (int i=1;i<=len;i++) {
843                        if(_pages!=null && !_pages.contains(Integer.valueOf(i))) continue;
844                        PdfContentByte cb =foreground? stamp.getOverContent(i):stamp.getUnderContent(i);
845                        PdfGState gs1 = new PdfGState();
846                        gs1.setFillOpacity(0);
847                        cb.setGState(gs1);
848                        cb.addImage(img);
849                    }
850                    if(bookmarks!=null)stamp.setOutlines(master);
851                    stamp.close();
852                }
853                finally {
854                        IOUtil.closeEL(os);
855                        if(os instanceof ByteArrayOutputStream) {
856                                if(destination!=null)IOUtil.copy(new ByteArrayInputStream(((ByteArrayOutputStream)os).toByteArray()), destination,true);// MUST overwrite
857                                if(!StringUtil.isEmpty(name)){
858                                        pageContext.setVariable(name,new PDFDocument(((ByteArrayOutputStream)os).toByteArray(),password));
859                                }
860                        }
861                }
862        }
863        
864        private void doActionDeletePages() throws PageException, IOException, DocumentException {
865                required("pdf", "deletePage", "pages", pages,true);
866                required("pdf", "deletePage", "source", source);
867                
868                PDFDocument doc = toPDFDocument(source, password, null);
869                doc.setPages(pages);
870                
871                
872                if(destination==null && StringUtil.isEmpty(name)){
873                        if(doc.getResource()==null)
874                                throw new ApplicationException("source is not based on a resource, destination attribute is required");
875                        destination=doc.getResource();
876                }
877                else if(destination!=null && destination.exists() && !overwrite)
878                        throw new ApplicationException("destination file ["+destination+"] already exists");
879                
880                boolean destIsSource = destination!=null && doc.getResource()!=null && destination.equals(doc.getResource());
881                
882                // output
883                OutputStream os=null;
884                if(!StringUtil.isEmpty(name) || destIsSource){
885                        os=new ByteArrayOutputStream();
886                }
887                else if(destination!=null) {
888                        os=destination.getOutputStream();
889                }
890                
891                try {
892                        PDFUtil.concat(new PDFDocument[]{doc}, os, true, true,true,version);
893                }
894                finally {
895                        //if(document!=null)document.close();
896                        IOUtil.closeEL(os);
897                        if(os instanceof ByteArrayOutputStream) {
898                                if(destination!=null)IOUtil.copy(new ByteArrayInputStream(((ByteArrayOutputStream)os).toByteArray()), destination,true);// MUST overwrite
899                                if(!StringUtil.isEmpty(name)){
900                                        pageContext.setVariable(name,new PDFDocument(((ByteArrayOutputStream)os).toByteArray(),password));
901                                }
902                        }
903                }
904        }
905        
906        
907        private void doActionMerge() throws ApplicationException, PageException, IOException, DocumentException {
908                
909                if(source==null && params==null && directory==null)
910                        throw new ApplicationException("at least one of the following constellation must be defined" +
911                                        " attribute source, attribute directory or cfpdfparam child tags");
912                if(destination==null && StringUtil.isEmpty(name,true))
913                        throw new ApplicationException("at least one of the following attributes must be defined " +
914                                        "[destination,name]");
915                if(destination!=null && !overwrite)
916                        throw new ApplicationException("destination file ["+destination+"] already exists");
917                
918                ArrayList docs = new ArrayList();
919                PDFDocument doc;
920                boolean isListing=false;
921                
922                // source
923                if(source!=null) {
924                        if(Decision.isArray(source)) {
925                                Array arr = Caster.toArray(source);
926                                int len = arr.size();
927                                for(int i=1;i<=len;i++) {
928                                        docs.add(doc=toPDFDocument(arr.getE(i),password,null));
929                                        doc.setPages(pages);
930                                }
931                        }
932                        else if(source instanceof String) {
933                                String[] sources = ListUtil.toStringArrayTrim(ListUtil.listToArrayRemoveEmpty((String)source, ','));
934                                for(int i=0;i<sources.length;i++) {
935                                        docs.add(doc=toPDFDocument(sources[i],password,null));
936                                        doc.setPages(pages);
937                                }
938                        }
939                        else docs.add(toPDFDocument(source,password,null));
940                        
941                }
942                boolean destIsSource = false;
943                
944                // params
945                if(directory!=null && !directory.isDirectory()) {
946                        if(!directory.exists())
947                                throw new ApplicationException("defined attribute directory does not exist");
948                        throw new ApplicationException("defined attribute directory is not a directory");
949                }
950                if(params!=null) {
951                        Iterator it = params.iterator();
952                        PDFParamBean param;
953                        while(it.hasNext()) {
954                                param=(PDFParamBean) it.next();
955                                docs.add(doc=toPDFDocument(param.getSource(), param.getPassword(),directory));
956                                doc.setPages(param.getPages());
957                        }
958                }
959                else if(directory!=null) {
960                        isListing=true;
961                        Resource[] children = ResourceUtil.listResources(directory, filter);
962                        
963                        if(ascending) {
964                                for(int i=children.length-1;i>=0;i--) {
965                                        if(destination!=null && children[i].equals(destination))        destIsSource=true;
966                                        docs.add(doc=toPDFDocument(children[i],password,null));
967                                        doc.setPages(pages);
968                                }
969                        }
970                        else {
971                                for(int i=0;i<children.length;i++) {
972                                        if(destination!=null && children[i].equals(destination))        destIsSource=true;
973                                        docs.add(doc=toPDFDocument(children[i],password,null));
974                                        doc.setPages(pages);
975                                }
976                        }
977                        
978                }
979                
980                int doclen=docs.size();
981                if(doclen==0)
982                        throw new ApplicationException("you have to define at leat 1 pdf file");
983                
984                // output
985                OutputStream os=null;
986                if(!StringUtil.isEmpty(name) || destIsSource){
987                        os=new ByteArrayOutputStream();
988                }
989                else if(destination!=null) {
990                        os=destination.getOutputStream();
991                }
992                
993                
994                /*com.lowagie.text.Document document=null;
995                PdfCopy copy=null;
996                PdfReader pr;
997                Set pages;
998                int size;*/
999                
1000                
1001                try {
1002                        if(!isListing)stopOnError=true;
1003                        PDFUtil.concat((PDFDocument[]) docs.toArray(new PDFDocument[docs.size()]), os, keepBookmark, false,stopOnError,version);
1004                        /*
1005                        boolean init=false;
1006                        for(int d=0;d<doclen;d++) {
1007                                doc=(PDFDocument) docs.get(d);
1008                                pages=doc.getPages();
1009                                try {
1010                                        pr=doc.getPdfReader();
1011                                        print.out(pr.getCatalog().getKeys());
1012                                        
1013                                }
1014                                catch(Throwable t) {
1015                                        if(isListing && !stopOnError)continue;
1016                                        throw Caster.toPageException(t);
1017                                }
1018                                print.out("d+"+d);
1019                                if(!init) {
1020                                        init=true;
1021                                        print.out("set");
1022                                        document = new com.lowagie.text.Document(pr.getPageSizeWithRotation(1));
1023                                        copy = new PdfCopy(document,os);
1024                                        document.open();
1025                                }
1026                                size=pr.getNumberOfPages();
1027                                print.out("pages:"+size);
1028                                for(int page=1;page<=size;page++) {
1029                                        if(pages==null || pages.contains(Constants.Integer(page))) {
1030                                                copy.addPage(copy.getImportedPage(pr, page));
1031                                        }
1032                                }
1033                        }*/
1034                }
1035                finally {
1036                        //if(document!=null)document.close();
1037                        IOUtil.closeEL(os);
1038                        if(os instanceof ByteArrayOutputStream) {
1039                                if(destination!=null)IOUtil.copy(new ByteArrayInputStream(((ByteArrayOutputStream)os).toByteArray()), destination,true);// MUST overwrite
1040                                if(!StringUtil.isEmpty(name))pageContext.setVariable(name, ((ByteArrayOutputStream)os).toByteArray());
1041                        }
1042                        
1043                }
1044                
1045                
1046        }
1047        private void doActionRead() throws PageException {
1048                required("pdf", "read", "name", name,true);
1049                required("pdf", "read", "source", source);
1050                
1051                pageContext.setVariable(name, toPDFDocument(source,password,null));
1052        }
1053        
1054        private void doActionProtect() throws PageException, IOException, DocumentException {
1055                required("pdf", "protect", "source", source);
1056                
1057                if(StringUtil.isEmpty(newUserPassword) && StringUtil.isEmpty(newOwnerPassword))
1058                        throw new ApplicationException("at least one of the following attributes must be defined [newUserPassword,newOwnerPassword]");
1059        
1060                
1061                PDFDocument doc = toPDFDocument(source,password,null);
1062                
1063                if(destination==null){
1064                        destination=doc.getResource();
1065                        if(destination==null)
1066                                throw new ApplicationException("source is not based on a resource, destination file is required");
1067                }
1068                else if(destination.exists() && !overwrite)
1069                        throw new ApplicationException("destination file ["+destination+"] already exists");
1070                
1071                boolean destIsSource = doc.getResource()!=null && destination.equals(doc.getResource());
1072                
1073                // output
1074                OutputStream os=null;
1075                if(destIsSource){
1076                        os=new ByteArrayOutputStream();
1077                }
1078                else {
1079                        os=destination.getOutputStream();
1080                }
1081                
1082                
1083                try{
1084                        PDFUtil.encrypt(doc,os,newUserPassword,newOwnerPassword,permissions,encrypt);
1085                }
1086                finally {
1087                        IOUtil.closeEL(os);
1088                        if(os instanceof ByteArrayOutputStream) {
1089                                IOUtil.copy(new ByteArrayInputStream(((ByteArrayOutputStream)os).toByteArray()), destination,true);// MUST overwrite
1090                        }
1091                        
1092                }
1093                
1094        }
1095        
1096
1097        
1098
1099        private void doActionSetInfo() throws PageException, IOException, DocumentException {
1100                required("pdf", "setInfo", "info", info);
1101                required("pdf", "getInfo", "source", source);
1102                
1103                PDFDocument doc = toPDFDocument(source,password,null);
1104                PdfReader pr = doc.getPdfReader();
1105                OutputStream os=null;
1106                try {
1107                        if(destination==null){
1108                                if(doc.getResource()==null)
1109                                        throw new ApplicationException("source is not based on a resource, destination file is required");
1110                                destination=doc.getResource();
1111                        }
1112                        else if(destination.exists() && !overwrite)
1113                                throw new ApplicationException("destination file ["+destination+"] already exists");
1114                        
1115                        PdfStamper stamp = new PdfStamper(pr, os=destination.getOutputStream());
1116                        HashMap moreInfo = new HashMap();
1117            
1118                        //Key[] keys = info.keys();
1119                        Iterator<Entry<Key, Object>> it = info.entryIterator();
1120                        Entry<Key, Object> e;
1121                        while(it.hasNext()) {
1122                                e = it.next();
1123                                moreInfo.put(StringUtil.ucFirst(e.getKey().getLowerString()), Caster.toString(e.getValue()));
1124                        }
1125                        // author
1126                        Object value = info.get(KeyConstants._author, null);    
1127                        if(value!=null)moreInfo.put("Author", Caster.toString(value));
1128                        // keywords
1129                        value = info.get("keywords", null);     
1130                        if(value!=null)moreInfo.put("Keywords", Caster.toString(value));
1131                        // title
1132                        value = info.get(KeyConstants._title, null);    
1133                        if(value!=null)moreInfo.put("Title", Caster.toString(value));
1134                        // subject
1135                        value = info.get(KeyConstants._subject, null);  
1136                        if(value!=null)moreInfo.put("Subject", Caster.toString(value));
1137                        // creator
1138                        value = info.get("creator", null);      
1139                        if(value!=null)moreInfo.put("Creator", Caster.toString(value));
1140                        // trapped
1141                        value = info.get("Trapped", null);      
1142                        if(value!=null)moreInfo.put("Trapped", Caster.toString(value));
1143                        // Created
1144                        value = info.get("Created", null);      
1145                        if(value!=null)moreInfo.put("Created", Caster.toString(value));
1146                        // Language
1147                        value = info.get("Language", null);     
1148                        if(value!=null)moreInfo.put("Language", Caster.toString(value));
1149                        
1150                        
1151                        stamp.setMoreInfo(moreInfo);
1152                        stamp.close();
1153                        
1154                }
1155                finally {
1156                        IOUtil.closeEL(os);
1157                        pr.close();
1158                }
1159        }
1160        
1161        private void doActionGetInfo() throws PageException {
1162                required("pdf", "getInfo", "name", name,true);
1163                required("pdf", "getInfo", "source", source);
1164                
1165                PDFDocument doc = toPDFDocument(source,password,null);
1166                pageContext.setVariable(name, doc.getInfo());
1167                
1168        }
1169        private void doActionExtractText() throws PageException, IOException, CryptographyException, InvalidPasswordException {
1170                required("pdf", "extractText", "name", name,true);
1171                
1172                PDFDocument doc = toPDFDocument(source,password,null);
1173                doc.setPages(pages);
1174                
1175                pageContext.setVariable(name, PDFUtil.extractText(doc,doc.getPages()));
1176                /*
1177                 <cfpdf 
1178 required 
1179    action="extracttext" <!---extract all the words in the PDF.---> 
1180    ***source= "absolute or relative path of the PDF file|PDF document variable| 
1181            cfdocument variable" 
1182    pages = "*" <!----page numbers from where the text needs to be extracted from the 
1183                PDF document---> 
1184
1185optional 
1186    addquads = "add the position or quadrants for the text in the PDF" 
1187    honourspaces = "true|false" 
1188    overwrite = "true" <!---Overwrite the specified object in the PDF document---> 
1189    ***password = "" <!--- PDF document password---> 
1190    type = "string|xml" <!---format in which the text needs to be extracted---> 
1191    one of the following: 
1192    destination = "PDF output file pathname" 
1193    name = "PDF document variable" 
1194    usestructure = "true|false" 
1195                 * */
1196        }
1197        
1198        private Object allowed(boolean encrypted, int permissions, int permission) {
1199                return (!encrypted || (permissions&permission)>0)?"Allowed":"Not Allowed";
1200        }
1201        
1202
1203        private PDFDocument toPDFDocument(Object source,String password, Resource directory) throws PageException {
1204                
1205                if(source instanceof PDFDocument) 
1206                        return (PDFDocument)source;
1207                if(Decision.isBinary(source)){
1208                        return new PDFDocument(Caster.toBinary(source),password);
1209                }
1210                if(source instanceof Resource){ 
1211                        return new PDFDocument((Resource) source,password);
1212                }
1213                if(source instanceof String){
1214                        if(directory!=null) {
1215                                Resource res = directory.getRealResource((String)source);
1216                                if(!res.isFile()){
1217                                        Resource res2 = ResourceUtil.toResourceNotExisting(pageContext, (String)source);
1218                                        if(res2.isFile())
1219                                                res=res2;
1220                                        else 
1221                                                throw new ExpressionException("file or directory "+res+" not exist");
1222                                }
1223                                return new PDFDocument(res,password);   
1224                        }
1225                        return new PDFDocument(ResourceUtil.toResourceExisting(pageContext, (String)source),password);  
1226                }
1227                
1228                throw new CasterException(source,PdfReader.class);
1229        }
1230        
1231        /*private byte[] toBinary(Object source) throws ExpressionException, IOException {
1232                
1233                if(source instanceof PDFDocument) 
1234                        return toBinary(((PDFDocument)source).getResource());
1235                if(Decision.isBinary(source)){
1236                        return Caster.toBinary(source);
1237                }
1238                if(source instanceof Resource){ 
1239                        return IOUtil.toBytes((Resource)source);
1240                }
1241                if(source instanceof String){
1242                        if(directory!=null) {
1243                                Resource res = directory.getRealResource((String)source);
1244                                if(!res.isFile()){
1245                                        Resource res2 = ResourceUtil.toResourceNotExisting(pageContext, (String)source);
1246                                        if(res2.isFile())
1247                                                res=res2;
1248                                        else 
1249                                                throw new ExpressionException("file or directory "+res+" not exist");
1250                                }
1251                                return IOUtil.toBytes(res);     
1252                        }
1253                        return IOUtil.toBytes(ResourceUtil.toResourceExisting(pageContext, (String)source));    
1254                }
1255                
1256                throw new CasterException(source,PdfReader.class);
1257        }*/
1258        
1259        protected void setParam(PDFParamBean param) {
1260                if(params==null)
1261                        params=new ArrayList<PDFParamBean>();
1262                params.add(param);
1263        }
1264        
1265        
1266        
1267
1268}