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.font.TextAttribute;
27 import java.lang.Character.UnicodeBlock;
28 import java.text.AttributedCharacterIterator;
29 import java.text.AttributedCharacterIterator.Attribute;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Set;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39
40 import com.lowagie.text.Font;
41 import com.lowagie.text.pdf.BaseFont;
42
43 import net.sf.jasperreports.engine.JRPrintText;
44 import net.sf.jasperreports.engine.JRPropertiesUtil.PropertySuffix;
45 import net.sf.jasperreports.engine.export.AbstractPdfTextRenderer;
46 import net.sf.jasperreports.engine.export.PdfGlyphRenderer;
47 import net.sf.jasperreports.engine.fonts.AwtFontAttribute;
48 import net.sf.jasperreports.engine.util.JRStyledText;
49 import net.sf.jasperreports.export.PdfReportConfiguration;
50 import net.sf.jasperreports.export.pdf.PdfProducerContext;
51 import net.sf.jasperreports.export.type.PdfVersionEnum;
52
53 /**
54  * 
55  * @author Lucian Chirita (lucianc@users.sourceforge.net)
56  */

57 public class GlyphRendering
58 {
59     
60     private static final Log log = LogFactory.getLog(GlyphRendering.class);
61     
62     private ClassicPdfProducer pdfProducer;
63     private Set<UnicodeBlock> glyphRendererBlocks;
64     private boolean glyphRendererAddActualText;
65     private Map<FontKey, Boolean> glyphRendererFonts;
66
67     public GlyphRendering(ClassicPdfProducer pdfProducer)
68     {
69         this.pdfProducer = pdfProducer;
70         this.glyphRendererFonts = new HashMap<FontKey, Boolean>();
71         
72         PdfProducerContext context = pdfProducer.getContext();
73         this.glyphRendererAddActualText = context.getProperties().getBooleanProperty( 
74                 PdfReportConfiguration.PROPERTY_GLYPH_RENDERER_ADD_ACTUAL_TEXT, false);
75         if (glyphRendererAddActualText && !context.isTagged() && PdfGlyphRenderer.supported())
76         {
77             context.setMinimalVersion(PdfVersionEnum.VERSION_1_5);
78         }
79     }
80
81     protected void initGlyphRenderer() 
82     {
83         glyphRendererBlocks = new HashSet<Character.UnicodeBlock>();
84         PdfProducerContext context = pdfProducer.getContext();
85         List<PropertySuffix> props = context.getProperties().getAllProperties(
86                 context.getCurrentJasperPrint(), 
87                 PdfReportConfiguration.PROPERTY_PREFIX_GLYPH_RENDERER_BLOCKS);
88         for (PropertySuffix prop : props)
89         {
90             String blocks = prop.getValue();
91             for (String blockToken : blocks.split(","))
92             {
93                 UnicodeBlock block = resolveUnicodeBlock(blockToken);
94                 if (block != null)
95                 {
96                     if (log.isDebugEnabled())
97                     {
98                         log.debug("glyph renderer block " + block);
99                     }
100                     glyphRendererBlocks.add(block);
101                 }
102             }
103         }
104     }
105     
106     protected UnicodeBlock resolveUnicodeBlock(String name)
107     {
108         if (name.trim().isEmpty())
109         {
110             return null;
111         }
112         
113         try 
114         {
115             return UnicodeBlock.forName(name.trim());
116         } 
117         catch (IllegalArgumentException e) 
118         {
119             log.warn("Could not resolve \"" + name + "\" to a Unicode block");
120             return null;
121         } 
122     }
123     
124     public AbstractPdfTextRenderer getGlyphTextRenderer(
125             JRPrintText text, JRStyledText styledText, Locale textLocale, 
126             boolean awtIgnoreMissingFont, boolean defaultIndentFirstLine, boolean defaultJustifyLastLine)
127     {
128         if (toUseGlyphRenderer(text)
129                 && PdfGlyphRenderer.supported()
130                 && canUseGlyphRendering(text, styledText, textLocale, awtIgnoreMissingFont))
131         {
132             PdfProducerContext context = pdfProducer.getContext();
133             PdfGlyphRenderer textRenderer = 
134                     new PdfGlyphRenderer(
135                         context.getJasperReportsContext(), 
136                         awtIgnoreMissingFont,
137                         defaultIndentFirstLine,
138                         defaultJustifyLastLine,
139                         glyphRendererAddActualText
140                         );
141             return textRenderer;
142         }
143         
144         return null;
145     }
146     
147     protected boolean canUseGlyphRendering(JRPrintText text, JRStyledText styledText, Locale locale, 
148             boolean awtIgnoreMissingFont)
149     {
150         AttributedCharacterIterator attributesIterator = styledText.getAttributedString().getIterator();
151         int index = 0;
152         while (index < styledText.length())
153         {
154             FontKey fontKey = extractFontKey(attributesIterator.getAttributes(), locale);
155             if (!fontKey.fontAttribute.hasAttribute())
156             {
157                 return false;
158             }
159             
160             Boolean canUse = glyphRendererFonts.get(fontKey);
161             if (canUse == null)
162             {
163                 canUse = canUseGlyphRendering(fontKey, awtIgnoreMissingFont);
164                 glyphRendererFonts.put(fontKey, canUse);
165             }
166             
167             if (!canUse)
168             {
169                 return false;
170             }
171             
172             index = attributesIterator.getRunLimit();
173             attributesIterator.setIndex(index);
174         }
175         return true;
176     }
177
178     protected FontKey extractFontKey(Map<Attribute, Object> attributes, Locale locale)
179     {
180         AwtFontAttribute fontAttribute = AwtFontAttribute.fromAttributes(attributes);
181         
182         Number posture = (Number) attributes.get(TextAttribute.POSTURE);
183         boolean italic = TextAttribute.POSTURE_OBLIQUE.equals(posture);//FIXME check for non standard posture
184
185         Number weight = (Number) attributes.get(TextAttribute.WEIGHT);
186         boolean bold = TextAttribute.WEIGHT_BOLD.equals(weight);
187         
188         return new FontKey(fontAttribute, italic, bold, locale);
189     }
190     
191     protected boolean canUseGlyphRendering(FontKey fontKey, boolean awtIgnoreMissingFont) 
192     {
193         Map<Attribute, Object> fontAttributes = new HashMap<Attribute, Object>();
194         fontKey.fontAttribute.putAttributes(fontAttributes);
195         fontAttributes.put(TextAttribute.SIZE, 10f);
196
197         int style = 0;
198         if (fontKey.italic)
199         {
200             style |= java.awt.Font.ITALIC;
201             fontAttributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
202         }
203         if (fontKey.bold)
204         {
205             style |= java.awt.Font.BOLD;
206             fontAttributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
207         }
208         
209         Font pdfFont = pdfProducer.getFont(fontAttributes, fontKey.locale);
210         BaseFont baseFont = pdfFont.getBaseFont();
211         if (baseFont.getFontType() != BaseFont.FONT_TYPE_TTUNI
212                 || baseFont.isFontSpecific())
213         {
214             if (log.isDebugEnabled())
215             {
216                 log.debug("pdf font for " + fontKey + " has type " + baseFont.getFontType()
217                         + ", symbol " + baseFont.isFontSpecific()
218                         + ", cannot use glyph rendering");
219             }
220             return false;
221         }
222         
223         java.awt.Font awtFont = pdfProducer.getContext().getFontUtil().getAwtFontFromBundles(fontKey.fontAttribute, style,
224                 10f, fontKey.locale, awtIgnoreMissingFont);
225         if (awtFont == null)
226         {
227             awtFont = new java.awt.Font(fontAttributes);
228         }
229         String awtFontName = awtFont.getFontName();
230         
231         if (log.isDebugEnabled())
232         {
233             log.debug(fontKey + " resolved to awt font " + awtFontName);
234         }
235         
236         // we need the fonts to be identical.
237         // it would be safer to only allow fonts from extensions, 
238         // but for now we are just checking the font names.
239         // we need to compare full names because we can't get the base name from awt.
240         String[][] pdfFontNames = baseFont.getFullFontName();
241         boolean nameMatch = false;
242         for (String[] nameArray : pdfFontNames)
243         {
244             if (nameArray.length >= 4)
245             {
246                 if (log.isDebugEnabled())
247                 {
248                     log.debug(fontKey + " resolved to pdf font " + nameArray[3]);
249                 }
250                 
251                 if (awtFontName.equals(nameArray[3]))
252                 {
253                     nameMatch = true;
254                     break;
255                 }
256             }
257         }
258         
259         return nameMatch;
260     }
261     
262     public boolean toUseGlyphRenderer(JRPrintText text)
263     {
264         String value = pdfProducer.getContext().getStyledTextUtil().getTruncatedText(text);
265         if (value == null)
266         {
267             return false;
268         }
269         
270         if (glyphRendererBlocks.isEmpty())
271         {
272             return false;
273         }
274         
275         int charCount = value.length();
276         char[] chars = new char[charCount];
277         value.getChars(0, charCount, chars, 0);
278         for (char c : chars)
279         {
280             UnicodeBlock block = UnicodeBlock.of(c);
281             if (glyphRendererBlocks.contains(block))
282             {
283                 if (log.isTraceEnabled())
284                 {
285                     log.trace("found character in block " + block + ", using the glyph renderer");
286                 }
287                 
288                 return true;
289             }
290         }
291         
292         return false;
293     }
294     
295     protected static class FontKey
296     {
297         AwtFontAttribute fontAttribute;
298         boolean italic;
299         boolean bold;
300         Locale locale;
301         
302         public FontKey(AwtFontAttribute fontAttribute, boolean italic, boolean bold, Locale locale)
303         {
304             this.fontAttribute = fontAttribute;
305             this.italic = italic;
306             this.bold = bold;
307             this.locale = locale;
308         }
309         
310         @Override
311         public int hashCode()
312         {
313             int hash = 43;
314             hash = hash*29 + fontAttribute.hashCode();
315             hash = hash*29 + (italic ? 1231 : 1237);
316             hash = hash*29 + (bold ? 1231 : 1237);
317             hash = hash*29 + (locale == null ? 0 : locale.hashCode());
318             return hash;
319         }
320         
321         @Override
322         public boolean equals(Object obj)
323         {
324             FontKey key = (FontKey) obj;
325             return fontAttribute.equals(key.fontAttribute) && italic == key.italic && bold == key.bold
326                     && ((locale == null) ? (key.locale == null) : (key.locale != null && locale.equals(key.locale)));
327         }
328         
329         @Override
330         public String toString()
331         {
332             return "{font: " + fontAttribute
333                     + ", italic: " + italic
334                     + ", bold: " + bold
335                     + "}";
336         }
337     }
338
339 }
340