/**
* ===========================================
* LibFonts : a free Java font reading library
* ===========================================
*
* Project Info: http://reporting.pentaho.org/libfonts/
*
* (C) Copyright 2006-2008, by Pentaho Corporation and Contributors.
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library 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.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* ------------
* BaseFontSupport.java
* ------------
*/
package org.pentaho.reporting.libraries.fonts.itext;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;
import java.awt.Font;
import com.lowagie.text.pdf.FontMapper;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.DocumentException;
import org.pentaho.reporting.libraries.fonts.registry.FontRecord;
import org.pentaho.reporting.libraries.fonts.registry.FontSource;
import org.pentaho.reporting.libraries.fonts.registry.FontFamily;
import org.pentaho.reporting.libraries.fonts.truetype.TrueTypeFontRecord;
import org.pentaho.reporting.libraries.fonts.LibFontBoot;
import org.pentaho.reporting.libraries.fonts.FontMappingUtility;
import org.pentaho.reporting.libraries.fonts.merge.CompoundFontRecord;
import org.pentaho.reporting.libraries.base.config.ExtendedConfiguration;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* iText font support.
*
* @author Thomas Morgner
*/
public class BaseFontSupport implements FontMapper
{
private static Log logger = LogFactory.getLog(BaseFontSupport.class);
/**
* Storage for BaseFont objects created.
*/
private final Map baseFonts;
private String defaultEncoding;
private boolean useGlobalCache;
private boolean embedFonts;
private ITextFontRegistry registry;
/**
* Creates a new support instance.
*/
public BaseFontSupport(final ITextFontRegistry registry)
{
this(registry, "UTF-8");
}
public BaseFontSupport(final ITextFontRegistry registry,
final String defaultEncoding)
{
this.baseFonts = new HashMap();
this.registry = registry;
this.defaultEncoding = defaultEncoding;
final ExtendedConfiguration extendedConfig = LibFontBoot.getInstance().getExtendedConfig();
this.useGlobalCache = extendedConfig.getBoolProperty("org.pentaho.reporting.libraries.fonts.itext.UseGlobalFontCache");
}
public String getDefaultEncoding()
{
return defaultEncoding;
}
public void setDefaultEncoding(final String defaultEncoding)
{
if (defaultEncoding == null)
{
throw new NullPointerException("DefaultEncoding is null.");
}
this.defaultEncoding = defaultEncoding;
}
public boolean isEmbedFonts()
{
return embedFonts;
}
public void setEmbedFonts(final boolean embedFonts)
{
this.embedFonts = embedFonts;
}
/**
* Close the font support.
*/
public void close()
{
this.baseFonts.clear();
}
/**
* Creates a iText-BaseFont for an font. If no basefont could be created, an BaseFontCreateException is thrown.
*
* @param logicalName the name of the font (null not permitted).
* @param bold a flag indicating whether the font is rendered as bold font.
* @param italic a flag indicating whether the font is rendered as italic or cursive font.
* @param encoding the encoding.
* @param embedded a flag indicating whether to embed the font glyphs in the generated documents.
* @return the base font record.
* @throws BaseFontCreateException if there was a problem setting the font for the target.
*/
public BaseFont createBaseFont(final String logicalName,
final boolean bold,
final boolean italic,
final String encoding,
final boolean embedded)
throws BaseFontCreateException
{
return createBaseFontRecord(logicalName, bold, italic, encoding, embedded).getBaseFont();
}
/**
* Creates a BaseFontRecord for an font. If no basefont could be created, an BaseFontCreateException is thrown.
*
* @param logicalName the name of the font (null not permitted).
* @param bold a flag indicating whether the font is rendered as bold font.
* @param italic a flag indicating whether the font is rendered as italic or cursive font.
* @param encoding the encoding.
* @param embedded a flag indicating whether to embed the font glyphs in the generated documents.
* @return the base font record.
* @throws BaseFontCreateException if there was a problem setting the font for the target.
*/
public BaseFontRecord createBaseFontRecord(final String logicalName,
final boolean bold,
final boolean italic,
String encoding,
final boolean embedded)
throws BaseFontCreateException
{
if (logicalName == null)
{
throw new NullPointerException("Font definition is null.");
}
if (encoding == null)
{
encoding = getDefaultEncoding();
}
// use the Java logical font name to map to a predefined iText font.
final String fontKey;
if (FontMappingUtility.isCourier(logicalName))
{
fontKey = "Courier";
}
else if (FontMappingUtility.isSymbol(logicalName))
{
fontKey = "Symbol";
}
else if (FontMappingUtility.isSerif(logicalName))
{
fontKey = "Times";
}
else if (FontMappingUtility.isSansSerif(logicalName))
{
// default, this catches Dialog and SansSerif
fontKey = "Helvetica";
}
else
{
fontKey = logicalName;
}
// iText uses some weird mapping between IDENTY-H/V and java supported encoding, IDENTITY-H/V is
// used to recognize TrueType fonts, but the real JavaEncoding is used to encode Type1 fonts
final String stringEncoding;
if ("utf-8".equalsIgnoreCase(encoding))
{
stringEncoding = "utf-8";
encoding = BaseFont.IDENTITY_H;
}
else if ("utf-16".equalsIgnoreCase(encoding))
{
stringEncoding = "utf-16";
encoding = BaseFont.IDENTITY_H;
}
else
{
// Correct the encoding for truetype fonts
// iText will crash if IDENTITY_H is used to create a base font ...
if (encoding.equalsIgnoreCase(BaseFont.IDENTITY_H) ||
encoding.equalsIgnoreCase(BaseFont.IDENTITY_V))
{
//changed to UTF to support all unicode characters ..
stringEncoding = "utf-8";
}
else
{
stringEncoding = encoding;
}
}
try
{
final FontFamily registryFontFamily = registry.getFontFamily(fontKey);
FontRecord registryFontRecord = null;
if (registryFontFamily != null)
{
registryFontRecord = registryFontFamily.getFontRecord(bold, italic);
if (registryFontRecord instanceof CompoundFontRecord)
{
final CompoundFontRecord cfr = (CompoundFontRecord) registryFontRecord;
registryFontRecord = cfr.getBase();
}
}
if (registryFontRecord != null)
{
// Check, whether this is an built-in font. If not, then the record points to a file.
if ((registryFontRecord instanceof ITextBuiltInFontRecord) == false)
{
boolean embeddedOverride = embedded;
if (embedded == true && registryFontRecord instanceof FontSource)
{
final FontSource source = (FontSource) registryFontRecord;
if (source.isEmbeddable() == false)
{
logger.warn("License of font forbids embedded usage for font: " + fontKey);
// strict mode here?
embeddedOverride = false;
}
}
final BaseFontRecord fontRecord = createFontFromTTF
(registryFontRecord, bold, italic, encoding, stringEncoding, embeddedOverride);
if (fontRecord != null)
{
return fontRecord;
}
}
else
{
final ITextBuiltInFontRecord buildInFontRecord = (ITextBuiltInFontRecord) registryFontRecord;
// So this is one of the built-in records.
final String fontName = buildInFontRecord.getFullName();
// Alternative: No Registered TrueType font was found. OK; don't panic,
// we try to create a font anyway..
BaseFontRecord fontRecord = getFromCache(fontName, encoding, embedded);
if (fontRecord != null)
{
return fontRecord;
}
fontRecord = getFromCache(fontName, stringEncoding, embedded);
if (fontRecord != null)
{
return fontRecord;
}
// filename is null, so no ttf file registered for the fontname, maybe this is
// one of the internal fonts ...
final BaseFont f = BaseFont.createFont(fontName, stringEncoding, embedded,
useGlobalCache, null, null);
if (f != null)
{
fontRecord = new BaseFontRecord(fontName, false, embedded, f, bold, italic);
putToCache(fontRecord);
return fontRecord;
}
}
}
// If we got to this point, then the font was not recognized as any known font. We will fall back
// to Helvetica instead ..
}
catch (Exception e)
{
if (logger.isDebugEnabled())
{
logger.debug("BaseFont.createFont failed. Key = " + fontKey + ": " + e.getMessage(), e);
}
else if (logger.isWarnEnabled())
{
logger.warn("BaseFont.createFont failed. Key = " + fontKey + ": " + e.getMessage());
}
}
// fallback .. use BaseFont.HELVETICA as default
try
{
// check, whether HELVETICA is already created - yes, then return cached instance instead
BaseFontRecord fontRecord = getFromCache(BaseFont.HELVETICA, stringEncoding, embedded);
if (fontRecord != null)
{
// map all font references of the invalid font to the default font..
// this might be not very nice, but at least the report can go on..
putToCache(new BaseFontRecordKey(fontKey, encoding, embedded), fontRecord);
return fontRecord;
}
// no helvetica created, so do this now ...
final BaseFont f = BaseFont.createFont(BaseFont.HELVETICA, stringEncoding, embedded,
useGlobalCache, null, null);
if (f != null)
{
fontRecord = new BaseFontRecord
(BaseFont.HELVETICA, false, embedded, f, bold, italic);
putToCache(fontRecord);
putToCache(new BaseFontRecordKey(fontKey, encoding, embedded), fontRecord);
return fontRecord;
}
}
catch (Exception e)
{
logger.warn("BaseFont.createFont for FALLBACK failed.", e);
throw new BaseFontCreateException("Null font = " + fontKey);
}
throw new BaseFontCreateException("BaseFont creation failed, null font: " + fontKey);
}
//
// /**
// *
// * @param fileName
// * @param fontName iTexts idea of mixing font meta data with filenames
// * @param encoding
// * @param embedded
// * @return
// */
// private BaseFont loadFromLibLoader (final String fileName,
// final String fontName,
// final String encoding,
// final boolean embedded)
// {
// final HashMap map = new HashMap();
// map.put(BaseFontResourceFactory.FONTNAME, fontName);
// map.put(BaseFontResourceFactory.ENCODING, encoding);
// map.put(BaseFontResourceFactory.EMBEDDED, new Boolean(embedded));
// map.put(ResourceKey.CONTENT_KEY, new File (fileName));
//
// try
// {
// final Resource res =
// getResourceManager().createDirectly(map, BaseFont.class);
// return (BaseFont) res.getResource();
// }
// catch (ResourceException e)
// {
// return null;
// }
// }
/**
* Creates a PDF font record from a true type font.
*
* @param encoding the encoding.
* @param stringEncoding the string encoding.
* @param embedded a flag indicating whether to embed the font glyphs in the generated documents.
* @return the PDF font record.
* @throws com.lowagie.text.DocumentException
* if the BaseFont could not be created.
*/
private BaseFontRecord createFontFromTTF(final FontRecord fontRecord,
final boolean bold,
final boolean italic,
final String encoding,
final String stringEncoding,
final boolean embedded)
throws DocumentException
{
// check if this font is in the cache ...
//Log.warn ("TrueTypeFontKey : " + fontKey + " Font: " + font.isItalic() + " Encoding: "
// + encoding);
final String rawFilename;
if (fontRecord instanceof TrueTypeFontRecord)
{
final TrueTypeFontRecord ttfRecord = (TrueTypeFontRecord) fontRecord;
if (ttfRecord.getCollectionIndex() >= 0)
{
rawFilename = ttfRecord.getFontSource() + ',' + ttfRecord.getCollectionIndex();
}
else
{
rawFilename = ttfRecord.getFontSource();
}
}
else if (fontRecord instanceof FontSource)
{
final FontSource source = (FontSource) fontRecord;
rawFilename = source.getFontSource();
}
else
{
return null;
}
final String filename;
// check, whether the the physical font does not provide some of the
// required styles. We have to synthesize them, if neccessary
if ((fontRecord.isBold() == false && bold) &&
(fontRecord.isItalic() == false && italic))
{
filename = rawFilename + ",BoldItalic";
}
else if (fontRecord.isBold() == false && bold)
{
filename = rawFilename + ",Bold";
}
else if (fontRecord.isItalic() == false && italic)
{
filename = rawFilename + ",Italic";
}
else
{
filename = rawFilename;
}
final BaseFontRecord fontRec = getFromCache(filename, encoding, embedded);
if (fontRec != null)
{
return fontRec;
}
BaseFont f;
try
{
try
{
f = BaseFont.createFont(filename, encoding, embedded, false, null, null);
}
catch (DocumentException e)
{
f = BaseFont.createFont(filename, stringEncoding, embedded, false, null, null);
}
}
catch (IOException ioe)
{
throw new DocumentException("Failed to read the font: " + ioe);
}
// no, we have to create a new instance
final BaseFontRecord record = new BaseFontRecord
(filename, true, embedded, f, fontRecord.isBold(), fontRecord.isItalic());
putToCache(record);
return record;
}
/**
* Stores a record in the cache.
*
* @param record the record.
*/
private void putToCache(final BaseFontRecord record)
{
final BaseFontRecordKey key = record.createKey();
putToCache(key, record);
}
private void putToCache(final BaseFontRecordKey key, final BaseFontRecord record)
{
baseFonts.put(key, record);
}
/**
* Retrieves a record from the cache.
*
* @param fileName the physical filename name of the font file.
* @param encoding the encoding; never null.
* @return the PDF font record or null, if not found.
*/
private BaseFontRecord getFromCache(final String fileName,
final String encoding,
final boolean embedded)
{
final Object key = new BaseFontRecordKey(fileName, encoding, embedded);
final BaseFontRecord r = (BaseFontRecord) baseFonts.get(key);
if (r != null)
{
return r;
}
return null;
}
/**
* Returns a BaseFont which can be used to represent the given AWT Font
*
* @param font the font to be converted
* @return a BaseFont which has similar properties to the provided Font
*/
public BaseFont awtToPdf(final Font font)
{
// this has to be defined in the element, an has to set as a default...
final boolean embed = isEmbedFonts();
final String encoding = getDefaultEncoding();
try
{
return createBaseFont(font.getName(), font.isBold(), font.isItalic(), encoding, embed);
}
catch (Exception e)
{
// unable to handle font creation exceptions properly, all we can
// do is throw a runtime exception and hope the best ..
throw new BaseFontCreateException("Unable to create font: " + font, e);
}
}
/**
* Returns an AWT Font which can be used to represent the given BaseFont
*
* @param font the font to be converted
* @param size the desired point size of the resulting font
* @return a Font which has similar properties to the provided BaseFont
*/
public Font pdfToAwt(final BaseFont font, final int size)
{
final String logicalName = getFontName(font);
boolean bold = false;
boolean italic = false;
if (StringUtils.endsWithIgnoreCase(logicalName, "bolditalic"))
{
bold = true;
italic = true;
}
else if (StringUtils.endsWithIgnoreCase(logicalName, "bold"))
{
bold = true;
}
else if (StringUtils.endsWithIgnoreCase(logicalName, "italic"))
{
italic = true;
}
int style = Font.PLAIN;
if (bold)
{
style |= Font.BOLD;
}
if (italic)
{
style |= Font.ITALIC;
}
return new Font(logicalName, style, size);
}
private String getFontName(final BaseFont font)
{
final String[][] names = font.getFullFontName();
final int nameCount = names.length;
if (nameCount == 1)
{
return names[0][3];
}
String nameExtr = null;
for (int k = 0; k < nameCount; ++k)
{
final String[] name = names[k];
// Macintosh language english
if ("1".equals(name[0]) && "0".equals(name[1]))
{
nameExtr = name[3];
}
// Microsoft language code for US-English ...
else if ("1033".equals(name[2]))
{
nameExtr = name[3];
break;
}
}
if (nameExtr != null)
{
return nameExtr;
}
return names[0][3];
}
}