org.jpedal.fonts.FontMappings Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of OpenViewerFX Show documentation
Show all versions of OpenViewerFX Show documentation
An Open Source JavaFX PDF Viewer
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2015 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
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
*
* ---------------
* FontMappings.java
* ---------------
*/
package org.jpedal.fonts;
import org.jpedal.PdfDecoderInt;
import org.jpedal.exception.PdfFontException;
import org.jpedal.parser.DecoderOptions;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.Strip;
import java.awt.*;
import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Holds Maps which are used to map font names onto actual fonts and files
*/
public class FontMappings {
/**ensure fonts setup only once*/
public static boolean fontsInitialised;
/**
* font to use in preference to Lucida
*/
public static String defaultFont;
/**
* flag to show if there must be a mapping value (program exits if none
* found)
*/
public static boolean enforceFontSubstitution;
/**
* used to remap fonts onto truetype fonts (set internally)
*/
public static Map fontSubstitutionTable;
/**
* hold details of all fonts
*/
public static Map fontPropertiesTable;
/**
* used to ensure substituted fonts unique
*/
public static Map fontPossDuplicates;
/**
* used to store number for subfonts in TTC
*/
public static Map fontSubstitutionFontID;
/**
* used to remap fonts onto truetype fonts (set internally)
*/
public static Map fontSubstitutionLocation = new ConcurrentHashMap();
/**
* used to remap fonts onto truetype fonts (set internally)
*/
public static Map fontSubstitutionAliasTable = new ConcurrentHashMap();
/**only upload all fonts once*/
private static boolean fontsSet;
private static final String separator = System.getProperty("file.separator");
/**put fonts in variable so can be altered if needed by Client*/
public static String[] defaultFontDirs= {"C:/windows/fonts/","C:/winNT/fonts/",
"/Library/Fonts/",
//"/System/Library/Fonts/", ms 20111013 commented out as breaks forms with Zapf
"/usr/share/fonts/truetype/msttcorefonts/",
//"/usr/share/fonts/truetype/",
//"/windows/D/Windows/Fonts/"
"usr/local/Fonts/",
};
/**
* determine how font substitution is done
*/
private static int fontSubstitutionMode = PdfDecoderInt.SUBSTITUTE_FONT_USING_FILE_NAME;
//private static int fontSubstitutionMode=PdfDecoderInt.SUBSTITUTE_FONT_USING_POSTSCRIPT_NAME;
//private static int fontSubstitutionMode=PdfDecoderInt.SUBSTITUTE_FONT_USING_FULL_FONT_NAME;
//private static int fontSubstitutionMode=PdfDecoderInt.SUBSTITUTE_FONT_USING_FAMILY_NAME;
//private static int fontSubstitutionMode=PdfDecoderInt.SUBSTITUTE_FONT_USING_POSTSCRIPT_NAME_USE_FAMILY_NAME_IF_DUPLICATES;
private FontMappings(){}
/**
* used internally to pick uo org.jpedal.fontmaps property and set
*/
public static void initFonts(){
// pick up D options and use settings
try {
final String fontMaps = System.getProperty("org.jpedal.fontmaps");
if (fontMaps != null) {
final StringTokenizer fontPaths = new StringTokenizer(fontMaps, ",");
while (fontPaths.hasMoreTokens()) {
final String fontPath = fontPaths.nextToken();
final StringTokenizer values = new StringTokenizer(fontPath, "=:");
final int count = values.countTokens() - 1;
final String[] nameInPDF = new String[count];
final String key = values.nextToken();
for (int i = 0; i < count; i++) {
nameInPDF[i] = values.nextToken();
}
setSubstitutedFontAliases(key, nameInPDF); //$NON-NLS-1$
}
}
} catch (final Exception e) {
LogWriter.writeLog("Unable to read org.jpedal.fontmaps " + e.getMessage());
}
// pick up D options and use settings
try {
final String fontDirs = System.getProperty("org.jpedal.fontdirs");
String failed = null;
if (fontDirs != null) {
failed = FontMappings.addFonts(fontDirs, failed);
}
if (failed != null) {
LogWriter.writeLog("Could not find " + failed);
}
} catch (final Exception e) {
LogWriter.writeLog("Unable to read FontDirs " + e.getMessage());
}
}
/**
* set mode to use when substituting fonts (default is to use Filename (ie arial.ttf)
* Options are SUBSTITUTE_* values from PdfDecoder
*/
public static void setFontSubstitutionMode(final int mode) {
fontSubstitutionMode = mode;
}
/**
* set mode to use when substituting fonts (default is to use Filename (ie arial.ttf)
* Options are SUBSTITUTE_* values from PdfDecoder
*/
public static int getFontSubstitutionMode() {
return fontSubstitutionMode;
}
/**
* allows a number of fonts to be mapped onto an actual font and provides a
* way around slightly differing font naming when substituting fonts - So if
* arialMT existed on the target machine and the PDF contained arial and
* helvetica (which you wished to replace with arialmt), you would use the
* following code -
*
* String[] aliases={"arial","helvetica"};
* currentPdfDecoder.setSubstitutedFontAliases("arialmt",aliases); -
*
* comparison is case-insensitive and file type/ending should not be
* included - For use in conjunction with -Dorg.jpedal.fontdirs options which allows
* user to pass a set of comma separated directories with Truetype fonts
* (directories do not need to exist so can be multi-platform setting)
*/
public static void setSubstitutedFontAliases(final String fontFileName, final String[] aliases) {
if (aliases != null) {
final String name = fontFileName.toLowerCase();
String alias;
for (final String aliase : aliases) {
alias = aliase.toLowerCase();
if (!alias.equals(name)) {
fontSubstitutionAliasTable.put(alias, name);
}
}
}
}
/**
* takes a comma separated list of font directories and add to substitution
*/
public static String addFonts(final String fontDirs, final String failed) {
final StringTokenizer fontPaths = new StringTokenizer(fontDirs, ",");
while (fontPaths.hasMoreTokens()) {
String fontPath = fontPaths.nextToken();
if (!fontPath.endsWith("/") && !fontPath.endsWith("\\")) {
fontPath += separator;
}
//LogWriter.writeLog("Looking in " + fontPath + " for TT fonts");
addTTDir(fontPath, failed);
}
return failed;
}
public static void dispose(){
fontSubstitutionTable=null;
fontPropertiesTable=null;
fontPossDuplicates=null;
fontSubstitutionFontID = null;
fontSubstitutionLocation = null;
fontSubstitutionAliasTable = null;
}
/**
* add a truetype font directory and contents to substitution
*/
public static String addTTDir(final String fontPath, String failed) {
if ( fontSubstitutionTable == null) {
fontSubstitutionTable = new ConcurrentHashMap();
fontSubstitutionFontID = new ConcurrentHashMap();
fontPossDuplicates = new ConcurrentHashMap();
fontPropertiesTable = new ConcurrentHashMap();
}
final File currentDir = new File(fontPath);
if ((currentDir.exists()) && (currentDir.isDirectory())) {
final String[] files = currentDir.list();
if (files != null) {
for (final String currentFont : files) {
addFontFile(currentFont, fontPath);
}
}
} else {
if (failed == null) {
failed = fontPath;
} else {
failed = failed + ',' + fontPath;
}
}
return failed;
}
/**
* add a list of settings to map common fonts which can be substituted onto correct platform settings for Windows/MAC/Linux so JPedal
* will try to use the fonts on the computer if possible to produce most accurate display.
*/
public static void setFontReplacements() {
//this is where we setup specific font mapping to use fonts on local machines
//note different settigns for Win, linux, MAC
//general
final String[] aliases6={/**"AcArial"};//,/**/"acarialunicodems__cn"};//,"acarial,bold"};
setSubstitutedFontAliases("adobeheitistd-regular",aliases6);
//platform settings
if(DecoderOptions.isRunningOnMac){
//Courier (CourierNew) both on Mac and different
setSubstitutedFontAliases("Courier italic",new String[]{"Courier-Oblique"});
setSubstitutedFontAliases("Courier bold",new String[]{"Courier-Bold"});
setSubstitutedFontAliases("Courier bold italic",new String[]{"Courier-BoldOblique"});
setSubstitutedFontAliases("Courier new italic",new String[]{"CourierNew,italic","CourierStd-Oblique","CourierNewPS-ItalicMT"});
setSubstitutedFontAliases("Courier new bold",new String[]{"CourierNew,Bold","Courier-Bold","CourierStd-Bold","CourierNewPS-BoldMT"});
setSubstitutedFontAliases("Courier new bold italic",new String[]{"CourierNew-BoldOblique","CourierStd-BoldOblique","CourierNewPS-BoldItalicMT"});
setSubstitutedFontAliases("Courier new",new String[]{"CourierNew","Courier","CourierStd","CourierNewPSMT"});
//Helvetica (Arial)
setSubstitutedFontAliases("arial",new String[]{"Helvetica","arialmt"});
setSubstitutedFontAliases("arial italic",new String[]{"arial-italic", "arial-italicmt","Helvetica-Oblique","Arial,Italic"});
setSubstitutedFontAliases("arial bold",new String[]{"arial-boldmt,bold","arial-boldmt","Helvetica-Bold","Arial,bold"});
setSubstitutedFontAliases("arial bold italic",new String[]{"Arial-BoldItalicMT","Helvetica-BoldOblique"});
//Arial Narrow - not actually one of fonts but very common so added
setSubstitutedFontAliases("arial Narrow",new String[]{"ArialNarrow",}); //called ArialNarrow in PDF, needs to be arialn for Windows
setSubstitutedFontAliases("arial Narrow italic",new String[]{"ArialNarrow-italic"});
setSubstitutedFontAliases("arial Narrow bold",new String[]{"ArialNarrow-bold","ArialNarrow,Bold"});
setSubstitutedFontAliases("arial Narrow bold italic",new String[]{"ArialNarrow-bolditalic"});
//Times/TimesNewRoman
setSubstitutedFontAliases("times new roman bold",new String[] {"Times-Bold","TimesNewRoman,Bold","TimesNewRomanPS-BoldMT"});
setSubstitutedFontAliases("times new roman bold italic",new String[] {"Times-BoldItalic","TimesNewRoman,BoldItalic","TimesNewRomanPS-BoldItalicMT"});
setSubstitutedFontAliases("times new roman italic",new String[] {"Times-Italic","TimesNewRoman,Italic","TimesNewRomanPS-ItalicMT"});
setSubstitutedFontAliases("times new roman",new String[] {"Times-Roman","TimesNewRoman","Times","TimesNewRomanPSMT"});
setSubstitutedFontAliases("wingdings",new String[] {"ZapfDingbats","ZaDb"});
//default at present for others as well
}else {//if(PdfDecoder.isRunningOnWindows){
//Courier (CourierNew)
setSubstitutedFontAliases("Couri",new String[]{"Courier-Oblique", "CourierNew,italic","CourierStd-Oblique","CourierNewPS-ItalicMT"});
setSubstitutedFontAliases("Courbd",new String[]{"Courier-Bold","CourierNew,Bold","CourierStd-Bold","CourierNewPS-BoldMT"});
setSubstitutedFontAliases("Courbi",new String[]{"Courier-BoldOblique","CourierNew-BoldOblique","CourierStd-BoldOblique","CourierNewPS-BoldItalicMT"});
setSubstitutedFontAliases("Cour",new String[]{"CourierNew","Courier","CourierStd","CourierNewPSMT","CourierNewPSMT"});
//Helvetica (Arial)
setSubstitutedFontAliases("arial",new String[]{"Helvetica","arialmt","ArialNarrow"});
setSubstitutedFontAliases("ariali",new String[]{"arial-italic", "arial-italicmt","Helvetica-Oblique","Arial,Italic", "ArialNarrow-Italic"});
setSubstitutedFontAliases("arialbd",new String[]{"arial-boldmt,bold","arial-boldmt","Helvetica-Bold","Arial,bold","arial bold", "ArialNarrow-Bold"});
setSubstitutedFontAliases("arialbi",new String[]{"Arial-BoldItalicMT","Helvetica-BoldOblique", "ArialNarrow-BoldItalic"});
//Font doesn't work in generic Windows 8 (commented out by Mark) 14/11/2013
//Arial Narrow - not actually one of fonts but very common so added
//setSubstitutedFontAliases("arialn",new String[]{"ArialNarrow",}); //called ArialNarrow in PDF, needs to be arialn for Windows
//setSubstitutedFontAliases("arialni",new String[]{"ArialNarrow-italic"});
//setSubstitutedFontAliases("arialnb",new String[]{"ArialNarrow-bold","ArialNarrow,Bold"});
//setSubstitutedFontAliases("arialnbi",new String[]{"ArialNarrow-bolditalic"});
//Times/TimesNewRoman
setSubstitutedFontAliases("timesbd",new String[] {"Times-Bold","TimesNewRoman,Bold","TimesNewRomanPS-BoldMT"});
setSubstitutedFontAliases("timesi",new String[] {"Times-BoldItalic","TimesNewRoman,BoldItalic"});
setSubstitutedFontAliases("timesbi",new String[] {"Times-Italic","TimesNewRoman,Italic"});
setSubstitutedFontAliases("times",new String[] {"Times-Roman","TimesNewRoman","Times","TimesNewRomanPSMT"});
setSubstitutedFontAliases("wingdings",new String[] {"ZapfDingbats","ZaDb"});
}
setSubstitutedFontAliases("AdobeSongStd-Light",new String[] {"STSong-Light"});
// setSubstitutedFontAliases("AdobeSongStd-Light",new String[] {"MHei-Medium"});
// setSubstitutedFontAliases("AdobeSongStd-Light",new String[] {"MSung-Light"});
// setSubstitutedFontAliases("AdobeSongStd-Light",new String[] {"HeiseiKakuGo-W5"});
// setSubstitutedFontAliases("AdobeSongStd-Light",new String[] {"HeiseiMin-W3"});
// setSubstitutedFontAliases("AdobeSongStd-Light",new String[] {"HYGoThic-Medium"});
// setSubstitutedFontAliases("AdobeSongStd-Light",new String[] {"HYSMyeongJo-Medium"});
//set general mappings for non-embedded fonts (assumes names the same) - do first time used
if(!fontsSet){
fontsSet=true;
//now in public static variable so can be altered
setFontDirs(defaultFontDirs);
/**check for any windows fonts lurking in Adobe folders as well*/
if(DecoderOptions.isRunningOnWindows){
final File adobeFonts=new File("C:\\Program Files\\Adobe\\");
if(adobeFonts.exists()){
final String[] subdirs=adobeFonts.list();
for(final String path : subdirs){
final String adobePath="C:\\Program Files\\Adobe\\"+path+"\\Resource\\CIDFont";
final File testAdobe=new File(adobePath);
//add if it exists
if(testAdobe.exists()){
addTTDir(adobePath, "");
}
}
}
}
}
}
/**
* takes a String[] of font directories and adds to substitution - Can just
* be called for each JVM - Should be called before file opened - this
* offers an alternative to the call -DFontDirs - Passing a null value
* flushes all settings
*
* @return String which will be null or list of directories it could not
* find
*/
public static String setFontDirs(final String[] fontDirs) {
String failed = null;
if (FontMappings.fontSubstitutionTable == null) {
fontSubstitutionTable = new ConcurrentHashMap();
fontSubstitutionFontID = new ConcurrentHashMap();
fontPossDuplicates = new ConcurrentHashMap();
fontPropertiesTable = new ConcurrentHashMap();
}
try {
if (fontDirs == null) { // idiot safety test
LogWriter.writeLog("Null font parameter passed");
fontSubstitutionAliasTable.clear();
fontSubstitutionLocation.clear();
fontSubstitutionTable.clear();
fontSubstitutionFontID.clear();
fontPossDuplicates.clear();
fontPropertiesTable.clear();
fontsSet=false;
} else {
for (final String fontDir : fontDirs) {
String fontPath = fontDir;
// allow for 'wrong' separator
if (!fontPath.endsWith("/") && !fontPath.endsWith("\\")) {
fontPath += separator;
}
failed = addTTDir(fontPath, failed);
}
}
} catch (final Exception e) {
LogWriter.writeLog("Unable to run setFontDirs " + e.getMessage());
}
return failed;
}
/**
* This routine allows the user to add truetype,
* type1 or type1C fonts which will be used to disalay the fonts in PDF
* rendering and substitution as if the fonts were embedded in the PDF
* This is very useful for clients looking to keep down the size of PDFs
* transmitted and control display quality -
*
* Thanks to Peter for the idea/code -
*
* How to set it up -
*
* JPedal will look for the existence of the directory fontPath (ie
* com/myCompany/Fonts) -
*
* If this exists, Jpedal will look for 3 possible directories (tt,t1c,t1)
* and make a note of any fonts if these directories exist -
*
* When fonts are resolved, this option will be tested first and if a font
* if found, it will be used to display the font (the effect will be the
* same as if the font was embedded) -
*
* If the enforceMapping is true, JPedal assumes there must be a match and
* will throw a PdfFontException -
*
* Otherwise Jpedal will look in the java font path for a match or
* approximate with Lucida -
*
* The Format is defined as follows: -
*
* fontname = filename
*
* Type1/Type1C Font names exclude any prefix so /OEGPNB+FGHeavyItalic is
* resolved to FGHeavyItalic -
*
* Each font have the same name as the font it replaces (so Arial will
* require a font file such as Arial.ttf) and it must be unique (there
* cannot be an Arial font in each sub-directory) -
*
* So to use this functionality, place the fonts in a jar or add to the
* JPedal jar and call this method after instancing PdfDecoder - JPedal will
* do the rest
*
* @param fontPath -
* root directory for fonts
* @param enforceMapping -
* tell JPedal if all fonts should be in this directory
* @return flag (true if fonts added)
*/
public static boolean addSubstituteFonts(String fontPath, final boolean enforceMapping) {
boolean hasFonts = false;
InputStream in=null, dir=null;
try {
final String[] dirs = {"tt", "t1c", "t1"};
final String[] types = {"/TrueType", "/Type1C", "/Type1"};
// check fontpath ends with separator - we may need to check this.
// if((!fontPath.endsWith("/"))&(!fontPath.endsWith("\\")))
// fontPath=fontPath=fontPath+separator;
enforceFontSubstitution = enforceMapping;
final ClassLoader loader = FontMappings.class.getClass().getClassLoader();
// see if root dir exists
dir = loader.getResourceAsStream(fontPath);
LogWriter.writeLog("Looking for root " + fontPath);
// if it does, look for sub-directories
if (in != null) {
LogWriter.writeLog("Adding fonts fonts found in tt,t1c,t1 sub-directories of "+ fontPath);
hasFonts = true;
for (int i = 0; i < dirs.length; i++) {
if (!fontPath.endsWith("/")) {
fontPath += '/';
}
final String path = fontPath + dirs[i] + '/';
// see if it exists
in = loader.getResourceAsStream(path);
// if it does read its contents and store
if (in != null) {
System.out.println("Found " + path + ' ' + in);
final ArrayList fonts;
try {
// works with IDE or jar
if (in instanceof ByteArrayInputStream) {
fonts = readIndirectValues(in);
} else {
fonts = getDirectoryMatches(path);
}
String value, fontName;
// now assign the fonts
for (final Object font : fonts) {
value = (String) font;
if (value == null) {
break;
}
final int pointer = value.indexOf('.');
if (pointer == -1) {
fontName = value.toLowerCase();
} else {
fontName = value.substring(0, pointer).toLowerCase();
}
fontSubstitutionTable.put(fontName, types[i]);
fontSubstitutionLocation.put(fontName, path + value);
}
} catch (final Exception e) {
LogWriter.writeLog("Exception " + e+ " reading substitute fonts");
}finally {
if(in!=null){
try {
in.close();
} catch (final IOException e) {
LogWriter.writeLog("Exception: "+e.getMessage());
}
}
}
}
}
} else {
LogWriter.writeLog("No fonts found at " + fontPath);
}
} catch (final Exception e) {
LogWriter.writeLog("Exception adding substitute fonts "+ e.getMessage());
}finally { //close streams if open
if(in!=null){
try {
in.close();
} catch (final IOException e) {
LogWriter.writeLog("Exception: "+e.getMessage());
}
}
if(dir!=null){
try {
dir.close();
} catch (final IOException e) {
LogWriter.writeLog("Exception: "+e.getMessage());
}
}
}
return hasFonts;
}
/**
* method to add a single file to the PDF renderer
*
* @param currentFont - actual font name we use to identify
* @param fontPath - full path to font file used for this font
*/
public static void addFontFile(final String currentFont, String fontPath) {
if ( fontSubstitutionTable == null) {
fontSubstitutionTable = new ConcurrentHashMap();
fontSubstitutionFontID = new ConcurrentHashMap();
fontPossDuplicates = new ConcurrentHashMap();
fontPropertiesTable = new ConcurrentHashMap();
}
//add separator if needed
if (fontPath != null && !fontPath.endsWith("/") && !fontPath.endsWith("\\")) {
fontPath += separator;
}
final String name = currentFont.toLowerCase();
//decide font type
final int type = StandardFonts.getFontType(name);
InputStream in = null;
if (type != StandardFonts.FONT_UNSUPPORTED && new File(fontPath + currentFont).exists()) {
// see if root dir exists
boolean failed=false;
try {
in = new FileInputStream(fontPath + currentFont);
} catch (final Exception e) {
LogWriter.writeLog("Exception: "+e.getMessage());
failed=true;
} catch (final Error err) {
LogWriter.writeLog("Error: "+err.getMessage());
failed=true;
}
// if it does, add
if (!failed) {
final String fontName;
//name from file
final int pointer = currentFont.indexOf('.');
if (pointer == -1) {
fontName = currentFont.toLowerCase();
} else {
fontName = currentFont.substring(0, pointer).toLowerCase();
}
//choose filename or over-ride if OpenType
if (fontSubstitutionMode == PdfDecoderInt.SUBSTITUTE_FONT_USING_FILE_NAME|| type == StandardFonts.OPENTYPE) {
if(type==StandardFonts.TYPE1) {
fontSubstitutionTable.put(fontName, "/Type1");
} else {
//TT or OTF
fontSubstitutionTable.put(fontName, "/TrueType");
}
fontSubstitutionLocation.put(fontName, fontPath + currentFont);
//store details under file
fontPropertiesTable.put(fontName+"_type", type);
fontPropertiesTable.put(fontName+"_path",fontPath + currentFont);
} else if (type == StandardFonts.TRUETYPE_COLLECTION || type == StandardFonts.TRUETYPE) {
if(fontSubstitutionMode== PdfDecoderInt.SUBSTITUTE_FONT_USING_POSTSCRIPT_NAME_USE_FAMILY_NAME_IF_DUPLICATES){
//get both possible values
String[] postscriptNames=null;
try {
postscriptNames = StandardFonts.readNamesFromFont(type, fontPath + currentFont, PdfDecoderInt.SUBSTITUTE_FONT_USING_POSTSCRIPT_NAME);
} catch (final Exception e) {
LogWriter.writeLog("Exception: "+e.getMessage());
}
String[] familyNames =null;
try {
familyNames = StandardFonts.readNamesFromFont(type, fontPath + currentFont, PdfDecoderInt.SUBSTITUTE_FONT_USING_FAMILY_NAME);
} catch (final Exception e) {
LogWriter.writeLog("Exception: "+e.getMessage());
}
int fontCount=0;
if(postscriptNames!=null) {
fontCount=postscriptNames.length;
}
for(int ii=0;ii