1
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
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);
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
237
238
239
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