1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.font;
  27 
  28 import java.io.File;
  29 import java.io.FileInputStream;
  30 import java.io.FileNotFoundException;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.security.AccessController;
  34 import java.security.PrivilegedAction;
  35 import java.util.ArrayList;
  36 import java.util.HashMap;
  37 import java.util.List;
  38 import java.util.Locale;
  39 
  40 import javax.xml.parsers.SAXParser;
  41 import javax.xml.parsers.SAXParserFactory;
  42 
  43 import org.xml.sax.Attributes;
  44 import org.xml.sax.SAXException;
  45 import org.xml.sax.helpers.DefaultHandler;
  46 
  47 import com.sun.glass.utils.NativeLibLoader;
  48 
  49 /**
  50  * Class AndroidFontFinder reads font descriptor from
  51  * /system/etc/system_fonts.xml. If that file doesn't exist it is replaced by
  52  * embedded font descriptor {@link com/sun/javafx/font/android_system_fonts.xml} which
  53  * defines some basic mappings based on best guess which fonts are mandatory on
  54  * platforms lower than 4.0 and how they map to typefaces.
  55  */
  56 class AndroidFontFinder {
  57 
  58     private final static String SYSTEM_FONT_NAME    = "sans serif";
  59     private final static float SYSTEM_FONT_SIZE     = 16.0f;
  60 
  61     final static String fontDescriptor_2_X_Path = "/com/sun/javafx/font/android_system_fonts.xml";
  62     final static String fontDescriptor_4_X_Path = "/system/etc/system_fonts.xml";
  63     final static String systemFontsDir = "/system/fonts";
  64 
  65     static {
  66         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
  67             NativeLibLoader.loadLibrary("javafx_font");
  68             return null;
  69         });
  70     }
  71 
  72     public static String getSystemFont() {
  73         return SYSTEM_FONT_NAME;
  74     }
  75 
  76     public static float getSystemFontSize() {
  77         return SYSTEM_FONT_SIZE;
  78     }
  79 
  80     public static String getSystemFontsDir() {
  81         return systemFontsDir;
  82     }
  83 
  84     private static boolean parse_2_X_SystemDefaultFonts(
  85             final HashMap<String, String> fontToFileMap,
  86             final HashMap<String, String> fontToFamilyNameMap,
  87             final HashMap<String, ArrayList<String>> familyToFontListMap) {
  88 
  89         InputStream is = AndroidFontFinder.class
  90                 .getResourceAsStream(fontDescriptor_2_X_Path);
  91         if (is == null) {
  92             System.err
  93                     .println("Resource not found: " + fontDescriptor_2_X_Path);
  94             return false;
  95         }
  96         return parseSystemDefaultFonts(is, fontToFileMap, fontToFamilyNameMap,
  97                 familyToFontListMap);
  98     }
  99 
 100     private static boolean parse_4_X_SystemDefaultFonts(
 101             final HashMap<String, String> fontToFileMap,
 102             final HashMap<String, String> fontToFamilyNameMap,
 103             final HashMap<String, ArrayList<String>> familyToFontListMap) {
 104         File iFile = new File(fontDescriptor_4_X_Path);
 105         try {
 106             return parseSystemDefaultFonts(new FileInputStream(iFile),
 107                     fontToFileMap, fontToFamilyNameMap, familyToFontListMap);
 108 
 109         } catch (FileNotFoundException e) {
 110             System.err.println("File not found: " + fontDescriptor_4_X_Path);
 111         }
 112         return false;
 113     }
 114 
 115     private static boolean parseSystemDefaultFonts(final InputStream is,
 116             final HashMap<String, String> fontToFileMap,
 117             final HashMap<String, String> fontToFamilyNameMap,
 118             final HashMap<String, ArrayList<String>> familyToFontListMap) {
 119 
 120         try {
 121 
 122             SAXParserFactory factory = SAXParserFactory.newInstance();
 123             SAXParser saxParser = factory.newSAXParser();
 124 
 125             DefaultHandler handler = new DefaultHandler() {
 126                 private final static char DASH      = '-';
 127                 private final static String FAMILY  = "family";
 128 
 129                 private final static String FILE    = "file";
 130                 private final static String FILESET = "fileset";
 131                 private final static String NAME    = "name";
 132                 private final static String NAMESET = "nameset";
 133                 private final static char SPACE     = ' ';
 134                 final List<String> filesets = new ArrayList<>();
 135 
 136                 boolean inFamily = false;
 137                 boolean inFile = false;
 138                 boolean inFileset = false;
 139                 boolean inName = false;
 140                 boolean inNameset = false;
 141 
 142                 private final List<String> namesets = new ArrayList<>();
 143                 private final String[] styles = new String[] {
 144                         "regular", "bold", "italic", "bold italic" };
 145 
 146                 public void characters(char[] ch, int start, int length)
 147                         throws SAXException {
 148                     if (inName) {
 149                         String nameset = new String(ch, start, length)
 150                                 .toLowerCase();
 151                         namesets.add(nameset);
 152                     } else if (inFile) {
 153                         String fileset = new String(ch, start, length);
 154                         filesets.add(fileset);
 155                     }
 156                 }
 157 
 158                 public void endElement(String uri, String localName,
 159                         String qName) throws SAXException {
 160                     if (qName.equalsIgnoreCase(FAMILY)) {
 161                         for (String family : namesets) {
 162                             int i = 0;
 163                             String familyName = family.replace(DASH, SPACE);
 164                             for (String file : filesets) {
 165                                 String fullName = familyName + " " + styles[i];
 166                                 String fullFile = systemFontsDir
 167                                         + File.separator + file;
 168                                 File f = new File(fullFile);
 169                                 if (!f.exists() || !f.canRead()) {
 170                                     continue;
 171                                 }
 172                                 fontToFileMap.put(fullName, fullFile);
 173                                 fontToFamilyNameMap.put(fullName, familyName);
 174                                 ArrayList<String> list = familyToFontListMap
 175                                         .get(familyName);
 176                                 if (list == null) {
 177                                     list = new ArrayList<String>();
 178                                     familyToFontListMap.put(familyName, list);
 179                                 }
 180                                 list.add(fullName);
 181                                 i++;
 182                             }
 183                         }
 184                         inFamily = false;
 185                     } else if (qName.equalsIgnoreCase(NAMESET)) {
 186                         inNameset = false;
 187                     } else if (qName.equalsIgnoreCase(FILESET)) {
 188                         inFileset = false;
 189                     } else if (qName.equalsIgnoreCase(NAME)) {
 190                         inName = false;
 191                     } else if (qName.equalsIgnoreCase(FILE)) {
 192                         inFile = false;
 193                     }
 194                 }
 195 
 196                 @Override
 197                 public void startElement(String uri, String localName,
 198                         String qName, Attributes attributes)
 199                         throws SAXException {
 200                     if (qName.equalsIgnoreCase(FAMILY)) {
 201                         inFamily = true;
 202                         namesets.clear();
 203                         filesets.clear();
 204                     } else if (qName.equalsIgnoreCase(NAMESET)) {
 205                         inNameset = true;
 206                     } else if (qName.equalsIgnoreCase(FILESET)) {
 207                         inFileset = true;
 208                     } else if (qName.equalsIgnoreCase(NAME)) {
 209                         inName = true;
 210                     } else if (qName.equalsIgnoreCase(FILE)) {
 211                         inFile = true;
 212                     }
 213                 }
 214             };// DefaultHandler
 215 
 216             saxParser.parse(is, handler);
 217             return true;
 218 
 219         } catch (IOException e) {
 220             System.err.println("Failed to load default fonts descriptor: "
 221                     + fontDescriptor_4_X_Path);
 222         } catch (Exception e) {
 223             System.err.println("Failed parsing default fonts descriptor;");
 224             e.printStackTrace();
 225         }
 226         return false;
 227     }
 228 
 229     public static void populateFontFileNameMap(
 230             HashMap<String, String> fontToFileMap,
 231             HashMap<String, String> fontToFamilyNameMap,
 232             HashMap<String, ArrayList<String>> familyToFontListMap,
 233             Locale locale) {
 234 
 235         if (fontToFileMap == null || fontToFamilyNameMap == null
 236                 || familyToFontListMap == null) {
 237             return;
 238         }
 239         if (locale == null) {
 240             locale = Locale.ENGLISH;
 241         }
 242 
 243         boolean systemFonts_4_X_DescriptorFound = parse_4_X_SystemDefaultFonts(
 244                 fontToFileMap, fontToFamilyNameMap, familyToFontListMap);
 245         if (!systemFonts_4_X_DescriptorFound) {
 246             parse_2_X_SystemDefaultFonts(fontToFileMap, fontToFamilyNameMap,
 247                     familyToFontListMap);
 248         }
 249     }
 250 }