net.sf.jasperreports.engine.fonts.FontUtil.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.engine.fonts.FontUtil.java

Source

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

import java.awt.Font;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.jasperreports.engine.JRFont;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.util.JRFontNotFoundException;
import net.sf.jasperreports.engine.util.JRGraphEnvInitializer;
import net.sf.jasperreports.engine.util.JRTextAttribute;

/**
 * @author Teodor Danciu (teodord@users.sourceforge.net)
 */
public final class FontUtil {
    private static final Log log = LogFactory.getLog(FontUtil.class);
    public static final String EXCEPTION_MESSAGE_KEY_NULL_FONT = "engine.fonts.null.font";
    public static final String EXCEPTION_MESSAGE_KEY_FONT_SET_FAMILY_NOT_FOUND = "util.font.set.family.not.found";

    private JasperReportsContext jasperReportsContext;

    /**
     *
     */
    private FontUtil(JasperReportsContext jasperReportsContext) {
        this.jasperReportsContext = jasperReportsContext;
    }

    /**
     *
     */
    public static FontUtil getInstance(JasperReportsContext jasperReportsContext) {
        return new FontUtil(jasperReportsContext);
    }

    /**
     *.
     */ //FIXMECONTEXT this should no longer be a thread local
    private static final InheritableThreadLocal<Set<String>> threadMissingFontsCache = new InheritableThreadLocal<Set<String>>() {
        @Override
        protected Set<String> initialValue() {
            return new HashSet<String>();
        }
    };

    /**
     *
     */
    public static void copyNonNullOwnProperties(JRFont srcFont, JRFont destFont) {
        if (srcFont != null && destFont != null) {
            if (srcFont.getOwnFontName() != null) {
                destFont.setFontName(srcFont.getOwnFontName());
            }
            if (srcFont.isOwnBold() != null) {
                destFont.setBold(srcFont.isOwnBold());
            }
            if (srcFont.isOwnItalic() != null) {
                destFont.setItalic(srcFont.isOwnItalic());
            }
            if (srcFont.isOwnUnderline() != null) {
                destFont.setUnderline(srcFont.isOwnUnderline());
            }
            if (srcFont.isOwnStrikeThrough() != null) {
                destFont.setStrikeThrough(srcFont.isOwnStrikeThrough());
            }
            if (srcFont.getOwnFontsize() != null) {
                destFont.setFontSize(srcFont.getOwnFontsize());
            }
            if (srcFont.getOwnPdfFontName() != null) {
                destFont.setPdfFontName(srcFont.getOwnPdfFontName());
            }
            if (srcFont.getOwnPdfEncoding() != null) {
                destFont.setPdfEncoding(srcFont.getOwnPdfEncoding());
            }
            if (srcFont.isOwnPdfEmbedded() != null) {
                destFont.setPdfEmbedded(srcFont.isOwnPdfEmbedded());
            }
        }
    }

    /**
     *
     */
    public Map<Attribute, Object> getAttributesWithoutAwtFont(Map<Attribute, Object> attributes, JRFont font) {
        attributes.put(TextAttribute.FAMILY, font.getFontName());

        attributes.put(TextAttribute.SIZE, font.getFontsize());

        if (font.isBold()) {
            attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
        }
        if (font.isItalic()) {
            attributes.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
        }
        if (font.isUnderline()) {
            attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
        }
        if (font.isStrikeThrough()) {
            attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
        }

        attributes.put(JRTextAttribute.PDF_FONT_NAME, font.getPdfFontName());
        attributes.put(JRTextAttribute.PDF_ENCODING, font.getPdfEncoding());

        if (font.isPdfEmbedded()) {
            attributes.put(JRTextAttribute.IS_PDF_EMBEDDED, Boolean.TRUE);
        }

        return attributes;
    }

    /**
     * Returns font information containing the font family, font face and font style.
     * 
     * @param name the font family or font face name
     * @param ignoreCase the flag to specify if family names or face names are searched by ignoring case or not
     * @param locale the locale
     * @return a font info object
     */
    public FontInfo getFontInfo(String name, boolean ignoreCase, Locale locale) {
        FontInfo awtFamilyMatchFontInfo = null;

        //FIXMEFONT do some cache
        List<FontFamily> families = jasperReportsContext.getExtensions(FontFamily.class);
        for (Iterator<FontFamily> itf = families.iterator(); itf.hasNext();) {
            FontFamily family = itf.next();
            if (locale == null || family.supportsLocale(locale)) {
                if (equals(name, family.getName(), ignoreCase)) {
                    return new FontInfo(family, null, Font.PLAIN);
                }

                FontFace face = family.getNormalFace();
                if (face != null) {
                    if (equals(name, face.getName(), ignoreCase)) {
                        return new FontInfo(family, face, Font.PLAIN);
                    } else if (awtFamilyMatchFontInfo == null && face.getFont() != null
                            && equals(name, face.getFont().getFamily(), ignoreCase)) {
                        awtFamilyMatchFontInfo = new FontInfo(family, face, Font.PLAIN);
                    }
                }

                face = family.getBoldFace();
                if (face != null) {
                    if (equals(name, face.getName(), ignoreCase)) {
                        return new FontInfo(family, face, Font.BOLD);
                    } else if (awtFamilyMatchFontInfo == null && face.getFont() != null
                            && equals(name, face.getFont().getFamily(), ignoreCase)) {
                        awtFamilyMatchFontInfo = new FontInfo(family, face, Font.BOLD);
                    }
                }

                face = family.getItalicFace();
                if (face != null) {
                    if (equals(name, face.getName(), ignoreCase)) {
                        return new FontInfo(family, face, Font.ITALIC);
                    } else if (awtFamilyMatchFontInfo == null && face.getFont() != null
                            && equals(name, face.getFont().getFamily(), ignoreCase)) {
                        awtFamilyMatchFontInfo = new FontInfo(family, face, Font.ITALIC);
                    }
                }

                face = family.getBoldItalicFace();
                if (face != null) {
                    if (equals(name, face.getName(), ignoreCase)) {
                        return new FontInfo(family, face, Font.BOLD | Font.ITALIC);
                    } else if (awtFamilyMatchFontInfo == null && face.getFont() != null
                            && equals(name, face.getFont().getFamily(), ignoreCase)) {
                        awtFamilyMatchFontInfo = new FontInfo(family, face, Font.BOLD | Font.ITALIC);
                    }
                }
            }
        }

        return awtFamilyMatchFontInfo;
    }

    private static boolean equals(String value1, String value2, boolean ignoreCase) {
        return ignoreCase ? value1.equalsIgnoreCase(value2) : value1.equals(value2);
    }

    /**
     * Returns font information containing the font family, font face and font style, searching for names case sensitive.
     * 
     * @param name the font family or font face name
     * @param locale the locale
     * @return a font info object
     */
    public FontInfo getFontInfo(String name, Locale locale) {
        return getFontInfo(name, false, locale);
    }

    /**
     * Returns font information containing the font family, font face and font style, searching for names case insensitive.
     * 
     * @param name the font family or font face name
     * @param locale the locale
     * @return a font info object
     * @deprecated Replaced by {@link #getFontInfo(String, boolean, Locale)}.
     */
    public FontInfo getFontInfoIgnoreCase(String name, Locale locale) {
        return getFontInfo(name, true, locale);
    }

    public FontSetInfo getFontSetInfo(String name, Locale locale, boolean ignoreMissingFonts) {
        //FIXMEFONT do some cache
        List<FontFamily> allFontFamilies = jasperReportsContext.getExtensions(FontFamily.class);
        HashMap<String, FontFamily> fontFamilies = new HashMap<String, FontFamily>(allFontFamilies.size() * 4 / 3,
                .75f);
        for (FontFamily family : allFontFamilies) {
            if (family.getName() != null && (locale == null || family.supportsLocale(locale))) {
                fontFamilies.put(family.getName(), family);
            }
        }

        Map<String, FontSetFamilyInfo> setFamilyInfos = new LinkedHashMap<String, FontSetFamilyInfo>();
        List<FontSet> allSets = jasperReportsContext.getExtensions(FontSet.class);
        FontSet foundFontSet = null;
        FontSetFamilyInfo primaryFamily = null;
        for (FontSet fontSet : allSets) {
            if (name.equals(fontSet.getName())) {
                foundFontSet = fontSet;

                List<FontSetFamily> setFamilies = fontSet.getFamilies();
                for (FontSetFamily fontSetFamily : setFamilies) {
                    FontFamily fontFamily = fontFamilies.get(fontSetFamily.getFamilyName());
                    if (fontFamily != null) {
                        FontSetFamilyInfo familyInfo = new FontSetFamilyInfo(fontSetFamily, fontFamily);
                        setFamilyInfos.put(fontSetFamily.getFamilyName(), familyInfo);

                        if (fontSetFamily.isPrimary()) {
                            primaryFamily = familyInfo;
                        }
                    } else {
                        if (ignoreMissingFonts) {
                            if (log.isWarnEnabled()) {
                                log.warn("Font family " + fontSetFamily.getFamilyName()
                                        + " was not found for font set " + name);
                            }
                        } else {
                            throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_FONT_SET_FAMILY_NOT_FOUND,
                                    new Object[] { fontSetFamily.getFamilyName(), name });
                        }
                    }
                }
            }
        }

        if (foundFontSet == null) {
            return null;
        }

        //TODO lucianc handle sets with no families
        List<FontSetFamilyInfo> familyInfoList = new ArrayList<FontSetFamilyInfo>(setFamilyInfos.values());
        if (primaryFamily == null && !familyInfoList.isEmpty()) {
            primaryFamily = familyInfoList.get(0);
        }
        return new FontSetInfo(foundFontSet, primaryFamily, familyInfoList);
    }

    public String getExportFontFamily(String name, Locale locale, String exporterKey) {
        //FIXMEFONT do some cache
        FontInfo fontInfo = getFontInfo(name, locale);
        if (fontInfo != null) {
            FontFamily family = fontInfo.getFontFamily();
            String exportFont = family.getExportFont(exporterKey);
            return exportFont == null ? name : exportFont;
        }

        FontSetInfo fontSetInfo = getFontSetInfo(name, locale, true);
        if (fontSetInfo != null) {
            String exportFont = fontSetInfo.getFontSet().getExportFont(exporterKey);
            //TODO also look at the primary family?
            return exportFont == null ? name : exportFont;
        }

        return name;
    }

    /**
     * Returns the font family names available through extensions, in alphabetical order.
     */
    public Collection<String> getFontFamilyNames() {
        TreeSet<String> familyNames = new TreeSet<String>();//FIXMEFONT use collator for order?
        //FIXMEFONT do some cache
        collectFontFamilyNames(familyNames);
        return familyNames;
    }

    protected void collectFontFamilyNames(Collection<String> names) {
        List<FontFamily> families = jasperReportsContext.getExtensions(FontFamily.class);
        for (Iterator<FontFamily> itf = families.iterator(); itf.hasNext();) {
            FontFamily family = itf.next();
            if (family.isVisible()) {
                names.add(family.getName());
            }
        }
    }

    /**
     * Returns the font names available through extensions, in alphabetical order.
     * 
     * @return the list of font names provided by extensions
     */
    public Collection<String> getFontNames() {
        TreeSet<String> fontNames = new TreeSet<String>();//FIXMEFONT use collator for order?
        //FIXMEFONT do some cache
        collectFontFamilyNames(fontNames);
        collectFontSetNames(fontNames);
        return fontNames;
    }

    protected void collectFontSetNames(Collection<String> names) {
        List<FontSet> fontSets = jasperReportsContext.getExtensions(FontSet.class);
        for (Iterator<FontSet> itf = fontSets.iterator(); itf.hasNext();) {
            FontSet fontSet = itf.next();
            names.add(fontSet.getName());
        }
    }

    /**
     * @deprecated Replaced by {@link #getAwtFontFromBundles(String, int, float, Locale, boolean)}.
     */
    public Font getAwtFontFromBundles(String name, int style, int size, Locale locale, boolean ignoreMissingFont) {
        return getAwtFontFromBundles(name, style, (float) size, locale, ignoreMissingFont);
    }

    /**
     * Calls {@link #getAwtFontFromBundles(boolean, String, int, float, Locale, boolean)} with the ignoreCase parameter set to false.
     */
    public Font getAwtFontFromBundles(String name, int style, float size, Locale locale,
            boolean ignoreMissingFont) {
        return getAwtFontFromBundles(false, name, style, size, locale, ignoreMissingFont);
    }

    /**
     *
     */
    public Font getAwtFontFromBundles(boolean ignoreCase, String name, int style, float size, Locale locale,
            boolean ignoreMissingFont) {
        Font awtFont = null;
        FontInfo fontInfo = ignoreCase ? getFontInfoIgnoreCase(name, locale) : getFontInfo(name, locale);

        if (fontInfo != null) {
            awtFont = getAwtFont(fontInfo, style, size, ignoreMissingFont);
        }

        return awtFont;
    }

    protected Font getAwtFont(FontInfo fontInfo, int style, float size, boolean ignoreMissingFont) {
        @SuppressWarnings("unused")
        int faceStyle = Font.PLAIN;
        FontFamily family = fontInfo.getFontFamily();
        FontFace face = fontInfo.getFontFace();
        if (face == null) {
            if (((style & Font.BOLD) > 0) && ((style & Font.ITALIC) > 0)) {
                face = family.getBoldItalicFace();
                faceStyle = Font.BOLD | Font.ITALIC;
            }

            if ((face == null || face.getFont() == null) && ((style & Font.BOLD) > 0)) {
                face = family.getBoldFace();
                faceStyle = Font.BOLD;
            }

            if ((face == null || face.getFont() == null) && ((style & Font.ITALIC) > 0)) {
                face = family.getItalicFace();
                faceStyle = Font.ITALIC;
            }

            if (face == null || face.getFont() == null) {
                face = family.getNormalFace();
                faceStyle = Font.PLAIN;
            }

            //         if (face == null)
            //         {
            //            throw new JRRuntimeException("Font family '" + family.getName() + "' does not have the normal font face.");
            //         }
        } else {
            faceStyle = fontInfo.getStyle();
        }

        Font awtFont;
        if (face == null || face.getFont() == null) {
            // None of the family's font faces was found to match, neither by name, nor by style and the font family does not even specify a normal face font.
            // In such case, we take the family name and consider it as JVM available font name.
            checkAwtFont(family.getName(), ignoreMissingFont);

            awtFont = new Font(family.getName(), style, (int) size);
            awtFont = awtFont.deriveFont(size);
        } else {
            awtFont = face.getFont();
            if (awtFont == null) {
                throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_NULL_FONT,
                        new Object[] { face.getName(), family.getName() });
            }

            //deriving with style and size in one call, because deriving with size and then style loses the float size
            awtFont = awtFont.deriveFont(style, size);// & ~faceStyle);
        }
        return awtFont;
    }

    public Font getAwtFontFromBundles(AwtFontAttribute fontAttribute, int style, float size, Locale locale,
            boolean ignoreMissingFont) {
        FontInfo fontInfo = fontAttribute.getFontInfo();
        if (fontInfo == null) {
            fontInfo = getFontInfo(fontAttribute.getFamily(), locale);
        }

        Font awtFont = null;
        if (fontInfo != null) {
            awtFont = getAwtFont(fontInfo, style, size, ignoreMissingFont);
        }
        return awtFont;
    }

    /**
     *
     */ //FIXMECONTEXT check how to make this cache effective again
    public void resetThreadMissingFontsCache() {
        threadMissingFontsCache.set(new HashSet<String>());
    }

    /**
     *
     */
    public void checkAwtFont(String name, boolean ignoreMissingFont) {
        if (!JRGraphEnvInitializer.isAwtFontAvailable(name)) {
            if (ignoreMissingFont) {
                Set<String> missingFontNames = threadMissingFontsCache.get();
                if (!missingFontNames.contains(name)) {
                    missingFontNames.add(name);
                    if (log.isWarnEnabled()) {
                        log.warn("Font '" + name
                                + "' is not available to the JVM. For more details, see http://jasperreports.sourceforge.net/api/net/sf/jasperreports/engine/util/JRFontNotFoundException.html");
                    }
                }
            } else {
                throw new JRFontNotFoundException(name);
            }
        }
    }

    /**
     * Returns a java.awt.Font instance by converting a JRFont instance.
     * Mostly used in combination with third-party visualization packages such as JFreeChart (for chart themes).
     * Unless the font parameter is null, this method always returns a non-null AWT font, regardless whether it was
     * found in the font extensions or not. This is because we do need a font to draw with and there is no point
     * in raising a font missing exception here, as it is not JasperReports who does the drawing. 
     */
    public Font getAwtFont(JRFont font, Locale locale) {
        if (font == null) {
            return null;
        }

        // ignoring missing font as explained in the Javadoc
        Font awtFont = getAwtFontFromBundles(font.getFontName(),
                ((font.isBold() ? Font.BOLD : Font.PLAIN) | (font.isItalic() ? Font.ITALIC : Font.PLAIN)),
                font.getFontsize(), locale, true);

        if (awtFont == null) {
            awtFont = new Font(getAttributesWithoutAwtFont(new HashMap<Attribute, Object>(), font));
        } else {
            // add underline and strikethrough attributes since these are set at
            // style/font level
            Map<Attribute, Object> attributes = new HashMap<Attribute, Object>();
            if (font.isUnderline()) {
                attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
            }
            if (font.isStrikeThrough()) {
                attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
            }

            if (!attributes.isEmpty()) {
                awtFont = awtFont.deriveFont(attributes);
            }
        }

        return awtFont;
    }

    private FontUtil() {
    }
}