From 1e49e903b786f4bd8a8ae5b254faad4e477cf6ac Mon Sep 17 00:00:00 2001
From: Cornelis Hoeflake <c.hoeflake@o3spaces.com>
Date: Fri, 31 Oct 2014 10:48:14 +0100
Subject: [PATCH] separates font substitute logic from ExternalFonts PDFBOX-2467

---
 .../apache/pdfbox/pdmodel/font/ExternalFonts.java  | 214 ++++-----------------
 .../pdmodel/font/SubstituteFontProvider.java       | 201 +++++++++++++++++++
 2 files changed, 238 insertions(+), 177 deletions(-)
 create mode 100644 pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/SubstituteFontProvider.java

diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ExternalFonts.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ExternalFonts.java
index b04c1ca..8b0f49d 100644
--- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ExternalFonts.java
+++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/ExternalFonts.java
@@ -19,9 +19,7 @@ package org.apache.pdfbox.pdmodel.font;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -33,8 +31,8 @@ import org.apache.fontbox.cff.CFFFont;
 import org.apache.fontbox.cff.CFFParser;
 import org.apache.fontbox.cff.CFFType1Font;
 import org.apache.fontbox.ttf.TTFParser;
-import org.apache.fontbox.ttf.Type1Equivalent;
 import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.ttf.Type1Equivalent;
 import org.apache.fontbox.type1.Type1Font;
 import org.apache.pdfbox.io.IOUtils;
 
@@ -59,8 +57,15 @@ public final class ExternalFonts
     /** fallback fonts, used as as a last resort */
     private static final TrueTypeFont ttfFallbackFont;
     private static final CFFCIDFont cidFallbackFont;
+    private final static Map<String, List<String>> registrySubstitutes = new HashMap<String, List<String>>();
     static
     {
+        // extra substitute mechanism for CJK CIDFonts when all we know is the ROS
+        registrySubstitutes.put("$Adobe-CNS1", Arrays.asList("AdobeMingStd-Light"));
+        registrySubstitutes.put("$Adobe-Japan1", Arrays.asList("KozMinPr6N-Regular"));
+        registrySubstitutes.put("$Adobe-Korea1", Arrays.asList("AdobeGothicStd-Bold"));
+        registrySubstitutes.put("$Adobe-GB1", Arrays.asList("AdobeHeitiStd-Regular"));
+
         try
         {
             // ttf
@@ -97,7 +102,19 @@ public final class ExternalFonts
      */
     public static void setProvider(FontProvider fontProvider)
     {
-        ExternalFonts.fontProvider = fontProvider;
+        setProvider(fontProvider, true);
+    }
+    
+    /**
+     * Sets the font service provider.
+     */
+    public static void setProvider(FontProvider fontProvider, boolean withSubstitutes)
+    {
+    	if (withSubstitutes && !(fontProvider instanceof SubstituteFontProvider)) {
+    		ExternalFonts.fontProvider = new SubstituteFontProvider(fontProvider);
+    	} else {
+    		ExternalFonts.fontProvider = fontProvider;
+    	}
     }
 
     /**
@@ -112,117 +129,6 @@ public final class ExternalFonts
         return fontProvider;
     }
 
-    /** Map of PostScript name substitutes, in priority order. */
-    private final static Map<String, List<String>> substitutes = new HashMap<String, List<String>>();
-    static
-    {
-        // substitutes for standard 14 fonts
-        substitutes.put("Courier",
-                Arrays.asList("CourierNew", "CourierNewPSMT", "LiberationMono", "NimbusMonL-Regu"));
-        substitutes.put("Courier-Bold",
-                Arrays.asList("CourierNewPS-BoldMT", "CourierNew-Bold", "LiberationMono-Bold",
-                              "NimbusMonL-Bold"));
-        substitutes.put("Courier-Oblique",
-                Arrays.asList("CourierNewPS-ItalicMT","CourierNew-Italic",
-                              "LiberationMono-Italic", "NimbusMonL-ReguObli"));
-        substitutes.put("Courier-BoldOblique",
-                Arrays.asList("CourierNewPS-BoldItalicMT","CourierNew-BoldItalic",
-                              "LiberationMono-BoldItalic", "NimbusMonL-BoldObli"));
-        substitutes.put("Helvetica",
-                Arrays.asList("ArialMT", "Arial", "LiberationSans", "NimbusSanL-Regu"));
-        substitutes.put("Helvetica-Bold",
-                Arrays.asList("Arial-BoldMT", "Arial-Bold", "LiberationSans-Bold",
-                              "NimbusSanL-Bold"));
-        substitutes.put("Helvetica-Oblique",
-                Arrays.asList("Arial-ItalicMT", "Arial-Italic", "Helvetica-Italic",
-                              "LiberationSans-Italic", "NimbusSanL-ReguItal"));
-        substitutes.put("Helvetica-BoldOblique",
-                Arrays.asList("Arial-BoldItalicMT", "Helvetica-BoldItalic",
-                              "LiberationSans-BoldItalic", "NimbusSanL-BoldItal"));
-        substitutes.put("Times-Roman",
-                Arrays.asList("TimesNewRomanPSMT", "TimesNewRoman", "TimesNewRomanPS",
-                              "LiberationSerif", "NimbusRomNo9L-Regu"));
-        substitutes.put("Times-Bold",
-                Arrays.asList("TimesNewRomanPS-BoldMT", "TimesNewRomanPS-Bold",
-                              "TimesNewRoman-Bold", "LiberationSerif-Bold",
-                              "NimbusRomNo9L-Medi"));
-        substitutes.put("Times-Italic",
-                Arrays.asList("TimesNewRomanPS-ItalicMT", "TimesNewRomanPS-Italic",
-                              "TimesNewRoman-Italic", "LiberationSerif-Italic",
-                              "NimbusRomNo9L-ReguItal"));
-        substitutes.put("Times-BoldItalic",
-                Arrays.asList("TimesNewRomanPS-BoldItalicMT", "TimesNewRomanPS-BoldItalic",
-                             "TimesNewRoman-BoldItalic", "LiberationSerif-BoldItalic",
-                             "NimbusRomNo9L-MediItal"));
-        substitutes.put("Symbol", Arrays.asList("SymbolMT", "StandardSymL"));
-        substitutes.put("ZapfDingbats", Arrays.asList("ZapfDingbatsITC", "Dingbats"));
-
-        // extra substitute mechanism for CJK CIDFonts when all we know is the ROS
-        substitutes.put("$Adobe-CNS1", Arrays.asList("AdobeMingStd-Light"));
-        substitutes.put("$Adobe-Japan1", Arrays.asList("KozMinPr6N-Regular"));
-        substitutes.put("$Adobe-Korea1", Arrays.asList("AdobeGothicStd-Bold"));
-        substitutes.put("$Adobe-GB1", Arrays.asList("AdobeHeitiStd-Regular"));
-
-        // Acrobat also uses alternative names for Standard 14 fonts, which we map to those above
-        // these include names such as "Arial" and "TimesNewRoman"
-        for (String baseName : Standard14Fonts.getNames())
-        {
-            if (!substitutes.containsKey(baseName))
-            {
-                String mappedName = Standard14Fonts.getMappedFontName(baseName);
-                substitutes.put(baseName, copySubstitutes(mappedName));
-            }
-        }
-    }
-
-    /**
-     * Copies a list of font substitutes, adding the original font at the start of the list.
-     */
-    private static List<String> copySubstitutes(String postScriptName)
-    {
-        List<String> copy = new ArrayList<String>(substitutes.get(postScriptName));
-        copy.add(0, postScriptName);
-        return copy;
-    }
-
-    /**
-     * Adds a top-priority substitute for the given font.
-     *
-     * @param match PostScript name of the font to match
-     * @param replace PostScript name of the font to use as a replacement
-     */
-    public static void addSubstitute(String match, String replace)
-    {
-        if (!substitutes.containsKey(match))
-        {
-            substitutes.put(match, new ArrayList<String>());
-        }
-        substitutes.get(match).add(replace);
-    }
-
-    /**
-     * Returns the substitutes for a given font.
-     */
-    private static List<String> getSubstitutes(String postScriptName)
-    {
-        List<String> subs = substitutes.get(postScriptName.replaceAll(" ", ""));
-        if (subs != null)
-        {
-            return subs;
-        }
-        else
-        {
-            return Collections.emptyList();
-        }
-    }
-
-    /**
-     * Windows name (ArialNarrow,Bold) to PostScript name (ArialNarrow-Bold)
-     */
-    private static String windowsToPs(String windowsName)
-    {
-        return windowsName.replaceAll(",", "-");
-    }
 
     /**
      * Finds a CFF CID-Keyed font with the given PostScript name, or a suitable substitute, or null.
@@ -237,16 +143,19 @@ public final class ExternalFonts
         // todo: this is a fairly primitive mechanism and could be improved
         if (registryOrdering != null)
         {
-            for (String substituteName : getSubstitutes("$" + registryOrdering))
-            {
-                CFFFont cff = getProvider().getCFFFont(substituteName);
-                if (cff != null)
-                {
-                    if (cff instanceof CFFCIDFont)
-                    {
-                        return (CFFCIDFont)cff;
-                    }
-                }
+            List<String> list = registrySubstitutes.get("$" + registryOrdering);
+            if (list != null) {
+				for (String substituteName : list)
+	            {
+	                CFFFont cff = getProvider().getCFFFont(substituteName);
+	                if (cff != null)
+	                {
+	                    if (cff instanceof CFFCIDFont)
+	                    {
+	                        return (CFFCIDFont)cff;
+	                    }
+	                }
+	            }
             }
         }
         return cidFallbackFont;
@@ -376,23 +285,7 @@ public final class ExternalFonts
      */
     public static TrueTypeFont getTrueTypeFont(String postScriptName)
     {
-        // first ask the font provider for the font
-        TrueTypeFont ttf = getProvider().getTrueTypeFont(postScriptName);
-        if (ttf == null)
-        {
-            // then try substitutes
-            for (String substituteName : getSubstitutes(postScriptName))
-            {
-                ttf = getProvider().getTrueTypeFont(substituteName);
-                if (ttf != null)
-                {
-                    return ttf;
-                }
-            }
-            // then Windows name
-            ttf = getProvider().getTrueTypeFont(windowsToPs(postScriptName));
-        }
-        return ttf;
+    	return getProvider().getTrueTypeFont(postScriptName);
     }
 
     /**
@@ -402,23 +295,7 @@ public final class ExternalFonts
      */
     public static Type1Font getType1Font(String postScriptName)
     {
-        // first ask the font provider for the font
-        Type1Font t1 = getProvider().getType1Font(postScriptName);
-        if (t1 == null)
-        {
-            // then try substitutes
-            for (String substituteName : getSubstitutes(postScriptName))
-            {
-                t1 = getProvider().getType1Font(substituteName);
-                if (t1 != null)
-                {
-                    return t1;
-                }
-            }
-            // then Windows name
-            t1 = getProvider().getType1Font(windowsToPs(postScriptName));
-        }
-        return t1;
+    	return getProvider().getType1Font(postScriptName);
     }
 
     /**
@@ -458,24 +335,7 @@ public final class ExternalFonts
      */
     private static CFFFont getCFFFont(String postScriptName)
     {
-        // first ask the font provider for the font
-        CFFFont cff = getProvider().getCFFFont(postScriptName);
-        if (cff == null)
-        {
-            // then try substitutes
-            for (String substituteName : getSubstitutes(postScriptName))
-            {
-                cff = getProvider().getCFFFont(substituteName);
-                if (cff != null)
-                {
-                    return cff;
-                }
-            }
-
-            // then Windows name
-            cff = getProvider().getCFFFont(windowsToPs(postScriptName));
-        }
-        return cff;
+    	return getProvider().getCFFFont(postScriptName);
     }
 
     /**
diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/SubstituteFontProvider.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/SubstituteFontProvider.java
new file mode 100644
index 0000000..7034da5
--- /dev/null
+++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/SubstituteFontProvider.java
@@ -0,0 +1,201 @@
+package org.apache.pdfbox.pdmodel.font;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fontbox.cff.CFFFont;
+import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.type1.Type1Font;
+
+public class SubstituteFontProvider implements FontProvider {
+	private FontProvider delegate;
+
+    /** Map of PostScript name substitutes, in priority order. */
+    private final static Map<String, List<String>> substitutes = new HashMap<String, List<String>>();
+    static
+    {
+        // substitutes for standard 14 fonts
+        substitutes.put("Courier",
+                Arrays.asList("CourierNew", "CourierNewPSMT", "LiberationMono", "NimbusMonL-Regu"));
+        substitutes.put("Courier-Bold",
+                Arrays.asList("CourierNewPS-BoldMT", "CourierNew-Bold", "LiberationMono-Bold",
+                              "NimbusMonL-Bold"));
+        substitutes.put("Courier-Oblique",
+                Arrays.asList("CourierNewPS-ItalicMT","CourierNew-Italic",
+                              "LiberationMono-Italic", "NimbusMonL-ReguObli"));
+        substitutes.put("Courier-BoldOblique",
+                Arrays.asList("CourierNewPS-BoldItalicMT","CourierNew-BoldItalic",
+                              "LiberationMono-BoldItalic", "NimbusMonL-BoldObli"));
+        substitutes.put("Helvetica",
+                Arrays.asList("ArialMT", "Arial", "LiberationSans", "NimbusSanL-Regu"));
+        substitutes.put("Helvetica-Bold",
+                Arrays.asList("Arial-BoldMT", "Arial-Bold", "LiberationSans-Bold",
+                              "NimbusSanL-Bold"));
+        substitutes.put("Helvetica-Oblique",
+                Arrays.asList("Arial-ItalicMT", "Arial-Italic", "Helvetica-Italic",
+                              "LiberationSans-Italic", "NimbusSanL-ReguItal"));
+        substitutes.put("Helvetica-BoldOblique",
+                Arrays.asList("Arial-BoldItalicMT", "Helvetica-BoldItalic",
+                              "LiberationSans-BoldItalic", "NimbusSanL-BoldItal"));
+        substitutes.put("Times-Roman",
+                Arrays.asList("TimesNewRomanPSMT", "TimesNewRoman", "TimesNewRomanPS",
+                              "LiberationSerif", "NimbusRomNo9L-Regu"));
+        substitutes.put("Times-Bold",
+                Arrays.asList("TimesNewRomanPS-BoldMT", "TimesNewRomanPS-Bold",
+                              "TimesNewRoman-Bold", "LiberationSerif-Bold",
+                              "NimbusRomNo9L-Medi"));
+        substitutes.put("Times-Italic",
+                Arrays.asList("TimesNewRomanPS-ItalicMT", "TimesNewRomanPS-Italic",
+                              "TimesNewRoman-Italic", "LiberationSerif-Italic",
+                              "NimbusRomNo9L-ReguItal"));
+        substitutes.put("Times-BoldItalic",
+                Arrays.asList("TimesNewRomanPS-BoldItalicMT", "TimesNewRomanPS-BoldItalic",
+                             "TimesNewRoman-BoldItalic", "LiberationSerif-BoldItalic",
+                             "NimbusRomNo9L-MediItal"));
+        substitutes.put("Symbol", Arrays.asList("SymbolMT", "StandardSymL"));
+        substitutes.put("ZapfDingbats", Arrays.asList("ZapfDingbatsITC", "Dingbats"));
+
+        // Acrobat also uses alternative names for Standard 14 fonts, which we map to those above
+        // these include names such as "Arial" and "TimesNewRoman"
+        for (String baseName : Standard14Fonts.getNames())
+        {
+            if (!substitutes.containsKey(baseName))
+            {
+                String mappedName = Standard14Fonts.getMappedFontName(baseName);
+                substitutes.put(baseName, copySubstitutes(mappedName));
+            }
+        }
+    }
+
+    /**
+     * Copies a list of font substitutes, adding the original font at the start of the list.
+     */
+    private static List<String> copySubstitutes(String postScriptName)
+    {
+        List<String> copy = new ArrayList<String>(substitutes.get(postScriptName));
+        copy.add(0, postScriptName);
+        return copy;
+    }
+
+    /**
+     * Adds a top-priority substitute for the given font.
+     *
+     * @param match PostScript name of the font to match
+     * @param replace PostScript name of the font to use as a replacement
+     */
+    public static void addSubstitute(String match, String replace)
+    {
+        if (!substitutes.containsKey(match))
+        {
+            substitutes.put(match, new ArrayList<String>());
+        }
+        substitutes.get(match).add(replace);
+    }
+
+    /**
+     * Returns the substitutes for a given font.
+     */
+    protected static List<String> getSubstitutes(String postScriptName)
+    {
+        List<String> subs = substitutes.get(postScriptName.replaceAll(" ", ""));
+        if (subs != null)
+        {
+            return subs;
+        }
+        else
+        {
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Windows name (ArialNarrow,Bold) to PostScript name (ArialNarrow-Bold)
+     */
+    private static String windowsToPs(String windowsName)
+    {
+        return windowsName.replaceAll(",", "-");
+    }
+
+    public SubstituteFontProvider(FontProvider delegate) {
+		this.delegate = delegate;
+	}
+	
+
+	@Override
+	public TrueTypeFont getTrueTypeFont(String postScriptName) {
+        // first ask the font provider for the font
+        TrueTypeFont ttf = delegate.getTrueTypeFont(postScriptName);
+        if (ttf == null)
+        {
+            // then try substitutes
+            for (String substituteName : getSubstitutes(postScriptName))
+            {
+                ttf = delegate.getTrueTypeFont(substituteName);
+                if (ttf != null)
+                {
+                    return ttf;
+                }
+            }
+            // then Windows name
+            ttf = delegate.getTrueTypeFont(windowsToPs(postScriptName));
+        }
+        return ttf;
+	}
+
+	@Override
+	public CFFFont getCFFFont(String postScriptName) {
+        // first ask the font provider for the font
+        CFFFont cff = delegate.getCFFFont(postScriptName);
+        if (cff == null)
+        {
+            // then try substitutes
+            for (String substituteName : getSubstitutes(postScriptName))
+            {
+                cff = delegate.getCFFFont(substituteName);
+                if (cff != null)
+                {
+                    return cff;
+                }
+            }
+
+            // then Windows name
+            cff = delegate.getCFFFont(windowsToPs(postScriptName));
+        }
+        return cff;
+	}
+
+	@Override
+	public Type1Font getType1Font(String postScriptName) {
+        // first ask the font provider for the font
+        Type1Font t1 = delegate.getType1Font(postScriptName);
+        if (t1 == null)
+        {
+            // then try substitutes
+            for (String substituteName : getSubstitutes(postScriptName))
+            {
+                t1 = delegate.getType1Font(substituteName);
+                if (t1 != null)
+                {
+                    return t1;
+                }
+            }
+            // then Windows name
+            t1 = delegate.getType1Font(windowsToPs(postScriptName));
+        }
+        return t1;
+	}
+
+	@Override
+	public String toDebugString() {
+		String message = delegate.toDebugString();
+		if (message != null) {
+			return "Substitude font provider with delegate: " + message;
+		}
+		return null;
+	}
+
+}
-- 
1.8.1.msysgit.1

