1
24 package net.sf.jasperreports.engine.fonts;
25
26 import java.awt.Font;
27 import java.awt.font.TextAttribute;
28 import java.text.AttributedCharacterIterator.Attribute;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.LinkedHashMap;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.TreeSet;
40
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43
44 import net.sf.jasperreports.engine.JRFont;
45 import net.sf.jasperreports.engine.JRRuntimeException;
46 import net.sf.jasperreports.engine.JasperReportsContext;
47 import net.sf.jasperreports.engine.util.JRFontNotFoundException;
48 import net.sf.jasperreports.engine.util.JRGraphEnvInitializer;
49 import net.sf.jasperreports.engine.util.JRTextAttribute;
50
51
52
55 public final class FontUtil
56 {
57 private static final Log log = LogFactory.getLog(FontUtil.class);
58 public static final String EXCEPTION_MESSAGE_KEY_NULL_FONT = "engine.fonts.null.font";
59 public static final String EXCEPTION_MESSAGE_KEY_FONT_SET_FAMILY_NOT_FOUND = "util.font.set.family.not.found";
60
61 private JasperReportsContext jasperReportsContext;
62
63
64
67 private FontUtil(JasperReportsContext jasperReportsContext)
68 {
69 this.jasperReportsContext = jasperReportsContext;
70 }
71
72
73
76 public static FontUtil getInstance(JasperReportsContext jasperReportsContext)
77 {
78 return new FontUtil(jasperReportsContext);
79 }
80
81
82
85 private static final InheritableThreadLocal<Set<String>> threadMissingFontsCache = new InheritableThreadLocal<Set<String>>()
86 {
87 @Override
88 protected Set<String> initialValue() {
89 return new HashSet<String>();
90 }
91 };
92
93
96 public static void copyNonNullOwnProperties(JRFont srcFont, JRFont destFont)
97 {
98 if(srcFont != null && destFont != null)
99 {
100 if (srcFont.getOwnFontName() != null)
101 {
102 destFont.setFontName(srcFont.getOwnFontName());
103 }
104 if (srcFont.isOwnBold() != null)
105 {
106 destFont.setBold(srcFont.isOwnBold());
107 }
108 if (srcFont.isOwnItalic() != null)
109 {
110 destFont.setItalic(srcFont.isOwnItalic());
111 }
112 if (srcFont.isOwnUnderline() != null)
113 {
114 destFont.setUnderline(srcFont.isOwnUnderline());
115 }
116 if (srcFont.isOwnStrikeThrough() != null)
117 {
118 destFont.setStrikeThrough(srcFont.isOwnStrikeThrough());
119 }
120 if (srcFont.getOwnFontsize() != null)
121 {
122 destFont.setFontSize(srcFont.getOwnFontsize());
123 }
124 if (srcFont.getOwnPdfFontName() != null)
125 {
126 destFont.setPdfFontName(srcFont.getOwnPdfFontName());
127 }
128 if (srcFont.getOwnPdfEncoding() != null)
129 {
130 destFont.setPdfEncoding(srcFont.getOwnPdfEncoding());
131 }
132 if (srcFont.isOwnPdfEmbedded() != null)
133 {
134 destFont.setPdfEmbedded(srcFont.isOwnPdfEmbedded());
135 }
136 }
137 }
138
139
140
143 public Map<Attribute,Object> getAttributesWithoutAwtFont(Map<Attribute,Object> attributes, JRFont font)
144 {
145 attributes.put(TextAttribute.FAMILY, font.getFontName());
146
147 attributes.put(TextAttribute.SIZE, font.getFontsize());
148
149 if (font.isBold())
150 {
151 attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
152 }
153 if (font.isItalic())
154 {
155 attributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
156 }
157 if (font.isUnderline())
158 {
159 attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
160 }
161 if (font.isStrikeThrough())
162 {
163 attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
164 }
165
166 attributes.put(JRTextAttribute.PDF_FONT_NAME, font.getPdfFontName());
167 attributes.put(JRTextAttribute.PDF_ENCODING, font.getPdfEncoding());
168
169 if (font.isPdfEmbedded())
170 {
171 attributes.put(JRTextAttribute.IS_PDF_EMBEDDED, Boolean.TRUE);
172 }
173
174 return attributes;
175 }
176
177
178
186 public FontInfo getFontInfo(String name, boolean ignoreCase, Locale locale)
187 {
188 FontInfo awtFamilyMatchFontInfo = null;
189
190
191 List<FontFamily> families = jasperReportsContext.getExtensions(FontFamily.class);
192 for (Iterator<FontFamily> itf = families.iterator(); itf.hasNext();)
193 {
194 FontFamily family = itf.next();
195 if (locale == null || family.supportsLocale(locale))
196 {
197 if (equals(name, family.getName(), ignoreCase))
198 {
199 return new FontInfo(family, null, Font.PLAIN);
200 }
201
202 FontFace face = family.getNormalFace();
203 if (face != null)
204 {
205 if (equals(name, face.getName(), ignoreCase))
206 {
207 return new FontInfo(family, face, Font.PLAIN);
208 }
209 else if (
210 awtFamilyMatchFontInfo == null
211 && face.getFont() != null
212 && equals(name, face.getFont().getFamily(), ignoreCase)
213 )
214 {
215 awtFamilyMatchFontInfo = new FontInfo(family, face, Font.PLAIN);
216 }
217 }
218
219 face = family.getBoldFace();
220 if (face != null)
221 {
222 if (equals(name, face.getName(), ignoreCase))
223 {
224 return new FontInfo(family, face, Font.BOLD);
225 }
226 else if (
227 awtFamilyMatchFontInfo == null
228 && face.getFont() != null
229 && equals(name, face.getFont().getFamily(), ignoreCase)
230 )
231 {
232 awtFamilyMatchFontInfo = new FontInfo(family, face, Font.BOLD);
233 }
234 }
235
236 face = family.getItalicFace();
237 if (face != null)
238 {
239 if (equals(name, face.getName(), ignoreCase))
240 {
241 return new FontInfo(family, face, Font.ITALIC);
242 }
243 else if (
244 awtFamilyMatchFontInfo == null
245 && face.getFont() != null
246 && equals(name, face.getFont().getFamily(), ignoreCase)
247 )
248 {
249 awtFamilyMatchFontInfo = new FontInfo(family, face, Font.ITALIC);
250 }
251 }
252
253 face = family.getBoldItalicFace();
254 if (face != null)
255 {
256 if (equals(name, face.getName(), ignoreCase))
257 {
258 return new FontInfo(family, face, Font.BOLD | Font.ITALIC);
259 }
260 else if (
261 awtFamilyMatchFontInfo == null
262 && face.getFont() != null
263 && equals(name, face.getFont().getFamily(), ignoreCase)
264 )
265 {
266 awtFamilyMatchFontInfo = new FontInfo(family, face, Font.BOLD | Font.ITALIC);
267 }
268 }
269 }
270 }
271
272 return awtFamilyMatchFontInfo;
273 }
274
275
276 private static boolean equals(String value1, String value2, boolean ignoreCase)
277 {
278 return ignoreCase ? value1.equalsIgnoreCase(value2) : value1.equals(value2);
279 }
280
281
282
289 public FontInfo getFontInfo(String name, Locale locale)
290 {
291 return getFontInfo(name, false, locale);
292 }
293
294
295
303 public FontInfo getFontInfoIgnoreCase(String name, Locale locale)
304 {
305 return getFontInfo(name, true, locale);
306 }
307
308
309 public FontSetInfo getFontSetInfo(String name, Locale locale, boolean ignoreMissingFonts)
310 {
311
312 List<FontFamily> allFontFamilies = jasperReportsContext.getExtensions(FontFamily.class);
313 HashMap<String, FontFamily> fontFamilies = new HashMap<String, FontFamily>(allFontFamilies.size() * 4 / 3, .75f);
314 for (FontFamily family : allFontFamilies)
315 {
316 if (family.getName() != null
317 && (locale == null || family.supportsLocale(locale)))
318 {
319 fontFamilies.put(family.getName(), family);
320 }
321 }
322
323 Map<String, FontSetFamilyInfo> setFamilyInfos = new LinkedHashMap<String, FontSetFamilyInfo>();
324 List<FontSet> allSets = jasperReportsContext.getExtensions(FontSet.class);
325 FontSet foundFontSet = null;
326 FontSetFamilyInfo primaryFamily = null;
327 for (FontSet fontSet : allSets)
328 {
329 if (name.equals(fontSet.getName()))
330 {
331 foundFontSet = fontSet;
332
333 List<FontSetFamily> setFamilies = fontSet.getFamilies();
334 for (FontSetFamily fontSetFamily : setFamilies)
335 {
336 FontFamily fontFamily = fontFamilies.get(fontSetFamily.getFamilyName());
337 if (fontFamily != null)
338 {
339 FontSetFamilyInfo familyInfo = new FontSetFamilyInfo(fontSetFamily, fontFamily);
340 setFamilyInfos.put(fontSetFamily.getFamilyName(), familyInfo);
341
342 if (fontSetFamily.isPrimary())
343 {
344 primaryFamily = familyInfo;
345 }
346 }
347 else
348 {
349 if (ignoreMissingFonts)
350 {
351 if (log.isWarnEnabled())
352 {
353 log.warn("Font family " + fontSetFamily.getFamilyName()
354 + " was not found for font set " + name);
355 }
356 }
357 else
358 {
359 throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_FONT_SET_FAMILY_NOT_FOUND,
360 new Object[]{fontSetFamily.getFamilyName(), name});
361 }
362 }
363 }
364 }
365 }
366
367 if (foundFontSet == null)
368 {
369 return null;
370 }
371
372
373 List<FontSetFamilyInfo> familyInfoList = new ArrayList<FontSetFamilyInfo>(setFamilyInfos.values());
374 if (primaryFamily == null && !familyInfoList.isEmpty())
375 {
376 primaryFamily = familyInfoList.get(0);
377 }
378 return new FontSetInfo(foundFontSet, primaryFamily, familyInfoList);
379 }
380
381 public String getExportFontFamily(String name, Locale locale, String exporterKey)
382 {
383
384 FontInfo fontInfo = getFontInfo(name, locale);
385 if (fontInfo != null)
386 {
387 FontFamily family = fontInfo.getFontFamily();
388 String exportFont = family.getExportFont(exporterKey);
389 return exportFont == null ? name : exportFont;
390 }
391
392 FontSetInfo fontSetInfo = getFontSetInfo(name, locale, true);
393 if (fontSetInfo != null)
394 {
395 String exportFont = fontSetInfo.getFontSet().getExportFont(exporterKey);
396
397 return exportFont == null ? name : exportFont;
398 }
399
400 return name;
401 }
402
403
406 public Collection<String> getFontFamilyNames()
407 {
408 TreeSet<String> familyNames = new TreeSet<String>();
409
410 collectFontFamilyNames(familyNames);
411 return familyNames;
412 }
413
414 protected void collectFontFamilyNames(Collection<String> names)
415 {
416 List<FontFamily> families = jasperReportsContext.getExtensions(FontFamily.class);
417 for (Iterator<FontFamily> itf = families.iterator(); itf.hasNext();)
418 {
419 FontFamily family = itf.next();
420 if (family.isVisible())
421 {
422 names.add(family.getName());
423 }
424 }
425 }
426
427
432 public Collection<String> getFontNames()
433 {
434 TreeSet<String> fontNames = new TreeSet<String>();
435
436 collectFontFamilyNames(fontNames);
437 collectFontSetNames(fontNames);
438 return fontNames;
439 }
440
441 protected void collectFontSetNames(Collection<String> names)
442 {
443 List<FontSet> fontSets = jasperReportsContext.getExtensions(FontSet.class);
444 for (Iterator<FontSet> itf = fontSets.iterator(); itf.hasNext();)
445 {
446 FontSet fontSet = itf.next();
447 names.add(fontSet.getName());
448 }
449 }
450
451
454 public Font getAwtFontFromBundles(String name, int style, int size, Locale locale, boolean ignoreMissingFont)
455 {
456 return getAwtFontFromBundles(name, style, (float)size, locale, ignoreMissingFont);
457 }
458
459
460
463 public Font getAwtFontFromBundles(String name, int style, float size, Locale locale, boolean ignoreMissingFont)
464 {
465 return getAwtFontFromBundles(false, name, style, size, locale, ignoreMissingFont);
466 }
467
468
469
472 public Font getAwtFontFromBundles(boolean ignoreCase, String name, int style, float size, Locale locale, boolean ignoreMissingFont)
473 {
474 Font awtFont = null;
475 FontInfo fontInfo = ignoreCase ? getFontInfoIgnoreCase(name, locale) : getFontInfo(name, locale);
476
477 if (fontInfo != null)
478 {
479 awtFont = getAwtFont(fontInfo, style, size, ignoreMissingFont);
480 }
481
482 return awtFont;
483 }
484
485
486 protected Font getAwtFont(FontInfo fontInfo, int style, float size, boolean ignoreMissingFont)
487 {
488 @SuppressWarnings("unused")
489 int faceStyle = Font.PLAIN;
490 FontFamily family = fontInfo.getFontFamily();
491 FontFace face = fontInfo.getFontFace();
492 if (face == null)
493 {
494 if (((style & Font.BOLD) > 0) && ((style & Font.ITALIC) > 0))
495 {
496 face = family.getBoldItalicFace();
497 faceStyle = Font.BOLD | Font.ITALIC;
498 }
499
500 if ((face == null || face.getFont() == null) && ((style & Font.BOLD) > 0))
501 {
502 face = family.getBoldFace();
503 faceStyle = Font.BOLD;
504 }
505
506 if ((face == null || face.getFont() == null) && ((style & Font.ITALIC) > 0))
507 {
508 face = family.getItalicFace();
509 faceStyle = Font.ITALIC;
510 }
511
512 if (face == null || face.getFont() == null)
513 {
514 face = family.getNormalFace();
515 faceStyle = Font.PLAIN;
516 }
517
518
519
520
521
522 }
523 else
524 {
525 faceStyle = fontInfo.getStyle();
526 }
527
528 Font awtFont;
529 if (face == null || face.getFont() == null)
530 {
531
532
533 checkAwtFont(family.getName(), ignoreMissingFont);
534
535 awtFont = new Font(family.getName(), style, (int)size);
536 awtFont = awtFont.deriveFont(size);
537 }
538 else
539 {
540 awtFont = face.getFont();
541 if (awtFont == null)
542 {
543 throw
544 new JRRuntimeException(
545 EXCEPTION_MESSAGE_KEY_NULL_FONT,
546 new Object[]{face.getName(), family.getName()});
547 }
548
549
550 awtFont = awtFont.deriveFont(style, size);
551 }
552 return awtFont;
553 }
554
555
556 public Font getAwtFontFromBundles(AwtFontAttribute fontAttribute, int style, float size, Locale locale, boolean ignoreMissingFont)
557 {
558 FontInfo fontInfo = fontAttribute.getFontInfo();
559 if (fontInfo == null)
560 {
561 fontInfo = getFontInfo(fontAttribute.getFamily(), locale);
562 }
563
564 Font awtFont = null;
565 if (fontInfo != null)
566 {
567 awtFont = getAwtFont(fontInfo, style, size, ignoreMissingFont);
568 }
569 return awtFont;
570 }
571
572
573
576 public void resetThreadMissingFontsCache()
577 {
578 threadMissingFontsCache.set(new HashSet<String>());
579 }
580
581
582
585 public void checkAwtFont(String name, boolean ignoreMissingFont)
586 {
587 if (!JRGraphEnvInitializer.isAwtFontAvailable(name))
588 {
589 if (ignoreMissingFont)
590 {
591 Set<String> missingFontNames = threadMissingFontsCache.get();
592 if (!missingFontNames.contains(name))
593 {
594 missingFontNames.add(name);
595 if (log.isWarnEnabled())
596 {
597 log.warn("Font '" + name + "' is not available to the JVM. For more details, see http:);
598 }
599 }
600 }
601 else
602 {
603 throw new JRFontNotFoundException(name);
604 }
605 }
606 }
607
608
609
616 public Font getAwtFont(JRFont font, Locale locale)
617 {
618 if (font == null)
619 {
620 return null;
621 }
622
623
624 Font awtFont =
625 getAwtFontFromBundles(
626 font.getFontName(),
627 ((font.isBold()?Font.BOLD:Font.PLAIN)|(font.isItalic()?Font.ITALIC:Font.PLAIN)),
628 font.getFontsize(),
629 locale,
630 true
631 );
632
633 if (awtFont == null)
634 {
635 awtFont = new Font(getAttributesWithoutAwtFont(new HashMap<Attribute,Object>(), font));
636 }
637 else
638 {
639
640
641 Map<Attribute, Object> attributes = new HashMap<Attribute, Object>();
642 if (font.isUnderline())
643 {
644 attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
645 }
646 if (font.isStrikeThrough())
647 {
648 attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
649 }
650
651 if (!attributes.isEmpty())
652 {
653 awtFont = awtFont.deriveFont(attributes);
654 }
655 }
656
657 return awtFont;
658 }
659
660
661 private FontUtil()
662 {
663 }
664 }
665