001    /*
002     * Copyright 2007 - 2008 JEuclid, http://jeuclid.sf.net
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    /* $Id: DefaultFontFactory.java,v 2bab6eb875e8 2010/08/11 16:45:50 max $ */
018    
019    package net.sourceforge.jeuclid.font;
020    
021    import java.awt.Font;
022    import java.awt.FontFormatException;
023    import java.awt.GraphicsEnvironment;
024    import java.io.File;
025    import java.io.IOException;
026    import java.io.InputStream;
027    import java.net.URL;
028    import java.util.Collection;
029    import java.util.HashMap;
030    import java.util.HashSet;
031    import java.util.List;
032    import java.util.Locale;
033    import java.util.Map;
034    import java.util.Set;
035    
036    import javax.annotation.concurrent.ThreadSafe;
037    
038    import org.apache.commons.logging.Log;
039    import org.apache.commons.logging.LogFactory;
040    import org.apache.xmlgraphics.util.ClasspathResource;
041    
042    /**
043     * Concrete FontFactory implementation that does simple caching of Fonts
044     * loaded via {@link Font#createFont(int, File)} APIs.
045     * 
046     * @version $Revision: 2bab6eb875e8 $
047     */
048    @ThreadSafe
049    public class DefaultFontFactory extends FontFactory {
050    
051        private static final int CACHE_FONT_SIZE = 12;
052    
053        private static final int NUM_STYLES = 4;
054    
055        /**
056         * Logger for this class
057         */
058        private static final Log LOGGER = LogFactory
059                .getLog(DefaultFontFactory.class);
060    
061        private final Map<String, Font[]> fontCache = new HashMap<String, Font[]>();
062    
063        DefaultFontFactory() {
064            this.autoloadFontsFromAWT();
065            this.autoloadFontsFromClasspath();
066        }
067    
068        private void autoloadFontsFromAWT() {
069            final String[] fam = GraphicsEnvironment
070                    .getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
071            for (final String element : fam) {
072                for (int i = 0; i < DefaultFontFactory.NUM_STYLES; i++) {
073                    final Font f = new Font(element, i,
074                            DefaultFontFactory.CACHE_FONT_SIZE);
075                    this.cacheFont(f);
076                }
077            }
078        }
079    
080        @SuppressWarnings("unchecked")
081        private void autoloadFontsFromClasspath() {
082            final List<URL> fonts = ClasspathResource.getInstance()
083                    .listResourcesOfMimeType("application/x-font");
084            for (final URL u : fonts) {
085                try {
086                    try {
087                        this.cacheFont(Font.createFont(Font.TRUETYPE_FONT, u
088                                .openStream()));
089                    } catch (final FontFormatException e) {
090                        try {
091                            this.cacheFont(Font.createFont(Font.TYPE1_FONT, u
092                                    .openStream()));
093                        } catch (final FontFormatException e1) {
094                            DefaultFontFactory.LOGGER.warn(e.getMessage());
095                        }
096                    }
097                } catch (final IOException e) {
098                    DefaultFontFactory.LOGGER.warn(e.getMessage());
099                }
100            }
101    
102        }
103    
104        /**
105         * Create a font object with specified properties. Font name may refer to
106         * either 'built-in' or loaded externally and 'cached' font.
107         * 
108         * @param name
109         *            font name or font family name
110         * @param style
111         *            font style
112         * @param size
113         *            font size
114         * @return Font instance
115         * @see java.awt.Font#Font(String, int, int)
116         */
117        @Override
118        public Font getFont(final String name, final int style, final float size) {
119            Font font;
120            synchronized (this.fontCache) {
121                final Font[] fonts = this.fontCache.get(name
122                        .toLowerCase(Locale.ENGLISH));
123                if (fonts == null) {
124                    font = this.cacheFont(
125                            new Font(name, Font.PLAIN,
126                                    DefaultFontFactory.CACHE_FONT_SIZE))
127                            .deriveFont(style, size);
128                } else {
129                    font = fonts[style];
130                    if (font == null) {
131                        font = fonts[0].deriveFont(style, size);
132                        fonts[style] = font;
133                    } else {
134                        font = font.deriveFont(size);
135                    }
136                }
137            }
138            return font;
139        }
140    
141        /** {@inheritDoc} */
142        @Override
143        public Font getFont(final List<String> preferredFonts,
144                final int codepoint, final int style, final float size) {
145            Font font = this.searchFontList(preferredFonts, codepoint, style,
146                    size);
147            if (font == null) {
148                font = this.searchFontList(this.fontCache.keySet(), codepoint,
149                        style, size);
150            }
151            return font;
152        }
153    
154        private Font searchFontList(final Collection<String> fontList,
155                final int codepoint, final int style, final float size) {
156            for (final String fontName : fontList) {
157                final Font font = this.getFont(fontName, style, size);
158                final String desiredFont = fontName.trim();
159                if (((font.getFamily().equalsIgnoreCase(desiredFont)) || (font
160                        .getFontName().equalsIgnoreCase(desiredFont)))
161                        && (font.canDisplay(codepoint))) {
162                    return font;
163                }
164            }
165            return null;
166        }
167    
168        /**
169         * Load an external font from a file and 'register' (aka 'cache') it for
170         * future use.
171         * 
172         * @param format
173         *            font format (TTF or TYPE_1 currently supported by the
174         *            platform)
175         * @param fontFile
176         *            file which contains the font
177         * @return The newly created Font instance
178         * @throws FontFormatException
179         *             if font contained in the file doesn't match the specified
180         *             format
181         * @throws IOException
182         *             in case of problem while reading the file
183         * @see java.awt.Font#createFont(int, File)
184         */
185        @Override
186        public Font registerFont(final int format, final File fontFile)
187                throws IOException, FontFormatException {
188    
189            return this.cacheFont(Font.createFont(format, fontFile));
190        }
191    
192        /**
193         * Load an external font from a stream and 'register' (aka 'cache') it for
194         * future use.
195         * 
196         * @param format
197         *            font format (TTF or TYPE_1 currently supported by the
198         *            platform)
199         * @param fontStream
200         *            file which contains the font
201         * @return The newly created Font instance
202         * @throws FontFormatException
203         *             if font contained in the stream doesn't match the specified
204         *             format
205         * @throws IOException
206         *             in case of problem while reading the stream
207         * @see java.awt.Font#createFont(int, InputStream)
208         */
209        @Override
210        public Font registerFont(final int format, final InputStream fontStream)
211                throws IOException, FontFormatException {
212    
213            return this.cacheFont(Font.createFont(format, fontStream));
214        }
215    
216        /**
217         * Actually stores a font in the cache. Uses font name and font family as
218         * keys.
219         * 
220         * @param font
221         *            Font instance to cache
222         * @return the font instance that was cached
223         */
224        private Font cacheFont(final Font font) {
225            final String family = font.getFamily().trim().toLowerCase(
226                    Locale.ENGLISH);
227            final String fontname = font.getName().trim().toLowerCase(
228                    Locale.ENGLISH);
229            int style = font.getStyle();
230            if (fontname.contains("italic")) {
231                style |= Font.ITALIC;
232            }
233            if (fontname.contains("oblique")) {
234                style |= Font.ITALIC;
235            }
236            if (fontname.contains("bold")) {
237                style |= Font.BOLD;
238            }
239            this.cacheFontWithStyle(font, family, style);
240            this.cacheFontWithStyle(font, fontname, style);
241            return font;
242        }
243    
244        private void cacheFontWithStyle(final Font font, final String cacheName,
245                final int style) {
246            synchronized (this.fontCache) {
247                Font[] fonts = this.fontCache.get(cacheName);
248                if (fonts == null) {
249                    fonts = new Font[DefaultFontFactory.NUM_STYLES];
250                    this.fontCache.put(cacheName, fonts);
251                    fonts[0] = font;
252                }
253                fonts[style] = font;
254            }
255        }
256    
257        /** {@inheritDoc} */
258        @Override
259        public Set<String> listFontNames() {
260            final Set<String> retVal;
261            synchronized (this.fontCache) {
262                retVal = new HashSet<String>(this.fontCache.keySet());
263            }
264            return retVal;
265        }
266    }