001    package railo.commons.pdf;
002    
003    import java.awt.Dimension;
004    import java.awt.Insets;
005    import java.io.ByteArrayInputStream;
006    import java.io.ByteArrayOutputStream;
007    import java.io.IOException;
008    import java.io.InputStream;
009    import java.io.InputStreamReader;
010    import java.io.OutputStream;
011    import java.io.StringReader;
012    import java.net.MalformedURLException;
013    import java.net.URL;
014    
015    import org.w3c.dom.Document;
016    import org.w3c.dom.Element;
017    import org.xml.sax.InputSource;
018    import org.xml.sax.SAXException;
019    
020    import railo.commons.io.IOUtil;
021    import railo.commons.io.SystemUtil;
022    import railo.commons.io.res.ContentType;
023    import railo.commons.io.res.Resource;
024    import railo.commons.io.res.util.ResourceUtil;
025    import railo.commons.lang.HTMLEntities;
026    import railo.commons.lang.StringUtil;
027    import railo.commons.net.HTTPUtil;
028    import railo.commons.net.http.HTTPEngine;
029    import railo.commons.net.http.HTTPResponse;
030    import railo.runtime.Info;
031    import railo.runtime.PageContext;
032    import railo.runtime.config.ConfigWeb;
033    import railo.runtime.exp.ExpressionException;
034    import railo.runtime.exp.PageException;
035    import railo.runtime.functions.system.ContractPath;
036    import railo.runtime.functions.system.GetDirectoryFromPath;
037    import railo.runtime.net.http.ReqRspUtil;
038    import railo.runtime.net.proxy.ProxyData;
039    import railo.runtime.net.proxy.ProxyDataImpl;
040    import railo.runtime.op.Caster;
041    import railo.runtime.text.xml.XMLCaster;
042    import railo.runtime.text.xml.XMLUtil;
043    import railo.runtime.type.scope.CGIImpl;
044    import railo.runtime.type.util.ListUtil;
045    import railo.runtime.util.URLResolver;
046    
047    public final class PDFDocument {
048    
049            // PageType
050        public static final Dimension PAGETYPE_ISOB5 = new Dimension(501, 709);
051        public static final Dimension PAGETYPE_ISOB4 = new Dimension(709, 1002);
052        public static final Dimension PAGETYPE_ISOB3 = new Dimension(1002, 1418);
053        public static final Dimension PAGETYPE_ISOB2 = new Dimension(1418, 2004);
054        public static final Dimension PAGETYPE_ISOB1 = new Dimension(2004, 2836);
055        public static final Dimension PAGETYPE_ISOB0 = new Dimension(2836, 4008);
056        public static final Dimension PAGETYPE_HALFLETTER = new Dimension(396, 612);
057        public static final Dimension PAGETYPE_LETTER = new Dimension(612, 792);
058        public static final Dimension PAGETYPE_TABLOID = new Dimension(792, 1224);
059        public static final Dimension PAGETYPE_LEDGER = new Dimension(1224, 792);
060        public static final Dimension PAGETYPE_NOTE = new Dimension(540, 720);
061        public static final Dimension PAGETYPE_LEGAL = new Dimension(612, 1008);
062            
063        public static final Dimension PAGETYPE_A10 = new Dimension(74, 105);
064        public static final Dimension PAGETYPE_A9 = new Dimension(105, 148);
065        public static final Dimension PAGETYPE_A8 = new Dimension(148, 210);
066        public static final Dimension PAGETYPE_A7 = new Dimension(210, 297);
067        public static final Dimension PAGETYPE_A6 = new Dimension(297, 421);
068        public static final Dimension PAGETYPE_A5 = new Dimension(421, 595);
069        public static final Dimension PAGETYPE_A4 = new Dimension(595, 842);
070        public static final Dimension PAGETYPE_A3 = new Dimension(842, 1190);
071        public static final Dimension PAGETYPE_A2 = new Dimension(1190, 1684);
072        public static final Dimension PAGETYPE_A1 = new Dimension(1684, 2384);
073        public static final Dimension PAGETYPE_A0 = new Dimension(2384, 3370);
074            
075            
076            public static final Dimension PAGETYPE_B4=new Dimension(708,1000);
077            public static final Dimension PAGETYPE_B5=new Dimension(499,708);
078            public static final Dimension PAGETYPE_B4_JIS=new Dimension(728,1031);
079            public static final Dimension PAGETYPE_B5_JIS=new Dimension(516,728);
080            public static final Dimension PAGETYPE_CUSTOM=new Dimension(1,1);
081                            
082            // encryption
083            public static final int ENC_NONE=0;
084            public static final int ENC_40BIT=1;
085            public static final int ENC_128BIT=2;
086            
087            //      fontembed 
088            public static final int FONT_EMBED_NO=0;
089            public static final int FONT_EMBED_YES=1;
090            public static final int FONT_EMBED_SELECCTIVE=FONT_EMBED_YES;
091    
092            // unit
093            public static final double UNIT_FACTOR_CM=85d/3d;// =28.333333333333333333333333333333333333333333;
094            public static final double UNIT_FACTOR_IN=UNIT_FACTOR_CM*2.54;
095            public static final double UNIT_FACTOR_POINT=1;
096                    
097            // margin init
098            private static final int MARGIN_INIT=36;
099    
100            // mimetype
101            private static final int MIMETYPE_TEXT_HTML = 0;
102            private static final int MIMETYPE_TEXT = 1;
103            private static final int MIMETYPE_IMAGE = 2;  
104            private static final int MIMETYPE_APPLICATION = 3;
105            private static final int MIMETYPE_OTHER = -1;
106            private static final String USER_AGENT = "Railo "+Info.getVersionAsString()+" "+Info.getStateAsString();
107            
108                    
109            private double margintop=-1;
110            private double marginbottom=-1;
111            private double marginleft=-1;
112            private double marginright=-1;
113    
114            private int mimetype=MIMETYPE_TEXT_HTML;
115            private String strMimetype=null;
116            private String strCharset=null;
117    
118            private boolean backgroundvisible;
119            private boolean fontembed=true;
120            private PDFPageMark header;
121            private PDFPageMark footer;
122            
123            private String proxyserver;
124            private int proxyport=80;
125            private String proxyuser=null;
126            private String proxypassword="";
127    
128            private String src=null;
129            private Resource srcfile=null;
130            private String body;
131            //private boolean isEvaluation;
132            private String name;
133            private String authUser;
134            private String authPassword;
135            private String userAgent=USER_AGENT;
136            private boolean localUrl;
137            private boolean bookmark; 
138            private boolean htmlBookmark;
139            
140            
141            
142            public PDFDocument(){
143                    //this.isEvaluation=isEvaluation;
144                
145            }
146    
147    
148            public void setHeader(PDFPageMark header) {
149                    this.header=header;
150            }
151    
152            public void setFooter(PDFPageMark footer) {
153                    this.footer=footer;
154            }
155            
156    
157            /**
158             * @param marginbottom the marginbottom to set
159             */
160            public void setMarginbottom(double marginbottom) {
161                    this.marginbottom = marginbottom;
162            }
163    
164            /**
165             * @param marginleft the marginleft to set
166             */
167            public void setMarginleft(double marginleft) {
168                    this.marginleft = marginleft;
169            }
170    
171            /**
172             * @param marginright the marginright to set
173             */
174            public void setMarginright(double marginright) {
175                    this.marginright = marginright;
176            }
177    
178            /**
179             * @param margintop the margintop to set
180             */
181            public void setMargintop(double margintop) {
182                    this.margintop = margintop;
183            }
184            
185            /**
186             * @param strMimetype the mimetype to set
187             */
188            public void setMimetype(String strMimetype) {
189                    strMimetype = strMimetype.toLowerCase().trim();
190                    this.strMimetype=strMimetype;
191                    // mimetype
192                    if(strMimetype.startsWith("text/html"))                 mimetype=MIMETYPE_TEXT_HTML;
193                    else if(strMimetype.startsWith("text/"))                mimetype=MIMETYPE_TEXT;
194                    else if(strMimetype.startsWith("image/"))               mimetype=MIMETYPE_IMAGE;
195                    else if(strMimetype.startsWith("application/"))         mimetype=MIMETYPE_APPLICATION;
196                    else mimetype=MIMETYPE_OTHER;
197                    
198                    // charset
199                    String[] arr = ListUtil.listToStringArray(strMimetype, ';');
200                    if(arr.length>=2) {
201                            this.strMimetype=arr[0].trim();
202                            for(int i=1;i<arr.length;i++) {
203                                    String[] item = ListUtil.listToStringArray(arr[i], '=');
204                                    if(item.length==1) {
205                                            strCharset=item[0].trim();
206                                            break;
207                                    }
208                                    else if(item.length==2 && item[0].trim().equals("charset")) {
209                                            strCharset=item[1].trim();
210                                            break;
211                                    }
212                            }
213                    }
214            }
215            
216            /** set the value proxyserver
217            *  Host name or IP address of a proxy server.
218            * @param proxyserver value to set
219            **/
220            public void setProxyserver(String proxyserver)  {
221                    this.proxyserver=proxyserver;
222            }
223            
224            /** set the value proxyport
225            *  The port number on the proxy server from which the object is requested. Default is 80. When 
226            *       used with resolveURL, the URLs of retrieved documents that specify a port number are automatically 
227            *       resolved to preserve links in the retrieved document.
228            * @param proxyport value to set
229            **/
230            public void setProxyport(int proxyport) {
231                    this.proxyport=proxyport;
232            }
233    
234            /** set the value username
235            *  When required by a proxy server, a valid username.
236            * @param proxyuser value to set
237            **/
238            public void setProxyuser(String proxyuser)      {
239                    this.proxyuser=proxyuser;
240            }
241    
242            /** set the value password
243            *  When required by a proxy server, a valid password.
244            * @param proxypassword value to set
245            **/
246            public void setProxypassword(String proxypassword)      {
247                    this.proxypassword=proxypassword;
248            }
249    
250            /**
251             * @param src
252             * @throws PDFException
253             */
254            public void setSrc(String src) throws PDFException {
255                    if(srcfile!=null) throw new PDFException("You cannot specify both the src and srcfile attributes");
256                    this.src = src;
257            }
258            
259    
260            /**
261             * @param srcfile the srcfile to set
262             * @throws PDFException 
263             */
264            public void setSrcfile(Resource srcfile) throws PDFException {
265                    if(src!=null) throw new PDFException("You cannot specify both the src and srcfile attributes");
266                    this.srcfile=srcfile;
267            }
268    
269            public void setBody(String body) {
270                    this.body=body;
271            }
272    
273            public byte[] render(Dimension dimension,double unitFactor, PageContext pc,boolean generateOutlines) throws PageException, IOException {
274                    ConfigWeb config = pc.getConfig();
275                    PDF pd4ml = new PDF(config);
276                    pd4ml.generateOutlines(generateOutlines);
277                    pd4ml.enableTableBreaks(true);
278                    pd4ml.interpolateImages(true);
279                    // MUSTMUST DO NOT ENABLE, why this was disabled
280                    pd4ml.adjustHtmlWidth();
281                    
282                    //check size
283                    int mTop =      toPoint(margintop,unitFactor);
284                    int mLeft = toPoint(marginleft,unitFactor);
285                    int mBottom=toPoint(marginbottom,unitFactor);
286                    int mRight=toPoint(marginright,unitFactor);
287                    if((mLeft+mRight)>dimension.getWidth())
288                            throw new ExpressionException("current document width ("+Caster.toString(dimension.getWidth())+" point) is smaller that specified horizontal margin  ("+Caster.toString(mLeft+mRight)+" point).",
289                                            "1 in = "+Math.round(1*UNIT_FACTOR_IN)+" point and 1 cm = "+Math.round(1*UNIT_FACTOR_CM)+" point");
290                    if((mTop+mBottom)>dimension.getHeight())
291                            throw new ExpressionException("current document height ("+Caster.toString(dimension.getHeight())+" point) is smaller that specified vertical margin  ("+Caster.toString(mTop+mBottom)+" point).",
292                                            "1 in = "+Math.round(1*UNIT_FACTOR_IN)+" point and 1 cm = "+Math.round(1*UNIT_FACTOR_CM)+" point");
293                    
294                    // Size
295                    pd4ml.setPageInsets(new Insets(mTop,mLeft,mBottom,mRight));
296                    pd4ml.setPageSize(dimension);
297                    
298                    // header
299                    if(header!=null) pd4ml.setPageHeader(header);
300                    // footer
301                    if(footer!=null) pd4ml.setPageFooter(footer);
302                    
303                    // content
304                    ByteArrayOutputStream baos=new ByteArrayOutputStream();
305                    try {
306                            content(pd4ml,pc,baos);
307                            
308                    }
309                    finally {
310                            IOUtil.closeEL(baos);
311                    }
312                    return baos.toByteArray();
313            }
314    
315            private void content(PDF pd4ml, PageContext pc, OutputStream os) throws PageException, IOException {
316                    ConfigWeb config = pc.getConfig();
317                    pd4ml.useTTF("java:fonts", fontembed);
318                    
319                    // body
320            if(!StringUtil.isEmpty(body,true)) {
321                    // optimize html
322                    URL base = getBase(pc);
323                    try {
324                            body=beautifyHTML(new InputSource(new StringReader(body)),base);
325                            }catch (Throwable t) {}
326                            
327                    pd4ml.render(body, os,base);
328                            
329            }
330            // srcfile
331            else if(srcfile!=null) {
332                    if(StringUtil.isEmpty(strCharset))strCharset=pc.getConfig().getResourceCharset();
333                    
334                            // mimetype
335                            if(StringUtil.isEmpty(strMimetype)) {
336                                    String mt = ResourceUtil.getMimeType(srcfile,null);
337                                    if(mt!=null) setMimetype(mt);
338                            }
339                            InputStream is = srcfile.getInputStream();
340                    try {
341                            
342                            URL base = new URL("file://"+srcfile);
343                            if(!localUrl){
344                                    //PageContext pc = Thread LocalPageContext.get();
345                                    
346                                            String abs = srcfile.getAbsolutePath();
347                                            String contract = ContractPath.call(pc, abs);
348                                            if(!abs.equals(contract)) {
349                                                    base=HTTPUtil.toURL(CGIImpl.getDomain(pc.getHttpServletRequest())+contract);
350                                            }
351    
352                            }
353                            
354                            //URL base = localUrl?new URL("file://"+srcfile):getBase();
355                            render(pd4ml, is,os,base);
356                            } 
357                    catch (Throwable t) {}
358                    finally {
359                            IOUtil.closeEL(is);
360                    }
361            }
362            // src
363            else if(src!=null) {
364                    if(StringUtil.isEmpty(strCharset))strCharset="iso-8859-1";
365                    URL url = HTTPUtil.toURL(src);
366                            
367                            // set Proxy
368                            if(StringUtil.isEmpty(proxyserver) && config.isProxyEnableFor(url.getHost())) {
369                                    ProxyData pd = config.getProxyData();
370                                    proxyserver=pd==null?null:pd.getServer();
371                                    proxyport=pd==null?0:pd.getPort();
372                                    proxyuser=pd==null?null:pd.getUsername();
373                                    proxypassword=pd==null?null:pd.getPassword();
374                            }
375                            
376                            HTTPResponse method = HTTPEngine.get(url, authUser, authPassword, -1,HTTPEngine.MAX_REDIRECT, null, userAgent,
377                                            ProxyDataImpl.getInstance(proxyserver, proxyport, proxyuser, proxypassword),null);
378                            
379                            // mimetype
380                            if(StringUtil.isEmpty(strMimetype)) {
381                                    ContentType ct = method.getContentType();
382                                    if(ct!=null)
383                                            setMimetype(ct.toString());
384                                    
385                            }
386                            InputStream is = new ByteArrayInputStream(method.getContentAsByteArray());
387                            try {
388                                    
389                                    render(pd4ml, is, os,url);
390                            }
391                            finally {
392                                    IOUtil.closeEL(is);
393                            }
394            }
395            else {
396                    pd4ml.render("<html><body> </body></html>", os,null);
397            }
398            }
399    
400            private static String beautifyHTML(InputSource is,URL base) throws ExpressionException, SAXException, IOException {
401                    Document xml = XMLUtil.parse(is,null,true);
402                    patchPD4MLProblems(xml);
403                    
404                    if(base!=null)URLResolver.getInstance().transform(xml, base);
405                    String html = XMLCaster.toHTML(xml);
406                    return html;
407            }
408    
409            private static void patchPD4MLProblems(Document xml) {
410                    Element b = XMLUtil.getChildWithName("body", xml.getDocumentElement());
411                    if(!b.hasChildNodes()){
412                            b.appendChild(xml.createTextNode(" "));
413                    }
414            }
415    
416    
417            private static URL getBase(PageContext pc) throws MalformedURLException {
418                    //PageContext pc = Thread LocalPageContext.get();
419                    if(pc==null)return null;
420                    
421                    String userAgent = pc.getHttpServletRequest().getHeader("User-Agent");
422                    // bug in pd4ml-> html badse definition create a call
423                    if(!StringUtil.isEmpty(userAgent) && userAgent.startsWith("Java"))return null;
424                    
425                    return HTTPUtil.toURL(GetDirectoryFromPath.call(pc, ReqRspUtil.getRequestURL(pc.getHttpServletRequest(), false)));
426            }
427    
428    
429            private void render(PDF pd4ml, InputStream is,OutputStream os, URL base) throws IOException, PageException {
430                    try {
431                            
432                            // text/html
433                            if(mimetype==MIMETYPE_TEXT_HTML) {
434                                    body="";
435                                    
436                                    try {
437                                            InputSource input = new InputSource(IOUtil.getReader(is,strCharset));
438                                            body=beautifyHTML(input,base);
439                                    } 
440                                    catch (Throwable t) {}
441                                    //else if(body==null)body =IOUtil.toString(is,strCharset); 
442                                    pd4ml.render(body, os,base);
443                            }
444                            // text
445                            else if(mimetype==MIMETYPE_TEXT) {
446                                    body =IOUtil.toString(is,strCharset); 
447                                    body="<html><body><pre>"+HTMLEntities.escapeHTML(body)+"</pre></body></html>";
448                                    pd4ml.render(body, os,null);
449                            }
450                            // image
451                            else if(mimetype==MIMETYPE_IMAGE) {
452                                    Resource tmpDir= SystemUtil.getTempDirectory();
453                                    Resource tmp = tmpDir.getRealResource(this+"-"+Math.random());
454                                    IOUtil.copy(is, tmp,true);
455                                    body="<html><body><img src=\"file://"+tmp+"\"></body></html>";
456                                    try {
457                                            pd4ml.render(body, os,null);
458                                    }
459                                    finally {
460                                            tmp.delete();
461                                    }       
462                            }
463                            // Application
464                            else if(mimetype==MIMETYPE_APPLICATION && "application/pdf".equals(strMimetype)) {
465                                    IOUtil.copy(is, os,true,true);
466                            }
467                            else pd4ml.render(new InputStreamReader(is), os);
468                    }
469                    finally {
470                            IOUtil.closeEL(is,os);
471                    }
472            }
473    
474            public static int toPoint(double value,double unitFactor) {
475                    if(value<0) return MARGIN_INIT;
476                    return (int)Math.round(value*unitFactor);
477                    //return r;
478            }
479    
480            public PDFPageMark getHeader() {
481                    return header;
482            }
483            public PDFPageMark getFooter() {
484                    return footer;
485            }
486    
487            public void setFontembed(int fontembed) {
488                    this.fontembed=fontembed!=FONT_EMBED_NO;
489            }
490    
491    
492            /**
493             * @return the name
494             */
495            public String getName() {
496                    return name;
497            }
498    
499    
500            /**
501             * @param name the name to set
502             */
503            public void setName(String name) {
504                    this.name = name;
505            }
506    
507    
508            /**
509             * @return the authUser
510             */
511            public String getAuthUser() {
512                    return authUser;
513            }
514    
515    
516            /**
517             * @param authUser the authUser to set
518             */
519            public void setAuthUser(String authUser) {
520                    this.authUser = authUser;
521            }
522    
523    
524            /**
525             * @return the authPassword
526             */
527            public String getAuthPassword() {
528                    return authPassword;
529            }
530    
531    
532            /**
533             * @param authPassword the authPassword to set
534             */
535            public void setAuthPassword(String authPassword) {
536                    this.authPassword = authPassword;
537            }
538    
539    
540            /**
541             * @return the userAgent
542             */
543            public String getUserAgent() {
544                    return userAgent;
545            }
546    
547    
548            /**
549             * @param userAgent the userAgent to set
550             */
551            public void setUserAgent(String userAgent) {
552                    this.userAgent = userAgent;
553            }
554    
555    
556            /**
557             * @return the proxyserver
558             */
559            public String getProxyserver() {
560                    return proxyserver;
561            }
562    
563    
564            /**
565             * @return the proxyport
566             */
567            public int getProxyport() {
568                    return proxyport;
569            }
570    
571    
572            /**
573             * @return the proxyuser
574             */
575            public String getProxyuser() {
576                    return proxyuser;
577            }
578    
579    
580            /**
581             * @return the proxypassword
582             */
583            public String getProxypassword() {
584                    return proxypassword;
585            }
586    
587    
588            public boolean hasProxy() {
589                    return !StringUtil.isEmpty(proxyserver);
590            }
591    
592    
593            /**
594             * @return the localUrl
595             */
596            public boolean getLocalUrl() {
597                    return localUrl;
598            }
599    
600    
601            /**
602             * @param localUrl the localUrl to set
603             */
604            public void setLocalUrl(boolean localUrl) {
605                    this.localUrl = localUrl;
606            }
607    
608    
609            /**
610             * @return the bookmark
611             */
612            public boolean getBookmark() {
613                    return bookmark;
614            }
615    
616    
617            /**
618             * @param bookmark the bookmark to set
619             */
620            public void setBookmark(boolean bookmark) {
621                    this.bookmark = bookmark;
622            }
623    
624    
625            /**
626             * @return the htmlBookmark
627             */
628            public boolean getHtmlBookmark() {
629                    return htmlBookmark;
630            }
631    
632    
633            /**
634             * @param htmlBookmark the htmlBookmark to set
635             */
636            public void setHtmlBookmark(boolean htmlBookmark) {
637                    this.htmlBookmark = htmlBookmark;
638            }
639    
640    }