package railo.commons.pdf;
import java.awt.Dimension;
import java.awt.Insets;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import railo.commons.io.CharsetUtil;
import railo.commons.io.IOUtil;
import railo.commons.io.SystemUtil;
import railo.commons.io.res.ContentType;
import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.HTMLEntities;
import railo.commons.lang.StringUtil;
import railo.commons.net.HTTPUtil;
import railo.commons.net.http.HTTPEngine;
import railo.commons.net.http.HTTPResponse;
import railo.runtime.Info;
import railo.runtime.PageContext;
import railo.runtime.config.ConfigWeb;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.functions.system.ContractPath;
import railo.runtime.functions.system.GetDirectoryFromPath;
import railo.runtime.net.http.ReqRspUtil;
import railo.runtime.net.proxy.ProxyData;
import railo.runtime.net.proxy.ProxyDataImpl;
import railo.runtime.op.Caster;
import railo.runtime.text.xml.XMLCaster;
import railo.runtime.text.xml.XMLUtil;
import railo.runtime.type.scope.CGIImpl;
import railo.runtime.type.util.ListUtil;
import railo.runtime.util.URLResolver;
public final class PDFDocument {
// PageType
public static final Dimension PAGETYPE_ISOB5 = new Dimension(501, 709);
public static final Dimension PAGETYPE_ISOB4 = new Dimension(709, 1002);
public static final Dimension PAGETYPE_ISOB3 = new Dimension(1002, 1418);
public static final Dimension PAGETYPE_ISOB2 = new Dimension(1418, 2004);
public static final Dimension PAGETYPE_ISOB1 = new Dimension(2004, 2836);
public static final Dimension PAGETYPE_ISOB0 = new Dimension(2836, 4008);
public static final Dimension PAGETYPE_HALFLETTER = new Dimension(396, 612);
public static final Dimension PAGETYPE_LETTER = new Dimension(612, 792);
public static final Dimension PAGETYPE_TABLOID = new Dimension(792, 1224);
public static final Dimension PAGETYPE_LEDGER = new Dimension(1224, 792);
public static final Dimension PAGETYPE_NOTE = new Dimension(540, 720);
public static final Dimension PAGETYPE_LEGAL = new Dimension(612, 1008);
public static final Dimension PAGETYPE_A10 = new Dimension(74, 105);
public static final Dimension PAGETYPE_A9 = new Dimension(105, 148);
public static final Dimension PAGETYPE_A8 = new Dimension(148, 210);
public static final Dimension PAGETYPE_A7 = new Dimension(210, 297);
public static final Dimension PAGETYPE_A6 = new Dimension(297, 421);
public static final Dimension PAGETYPE_A5 = new Dimension(421, 595);
public static final Dimension PAGETYPE_A4 = new Dimension(595, 842);
public static final Dimension PAGETYPE_A3 = new Dimension(842, 1190);
public static final Dimension PAGETYPE_A2 = new Dimension(1190, 1684);
public static final Dimension PAGETYPE_A1 = new Dimension(1684, 2384);
public static final Dimension PAGETYPE_A0 = new Dimension(2384, 3370);
public static final Dimension PAGETYPE_B4=new Dimension(708,1000);
public static final Dimension PAGETYPE_B5=new Dimension(499,708);
public static final Dimension PAGETYPE_B4_JIS=new Dimension(728,1031);
public static final Dimension PAGETYPE_B5_JIS=new Dimension(516,728);
public static final Dimension PAGETYPE_CUSTOM=new Dimension(1,1);
// encryption
public static final int ENC_NONE=0;
public static final int ENC_40BIT=1;
public static final int ENC_128BIT=2;
// fontembed
public static final int FONT_EMBED_NO=0;
public static final int FONT_EMBED_YES=1;
public static final int FONT_EMBED_SELECCTIVE=FONT_EMBED_YES;
// unit
public static final double UNIT_FACTOR_CM=85d/3d;// =28.333333333333333333333333333333333333333333;
public static final double UNIT_FACTOR_IN=UNIT_FACTOR_CM*2.54;
public static final double UNIT_FACTOR_POINT=1;
// margin init
private static final int MARGIN_INIT=36;
// mimetype
private static final int MIMETYPE_TEXT_HTML = 0;
private static final int MIMETYPE_TEXT = 1;
private static final int MIMETYPE_IMAGE = 2;
private static final int MIMETYPE_APPLICATION = 3;
private static final int MIMETYPE_OTHER = -1;
private static final String USER_AGENT = "Railo "+Info.getVersionAsString()+" "+Info.getStateAsString();
private double margintop=-1;
private double marginbottom=-1;
private double marginleft=-1;
private double marginright=-1;
private int mimetype=MIMETYPE_TEXT_HTML;
private String strMimetype=null;
private String strCharset=null;
private boolean backgroundvisible;
private boolean fontembed=true;
private PDFPageMark header;
private PDFPageMark footer;
private String proxyserver;
private int proxyport=80;
private String proxyuser=null;
private String proxypassword="";
private String src=null;
private Resource srcfile=null;
private String body;
//private boolean isEvaluation;
private String name;
private String authUser;
private String authPassword;
private String userAgent=USER_AGENT;
private boolean localUrl;
private boolean bookmark;
private boolean htmlBookmark;
public PDFDocument(){
//this.isEvaluation=isEvaluation;
}
public void setHeader(PDFPageMark header) {
this.header=header;
}
public void setFooter(PDFPageMark footer) {
this.footer=footer;
}
/**
* @param marginbottom the marginbottom to set
*/
public void setMarginbottom(double marginbottom) {
this.marginbottom = marginbottom;
}
/**
* @param marginleft the marginleft to set
*/
public void setMarginleft(double marginleft) {
this.marginleft = marginleft;
}
/**
* @param marginright the marginright to set
*/
public void setMarginright(double marginright) {
this.marginright = marginright;
}
/**
* @param margintop the margintop to set
*/
public void setMargintop(double margintop) {
this.margintop = margintop;
}
/**
* @param strMimetype the mimetype to set
*/
public void setMimetype(String strMimetype) {
strMimetype = strMimetype.toLowerCase().trim();
this.strMimetype=strMimetype;
// mimetype
if(strMimetype.startsWith("text/html")) mimetype=MIMETYPE_TEXT_HTML;
else if(strMimetype.startsWith("text/")) mimetype=MIMETYPE_TEXT;
else if(strMimetype.startsWith("image/")) mimetype=MIMETYPE_IMAGE;
else if(strMimetype.startsWith("application/")) mimetype=MIMETYPE_APPLICATION;
else mimetype=MIMETYPE_OTHER;
// charset
String[] arr = ListUtil.listToStringArray(strMimetype, ';');
if(arr.length>=2) {
this.strMimetype=arr[0].trim();
for(int i=1;i<arr.length;i++) {
String[] item = ListUtil.listToStringArray(arr[i], '=');
if(item.length==1) {
strCharset=item[0].trim();
break;
}
else if(item.length==2 && item[0].trim().equals("charset")) {
strCharset=item[1].trim();
break;
}
}
}
}
/** set the value proxyserver
* Host name or IP address of a proxy server.
* @param proxyserver value to set
**/
public void setProxyserver(String proxyserver) {
this.proxyserver=proxyserver;
}
/** set the value proxyport
* The port number on the proxy server from which the object is requested. Default is 80. When
* used with resolveURL, the URLs of retrieved documents that specify a port number are automatically
* resolved to preserve links in the retrieved document.
* @param proxyport value to set
**/
public void setProxyport(int proxyport) {
this.proxyport=proxyport;
}
/** set the value username
* When required by a proxy server, a valid username.
* @param proxyuser value to set
**/
public void setProxyuser(String proxyuser) {
this.proxyuser=proxyuser;
}
/** set the value password
* When required by a proxy server, a valid password.
* @param proxypassword value to set
**/
public void setProxypassword(String proxypassword) {
this.proxypassword=proxypassword;
}
/**
* @param src
* @throws PDFException
*/
public void setSrc(String src) throws PDFException {
if(srcfile!=null) throw new PDFException("You cannot specify both the src and srcfile attributes");
this.src = src;
}
/**
* @param srcfile the srcfile to set
* @throws PDFException
*/
public void setSrcfile(Resource srcfile) throws PDFException {
if(src!=null) throw new PDFException("You cannot specify both the src and srcfile attributes");
this.srcfile=srcfile;
}
public void setBody(String body) {
this.body=body;
}
public byte[] render(Dimension dimension,double unitFactor, PageContext pc,boolean generateOutlines) throws PageException, IOException {
ConfigWeb config = pc.getConfig();
PDF pd4ml = new PDF(config);
pd4ml.generateOutlines(generateOutlines);
pd4ml.enableTableBreaks(true);
pd4ml.interpolateImages(true);
// MUSTMUST DO NOT ENABLE, why this was disabled
pd4ml.adjustHtmlWidth();
//check size
int mTop = toPoint(margintop,unitFactor);
int mLeft = toPoint(marginleft,unitFactor);
int mBottom=toPoint(marginbottom,unitFactor);
int mRight=toPoint(marginright,unitFactor);
if((mLeft+mRight)>dimension.getWidth())
throw new ExpressionException("current document width ("+Caster.toString(dimension.getWidth())+" point) is smaller that specified horizontal margin ("+Caster.toString(mLeft+mRight)+" point).",
"1 in = "+Math.round(1*UNIT_FACTOR_IN)+" point and 1 cm = "+Math.round(1*UNIT_FACTOR_CM)+" point");
if((mTop+mBottom)>dimension.getHeight())
throw new ExpressionException("current document height ("+Caster.toString(dimension.getHeight())+" point) is smaller that specified vertical margin ("+Caster.toString(mTop+mBottom)+" point).",
"1 in = "+Math.round(1*UNIT_FACTOR_IN)+" point and 1 cm = "+Math.round(1*UNIT_FACTOR_CM)+" point");
// Size
pd4ml.setPageInsets(new Insets(mTop,mLeft,mBottom,mRight));
pd4ml.setPageSize(dimension);
// header
if(header!=null) pd4ml.setPageHeader(header);
// footer
if(footer!=null) pd4ml.setPageFooter(footer);
// content
ByteArrayOutputStream baos=new ByteArrayOutputStream();
try {
content(pd4ml,pc,baos);
}
finally {
IOUtil.closeEL(baos);
}
return baos.toByteArray();
}
private void content(PDF pd4ml, PageContext pc, OutputStream os) throws PageException, IOException {
ConfigWeb config = pc.getConfig();
pd4ml.useTTF("java:fonts", fontembed);
// body
if(!StringUtil.isEmpty(body,true)) {
// optimize html
URL base = getBase(pc);
try {
body=beautifyHTML(new InputSource(new StringReader(body)),base);
}catch (Throwable t) {}
pd4ml.render(body, os,base);
}
// srcfile
else if(srcfile!=null) {
if(StringUtil.isEmpty(strCharset))strCharset=pc.getConfig().getResourceCharset();
// mimetype
if(StringUtil.isEmpty(strMimetype)) {
String mt = ResourceUtil.getMimeType(srcfile,null);
if(mt!=null) setMimetype(mt);
}
InputStream is = srcfile.getInputStream();
try {
URL base = new URL("file://"+srcfile);
if(!localUrl){
//PageContext pc = Thread LocalPageContext.get();
String abs = srcfile.getAbsolutePath();
String contract = ContractPath.call(pc, abs);
if(!abs.equals(contract)) {
base=HTTPUtil.toURL(CGIImpl.getDomain(pc.getHttpServletRequest())+contract,true);
}
}
//URL base = localUrl?new URL("file://"+srcfile):getBase();
render(pd4ml, is,os,base);
}
catch (Throwable t) {}
finally {
IOUtil.closeEL(is);
}
}
// src
else if(src!=null) {
if(StringUtil.isEmpty(strCharset))strCharset="iso-8859-1";
URL url = HTTPUtil.toURL(src,true);
// set Proxy
if(StringUtil.isEmpty(proxyserver) && config.isProxyEnableFor(url.getHost())) {
ProxyData pd = config.getProxyData();
proxyserver=pd==null?null:pd.getServer();
proxyport=pd==null?0:pd.getPort();
proxyuser=pd==null?null:pd.getUsername();
proxypassword=pd==null?null:pd.getPassword();
}
HTTPResponse method = HTTPEngine.get(url, authUser, authPassword, -1,HTTPEngine.MAX_REDIRECT, null, userAgent,
ProxyDataImpl.getInstance(proxyserver, proxyport, proxyuser, proxypassword),null);
// mimetype
if(StringUtil.isEmpty(strMimetype)) {
ContentType ct = method.getContentType();
if(ct!=null)
setMimetype(ct.toString());
}
InputStream is = new ByteArrayInputStream(method.getContentAsByteArray());
try {
render(pd4ml, is, os,url);
}
finally {
IOUtil.closeEL(is);
}
}
else {
pd4ml.render("<html><body> </body></html>", os,null);
}
}
private static String beautifyHTML(InputSource is,URL base) throws ExpressionException, SAXException, IOException {
Document xml = XMLUtil.parse(is,null,true);
patchPD4MLProblems(xml);
if(base!=null)URLResolver.getInstance().transform(xml, base);
String html = XMLCaster.toHTML(xml);
return html;
}
private static void patchPD4MLProblems(Document xml) {
Element b = XMLUtil.getChildWithName("body", xml.getDocumentElement());
if(!b.hasChildNodes()){
b.appendChild(xml.createTextNode(" "));
}
}
private static URL getBase(PageContext pc) throws MalformedURLException {
//PageContext pc = Thread LocalPageContext.get();
if(pc==null)return null;
String userAgent = pc.getHttpServletRequest().getHeader("User-Agent");
// bug in pd4ml-> html badse definition create a call
if(!StringUtil.isEmpty(userAgent) && userAgent.startsWith("Java"))return null;
return HTTPUtil.toURL(GetDirectoryFromPath.call(pc, ReqRspUtil.getRequestURL(pc.getHttpServletRequest(), false)),true);
}
private void render(PDF pd4ml, InputStream is,OutputStream os, URL base) throws IOException, PageException {
try {
// text/html
if(mimetype==MIMETYPE_TEXT_HTML) {
body="";
try {
InputSource input = new InputSource(IOUtil.getReader(is,CharsetUtil.toCharset(strCharset)));
body=beautifyHTML(input,base);
}
catch (Throwable t) {}
//else if(body==null)body =IOUtil.toString(is,strCharset);
pd4ml.render(body, os,base);
}
// text
else if(mimetype==MIMETYPE_TEXT) {
body =IOUtil.toString(is,strCharset);
body="<html><body><pre>"+HTMLEntities.escapeHTML(body)+"</pre></body></html>";
pd4ml.render(body, os,null);
}
// image
else if(mimetype==MIMETYPE_IMAGE) {
Resource tmpDir= SystemUtil.getTempDirectory();
Resource tmp = tmpDir.getRealResource(this+"-"+Math.random());
IOUtil.copy(is, tmp,true);
body="<html><body><img src=\"file://"+tmp+"\"></body></html>";
try {
pd4ml.render(body, os,null);
}
finally {
tmp.delete();
}
}
// Application
else if(mimetype==MIMETYPE_APPLICATION && "application/pdf".equals(strMimetype)) {
IOUtil.copy(is, os,true,true);
}
else pd4ml.render(new InputStreamReader(is), os);
}
finally {
IOUtil.closeEL(is,os);
}
}
public static int toPoint(double value,double unitFactor) {
if(value<0) return MARGIN_INIT;
return (int)Math.round(value*unitFactor);
//return r;
}
public PDFPageMark getHeader() {
return header;
}
public PDFPageMark getFooter() {
return footer;
}
public void setFontembed(int fontembed) {
this.fontembed=fontembed!=FONT_EMBED_NO;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the authUser
*/
public String getAuthUser() {
return authUser;
}
/**
* @param authUser the authUser to set
*/
public void setAuthUser(String authUser) {
this.authUser = authUser;
}
/**
* @return the authPassword
*/
public String getAuthPassword() {
return authPassword;
}
/**
* @param authPassword the authPassword to set
*/
public void setAuthPassword(String authPassword) {
this.authPassword = authPassword;
}
/**
* @return the userAgent
*/
public String getUserAgent() {
return userAgent;
}
/**
* @param userAgent the userAgent to set
*/
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
/**
* @return the proxyserver
*/
public String getProxyserver() {
return proxyserver;
}
/**
* @return the proxyport
*/
public int getProxyport() {
return proxyport;
}
/**
* @return the proxyuser
*/
public String getProxyuser() {
return proxyuser;
}
/**
* @return the proxypassword
*/
public String getProxypassword() {
return proxypassword;
}
public boolean hasProxy() {
return !StringUtil.isEmpty(proxyserver);
}
/**
* @return the localUrl
*/
public boolean getLocalUrl() {
return localUrl;
}
/**
* @param localUrl the localUrl to set
*/
public void setLocalUrl(boolean localUrl) {
this.localUrl = localUrl;
}
/**
* @return the bookmark
*/
public boolean getBookmark() {
return bookmark;
}
/**
* @param bookmark the bookmark to set
*/
public void setBookmark(boolean bookmark) {
this.bookmark = bookmark;
}
/**
* @return the htmlBookmark
*/
public boolean getHtmlBookmark() {
return htmlBookmark;
}
/**
* @param htmlBookmark the htmlBookmark to set
*/
public void setHtmlBookmark(boolean htmlBookmark) {
this.htmlBookmark = htmlBookmark;
}
}