1 /*
2  * Copyright 2008-2019 by Emeric Vernat
3  *
4  *     This file is part of Java Melody.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.bull.javamelody.internal.web.pdf;
19
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.util.HashMap;
23 import java.util.Locale;
24 import java.util.Map;
25
26 import com.lowagie.text.BadElementException;
27 import com.lowagie.text.ChapterAutoNumber;
28 import com.lowagie.text.Chunk;
29 import com.lowagie.text.Document;
30 import com.lowagie.text.DocumentException;
31 import com.lowagie.text.Element;
32 import com.lowagie.text.Font;
33 import com.lowagie.text.HeaderFooter;
34 import com.lowagie.text.Image;
35 import com.lowagie.text.PageSize;
36 import com.lowagie.text.Paragraph;
37 import com.lowagie.text.Phrase;
38 import com.lowagie.text.Rectangle;
39 import com.lowagie.text.pdf.BaseFont;
40 import com.lowagie.text.pdf.PdfContentByte;
41 import com.lowagie.text.pdf.PdfPageEventHelper;
42 import com.lowagie.text.pdf.PdfTemplate;
43 import com.lowagie.text.pdf.PdfWriter;
44
45 import net.bull.javamelody.internal.common.I18N;
46 import net.bull.javamelody.internal.common.Parameters;
47 import net.bull.javamelody.internal.model.Range;
48
49 /**
50  * Factory pour les documents pdf.
51  * @author Emeric Vernat
52  */

53 class PdfDocumentFactory {
54     private final String application;
55     private final Range range;
56     private final OutputStream output;
57     private final Map<String, Image> paragraphImagesByResourceName = new HashMap<>();
58     private final Map<String, Image> smallImagesByResourceName = new HashMap<>();
59     private final Font paragraphTitleFont = PdfFonts.PARAGRAPH_TITLE.getFont();
60
61     private static class PdfAdvancedPageNumberEvents extends PdfPageEventHelper {
62         // This is the contentbyte object of the writer
63         private PdfContentByte cb;
64
65         // we will put the final number of pages in a template
66         private PdfTemplate template;
67
68         // this is the BaseFont we are going to use for the header / footer
69         private final BaseFont bf;
70
71         PdfAdvancedPageNumberEvents() throws DocumentException, IOException {
72             super();
73             bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
74         }
75
76         // we override the onGenericTag method
77         /** {@inheritDoc} */
78         @Override
79         public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {
80             // rien ici
81         }
82
83         // we override the onOpenDocument method
84         /** {@inheritDoc} */
85         @Override
86         public void onOpenDocument(PdfWriter writer, Document document) {
87             cb = writer.getDirectContent();
88             template = cb.createTemplate(50, 50);
89         }
90
91         // we override the onChapter method
92         /** {@inheritDoc} */
93         @Override
94         public void onChapter(PdfWriter writer, Document document, float paragraphPosition,
95                 Paragraph title) {
96             // rien ici
97         }
98
99         // we override the onEndPage method
100         /** {@inheritDoc} */
101         @Override
102         public void onEndPage(PdfWriter writer, Document document) {
103             final int pageN = writer.getPageNumber();
104             final String text = pageN + " / ";
105             final float len = bf.getWidthPoint(text, 8);
106             cb.beginText();
107             cb.setFontAndSize(bf, 8);
108             final float width = document.getPageSize().getWidth();
109             cb.setTextMatrix(width / 2, 30);
110             cb.showText(text);
111             cb.endText();
112             cb.addTemplate(template, width / 2 + len, 30);
113         }
114
115         // we override the onCloseDocument method
116         /** {@inheritDoc} */
117         @Override
118         public void onCloseDocument(PdfWriter writer, Document document) {
119             template.beginText();
120             template.setFontAndSize(bf, 8);
121             template.showText(String.valueOf(writer.getPageNumber() - 1));
122             template.endText();
123         }
124     }
125
126     PdfDocumentFactory(String application, Range range, OutputStream output) {
127         super();
128         assert application != null;
129         assert output != null;
130         // range peut être null
131         this.application = application;
132         this.range = range;
133         this.output = output;
134     }
135
136     Document createDocument() throws DocumentException, IOException {
137         return createDocument(false);
138     }
139
140     Document createDocument(boolean landscape) throws DocumentException, IOException {
141         // creation of a document-object
142         final Rectangle pageSize = getPageSize(landscape);
143         // marges de 20 à gauche, à droite et en haut pour bien utiliser la largeur
144         // et avoir une meilleur lisibilité sur les tableaux larges,
145         // mais marge de 40 en bas pour ne pas empiéter sur les numéros de pages
146         final Document document = new Document(pageSize, 20, 20, 20, 40);
147
148         final String title;
149         if (range == null) {
150             title = I18N.getFormattedString("Monitoring_sur", application);
151         } else {
152             title = I18N.getFormattedString("Monitoring_sur", application) + " - "
153                     + range.getLabel();
154         }
155         createWriter(document, title);
156
157         // we add some meta information to the document (after writer)
158         document.addAuthor(application);
159         document.addCreator(
160                 "JavaMelody par E. Vernat, https://github.com/javamelody/javamelody/wiki");
161         document.addTitle(title);
162         return document;
163     }
164
165     private Rectangle getPageSize(boolean landscape) {
166         Rectangle pageSize;
167         if (Locale.US.getCountry().equals(I18N.getCurrentLocale().getCountry())) {
168             // Letter size paper is used in the US instead of the ISO standard A4
169             pageSize = PageSize.LETTER;
170         } else {
171             pageSize = PageSize.A4;
172         }
173         if (landscape) {
174             pageSize = pageSize.rotate();
175         }
176         return pageSize;
177     }
178
179     // We create a writer that listens to the document and directs a PDF-stream to output
180     private void createWriter(Document document, String title)
181             throws DocumentException, IOException {
182         final PdfWriter writer = PdfWriter.getInstance(document, output);
183         //writer.setViewerPreferences(PdfWriter.PageLayoutTwoColumnLeft);
184
185         // title
186         final HeaderFooter header = new HeaderFooter(new Phrase(title), false);
187         header.setAlignment(Element.ALIGN_LEFT);
188         header.setBorder(Rectangle.NO_BORDER);
189         document.setHeader(header);
190
191         // simple page numbers : x
192         //HeaderFooter footer = new HeaderFooter(new Phrase(), true);
193         //footer.setAlignment(Element.ALIGN_RIGHT);
194         //footer.setBorder(Rectangle.TOP);
195         //document.setFooter(footer);
196
197         // add the event handler for advanced page numbers : x/y
198         writer.setPageEvent(new PdfAdvancedPageNumberEvents());
199     }
200
201     Element createParagraphElement(String paragraphTitle, String iconName)
202             throws DocumentException, IOException {
203         final Paragraph paragraph = new Paragraph("", paragraphTitleFont);
204         paragraph.setSpacingBefore(5);
205         paragraph.setSpacingAfter(5);
206         if (iconName != null) {
207             paragraph.add(new Chunk(getParagraphImage(iconName), 0, -5));
208         }
209         final Phrase element = new Phrase(' ' + paragraphTitle, paragraphTitleFont);
210         element.setLeading(12);
211         paragraph.add(element);
212         // chapter pour avoir la liste des signets
213         final ChapterAutoNumber chapter = new ChapterAutoNumber(paragraph);
214         // sans numéro de chapitre
215         chapter.setNumberDepth(0);
216         chapter.setBookmarkOpen(false);
217         chapter.setTriggerNewPage(false);
218         return chapter;
219     }
220
221     private Image getParagraphImage(String resourceFileName) throws DocumentException, IOException {
222         Image image = paragraphImagesByResourceName.get(resourceFileName);
223         if (image == null) {
224             image = getImage(resourceFileName);
225             image.scaleAbsolute(16, 16);
226             paragraphImagesByResourceName.put(resourceFileName, image);
227         }
228         return image;
229     }
230
231     Image getSmallImage(String resourceFileName) throws DocumentException, IOException {
232         Image image = smallImagesByResourceName.get(resourceFileName);
233         if (image == null) {
234             image = getImage(resourceFileName);
235             image.scaleAbsolute(8, 8);
236             smallImagesByResourceName.put(resourceFileName, image);
237         }
238         return image;
239     }
240
241     static Image getImage(String resourceFileName) throws BadElementException, IOException {
242         return Image.getInstance(
243                 PdfDocumentFactory.class.getResource(Parameters.getResourcePath(resourceFileName)));
244     }
245 }
246