/*
* 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 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;
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;
/**
* 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 ) + fs + "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" );
}
}