org.pz.platypus.TypefaceMap.java Source code

Java tutorial

Introduction

Here is the source code for org.pz.platypus.TypefaceMap.java

Source

/***
 *  Platypus: Page Layout and Typesetting Software (free at platypus.pz.org)
 *
 *  Platypus is (c) Copyright 2006-09 Pacific Data Works LLC. All Rights Reserved.
 *  Licensed under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)
 */

package org.pz.platypus;

import org.pz.platypus.exceptions.InvalidConfigFileException;
import org.pz.platypus.exceptions.FileCloseException;

import java.util.*;
import java.io.*;

import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.DocumentException;

/**
 * Map that maps a typeface family to font files on the runtime system that implement that
 * typeface family.
 *
 * Entries consist of a typeface name -> linked-list of file URIs. So, for example,
 *  Arial -> c:\windows\fonts\arial.ttf, c:\windows\fonts\ariali.ttf, etc.
 *
 * This map is consulted when the user specifies a typeface other than the 14 base PDF fonts
 *
 * @author alb
 */
public class TypefaceMap {
    /* the data structure mapping font family to filenames */
    private HashMap<String, LinkedList<String>> map;

    private GDD gdd;

    public TypefaceMap(GDD Gdd) {
        gdd = Gdd;
        map = new HashMap<String, LinkedList<String>>();
    }

    /**
     * Reads the config/fontlist.txt file in PLATYPUS_HOME and loads the entries into the map.
     *
     * @throws InvalidConfigFileException if the fontlist.txt file is missing
     */
    public void loadMapFromFile() {
        String defaultFontFile = gdd.getHomeDirectory() + "config/fontlist.txt";
        try {
            loadFromFile(defaultFontFile);
        } catch (InvalidConfigFileException icfe) {
            gdd.logSevere(gdd.getLit("ERROR.MISSING_FILELIST") + " " + gdd.getLit("EXITING"));
            throw new InvalidConfigFileException("Missing filelist");
        }
    }

    /**
     * Writes the current map to an output file after sorting the entries on font family.
     *
     * @param filename name of file to write out.
     */
    public void writeMapToFile(final String filename) {
        if (filename == null || filename.isEmpty() || map.size() == 0) {
            return; //TODO: throw exception
        }

        // sort the file by font family (called entry here)
        Set<String> entries = map.keySet();
        Object[] sEntries = entries.toArray();
        Arrays.sort(sEntries);

        // open the file for output
        try {
            FileWriter outFile = new FileWriter(filename);
            PrintWriter out = new PrintWriter(outFile);
            for (Object entry : sEntries) {
                String[] files = getFamilyFilenames((String) entry);
                if (files.length > 0) {
                    writeMapEntryToFile(out, (String) entry, files);
                }
            }
            outFile.close();
            gdd.log("Completed writing font list to " + filename);
        } catch (IOException ioe) {
            System.out.println("Exception writing fontlist file in TypefaceMap.writeMapToFile()");
        }
    }

    /**
     * Writes a single map entry to file
     * @param out the file writer for text
     * @param family the family part of the map entry
     * @param files the files part of the map entry
     */
    void writeMapEntryToFile(PrintWriter out, String family, String[] files) {
        for (String file : files) {
            out.println(family + "=" + file);
        }
    }

    /**
     * Load the font families with their respective font files into the table
     *
     * 1. Get the list of font directories
     * 2. Get the list of fonts files from within those directories
     * 3. For each font file, load its family and name into the table
     * 4. Write out a file with the family and font files to PLATYPUS_HOME/config
     */
    public void loadFamilies() {
        String[] fontDirs = new FontDirectoryList(gdd).getDirs();
        LinkedList<String> fontFiles = getFontFileList(fontDirs);
        BaseFont bf = null;

        for (String fontFilename : fontFiles) {
            try {
                loadFontFile(fontFilename, bf);
            } catch (Exception e) {
                System.out.println("Exception loading: " + fontFilename);
            }
        }
        gdd.log("Loaded Platypus font-family with " + map.size() + " entries");
    }

    /**
     * Loads an individual font file to the map. First extracts the family, then if no error
     * occurred, loads the family and the font name to the map.
     *
     * @param fontFilename font file to load to map
     * @param bf is the base font
     */
    public void loadFontFile(final String fontFilename, final BaseFont bf) {
        if (fontFilename == null) {
            return;
        }

        String[] family = extractFamilyNames(fontFilename, bf);
        if (family.length > 0) {
            for (String familyName : family)
                addFontFileToFamily(familyName, fontFilename);
        }
    }

    /**
     * Uses iText to extract the family name (or in case of .ttc the names) of the font family.
     *
     * @param fontFilename font file
     * @param bf base font we create to get the family font name
     *
     * @return the font's family name(s) or an empty array if an error occurred.
     *
     * For explanation of .ttc handling, see:
     * http://itextdocs.lowagie.com/tutorial/fonts/getting/index.php
     *
     */
    public String[] extractFamilyNames(String fontFilename, BaseFont bf) {
        String familyName[] = new String[1];

        String names[][];
        File f = new File(fontFilename);
        if (f.exists() || !f.isDirectory()) {
            try {
                if (fontFilename.toLowerCase().endsWith(".ttc")) {
                    return (BaseFont.enumerateTTCNames(fontFilename));
                }

                bf = BaseFont.createFont(fontFilename, "winansi", BaseFont.NOT_EMBEDDED);
                names = bf.getFamilyFontName();
            } catch (IOException ioe) {
                gdd.logInfo("IOException loading " + fontFilename + " into font-family map");
                return (new String[0]);
            } catch (DocumentException de) {
                gdd.logInfo("Document Exception loading " + fontFilename + " into font-family map");
                return (new String[0]);
            }

            if (names != null && names[0] != null) {
                familyName[0] = names[0][3];
                return familyName;
            } else {
                return (new String[0]);
            }
        }
        return (new String[0]);
    }

    /**
     * Return a linked list of font files found in the passed-in list of font directories.
     * Font files must use a supported format: .ttf, .otf, .afm, or .ttc
     *
     * @param fontDirs array of directory names that should contain fonts
     * @return linked list of filenames. Will have length = 0, if not font files were found.
     */
    LinkedList<String> getFontFileList(final String[] fontDirs) {
        LinkedList<String> fontFiles = new LinkedList<String>();

        for (String dirName : fontDirs) {
            File dir = new File(dirName);
            String[] fileNames = dir.list();
            if (fileNames != null && fileNames.length != 0) {
                for (String fName : fileNames) {
                    if (fName.toLowerCase().endsWith(".ttf") || fName.toLowerCase().endsWith(".ttc")
                            || fName.toLowerCase().endsWith(".otf") || fName.toLowerCase().endsWith(".afm")
                            || fName.toLowerCase().endsWith(".pfm")) {
                        fontFiles.add(dirName + "/" + fName);
                    } // end if filename has correct extension
                } // end for every file name
            } // end if there are fileNames
        }

        gdd.log("Located " + fontFiles.size() + " font files");
        return (fontFiles);
    }

    /**
     * As a family name with a linked list of one or more associated font files
     * @param family  font family (key)
     * @param filenames font files associated with the family (value)
     */
    public void addFamily(final String family, final LinkedList<String> filenames) {
        map.put(family, filenames);
    }

    /**
     * add a filename to a family, as long as the filename was not previously added .
     *
     * @param family the family name
     * @param filename the filename to add
     */
    public void addFontFileToFamily(final String family, final String filename) {
        if (family == null || filename == null) {
            throw new IllegalArgumentException("null passed to TypefaceMap.addFile()");
        }

        LinkedList<String> fontFiles = map.get(family);

        // if the family is not found in the map (so this is the first entry)
        if (fontFiles == null) {
            LinkedList<String> ll = new LinkedList<String>();
            ll.add(filename);
            addFamily(family, ll);
            return;
        }

        // family already exists, so add filename, as long as it hasn't previously been added
        if (!fontFiles.contains(filename)) {
            fontFiles.add(filename);
        }
    }

    /**
     * Returns the list of filenames that correspond to a font/typeface family.
     *
     * @param family the family to look up
     * @return an array of filenames; an empty array in the event that the family is not found
     */
    public String[] getFamilyFilenames(final String family) {
        if (family == null) {
            throw new IllegalArgumentException("null passed to TypefaceMap.get()");
        }

        LinkedList<String> typeface = map.get(family);
        if (typeface == null) {
            return (new String[] {});
        }

        int size = typeface.size();
        String[] filenames = new String[size];

        int i = 0;
        for (String filename : typeface) {
            filenames[i++] = filename;
        }

        return (filenames);
    }

    /**
     * Load the Typeface map from a configuration file consisting on line entries of the form:
     * family=fontFilename;
     *
     * @param filename name of the file to read
     */
    public void loadFromFile(final String filename) {
        final String thisMethod = "TypefaceMap.loadFromFile()";

        if (filename == null) {
            throw new InvalidConfigFileException("Invalid filename: [null]", thisMethod);
        }

        File f = new File(filename);
        if (!f.isFile() || f.length() == 0L || !f.canRead()) {
            throw new InvalidConfigFileException("Invalid filename: " + filename, thisMethod);
        }

        BufferedReader inReader;
        try {
            inReader = new BufferedReader(new FileReader(filename));
        } catch (FileNotFoundException e) {
            throw new InvalidConfigFileException("Error reading: " + filename, thisMethod);
        }

        String line;
        try {
            while ((line = inReader.readLine()) != null) {
                loadLine(line.trim());
            }
        } catch (IOException ioe) {
            throw new InvalidConfigFileException("Error reading: " + filename, thisMethod);
        } finally {
            try {
                inReader.close();
            } catch (IOException ioe) {
                throw new FileCloseException(filename + " in " + thisMethod);
            }
        }
    }

    /**
     * Loads a line from the config file. Note: lines commencing with a # are comments
     *
     * @param line the line read from the file containing: family=file name/location
     */
    public void loadLine(final String line) {
        String family, fileLocation;

        if (line == null || line.isEmpty()) {
            return;
        }

        if (line.startsWith("#")) { // comment lines start with #
            return;
        }

        int equalsSign = line.indexOf("=");
        if (equalsSign < 0) {
            return; //curr: issue err msg here
        }

        family = line.substring(0, equalsSign);
        fileLocation = line.substring(equalsSign + 1, line.length());
        addFontFileToFamily(family, fileLocation);
    }

    /**
     * Used mostly for testing
     * @return the hash map
     */
    public HashMap<String, LinkedList<String>> getMap() {
        return (map);
    }
}