FOP-3277: Add font substitution support for PDFTranscoder

diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
index 6fdc697..8f4e671 100644
--- a/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
+++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java
@@ -98,7 +98,7 @@
      * be used to influence the start position of the first letter. The entry i+1 defines the
      * cursor advancement after the character i. A null entry means no special advancement.
      */
-    private final MinOptMax[] letterSpaceAdjustArray; //size = textArray.length + 1
+    private MinOptMax[] letterSpaceAdjustArray; //size = textArray.length + 1
 
     /** Font used for the space between words. */
     private Font spaceFont;
@@ -142,7 +142,6 @@
      */
     public TextLayoutManager(FOText node, FOUserAgent userAgent) {
         foText = node;
-        letterSpaceAdjustArray = new MinOptMax[node.length() + 1];
         mappings = new ArrayList<GlyphMapping>();
         this.userAgent = userAgent;
     }
@@ -267,7 +266,7 @@
         }
 
         for (int i = mapping.startIndex; i < mapping.endIndex; i++) {
-            MinOptMax letterSpaceAdjustment = letterSpaceAdjustArray[i + 1];
+            MinOptMax letterSpaceAdjustment = getLetterSpaceAdjustment(i + 1);
             if (letterSpaceAdjustment != null && letterSpaceAdjustment.isElastic()) {
                 letterSpaceCount++;
             }
@@ -659,8 +658,7 @@
                 int j = letterSpaceAdjustIndex + i;
                 if (j > 0) {
                     int k = wordMapping.startIndex + i;
-                    MinOptMax adj = (k < letterSpaceAdjustArray.length)
-                        ? letterSpaceAdjustArray [ k ] : null;
+                    MinOptMax adj = getLetterSpaceAdjustment(k);
                     letterSpaceAdjust [ j ] = (adj == null) ? 0 : adj.getOpt();
                 }
                 if (letterSpaceCount > 0) {
@@ -975,6 +973,9 @@
         char breakOpportunityChar = breakOpportunity ? ch : 0;
         char precedingChar = prevMapping != null && !prevMapping.isSpace
                 && prevMapping.endIndex > 0 ? foText.charAt(prevMapping.endIndex - 1) : 0;
+        if (letterSpaceAdjustArray == null && font.hasKerning()) {
+            letterSpaceAdjustArray = new MinOptMax[foText.length() + 1];
+        }
         GlyphMapping mapping = GlyphMapping.doGlyphMapping(foText, thisStart, lastIndex, font,
                 letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar,
                 endsWithHyphen, level, false, false, retainControls);
@@ -1076,7 +1077,7 @@
                 newIPD = newIPD.plus(font.getCharWidth(cp));
                 //if (i > startIndex) {
                 if (i < stopIndex) {
-                    MinOptMax letterSpaceAdjust = letterSpaceAdjustArray[i + 1];
+                    MinOptMax letterSpaceAdjust = getLetterSpaceAdjustment(i + 1);
                     if (i == stopIndex - 1 && hyphenFollows) {
                         //the letter adjust here needs to be handled further down during
                         //element generation because it depends on hyph/no-hyph condition
@@ -1388,7 +1389,7 @@
             MinOptMax widthIfNoBreakOccurs = null;
             if (mapping.endIndex < foText.length()) {
                 //Add in kerning in no-break condition
-                widthIfNoBreakOccurs = letterSpaceAdjustArray[mapping.endIndex];
+                widthIfNoBreakOccurs = getLetterSpaceAdjustment(mapping.endIndex);
             }
             //if (mapping.breakIndex)
 
@@ -1523,4 +1524,10 @@
             + "}";
     }
 
+    protected MinOptMax getLetterSpaceAdjustment(int i) {
+        if (letterSpaceAdjustArray == null || i >= letterSpaceAdjustArray.length) {
+            return null;
+        }
+        return letterSpaceAdjustArray[i];
+    }
 }
diff --git a/fop-core/src/main/java/org/apache/fop/traits/MinOptMax.java b/fop-core/src/main/java/org/apache/fop/traits/MinOptMax.java
index a647ae3..fb339b7 100644
--- a/fop-core/src/main/java/org/apache/fop/traits/MinOptMax.java
+++ b/fop-core/src/main/java/org/apache/fop/traits/MinOptMax.java
@@ -20,6 +20,7 @@
 package org.apache.fop.traits;
 
 import java.io.Serializable;
+import java.util.Objects;
 
 /**
  * This class holds the resolved (as mpoints) form of a
@@ -37,7 +38,7 @@
     /**
      * The zero <code>MinOptMax</code> instance with <code>min == opt == max == 0</code>.
      */
-    public static final MinOptMax ZERO = getInstance(0);
+    public static final MinOptMax ZERO = new MinOptMax(0, 0, 0);
 
     private final int min;
     private final int opt;
@@ -59,6 +60,9 @@
         if (max < opt) {
             throw new IllegalArgumentException("max (" + max + ") < opt (" + opt + ")");
         }
+        if (min == 0 && opt == 0 && max == 0) {
+            return ZERO;
+        }
         return new MinOptMax(min, opt, max);
     }
 
@@ -71,7 +75,7 @@
      * @see #isStiff()
      */
     public static MinOptMax getInstance(int value) {
-        return new MinOptMax(value, value, value);
+        return getInstance(value, value, value);
     }
 
     // Private constructor without consistency checks
@@ -136,7 +140,7 @@
      * @return the sum of this <code>MinOptMax</code> and the given <code>MinOptMax</code>.
      */
     public MinOptMax plus(MinOptMax operand) {
-        return new MinOptMax(min + operand.min, opt + operand.opt, max + operand.max);
+        return getInstance(min + operand.min, opt + operand.opt, max + operand.max);
     }
 
 
@@ -147,7 +151,7 @@
      * @return the result of the addition
      */
     public MinOptMax plus(int value) {
-        return new MinOptMax(min + value, opt + value, max + value);
+        return getInstance(min + value, opt + value, max + value);
     }
 
     /**
@@ -166,7 +170,7 @@
     public MinOptMax minus(MinOptMax operand) throws ArithmeticException {
         checkCompatibility(getShrink(), operand.getShrink(), "shrink");
         checkCompatibility(getStretch(), operand.getStretch(), "stretch");
-        return new MinOptMax(min - operand.min, opt - operand.opt, max - operand.max);
+        return getInstance(min - operand.min, opt - operand.opt, max - operand.max);
     }
 
     private void checkCompatibility(int thisElasticity, int operandElasticity, String msge) {
@@ -184,7 +188,7 @@
      * @return the result of the subtraction
      */
     public MinOptMax minus(int value) {
-        return new MinOptMax(min - value, opt - value, max - value);
+        return getInstance(min - value, opt - value, max - value);
     }
 
     /**
@@ -328,10 +332,7 @@
      * {@inheritDoc}
      */
     public int hashCode() {
-        int result = min;
-        result = 31 * result + opt;
-        result = 31 * result + max;
-        return result;
+        return Objects.hash(min, opt, max);
     }
 
     /**
diff --git a/fop-core/src/test/java/org/apache/fop/layoutmgr/inline/TextLayoutManagerTestCase.java b/fop-core/src/test/java/org/apache/fop/layoutmgr/inline/TextLayoutManagerTestCase.java
new file mode 100644
index 0000000..c2b19fe
--- /dev/null
+++ b/fop-core/src/test/java/org/apache/fop/layoutmgr/inline/TextLayoutManagerTestCase.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+package org.apache.fop.layoutmgr.inline;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TextLayoutManagerTestCase {
+    @Test
+    public void testLetterSpaceAdjustArray() {
+        Assert.assertNull(new TextLayoutManager(null, null).getLetterSpaceAdjustment(1));
+    }
+}
diff --git a/fop-core/src/test/java/org/apache/fop/traits/MinOptMaxTestCase.java b/fop-core/src/test/java/org/apache/fop/traits/MinOptMaxTestCase.java
index 190fb47..abe0c2c 100644
--- a/fop-core/src/test/java/org/apache/fop/traits/MinOptMaxTestCase.java
+++ b/fop-core/src/test/java/org/apache/fop/traits/MinOptMaxTestCase.java
@@ -37,6 +37,7 @@
     @Test
     public void testZero() {
         assertEquals(MinOptMax.getInstance(0), MinOptMax.ZERO);
+        assertTrue(MinOptMax.getInstance(0, 0, 0) == MinOptMax.ZERO);
     }
 
     @Test