1 /*
2  * JasperReports - Free Java Reporting Library.
3  * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4  * http://www.jaspersoft.com
5  *
6  * Unless you have purchased a commercial license agreement from Jaspersoft,
7  * the following license terms apply:
8  *
9  * This program is part of JasperReports.
10  *
11  * JasperReports is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * JasperReports is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23  */

24 package net.sf.jasperreports.export.pdf.classic;
25
26 import java.awt.Graphics2D;
27 import java.awt.geom.Rectangle2D;
28 import java.awt.image.BufferedImage;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.text.AttributedCharacterIterator.Attribute;
32 import java.util.HashMap;
33 import java.util.Locale;
34 import java.util.Map;
35
36 import com.lowagie.text.BadElementException;
37 import com.lowagie.text.Chunk;
38 import com.lowagie.text.Document;
39 import com.lowagie.text.DocumentException;
40 import com.lowagie.text.Font;
41 import com.lowagie.text.Image;
42 import com.lowagie.text.Phrase;
43 import com.lowagie.text.Rectangle;
44 import com.lowagie.text.SplitCharacter;
45 import com.lowagie.text.pdf.PdfContentByte;
46 import com.lowagie.text.pdf.PdfFormField;
47 import com.lowagie.text.pdf.PdfOutline;
48 import com.lowagie.text.pdf.PdfTemplate;
49 import com.lowagie.text.pdf.PdfWriter;
50 import com.lowagie.text.pdf.RadioCheckField;
51 import com.lowagie.text.pdf.TextField;
52
53 import net.sf.jasperreports.engine.JRException;
54 import net.sf.jasperreports.engine.JRPrintImage;
55 import net.sf.jasperreports.engine.JRPrintText;
56 import net.sf.jasperreports.engine.JRRuntimeException;
57 import net.sf.jasperreports.engine.PrintPageFormat;
58 import net.sf.jasperreports.engine.export.AbstractPdfTextRenderer;
59 import net.sf.jasperreports.engine.export.PdfTextRenderer;
60 import net.sf.jasperreports.engine.export.SimplePdfTextRenderer;
61 import net.sf.jasperreports.engine.export.type.PdfFieldTypeEnum;
62 import net.sf.jasperreports.engine.type.ModeEnum;
63 import net.sf.jasperreports.engine.util.BreakIteratorSplitCharacter;
64 import net.sf.jasperreports.engine.util.JRStyledText;
65 import net.sf.jasperreports.engine.util.NullOutputStream;
66 import net.sf.jasperreports.export.pdf.PdfChunk;
67 import net.sf.jasperreports.export.pdf.PdfContent;
68 import net.sf.jasperreports.export.pdf.PdfDocument;
69 import net.sf.jasperreports.export.pdf.PdfDocumentWriter;
70 import net.sf.jasperreports.export.pdf.PdfImage;
71 import net.sf.jasperreports.export.pdf.PdfOutlineEntry;
72 import net.sf.jasperreports.export.pdf.PdfPhrase;
73 import net.sf.jasperreports.export.pdf.PdfProducer;
74 import net.sf.jasperreports.export.pdf.PdfProducerContext;
75 import net.sf.jasperreports.export.pdf.PdfRadioCheck;
76 import net.sf.jasperreports.export.pdf.PdfStructure;
77 import net.sf.jasperreports.export.pdf.PdfTextChunk;
78 import net.sf.jasperreports.export.pdf.PdfTextField;
79 import net.sf.jasperreports.renderers.Graphics2DRenderable;
80
81 /**
82  * 
83  * @author Lucian Chirita (lucianc@users.sourceforge.net)
84  */

85 public class ClassicPdfProducer implements PdfProducer
86 {
87     
88     private PdfProducerContext context;
89     
90     private ClassicPdfStructure pdfStructure;
91     
92     private ClassicDocument document;
93     private ClassicPdfWriter writer;
94
95     private Document imageTesterDocument;
96     private PdfContentByte imageTesterPdfContentByte;
97     
98     private SplitCharacter splitCharacter;
99     private GlyphRendering glyphRendering;
100     
101     private ClassicPdfContent pdfContent;
102     
103     private Map<String, RadioCheckField> radioFieldFactories;
104     private Map<String, PdfFormField> radioGroups;
105
106     public ClassicPdfProducer(PdfProducerContext context)
107     {
108         this.context = context;
109         this.glyphRendering = new GlyphRendering(this);
110     }
111
112     @Override
113     public PdfProducerContext getContext()
114     {
115         return context;
116     }
117
118     @Override
119     public PdfDocument createDocument(PrintPageFormat pageFormat)
120     {
121         Document pdfDocument =
122                 new Document(
123                     new Rectangle(
124                         pageFormat.getPageWidth(),
125                         pageFormat.getPageHeight()
126                     )
127                 );
128             
129         imageTesterDocument =
130                 new Document(
131                     new Rectangle(
132                         10, //jasperPrint.getPageWidth(),
133                         10 //jasperPrint.getPageHeight()
134                     )
135                 );
136         
137         document = new ClassicDocument(pdfDocument);
138         return document;
139     }
140
141     @Override
142     public PdfDocumentWriter createWriter(OutputStream os) throws JRException
143     {
144         try
145         {
146             PdfWriter pdfWriter = PdfWriter.getInstance(document.getDocument(), os);
147             pdfWriter.setCloseStream(false);
148             
149             PdfWriter imageTesterPdfWriter =
150                 PdfWriter.getInstance(
151                     imageTesterDocument,
152                     new NullOutputStream() // discard the output
153                     );
154             imageTesterDocument.open();
155             imageTesterDocument.newPage();
156             imageTesterPdfContentByte = imageTesterPdfWriter.getDirectContent();
157             imageTesterPdfContentByte.setLiteral("\n");
158             
159             writer = new ClassicPdfWriter(this, pdfWriter);
160             return writer;
161         }
162         catch (DocumentException e)
163         {
164             throw context.handleDocumentException(e);
165         }
166     }
167     
168     public PdfWriter getPdfWriter()
169     {
170         return writer.getPdfWriter();
171     }
172
173     @Override
174     public void setTagged()
175     {
176         writer.getPdfWriter().setTagged();
177     }
178
179     @Override
180     public PdfContent createPdfContent()
181     {
182         pdfContent = new ClassicPdfContent(writer.getPdfWriter());
183         return pdfContent;
184     }
185
186     @Override
187     public PdfContent getPdfContent()
188     {
189         return pdfContent;
190     }
191
192     public PdfContentByte getPdfContentByte()
193     {
194         return pdfContent.getPdfContentByte();
195     }
196
197     @Override
198     public void initReport()
199     {
200         glyphRendering.initGlyphRenderer();
201     }
202
203     @Override
204     public void setForceLineBreakPolicy(boolean forceLineBreakPolicy)
205     {
206         splitCharacter = forceLineBreakPolicy ? new BreakIteratorSplitCharacter() : null;
207     }
208     
209     @Override
210     public void newPage()
211     {
212         document.getDocument().newPage();
213         pdfContent.refreshContent();
214     }
215     
216     @Override
217     public void setPageSize(PrintPageFormat pageFormat, int pageWidth, int pageHeight)
218     {
219         Rectangle pageSize;
220         switch (pageFormat.getOrientation())
221         {
222         case LANDSCAPE:
223             // using rotate to indicate landscape page
224             pageSize = new Rectangle(pageHeight, pageWidth).rotate();
225             break;
226         default:
227             pageSize = new Rectangle(pageWidth, pageHeight);
228             break;
229         }
230         document.getDocument().setPageSize(pageSize);        
231     }
232
233     @Override
234     public void endPage()
235     {
236         if (radioGroups != null)
237         {
238             for (PdfFormField radioGroup : radioGroups.values())
239             {
240                 getPdfWriter().addAnnotation(radioGroup);
241             }
242             radioGroups = null;
243             radioFieldFactories = null// radio groups that overflow unto next page don't seem to work; reset everything as it does not make sense to keep them
244         }
245     }
246
247     @Override
248     public void close()
249     {
250         document.getDocument().close();
251         imageTesterDocument.close();
252     }
253
254     @Override
255     public AbstractPdfTextRenderer getTextRenderer(
256             JRPrintText text, JRStyledText styledText, Locale textLocale,
257             boolean awtIgnoreMissingFont, boolean defaultIndentFirstLine, boolean defaultJustifyLastLine)
258     {
259         AbstractPdfTextRenderer textRenderer = glyphRendering.getGlyphTextRenderer(text, styledText, textLocale,
260                 awtIgnoreMissingFont, defaultIndentFirstLine, defaultJustifyLastLine);
261         if (textRenderer == null)
262         {
263             if (text.getLeadingOffset() == 0)
264             {
265                 // leading offset is non-zero only for multiline texts that have at least one tab character or some paragraph indent (first, left or right)
266                 textRenderer = 
267                     new PdfTextRenderer(
268                         context.getJasperReportsContext(), 
269                         awtIgnoreMissingFont, 
270                         defaultIndentFirstLine,
271                         defaultJustifyLastLine
272                         );//FIXMENOW make some reusable instances here and below
273             }
274             else
275             {
276                 textRenderer = 
277                     new SimplePdfTextRenderer(
278                         context.getJasperReportsContext(), 
279                         awtIgnoreMissingFont, 
280                         defaultIndentFirstLine,
281                         defaultJustifyLastLine
282                         );//FIXMETAB optimize this
283             }
284         }
285         return textRenderer;
286     }
287
288     @Override
289     public PdfImage createImage(byte[] data, boolean verify) throws IOException, JRException
290     {
291         try
292         {
293             Image image = Image.getInstance(data);
294             
295             if (verify)
296             {
297                 imageTesterPdfContentByte.addImage(image, 10, 0, 0, 10, 0, 0);
298             }
299             
300             return new ClassicImage(image);
301         }
302         catch (DocumentException e)
303         {
304             throw context.handleDocumentException(e);
305         }
306     }
307     
308     @Override
309     public PdfImage createImage(BufferedImage bi, int angle) throws IOException
310     {
311         try
312         {
313             Image image = Image.getInstance(bi, null);
314             image.setRotationDegrees(angle);
315             
316             return new ClassicImage(image);
317         }
318         catch (BadElementException e)
319         {
320             //TODO message
321             throw new JRRuntimeException(e);
322         }
323     }
324     
325     @Override
326     public void drawImage(JRPrintImage image, Graphics2DRenderable renderer, boolean forceSvgShapes, 
327             double templateWidth, double templateHeight,
328             int translateX, int translateY, double angle, 
329             double renderWidth, double renderHeight, 
330             float ratioX, float ratioY, float x, float y) throws JRException, IOException
331     {
332         PdfContentByte pdfContentByte = getPdfContentByte();
333         PdfTemplate template = pdfContentByte.createTemplate(
334                 (float) templateWidth, (float) templateHeight);
335
336         Graphics2D g = forceSvgShapes
337             ? template.createGraphicsShapes((float) templateWidth, (float) templateHeight)
338             : template.createGraphics((float) templateWidth, (float) templateHeight, 
339                     new ClassicPdfFontMapper(this));
340
341         try
342         {
343             g.translate(translateX, translateY);
344
345             if (angle != 0)
346             {
347                 g.rotate(angle);
348             }
349             
350             if (image.getModeValue() == ModeEnum.OPAQUE)
351             {
352                 g.setColor(image.getBackcolor());
353                 g.fillRect(0, 0, (int) renderWidth, (int) renderHeight);
354             }
355
356             renderer.render(context.getJasperReportsContext(), g, 
357                     new Rectangle2D.Double(0, 0, renderWidth, renderHeight));
358         }
359         finally
360         {
361             g.dispose();
362         }
363
364         pdfContentByte.saveState();
365         pdfContentByte.addTemplate(
366             template,
367             ratioX, 0f, 0f, ratioY, x, y);
368         pdfContentByte.restoreState();
369         
370         getPdfWriter().releaseTemplate(template);
371     }
372     
373     public Font getFont(Map<Attribute,Object> attributes, Locale locale)
374     {
375         ClassicFontRecipient fontRecipient = new ClassicFontRecipient();
376         context.setFont(attributes, locale, false, fontRecipient);
377         Font font = fontRecipient.getFont();
378         return font;
379     }
380     
381     @Override
382     public PdfTextChunk createChunk(String text, Map<Attribute,Object> attributes, Locale locale)
383     {
384         Font font = getFont(attributes, locale);
385         Chunk chunk = new Chunk(text, font);
386
387         if (splitCharacter != null)
388         {
389             //TODO use line break offsets if available?
390             chunk.setSplitCharacter(splitCharacter);
391         }
392         
393         return new ClassicTextChunk(this, chunk, font);
394     }
395
396     @Override
397     public PdfChunk createChunk(PdfImage imageContainer)
398     {
399         Image image = ((ClassicImage) imageContainer).getImage();
400         Chunk chunk = new Chunk(image, 0, 0);
401         return new ClassicChunk(this, chunk);
402     }
403     
404     @Override
405     public PdfPhrase createPhrase()
406     {
407         Phrase phrase = new Phrase();
408         return new ClassicPhrase(this, phrase);
409     }
410
411     @Override
412     public PdfPhrase createPhrase(PdfChunk chunk)
413     {
414         Phrase phrase = new Phrase(((ClassicChunk) chunk).getChunk());
415         return new ClassicPhrase(this, phrase);
416     }
417
418     @Override
419     public PdfTextField createTextField(float llx, float lly, float urx, float ury, String fieldName)
420     {
421         TextField textField = createTextFormField(llx, lly, urx, ury, fieldName);
422         return new ClassicPdfTextField(this, textField, PdfFieldTypeEnum.TEXT);
423     }
424
425     protected TextField createTextFormField(float llx, float lly, float urx, float ury, String fieldName)
426     {
427         Rectangle rectangle = new Rectangle(llx, lly, urx, ury);
428         TextField textField = new TextField(writer.getPdfWriter(), rectangle, fieldName);
429         return textField;
430     }
431
432     @Override
433     public PdfTextField createComboField(float llx, float lly, float urx, float ury, String fieldName, 
434             String value, String[] choices)
435     {
436         TextField textField = createTextFormField(llx, lly, urx, ury, fieldName);        
437         setFieldChoices(textField, value, choices);
438         return new ClassicPdfTextField(this, textField, PdfFieldTypeEnum.COMBO);
439     }
440
441     protected void setFieldChoices(TextField textField, String value, String[] choices)
442     {
443         if (choices != null)
444         {
445             textField.setChoices(choices);
446             
447             if (value != null)
448             {
449                 int i = 0;
450                 for (String choice : choices)
451                 {
452                     if (value.equals(choice))
453                     {
454                         textField.setChoiceSelection(i);
455                         break;
456                     }
457                     i++;
458                 }
459             }
460         }
461     }
462
463     @Override
464     public PdfTextField createListField(float llx, float lly, float urx, float ury, String fieldName, 
465             String value, String[] choices)
466     {
467         TextField textField = createTextFormField(llx, lly, urx, ury, fieldName);        
468         setFieldChoices(textField, value, choices);
469         return new ClassicPdfTextField(this, textField, PdfFieldTypeEnum.LIST);
470     }
471
472     @Override
473     public PdfRadioCheck createCheckField(float llx, float lly, float urx, float ury, String fieldName, 
474             String onValue)
475     {
476         Rectangle rectangle = new Rectangle(llx, lly, urx, ury);
477         RadioCheckField radioField = new RadioCheckField(writer.getPdfWriter(), rectangle, fieldName, onValue);
478         return new ClassicRadioCheck(this, radioField);
479     }
480
481     @Override
482     public PdfRadioCheck getRadioField(float llx, float lly, float urx, float ury, String fieldName, 
483             String onValue)
484     {
485         Rectangle rectangle = new Rectangle(llx, lly, urx, ury);
486         //TODO does this make sense?
487         RadioCheckField radioField = radioFieldFactories == null ? null : radioFieldFactories.get(fieldName);
488         if (radioField == null)
489         {
490             radioField = new RadioCheckField(writer.getPdfWriter(), rectangle, fieldName, onValue);
491             if (radioFieldFactories == null)
492             {
493                 radioFieldFactories = new HashMap<>();
494             }
495             radioFieldFactories.put(fieldName, radioField);
496         }
497         
498         radioField.setBox(rectangle);
499         
500         return new ClassicRadioCheck(this, radioField);
501     }
502     
503     protected PdfFormField getRadioGroup(RadioCheckField radioCheckField)
504     {
505         String fieldName = radioCheckField.getFieldName();
506         PdfFormField radioGroup = radioGroups == null ? null : radioGroups.get(fieldName);
507         if (radioGroup == null)
508         {
509             if (radioGroups == null)
510             {
511                 radioGroups = new HashMap<>();
512             }
513             
514             radioGroup = radioCheckField.getRadioGroup(truefalse);
515             radioGroups.put(fieldName, radioGroup);
516         }
517         return radioGroup;
518     }
519
520     @Override
521     public PdfOutlineEntry getRootOutline()
522     {
523         PdfOutline rootOutline = pdfContent.getPdfContentByte().getRootOutline();
524         return new ClassicPdfOutline(rootOutline);
525     }
526
527     @Override
528     public PdfStructure getPdfStructure()
529     {
530         if (pdfStructure == null)
531         {
532             pdfStructure = new ClassicPdfStructure(this);
533         }
534         return pdfStructure;
535     }
536     
537 }
538