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.apache.commons.httpclient.Header;
016    import org.apache.commons.httpclient.HttpMethod;
017    import org.w3c.dom.Document;
018    import org.w3c.dom.Element;
019    import org.xml.sax.InputSource;
020    import org.xml.sax.SAXException;
021    
022    import railo.commons.io.IOUtil;
023    import railo.commons.io.SystemUtil;
024    import railo.commons.io.res.Resource;
025    import railo.commons.io.res.util.ResourceUtil;
026    import railo.commons.lang.HTMLEntities;
027    import railo.commons.lang.StringUtil;
028    import railo.commons.net.HTTPUtil;
029    import railo.runtime.Info;
030    import railo.runtime.PageContext;
031    import railo.runtime.config.ConfigWeb;
032    import railo.runtime.exp.ExpressionException;
033    import railo.runtime.exp.PageException;
034    import railo.runtime.functions.system.ContractPath;
035    import railo.runtime.functions.system.GetDirectoryFromPath;
036    import railo.runtime.op.Caster;
037    import railo.runtime.text.xml.XMLCaster;
038    import railo.runtime.text.xml.XMLUtil;
039    import railo.runtime.type.List;
040    import railo.runtime.type.scope.CGIImpl;
041    import railo.runtime.util.URLResolver;
042    
043    public final class PDFDocument {
044            
045    
046            
047        
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 mimetype 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 = List.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 = List.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.getMymeType(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                                    if(pc!=null) {
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                                    proxyserver=config.getProxyServer();
370                                    proxyport=config.getProxyPort();
371                                    proxyuser=config.getProxyUsername();
372                                    proxypassword=config.getProxyPassword();
373                            }
374                            
375                            HttpMethod method = HTTPUtil.invoke(url, authUser, authPassword, -1, null, userAgent,
376                                            proxyserver, 
377                                            proxyport, 
378                                            proxyuser, 
379                                            proxypassword,null);
380                            
381                            // mimetype
382                            if(StringUtil.isEmpty(strMimetype)) {
383                                    Header[] headers = method.getResponseHeaders();
384                                    for(int i=0;i<headers.length;i++) {
385                                            if(headers[i].getName().equalsIgnoreCase("Content-type") && !StringUtil.isEmpty(headers[i].getValue())) {
386                                                    setMimetype(headers[i].getValue());
387                                                    break;
388                                            }
389                                    }
390                            }
391                            InputStream is = 
392                                    new ByteArrayInputStream(method.getResponseBody());
393                                    //method.getResponseBodyAsStream();
394                            try {
395                                    
396                                    render(pd4ml, is, os,url);
397                            }
398                            finally {
399                                    IOUtil.closeEL(is);
400                            }
401            }
402            else {
403                    pd4ml.render("<html><body> </body></html>", os,null);
404            }
405            }
406    
407            private static String beautifyHTML(InputSource is,URL base) throws ExpressionException, SAXException, IOException {
408                    Document xml = XMLUtil.parse(is,null,true);
409                    patchPD4MLProblems(xml);
410                    
411                    if(base!=null)URLResolver.getInstance().transform(xml, base);
412                    String html = XMLCaster.toHTML(xml);
413                    return html;
414            }
415    
416            private static void patchPD4MLProblems(Document xml) {
417                    Element b = XMLUtil.getChildWithName("body", xml.getDocumentElement());
418                    if(!b.hasChildNodes()){
419                            b.appendChild(xml.createTextNode(" "));
420                    }
421            }
422    
423    
424            private static URL getBase(PageContext pc) throws MalformedURLException {
425                    //PageContext pc = Thread LocalPageContext.get();
426                    if(pc==null)return null;
427                    
428                    String userAgent = pc.getHttpServletRequest().getHeader("User-Agent");
429                    // bug in pd4ml-> html badse definition create a call
430                    if(!StringUtil.isEmpty(userAgent) && userAgent.startsWith("Java"))return null;
431                    
432                    return HTTPUtil.toURL(GetDirectoryFromPath.call(pc,CGIImpl.getCurrentURL(pc.getHttpServletRequest())));
433            }
434    
435    
436            private void render(PDF pd4ml, InputStream is,OutputStream os, URL base) throws IOException, PageException {
437                    try {
438                            
439                            // text/html
440                            if(mimetype==MIMETYPE_TEXT_HTML) {
441                                    body="";
442                                    
443                                    try {
444                                            InputSource input = new InputSource(IOUtil.getReader(is,strCharset));
445                                            body=beautifyHTML(input,base);
446                                    } 
447                                    catch (Throwable t) {}
448                                    //else if(body==null)body =IOUtil.toString(is,strCharset); 
449                                    pd4ml.render(body, os,base);
450                            }
451                            // text
452                            else if(mimetype==MIMETYPE_TEXT) {
453                                    body =IOUtil.toString(is,strCharset); 
454                                    body="<html><body><pre>"+HTMLEntities.escapeHTML(body)+"</pre></body></html>";
455                                    pd4ml.render(body, os,null);
456                            }
457                            // image
458                            else if(mimetype==MIMETYPE_IMAGE) {
459                                    Resource tmpDir= SystemUtil.getTempDirectory();
460                                    Resource tmp = tmpDir.getRealResource(this+"-"+Math.random());
461                                    IOUtil.copy(is, tmp,true);
462                                    body="<html><body><img src=\"file://"+tmp+"\"></body></html>";
463                                    try {
464                                            pd4ml.render(body, os,null);
465                                    }
466                                    finally {
467                                            tmp.delete();
468                                    }       
469                            }
470                            // Application
471                            else if(mimetype==MIMETYPE_APPLICATION && "application/pdf".equals(strMimetype)) {
472                                    IOUtil.copy(is, os,true,true);
473                            }
474                            else pd4ml.render(new InputStreamReader(is), os);
475                    }
476                    finally {
477                            IOUtil.closeEL(is,os);
478                    }
479            }
480    
481            public static int toPoint(double value,double unitFactor) {
482                    if(value<0) return MARGIN_INIT;
483                    return (int)Math.round(value*unitFactor);
484                    //return r;
485            }
486    
487            public PDFPageMark getHeader() {
488                    return header;
489            }
490            public PDFPageMark getFooter() {
491                    return footer;
492            }
493    
494            public void setFontembed(int fontembed) {
495                    this.fontembed=fontembed!=FONT_EMBED_NO;
496            }
497    
498    
499            /**
500             * @return the name
501             */
502            public String getName() {
503                    return name;
504            }
505    
506    
507            /**
508             * @param name the name to set
509             */
510            public void setName(String name) {
511                    this.name = name;
512            }
513    
514    
515            /**
516             * @return the authUser
517             */
518            public String getAuthUser() {
519                    return authUser;
520            }
521    
522    
523            /**
524             * @param authUser the authUser to set
525             */
526            public void setAuthUser(String authUser) {
527                    this.authUser = authUser;
528            }
529    
530    
531            /**
532             * @return the authPassword
533             */
534            public String getAuthPassword() {
535                    return authPassword;
536            }
537    
538    
539            /**
540             * @param authPassword the authPassword to set
541             */
542            public void setAuthPassword(String authPassword) {
543                    this.authPassword = authPassword;
544            }
545    
546    
547            /**
548             * @return the userAgent
549             */
550            public String getUserAgent() {
551                    return userAgent;
552            }
553    
554    
555            /**
556             * @param userAgent the userAgent to set
557             */
558            public void setUserAgent(String userAgent) {
559                    this.userAgent = userAgent;
560            }
561    
562    
563            /**
564             * @return the proxyserver
565             */
566            public String getProxyserver() {
567                    return proxyserver;
568            }
569    
570    
571            /**
572             * @return the proxyport
573             */
574            public int getProxyport() {
575                    return proxyport;
576            }
577    
578    
579            /**
580             * @return the proxyuser
581             */
582            public String getProxyuser() {
583                    return proxyuser;
584            }
585    
586    
587            /**
588             * @return the proxypassword
589             */
590            public String getProxypassword() {
591                    return proxypassword;
592            }
593    
594    
595            public boolean hasProxy() {
596                    return !StringUtil.isEmpty(proxyserver);
597            }
598    
599    
600            /**
601             * @return the localUrl
602             */
603            public boolean getLocalUrl() {
604                    return localUrl;
605            }
606    
607    
608            /**
609             * @param localUrl the localUrl to set
610             */
611            public void setLocalUrl(boolean localUrl) {
612                    this.localUrl = localUrl;
613            }
614    
615    
616            /**
617             * @return the bookmark
618             */
619            public boolean getBookmark() {
620                    return bookmark;
621            }
622    
623    
624            /**
625             * @param bookmark the bookmark to set
626             */
627            public void setBookmark(boolean bookmark) {
628                    this.bookmark = bookmark;
629            }
630    
631    
632            /**
633             * @return the htmlBookmark
634             */
635            public boolean getHtmlBookmark() {
636                    return htmlBookmark;
637            }
638    
639    
640            /**
641             * @param htmlBookmark the htmlBookmark to set
642             */
643            public void setHtmlBookmark(boolean htmlBookmark) {
644                    this.htmlBookmark = htmlBookmark;
645            }
646            
647    
648    
649    
650    }