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