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 }