com.centurylink.mdw.designer.pages.ExportHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.centurylink.mdw.designer.pages.ExportHelper.java

Source

/*
 * Copyright (C) 2017 CenturyLink, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.centurylink.mdw.designer.pages;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.imageio.ImageIO;
import javax.swing.JEditorPane;
import javax.swing.text.AbstractDocument.AbstractElement;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.StyleConstants;
import javax.swing.text.html.CSS;
import javax.swing.text.html.HTMLDocument;

import com.centurylink.mdw.common.constant.WorkAttributeConstant;
import com.centurylink.mdw.common.utilities.StringHelper;
import com.centurylink.mdw.common.utilities.bpmn.BPMNHelper;
import com.centurylink.mdw.designer.display.Graph;
import com.centurylink.mdw.designer.display.GraphCommon;
import com.centurylink.mdw.designer.display.Node;
import com.centurylink.mdw.designer.display.SubGraph;
import com.centurylink.mdw.designer.utils.DocxBuilder;
import com.centurylink.mdw.designer.utils.DocxBuilder.DocxCodebox;
import com.centurylink.mdw.designer.utils.DocxBuilder.DocxTable;
import com.centurylink.mdw.model.value.attribute.AttributeVO;
import com.centurylink.mdw.model.value.variable.VariableVO;
import com.lowagie.text.BadElementException;
import com.lowagie.text.Cell;
import com.lowagie.text.Chapter;
import com.lowagie.text.Chunk;
import com.lowagie.text.DocWriter;
import com.lowagie.text.Document;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.Image;
import com.lowagie.text.ImgTemplate;
import com.lowagie.text.ListItem;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Section;
import com.lowagie.text.Table;
import com.lowagie.text.html.HtmlWriter;
import com.lowagie.text.pdf.DefaultFontMapper;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.rtf.RtfWriter2;

/**
 * Export MDW processes to supported file formats.
 * TODO: improve styling for HTML output
 * TODO: use docx4j to produce RTF and PDF
 */
public class ExportHelper {

    public static final String PDF = "pdf";
    public static final String RTF = "rtf";
    public static final String HTML = "html";
    public static final String JPEG = "jpeg";
    public static final String JPG = "jpg";
    public static final String PNG = "png";
    public static final String DOCX = "docx";
    public static final String BPMN2 = "bpmn";

    public static final String VARIABLES = "Variables";
    public static final String DOCUMENTATION = "Documentation";
    public static final String ATTRIBUTES = "Attributes";

    public static final String SECTION_NUMBER = "Section Number";
    public static final String ACTIVITY_DOCUMENT = "Activity Documentation (LLD)";
    public static final String ACTIVITY_ATTRIBUTES = "Activity Attributes (LLD)";

    private Set<String> options;
    private String nodeIdType;
    private Font chapterFont, sectionFont, subSectionFont, normalFont, fixedWidthFont, boldFont;
    private Set<String> excludedAttributes;
    private Map<String, List<String>> tabularAttributes; // name, headers
    private Map<String, String> textboxAttributes;
    private Map<String, String> excludedAttributesForSpecificValues;

    /**
     * @param options options for printing, from the print dialog.
     * @param nodeIdType
     */
    public ExportHelper(Set<String> options, String nodeIdType) {
        this.options = options;
        this.nodeIdType = nodeIdType;
    }

    private void initialize(boolean isUseCase) {
        chapterFont = FontFactory.getFont(FontFactory.TIMES, isUseCase ? 16 : 20, Font.BOLD, new Color(0, 0, 0));
        sectionFont = FontFactory.getFont(FontFactory.TIMES, 16, Font.BOLD, new Color(0, 0, 0));
        subSectionFont = FontFactory.getFont(FontFactory.TIMES, 14, Font.BOLD, new Color(0, 0, 0));
        normalFont = FontFactory.getFont(FontFactory.TIMES, 12, Font.NORMAL, new Color(0, 0, 0));
        boldFont = FontFactory.getFont(FontFactory.TIMES, 12, Font.BOLD, new Color(0, 0, 0));
        fixedWidthFont = FontFactory.getFont(FontFactory.COURIER, 11, Font.NORMAL, new Color(0, 0, 0));
        excludedAttributes = new HashSet<String>();
        excludedAttributes.add(WorkAttributeConstant.LOGICAL_ID);
        excludedAttributes.add(WorkAttributeConstant.REFERENCE_ID);
        excludedAttributes.add(WorkAttributeConstant.WORK_DISPLAY_INFO);
        excludedAttributes.add(WorkAttributeConstant.REFERENCES);
        excludedAttributes.add(WorkAttributeConstant.DOCUMENTATION);
        excludedAttributes.add(WorkAttributeConstant.SIMULATION_STUB_MODE);
        excludedAttributes.add(WorkAttributeConstant.SIMULATION_RESPONSE);
        excludedAttributes.add(WorkAttributeConstant.DESCRIPTION);
        excludedAttributes.add(WorkAttributeConstant.BAM_START_MSGDEF);
        excludedAttributes.add(WorkAttributeConstant.BAM_FINISH_MSGDEF);
        excludedAttributesForSpecificValues = new HashMap<String, String>();
        excludedAttributesForSpecificValues.put("DoNotNotifyCaller", "false");
        excludedAttributesForSpecificValues.put("DO_LOGGING", "True");
        tabularAttributes = new HashMap<String, List<String>>();
        tabularAttributes.put("Notices",
                Arrays.asList(new String[] { "Outcome", "Template", "Notifier Class(es)" }));
        tabularAttributes.put("Variables",
                Arrays.asList(new String[] { "Variable", "ReferredAs", "Display", "Seq.", "Index" }));
        tabularAttributes.put("WAIT_EVENT_NAMES",
                Arrays.asList(new String[] { "Event Name", "Completion Code", "Recurring" }));
        tabularAttributes.put("variables",
                Arrays.asList(new String[] { "=", "SubProcess Variable", "Binding Expression" }));
        tabularAttributes.put("processmap",
                Arrays.asList(new String[] { "=", "Logical Name", "Process Name", "Process Version" }));
        tabularAttributes.put("Bindings", Arrays.asList(new String[] { "=", "Variable", "LDAP Attribute" }));
        tabularAttributes.put("Parameters",
                Arrays.asList(new String[] { "=", "Input Variable", "Binding Expression" }));
        textboxAttributes = new HashMap<String, String>();
        textboxAttributes.put("Rule", "Code");
        textboxAttributes.put("Java", "Java");
        textboxAttributes.put("PreScript", "Pre-Script");
        textboxAttributes.put("PostScript", "Post-Script");
    }

    /**
     * Export a process
     * @param filename the file name (including path) where the document will be generated
     * @param format can be docx, pdf, rtf, html and bpmn
     * @param canvas for printing process images
     * @param graph the process to be printed.
     */
    public void exportProcess(String filename, String format, Graph process, DesignerCanvas canvas)
            throws Exception {

        initialize(false);

        String oldNodeIdType = process.getNodeIdType();

        try {
            process.setNodeIdType(nodeIdType);
            options.add(SECTION_NUMBER);
            if (format.equals(DOCX)) {
                DocxBuilder builder = printProcessDocx(filename, process, canvas);
                builder.save(new java.io.File(filename));
                return;
            } else if (format.equals(HTML)) {
                StringBuffer sb = printPrologHtml("Process " + process.getName());
                printProcessHtml(sb, canvas, 0, process, filename);
                printEpilogHtml(sb, filename);
                return;
            } else if (format.equals(JPG) || format.equals(PNG)) {
                byte[] imgBytes = printImage(-1f, canvas, process.getGraphSize(),
                        format.equals(JPG) ? "jpeg" : "png");
                OutputStream os = null;
                try {
                    os = new FileOutputStream(new File(filename));
                    os.write(imgBytes);
                    return;
                } catch (Exception ex) {
                    ex.printStackTrace();
                    throw ex;
                } finally {
                    if (os != null)
                        os.close();
                }
            } else if (format.equals(BPMN2)) {
                new BPMNHelper().exportProcess(process.getProcessVO(), filename);
            } else { // itext processor
                Document document = new Document();
                try {
                    DocWriter writer = null;
                    if (format.equals(RTF)) {
                        writer = RtfWriter2.getInstance(document, new FileOutputStream(filename));
                    } else if (format.equals(PDF)) {
                        writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
                    }

                    document.open();
                    document.setPageSize(PageSize.LETTER);
                    Rectangle page_size = document.getPageSize();
                    Chapter chapter = printOneProcessPdf(writer, canvas, format, 1, process, filename, page_size);
                    document.add(chapter);
                } catch (Exception ex) {
                    ex.printStackTrace();
                    throw ex;
                } finally {
                    // step 5: we close the document
                    document.close();
                }
            }
        } finally {
            process.setNodeIdType(oldNodeIdType);
        }
    }

    /**
     * Export multiple processes
     * @param filename the file name (including path) where the document will be generated
     * @param type can be pdf, rtf and html
     * @param flowchart the designer page (for using its canvas and report errors)
     * @param graphs the list of processes to be printed.
     * @param options options for printing, from the print dialog.
     */
    public void exportProcesses(String filename, String type, FlowchartPage flowchart, List<Graph> graphs)
            throws Exception {
        initialize(false);
        options.add(SECTION_NUMBER);
        // step 1: creation of a document-object
        Document document = new Document();
        try {
            // step 2: create PDF or RTF writer
            DocWriter writer;
            if (type.equals(RTF)) {
                writer = RtfWriter2.getInstance(document, new FileOutputStream(filename));
            } else if (type.equals(PDF)) {
                writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
            } else {
                boolean directHtml = true;
                if (directHtml) {
                    StringBuffer sb = printPrologHtml("Processes");
                    Graph process;
                    for (int i = 0; i < graphs.size(); i++) {
                        process = graphs.get(i);
                        flowchart.setProcess(process);
                        this.printProcessHtml(sb, flowchart.canvas, i + 1, process, filename);
                    }
                    printEpilogHtml(sb, filename);
                    return;
                }
                writer = HtmlWriter.getInstance(document, new FileOutputStream(filename));
            }
            // step 3: we open the document
            document.open();
            // step 4: we add contents to the document
            document.setPageSize(PageSize.LETTER);
            Graph process;
            Chapter chapter;
            Rectangle page_size = document.getPageSize();
            for (int i = 0; i < graphs.size(); i++) {
                process = graphs.get(i);
                process.setNodeIdType(nodeIdType);
                flowchart.setProcess(process);
                chapter = printOneProcessPdf(writer, flowchart.canvas, type, i + 1, process, filename, page_size);
                document.add(chapter);
            }
        } finally {
            // step 5: we close the document
            document.close();
        }
    }

    private DocxBuilder printProcessDocx(String filename, Graph process, DesignerCanvas canvas) throws Exception {

        DocxBuilder builder = new DocxBuilder();

        // title
        String title = "Workflow: \"" + process.getName() + "\"";
        builder.addParagraph("Heading1", title);

        // process image
        int zoomSave = process.zoom;
        process.zoom = 100;
        byte[] imgBytes = printImage(-1f, canvas, process.getGraphSize(), "png");
        process.zoom = zoomSave;
        builder.addImage(imgBytes);

        printProcessBodyDocx(builder, process);

        // embedded subprocesses
        for (SubGraph subprocess : process.getSubgraphs(nodeIdType)) {
            String id = subprocess.getDisplayId(nodeIdType);
            String spTitle = "Embedded Subprocess " + id + ": \"" + subprocess.getName() + "\"";
            builder.addParagraph("Heading1", spTitle);
            printProcessBodyDocx(builder, subprocess);
        }

        // process variables
        if (options.contains(VARIABLES)) {
            List<VariableVO> variables = process.getProcessVO().getVariables();
            if (variables != null && !variables.isEmpty()) {
                builder.addParagraph("Heading2", "Process Variables");
                String[] headers = new String[] { "Name", "Type", "Mode", "Description" };
                String[][] values = new String[4][variables.size()];
                for (int i = 0; i < variables.size(); i++) {
                    VariableVO var = variables.get(i);
                    values[0][i] = var.getName();
                    values[1][i] = var.getVariableType();
                    values[2][i] = VariableVO.VariableCategories[var.getVariableCategory()];
                    values[3][i] = var.getVariableReferredAs();
                }

                builder.addTable(headers, values, 11, 120);
            }
        }

        // System.out.println(builder.toXml());

        return builder;
    }

    private void printProcessBodyDocx(DocxBuilder builder, GraphCommon process) throws Exception {
        String summary = process.getProcessVO().getProcessDescription();
        if (summary != null && summary.length() > 0) {
            builder.addBoldParagraph(summary);
        }
        if (options.contains(DOCUMENTATION)) {
            String detail = process.getAttribute(WorkAttributeConstant.DOCUMENTATION);
            if (detail != null && detail.length() > 0) {
                if (detail.startsWith("<html>")) {
                    builder.addHtml(detail);
                } else {
                    byte[] docBytes = decodeBase64(detail);
                    builder.addDoc(docBytes);
                }
            }
        }
        builder.addBreak();

        // activities
        for (Node node : process.getNodes(nodeIdType))
            printActivityDocx(builder, node);
    }

    private void printActivityDocx(DocxBuilder builder, Node node) throws Exception {
        String id = node.getDisplayId(nodeIdType);
        String actTitle = "Activity " + id + ": \"" + node.getName().replace('\n', ' ') + "\"";
        builder.addParagraph("Heading2", actTitle);

        String summary = node.getAttribute(WorkAttributeConstant.DESCRIPTION);
        if (summary != null && summary.length() > 0) {
            builder.addBoldParagraph(summary);
        }

        // documentation
        if (options.contains(DOCUMENTATION)) {
            String detail = node.getAttribute(WorkAttributeConstant.DOCUMENTATION);
            if (detail != null && detail.length() > 0) {
                if (detail.startsWith("<html>")) {
                    builder.addHtml(detail);
                } else {
                    byte[] docBytes = decodeBase64(detail);
                    builder.addDoc(docBytes);
                }
            }
        }

        // attributes
        if (options.contains(ATTRIBUTES)) {
            Map<String, Object> attrs = new TreeMap<String, Object>();
            if (node.getAttributes() != null) {
                for (AttributeVO attr : node.getAttributes()) {
                    String name = attr.getAttributeName();
                    String val = attr.getAttributeValue();
                    if (!excludeAttribute(name, val)) {
                        if (tabularAttributes.containsKey(name)) {
                            DocxTable docxTable = parseTableDocx(builder, tabularAttributes.get(name), val);
                            docxTable.setFontSize(9);
                            docxTable.setIndent(800);
                            attrs.put(name, docxTable);
                        } else if (textboxAttributes.containsKey(name)) {
                            DocxCodebox docxTextbox = builder.new DocxCodebox(textboxAttributes.get(name), val);
                            attrs.put(name, docxTextbox);
                        } else {
                            attrs.put(name, val);
                        }
                    }
                }
            }
            if (!attrs.isEmpty()) {
                builder.addParagraph("Heading3", "Activity Attributes");
                builder.addBulletList(attrs);
            }
        }

        builder.addBreak();
    }

    private DocxBuilder.DocxTable parseTableDocx(DocxBuilder builder, List<String> labels, String value) {
        List<String> cols = new ArrayList<String>(labels);
        char colDelim = ',';
        if (cols.get(0).equals("=")) {
            colDelim = '=';
            cols.remove(0);
        }
        String[] headers = cols.toArray(new String[0]);
        List<String[]> rows = StringHelper.parseTable(value, colDelim, ';', headers.length);
        String[][] values = new String[headers.length][rows.size()];
        for (int i = 0; i < headers.length; i++) {
            for (int j = 0; j < rows.size(); j++) {
                values[i][j] = rows.get(j)[i];
            }
        }
        return builder.new DocxTable(headers, values);
    }

    private void printProcessHtml(StringBuffer sb, DesignerCanvas canvas, int chapter, Graph process,
            String filename) throws Exception {
        sb.append("<h1>");
        if (chapter > 0)
            sb.append(Integer.toString(chapter)).append(". ");
        sb.append("Workflow: \"").append(process.getName()).append("\"</h1>\n");
        // print image
        printGraphHtml(sb, canvas, process, filename, chapter);
        // print documentation text
        sb.append("<br/>");
        printProcessBodyHtml(sb, process);
        for (Node node : process.getNodes(nodeIdType)) {
            printActivityHtml(sb, node);
        }
        for (SubGraph subgraph : process.getSubgraphs(nodeIdType)) {
            printProcessBodyHtml(sb, subgraph);
            for (Node node : subgraph.getNodes(nodeIdType)) {
                printActivityHtml(sb, node);
            }
        }
        if (options.contains(VARIABLES)) {
            List<VariableVO> variables = process.getProcessVO().getVariables();
            if (variables != null && !variables.isEmpty()) {
                sb.append("<h2>Process Variables</h2>\n");
                String[] headers = new String[] { "Name", "Type", "Mode", "Description" };
                String[][] values = new String[4][variables.size()];
                for (int i = 0; i < variables.size(); i++) {
                    VariableVO var = variables.get(i);
                    values[0][i] = var.getName();
                    values[1][i] = var.getVariableType();
                    values[2][i] = VariableVO.VariableCategories[var.getVariableCategory()];
                    values[3][i] = var.getVariableReferredAs();
                }
                printTableHtml(sb, headers, values);
            }
        }
    }

    private void printTableHtml(StringBuffer sb, String[] headers, String[][] values) {
        String border = "border:1px solid black;";
        String padding = "padding:3px;";
        sb.append(
                "<table style='width:90%;text-align:left;border:1px solid black;border-spacing:0;border-collapse:collapse;font-size:12px;'>\n");
        sb.append("<thead style='font-weight:bold'>\n<tr>\n");
        for (int i = 0; i < headers.length; i++) {
            sb.append("<th style='" + border + padding + "'>").append(headers[i]).append("</th>\n");
        }
        sb.append("</tr></thead>\n");
        sb.append("<tbody>\n");
        for (int i = 0; i < values[0].length; i++) {
            sb.append("<tr>");
            for (int j = 0; j < headers.length; j++) {
                sb.append("<td style='" + border + padding + "'>").append(values[j][i]).append("</td>");
            }
            sb.append("</tr>\n");
        }
        sb.append("</tbody>\n");
        sb.append("</table>\n");
    }

    private void printProcessBodyHtml(StringBuffer sb, GraphCommon graph) throws Exception {
        String tmp = null;
        if (graph instanceof SubGraph) {
            SubGraph subgraph = (SubGraph) graph;
            String id = subgraph.getDisplayId(nodeIdType);
            if (id == null || id.length() == 0)
                id = subgraph.getId().toString();
            tmp = "Subprocess " + id + " - " + subgraph.getName().replace('\n', ' ');
        }
        if (tmp != null)
            sb.append("<h2>").append(tmp).append("</h2>\n");
        String summary = (graph instanceof SubGraph) ? ((SubGraph) graph).getProcessVO().getProcessDescription()
                : ((Graph) graph).getProcessVO().getProcessDescription();
        if (summary != null && summary.length() > 0)
            sb.append("<span style='font-weight:bold'>").append(summary).append("</span>");
        if (options.contains(DOCUMENTATION)) {
            String detail = graph.getProcessVO().getAttribute(WorkAttributeConstant.DOCUMENTATION);
            if (detail != null && detail.length() > 0) {
                printParagraphsHtml(sb, detail, 2);
            }
        }
        if (options.contains(ATTRIBUTES) && graph instanceof SubGraph) {
            printAttributesHtml(sb, graph.getProcessVO().getAttributes());
        }
        sb.append("<br/>");
    }

    private void printGraphHtml(StringBuffer sb, CanvasCommon canvas, Graph process, String filename,
            int chapterNumber) throws Exception {
        int zoomSave = process.zoom;
        process.zoom = 100;
        String imgfilepath;
        int k = filename.lastIndexOf('/');
        if (k < 0)
            k = filename.lastIndexOf('\\');
        String imgfilename = process.getName() + "_" + process.getProcessVO().getVersionString() + "_ch"
                + chapterNumber + ".jpg";
        if (k > 0)
            imgfilepath = filename.substring(0, k + 1) + imgfilename;
        else
            imgfilepath = imgfilename;
        // TODO need to generate different names for the same process
        // in different use cases, as the step markers may be different.
        printImage(imgfilepath, -1f, canvas, process.getGraphSize());
        process.zoom = zoomSave;
        sb.append("<img src='").append(escapeXml(imgfilename)).append("'/>\n");
    }

    private void printActivityHtml(StringBuffer sb, Node node) throws Exception {
        String id = node.getDisplayId(nodeIdType);
        if (id == null || id.length() == 0)
            id = node.getId().toString();
        String tmp = "Activity " + id + ": \"" + node.getName().replace('\n', ' ') + "\"";
        sb.append("<h2>").append(tmp).append("</h2>\n");
        String summary = node.getAttribute(WorkAttributeConstant.DESCRIPTION);
        if (summary != null && summary.length() > 0)
            sb.append("<span style='font-weight:bold'>").append(summary).append("</span>");

        if (options.contains(DOCUMENTATION)) {
            String detail = node.getAttribute(WorkAttributeConstant.DOCUMENTATION);
            if (detail != null && detail.length() > 0) {
                printParagraphsHtml(sb, detail, 2);
            }
        }
        if (options.contains(ATTRIBUTES)) {
            printAttributesHtml(sb, node.getAttributes());
        }
        sb.append("<br/>");
    }

    private void printAttributesHtml(StringBuffer sb, List<AttributeVO> attributes) {
        if (attributes != null) {
            List<AttributeVO> sortedAttrs = new ArrayList<AttributeVO>(attributes);
            Collections.sort(sortedAttrs);
            sb.append("<h3>Activity Attributes</h3>\n");
            sb.append("<ul>\n");
            for (AttributeVO attr : sortedAttrs) {
                String name = attr.getAttributeName();
                String val = attr.getAttributeValue();
                if (!excludeAttribute(name, val)) {
                    sb.append("<li>");
                    if (tabularAttributes.containsKey(name)) {
                        sb.append(name + ":");
                        List<String> cols = new ArrayList<String>(tabularAttributes.get(name));
                        char colDelim = ',';
                        if (cols.get(0).equals("=")) {
                            colDelim = '=';
                            cols.remove(0);
                        }
                        String[] headers = cols.toArray(new String[0]);
                        List<String[]> rows = StringHelper.parseTable(escapeXml(val), colDelim, ';',
                                headers.length);
                        String[][] values = new String[headers.length][rows.size()];
                        for (int i = 0; i < headers.length; i++) {
                            for (int j = 0; j < rows.size(); j++) {
                                values[i][j] = rows.get(j)[i];
                            }
                        }
                        printTableHtml(sb, headers, values);
                    } else if (textboxAttributes.containsKey(name)) {
                        sb.append(textboxAttributes.get(name) + ":");
                        printCodeBoxHtml(sb, escapeXml(val));
                    } else {
                        sb.append(name + " = " + val);
                    }
                    sb.append("</li>");
                }
            }
            sb.append("</ul>");
        }
    }

    private String escapeXml(String str) {
        return str.replaceAll("&", "&amp;").replaceAll("<", "&lt;");
    }

    private void printCodeBoxHtml(StringBuffer sb, String content) {
        sb.append("<pre style='border:1px solid black;font-size:12px;'>").append(content).append("</pre>");
    }

    private void printElementHtml(Element element, Object parent, int depth, Font font, int parentLevel) {
        String tag = element.getName();
        Object av;
        if (element instanceof HTMLDocument.RunElement) {
            HTMLDocument.RunElement re = (HTMLDocument.RunElement) element;
            int start = re.getStartOffset();
            int end = re.getEndOffset();
            try {
                String content = re.getDocument().getText(start, end - start);
                printAttributesHtml(re);
                av = re.getAttribute(CSS.Attribute.FONT_SIZE);
                String fontsize = av == null ? null : av.toString();
                av = re.getAttribute(CSS.Attribute.FONT_FAMILY);
                String fontfamily = av == null ? null : av.toString();
                av = re.getAttribute(CSS.Attribute.COLOR);
                String fontcolor = av == null ? null : av.toString();
                if (fontcolor != null || fontsize != null || fontfamily != null) {
                    if (fontfamily == null)
                        fontfamily = font.getFamilyname();
                    float size = fontsize == null ? font.getSize() : (Float.parseFloat(fontsize) + 9);
                    int style = font.getStyle();
                    Color color;
                    if (fontcolor != null) {
                        color = Color.decode(fontcolor);
                    } else
                        color = font.getColor();
                    font = FontFactory.getFont(fontfamily, size, style, color);
                }
                if (parent instanceof Paragraph) {
                    ((Paragraph) parent).add(new Chunk(content, font));
                } else {
                    System.err.println("chunk with parent "
                            + (parent == null ? "null" : parent.getClass().getName()) + ": " + content);
                }
            } catch (BadLocationException e) {
                e.printStackTrace();
            }
        } else if (element instanceof HTMLDocument.BlockElement) {
            HTMLDocument.BlockElement be = (HTMLDocument.BlockElement) element;
            printAttributesHtml(be);
            av = be.getAttribute(javax.swing.text.html.HTML.Attribute.ALIGN);
            String align = av == null ? null : av.toString();
            if (tag.equalsIgnoreCase("html")) {
                printElementChildrenHtml(element, parent, depth + 1, font, parentLevel);
            } else if (tag.equalsIgnoreCase("head")) {
                // do nothing
            } else if (tag.equalsIgnoreCase("body")) {
                printElementChildrenHtml(element, parent, depth + 1, font, parentLevel);
            } else if (tag.equalsIgnoreCase("p")) {
                if (parent instanceof Section) {
                    Paragraph paragraph = new Paragraph();
                    if (align != null) {
                        paragraph.setAlignment(align);
                    }
                    printElementChildrenHtml(element, paragraph, depth + 1, normalFont, parentLevel);
                    ((Section) parent).add(paragraph);
                } else {
                    System.err.println("p with parent " + (parent == null ? "null" : parent.getClass().getName()));
                }
            } else if (tag.equalsIgnoreCase("h1") || tag.equalsIgnoreCase("h2") || tag.equalsIgnoreCase("h3")) {
                if (parent instanceof Section) {
                    Paragraph title = new Paragraph();
                    printElementChildrenHtml(element, title, depth + 1, subSectionFont, parentLevel);
                    ((Section) parent).addSection(title, parentLevel == 0 ? 0 : (parentLevel + 1));
                } else {
                    System.err
                            .println("list with parent " + (parent == null ? "null" : parent.getClass().getName()));
                }
            } else if (tag.equalsIgnoreCase("ul")) {
                if (parent instanceof Section) {
                    com.lowagie.text.List list = new com.lowagie.text.List(false, false, 20.0f);
                    printElementChildrenHtml(element, list, depth + 1, normalFont, parentLevel);
                    ((Section) parent).add(list);
                } else {
                    System.err
                            .println("list with parent " + (parent == null ? "null" : parent.getClass().getName()));
                }
            } else if (tag.equalsIgnoreCase("ol")) {
                if (parent instanceof Section) {
                    com.lowagie.text.List list = new com.lowagie.text.List(true, false, 20.0f);
                    printElementChildrenHtml(element, list, depth + 1, normalFont, parentLevel);
                    ((Section) parent).add(list);
                } else {
                    System.err
                            .println("list with parent " + (parent == null ? "null" : parent.getClass().getName()));
                }
            } else if (tag.equalsIgnoreCase("li")) {
                ListItem li = new ListItem();
                li.setSpacingAfter(0.0f);
                printElementChildrenHtml(element, li, depth + 1, normalFont, parentLevel);
                ((com.lowagie.text.List) parent).add(li);
            } else if (tag.equalsIgnoreCase("p-implied")) {
                if (parent instanceof ListItem) {
                    Paragraph paragraph = new Paragraph();
                    printElementChildrenHtml(element, paragraph, depth + 1, normalFont, parentLevel);
                    ((ListItem) parent).add(paragraph);
                } else if (parent instanceof Cell) {
                    Paragraph paragraph = new Paragraph();
                    printElementChildrenHtml(element, paragraph, depth + 1, normalFont, parentLevel);
                    ((Cell) parent).add(paragraph);
                }
            } else if (tag.equalsIgnoreCase("table")) {
                try {
                    Table table = new Table(3);
                    table.setBorderWidth(1);
                    table.setBorderColor(new Color(0, 128, 128));
                    table.setPadding(1.0f);
                    table.setSpacing(0.5f);
                    Cell c = new Cell("header");
                    c.setHeader(true);
                    c.setColspan(3);
                    table.addCell(c);
                    table.endHeaders();
                    printElementChildrenHtml(element, table, depth + 1, normalFont, parentLevel); // TODO
                    ((Section) parent).add(table);
                } catch (BadElementException e) {
                    e.printStackTrace();
                }
            } else if (tag.equalsIgnoreCase("tr")) {
                printElementChildrenHtml(element, parent, depth + 1, normalFont, parentLevel); // TODO
            } else if (tag.equalsIgnoreCase("td")) {
                Cell cell = new Cell();
                printElementChildrenHtml(element, cell, depth + 1, normalFont, parentLevel); // TODO
                ((Table) parent).addCell(cell);
            } else {
                System.err.println("Unknown element " + element.getName());
                printElementChildrenHtml(element, parent, depth + 1, normalFont, parentLevel);
            }
        } else {
            return; // could be BidiElement - not sure what it is
        }
    }

    private List<Object> generateElementChildrenHtml(Element element, int depth, Font font) {
        int n = element.getElementCount();
        List<Object> children = new ArrayList<Object>();
        for (int i = 0; i < n; i++) {
            Element subelem = element.getElement(i);
            Object child = generateElementHtml(subelem, depth, font);
            if (child != null)
                children.add(child);
        }
        return children;
    }

    private Object generateElementHtml(Element element, int depth, Font font) {
        String tag = element.getName();
        Object myself;
        Object av;
        if (element instanceof HTMLDocument.RunElement) {
            HTMLDocument.RunElement re = (HTMLDocument.RunElement) element;
            int start = re.getStartOffset();
            int end = re.getEndOffset();
            try {
                String content = re.getDocument().getText(start, end - start);
                HtmlAttr htmlattr = printAttributesHtml(re);
                av = re.getAttribute(CSS.Attribute.FONT_SIZE);
                String fontsize = av == null ? null : av.toString();
                av = re.getAttribute(CSS.Attribute.FONT_FAMILY);
                String fontfamily = av == null ? null : av.toString();
                av = re.getAttribute(CSS.Attribute.COLOR);
                String fontcolor = av == null ? null : av.toString();
                if (fontcolor != null || fontsize != null || fontfamily != null) {
                    if (fontfamily == null)
                        fontfamily = font.getFamilyname();
                    if (fontsize != null && fontsize.endsWith("pt"))
                        fontsize = fontsize.substring(0, fontsize.indexOf("pt"));
                    float size = fontsize == null ? font.getSize() : (Float.parseFloat(fontsize) + 8);
                    int style = font.getStyle();
                    Color color;
                    if (fontcolor != null) {
                        color = Color.decode(fontcolor);
                    } else
                        color = font.getColor();
                    font = FontFactory.getFont(fontfamily, size, style, color);
                } else if (htmlattr.bold || htmlattr.italic) {
                    String family = font.getFamilyname();
                    float size = font.getSize();
                    Color color = font.getColor();
                    if (htmlattr.bold && htmlattr.italic)
                        font = FontFactory.getFont(family, size, Font.BOLDITALIC, color);
                    else if (htmlattr.italic)
                        font = FontFactory.getFont(family, size, Font.ITALIC, color);
                    else if (htmlattr.bold)
                        font = FontFactory.getFont(family, size, Font.BOLD);
                }
                myself = new Chunk(content, font);
            } catch (BadLocationException e) {
                e.printStackTrace();
                myself = null;
            }
        } else if (element instanceof HTMLDocument.BlockElement) {
            HTMLDocument.BlockElement be = (HTMLDocument.BlockElement) element;
            HtmlAttr htmlattr = printAttributesHtml(be);
            if (htmlattr.bold) {
                System.out.println("+++BOLD!!!");
            }
            av = be.getAttribute(javax.swing.text.html.HTML.Attribute.ALIGN);
            String align = av == null ? null : av.toString();
            if (htmlattr.bold || htmlattr.italic) {
                String family = font.getFamilyname();
                float size = font.getSize();
                Color color = font.getColor();
                if (htmlattr.bold && htmlattr.italic)
                    font = FontFactory.getFont(family, size, Font.BOLDITALIC, color);
                else if (htmlattr.italic)
                    font = FontFactory.getFont(family, size, Font.ITALIC, color);
                else if (htmlattr.bold)
                    font = FontFactory.getFont(family, size, Font.BOLD, Color.blue);
            }
            if (tag.equalsIgnoreCase("html")) {
                myself = generateElementChildrenHtml(element, depth + 1, font);
            } else if (tag.equalsIgnoreCase("head")) {
                myself = null;
            } else if (tag.equalsIgnoreCase("body")) {
                myself = generateElementChildrenHtml(element, depth + 1, font);
            } else if (tag.equalsIgnoreCase("p") || tag.equalsIgnoreCase("p-implied")) {
                List<Object> children = generateElementChildrenHtml(element, depth + 1, normalFont);
                Paragraph paragraph = new Paragraph();
                paragraph.setFirstLineIndent(0F);
                for (Object child : children) {
                    if (child instanceof Chunk) {
                        Chunk chunk = (Chunk) child;
                        /*if (!chunk.getContent().equals("\n"))*/ paragraph.add(chunk);
                    } else
                        paragraph.add(child);
                }
                if (align != null)
                    paragraph.setAlignment(align);
                myself = paragraph;
            } else if (tag.equalsIgnoreCase("h1") || tag.equalsIgnoreCase("h2") || tag.equalsIgnoreCase("h3")) {
                List<Object> children = generateElementChildrenHtml(element, depth + 1, subSectionFont);
                Paragraph title = new Paragraph();
                for (Object child : children) {
                    title.add(child);
                }
                myself = new TempSectionPdf(title);
            } else if (tag.equalsIgnoreCase("ul")) {
                com.lowagie.text.List list = new com.lowagie.text.List(false, false, 20.0f);
                list.setIndentationLeft(25.0f);
                List<Object> children = generateElementChildrenHtml(element, depth + 1, normalFont);
                for (Object child : children) {
                    list.add(child);
                }
                myself = list;
            } else if (tag.equalsIgnoreCase("ol")) {
                com.lowagie.text.List list = new com.lowagie.text.List(true, false, 20.0f);
                list.setIndentationLeft(25.0f);
                List<Object> children = generateElementChildrenHtml(element, depth + 1, normalFont);
                for (Object child : children) {
                    list.add(child);
                }
                myself = list;
            } else if (tag.equalsIgnoreCase("li")) {
                ListItem li = new ListItem();
                li.setSpacingAfter(0.0f);
                List<Object> children = generateElementChildrenHtml(element, depth + 1, normalFont);
                for (Object child : children) {
                    li.add(child);
                }
                myself = li;
            } else if (tag.equalsIgnoreCase("table")) {
                List<Object> rows = generateElementChildrenHtml(element, depth + 1, normalFont);
                try {
                    int ncols = 0;
                    for (Object row : rows) {
                        if (row instanceof List<?>) {
                            int n = ((List<?>) row).size();
                            if (n > ncols)
                                ncols = n;
                        }
                    }
                    Table table = new Table(2);
                    table.setBorderWidth(1);
                    table.setBorderColor(new Color(0, 128, 128));
                    table.setPadding(1.0f);
                    table.setSpacing(0.5f);
                    Cell c = new Cell("header");
                    c.setHeader(true);
                    c.setColspan(ncols);
                    table.addCell(c);
                    table.endHeaders();
                    for (Object row : rows) {
                        if (row instanceof List<?>) {
                            for (Object cell : (List<?>) row) {
                                if (cell instanceof Cell)
                                    table.addCell((Cell) cell);
                            }
                        }
                    }
                    myself = table;
                } catch (BadElementException e) {
                    e.printStackTrace();
                    myself = null;
                }
            } else if (tag.equalsIgnoreCase("tr")) {
                List<Object> children = generateElementChildrenHtml(element, depth + 1, normalFont);
                myself = children;
            } else if (tag.equalsIgnoreCase("td")) {
                Cell cell = new Cell();
                List<Object> children = generateElementChildrenHtml(element, depth + 1, normalFont);
                for (Object child : children) {
                    cell.add(child);
                }
                myself = cell;
            } else if (tag.equalsIgnoreCase("div")) {
                List<Object> children = generateElementChildrenHtml(element, depth + 1, normalFont);
                Paragraph paragraph = new Paragraph();
                paragraph.setFirstLineIndent(0F);
                for (Object child : children) {
                    paragraph.add(child);
                }
                if (align != null)
                    paragraph.setAlignment(align);
                myself = paragraph;
            } else {
                System.err.println("Unknown element " + element.getName());
                myself = null;
            }
        } else {
            myself = null; // could be BidiElement - not sure what it is
        }
        return myself;
    }

    private void printElementChildrenHtml(Element element, Object parent, int depth, Font font, int parent_level) {
        int n = element.getElementCount();
        for (int i = 0; i < n; i++) {
            Element subelem = element.getElement(i);
            printElementHtml(subelem, parent, depth, font, parent_level);
        }
    }

    private void printParagraphsHtml(StringBuffer sb, String content, int parentLevel) throws Exception {
        if (content == null || content.length() == 0)
            return;
        if (isBase64Encoded(content) || "true".equals(System.getProperty("mdw.designer.force.msword"))) {
            byte[] docBytes = decodeBase64(content);
            content = new DocxBuilder(docBytes).toHtml();
        }
        if (content.contains("<html>")) {
            int k1 = content.indexOf("<html>");
            int k2 = content.indexOf("<body>");
            int k3 = content.indexOf("</body>");
            int k4 = content.indexOf("</html>");
            if (k2 > 0) {
                if (k3 > 0)
                    content = content.substring(k2 + 6, k3);
                else if (k4 > 0)
                    content = content.substring(k2 + 6, k4);
                else
                    content = content.substring(k2 + 6);
            } else {
                if (k3 > 0)
                    content = content.substring(k1 + 6, k3);
                else if (k4 > 0)
                    content = content.substring(k1 + 6, k4);
                else
                    content = content.substring(k1 + 6);
            }
            sb.append(content);
        } else {
            String[] details = content.split("\n");
            for (int j = 0; j < details.length; j++) {
                if (details[j].length() == 0)
                    sb.append("<br/>\n");
                else
                    sb.append(escapeXml(details[j])).append("\n");
            }
        }
    }

    private StringBuffer printPrologHtml(String title) {
        StringBuffer sb = new StringBuffer();
        sb.append("<html>\n<head>\n");
        sb.append("<title>").append(title).append("</title>\n");
        sb.append("<style>\n");
        sb.append("body {font-family: Arial; font-size: smaller; padding:5px; }\n");
        sb.append("em { color: #a52a2a; }\n");
        sb.append("</style>\n");
        sb.append("</head>\n<body>\n");
        return sb;
    }

    private void printEpilogHtml(StringBuffer sb, String filename) throws Exception {
        FileOutputStream os = null;
        try {
            sb.append("</body></html>\n");
            os = new FileOutputStream(filename);
            os.write(sb.toString().getBytes());
            os.close();
        } finally {
            if (os != null) {
                os.close();
            }
        }
    }

    private class HtmlAttr {
        boolean bold = false;
        boolean italic = false;
    }

    private HtmlAttr printAttributesHtml(AbstractElement ae) {
        Enumeration<?> attrs = ae.getAttributeNames();
        HtmlAttr htmlattr = new HtmlAttr();
        while (attrs.hasMoreElements()) {
            Object attr = attrs.nextElement();
            if (attr.equals(StyleConstants.NameAttribute))
                continue;
            if (attr.equals("CR"))
                continue;
            if (attr.equals(javax.swing.text.html.HTML.Tag.FONT))
                continue;
            //            if (attr.equals(CSS.Attribute.FONT_FAMILY)) continue;
            //            if (attr.equals(CSS.Attribute.FONT_SIZE)) continue;
            if (attr instanceof javax.swing.text.html.HTML.Tag) {
                if (attr.toString().equals("b"))
                    htmlattr.bold = true;
                else if (attr.toString().equals("i"))
                    htmlattr.italic = true;
                else
                    printUnknownAttributeHtml(ae, attr);
            } else if (attr instanceof javax.swing.text.html.CSS.Attribute) {
                // it seems both HTML.Tag and CSS.Attribute are set for bold/italic
                // the following code duplicate above, have it here just to
                // avoid printing unknown attributes
                if (attr.toString().equals("font-weight")) {
                    if (ae.getAttribute(attr).toString().equals("bold"))
                        htmlattr.bold = true;
                } else if (attr.toString().equals("font-style")) {
                    if (ae.getAttribute(attr).toString().equals("italic"))
                        htmlattr.italic = true;
                } else
                    printUnknownAttributeHtml(ae, attr);
            } else
                printUnknownAttributeHtml(ae, attr);
        }
        return htmlattr;
    }

    private void printUnknownAttributeHtml(AbstractElement ae, Object attr) {
        Object v = ae.getAttribute(attr);
        System.out.println(" attr: " + attr.toString() + " (" + attr.getClass().getName() + "): " + v.toString()
                + " (" + v.getClass().getName() + ")");
    }

    private Chapter printOneProcessPdf(DocWriter writer, DesignerCanvas canvas, String type, int chapter_number,
            Graph process, String filename, Rectangle page_size) throws Exception {
        Paragraph cTitle = new Paragraph("Workflow Process: \"" + process.getName() + "\"", chapterFont);
        Chapter chapter = new Chapter(cTitle, chapter_number);
        // print image
        printGraphPdf(writer, canvas, process, page_size, type, filename, chapter, chapter_number);
        // print documentation text
        printGraphPdf(process, chapter);
        for (Node node : process.getNodes(nodeIdType)) {
            printNodePdf(node, chapter);
        }
        for (SubGraph subgraph : process.getSubgraphs(nodeIdType)) {
            printGraphPdf(subgraph, chapter);
            for (Node node : subgraph.getNodes(nodeIdType)) {
                printNodePdf(node, chapter);
            }
        }
        if (options.contains(VARIABLES)) {
            printVariablesPdf(chapter, process.getProcessVO().getVariables(),
                    options.contains(SECTION_NUMBER) ? 1 : 0);
        }

        return chapter;
    }

    private void printGraphPdf(GraphCommon graph, Chapter chapter) throws Exception {
        Section section;
        String tmp = null;
        if (graph instanceof SubGraph) {
            SubGraph subgraph = (SubGraph) graph;
            String id = subgraph.getDisplayId(nodeIdType);
            if (id == null || id.length() == 0)
                id = subgraph.getId().toString();
            tmp = "Subprocess " + id + ": \"" + subgraph.getName().replace('\n', ' ') + "\"";
        } else {
            tmp = "Process Description";
        }
        Paragraph sTitle = new Paragraph(tmp, sectionFont);
        sTitle.setSpacingBefore(10);
        section = chapter.addSection(sTitle, options.contains(SECTION_NUMBER) ? 2 : 0);
        String summary = (graph instanceof SubGraph) ? ((SubGraph) graph).getProcessVO().getProcessDescription()
                : ((Graph) graph).getProcessVO().getProcessDescription();
        if (summary != null && summary.length() > 0)
            printBoldParagraphsPdf(section, summary);
        if (options.contains(DOCUMENTATION)) {
            String detail = graph.getProcessVO().getAttribute(WorkAttributeConstant.DOCUMENTATION);
            if (detail != null && detail.length() > 0) {
                printHtmlParagraphsPdf(section, detail, options.contains(SECTION_NUMBER) ? 2 : 0);
            }
        }
        if (options.contains(ATTRIBUTES) && !graph.getProcessVO().getAttributes().isEmpty()
                && graph instanceof SubGraph) {
            printAttributesPdf(section, graph.getProcessVO().getAttributes(),
                    options.contains(SECTION_NUMBER) ? 2 : 0);
        }
    }

    private void printNodePdf(Node node, Chapter chapter) throws Exception {
        Section section;
        String id = node.getDisplayId(nodeIdType);
        if (id == null || id.length() == 0)
            id = node.getId().toString();
        String tmp = "Activity " + id + ": \"" + node.getName().replace('\n', ' ') + "\n";
        Paragraph sTitle = new Paragraph(tmp, sectionFont);
        section = chapter.addSection(sTitle, options.contains(SECTION_NUMBER) ? 2 : 0);
        String summary = node.getAttribute(WorkAttributeConstant.DESCRIPTION);
        if (summary != null && summary.length() > 0) {
            printBoldParagraphsPdf(section, summary);
        }

        if (options.contains(DOCUMENTATION)) {
            String detail = node.getAttribute(WorkAttributeConstant.DOCUMENTATION);
            if (detail != null && detail.length() > 0) {
                printHtmlParagraphsPdf(section, detail, options.contains(SECTION_NUMBER) ? 2 : 0);
                section.add(new Paragraph("\n",
                        FontFactory.getFont(FontFactory.TIMES, 4, Font.NORMAL, new Color(0, 0, 0))));
            }
        }
        if (options.contains(ATTRIBUTES) && !node.getAttributes().isEmpty()) {
            printAttributesPdf(section, node.getAttributes(), options.contains(SECTION_NUMBER) ? 2 : 0);
        }
        section.add(new Paragraph("\n", normalFont));
    }

    public void printImage(String filename, float scale, CanvasCommon canvas, Dimension graphsize) {
        try {
            int h_margin = 72, v_margin = 72;
            BufferedImage image = new BufferedImage(graphsize.width + h_margin, graphsize.height + v_margin,
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g2 = image.createGraphics();
            if (scale > 0)
                g2.scale(scale, scale);
            g2.setBackground(Color.WHITE);
            g2.clearRect(0, 0, image.getWidth(), image.getHeight());
            // canvas.paint(g2);
            Color bgsave = canvas.getBackground();
            canvas.setBackground(Color.white);
            canvas.paintComponent(g2);
            canvas.setBackground(bgsave);
            g2.dispose();
            ImageIO.write(image, "jpeg", new File(filename));
            image = null;
            Runtime r = Runtime.getRuntime();
            r.gc();
        } catch (IOException e) {
            System.err.println(e);
        }
    }

    public byte[] printImage(float scale, CanvasCommon canvas, Dimension graphsize, String format)
            throws IOException {
        int h_margin = 72, v_margin = 72;
        BufferedImage image = new BufferedImage(graphsize.width + h_margin, graphsize.height + v_margin,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = image.createGraphics();
        if (scale > 0)
            g2.scale(scale, scale);
        g2.setBackground(Color.WHITE);
        g2.clearRect(0, 0, image.getWidth(), image.getHeight());
        // canvas.paint(g2);
        Color bgsave = canvas.getBackground();
        boolean edsave = canvas.editable;
        canvas.editable = false;
        canvas.setBackground(Color.white);
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        canvas.paintComponent(g2);
        canvas.setBackground(bgsave);
        canvas.editable = edsave;
        g2.dispose();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, format, baos);
        image = null;
        Runtime r = Runtime.getRuntime();
        r.gc();
        return baos.toByteArray();
    }

    public void printImagePdf(String filename, DesignerCanvas canvas, Dimension graphsize) {
        try {
            DefaultFontMapper mapper = new DefaultFontMapper();
            FontFactory.registerDirectories();
            mapper.insertDirectory("c:\\winnt\\fonts");
            // mapper.insertDirectory("c:\\windows\\fonts");
            // we create a template and a Graphics2D object that corresponds
            // with it
            int margin = 72; // 1 inch
            float scale = 0.5f;
            boolean multiple_page = true;
            Rectangle page_size;
            if (multiple_page) {
                page_size = PageSize.LETTER.rotate();
            } else {
                page_size = new Rectangle((int) (graphsize.getWidth() * scale) + margin,
                        (int) (graphsize.getHeight() * scale) + margin);
            }
            Document document = new Document(page_size);
            DocWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
            document.open();
            document.setPageSize(page_size);
            int image_w = (int) page_size.getWidth() - margin;
            int image_h = (int) page_size.getHeight() - margin;
            boolean edsave = canvas.editable;
            canvas.editable = false;
            Color bgsave = canvas.getBackground();
            canvas.setBackground(Color.white);
            if (multiple_page) {
                int horizontal_pages = (int) (graphsize.width * scale) / image_w + 1;
                int vertical_pages = (int) (graphsize.height * scale) / image_h + 1;
                for (int i = 0; i < horizontal_pages; i++) {
                    for (int j = 0; j < vertical_pages; j++) {
                        Image img;
                        PdfContentByte cb = ((PdfWriter) writer).getDirectContent();
                        PdfTemplate tp = cb.createTemplate(image_w, image_h);
                        Graphics2D g2 = tp.createGraphics(image_w, image_h, mapper);
                        tp.setWidth(image_w);
                        tp.setHeight(image_h);
                        g2.scale(scale, scale);
                        g2.translate(-i * image_w / scale, -j * image_h / scale);
                        canvas.paintComponent(g2);
                        g2.dispose();
                        img = new ImgTemplate(tp);
                        document.add(img);
                    }
                }
            } else {
                Image img;
                PdfContentByte cb = ((PdfWriter) writer).getDirectContent();
                PdfTemplate tp = cb.createTemplate(image_w, image_h);
                Graphics2D g2 = tp.createGraphics(image_w, image_h, mapper);
                tp.setWidth(image_w);
                tp.setHeight(image_h);
                g2.scale(scale, scale);
                canvas.paintComponent(g2);
                g2.dispose();
                img = new ImgTemplate(tp);
                document.add(img);
            }
            canvas.setBackground(bgsave);
            canvas.editable = edsave;
            document.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void printGraphPdf(DocWriter writer, CanvasCommon canvas, Graph process, Rectangle page_size,
            String type, String filename, Chapter chapter, int chapter_number) throws Exception {
        Dimension graphsize = process.getGraphSize();
        // we create a fontMapper and read all the fonts in the font directory
        DefaultFontMapper mapper = new DefaultFontMapper();
        FontFactory.registerDirectories();
        mapper.insertDirectory("c:\\winnt\\fonts");
        // mapper.insertDirectory("c:\\windows\\fonts");
        // we create a template and a Graphics2D object that corresponds with it
        int w, h;
        float scale;
        if ((float) graphsize.width < page_size.getWidth() * 0.8
                && (float) graphsize.height < page_size.getHeight() * 0.8 || type.equals(HTML)) {
            w = graphsize.width + 36;
            h = graphsize.height + 36;
            scale = -1f;
        } else {
            scale = page_size.getWidth() * 0.8f / (float) graphsize.width;
            if (scale > page_size.getHeight() * 0.8f / (float) graphsize.height)
                scale = page_size.getHeight() * 0.8f / (float) graphsize.height;
            w = (int) (graphsize.width * scale) + 36;
            h = (int) (graphsize.height * scale) + 36;
        }
        Image img;
        int zoomSave = process.zoom;
        process.zoom = 100;
        Color bgsave = canvas.getBackground();
        boolean edsave = canvas.editable;
        canvas.editable = false;
        canvas.setBackground(Color.white);

        if (type.equals(PDF)) {
            PdfContentByte cb = ((PdfWriter) writer).getDirectContent();
            PdfTemplate tp = cb.createTemplate(w, h);
            Graphics2D g2 = tp.createGraphics(w, h, mapper);
            if (scale > 0)
                g2.scale(scale, scale);
            tp.setWidth(w);
            tp.setHeight(h);
            canvas.paintComponent(g2);
            g2.dispose();
            // cb.addTemplate(tp, 50, 400);
            img = new ImgTemplate(tp);
        } else {
            String imgfilename = filename + "." + process.getName() + "_ch" + chapter_number + ".jpg";
            printImage(imgfilename, -1f, canvas, graphsize);
            img = Image.getInstance(imgfilename);
            if (scale > 0)
                img.scalePercent(scale * 100);
        }
        process.zoom = zoomSave;
        canvas.setBackground(bgsave);
        canvas.editable = edsave;
        if (img != null)
            chapter.add(img);
    }

    private void printBoldParagraphsPdf(Section section, String content) {
        if (content == null || content.length() == 0)
            return;
        String[] details = content.split("\n");
        String tmp = null;
        for (int j = 0; j < details.length; j++) {
            if (details[j].length() == 0) {
                if (tmp != null)
                    section.add(new Paragraph(tmp + "\n", boldFont));
                tmp = null;
            } else if (tmp == null) {
                tmp = details[j];
            } else {
                tmp += " " + details[j];
            }
        }
        if (tmp != null) {
            section.add(new Paragraph(tmp + "\n", boldFont));
        }
    }

    private void printHtmlParagraphsPdf(Section section, String content, int parentLevel) throws Exception {
        if (content == null || content.length() == 0)
            return;
        if (isBase64Encoded(content) || "true".equals(System.getProperty("mdw.designer.force.msword"))) {
            byte[] docBytes = decodeBase64(content);
            content = new DocxBuilder(docBytes).toHtml();
            content = content.replaceAll("&nbsp;", "&#160;");
        }
        JEditorPane documentation = new JEditorPane();
        documentation.setContentType("text/html");
        documentation.setText(content);
        javax.swing.text.Document swingdoc = documentation.getDocument();
        Element[] elements = swingdoc.getRootElements();
        boolean useGenerate = true;
        if (useGenerate) {
            for (Element e : elements) {
                Object gen = generateElementHtml(e, 0, normalFont);
                addSectionContentPdf(section, gen, parentLevel);
            }
        } else { // use print
            for (Element e : elements) {
                printElementHtml(e, section, 0, normalFont, parentLevel);
            }
        }
    }

    class TempSectionPdf {
        Paragraph title;

        TempSectionPdf(Paragraph title) {
            this.title = title;
        }
    }

    private void addSectionContentPdf(Section section, Object one, int parent_level) {
        if (one == null)
            return;
        if (one instanceof List<?>) {
            for (Object two : (List<?>) one) {
                addSectionContentPdf(section, two, parent_level);
            }
        } else if (one instanceof TempSectionPdf) {
            section.addSection(((TempSectionPdf) one).title, parent_level == 0 ? 0 : (parent_level + 1));
        } else {
            section.add(one);
        }
    }

    private void printAttributesPdf(Section section, List<AttributeVO> attrs, int parentLevel) {
        Paragraph sTitle = new Paragraph("Activity Attributes", subSectionFont);
        Section subsection = section.addSection(sTitle, parentLevel == 0 ? 0 : (parentLevel + 1));
        com.lowagie.text.List list = new com.lowagie.text.List(false, false, 20.0f);
        for (AttributeVO attr : attrs) {
            if (excludeAttribute(attr.getAttributeName(), attr.getAttributeValue()))
                continue;
            Phrase phrase = new Phrase();
            phrase.add(new Chunk(attr.getAttributeName(), fixedWidthFont));
            String v = attr.getAttributeValue();
            if (v == null)
                v = "";
            phrase.add(new Chunk(": " + v, normalFont));
            list.add(new ListItem(phrase));
        }
        subsection.add(list);
    }

    private void printVariablesPdf(Chapter chapter, List<VariableVO> variables, int parentLevel) {
        Paragraph sTitle = new Paragraph("Process Variables", sectionFont);
        Section section = chapter.addSection(sTitle, parentLevel == 0 ? 0 : (parentLevel + 1));
        com.lowagie.text.List list = new com.lowagie.text.List(false, false, 10.0f);
        for (VariableVO var : variables) {
            Phrase phrase = new Phrase();
            phrase.add(new Chunk(var.getVariableName(), fixedWidthFont));
            String v = var.getVariableType();
            if (var.getDescription() != null)
                v += " (" + var.getDescription() + ")";
            phrase.add(new Chunk(": " + v + "\n", normalFont));
            list.add(new ListItem(phrase));
        }
        section.add(list);
    }

    private boolean excludeAttribute(String name, String value) {
        if (value == null || value.isEmpty())
            return true;
        if (excludedAttributes.contains(name))
            return true;
        if (value.equals(excludedAttributesForSpecificValues.get(name)))
            return true;
        return false;
    }

    private boolean isBase64Encoded(String input) {
        return input != null && input.length() > 20 && !input.startsWith("<html>")
                && input.matches("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$");
    }

    private byte[] decodeBase64(String inputString) throws Exception {
        // avoid Designer Classic dependency
        return (byte[]) Class.forName("org.apache.commons.codec.binary.Base64")
                .getMethod("decodeBase64", byte[].class).invoke(null, inputString.getBytes());
    }
}