/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2006 - 2013 Pentaho Corporation and Contributors. All rights reserved.
*/
package org.pentaho.reporting.libraries.fonts.registry;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.fonts.LibFontBoot;
import org.pentaho.reporting.libraries.fonts.encoding.EncodingRegistry;
import org.pentaho.reporting.libraries.resourceloader.ResourceData;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
/**
* Creation-Date: 21.07.2007, 17:01:15
*
* @author Thomas Morgner
* @noinspection HardCodedStringLiteral
*/
public abstract class AbstractFontFileRegistry implements FontRegistry
{
private static final Log logger = LogFactory.getLog(AbstractFontFileRegistry.class);
private HashMap<String, FontFileRecord> seenFiles;
private HashMap<String, DefaultFontFamily> fontFamilies;
private HashMap<String, DefaultFontFamily> alternateFamilyNames;
private HashMap<String, DefaultFontFamily> fullFontNames;
protected AbstractFontFileRegistry()
{
seenFiles = new HashMap<String, FontFileRecord>();
this.fontFamilies = new HashMap<String, DefaultFontFamily>();
this.alternateFamilyNames = new HashMap<String, DefaultFontFamily>();
this.fullFontNames = new HashMap<String, DefaultFontFamily>();
}
protected HashMap<String, FontFileRecord> getSeenFiles()
{
return seenFiles;
}
protected abstract FileFilter getFileFilter();
public void initialize()
{
registerDefaultFontPath();
final Configuration configuration = LibFontBoot.getInstance().getGlobalConfig();
final Iterator extraDirIt =
configuration.findPropertyKeys("org.pentaho.reporting.libraries.fonts.extra-font-dirs.");
while (extraDirIt.hasNext())
{
final String extraDirKey = (String) extraDirIt.next();
final String extraDir = configuration.getConfigProperty(extraDirKey);
final File extraDirFile = new File(extraDir);
try
{
if (extraDirFile.isDirectory())
{
registerFontPath(extraDirFile, getDefaultEncoding());
}
}
catch (Exception e)
{
logger.warn("Extra font path " + extraDir + " could not be fully registered.", e);
}
}
}
protected String getDefaultEncoding()
{
return LibFontBoot.getInstance().getGlobalConfig().getConfigProperty
("org.pentaho.reporting.libraries.fonts.itext.FontEncoding", EncodingRegistry.getPlatformDefaultEncoding());
}
/**
* Register os-specific font paths to the PDF-FontFactory. For unix-like operating systems, X11 is searched in
* /usr/X11R6 and the default truetype fontpath is added. For windows the system font path is added (%windir%/fonts)
*/
public void registerDefaultFontPath()
{
final String encoding = getDefaultEncoding();
loadFromCache(encoding);
final String osname = safeSystemGetProperty("os.name", "<protected by system security>");
final String jrepath = safeSystemGetProperty("java.home", ".");
final String fs = safeSystemGetProperty("file.separator", File.separator);
logger.debug("Running on operating system: " + osname);
logger.debug("Character encoding used as default: " + encoding);
if (StringUtils.startsWithIgnoreCase(osname, "mac os x"))
{
final String userhome = safeSystemGetProperty("user.home", ".");
logger.debug("Detected MacOS.");
registerFontPath(new File(userhome + "/Library/Fonts"), encoding);
registerFontPath(new File("/Library/Fonts"), encoding);
registerFontPath(new File("/Network/Library/Fonts"), encoding);
registerFontPath(new File("/System/Library/Fonts"), encoding);
}
else if (StringUtils.startsWithIgnoreCase(osname, "windows"))
{
registerWindowsFontPath(encoding);
}
else
{
logger.debug("Assuming unix like file structures");
// Assume X11 is installed in the default location.
registerFontPath(new File("/usr/X11R6/lib/X11/fonts"), encoding);
registerFontPath(new File("/usr/share/fonts"), encoding);
}
registerFontPath(new File(jrepath, "lib" + fs + "fonts"), encoding);
storeToCache(encoding);
logger.info("Completed font registration.");
}
protected void registerPrimaryName(final String name, final DefaultFontFamily family)
{
this.fontFamilies.put(name, family);
}
protected void registerAlternativeName(final String name, final DefaultFontFamily family)
{
this.alternateFamilyNames.put(name, family);
}
protected void registerFullName(final String name, final DefaultFontFamily family)
{
this.fullFontNames.put(name, family);
}
protected DefaultFontFamily createFamily(final String name)
{
final DefaultFontFamily fontFamily = this.fontFamilies.get(name);
if (fontFamily != null)
{
return fontFamily;
}
final DefaultFontFamily createdFamily = new DefaultFontFamily(name);
this.fontFamilies.put(name, createdFamily);
return createdFamily;
}
public String[] getRegisteredFamilies()
{
return fontFamilies.keySet().toArray(new String[fontFamilies.size()]);
}
public String[] getAllRegisteredFamilies()
{
return alternateFamilyNames.keySet().toArray(new String[alternateFamilyNames.size()]);
}
public FontFamily getFontFamily(final String name)
{
final FontFamily primary = this.fontFamilies.get(name);
if (primary != null)
{
return primary;
}
final FontFamily secondary = this.alternateFamilyNames.get(name);
if (secondary != null)
{
return secondary;
}
return this.fullFontNames.get(name);
}
protected void loadFromCache(final String encoding)
{
final String fileName = getCacheFileName();
if (fileName == null)
{
return;
}
loadFromCache(encoding, fileName);
}
protected void populateFromCache(final HashMap<String, DefaultFontFamily> cachedFontFamilies,
final HashMap<String, DefaultFontFamily> cachedFullFontNames,
final HashMap<String, DefaultFontFamily> cachedAlternateNames)
{
this.fontFamilies.putAll(cachedFontFamilies);
this.fullFontNames.putAll(cachedFullFontNames);
this.alternateFamilyNames.putAll(cachedAlternateNames);
}
protected void loadFromCache(final String encoding, final String filename)
{
final ResourceManager resourceManager = new ResourceManager();
final File location = createStorageLocation();
if (location == null)
{
return;
}
final File ttfCache = new File(location, filename);
try
{
final ResourceKey resourceKey = resourceManager.createKey(ttfCache);
final ResourceData data = resourceManager.load(resourceKey);
final InputStream stream = data.getResourceAsStream(resourceManager);
final HashMap<String, FontFileRecord> cachedSeenFiles;
final HashMap<String, DefaultFontFamily> cachedFontFamilies;
final HashMap<String, DefaultFontFamily> cachedFullFontNames;
final HashMap<String, DefaultFontFamily> cachedAlternateNames;
try
{
final ObjectInputStream oin = new ObjectInputStream(stream);
final Object[] cache = (Object[]) oin.readObject();
if (cache.length != 5)
{
return;
}
if (ObjectUtilities.equal(encoding, cache[0]) == false)
{
return;
}
cachedSeenFiles = (HashMap<String, FontFileRecord>) cache[1];
cachedFontFamilies = (HashMap<String, DefaultFontFamily>) cache[2];
cachedFullFontNames = (HashMap<String, DefaultFontFamily>) cache[3];
cachedAlternateNames = (HashMap<String, DefaultFontFamily>) cache[4];
}
finally
{
stream.close();
}
// next; check the font-cache for validity. We cannot cleanly remove
// entries from the cache once they become invalid, so we have to rebuild
// the cache from scratch, if it is invalid.
//
// This should not matter that much, as font installations do not happen
// every day.
if (isCacheValid(cachedSeenFiles))
{
this.getSeenFiles().putAll(cachedSeenFiles);
populateFromCache(cachedFontFamilies, cachedFullFontNames, cachedAlternateNames);
}
}
catch (final ClassNotFoundException cnfe)
{
// ignore the exception.
logger.debug("Failed to restore the cache: Cache was created by a different version of LibFonts");
}
catch (Exception e)
{
logger.debug("Non-Fatal: Failed to restore the cache. The cache will be rebuilt.", e);
}
}
protected String getCacheFileName()
{
return null;
}
protected void storeToCache(final String encoding)
{
final String cacheFileName = getCacheFileName();
if (cacheFileName == null)
{
return;
}
final File location = createStorageLocation();
if (location == null)
{
return;
}
location.mkdirs();
if (location.exists() == false || location.isDirectory() == false)
{
return;
}
final File ttfCache = new File(location, cacheFileName);
try
{
final FileOutputStream fout = new FileOutputStream(ttfCache);
try
{
final Object[] map = new Object[5];
map[0] = encoding;
map[1] = getSeenFiles();
map[2] = fontFamilies;
map[3] = fullFontNames;
map[4] = alternateFamilyNames;
final ObjectOutputStream objectOut = new ObjectOutputStream(new BufferedOutputStream(fout));
objectOut.writeObject(map);
objectOut.close();
}
finally
{
try
{
fout.close();
}
catch (IOException e)
{
// ignore ..
logger.debug("Failed to store cached font data", e);
}
}
}
catch (IOException e)
{
// should not happen
logger.debug("Failed to store cached font data", e);
}
}
/**
* Registers the default windows font path. Once a font was found in the old seenFiles map and confirmed, that this
* font still exists, it gets copied into the confirmedFiles map.
*
* @param encoding the default font encoding.
*/
private void registerWindowsFontPath(final String encoding)
{
logger.debug("Found 'Windows' in the OS name, assuming DOS/Win32 structures");
// Assume windows
// If you are not using windows, ignore this. This just checks if a windows system
// directory exist and includes a font dir.
String fontPath = null;
final String windirs = safeSystemGetProperty("java.library.path", null);
final String fs = safeSystemGetProperty("file.separator", File.separator);
if (windirs != null)
{
final StringTokenizer strtok = new StringTokenizer
(windirs, safeSystemGetProperty("path.separator", File.pathSeparator));
while (strtok.hasMoreTokens())
{
final String token = strtok.nextToken();
if (StringUtils.endsWithIgnoreCase(token, "System32"))
{
// found windows folder ;-)
final int lastBackslash = token.lastIndexOf(fs);
if (lastBackslash != -1)
{
fontPath = token.substring(0, lastBackslash) + fs + "Fonts";
break;
}
// try with forward slashs. Some systems may use the unix-semantics instead.
// (Windows accepts both characters as path-separators for historical reasons)
final int lastSlash = token.lastIndexOf('/');
if (lastSlash != -1)
{
fontPath = token.substring(0, lastSlash) + lastSlash + "Fonts";
break;
}
}
}
}
logger.debug("Fonts located in \"" + fontPath + '\"');
if (fontPath != null)
{
final File file = new File(fontPath);
registerFontPath(file, encoding);
}
}
/**
* Register all fonts (*.ttf files) in the given path.
*
* @param file the directory that contains the font files.
* @param encoding the encoding for the given font.
*/
public void registerFontPath(final File file, final String encoding)
{
if (file.exists() && file.isDirectory() && file.canRead())
{
final File[] files = file.listFiles(getFileFilter());
final int fileCount = files.length;
for (int i = 0; i < fileCount; i++)
{
final File currentFile = files[i];
if (currentFile.isDirectory())
{
registerFontPath(currentFile, encoding);
}
else
{
if (isCached(currentFile) == false)
{
registerFontFile(currentFile, encoding);
}
}
}
}
}
protected boolean isCached(final File file)
{
try
{
final FontFileRecord stored = seenFiles.get(file.getCanonicalPath());
if (stored == null)
{
return false;
}
final FontFileRecord rec = new FontFileRecord(file);
if (stored.equals(rec) == false)
{
seenFiles.remove(file.getCanonicalPath());
return false;
}
return true;
}
catch (IOException e)
{
return false;
}
}
/**
* Register the font (must end this *.ttf) to the FontFactory.
*
* @param filename the filename.
* @param encoding the encoding.
*/
public void registerFontFile(final String filename,
final String encoding)
{
final File file = new File(filename);
registerFontFile(file, encoding);
}
public synchronized void registerFontFile(final File file, final String encoding)
{
if (getFileFilter().accept(file) && file.exists() && file.isFile() && file.canRead())
{
try
{
if (file.length() == 0)
{
logger.warn("Font " + file + " is invalid [zero size].");
return;
}
if (addFont(file, encoding))
{
final FontFileRecord value = new FontFileRecord(file);
seenFiles.put(file.getCanonicalPath(), value);
}
}
catch (Exception e)
{
logger.warn("Font " + file + " is invalid. Message:" + e.getMessage(), e);
}
}
}
/**
* Adds the fontname by creating the basefont object. This method tries to load the fonts as embeddable fonts, if this
* fails, it repeats the loading with the embedded-flag set to false.
*
* @param font the font file name.
* @param encoding the encoding.
* @return true, if registration was successful, false otherwise.
* @throws java.io.IOException if the base font file could not be read.
*/
protected abstract boolean addFont(final File font, final String encoding)
throws IOException;
protected String safeSystemGetProperty(final String name,
final String defaultValue)
{
try
{
return System.getProperty(name, defaultValue);
}
catch (SecurityException se)
{
return defaultValue;
}
}
protected boolean isCacheValid(final HashMap cachedSeenFiles)
{
final Iterator iterator = cachedSeenFiles.entrySet().iterator();
while (iterator.hasNext())
{
final Map.Entry entry = (Map.Entry) iterator.next();
final String fullFileName = (String) entry.getKey();
final FontFileRecord fontFileRecord = (FontFileRecord) entry.getValue();
final File fontFile = new File(fullFileName);
if (fontFile.isFile() == false || fontFile.exists() == false)
{
return false;
}
if (fontFile.length() != fontFileRecord.getFileSize())
{
return false;
}
if (fontFile.lastModified() != fontFileRecord.getLastAccessTime())
{
return false;
}
}
return true;
}
protected File createStorageLocation()
{
if ("true".equals(LibFontBoot.getInstance().getGlobalConfig().getConfigProperty
("org.pentaho.reporting.libraries.fonts.CacheFontRegistration")) == false)
{
return null;
}
final String homeDirectory = safeSystemGetProperty("user.home", null);
if (homeDirectory == null)
{
return null;
}
final File homeFile = new File(homeDirectory);
if (homeFile.isDirectory() == false)
{
return null;
}
return new File(homeFile, ".pentaho/caches/libfonts2");
}
}