/* * 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.itext; import com.lowagie.text.DocumentException; import com.lowagie.text.pdf.BaseFont; import com.lowagie.text.pdf.FontMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.libraries.base.config.ExtendedConfiguration; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.fonts.FontMappingUtility; import org.pentaho.reporting.libraries.fonts.LibFontBoot; import org.pentaho.reporting.libraries.fonts.merge.CompoundFontRecord; import org.pentaho.reporting.libraries.fonts.registry.FontFamily; import org.pentaho.reporting.libraries.fonts.registry.FontRecord; import org.pentaho.reporting.libraries.fonts.registry.FontSource; import org.pentaho.reporting.libraries.fonts.truetype.TrueTypeFontRecord; import java.awt.*; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * iText font support. * * @author Thomas Morgner */ public class BaseFontSupport implements FontMapper { private static final 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(), e ); } } // 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 ]; } }