# Standard14Fonts.java

    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    package org.apache.pdfbox.pdmodel.font;

    import static org.apache.pdfbox.pdmodel.font.UniUtil.getUniNameOfCodePoint;

    import java.awt.geom.GeneralPath;
    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Collections;
    import java.util.EnumMap;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import org.apache.fontbox.FontBoxFont;
    import org.apache.fontbox.afm.AFMParser;
    import org.apache.fontbox.afm.FontMetrics;
    import org.apache.pdfbox.pdmodel.font.encoding.GlyphList;
    import org.apache.pdfbox.pdmodel.font.encoding.SymbolEncoding;

    /**
     * The "Standard 14" PDF fonts, also known as the "base 14" fonts.
     * There are 14 font files, but Acrobat uses additional names for compatibility, e.g. Arial.
     *
     * @author John Hewson
     */
    public final class Standard14Fonts
    {
        /**
         * Contains all base names and alias names for the known fonts.
         * For base fonts both the key and the value will be the base name.
         * For aliases, the key is an alias, and the value is a FontName.
         * We want a single lookup in the map to find the font both by a base name or an alias.
         */
        private static final Map<String, FontName> ALIASES = new HashMap<>(38);

        /**
         * Contains the font metrics for the standard 14 fonts.
         * The key is the font name, value is a FontMetrics instance.
         * Metrics are loaded into this map on demand, only if needed.
         *
         * @see #getAFM
         */
        private static final Map<FontName, FontMetrics> FONTS = new EnumMap<>(FontName.class);

        /**
         * Contains the mapped fonts for the standard 14 fonts.
         * The key is the font name, value is a FontBoxFont instance.
         * FontBoxFont are loaded into this map on demand, only if needed.
         */
        private static final Map<FontName, FontBoxFont> GENERIC_FONTS = new EnumMap<>(FontName.class);

        static
        {
            // the 14 standard fonts
            mapName(FontName.COURIER);
            mapName(FontName.COURIER_BOLD);
            mapName(FontName.COURIER_BOLD_OBLIQUE);
            mapName(FontName.COURIER_OBLIQUE);
            mapName(FontName.HELVETICA);
            mapName(FontName.HELVETICA_BOLD);
            mapName(FontName.HELVETICA_BOLD_OBLIQUE);
            mapName(FontName.HELVETICA_OBLIQUE);
            mapName(FontName.TIMES_ROMAN);
            mapName(FontName.TIMES_BOLD);
            mapName(FontName.TIMES_BOLD_ITALIC);
            mapName(FontName.TIMES_ITALIC);
            mapName(FontName.SYMBOL);
            mapName(FontName.ZAPF_DINGBATS);

            // alternative names from Adobe Supplement to the ISO 32000
            mapName("CourierCourierNew", FontName.COURIER);
            mapName("CourierNew", FontName.COURIER);
            mapName("CourierNew,Italic", FontName.COURIER_OBLIQUE);
            mapName("CourierNew,Bold", FontName.COURIER_BOLD);
            mapName("CourierNew,BoldItalic", FontName.COURIER_BOLD_OBLIQUE);
            mapName("Arial", FontName.HELVETICA);
            mapName("Arial,Italic", FontName.HELVETICA_OBLIQUE);
            mapName("Arial,Bold", FontName.HELVETICA_BOLD);
            mapName("Arial,BoldItalic", FontName.HELVETICA_BOLD_OBLIQUE);
            mapName("TimesNewRoman", FontName.TIMES_ROMAN);
            mapName("TimesNewRoman,Italic", FontName.TIMES_ITALIC);
            mapName("TimesNewRoman,Bold", FontName.TIMES_BOLD);
            mapName("TimesNewRoman,BoldItalic", FontName.TIMES_BOLD_ITALIC);

            // Acrobat treats these fonts as "standard 14" too (at least Acrobat preflight says so)
            mapName("Symbol,Italic", FontName.SYMBOL);
            mapName("Symbol,Bold", FontName.SYMBOL);
            mapName("Symbol,BoldItalic", FontName.SYMBOL);
            mapName("Times", FontName.TIMES_ROMAN);
            mapName("Times,Italic", FontName.TIMES_ITALIC);
            mapName("Times,Bold", FontName.TIMES_BOLD);
            mapName("Times,BoldItalic", FontName.TIMES_BOLD_ITALIC);

            // PDFBOX-3457: PDF.js file bug864847.pdf
            mapName("ArialMT", FontName.HELVETICA);
            mapName("Arial-ItalicMT", FontName.HELVETICA_OBLIQUE);
            mapName("Arial-BoldMT", FontName.HELVETICA_BOLD);
            mapName("Arial-BoldItalicMT", FontName.HELVETICA_BOLD_OBLIQUE);
        }

        private Standard14Fonts()
        {
        }

        /**
         * Loads the metrics for the base font specified by name. Metric file must exist in the pdfbox jar under
         * /org/apache/pdfbox/resources/afm/
         *
         * @param fontName one of the standard 14 font names for which to load the metrics.
         * @throws IOException if no metrics exist for that font.
         */
        private static void loadMetrics(FontName fontName) throws IOException
        {
            String resourceName = "/org/apache/pdfbox/resources/afm/" + fontName.getName() + ".afm";
            InputStream resourceAsStream = PDType1Font.class.getResourceAsStream(resourceName);
            if (resourceAsStream == null)
            {
                throw new IOException("resource '" + resourceName + "' not found");
            }
            try (InputStream afmStream = new BufferedInputStream(resourceAsStream))
            {
                AFMParser parser = new AFMParser(afmStream);
                FontMetrics metric = parser.parse(true);
                FONTS.put(fontName, metric);
            }
        }

        /**
         * Adds a standard font name to the map of known aliases, to simplify the logic of finding
         * font metrics by name. We want a single lookup in the map to find the font both by a base name or
         * an alias.
         *
         * @see #getAFM
         * @param baseName the font name of the Standard 14 font
         */
        private static void mapName(FontName baseName)
        {
            ALIASES.put(baseName.getName(), baseName);
        }

        /**
         * Adds an alias name for a standard font to the map of known aliases to the map of aliases (alias as key, standard
         * name as value). We want a single lookup in tbaseNamehe map to find the font both by a base name or an alias.
         *
         * @param alias an alias for the font
         * @param baseName  the font name of the Standard 14 font
         */
        private static void mapName(String alias, FontName baseName)
        {
            ALIASES.put(alias, baseName);
        }

        /**
         * Returns the metrics for font specified by fontName. Loads the font metrics if not already
         * loaded.
         *
         * @param fontName name of font; either a base name or alias
         * @return the font metrics or null if the name is not one of the known names
         * @throws IllegalArgumentException if no metrics exist for that font.
         */
        public static FontMetrics getAFM(String fontName)
        {
            FontName baseName = ALIASES.get(fontName);
            if (baseName == null)
            {
                return null;
            }

            if (FONTS.get(baseName) == null)
            {
                synchronized (FONTS)
                {
                    if (FONTS.get(baseName) == null)
                    {
                        try
                        {
                            loadMetrics(baseName);
                        }
                        catch (IOException e)
                        {
                            throw new IllegalArgumentException(e);
                        }
                    }
                }
            }

            return FONTS.get(baseName);
        }

        /**
         * Returns true if the given font name is one of the known names, including alias.
         *
         * @param fontName the name of font, either a base name or alias
         * @return true if the name is one of the known names
         */
        public static boolean containsName(String fontName)
        {
            return ALIASES.containsKey(fontName);
        }

        /**
         * Returns the set of known font names, including aliases.
         */
        public static Set<String> getNames()
        {
            return Collections.unmodifiableSet(ALIASES.keySet());
        }

        /**
         * Returns the base name of the font which the given font name maps to.
         *
         * @param fontName name of font, either a base name or an alias
         * @return the base name or null if this is not one of the known names
         */
        public static FontName getMappedFontName(String fontName)
        {
            return ALIASES.get(fontName);
        }

        /**
         * Returns the mapped font for the specified Standard 14 font. The mapped font is cached.
         *
         * @param baseName name of the standard 14 font
         * @return the mapped font
         */
        private static FontBoxFont getMappedFont(FontName baseName)
        {
            if (!GENERIC_FONTS.containsKey(baseName))
            {
                synchronized (GENERIC_FONTS)
                {
                    if (!GENERIC_FONTS.containsKey(baseName))
                    {
                        PDType1Font type1Font = new PDType1Font(baseName);
                        GENERIC_FONTS.put(baseName, type1Font.getFontBoxFont());
                    }
                }
            }
            return GENERIC_FONTS.get(baseName);
        }

        /**
         * Returns the path for the character with the given name for the specified Standard 14 font. The mapped font is
         * cached. The path may differ in different environments as it depends on the mapped font.
         *
         * @param baseName name of the standard 14 font
         * @param glyphName name of glyph
         * @return the mapped font
         */
        public static GeneralPath getGlyphPath(FontName baseName, String glyphName) throws IOException
        {
            // copied and adapted from PDType1Font.getNameInFont(String)
            if (!glyphName.equals(".notdef"))
            {
                FontBoxFont mappedFont = getMappedFont(baseName);
                if (mappedFont != null)
                {
                    if (mappedFont.hasGlyph(glyphName))
                    {
                        return mappedFont.getPath(glyphName);
                    }
                    String unicodes = getGlyphList(baseName).toUnicode(glyphName);
                    if (unicodes != null && unicodes.length() == 1)
                    {
                        String uniName = getUniNameOfCodePoint(unicodes.codePointAt(0));
                        if (mappedFont.hasGlyph(uniName))
                        {
                            return mappedFont.getPath(uniName);
                        }
                    }
                    if ("SymbolMT".equals(mappedFont.getName()))
                    {
                        Integer code = SymbolEncoding.INSTANCE.getNameToCodeMap().get(glyphName);
                        if (code != null)
                        {
                            String uniName = getUniNameOfCodePoint(code + 0xF000);
                            if (mappedFont.hasGlyph(uniName))
                            {
                                return mappedFont.getPath(uniName);
                            }
                        }
                    }
                }
            }
            return new GeneralPath();
        }

        private static GlyphList getGlyphList(FontName baseName)
        {
            return FontName.ZAPF_DINGBATS == baseName ? GlyphList.getZapfDingbats()
                    : GlyphList.getAdobeGlyphList();
        }
        /**
         * Enum for the names of the 14 standard fonts.
         */
        public enum FontName
        {
            TIMES_ROMAN("Times-Roman"), //
            TIMES_BOLD("Times-Bold"), //
            TIMES_ITALIC("Times-Italic"), //
            TIMES_BOLD_ITALIC("Times-BoldItalic"), //
            HELVETICA("Helvetica"), //
            HELVETICA_BOLD("Helvetica-Bold"), //
            HELVETICA_OBLIQUE("Helvetica-Oblique"), //
            HELVETICA_BOLD_OBLIQUE("Helvetica-BoldOblique"), //
            COURIER("Courier"), //
            COURIER_BOLD("Courier-Bold"), //
            COURIER_OBLIQUE("Courier-Oblique"), //
            COURIER_BOLD_OBLIQUE("Courier-BoldOblique"), //
            SYMBOL("Symbol"), //
            ZAPF_DINGBATS("ZapfDingbats");

            private final String name;

            private FontName(String name)
            {
                this.name = name;
            }

            public String getName()
            {
                return name;
            }

            @Override
            public String toString()
            {
                return name;
            }
        }

    }