PdfTools.java

package sk.iway.iwcm.common;

import static sk.iway.iwcm.Tools.getIntValue;
import static sk.iway.iwcm.Tools.isInteger;

import java.awt.Dimension;
import java.awt.Insets;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;

import org.zefer.pd4ml.PD4Constants;
import org.zefer.pd4ml.PD4ML;
import org.zefer.pd4ml.PD4PageMark;

import sk.iway.iwcm.Constants;
import sk.iway.iwcm.Logger;
import sk.iway.iwcm.PageLng;
import sk.iway.iwcm.Pd4mlOptions;
import sk.iway.iwcm.SetCharacterEncodingFilter;
import sk.iway.iwcm.SpamProtection;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.doc.DocDB;
import sk.iway.iwcm.doc.DocDetails;
import sk.iway.iwcm.doc.GroupDetails;
import sk.iway.iwcm.doc.GroupsDB;
import sk.iway.iwcm.i18n.Prop;
import sk.iway.iwcm.io.IwcmFile;
import sk.iway.iwcm.system.context.ContextFilter;

public class PdfTools {

    private static String ORIGINAL_JAVA_VERSION = null;

    private PdfTools() {

    }

    /**
     * Do zadaneho output streamu vygeneruje PDF verziu zadaneho docId
     * @param docId
     * @param request
     * @param output
     * @return
     */
    public static boolean getPdfVersion(int docId, HttpServletRequest request, OutputStream output)
    {
        try
         {
             DocDetails doc = DocDB.getInstance().getDoc(docId);
             if (doc == null) return false;
             GroupDetails group = GroupsDB.getInstance().getGroup(doc.getGroupId());
             if (group != null)
             {
                if (Tools.isNotEmpty(group.getDomainName())) request.getSession().setAttribute("preview.editorDomainName", group.getDomainName());
             }

             String qs = request.getQueryString();
             if (Tools.isEmpty(qs)) qs = "a=1";

             StringBuilder url = new StringBuilder(Tools.getBaseHrefLoopback(request)).append("/showdoc.do?docid=").append(docId).append("&NO_WJTOOLBAR=true&isPdfVersion=true&").append(qs);

             if (request.getParameter("forward")==null) url.append("&forceBrowserDetector=pdfprint");

             //http://testvubcms:8080/showdoc.do?isPdfVersion=true&docid=2401&forward=pdf_cennik.jsp&date=04.01.2010&forIntranet=true

             String data = Tools.downloadUrl(url.toString(), SetCharacterEncodingFilter.getEncoding());

             if (Tools.isEmpty(data)) return false;

             //asi nastalo presmerovanie na https verziu, loopback nefunguje
             if (data.startsWith("<html><body>\n<a href='")) data = "";

             request.setAttribute("docId", Integer.toString(docId));
             renderHtmlCode(data, output, request);

             return true;
         }
         catch (Exception e)
         {
             Logger.error(PdfTools.class, e);
         }

         return false;
    }

    public static void renderHtmlCode(String data, OutputStream output, HttpServletRequest request) throws IOException
    {
        renderHtmlCode(data, output, request, null);
    }

    public static void renderHtmlCode(String data, OutputStream output, HttpServletRequest request, Pd4mlOptions options) throws IOException
    {
        renderHtmlCode(data, output, request, options, true);
    }

    //ing formulare sa generuju 2x zasebou v case mensom ako 1s
    public static void renderHtmlCode(String data, OutputStream output, HttpServletRequest request, Pd4mlOptions options, boolean useSpamProtection) throws IOException
    {
        Prop prop = Prop.getInstance(PageLng.getUserLng(request));

        String editorDomainName = null;
        if (request != null) editorDomainName = (String)request.getSession().getAttribute("preview.editorDomainName");

        if (data == null || data.length()==0 || output == null)
        {
            throw new IllegalArgumentException(prop.getText("html2pdf.error.nodata"));
        }
        if (useSpamProtection && request != null && !SpamProtection.canPost("HtmlToPdfAction", data, request))
        {
            throw new IllegalArgumentException(prop.getText("html2pdf.error.spam"));
        }


        //prehodime <style> @import "nieco" </style> na <link> tag
        //a to tak, ze ich najprv najdeme...
        Pattern styleImportPattern = Pattern.compile("<style(\\s*[a-z]+=['\"].*?['\"]\\s*)>\\s*@import\\s*['\"](.*?)['\"];\\s*</style>");
        Matcher styleImportMatcher = styleImportPattern.matcher(data);
        //pre kazdy takyto pattern
        while(styleImportMatcher.find())
        {
            //skopiruj atributy do noveho <link> tagu
            StringBuilder attributes = new StringBuilder();
            for (int attributeIndex =1;attributeIndex<styleImportMatcher.groupCount() ; attributeIndex++)
                attributes.append(styleImportMatcher.group(attributeIndex));
            //a @import prehod na href="nieco" tag
            String href = styleImportMatcher.group( styleImportMatcher.groupCount() );
            data = styleImportMatcher.replaceAll("<link href=\""+href+"\" "+attributes.toString()+" rel=\"stylesheet\" />");
            styleImportMatcher.reset(data);
        }

        //fixni ?v=XXXX" parameter v obrazkoch
        data = data.replaceAll("\\?v=\\d+\"", "\"");
        data = data.replaceAll("\\?v=\\d+'", "'");

        //fixni cestu k obrazku pri pouziti externeho adresara
        if (FilePathTools.isExternalDir("/images/")) {
            String[] baseDirs = {"/images/", "/files/", "/shared/"};
            for (String baseDir : baseDirs) {
                String realPath = Tools.getRealPath(baseDir);
                data = Tools.replace(data, "src=\""+baseDir, "src=\""+realPath);
                data = Tools.replace(data, "src='"+baseDir, "src='"+realPath);
                data = Tools.replace(data, "url("+baseDir, "url("+realPath);
            }
        }

        if (request!=null && "true".equals(request.getParameter("screen"))==false)
        {
            //najdeme vsetky style, ktore nie su pre media="print" a zakomentujeme ich
             Pattern styleSheetPattern = Pattern.compile("<link.*?/?>", Pattern.CASE_INSENSITIVE);
             Matcher styleSheetMatcher = styleSheetPattern.matcher(data);
             while (styleSheetMatcher.find())
             {
                 if (!styleSheetMatcher.group().contains("media=\"print\"") && !styleSheetMatcher.group().contains("media=\"all\""))
                 {
                    Logger.debug(PdfTools.class, "HtmlToPdfConverter => replacing " + styleSheetMatcher.group());
                     data = data.replace(styleSheetMatcher.group(), "<!--" + styleSheetMatcher.group() + "-->");
                 }
             }
             styleSheetMatcher = Pattern.compile("<style.*?/?>", Pattern.CASE_INSENSITIVE).matcher(data);
             while (styleSheetMatcher.find())
             {
                 if (!styleSheetMatcher.group().contains("media=\"print\"") && !styleSheetMatcher.group().contains("media=\"all\""))
                 {
                    Logger.debug(PdfTools.class, "HtmlToPdfConverter => replacing " + styleSheetMatcher.group());
                     data = data.replace(styleSheetMatcher.group(), "<!--" + styleSheetMatcher.group() + "-->");
                 }
             }
             data = data.replaceAll("media=\"print\"", "media=\"screen\"");
        }
         data = data.replaceAll("media=\"all\"", "media=\"screen\"");
         data = data.replaceAll("class=\"printButton\"", "class=\"printButton\" style=\"display:none;\"");


         fixJavaVersion();
         PD4ML pd4ml = new PD4ML();

         //doplnena moznost pre zabezpecenie PDF dokumentu
         String password = (request != null && request.getAttribute("pdfPassword") != null) ? (String)request.getAttribute("pdfPassword") : null;
         Integer permissions = (request != null && request.getAttribute("pdfPermissions") != null && request.getAttribute("pdfPermissions") instanceof Integer) ? (Integer)request.getAttribute("pdfPermissions") : null;
         if(Tools.isNotEmpty(password) || permissions != null)
         {
             //ak nezadavam svoje heslo, bude pristup bez hesla
             if(Tools.isEmpty(password)) password = "empty";
             //ak nie je definovane povolim vsetko
             if(permissions == null) permissions = 0xffffffff;
             pd4ml.setPermissions(password, permissions, true);
         }

         if (request != null && request.getAttribute("htmlWidth") != null)	pd4ml.setHtmlWidth(Tools.getIntValue((String)request.getAttribute("htmlWidth"), 1024));
         else if (request != null)	pd4ml.setHtmlWidth(getIntValue(request.getParameter("width"), 1024));
         else 	pd4ml.setHtmlWidth(1024);

         pd4ml.enableImgSplit(false);
         pd4ml.generateOutlines(true);
         pd4ml.enableDebugInfo();
         pd4ml.setAuthorName(Constants.getString("pdfAuthorName"));
         if (request != null && isInteger(request.getParameter("insets")))
         {
             int insets = getIntValue(request.getParameter("insets"), 10);
             pd4ml.setPageInsets(new Insets(insets, insets, insets, insets));
         }
         else if(request != null)
         {
             if(request.getAttribute("insets") != null)
             {
                 int insets = getIntValue((String)request.getAttribute("insets"), 0);
                 pd4ml.setPageInsets(new Insets(insets, insets, insets, insets));
             }
             else if(request.getAttribute("inset-top") != null || request.getAttribute("inset-bottom") != null ||
                       request.getAttribute("inset-left") != null || request.getAttribute("inset-right") != null)
             {
                 int insertTop = request.getAttribute("inset-top") != null ? Tools.getIntValue((String)request.getAttribute("inset-top"), 0) : 0;
                 int insertLeft = request.getAttribute("inset-left") != null ? Tools.getIntValue((String)request.getAttribute("inset-left"), 0) : 0;
                 int insertBottom = request.getAttribute("inset-bottom") != null ? Tools.getIntValue((String)request.getAttribute("inset-bottom"), 0) : 0;
                 int insertRight = request.getAttribute("inset-right") != null ? Tools.getIntValue((String)request.getAttribute("inset-right"), 0) : 0;
                 pd4ml.setPageInsets(new Insets(insertLeft, insertTop, insertBottom, insertRight));
             }
         }
         int x = PD4ML.A4.width;
         int y = PD4ML.A4.height;
         if (request != null)
         {
             x = getIntValue(request.getParameter("width"), PD4ML.A4.width);
             y = getIntValue(request.getParameter("height"), PD4ML.A4.height);
             if(request.getAttribute("pageWidth") != null)
                x = Tools.getIntValue((Integer)request.getAttribute("pageWidth"), PD4ML.A4.width);
             if(request.getAttribute("pageHeight") != null)
                y = Tools.getIntValue((Integer)request.getAttribute("pageHeight"), PD4ML.A4.height);
         }
         pd4ml.setPageSize(new Dimension(x, y));

         String conReadonlyDocIds = Constants.getString("htmlToPdfReadonlyDocIds");
         String docId = null;
         //BUGFIX  9091
         if(request != null) docId =(String)request.getAttribute("docId");

         //nastavujem readonly pre zadane docIds
         if(Tools.isNotEmpty(conReadonlyDocIds) && Tools.isNotEmpty(docId))
         {
             Logger.debug(PdfTools.class, "readonly docIds: "+conReadonlyDocIds);
             conReadonlyDocIds = conReadonlyDocIds.replaceAll(";", "+").replaceAll(" ", "+");
             String[] readonlyDocIds = Tools.getTokens(conReadonlyDocIds, "+", true);
             for(String readOnlyDocId : readonlyDocIds)
             {
                 if(readOnlyDocId.equals(docId))
                 {
                     Logger.debug(PdfTools.class, "nastavujem readonly pre docId: "+docId);
                     pd4ml.setPermissions("empty", PD4Constants.AllowContentExtraction+PD4Constants.AllowModify, true);
                     break;
                 }
             }
         }

         if (options != null )
         {
             if (options.isFitPageVertically())
             {
                 pd4ml.fitPageVertically();
             }
             if (options.getHtmlWidth()>0)
             {
                 pd4ml.setHtmlWidth(options.getHtmlWidth());
             }
         }

         if (request != null)
         {
             String headerHtml = (String)request.getAttribute("pdfHeaderHtml");
             if (Tools.isNotEmpty(headerHtml))
             {
                 PD4PageMark header = new PD4PageMark();
                 header.setAreaHeight( Tools.getIntValue((String)request.getAttribute("pdfHeaderHeight"), 30 ));
                 header.setHtmlTemplate( headerHtml );
                 pd4ml.setPageHeader( header );
             }

             String footerHtml = (String)request.getAttribute("pdfFooterHtml");
             if (Tools.isNotEmpty(footerHtml))
             {
                 PD4PageMark footer = new PD4PageMark();
                 footer.setAreaHeight( Tools.getIntValue((String)request.getAttribute("pdfFooterHeight"), -1 ) );
                 footer.setHtmlTemplate( footerHtml );
                 pd4ml.setPageFooter( footer );
             }
         }

         if (request != null) pd4ml.setSessionID(request.getSession().getId());

         URL base;
         String pdfBaseUrl = Constants.getString("pdfBaseUrl");
         if ("NULL".equalsIgnoreCase(pdfBaseUrl))
         {
             //v tomto pripade si PD4ML sam nacita obrazky z file systemu, potrebuje astaveny Context a Request
             pd4ml.useServletContext(Constants.getServletContext());
             if (request != null) pd4ml.useHttpRequest(request, null);

             base = null;

             String contextPath = "";
             if (request != null && ContextFilter.isRunning(request))
             {
                 contextPath = request.getContextPath();
             }

             //pre dynamicke obrazky musime pridat HTTP loopback
             data = Tools.replace(data, "'"+contextPath+"/admin/statchart", "'"+Tools.getBaseHrefLoopback(request)+"/admin/statchart");
             data = Tools.replace(data, "\""+contextPath+"/admin/statchart", "\""+Tools.getBaseHrefLoopback(request)+"/admin/statchart");

             data = Tools.replace(data, "'"+contextPath+"/graph.do", "'"+Tools.getBaseHrefLoopback(request)+"/graph.do");
             data = Tools.replace(data, "\""+contextPath+"/graph.do", "\""+Tools.getBaseHrefLoopback(request)+"/graph.do");

             data = Tools.replace(data, "'"+contextPath+"/thumb/", "'"+Tools.getBaseHrefLoopback(request)+"/thumb/");
             data = Tools.replace(data, "\""+contextPath+"/thumb/", "\""+Tools.getBaseHrefLoopback(request)+"/thumb/");
             if (request != null && ContextFilter.isRunning(request))
             {
                 //toto je tu kvoli _printAsPdf=true kedy je HTML kod bez prefixov
                 data = Tools.replace(data, "'/thumb/", "'"+Tools.getBaseHrefLoopback(request)+"/thumb/");
                 data = Tools.replace(data, "\"/thumb/", "\""+Tools.getBaseHrefLoopback(request)+"/thumb/");
             }

             if (request != null && ContextFilter.isRunning(request))
             {
                 data = Tools.replace(data, "'"+request.getContextPath()+"/", "'/");
                 data = Tools.replace(data, "\""+request.getContextPath()+"/", "\"/");
             }
         }
         else if ("LOOPBACK".equalsIgnoreCase(pdfBaseUrl)) base = new URL(Tools.getBaseHrefLoopback(request));
         else base = new URL(pdfBaseUrl);

         if (base != null && request!=null && ContextFilter.isRunning(request))
         {
             //pridaj ContextPath k HTML kodu
             data = ContextFilter.addContextPath(request.getContextPath(), data);
         }
         try
         {
             String pdfFontDirectory = Constants.getString("pdfFontDirectory");
             if (base != null) pdfFontDirectory = Tools.getRealPath(Tools.replace(pdfFontDirectory, "file://", "/"));
             IwcmFile pdfFontDirIwcmFile = new IwcmFile(base != null ? pdfFontDirectory : Tools.getRealPath(Tools.replace(pdfFontDirectory, "file://", "/")));
             Logger.debug(PdfTools.class, "FONT PATH: "+pdfFontDirectory);
             if(pdfFontDirIwcmFile.exists() == false)
                 Logger.error(PdfTools.class, "FONT PATH "+pdfFontDirIwcmFile.getAbsolutePath()+" neexistuje!!");

             //pd4ml.useAdobeFontMetrics( true );
             //pd4ml.useTTF( Tools.getStringValue(Constants.getString("pdfFontDirectory"), "C:\\WINDOWS\\Fonts"), true);

             pd4ml.useTTF(pdfFontDirectory, true);
         }
         catch (FileNotFoundException exc)
         {
            Logger.error(PdfTools.class, exc);
             //nic, iba nebude pouzivat default fonty
         }

         //generovanie vo formate rtf
         if(request != null && request.getParameter("renderAsRtf") != null && request.getParameter("renderAsRtf").toLowerCase().equals("true") )
         {
             if(request.getParameter("imgQuality") != null && request.getParameter("imgQuality").toLowerCase().equals("wmf"))
                 pd4ml.outputFormat(PD4Constants.RTF_WMF);	//RTF_WMF - compatibility with WordPad WMF
             else
                 pd4ml.outputFormat(PD4Constants.RTF);	//RTF -  original format (compatible with MS Word and few other editors)
         }


         //nasledujuca funkcia je deprecated, a este k tomu zbytocna, pretoze je defaultne true
         //pd4ml.useAdobeFontMetrics(true);
         if (request!=null)
         {
             //zisti, ci nemame renderovat na obrazky
             boolean renderAsImages = false;
             if(request.getAttribute("renderAsImages") != null && request.getAttribute("renderAsImages") instanceof Boolean)
                 renderAsImages = (Boolean)request.getAttribute("renderAsImages");
             String imagesUrlwithPrefix = "/files/pdf_as_image";
             if(request.getAttribute("imagesUrlwithPrefix") != null)
                 imagesUrlwithPrefix = (String)request.getAttribute("imagesUrlwithPrefix");

             if(!renderAsImages)
             {
                 if (base == null)
                 {
                    pd4ml.render(new StringReader(data),  output);
                 }
                 else if ("true".equals(request.getParameter(SetCharacterEncodingFilter.PDF_PRINT_PARAM)) && "true".equals(request.getParameter(SetCharacterEncodingFilter.PDF_PRINT_PARAM+"No"))==false)
                 {
                     pd4ml.render(new StringReader(data),  output, base, SetCharacterEncodingFilter.getEncoding());
                 }
                 else
                 {
                     //pd4ml.render(new StringReader(data),  output, new URL(Tools.getBaseHrefLoopback(request)), SetCharacterEncodingFilter.encoding);
                     pd4ml.render(new StringReader(data),  output, base, SetCharacterEncodingFilter.getEncoding());
                 }
             }
             else
             {
                 if(request.getAttribute("SetCharacterEncodingFilter") != null && (Boolean)request.getAttribute("SetCharacterEncodingFilter"))
                     pd4ml.overrideDocumentEncoding(SetCharacterEncodingFilter.getEncoding());

                 BufferedImage[] biArray = pd4ml.renderAsImages(new StringReader(data), base, Tools.getIntValue((Integer)request.getAttribute("imageWidth"), PD4ML.A4.width),  Tools.getIntValue((Integer)request.getAttribute("imageHeight"), PD4ML.A4.height));

                 String imgSuff = "png";
                 if(Tools.getParamAttribute("ImageSuffix", request)  != null)
                 {
                     String suffix = Tools.getParamAttribute("ImageSuffix", request).toLowerCase();
                     if(suffix.equals("jpg"))
                         imgSuff = "jpg";

                     if(suffix.equals("tiff"))
                         imgSuff = "tiff";
                 }

                 for(int i = 0; i < biArray.length; i++)
                 {
                     IwcmFile dest = new IwcmFile(Tools.getRealPath(imagesUrlwithPrefix+(biArray.length > 1 ? "_"+i : "")+"."+imgSuff));
                     ImageIO.write(biArray[i], imgSuff, new File(dest.getPath()));
                 }
             }
         }
         else
         {
             pd4ml.render(new StringReader(data), output);
         }

        revertOriginalJavaVersion();

         //toto sa nam proste nejako straca...
         if (request != null && Tools.isNotEmpty(editorDomainName)) request.getSession().setAttribute("preview.editorDomainName", editorDomainName);
    }

    private static void fixJavaVersion() {
        if (ORIGINAL_JAVA_VERSION == null) {
            ORIGINAL_JAVA_VERSION = System.getProperty("java.version");
        }
        //FIX pd4ml wrong Java version detection based on 2nd digit
        String[] versions = Tools.getTokens(ORIGINAL_JAVA_VERSION, ".");
        //replace second digit with number 8
        if (versions.length > 1) {
            versions[1] = "8";
            System.setProperty("java.version", Tools.join(versions, "."));
        }
    }

    private static void revertOriginalJavaVersion() {
        if (ORIGINAL_JAVA_VERSION != null) System.setProperty("java.version", ORIGINAL_JAVA_VERSION);
    }
}