/* GanttProject is an opensource project management tool. License: GPL3 Copyright (C) 2011-2012 GanttProject Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.ganttproject.impex.htmlpdf.itext; import biz.ganttproject.core.model.task.TaskDefaultColumn; import biz.ganttproject.core.option.BooleanOption; import biz.ganttproject.core.option.DefaultBooleanOption; import biz.ganttproject.core.option.DefaultEnumerationOption; import biz.ganttproject.core.option.EnumerationOption; import biz.ganttproject.core.option.GPOptionGroup; import biz.ganttproject.core.table.ColumnList; import biz.ganttproject.core.table.ColumnList.Column; 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.PageSize; import com.itextpdf.text.Paragraph; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import com.itextpdf.text.pdf.PdfPageEvent; import com.itextpdf.text.pdf.PdfWriter; import net.sourceforge.ganttproject.IGanttProject; import net.sourceforge.ganttproject.ResourceDefaultColumn; import net.sourceforge.ganttproject.export.ExportException; import net.sourceforge.ganttproject.export.ExporterBase; import net.sourceforge.ganttproject.export.TaskVisitor; import net.sourceforge.ganttproject.gui.UIFacade; import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder; import net.sourceforge.ganttproject.gui.options.OptionsPageBuilder.I18N; import net.sourceforge.ganttproject.language.GanttLanguage; import net.sourceforge.ganttproject.language.LanguageOption; import net.sourceforge.ganttproject.resource.HumanResource; import net.sourceforge.ganttproject.roles.Role; import net.sourceforge.ganttproject.task.Task; import org.ganttproject.impex.htmlpdf.PropertyFetcher; import org.ganttproject.impex.htmlpdf.StylesheetImpl; import org.ganttproject.impex.htmlpdf.fonts.TTFontCache; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.net.URL; import java.text.DateFormat; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; /** * Implements Sortavala iText theme. * * @author dbarashev (Dmitry Barashev) */ class ThemeImpl extends StylesheetImpl implements PdfPageEvent, ITextStylesheet { private static List<String> ourSizes = new ArrayList<>(); static { for (Field field : PageSize.class.getDeclaredFields()) { if (field.getType().equals(Rectangle.class)) { ourSizes.add(field.getName()); } } } private static final BaseColor SORTAVALA_GREEN = new BaseColor(0x66, 0x99, 0x99); private Document myDoc; private PdfWriter myWriter; private IGanttProject myProject; private UIFacade myUIFacade; private String myLeftSubcolontitle; private final BooleanOption myShowNotesOption = new DefaultBooleanOption("export.itext.showNotes"); private final BooleanOption myLandscapeOption = new DefaultBooleanOption("export.itext.landscape"); private final EnumerationOption myPageSizeOption = new DefaultEnumerationOption<String>("export.itext.pageSize", ourSizes); private final LanguageOption myLanguageOption = new LanguageOption() { { setSelectedValue(GanttLanguage.getInstance().getLocale()); } @Override protected void applyLocale(Locale locale) { } }; private final GPOptionGroup myPageOptions = new GPOptionGroup("export.itext.page", myPageSizeOption, myLandscapeOption); private final GPOptionGroup myDataOptions = new GPOptionGroup("export.itext.data", myShowNotesOption); private boolean isColontitleEnabled = false; private final Properties myProperties; private FontSubstitutionModel mySubstitutionModel; private final ExporterBase myExporter; private final TTFontCache myFontCache; ThemeImpl(URL url, String localizedName, ExporterBase exporter, TTFontCache fontCache) { super(url, localizedName + " (iText)"); myFontCache = fontCache; myExporter = exporter; myProperties = new Properties(); try { myProperties.load(url.openStream()); } catch (IOException e) { throw new RuntimeException(e); } myFontCache.setProperties(myProperties); I18N i18n = new OptionsPageBuilder.I18N(); GPOptionGroup languageOptions = new GPOptionGroup("export.itext.language", myLanguageOption); languageOptions.setI18Nkey(i18n.getCanonicalOptionGroupLabelKey(languageOptions), "language"); languageOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myLanguageOption), "language"); myDataOptions.setI18Nkey(i18n.getCanonicalOptionGroupLabelKey(myDataOptions), "show"); myDataOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myShowNotesOption), "notes"); myDataOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myShowNotesOption) + ".yes", "yes"); myDataOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myShowNotesOption) + ".no", "no"); myPageOptions.setI18Nkey(i18n.getCanonicalOptionGroupLabelKey(myPageOptions), "choosePaperFormat"); myPageOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myLandscapeOption) + ".yes", "landscape"); myPageOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myLandscapeOption) + ".no", "portrait"); myPageOptions.setI18Nkey(i18n.getCanonicalOptionLabelKey(myPageSizeOption), "paperSize"); myPageOptions.lock(); myDataOptions.lock(); myPageSizeOption.setValue("A4"); myShowNotesOption.loadPersistentValue("true"); myLandscapeOption.loadPersistentValue("true"); myPageOptions.commit(); myDataOptions.commit(); } GPOptionGroup[] getOptions() { return new GPOptionGroup[] { myDataOptions, myPageOptions }; } protected IGanttProject getProject() { return myProject; } private UIFacade getUIFacade() { return myUIFacade; } // ///////////////////////////////////// // ITextStylesheet @Override public List<String> getFontFamilies() { return Collections.singletonList(getOriginalFontName()); } @Override public void setFontSubstitutionModel(FontSubstitutionModel model) { mySubstitutionModel = model; } private String getOriginalFontName() { return myProperties.getProperty("font-family"); } private String getFontName() { return mySubstitutionModel.getSubstitution(getOriginalFontName()).getSubstitutionFamily(); } private Font getSansRegular(float size) { return myFontCache.getFont(getFontName(), getCharset(), Font.NORMAL, size); } private Font getSansItalic(float size) { return myFontCache.getFont(getFontName(), getCharset(), Font.ITALIC, size); } private Font getSansRegularBold(float size) { return myFontCache.getFont(getFontName(), getCharset(), Font.BOLD, size); } protected Font getSansRegularBold() { return getSansRegularBold(12); } private String getCharset() { return myProperties.getProperty("charset", GanttLanguage.getInstance().getCharSet()); } private String i18n(String key) { String value = myProperties.getProperty(key); if (value != null) { return value; } // Locale selectedLocale = myLanguageOption.getSelectedValue(); // if (selectedLocale != null) { // value = GanttLanguage.getInstance().getText("impex.pdf.theme.sortavala." + key, selectedLocale); // } // if (value != null) { // return value; // } value = GanttLanguage.getInstance().getText("impex.pdf.theme.sortavala." + key); return value == null ? key : value; } void run(IGanttProject project, UIFacade facade, OutputStream out) throws ExportException { myProject = project; myUIFacade = facade; Rectangle pageSize = PageSize.getRectangle(myPageSizeOption.getValue()); if (myLandscapeOption.isChecked()) { pageSize = pageSize.rotate(); } myDoc = new Document(pageSize, 20, 20, 70, 40); try { myWriter = PdfWriter.getInstance(myDoc, out); myWriter.setPageEvent(this); myDoc.open(); writeProject(); } catch (Throwable e) { e.printStackTrace(); throw new ExportException("Export failed", e); } finally { myDoc.close(); } } private void writeProject() throws Exception { writeTitlePage(); myDoc.newPage(); isColontitleEnabled = true; myLeftSubcolontitle = i18n("title.tasks"); writeTasks(); myDoc.newPage(); myLeftSubcolontitle = i18n("title.resources"); writeResources(); myDoc.newPage(); writeGanttChart(); myDoc.newPage(); writeResourceChart(); } private void writeAttributes(PdfPTable table, LinkedHashMap<String, String> attrs) { for (Entry<String, String> nextEntry : attrs.entrySet()) { { Paragraph p = new Paragraph(nextEntry.getKey(), getSansRegularBold(12)); PdfPCell cell = new PdfPCell(p); cell.setBorder(PdfPCell.NO_BORDER); table.addCell(cell); } { Paragraph p = new Paragraph(nextEntry.getValue(), getSansRegular(12)); PdfPCell cell = new PdfPCell(p); cell.setBorder(PdfPCell.NO_BORDER); table.addCell(cell); } } } private void writeTitlePage() throws DocumentException { Rectangle page = myDoc.getPageSize(); PdfPTable head = new PdfPTable(1); PdfPTable colontitleTable = createColontitleTable(getProject().getProjectName(), GanttLanguage.getInstance().getMediumDateFormat().format(new Date()), getProject().getOrganization(), getProject().getWebLink()); head.setTotalWidth(page.getWidth() - myDoc.leftMargin() - myDoc.rightMargin()); { PdfPCell cell = new PdfPCell(colontitleTable); cell.setBorder(PdfPCell.NO_BORDER); head.addCell(cell); } addEmptyRow(head, 20); LinkedHashMap<String, String> attrs = new LinkedHashMap<>(); attrs.put(i18n("label.project_manager"), buildManagerString()); attrs.put(i18n("label.dates"), buildProjectDatesString()); attrs.put(" ", " "); attrs.put(i18n("label.completion"), buildProjectCompletionString()); attrs.put(i18n("label.tasks"), String.valueOf(getProject().getTaskManager().getTaskCount())); attrs.put(i18n("label.resources"), String.valueOf(getProject().getHumanResourceManager().getResources().size())); PdfPTable attrsTable = new PdfPTable(2); writeAttributes(attrsTable, attrs); PdfPCell attrsCell = new PdfPCell(attrsTable); attrsCell.setBorder(PdfPCell.NO_BORDER); head.addCell(attrsCell); addEmptyRow(head, 20); if (getProject().getDescription().length() > 0) { Paragraph p = new Paragraph(getProject().getDescription(), getSansRegular(12)); PdfPCell cell = new PdfPCell(p); cell.setBorder(PdfPCell.TOP | PdfPCell.BOTTOM); cell.setBorderColor(SORTAVALA_GREEN); cell.setBorderWidth(1); cell.setPadding(5); cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER); head.addCell(cell); } myDoc.add(head); } private String buildProjectCompletionString() { return String.valueOf(getProject().getTaskManager().getProjectCompletion()) + "%"; } private String buildManagerString() { Role managerRole = getProject().getRoleManager().getRole(myProperties.getProperty("manager-role")); if (managerRole == null) { return ""; } StringBuilder result = new StringBuilder(); String delimiter = ""; List<HumanResource> resources = getProject().getHumanResourceManager().getResources(); for (HumanResource resource : resources) { if (resource.getRole().equals(managerRole)) { result.append(delimiter).append(resource.getName()); delimiter = ", "; } } return result.toString(); } private String buildProjectDatesString() { DateFormat dateFormat = GanttLanguage.getInstance().getMediumDateFormat(); return MessageFormat.format( "{0} - {1}\n", dateFormat.format(getProject().getTaskManager().getProjectStart()), dateFormat.format(getProject().getTaskManager().getProjectEnd())); } private void writeGanttChart() { isColontitleEnabled = false; writeColontitle(getProject().getProjectName(), GanttLanguage.getInstance().getMediumDateFormat().format(new Date()), GanttLanguage.getInstance().getText("ganttChart"), String.valueOf(myWriter.getPageNumber())); ChartWriter ganttChartWriter = new ChartWriter(myUIFacade.getGanttChart(), myWriter, myDoc, myExporter.createExportSettings(), myFontCache, mySubstitutionModel, getCharset()); ganttChartWriter.write(); } private void writeResourceChart() { isColontitleEnabled = false; writeColontitle(getProject().getProjectName(), GanttLanguage.getInstance().getMediumDateFormat().format(new Date()), GanttLanguage.getInstance().getText("resourcesChart"), String.valueOf(myWriter.getPageNumber())); ChartWriter resourceChartWriter = new ChartWriter(myUIFacade.getResourceChart(), myWriter, myDoc, myExporter.createExportSettings(), myFontCache, mySubstitutionModel, getCharset()); resourceChartWriter.write(); } private PdfPTable createTableHeader(ColumnList tableHeader, ArrayList<Column> orderedColumns) { for (int i = 0; i < tableHeader.getSize(); i++) { Column c = tableHeader.getField(i); if (c.isVisible()) { orderedColumns.add(c); } } Collections.sort(orderedColumns, new Comparator<Column>() { @Override public int compare(Column lhs, Column rhs) { if (lhs == null || rhs == null) { return 0; } return lhs.getOrder() - rhs.getOrder(); } }); float[] widths = new float[orderedColumns.size()]; for (int i = 0; i < orderedColumns.size(); i++) { Column column = orderedColumns.get(i); widths[i] = column.getWidth(); } PdfPTable table = new PdfPTable(widths); table.setWidthPercentage(95); for (Column field : orderedColumns) { if (field.isVisible()) { PdfPCell cell = new PdfPCell(new Paragraph(field.getName(), getSansRegularBold(12f))); cell.setPaddingTop(4); cell.setPaddingBottom(4); cell.setPaddingLeft(5); cell.setPaddingRight(5); cell.setBorderWidth(0); cell.setBorder(PdfPCell.BOTTOM); cell.setBorderWidthBottom(1); cell.setBorderColor(SORTAVALA_GREEN); table.addCell(cell); } } table.setHeaderRows(1); return table; } private void addEmptyRow(PdfPTable table, float height) { PdfPCell emptyCell = new PdfPCell(new Paragraph(" ", getSansRegular(height))); emptyCell.setBorderWidth(0); for (int i = 0; i < table.getNumberOfColumns(); i++) { table.addCell(emptyCell); } } private void writeProperties(ArrayList<Column> orderedColumns, Map<String, String> id2value, PdfPTable table, Map<String, PdfPCell> id2cell) { for (Column column : orderedColumns) { PdfPCell cell = id2cell.get(column.getID()); if (cell == null) { String value = id2value.get(column.getID()); if (value == null) { value = ""; } Paragraph p = new Paragraph(value, getSansRegular(12)); cell = new PdfPCell(p); if (TaskDefaultColumn.COST.getStub().getID().equals(column.getID()) || ResourceDefaultColumn.STANDARD_RATE.getStub().getID().equals(column.getID()) || ResourceDefaultColumn.TOTAL_COST.getStub().getID().equals(column.getID())) { cell.setHorizontalAlignment(PdfPCell.ALIGN_RIGHT); } cell.setBorderWidth(0); cell.setPaddingLeft(5); } table.addCell(cell); } } private void writeTasks() throws Exception { ColumnList visibleFields = getUIFacade().getTaskTree().getVisibleFields(); final ArrayList<Column> orderedColumns = new ArrayList<>(); final PdfPTable table = createTableHeader(visibleFields, orderedColumns); TaskVisitor taskVisitor = new TaskVisitor() { int myPreviousChildTaskCount = 0; int myPreviousChildlessTaskCount = 0; PropertyFetcher myTaskProperty = new PropertyFetcher(getProject()); @Override protected String serializeTask(Task t, int depth) throws Exception { boolean addEmptyRow = false; if (depth == 0) { addEmptyRow = myPreviousChildTaskCount > 0; boolean hasNested = getProject().getTaskManager().getTaskHierarchy().hasNestedTasks(t); if (!addEmptyRow) { if (hasNested) { addEmptyRow = myPreviousChildlessTaskCount > 0; myPreviousChildlessTaskCount = 0; } } myPreviousChildTaskCount = 0; if (!hasNested) { myPreviousChildlessTaskCount++; } } else { myPreviousChildTaskCount++; myPreviousChildlessTaskCount = 0; } if (addEmptyRow) { addEmptyRow(table, 10); } HashMap<String, String> id2value = new HashMap<>(); myTaskProperty.getTaskAttributes(t, id2value); HashMap<String, PdfPCell> id2cell = new HashMap<>(); PdfPCell nameCell; if (myShowNotesOption.isChecked() && t.getNotes() != null && !"".equals(t.getNotes())) { nameCell = new PdfPCell(createNameCellContent(t)); } else { nameCell = new PdfPCell(new Paragraph(t.getName(), getSansRegular(12))); } nameCell.setBorderWidth(0); nameCell.setPaddingLeft(5 + depth * 10); id2cell.put("tpd3", nameCell); writeProperties(orderedColumns, id2value, table, id2cell); return ""; } private PdfPTable createNameCellContent(Task t) { PdfPTable table = new PdfPTable(1); Paragraph p = new Paragraph(t.getName(), getSansRegular(12)); PdfPCell cell1 = new PdfPCell(); cell1.setBorder(PdfPCell.NO_BORDER); cell1.setPhrase(p); cell1.setPaddingLeft(0); table.addCell(cell1); Paragraph notes = new Paragraph(t.getNotes(), getSansItalic(8)); PdfPCell cell2 = new PdfPCell(); cell2.setBorder(PdfPCell.NO_BORDER); cell2.setPhrase(notes); cell2.setPaddingLeft(3); table.addCell(cell2); return table; } }; taskVisitor.visit(getProject().getTaskManager()); myDoc.add(table); } private void writeResources() throws Exception { ColumnList visibleFields = getUIFacade().getResourceTree().getVisibleFields(); final ArrayList<Column> orderedColumns = new ArrayList<>(); final PdfPTable table = createTableHeader(visibleFields, orderedColumns); List<HumanResource> resources = getProject().getHumanResourceManager().getResources(); PropertyFetcher propFetcher = new PropertyFetcher(getProject()); for (HumanResource resource : resources) { HashMap<String, String> id2value = new HashMap<>(); propFetcher.getResourceAttributes(resource, id2value); HashMap<String, PdfPCell> id2cell = new HashMap<>(); writeProperties(orderedColumns, id2value, table, id2cell); } myDoc.add(table); } private PdfPTable createColontitleTable(String topLeft, String topRight, String bottomLeft, String bottomRight) { PdfPTable head = new PdfPTable(2); { PdfPCell cell = new PdfPCell(); cell.setBorder(Rectangle.NO_BORDER); Paragraph p = new Paragraph(topLeft, getSansRegularBold(18)); p.setAlignment(Paragraph.ALIGN_LEFT); // colontitle.setLeading(0); cell.setHorizontalAlignment(Element.ALIGN_LEFT); cell.setVerticalAlignment(Element.ALIGN_BOTTOM); // cell.setPaddingLeft(2); cell.setPaddingBottom(6); cell.setPhrase(p); head.addCell(cell); } { PdfPCell cell = new PdfPCell(); cell.setBorder(Rectangle.NO_BORDER); Paragraph p = new Paragraph(topRight, getSansRegularBold(10)); p.setAlignment(Paragraph.ALIGN_RIGHT); cell.setHorizontalAlignment(Element.ALIGN_RIGHT); cell.setVerticalAlignment(Element.ALIGN_BOTTOM); cell.setPaddingBottom(6); cell.setPhrase(p); head.addCell(cell); } { PdfPCell cell = new PdfPCell(); cell.setVerticalAlignment(Element.ALIGN_TOP); cell.setHorizontalAlignment(Element.ALIGN_LEFT); cell.setPaddingLeft(3); cell.setPaddingTop(2); cell.setPaddingBottom(6); cell.setBorder(Rectangle.TOP); cell.setBorderWidthTop(2); cell.setBorderColor(SORTAVALA_GREEN); Paragraph p = new Paragraph(bottomLeft, getSansRegularBold(18)); p.setAlignment(Paragraph.ALIGN_LEFT); p.setExtraParagraphSpace(0); p.setIndentationLeft(0); p.setSpacingBefore(0); cell.setPhrase(p); // cell.addElement(p); head.addCell(cell); } { PdfPCell cell = new PdfPCell(); cell.setVerticalAlignment(Element.ALIGN_TOP); cell.setHorizontalAlignment(Element.ALIGN_RIGHT); cell.setPaddingTop(2); cell.setPaddingBottom(6); cell.setBorder(Rectangle.TOP); cell.setBorderWidthTop(2); cell.setBorderColor(SORTAVALA_GREEN); Paragraph p = new Paragraph(bottomRight, getSansRegularBold(10)); p.setAlignment(Paragraph.ALIGN_RIGHT); cell.setPhrase(p); head.addCell(cell); } final Document document = myDoc; Rectangle page = document.getPageSize(); head.setTotalWidth(page.getWidth() - document.leftMargin() - document.rightMargin()); return head; } private void writeColontitle(String topLeft, String topRight, String bottomLeft, String bottomRight) { final Document document = myDoc; final PdfWriter writer = myWriter; Rectangle page = document.getPageSize(); PdfPTable colontitleTable = createColontitleTable(topLeft, topRight, bottomLeft, bottomRight); colontitleTable.writeSelectedRows(0, -1, document.leftMargin(), page.getHeight() - document.topMargin() + colontitleTable.getTotalHeight(), writer.getDirectContent()); } @Override public void onChapter(PdfWriter arg0, Document arg1, float arg2, Paragraph arg3) { } @Override public void onChapterEnd(PdfWriter arg0, Document arg1, float arg2) { } @Override public void onCloseDocument(PdfWriter arg0, Document arg1) { } @Override public void onEndPage(PdfWriter writer, Document document) { if (isColontitleEnabled) { writeColontitle(getProject().getProjectName(), GanttLanguage.getInstance().getMediumDateFormat().format(new Date()), myLeftSubcolontitle, String.valueOf(writer.getPageNumber())); } } @Override public void onGenericTag(PdfWriter arg0, Document arg1, Rectangle arg2, String arg3) { } @Override public void onOpenDocument(PdfWriter arg0, Document arg1) { } @Override public void onParagraph(PdfWriter arg0, Document arg1, float arg2) { } @Override public void onParagraphEnd(PdfWriter arg0, Document arg1, float arg2) { } @Override public void onSection(PdfWriter arg0, Document arg1, float arg2, int arg3, Paragraph arg4) { } @Override public void onSectionEnd(PdfWriter arg0, Document arg1, float arg2) { } @Override public void onStartPage(PdfWriter writer, Document document) { } }