org.apache.fop.fonts.FontCache Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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.
*/
/* $Id: FontCache.java 1805173 2017-08-16 10:50:04Z ssteiner $ */
package org.apache.fop.fonts;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.io.InternalResourceResolver;
import org.apache.fop.util.LogUtil;
/**
* Fop cache (currently only used for font info caching)
*/
public final class FontCache implements Serializable {
/**
* Serialization Version UID. Change this value if you want to make sure the
* user's cache file is purged after an update.
*/
private static final long serialVersionUID = 9129238336422194339L;
/** logging instance */
private static Log log = LogFactory.getLog(FontCache.class);
/** FOP's user directory name */
private static final String FOP_USER_DIR = ".fop";
/** font cache file path */
private static final String DEFAULT_CACHE_FILENAME = "fop-fonts.cache";
/** has this cache been changed since it was last read? */
private transient boolean changed;
/** change lock */
private final boolean[] changeLock = new boolean[1];
/**
* master mapping of font url -> font info. This needs to be a list, since a
* TTC file may contain more than 1 font.
* @serial
*/
private Map fontfileMap;
/**
* mapping of font url -> file modified date (for all fonts that have failed
* to load)
* @serial
*/
private Map failedFontMap;
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
}
private static File getUserHome() {
return toDirectory(System.getProperty("user.home"));
}
private static File getTempDirectory() {
return toDirectory(System.getProperty("java.io.tmpdir"));
}
private static File toDirectory(String path) {
if (path != null) {
File dir = new File(path);
if (dir.exists()) {
return dir;
}
}
return null;
}
/**
* Returns the default font cache file.
*
* @param forWriting
* true if the user directory should be created
* @return the default font cache file
*/
public static File getDefaultCacheFile(boolean forWriting) {
File userHome = getUserHome();
if (userHome != null) {
File fopUserDir = new File(userHome, FOP_USER_DIR);
if (forWriting) {
boolean writable = fopUserDir.canWrite();
if (!fopUserDir.exists()) {
writable = fopUserDir.mkdir();
}
if (!writable) {
userHome = getTempDirectory();
fopUserDir = new File(userHome, FOP_USER_DIR);
fopUserDir.mkdir();
}
}
return new File(fopUserDir, DEFAULT_CACHE_FILENAME);
}
return new File(FOP_USER_DIR);
}
/**
* Reads the default font cache file and returns its contents.
*
* @return the font cache deserialized from the file (or null if no cache
* file exists or if it could not be read)
* @deprecated use {@link #loadFrom(File)} instead
*/
public static FontCache load() {
return loadFrom(getDefaultCacheFile(false));
}
/**
* Reads a font cache file and returns its contents.
*
* @param cacheFile
* the cache file
* @return the font cache deserialized from the file (or null if no cache
* file exists or if it could not be read)
*/
public static FontCache loadFrom(File cacheFile) {
if (cacheFile.exists()) {
try {
if (log.isTraceEnabled()) {
log.trace("Loading font cache from "
+ cacheFile.getCanonicalPath());
}
InputStream in = new BufferedInputStream(new FileInputStream(cacheFile));
ObjectInputStream oin = new ObjectInputStream(in);
try {
return (FontCache) oin.readObject();
} finally {
IOUtils.closeQuietly(oin);
}
} catch (ClassNotFoundException e) {
// We don't really care about the exception since it's just a
// cache file
log.warn("Could not read font cache. Discarding font cache file. Reason: "
+ e.getMessage());
} catch (IOException ioe) {
// We don't really care about the exception since it's just a
// cache file
log.warn("I/O exception while reading font cache ("
+ ioe.getMessage() + "). Discarding font cache file.");
try {
cacheFile.delete();
} catch (SecurityException ex) {
log.warn("Failed to delete font cache file: "
+ cacheFile.getAbsolutePath());
}
}
}
return null;
}
/**
* Writes the font cache to disk.
*
* @throws FOPException fop exception
* @deprecated use {@link #saveTo(File)} instead
*/
public void save() throws FOPException {
saveTo(getDefaultCacheFile(true));
}
/**
* Writes the font cache to disk.
*
* @param cacheFile
* the file to write to
* @throws FOPException
* fop exception
*/
public void saveTo(File cacheFile) throws FOPException {
synchronized (changeLock) {
if (changed) {
try {
log.trace("Writing font cache to " + cacheFile.getCanonicalPath());
OutputStream out = new java.io.FileOutputStream(cacheFile);
out = new java.io.BufferedOutputStream(out);
ObjectOutputStream oout = new ObjectOutputStream(out);
try {
oout.writeObject(this);
} finally {
IOUtils.closeQuietly(oout);
}
} catch (IOException ioe) {
LogUtil.handleException(log, ioe, true);
}
changed = false;
log.trace("Cache file written.");
}
}
}
/**
* creates a key given a font info for the font mapping
*
* @param fontInfo
* font info
* @return font cache key
*/
protected static String getCacheKey(EmbedFontInfo fontInfo) {
if (fontInfo != null) {
URI embedFile = fontInfo.getEmbedURI();
URI metricsFile = fontInfo.getMetricsURI();
return (embedFile != null) ? embedFile.toASCIIString() : metricsFile.toASCIIString();
}
return null;
}
/**
* cache has been updated since it was read
*
* @return if this cache has changed
*/
public boolean hasChanged() {
return this.changed;
}
/**
* is this font in the cache?
*
* @param embedUrl
* font info
* @return boolean
*/
public boolean containsFont(String embedUrl) {
return (embedUrl != null && getFontFileMap().containsKey(embedUrl));
}
/**
* is this font info in the cache?
*
* @param fontInfo
* font info
* @return font
*/
public boolean containsFont(EmbedFontInfo fontInfo) {
return (fontInfo != null && getFontFileMap().containsKey(
getCacheKey(fontInfo)));
}
/**
* Tries to identify a File instance from an array of URLs. If there's no
* file URL in the array, the method returns null.
*
* @param urls
* array of possible font urls
* @return file font file
*/
public static File getFileFromUrls(String[] urls) {
for (String urlStr : urls) {
if (urlStr != null) {
File fontFile = null;
if (urlStr.startsWith("file:")) {
try {
URL url = new URL(urlStr);
fontFile = FileUtils.toFile(url);
} catch (MalformedURLException mfue) {
// do nothing
}
}
if (fontFile == null) {
fontFile = new File(urlStr);
}
if (fontFile.exists() && fontFile.canRead()) {
return fontFile;
}
}
}
return null;
}
private Map getFontFileMap() {
if (fontfileMap == null) {
fontfileMap = new HashMap();
}
return fontfileMap;
}
/**
* Adds a font info to cache
*
* @param fontInfo
* font info
*/
public void addFont(EmbedFontInfo fontInfo, InternalResourceResolver resourceResolver) {
String cacheKey = getCacheKey(fontInfo);
synchronized (changeLock) {
CachedFontFile cachedFontFile;
if (containsFont(cacheKey)) {
cachedFontFile = getFontFileMap().get(cacheKey);
if (!cachedFontFile.containsFont(fontInfo)) {
cachedFontFile.put(fontInfo);
}
} else {
// try and determine modified date
URI fontUri = resourceResolver.resolveFromBase(fontInfo.getEmbedURI());
long lastModified = getLastModified(fontUri);
cachedFontFile = new CachedFontFile(lastModified);
if (log.isTraceEnabled()) {
log.trace("Font added to cache: " + cacheKey);
}
cachedFontFile.put(fontInfo);
getFontFileMap().put(cacheKey, cachedFontFile);
changed = true;
}
}
}
/**
* Returns a font from the cache.
*
* @param embedUrl
* font info
* @return CachedFontFile object
*/
public CachedFontFile getFontFile(String embedUrl) {
return containsFont(embedUrl) ? getFontFileMap().get(embedUrl) : null;
}
/**
* Returns the EmbedFontInfo instances belonging to a font file. If the font
* file was modified since it was cached the entry is removed and null is
* returned.
*
* @param embedUrl
* the font URL
* @param lastModified
* the last modified date/time of the font file
* @return the EmbedFontInfo instances or null if there's no cached entry or
* if it is outdated
*/
public EmbedFontInfo[] getFontInfos(String embedUrl, long lastModified) {
CachedFontFile cff = getFontFile(embedUrl);
if (cff.lastModified() == lastModified) {
return cff.getEmbedFontInfos();
} else {
removeFont(embedUrl);
return null;
}
}
/**
* removes font from cache
*
* @param embedUrl
* embed url
*/
public void removeFont(String embedUrl) {
synchronized (changeLock) {
if (containsFont(embedUrl)) {
if (log.isTraceEnabled()) {
log.trace("Font removed from cache: " + embedUrl);
}
getFontFileMap().remove(embedUrl);
changed = true;
}
}
}
/**
* has this font previously failed to load?
*
* @param embedUrl
* embed url
* @param lastModified
* last modified
* @return whether this is a failed font
*/
public boolean isFailedFont(String embedUrl, long lastModified) {
synchronized (changeLock) {
if (getFailedFontMap().containsKey(embedUrl)) {
long failedLastModified = getFailedFontMap().get(
embedUrl);
if (lastModified != failedLastModified) {
// this font has been changed so lets remove it
// from failed font map for now
getFailedFontMap().remove(embedUrl);
changed = true;
}
return true;
} else {
return false;
}
}
}
/**
* Registers a failed font with the cache
*
* @param embedUrl
* embed url
* @param lastModified
* time last modified
*/
public void registerFailedFont(String embedUrl, long lastModified) {
synchronized (changeLock) {
if (!getFailedFontMap().containsKey(embedUrl)) {
getFailedFontMap().put(embedUrl, lastModified);
changed = true;
}
}
}
private Map getFailedFontMap() {
if (failedFontMap == null) {
failedFontMap = new HashMap();
}
return failedFontMap;
}
/**
* Clears font cache
*/
public void clear() {
synchronized (changeLock) {
if (log.isTraceEnabled()) {
log.trace("Font cache cleared.");
}
fontfileMap = null;
failedFontMap = null;
changed = true;
}
}
/**
* Retrieve the last modified date/time of a URI.
*
* @param uri the URI
* @return the last modified date/time
*/
public static long getLastModified(URI uri) {
try {
URL url = uri.toURL();
URLConnection conn = url.openConnection();
try {
return conn.getLastModified();
} finally {
// An InputStream is created even if it's not accessed, but we
// need to close it.
IOUtils.closeQuietly(conn.getInputStream());
}
} catch (IOException e) {
// Should never happen, because URL must be local
log.debug("IOError: " + e.getMessage());
return 0;
}
}
private static class CachedFontFile implements Serializable {
private static final long serialVersionUID = 4524237324330578883L;
/** file modify date (if available) */
private long lastModified = -1;
private Map filefontsMap;
public CachedFontFile(long lastModified) {
setLastModified(lastModified);
}
private Map getFileFontsMap() {
if (filefontsMap == null) {
filefontsMap = new HashMap();
}
return filefontsMap;
}
void put(EmbedFontInfo efi) {
getFileFontsMap().put(efi.getPostScriptName(), efi);
}
public boolean containsFont(EmbedFontInfo efi) {
return efi.getPostScriptName() != null
&& getFileFontsMap().containsKey(efi.getPostScriptName());
}
public EmbedFontInfo[] getEmbedFontInfos() {
return getFileFontsMap().values().toArray(
new EmbedFontInfo[getFileFontsMap().size()]);
}
/**
* Gets the modified timestamp for font file (not always available)
*
* @return modified timestamp
*/
public long lastModified() {
return this.lastModified;
}
/**
* Gets the modified timestamp for font file (used for the purposes of
* font info caching)
*
* @param lastModified
* modified font file timestamp
*/
public void setLastModified(long lastModified) {
this.lastModified = lastModified;
}
/**
* @return string representation of this object {@inheritDoc}
*/
public String toString() {
return super.toString() + ", lastModified=" + lastModified;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy