org.apache.pdfbox.pdmodel.font.PDTrueTypeFont Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pdfbox Show documentation
Show all versions of pdfbox Show documentation
The Apache PDFBox library is an open source Java tool for working with PDF documents.
/*
* 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.
*/
package org.apache.pdfbox.pdmodel.font;
import java.awt.Font;
import java.awt.FontFormatException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fontbox.ttf.CMAPEncodingEntry;
import org.apache.fontbox.ttf.CMAPTable;
import org.apache.fontbox.ttf.GlyphData;
import org.apache.fontbox.ttf.GlyphTable;
import org.apache.fontbox.ttf.HeaderTable;
import org.apache.fontbox.ttf.HorizontalHeaderTable;
import org.apache.fontbox.ttf.HorizontalMetricsTable;
import org.apache.fontbox.ttf.NameRecord;
import org.apache.fontbox.ttf.NamingTable;
import org.apache.fontbox.ttf.OS2WindowsMetricsTable;
import org.apache.fontbox.ttf.PostScriptTable;
import org.apache.fontbox.ttf.TTFParser;
import org.apache.fontbox.ttf.TTFSubFont;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.encoding.Encoding;
import org.apache.pdfbox.encoding.MacOSRomanEncoding;
import org.apache.pdfbox.encoding.WinAnsiEncoding;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.util.ResourceLoader;
/**
* This is the TrueType implementation of fonts.
*
* @author Ben Litchfield
*
*/
public class PDTrueTypeFont extends PDSimpleFont
{
/**
* Log instance.
*/
private static final Log log = LogFactory.getLog(PDTrueTypeFont.class);
/**
* Start of coderanges.
*/
private static final int START_RANGE_F000 = 0xF000;
private static final int START_RANGE_F100 = 0xF100;
private static final int START_RANGE_F200 = 0xF200;
private CMAPEncodingEntry cmapWinUnicode = null;
private CMAPEncodingEntry cmapWinSymbol = null;
private CMAPEncodingEntry cmapMacintoshSymbol = null;
private boolean cmapInitialized = false;
private TrueTypeFont trueTypeFont = null;
private HashMap advanceWidths = new HashMap ();
/**
* This is the key to a property in the PDFBox_External_Fonts.properties
* file to load a Font when a mapping does not exist for the current font.
*/
public static final String UNKNOWN_FONT = "UNKNOWN_FONT";
private Font awtFont = null;
private static Properties externalFonts = new Properties();
private static Map loadedExternalFonts = new HashMap();
static
{
try
{
ResourceLoader.loadProperties(
"org/apache/pdfbox/resources/PDFBox_External_Fonts.properties",
externalFonts );
}
catch( IOException io )
{
throw new RuntimeException( "Error loading font resources", io );
}
}
/**
* Constructor.
*/
public PDTrueTypeFont()
{
super();
font.setItem( COSName.SUBTYPE, COSName.TRUE_TYPE );
}
/**
* Constructor.
*
* @param fontDictionary The font dictionary according to the PDF specification.
*
* @throws IOException exception if something went wrong when loading the font.
*/
public PDTrueTypeFont( COSDictionary fontDictionary ) throws IOException
{
super( fontDictionary );
ensureFontDescriptor();
}
/**
* This will load a TTF font from a font file.
*
* @param doc The PDF document that will hold the embedded font.
* @param file The file on the filesystem that holds the font file.
* @return A true type font.
* @throws IOException If there is an error loading the file data.
*/
public static PDTrueTypeFont loadTTF( PDDocument doc, String file ) throws IOException
{
return loadTTF( doc, new File( file ) );
}
/**
* This will load a TTF to be embedded into a document.
*
* @param doc The PDF document that will hold the embedded font.
* @param file a ttf file.
* @return a PDTrueTypeFont instance.
* @throws IOException If there is an error loading the data.
*/
public static PDTrueTypeFont loadTTF( PDDocument doc, File file ) throws IOException
{
return loadTTF( doc, new FileInputStream( file ) );
}
/**
* This will load a TTF to be embedded into a document.
*
* @param doc The PDF document that will hold the embedded font.
* @param stream a ttf input stream.
* @return a PDTrueTypeFont instance.
* @throws IOException If there is an error loading the data.
*/
public static PDTrueTypeFont loadTTF( PDDocument doc, InputStream stream ) throws IOException
{
return PDTrueTypeFont.loadTTF(doc,stream,new WinAnsiEncoding());
}
/**
* This will load a TTF to be embedded into a document.
*
* @param doc The PDF document that will hold the embedded font.
* @param stream a ttf input stream.
* @param enc The font encoding.
* @return a PDTrueTypeFont instance.
* @throws IOException If there is an error loading the data.
*/
public static PDTrueTypeFont loadTTF( PDDocument doc, InputStream stream, Encoding enc ) throws IOException
{
PDStream fontStream = new PDStream(doc, stream, false );
fontStream.getStream().setInt( COSName.LENGTH1, fontStream.getByteArray().length );
fontStream.addCompression();
//only support winansi encoding right now, should really
//just use Identity-H with unicode mapping
return PDTrueTypeFont.loadTTF(fontStream,enc);
}
/**
* This will load a TTF to be embedded into a document.
*
* @param fontStream a ttf input stream.
* @param enc The font encoding.
* @return a PDTrueTypeFont instance.
* @throws IOException If there is an error loading the data.
*/
public static PDTrueTypeFont loadTTF( PDStream fontStream, Encoding enc ) throws IOException
{
PDTrueTypeFont retval = new PDTrueTypeFont();
retval.setFontEncoding( enc );
retval.setEncoding(enc.getCOSObject());
PDFontDescriptorDictionary fd = new PDFontDescriptorDictionary();
retval.setFontDescriptor( fd );
fd.setFontFile2( fontStream );
// As the stream was close within the PDStream constructor, we have to recreate it
InputStream stream = fontStream.createInputStream();
try
{
retval.loadDescriptorDictionary(fd, stream);
}
finally
{
stream.close();
}
return retval;
}
private void ensureFontDescriptor() throws IOException
{
if( getFontDescriptor() == null )
{
PDFontDescriptorDictionary fdd = new PDFontDescriptorDictionary();
setFontDescriptor(fdd);
InputStream ttfData = getExternalTTFData();
if( ttfData != null )
{
try
{
loadDescriptorDictionary(fdd, ttfData);
}
finally
{
ttfData.close();
}
}
}
}
private void loadDescriptorDictionary(PDFontDescriptorDictionary fd, InputStream ttfData) throws IOException
{
TrueTypeFont ttf = null;
try
{
TTFParser parser = new TTFParser();
ttf = parser.parseTTF( ttfData );
NamingTable naming = ttf.getNaming();
List records = naming.getNameRecords();
for( int i=0; i 0 );
fd.setItalicAngle( ps.getItalicAngle() );
String[] names = ps.getGlyphNames();
if( names != null )
{
for( int i=0; i codeToName = this.getFontEncoding().getCodeToNameMap();
int firstChar = Collections.min(codeToName.keySet());
int lastChar = Collections.max(codeToName.keySet());
HorizontalMetricsTable hMet = ttf.getHorizontalMetrics();
int[] widthValues = hMet.getAdvanceWidth();
// some monospaced fonts provide only one value for the width
// instead of an array containing the same value for every glyphid
boolean isMonospaced = fd.isFixedPitch() || widthValues.length == 1;
int nWidths=lastChar-firstChar+1;
List widths = new ArrayList(nWidths);
// use the first width as default
// proportional fonts -> width of the .notdef character
// monospaced-fonts -> the first width
int defaultWidth = Math.round(widthValues[0] * scaling);
for (int i = 0; i < nWidths; i++)
{
widths.add((float)defaultWidth);
}
// Encoding singleton to have acces to the chglyph name to
// unicode cpoint point mapping of Adobe's glyphlist.txt
Encoding glyphlist = WinAnsiEncoding.INSTANCE;
// A character code is mapped to a glyph name via the provided
// font encoding. Afterwards, the glyph name is translated to a
// glyph ID.
// For details, see PDFReference16.pdf, Section 5.5.5, p.401
//
for (Entry e : codeToName.entrySet())
{
String name = e.getValue();
// pdf code to unicode by glyph list.
String c = glyphlist.getCharacter(name);
int charCode = c.codePointAt(0);
int gid = uniMap.getGlyphId(charCode);
if (gid != 0)
{
if (isMonospaced)
{
widths.set(e.getKey().intValue() - firstChar, (float)defaultWidth);
}
else
{
widths.set(e.getKey().intValue() - firstChar, (float)Math.round(widthValues[gid] * scaling));
}
}
}
setWidths( widths );
setFirstChar( firstChar );
setLastChar( lastChar );
}
finally
{
if( ttf != null )
{
ttf.close();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public Font getawtFont() throws IOException
{
PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary)getFontDescriptor();
if( awtFont == null )
{
PDStream ff2Stream = fd.getFontFile2();
if( ff2Stream != null )
{
InputStream is = null;
try
{
// create a font with the embedded data
is = ff2Stream.createInputStream();
awtFont = Font.createFont(Font.TRUETYPE_FONT, is);
}
catch( FontFormatException f )
{
try
{
// as a workaround we try to rebuild the embedded subsfont
byte[] fontData = rebuildTTF(fd, ff2Stream.createInputStream());
if (fontData != null)
{
ByteArrayInputStream bais = new ByteArrayInputStream(fontData);
awtFont = Font.createFont( Font.TRUETYPE_FONT,bais);
}
}
catch (FontFormatException e)
{
log.info("Can't read the embedded font " + fd.getFontName() );
}
}
finally
{
IOUtils.closeQuietly(is);
}
if (awtFont == null)
{
if (fd.getFontName() != null)
{
awtFont = FontManager.getAwtFont(fd.getFontName());
}
if (awtFont != null)
{
log.info("Using font "+awtFont.getName()+ " instead");
}
setIsFontSubstituted(true);
}
}
else
{
// check if the font is part of our environment
if (fd.getFontName() != null)
{
awtFont = FontManager.getAwtFont(fd.getFontName());
}
if (awtFont == null)
{
log.info("Can't find the specified font " + fd.getFontName() );
// check if there is a font mapping for an external font file
TrueTypeFont ttf = getExternalFontFile2( fd );
if( ttf != null )
{
try
{
awtFont = Font.createFont( Font.TRUETYPE_FONT, ttf.getOriginalData() );
}
catch( FontFormatException f )
{
log.info("Can't read the external fontfile " + fd.getFontName() );
}
}
}
}
if (awtFont == null)
{
// we can't find anything, so we have to use the standard font
awtFont = FontManager.getStandardFont();
log.info("Using font "+awtFont.getName()+ " instead");
setIsFontSubstituted(true);
}
}
return awtFont;
}
private byte[] rebuildTTF(PDFontDescriptorDictionary fd, InputStream inputStream) throws IOException
{
// this is one possible case of an incomplete subfont which leads to a font exception
if (getFontEncoding() instanceof WinAnsiEncoding)
{
TTFParser ttfParser = new TTFParser(true);
TrueTypeFont ttf = ttfParser.parseTTF(inputStream);
TTFSubFont ttfSub = new TTFSubFont(ttf, "PDFBox-Rebuild");
for (int i=getFirstChar();i<=getLastChar();i++)
{
ttfSub.addCharCode(i);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ttfSub.writeToStream(baos);
return baos.toByteArray();
}
return null;
}
private InputStream getExternalTTFData() throws IOException
{
String ttfResource = externalFonts.getProperty( UNKNOWN_FONT );
String baseFont = getBaseFont();
if( baseFont != null && externalFonts.containsKey(baseFont) )
{
ttfResource = externalFonts.getProperty( baseFont );
}
return (ttfResource != null ? ResourceLoader.loadResource(ttfResource) : null);
}
/**
* Permit to load an external TTF Font program file
*
* Created by Pascal Allain
* Vertical7 Inc.
*
* @param fd The font descriptor currently used
* @return A PDStream with the Font File program, null if fd is null
* @throws IOException If the font is not found
*/
private TrueTypeFont getExternalFontFile2(PDFontDescriptorDictionary fd)
throws IOException
{
TrueTypeFont retval = null;
if ( fd != null )
{
String baseFont = getBaseFont();
// search for a suitable font in the local environment
retval = org.apache.fontbox.util.FontManager.findTTFont(baseFont);
// can't find any font, use fall back mapping
// NOTE: this fall back mechanism will be removed with PDFBox 2.0.0
if (retval == null)
{
String fontResource = externalFonts.getProperty( UNKNOWN_FONT );
if( (baseFont != null) &&
(externalFonts.containsKey(baseFont)) )
{
fontResource = externalFonts.getProperty(baseFont);
}
if( fontResource != null )
{
retval = (TrueTypeFont)loadedExternalFonts.get( baseFont );
if( retval == null )
{
TTFParser ttfParser = new TTFParser();
InputStream fontStream = ResourceLoader.loadResource( fontResource );
if( fontStream == null )
{
throw new IOException( "Error missing font resource '" + externalFonts.get(baseFont) + "'" );
}
retval = ttfParser.parseTTF( fontStream );
loadedExternalFonts.put( baseFont, retval );
}
}
}
}
return retval;
}
/**
* Return the TTF font as TrueTypeFont.
*
* @return the TTF font
* @throws IOException If there is an error loading the data
*/
public TrueTypeFont getTTFFont() throws IOException
{
if (trueTypeFont == null)
{
PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary) getFontDescriptor();
if (fd != null)
{
PDStream ff2Stream = fd.getFontFile2();
if (ff2Stream != null)
{
TTFParser ttfParser = new TTFParser(true);
trueTypeFont = ttfParser.parseTTF(ff2Stream.createInputStream());
}
}
if (trueTypeFont == null)
{
// check if there is a font mapping for an external font file
trueTypeFont = org.apache.fontbox.util.FontManager.findTTFont(getBaseFont());
}
}
return trueTypeFont;
}
@Override
public void clear()
{
super.clear();
cmapWinUnicode = null;
cmapWinSymbol = null;
cmapMacintoshSymbol = null;
trueTypeFont = null;
if (advanceWidths != null)
{
advanceWidths.clear();
}
}
@Override
public float getFontWidth(int charCode)
{
float width = super.getFontWidth(charCode);
if (width <= 0)
{
if (advanceWidths.containsKey(charCode))
{
width = advanceWidths.get(charCode);
}
else
{
TrueTypeFont ttf = null;
try
{
ttf = getTTFFont();
if (ttf != null)
{
int code = getGlyphcode(charCode);
width = ttf.getAdvanceWidth(code);
int unitsPerEM = ttf.getUnitsPerEm();
// do we have to scale the width
if (unitsPerEM != 1000)
{
width *= 1000f/unitsPerEM;
}
}
}
catch (IOException exception)
{
width = 250;
}
advanceWidths.put(charCode, width);
}
}
return width;
}
private int getGlyphcode(int code)
{
extractCMaps();
int result = 0;
if (getFontEncoding() != null && !isSymbolicFont())
{
try
{
String charactername = getFontEncoding().getName(code);
if (charactername != null)
{
if (cmapWinUnicode != null)
{
String unicode = Encoding.getCharacterForName(charactername);
if (unicode != null)
{
result = unicode.codePointAt(0);
}
result = cmapWinUnicode.getGlyphId(result);
}
else if (cmapMacintoshSymbol != null && MacOSRomanEncoding.INSTANCE.hasCodeForName(charactername))
{
result = MacOSRomanEncoding.INSTANCE.getCode(charactername);
result = cmapMacintoshSymbol.getGlyphId(result);
}
else if (cmapWinSymbol != null)
{
// fallback scenario if the glyph can't be found yet
// maybe the 3,0 cmap provides a suitable mapping
// see PDFBOX-2091
result = cmapWinSymbol.getGlyphId(code);
}
}
}
catch (IOException exception)
{
log.error("Caught an exception getGlyhcode: " + exception);
}
}
else if (getFontEncoding() == null || isSymbolicFont())
{
if (cmapWinSymbol != null)
{
result = cmapWinSymbol.getGlyphId(code);
if (code >= 0 && code <= 0xFF)
{
// the CMap may use one of the following code ranges,
// so that we have to add the high byte to get the
// mapped value
if (result == 0)
{
// F000 - F0FF
result = cmapWinSymbol.getGlyphId(code + START_RANGE_F000);
}
if (result == 0)
{
// F100 - F1FF
result = cmapWinSymbol.getGlyphId(code + START_RANGE_F100);
}
if (result == 0)
{
// F200 - F2FF
result = cmapWinSymbol.getGlyphId(code + START_RANGE_F200);
}
}
}
else if (cmapMacintoshSymbol != null)
{
result = cmapMacintoshSymbol.getGlyphId(code);
}
}
return result;
}
/**
* extract all useful CMaps.
*/
private void extractCMaps()
{
if (!cmapInitialized)
{
try
{
getTTFFont();
}
catch(IOException exception)
{
log.error("Can't read the true type font", exception);
}
CMAPTable cmapTable = trueTypeFont.getCMAP();
if (cmapTable != null)
{
// get all relevant CMaps
CMAPEncodingEntry[] cmaps = cmapTable.getCmaps();
for (int i = 0; i < cmaps.length; i++)
{
if (cmaps[i] == null)
{
continue;
}
if (CMAPTable.PLATFORM_WINDOWS == cmaps[i].getPlatformId())
{
if (CMAPTable.ENCODING_UNICODE == cmaps[i].getPlatformEncodingId())
{
cmapWinUnicode = cmaps[i];
}
else if (CMAPTable.ENCODING_SYMBOL == cmaps[i].getPlatformEncodingId())
{
cmapWinSymbol = cmaps[i];
}
}
else if (CMAPTable.PLATFORM_MACINTOSH == cmaps[i].getPlatformId())
{
if (CMAPTable.ENCODING_SYMBOL == cmaps[i].getPlatformEncodingId())
{
cmapMacintoshSymbol = cmaps[i];
}
}
}
}
cmapInitialized = true;
}
}
}