fenix.planner.pdf.PDFGenerator.java Source code

Java tutorial

Introduction

Here is the source code for fenix.planner.pdf.PDFGenerator.java

Source

/*
 * Fenix Planner
 * Copyright (C) 2013 Petter Holmstrm
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package fenix.planner.pdf;

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.List;
import com.itextpdf.text.ListItem;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BadPdfFormatException;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfCopy;
import com.itextpdf.text.pdf.PdfImportedPage;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfWriter;
import fenix.planner.ApplicationInfo;
import fenix.planner.model.Event;
import fenix.planner.model.Organizer;
import fenix.planner.model.Program;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * TODO Document me and translate strings
 *
 * @author peholmst
 */
public class PDFGenerator {

    private final Program program;
    private final Locale locale;
    private Document document;
    private static final float LOGO_HEIGHT = 70;
    private static final float LOGO_WIDTH = 70;
    private static final float LEFT_MARGIN = 36;
    private static final float RIGHT_MARGIN = 36;
    private static final float TOP_MARGIN = 100;
    private static final float BOTTOM_MARGIN = 64;
    private static final float ART_BOX_LEFT_MARGIN = 36;
    private static final float ART_BOX_RIGHT_MARGIN = 36;
    private static final float ART_BOX_TOP_MARGIN = 36;
    private static final float ART_BOX_BOTTOM_MARGIN = 36;
    private static final Font headerFont = new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD, BaseColor.BLACK);
    private static final Font subjectFont = new Font(Font.FontFamily.HELVETICA, 12, Font.NORMAL);
    private static final Font descriptionFont = new Font(Font.FontFamily.HELVETICA, 10, Font.ITALIC);
    private static final Font footerFont = new Font(Font.FontFamily.HELVETICA, 10, Font.NORMAL);
    private static final Font departmentNameFont = new Font(Font.FontFamily.HELVETICA, 14, Font.NORMAL);
    private static final Font sectionNameFont = new Font(Font.FontFamily.HELVETICA, 12, Font.NORMAL);
    private static final Font headingFont = new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD);
    private static final Font authorFont = new Font(Font.FontFamily.HELVETICA, 12, Font.NORMAL);
    private static final Font programFont = new Font(Font.FontFamily.HELVETICA, 7, Font.NORMAL, BaseColor.GRAY);
    private static final Font organizerFont = new Font(Font.FontFamily.HELVETICA, 10, Font.NORMAL);
    private static final Font freeTextFont = new Font(Font.FontFamily.HELVETICA, 12, Font.NORMAL);
    private static final Font subHeaderFont = new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD);

    public PDFGenerator(Program program, Locale locale) {
        this.program = program;
        this.locale = locale;
    }

    public void generate(OutputStream output) {
        final Rectangle pageSize = PageSize.A4;
        final ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();

        document = new Document(pageSize, LEFT_MARGIN, RIGHT_MARGIN, TOP_MARGIN, BOTTOM_MARGIN);
        try {
            final PdfWriter writer = PdfWriter.getInstance(document, outputBuffer);
            writer.setBoxSize("art", new Rectangle(ART_BOX_LEFT_MARGIN, ART_BOX_BOTTOM_MARGIN,
                    pageSize.getRight() - ART_BOX_RIGHT_MARGIN, pageSize.getTop() - ART_BOX_TOP_MARGIN));
            writer.setPageEvent(new HeaderFooter());
            document.open();
            createAndAddForeword();
            createAndAddEventTable();
            createAndAddAfterword();
            createAndAddOrganizerTable();
            document.close();
            writer.close();

            // Loop through the document again to add the missing page numbers
            document = new Document();
            final PdfCopy copy = new PdfCopy(document, output);
            document.open();
            final PdfReader reader = new PdfReader(outputBuffer.toByteArray());
            addPageNumbers(reader, copy);
            document.close();
            reader.close();
        } catch (DocumentException | IOException ex) {
            throw new PDFGenerationException("Error generating PDF", ex);
        }
    }

    private void createAndAddForeword() throws DocumentException {
        if (program.getForeword().length() > 0) {
            parseAndAddFreeText(program.getForeword());
        } else {
            System.out.println("No foreword found");
        }
    }

    private void createAndAddAfterword() throws DocumentException {
        if (program.getForeword().length() > 0) {
            parseAndAddFreeText(program.getAfterword());
        } else {
            System.out.println("No afterword found");
        }
    }

    private void parseAndAddFreeText(String text) throws DocumentException {
        List currentList = null;
        for (String line : text.split("\n")) {
            if (line.startsWith("- ")) {
                if (currentList == null) {
                    currentList = new List(false, 10f);
                    currentList.setListSymbol("\u2022");
                }
                ListItem item = new ListItem();
                currentList.add(item);
                parseAndAddBodyTextLineToParagraph(item, line.substring(2), freeTextFont);
            } else {
                if (currentList != null && !currentList.isEmpty()) {
                    currentList.getLastItem().setSpacingAfter(5);
                    currentList.getFirstItem().setSpacingBefore(5);
                    document.add(currentList);
                    currentList = null;
                }
                Paragraph p = new Paragraph();
                p.setSpacingAfter(5);
                p.setSpacingBefore(5);
                parseAndAddBodyTextLineToParagraph(p, line, freeTextFont);
                document.add(p);
            }
        }
    }

    private void parseAndAddBodyTextLineToParagraph(Paragraph paragraph, String line, Font font) {
        // TODO add support for additional styles
        // TODO add better error checking and reporting
        Font currentFont = new Font(font);
        StringBuilder sb = new StringBuilder();
        for (char c : line.toCharArray()) {
            if (c == '*') {
                if (sb.length() > 0) {
                    paragraph.add(new Phrase(sb.toString(), currentFont));
                    sb = new StringBuilder();
                }
                if (currentFont.isBold()) {
                    currentFont = deriveWithStyle(currentFont, currentFont.getStyle() & ~Font.BOLD);
                } else {
                    currentFont = deriveWithStyle(currentFont, currentFont.getStyle() | Font.BOLD);
                }
            } else if (c == '_') {
                if (sb.length() > 0) {
                    paragraph.add(new Phrase(sb.toString(), currentFont));
                    sb = new StringBuilder();
                }
                if (currentFont.isItalic()) {
                    currentFont = deriveWithStyle(currentFont, currentFont.getStyle() & ~Font.ITALIC);
                } else {
                    currentFont = deriveWithStyle(currentFont, currentFont.getStyle() | Font.ITALIC);
                }
            } else {
                sb.append(c);
            }
        }
        paragraph.add(new Phrase(sb.toString(), currentFont));
    }

    private static Font deriveWithStyle(Font original, int newStyle) {
        final Font copy = new Font(original);
        copy.setStyle(newStyle);
        return copy;
    }

    private void createAndAddEventTable() throws DocumentException {
        PdfPTable table = new PdfPTable(new float[] { 1, 7, 1.2f });
        table.setWidthPercentage(100f);
        table.getDefaultCell().setUseAscender(true);
        table.getDefaultCell().setUseDescender(true);
        table.setSpacingBefore(20);
        table.setSpacingAfter(20);

        // Header row
        table.getDefaultCell().setBorderWidthLeft(0);
        table.getDefaultCell().setBorderWidthTop(0);
        table.getDefaultCell().setBorderWidthRight(0);
        table.getDefaultCell().setPadding(5);
        table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
        table.addCell(new Phrase("Datum", headerFont));
        table.addCell(new Phrase("mne", headerFont));
        table.addCell(new Phrase("Ansvarig", headerFont));
        table.setHeaderRows(1);
        table.getDefaultCell().setBackgroundColor(null);

        // Events
        for (Event event : program.getSortedCopyOfEvents()) {
            if (event.getDate().getMonthOfYear() % 2 == 0) {
                table.getDefaultCell().setBackgroundColor(new BaseColor(0xdd, 0xdd, 0xdd));
            } else {
                table.getDefaultCell().setBackgroundColor(null);
            }

            if (!event.getType().getBackgroundColor().equals(Color.WHITE)) {
                table.getDefaultCell()
                        .setBackgroundColor(awtColorToBaseColor(event.getType().getBackgroundColor()));
            }

            final BaseColor textColor = awtColorToBaseColor(event.getType().getForegroundColor());

            PdfPCell dateCell = new PdfPCell(table.getDefaultCell());
            dateCell.addElement(
                    new Phrase(event.getDate().toString("dd.MM", locale), changeColor(subjectFont, textColor)));
            table.addCell(dateCell);

            PdfPCell subjectCell = new PdfPCell(table.getDefaultCell());
            subjectCell.addElement(new Phrase(event.getSubject(), changeColor(subjectFont, textColor)));
            if (event.getDescription().length() > 0) {
                subjectCell.addElement(new Phrase(event.getDescription(), changeColor(descriptionFont, textColor)));
            }
            if (event.getOrganizer() == null) {
                subjectCell.setColspan(2);
            }
            table.addCell(subjectCell);

            if (event.getOrganizer() != null) {
                PdfPCell organizerCell = new PdfPCell(table.getDefaultCell());
                organizerCell.addElement(
                        new Phrase(event.getOrganizer().getInitials(), changeColor(subjectFont, textColor)));
                table.addCell(organizerCell);
            }
        }

        document.add(table);
    }

    private void createAndAddOrganizerTable() throws DocumentException {
        PdfPTable table = new PdfPTable(new float[] { 1, 3, 4, 2 });
        table.setSpacingBefore(20);

        table.setWidthPercentage(100f);
        table.getDefaultCell().setUseAscender(true);
        table.getDefaultCell().setUseDescender(true);

        // Header row
        table.getDefaultCell().setBorder(0);
        table.getDefaultCell().setPadding(5);
        table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
        table.getDefaultCell().setBackgroundColor(null);

        for (Organizer organizer : program.getSortedCopyOfOrganizers()) {
            table.addCell(new Phrase(organizer.getInitials(), organizerFont));
            table.addCell(new Phrase(organizer.getFullName(), organizerFont));
            table.addCell(new Phrase(organizer.getEmail(), organizerFont));
            table.addCell(new Phrase(organizer.getPhoneNumber(), organizerFont));
        }

        document.add(table);
    }

    private static Font changeColor(Font original, BaseColor newColor) {
        Font copy = new Font(original);
        copy.setColor(newColor);
        return copy;
    }

    private static BaseColor awtColorToBaseColor(Color awtColor) {
        return new BaseColor(awtColor.getRGB());
    }

    private void addPageNumbers(PdfReader reader, PdfCopy copy) {
        int pageCount = reader.getNumberOfPages();
        PdfImportedPage page;
        PdfCopy.PageStamp stamp;

        for (int i = 1; i <= pageCount; ++i) {
            Rectangle rect = reader.getBoxSize(i, "art");
            page = copy.getImportedPage(reader, i);
            stamp = copy.createPageStamp(page);
            // add page numbers
            ColumnText.showTextAligned(stamp.getUnderContent(), Element.ALIGN_RIGHT,
                    new Phrase(String.format("%d / %d", i, pageCount), footerFont), rect.getRight(),
                    rect.getBottom() + 5, 0);
            try {
                stamp.alterContents();
                copy.addPage(page);
            } catch (BadPdfFormatException | IOException ex) {
                throw new PDFGenerationException("Error adding page number to page " + i, ex);
            }
        }
    }

    private class HeaderFooter extends PdfPageEventHelper {

        private final String currentDate;
        private final String programNameAndVersion;
        private Image logo;

        public HeaderFooter() {
            DateFormat format = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
            currentDate = format.format(new Date());
            logo = null;
            if (program.getHeader().hasLogo()) {
                try {
                    logo = Image.getInstance(program.getHeader().getLogo(), null);
                    logo.scaleToFit(LOGO_WIDTH, LOGO_HEIGHT);
                } catch (BadElementException | IOException ex) {
                    throw new PDFGenerationException("Error creating PDF image from logo", ex);
                }
            }
            programNameAndVersion = String.format("%s %s", ApplicationInfo.getTitle(),
                    ApplicationInfo.getVersion());
        }

        @Override
        public void onEndPage(PdfWriter writer, Document document) {
            writeHeader(writer, document);
            writeFooter(writer, document);
        }

        private void writeHeader(PdfWriter writer, Document document) {
            final Rectangle rect = writer.getBoxSize("art");

            float textXOffset = 0;
            if (logo != null) {
                try {
                    logo.setAbsolutePosition(rect.getLeft(),
                            rect.getTop() - logo.getScaledHeight() + departmentNameFont.getSize());
                    writer.getDirectContent().addImage(logo);
                    textXOffset = logo.getScaledWidth() + 10;
                } catch (DocumentException ex) {
                    throw new PDFGenerationException("Error adding logo to PDF page", ex);
                }
            }

            ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT,
                    new Phrase(program.getHeader().getDepartmentName(), departmentNameFont),
                    rect.getLeft() + textXOffset, rect.getTop(), 0);
            ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT,
                    new Phrase(program.getHeader().getSectionName(), sectionNameFont), rect.getLeft() + textXOffset,
                    rect.getTop() - 14, 0);
            ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT,
                    new Phrase(program.getHeader().getHeading().toUpperCase(), headingFont),
                    rect.getLeft() + textXOffset, rect.getTop() - 40, 0);
            ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_RIGHT,
                    new Phrase(program.getHeader().getAuthorInitials(), authorFont), rect.getRight(),
                    rect.getTop() - 40, 0);
        }

        private void writeFooter(PdfWriter writer, Document document) {
            final Rectangle rect = writer.getBoxSize("art");
            ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT,
                    new Phrase(String.format("Utskrivet %s", currentDate), footerFont), rect.getLeft(),
                    rect.getBottom() + 5, 0);
            ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT,
                    new Phrase("Skapad med " + programNameAndVersion, programFont), rect.getLeft(),
                    rect.getBottom() - 7, 0);
        }
    }
}