pdfbox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From til...@apache.org
Subject svn commit: r1829710 [1/3] - in /pdfbox/trunk: examples/src/main/java/org/apache/pdfbox/examples/pdmodel/ examples/src/main/resources/org/apache/pdfbox/resources/ttf/ fontbox/src/main/java/org/apache/fontbox/ttf/ fontbox/src/main/java/org/apache/fontbo...
Date Sat, 21 Apr 2018 14:17:29 GMT
Author: tilman
Date: Sat Apr 21 14:17:29 2018
New Revision: 1829710

URL: http://svn.apache.org/viewvc?rev=1829710&view=rev
Log:
PDFBOX-4189: Enable rendering of Indian languages by reading and utilizing the GSUB table, by Palash Ray

Added:
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/BengaliPdfGenerationHelloWorld.java   (with props)
    pdfbox/trunk/examples/src/main/resources/org/apache/pdfbox/resources/ttf/bengali-correct-text.png   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitter.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorker.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForBengali.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/package.html   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat1.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat2.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/FeatureListTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/FeatureRecord.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/FeatureTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/LangSysRecord.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/LangSysTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/LookupListTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/LookupSubTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/LookupTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/RangeRecord.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/ScriptRecord.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/ScriptTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/package.html   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/LigatureSetTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/LigatureTable.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/LookupTypeLigatureSubstitutionSubstFormat1.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/LookupTypeSingleSubstFormat1.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/LookupTypeSingleSubstFormat2.java   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/package.html   (with props)
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/package.html   (with props)
    pdfbox/trunk/fontbox/src/test/java/org/apache/fontbox/ttf/GlyphSubstitutionTableTest.java   (with props)
    pdfbox/trunk/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/
    pdfbox/trunk/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizerTest.java   (with props)
    pdfbox/trunk/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImplTest.java   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/abvs.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/akhn.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/blwf.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/blws.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/half.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/haln.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/init.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/nukt.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/pres.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/pstf.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/rphf.txt   (with props)
    pdfbox/trunk/fontbox/src/test/resources/gsub/lohit_bengali/bng2/vatu.txt   (with props)
Modified:
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
    pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageContentStream.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFont.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType0.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDCIDFontType2.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/PDType0Font.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/TrueTypeEmbedder.java

Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/BengaliPdfGenerationHelloWorld.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/BengaliPdfGenerationHelloWorld.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/BengaliPdfGenerationHelloWorld.java (added)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/BengaliPdfGenerationHelloWorld.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,115 @@
+/*
+ * 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.examples.pdmodel;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageContentStream;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType0Font;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
+
+/**
+ * Inspired from <a href=
+ * "https://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/HelloWorldTTF.java?view=markup">PdfBox
+ * Example</a>. This attempts to correctly demonstrate to what extent Bengali text rendering is
+ * supported. First, we render some text, and then embed an image with the correct text displayed on
+ * the next page.
+ *
+ * @author Palash Ray
+ *
+ */
+public class BengaliPdfGenerationHelloWorld
+{
+
+    /**
+     * The unicode of this is given below:
+     * 
+     * <pre>
+     * \u0986\u09ae\u09bf  \u0995\u09cb\u09a8 \u09aa\u09a5\u09c7  \u0995\u09cd\u09b7\u09c0\u09b0\u09c7\u09b0 \u09b7\u09a8\u09cd\u09a1  \u09aa\u09c1\u09a4\u09c1\u09b2 \u09b0\u09c1\u09aa\u09cb  \u0997\u0999\u09cd\u0997\u09be \u098b\u09b7\u09bf
+     * </pre>
+     * 
+     */
+    private static final String BANGLA_TEXT_1 = "আমি কোন পথে ক্ষীরের লক্ষ্মী ষন্ড পুতুল রুপো গঙ্গা ঋষি";
+    private static final String BANGLA_TEXT_2 = "দ্রুত গাঢ় শেয়াল অলস কুকুর জুড়ে জাম্প ধুর্ত  হঠাৎ ভাঙেনি মৌলিক ঐশি দৈ";
+    private static final String BANGLA_TEXT_3 = "ঋষি কল্লোল ব্যাস নির্ভয় ";
+
+    static
+    {
+        if (System.getProperty("java.version").startsWith("1.8"))
+        {
+            System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider");
+        }
+    }
+
+    public static void main(String[] args) throws IOException, URISyntaxException
+    {
+        if (args.length != 1)
+        {
+            System.err.println(
+                    "usage: " + BengaliPdfGenerationHelloWorld.class.getName() + " <output-file> ");
+            System.exit(1);
+        }
+
+        String filename = args[0];
+
+        System.out.println("The generated pdf filename is: " + filename);
+
+        PDDocument doc = new PDDocument();
+        try
+        {
+
+            PDPage page1 = new PDPage();
+            doc.addPage(page1);
+
+            PDFont font = PDType0Font.load(doc, BengaliPdfGenerationHelloWorld.class
+                    .getResourceAsStream("/org/apache/pdfbox/resources/ttf/Lohit-Bengali.ttf"),
+                    true);
+
+            PDPageContentStream contents = new PDPageContentStream(doc, page1);
+            contents.beginText();
+            contents.setFont(font, 12);
+            contents.newLineAtOffset(10, 750);
+            contents.showText(BANGLA_TEXT_1);
+            contents.newLineAtOffset(0, -50);
+            contents.showText(BANGLA_TEXT_2);
+            contents.newLineAtOffset(0, -30);
+            contents.showText(BANGLA_TEXT_3);
+            contents.endText();
+
+            PDImageXObject pdImage = PDImageXObject
+                    .createFromFile(BengaliPdfGenerationHelloWorld.class
+                            .getResource(
+                                    "/org/apache/pdfbox/resources/ttf/bengali-correct-text.png")
+                            // getFile() doesn't work if there is a space in the path
+                            .toURI().getPath(), doc);
+            contents.drawImage(pdImage, 0, 300, pdImage.getWidth(), pdImage.getHeight());
+            contents.close();
+
+            doc.save(filename);
+        }
+        finally
+        {
+            doc.close();
+        }
+    }
+
+}

Propchange: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/BengaliPdfGenerationHelloWorld.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/examples/src/main/resources/org/apache/pdfbox/resources/ttf/bengali-correct-text.png
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/resources/org/apache/pdfbox/resources/ttf/bengali-correct-text.png?rev=1829710&view=auto
==============================================================================
Binary file - no diff available.

Propchange: pdfbox/trunk/examples/src/main/resources/org/apache/pdfbox/resources/ttf/bengali-correct-text.png
------------------------------------------------------------------------------
    svn:mime-type = image/png

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java?rev=1829710&r1=1829709&r2=1829710&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java Sat Apr 21 14:17:29 2018
@@ -19,7 +19,6 @@ package org.apache.fontbox.ttf;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -31,6 +30,26 @@ import java.util.Map;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.ttf.gsub.GlyphSubstitutionDataExtractor;
+import org.apache.fontbox.ttf.table.common.CoverageTable;
+import org.apache.fontbox.ttf.table.common.CoverageTableFormat1;
+import org.apache.fontbox.ttf.table.common.CoverageTableFormat2;
+import org.apache.fontbox.ttf.table.common.FeatureListTable;
+import org.apache.fontbox.ttf.table.common.FeatureRecord;
+import org.apache.fontbox.ttf.table.common.FeatureTable;
+import org.apache.fontbox.ttf.table.common.LangSysRecord;
+import org.apache.fontbox.ttf.table.common.LangSysTable;
+import org.apache.fontbox.ttf.table.common.LookupListTable;
+import org.apache.fontbox.ttf.table.common.LookupSubTable;
+import org.apache.fontbox.ttf.table.common.LookupTable;
+import org.apache.fontbox.ttf.table.common.RangeRecord;
+import org.apache.fontbox.ttf.table.common.ScriptRecord;
+import org.apache.fontbox.ttf.table.common.ScriptTable;
+import org.apache.fontbox.ttf.table.gsub.LigatureSetTable;
+import org.apache.fontbox.ttf.table.gsub.LigatureTable;
+import org.apache.fontbox.ttf.table.gsub.LookupTypeLigatureSubstitutionSubstFormat1;
+import org.apache.fontbox.ttf.table.gsub.LookupTypeSingleSubstFormat1;
+import org.apache.fontbox.ttf.table.gsub.LookupTypeSingleSubstFormat2;
 
 /**
  * A glyph substitution 'GSUB' table in a TrueType or OpenType font.
@@ -43,16 +62,18 @@ public class GlyphSubstitutionTable exte
 
     public static final String TAG = "GSUB";
 
-    private LinkedHashMap<String, ScriptTable> scriptList;
+    private Map<String, ScriptTable> scriptList;
     // featureList and lookupList are not maps because we need to index into them
-    private FeatureRecord[] featureList;
-    private LookupTable[] lookupList;
+    private FeatureListTable featureListTable;
+    private LookupListTable lookupListTable;
 
     private final Map<Integer, Integer> lookupCache = new HashMap<>();
     private final Map<Integer, Integer> reverseLookup = new HashMap<>();
 
     private String lastUsedSupportedScript;
 
+    private Map<String, Map<List<Integer>, Integer>> rawGSubData;
+
     GlyphSubstitutionTable(TrueTypeFont font)
     {
         super(font);
@@ -77,118 +98,125 @@ public class GlyphSubstitutionTable exte
         }
 
         scriptList = readScriptList(data, start + scriptListOffset);
-        featureList = readFeatureList(data, start + featureListOffset);
-        lookupList = readLookupList(data, start + lookupListOffset);
+        featureListTable = readFeatureList(data, start + featureListOffset);
+        lookupListTable = readLookupList(data, start + lookupListOffset);
+
+        GlyphSubstitutionDataExtractor glyphSubstitutionDataExtractor = new GlyphSubstitutionDataExtractor();
+
+        rawGSubData = glyphSubstitutionDataExtractor
+                .getGsubData(scriptList, featureListTable, lookupListTable);
+        LOG.debug("rawGSubData: " + rawGSubData);
     }
 
-    LinkedHashMap<String, ScriptTable> readScriptList(TTFDataStream data, long offset) throws IOException
+    private Map<String, ScriptTable> readScriptList(TTFDataStream data, long offset)
+            throws IOException
     {
         data.seek(offset);
         int scriptCount = data.readUnsignedShort();
-        ScriptRecord[] scriptRecords = new ScriptRecord[scriptCount];
+        ScriptTable[] scriptTables= new ScriptTable[scriptCount];
         int[] scriptOffsets = new int[scriptCount];
+        String[] scriptTags = new String[scriptCount];
         for (int i = 0; i < scriptCount; i++)
         {
-            ScriptRecord scriptRecord = new ScriptRecord();
-            scriptRecord.scriptTag = data.readString(4);
+            scriptTags[i] = data.readString(4);
             scriptOffsets[i] = data.readUnsignedShort();
-            scriptRecords[i] = scriptRecord;
         }
         for (int i = 0; i < scriptCount; i++)
         {
-            scriptRecords[i].scriptTable = readScriptTable(data, offset + scriptOffsets[i]);
+            scriptTables[i] = readScriptTable(data, offset + scriptOffsets[i]);
         }
-        LinkedHashMap<String, ScriptTable> resultScriptList = new LinkedHashMap<>(scriptCount);
-        for (ScriptRecord scriptRecord : scriptRecords)
+        Map<String, ScriptTable> resultScriptList = new LinkedHashMap<>(scriptCount);
+        for (int i = 0; i < scriptCount; i++)
         {
-            resultScriptList.put(scriptRecord.scriptTag, scriptRecord.scriptTable);
+            ScriptRecord scriptRecord = new ScriptRecord(scriptTags[i], scriptTables[i]);
+            resultScriptList.put(scriptRecord.getScriptTag(), scriptRecord.getScriptTable());
         }
-        return resultScriptList;
+        return Collections.unmodifiableMap(resultScriptList);
     }
 
-    ScriptTable readScriptTable(TTFDataStream data, long offset) throws IOException
+    private ScriptTable readScriptTable(TTFDataStream data, long offset) throws IOException
     {
         data.seek(offset);
-        ScriptTable scriptTable = new ScriptTable();
         int defaultLangSys = data.readUnsignedShort();
         int langSysCount = data.readUnsignedShort();
         LangSysRecord[] langSysRecords = new LangSysRecord[langSysCount];
+        String[] langSysTags = new String[langSysCount];
         int[] langSysOffsets = new int[langSysCount];
         for (int i = 0; i < langSysCount; i++)
         {
-            LangSysRecord langSysRecord = new LangSysRecord();
-            langSysRecord.langSysTag = data.readString(4);
+            langSysTags[i] = data.readString(4);
             langSysOffsets[i] = data.readUnsignedShort();
-            langSysRecords[i] = langSysRecord;
         }
+
+        LangSysTable defaultLangSysTable = null;
+
         if (defaultLangSys != 0)
         {
-            scriptTable.defaultLangSysTable = readLangSysTable(data, offset + defaultLangSys);
+            defaultLangSysTable = readLangSysTable(data, offset + defaultLangSys);
         }
         for (int i = 0; i < langSysCount; i++)
         {
-            langSysRecords[i].langSysTable = readLangSysTable(data, offset + langSysOffsets[i]);
+            LangSysTable langSysTable = readLangSysTable(data, offset + langSysOffsets[i]);
+            langSysRecords[i] = new LangSysRecord(langSysTags[i], langSysTable);
         }
-        scriptTable.langSysTables = new LinkedHashMap<>(langSysCount);
+        Map<String, LangSysTable> langSysTables = new LinkedHashMap<>(langSysCount);
         for (LangSysRecord langSysRecord : langSysRecords)
         {
-            scriptTable.langSysTables.put(langSysRecord.langSysTag, langSysRecord.langSysTable);
+            langSysTables.put(langSysRecord.getLangSysTag(),
+                    langSysRecord.getLangSysTable());
         }
-        return scriptTable;
+        return new ScriptTable(defaultLangSysTable, Collections.unmodifiableMap(langSysTables));
     }
 
-    LangSysTable readLangSysTable(TTFDataStream data, long offset) throws IOException
+    private LangSysTable readLangSysTable(TTFDataStream data, long offset) throws IOException
     {
         data.seek(offset);
-        LangSysTable langSysTable = new LangSysTable();
-        @SuppressWarnings({"unused", "squid:S1854"})
         int lookupOrder = data.readUnsignedShort();
-        langSysTable.requiredFeatureIndex = data.readUnsignedShort();
+        int requiredFeatureIndex = data.readUnsignedShort();
         int featureIndexCount = data.readUnsignedShort();
-        langSysTable.featureIndices = new int[featureIndexCount];
+        int[] featureIndices = new int[featureIndexCount];
         for (int i = 0; i < featureIndexCount; i++)
         {
-            langSysTable.featureIndices[i] = data.readUnsignedShort();
+            featureIndices[i] = data.readUnsignedShort();
         }
-        return langSysTable;
+        return new LangSysTable(lookupOrder, requiredFeatureIndex, featureIndexCount,
+                featureIndices);
     }
 
-    FeatureRecord[] readFeatureList(TTFDataStream data, long offset) throws IOException
+    private FeatureListTable readFeatureList(TTFDataStream data, long offset) throws IOException
     {
         data.seek(offset);
         int featureCount = data.readUnsignedShort();
         FeatureRecord[] featureRecords = new FeatureRecord[featureCount];
         int[] featureOffsets = new int[featureCount];
+        String[] featureTags = new String[featureCount];
         for (int i = 0; i < featureCount; i++)
         {
-            FeatureRecord featureRecord = new FeatureRecord();
-            featureRecord.featureTag = data.readString(4);
+            featureTags[i] = data.readString(4);
             featureOffsets[i] = data.readUnsignedShort();
-            featureRecords[i] = featureRecord;
         }
         for (int i = 0; i < featureCount; i++)
         {
-            featureRecords[i].featureTable = readFeatureTable(data, offset + featureOffsets[i]);
+            FeatureTable featureTable = readFeatureTable(data, offset + featureOffsets[i]);
+            featureRecords[i] = new FeatureRecord(featureTags[i], featureTable);
         }
-        return featureRecords;
+        return new FeatureListTable(featureCount, featureRecords);
     }
 
-    FeatureTable readFeatureTable(TTFDataStream data, long offset) throws IOException
+    private FeatureTable readFeatureTable(TTFDataStream data, long offset) throws IOException
     {
         data.seek(offset);
-        FeatureTable featureTable = new FeatureTable();
-        @SuppressWarnings({"unused", "squid:S1854"})
         int featureParams = data.readUnsignedShort();
         int lookupIndexCount = data.readUnsignedShort();
-        featureTable.lookupListIndices = new int[lookupIndexCount];
+        int[] lookupListIndices = new int[lookupIndexCount];
         for (int i = 0; i < lookupIndexCount; i++)
         {
-            featureTable.lookupListIndices[i] = data.readUnsignedShort();
+            lookupListIndices[i] = data.readUnsignedShort();
         }
-        return featureTable;
+        return new FeatureTable(featureParams, lookupIndexCount, lookupListIndices);
     }
 
-    LookupTable[] readLookupList(TTFDataStream data, long offset) throws IOException
+    private LookupListTable readLookupList(TTFDataStream data, long offset) throws IOException
     {
         data.seek(offset);
         int lookupCount = data.readUnsignedShort();
@@ -202,42 +230,55 @@ public class GlyphSubstitutionTable exte
         {
             lookupTables[i] = readLookupTable(data, offset + lookups[i]);
         }
-        return lookupTables;
+        return new LookupListTable(lookupCount, lookupTables);
     }
 
-    LookupTable readLookupTable(TTFDataStream data, long offset) throws IOException
+    private LookupTable readLookupTable(TTFDataStream data, long offset) throws IOException
     {
         data.seek(offset);
-        LookupTable lookupTable = new LookupTable();
-        lookupTable.lookupType = data.readUnsignedShort();
-        lookupTable.lookupFlag = data.readUnsignedShort();
+        int lookupType = data.readUnsignedShort();
+        int lookupFlag = data.readUnsignedShort();
         int subTableCount = data.readUnsignedShort();
         int[] subTableOffets = new int[subTableCount];
         for (int i = 0; i < subTableCount; i++)
         {
             subTableOffets[i] = data.readUnsignedShort();
         }
-        if ((lookupTable.lookupFlag & 0x0010) != 0)
+
+        int markFilteringSet;
+        if ((lookupFlag & 0x0010) != 0)
+        {
+            markFilteringSet = data.readUnsignedShort();
+        }
+        else
         {
-            lookupTable.markFilteringSet = data.readUnsignedShort();
+            markFilteringSet = 0;
         }
-        lookupTable.subTables = new LookupSubTable[subTableCount];
-        switch (lookupTable.lookupType)
+        LookupSubTable[] subTables = new LookupSubTable[subTableCount];
+        switch (lookupType)
         {
         case 1: // Single
             for (int i = 0; i < subTableCount; i++)
             {
-                lookupTable.subTables[i] = readLookupSubTable(data, offset + subTableOffets[i]);
+                subTables[i] = readLookupSubTable(data, offset + subTableOffets[i]);
+            }
+            break;
+        case 4: // Ligature Substitution Subtable
+            for (int i = 0; i < subTableCount; i++)
+            {
+                subTables[i] = readLigatureSubstitutionSubtable(data,
+                        offset + subTableOffets[i]);
             }
             break;
         default:
             // Other lookup types are not supported
-            LOG.debug("Type " + lookupTable.lookupType + " GSUB lookup table is not supported and will be ignored");
+            LOG.debug("Type " + lookupType
+                    + " GSUB lookup table is not supported and will be ignored");
         }
-        return lookupTable;
+        return new LookupTable(lookupType, lookupFlag, markFilteringSet, subTables);
     }
 
-    LookupSubTable readLookupSubTable(TTFDataStream data, long offset) throws IOException
+    private LookupSubTable readLookupSubTable(TTFDataStream data, long offset) throws IOException
     {
         data.seek(offset);
         int substFormat = data.readUnsignedShort();
@@ -245,33 +286,122 @@ public class GlyphSubstitutionTable exte
         {
         case 1:
         {
-            LookupTypeSingleSubstFormat1 lookupSubTable = new LookupTypeSingleSubstFormat1();
-            lookupSubTable.substFormat = substFormat;
             int coverageOffset = data.readUnsignedShort();
-            lookupSubTable.deltaGlyphID = data.readSignedShort();
-            lookupSubTable.coverageTable = readCoverageTable(data, offset + coverageOffset);
-            return lookupSubTable;
+            short deltaGlyphID = data.readSignedShort();
+            CoverageTable coverageTable = readCoverageTable(data, offset + coverageOffset);
+            return new LookupTypeSingleSubstFormat1(substFormat, coverageTable, deltaGlyphID);
         }
         case 2:
         {
-            LookupTypeSingleSubstFormat2 lookupSubTable = new LookupTypeSingleSubstFormat2();
-            lookupSubTable.substFormat = substFormat;
             int coverageOffset = data.readUnsignedShort();
             int glyphCount = data.readUnsignedShort();
-            lookupSubTable.substituteGlyphIDs = new int[glyphCount];
+            int[] substituteGlyphIDs = new int[glyphCount];
             for (int i = 0; i < glyphCount; i++)
             {
-                lookupSubTable.substituteGlyphIDs[i] = data.readUnsignedShort();
+                substituteGlyphIDs[i] = data.readUnsignedShort();
             }
-            lookupSubTable.coverageTable = readCoverageTable(data, offset + coverageOffset);
-            return lookupSubTable;
+            CoverageTable coverageTable = readCoverageTable(data, offset + coverageOffset);
+            return new LookupTypeSingleSubstFormat2(substFormat, coverageTable, substituteGlyphIDs);
         }
         default:
             throw new IllegalArgumentException("Unknown substFormat: " + substFormat);
         }
     }
 
-    CoverageTable readCoverageTable(TTFDataStream data, long offset) throws IOException
+    private LookupSubTable readLigatureSubstitutionSubtable(TTFDataStream data, long offset)
+            throws IOException
+    {
+        data.seek(offset);
+        int substFormat = data.readUnsignedShort();
+
+        if (substFormat != 1)
+        {
+            throw new IllegalArgumentException(
+                    "The expected SubstFormat for LigatureSubstitutionTable is 1");
+        }
+
+        int coverage = data.readUnsignedShort();
+        int ligSetCount = data.readUnsignedShort();
+
+        int[] ligatureOffsets = new int[ligSetCount];
+
+        for (int i = 0; i < ligSetCount; i++)
+        {
+            ligatureOffsets[i] = data.readUnsignedShort();
+        }
+
+        CoverageTable coverageTable = readCoverageTable(data, offset + coverage);
+
+        if (ligSetCount != coverageTable.getSize())
+        {
+            throw new IllegalArgumentException(
+                    "According to the OpenTypeFont specifications, the coverage count should be equal to the no. of LigatureSetTables");
+        }
+
+        LigatureSetTable[] ligatureSetTables = new LigatureSetTable[ligSetCount];
+
+        for (int i = 0; i < ligSetCount; i++)
+        {
+
+            int coverageGlyphId = coverageTable.getGlyphId(i);
+
+            ligatureSetTables[i] = readLigatureSetTable(data,
+                    offset + ligatureOffsets[i], coverageGlyphId);
+        }
+
+        return new LookupTypeLigatureSubstitutionSubstFormat1(substFormat, coverageTable,
+                ligatureSetTables);
+    }
+
+    private LigatureSetTable readLigatureSetTable(TTFDataStream data, long ligatureSetTableLocation,
+            int coverageGlyphId) throws IOException
+    {
+        data.seek(ligatureSetTableLocation);
+
+        int ligatureCount = data.readUnsignedShort();
+        LOG.debug("ligatureCount=" + ligatureCount);
+
+        int[] ligatureOffsets = new int[ligatureCount];
+        LigatureTable[] ligatureTables = new LigatureTable[ligatureCount];
+
+        for (int i = 0; i < ligatureOffsets.length; i++)
+        {
+            ligatureOffsets[i] = data.readUnsignedShort();
+        }
+
+        for (int i = 0; i < ligatureOffsets.length; i++)
+        {
+            int ligatureOffset = ligatureOffsets[i];
+            ligatureTables[i] = readLigatureTable(data,
+                    ligatureSetTableLocation + ligatureOffset, coverageGlyphId);
+        }
+
+        return new LigatureSetTable(ligatureCount, ligatureTables);
+    }
+
+    private LigatureTable readLigatureTable(TTFDataStream data, long ligatureTableLocation,
+            int coverageGlyphId) throws IOException
+    {
+        data.seek(ligatureTableLocation);
+
+        int ligatureGlyph = data.readUnsignedShort();
+
+        int componentCount = data.readUnsignedShort();
+
+        int[] componentGlyphIDs = new int[componentCount];
+
+        componentGlyphIDs[0] = coverageGlyphId;
+
+        for (int i = 1; i <= componentCount - 1; i++)
+        {
+            componentGlyphIDs[i] = data.readUnsignedShort();
+        }
+
+        return new LigatureTable(ligatureGlyph, componentCount, componentGlyphIDs);
+
+    }
+
+    private CoverageTable readCoverageTable(TTFDataStream data, long offset) throws IOException
     {
         data.seek(offset);
         int coverageFormat = data.readUnsignedShort();
@@ -279,28 +409,26 @@ public class GlyphSubstitutionTable exte
         {
         case 1:
         {
-            CoverageTableFormat1 coverageTable = new CoverageTableFormat1();
-            coverageTable.coverageFormat = coverageFormat;
             int glyphCount = data.readUnsignedShort();
-            coverageTable.glyphArray = new int[glyphCount];
+            int[] glyphArray = new int[glyphCount];
             for (int i = 0; i < glyphCount; i++)
             {
-                coverageTable.glyphArray[i] = data.readUnsignedShort();
+                glyphArray[i] = data.readUnsignedShort();
             }
-            return coverageTable;
+            return new CoverageTableFormat1(coverageFormat, glyphArray);
         }
         case 2:
         {
-            CoverageTableFormat2 coverageTable = new CoverageTableFormat2();
-            coverageTable.coverageFormat = coverageFormat;
             int rangeCount = data.readUnsignedShort();
-            coverageTable.rangeRecords = new RangeRecord[rangeCount];
+            RangeRecord[] rangeRecords = new RangeRecord[rangeCount];
+
+
             for (int i = 0; i < rangeCount; i++)
             {
-                coverageTable.rangeRecords[i] = readRangeRecord(data);
+                rangeRecords[i] = readRangeRecord(data);
             }
-            return coverageTable;
 
+            return new CoverageTableFormat2(coverageFormat, rangeRecords);
         }
         default:
             // Should not happen (the spec indicates only format 1 and format 2)
@@ -308,10 +436,9 @@ public class GlyphSubstitutionTable exte
         }
     }
 
-
     /**
-     * Choose from one of the supplied OpenType script tags, depending on what the font supports and
-     * potentially on context.
+     * Choose from one of the supplied OpenType script tags, depending on what the font supports and potentially on
+     * context.
      *
      * @param tags
      * @return The best OpenType script tag
@@ -354,14 +481,14 @@ public class GlyphSubstitutionTable exte
         ScriptTable scriptTable = scriptList.get(scriptTag);
         if (scriptTable != null)
         {
-            if (scriptTable.defaultLangSysTable == null)
+            if (scriptTable.getDefaultLangSysTable() == null)
             {
-                result = scriptTable.langSysTables.values();
+                result = scriptTable.getLangSysTables().values();
             }
             else
             {
-                result = new ArrayList<>(scriptTable.langSysTables.values());
-                result.add(scriptTable.defaultLangSysTable);
+                result = new ArrayList<>(scriptTable.getLangSysTables().values());
+                result.add(scriptTable.getDefaultLangSysTable());
             }
         }
         return result;
@@ -390,17 +517,18 @@ public class GlyphSubstitutionTable exte
         List<FeatureRecord> result = new ArrayList<>();
         for (LangSysTable langSysTable : langSysTables)
         {
-            int required = langSysTable.requiredFeatureIndex;
+            int required = langSysTable.getRequiredFeatureIndex();
             if (required != 0xffff) // if no required features = 0xFFFF
             {
-                result.add(featureList[required]);
+                result.add(featureListTable.getFeatureRecords()[required]);
             }
-            for (int featureIndex : langSysTable.featureIndices)
+            for (int featureIndex : langSysTable.getFeatureIndices())
             {
                 if (enabledFeatures == null
-                        || enabledFeatures.contains(featureList[featureIndex].featureTag))
+                        || enabledFeatures.contains(
+                                featureListTable.getFeatureRecords()[featureIndex].getFeatureTag()))
                 {
-                    result.add(featureList[featureIndex]);
+                    result.add(featureListTable.getFeatureRecords()[featureIndex]);
                 }
             }
         }
@@ -419,8 +547,8 @@ public class GlyphSubstitutionTable exte
                 @Override
                 public int compare(FeatureRecord o1, FeatureRecord o2)
                 {
-                    return Integer.compare(enabledFeatures.indexOf(o1.featureTag),
-                            enabledFeatures.indexOf(o2.featureTag));
+                    return Integer.compare(enabledFeatures.indexOf(o1.getFeatureTag()),
+                            enabledFeatures.indexOf(o2.getFeatureTag()));
                 }
             });
         }
@@ -432,7 +560,7 @@ public class GlyphSubstitutionTable exte
     {
         for (FeatureRecord featureRecord : featureRecords)
         {
-            if (featureRecord.featureTag.equals(featureTag))
+            if (featureRecord.getFeatureTag().equals(featureTag))
             {
                 return true;
             }
@@ -445,7 +573,7 @@ public class GlyphSubstitutionTable exte
         Iterator<FeatureRecord> iter = featureRecords.iterator();
         while (iter.hasNext())
         {
-            if (iter.next().featureTag.equals(featureTag))
+            if (iter.next().getFeatureTag().equals(featureTag))
             {
                 iter.remove();
             }
@@ -455,13 +583,14 @@ public class GlyphSubstitutionTable exte
     private int applyFeature(FeatureRecord featureRecord, int gid)
     {
         int lookupResult = gid;
-        for (int lookupListIndex : featureRecord.featureTable.lookupListIndices)
+        for (int lookupListIndex : featureRecord.getFeatureTable().getLookupListIndices())
         {
-            LookupTable lookupTable = lookupList[lookupListIndex];
-            if (lookupTable.lookupType != 1)
+            LookupTable lookupTable = lookupListTable.getLookups()[lookupListIndex];
+            if (lookupTable.getLookupType() != 1)
             {
-                LOG.debug("Skipping GSUB feature '" + featureRecord.featureTag
-                        + "' because it requires unsupported lookup table type " + lookupTable.lookupType);
+                LOG.debug("Skipping GSUB feature '" + featureRecord.getFeatureTag()
+                        + "' because it requires unsupported lookup table type "
+                        + lookupTable.getLookupType());
                 continue;
             }
             lookupResult = doLookup(lookupTable, lookupResult);
@@ -471,9 +600,9 @@ public class GlyphSubstitutionTable exte
 
     private int doLookup(LookupTable lookupTable, int gid)
     {
-        for (LookupSubTable lookupSubtable : lookupTable.subTables)
+        for (LookupSubTable lookupSubtable : lookupTable.getSubTables())
         {
-            int coverageIndex = lookupSubtable.coverageTable.getCoverageIndex(gid);
+            int coverageIndex = lookupSubtable.getCoverageTable().getCoverageIndex(gid);
             if (coverageIndex >= 0)
             {
                 return lookupSubtable.doSubstitution(gid, coverageIndex);
@@ -542,210 +671,17 @@ public class GlyphSubstitutionTable exte
         return gid;
     }
 
-    RangeRecord readRangeRecord(TTFDataStream data) throws IOException
-    {
-        RangeRecord rangeRecord = new RangeRecord();
-        rangeRecord.startGlyphID = data.readUnsignedShort();
-        rangeRecord.endGlyphID = data.readUnsignedShort();
-        rangeRecord.startCoverageIndex = data.readUnsignedShort();
-        return rangeRecord;
-    }
-
-    static class ScriptRecord
-    {
-        // https://www.microsoft.com/typography/otspec/scripttags.htm
-        String scriptTag;
-        ScriptTable scriptTable;
-
-        @Override
-        public String toString()
-        {
-            return String.format("ScriptRecord[scriptTag=%s]", scriptTag);
-        }
-    }
-
-    static class ScriptTable
+    public Map<String, Map<List<Integer>, Integer>> getRawGSubData()
     {
-        LangSysTable defaultLangSysTable;
-        LinkedHashMap<String, LangSysTable> langSysTables;
-
-        @Override
-        public String toString()
-        {
-            return String.format("ScriptTable[hasDefault=%s,langSysRecordsCount=%d]",
-                    defaultLangSysTable != null, langSysTables.size());
-        }
+        return rawGSubData;
     }
 
-    static class LangSysRecord
+    private RangeRecord readRangeRecord(TTFDataStream data) throws IOException
     {
-        // https://www.microsoft.com/typography/otspec/languagetags.htm
-        String langSysTag;
-        LangSysTable langSysTable;
-
-        @Override
-        public String toString()
-        {
-            return String.format("LangSysRecord[langSysTag=%s]", langSysTag);
-        }
+        int startGlyphID = data.readUnsignedShort();
+        int endGlyphID = data.readUnsignedShort();
+        int startCoverageIndex = data.readUnsignedShort();
+        return new RangeRecord(startGlyphID, endGlyphID, startCoverageIndex);
     }
 
-    static class LangSysTable
-    {
-        int requiredFeatureIndex;
-        int[] featureIndices;
-
-        @Override
-        public String toString()
-        {
-            return String.format("LangSysTable[requiredFeatureIndex=%d]", requiredFeatureIndex);
-        }
-    }
-
-    static class FeatureRecord
-    {
-        String featureTag;
-        FeatureTable featureTable;
-
-        @Override
-        public String toString()
-        {
-            return String.format("FeatureRecord[featureTag=%s]", featureTag);
-        }
-    }
-
-    static class FeatureTable
-    {
-        int[] lookupListIndices;
-
-        @Override
-        public String toString()
-        {
-            return String.format("FeatureTable[lookupListIndiciesCount=%d]",
-                    lookupListIndices.length);
-        }
-    }
-
-    static class LookupTable
-    {
-        int lookupType;
-        int lookupFlag;
-        int markFilteringSet;
-        LookupSubTable[] subTables;
-
-        @Override
-        public String toString()
-        {
-            return String.format("LookupTable[lookupType=%d,lookupFlag=%d,markFilteringSet=%d]",
-                    lookupType, lookupFlag, markFilteringSet);
-        }
-    }
-
-    static abstract class LookupSubTable
-    {
-        int substFormat;
-        CoverageTable coverageTable;
-
-        abstract int doSubstitution(int gid, int coverageIndex);
-    }
-
-    static class LookupTypeSingleSubstFormat1 extends LookupSubTable
-    {
-        short deltaGlyphID;
-
-        @Override
-        int doSubstitution(int gid, int coverageIndex)
-        {
-            return coverageIndex < 0 ? gid : gid + deltaGlyphID;
-        }
-
-        @Override
-        public String toString()
-        {
-            return String.format("LookupTypeSingleSubstFormat1[substFormat=%d,deltaGlyphID=%d]",
-                    substFormat, deltaGlyphID);
-        }
-    }
-
-    static class LookupTypeSingleSubstFormat2 extends LookupSubTable
-    {
-        int[] substituteGlyphIDs;
-
-        @Override
-        int doSubstitution(int gid, int coverageIndex)
-        {
-            return coverageIndex < 0 ? gid : substituteGlyphIDs[coverageIndex];
-        }
-
-        @Override
-        public String toString()
-        {
-            return String.format(
-                    "LookupTypeSingleSubstFormat2[substFormat=%d,substituteGlyphIDs=%s]",
-                    substFormat, Arrays.toString(substituteGlyphIDs));
-        }
-    }
-
-    static abstract class CoverageTable
-    {
-        int coverageFormat;
-
-        abstract int getCoverageIndex(int gid);
-    }
-
-    static class CoverageTableFormat1 extends CoverageTable
-    {
-        int[] glyphArray;
-
-        @Override
-        int getCoverageIndex(int gid)
-        {
-            return Arrays.binarySearch(glyphArray, gid);
-        }
-
-        @Override
-        public String toString()
-        {
-            return String.format("CoverageTableFormat1[coverageFormat=%d,glyphArray=%s]",
-                    coverageFormat, Arrays.toString(glyphArray));
-        }
-    }
-
-    static class CoverageTableFormat2 extends CoverageTable
-    {
-        RangeRecord[] rangeRecords;
-
-        @Override
-        int getCoverageIndex(int gid)
-        {
-            for (RangeRecord rangeRecord : rangeRecords)
-            {
-                if (rangeRecord.startGlyphID <= gid && gid <= rangeRecord.endGlyphID)
-                {
-                    return rangeRecord.startCoverageIndex + gid - rangeRecord.startGlyphID;
-                }
-            }
-            return -1;
-        }
-
-        @Override
-        public String toString()
-        {
-            return String.format("CoverageTableFormat2[coverageFormat=%d]", coverageFormat);
-        }
-    }
-
-    static class RangeRecord
-    {
-        int startGlyphID;
-        int endGlyphID;
-        int startCoverageIndex;
-
-        @Override
-        public String toString()
-        {
-            return String.format("RangeRecord[startGlyphID=%d,endGlyphID=%d,startCoverageIndex=%d]",
-                    startGlyphID, endGlyphID, startCoverageIndex);
-        }
-    }
 }

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java?rev=1829710&r1=1829709&r2=1829710&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TTFSubsetter.java Sat Apr 21 14:17:29 2018
@@ -1128,4 +1128,10 @@ public final class TTFSubsetter
     {
         return (int)Math.round(Math.log(num) / Math.log(2));
     }
+
+    public void addGlyphIds(Set<Integer> allGlyphIds)
+    {
+        glyphIds.addAll(allGlyphIds);
+    }
+
 }

Modified: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java?rev=1829710&r1=1829709&r2=1829710&view=diff
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java (original)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java Sat Apr 21 14:17:29 2018
@@ -565,7 +565,7 @@ public class TrueTypeFont implements Fon
             GlyphSubstitutionTable table = getGsub();
             if (table != null)
             {
-                return new SubstitutingCmapLookup(cmap, (GlyphSubstitutionTable) table,
+                return new SubstitutingCmapLookup(cmap, table,
                         Collections.unmodifiableList(enabledGsubFeatures));
             }
         }
@@ -654,6 +654,17 @@ public class TrueTypeFont implements Fon
         return 0;
     }
 
+    public Map<String, Map<List<Integer>, Integer>> getGlyphSubstitutionMap() throws IOException
+    {
+        GlyphSubstitutionTable table = getGsub();
+        if (table == null)
+        {
+            return Collections.emptyMap();
+        }
+
+        return table.getRawGSubData();
+    }
+
     /**
      * Parses a Unicode PostScript name in the format uniXXXX.
      */

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,102 @@
+/*
+ * 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.fontbox.ttf.gsub;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Takes in the given text having compound-glyphs to substitute, and splits it into chunks consisting of parts that
+ * should be substituted and the ones that can be processed normally.
+ * 
+ * @author Palash Ray
+ * 
+ */
+public class CompoundCharacterTokenizer
+{
+
+    private final Pattern regexExpression;
+
+    public CompoundCharacterTokenizer(Set<String> compoundWords)
+    {
+        regexExpression = Pattern.compile(getRegexFromTokens(compoundWords));
+    }
+
+    public CompoundCharacterTokenizer(String singleRegex)
+    {
+        regexExpression = Pattern.compile(singleRegex);
+    }
+
+    public List<String> tokenize(String text)
+    {
+        List<String> tokens = new ArrayList<String>();
+
+        Matcher regexMatcher = regexExpression.matcher(text);
+
+        int lastIndexOfPrevMatch = 0;
+
+        while (regexMatcher.find())
+        {
+
+            int beginIndexOfNextMatch = regexMatcher.start();
+
+            String prevToken = text.substring(lastIndexOfPrevMatch, beginIndexOfNextMatch);
+
+            if (prevToken.length() > 0)
+            {
+                tokens.add(prevToken);
+            }
+
+            String currentMatch = regexMatcher.group();
+
+            tokens.add(currentMatch);
+
+            lastIndexOfPrevMatch = regexMatcher.end();
+
+        }
+
+        String tail = text.substring(lastIndexOfPrevMatch, text.length());
+
+        if (tail.length() > 0)
+        {
+            tokens.add(tail);
+        }
+
+        return tokens;
+    }
+
+    private String getRegexFromTokens(Set<String> compoundWords)
+    {
+        StringBuilder sb = new StringBuilder();
+
+        for (String compoundWord : compoundWords)
+        {
+            sb.append("(");
+            sb.append(compoundWord);
+            sb.append(")|");
+        }
+
+        sb.setLength(sb.length() - 1);
+
+        return sb.toString();
+    }
+
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitter.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitter.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitter.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,33 @@
+/*
+ * 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.fontbox.ttf.gsub;
+
+import java.util.List;
+
+/**
+ * This class splits an array of GlyphIds with a prospective match.
+ * 
+ * @author Palash Ray
+ *
+ */
+public interface GlyphArraySplitter
+{
+
+    List<List<Integer>> split(List<Integer> glyphIds);
+
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,95 @@
+/*
+ * 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.fontbox.ttf.gsub;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This is an in-efficient implementation based on regex, which helps split the array.
+ * 
+ * @author Palash Ray
+ *
+ */
+public class GlyphArraySplitterRegexImpl implements GlyphArraySplitter
+{
+    private static final String GLYPH_ID_SEPARATOR = "_";
+
+    private final CompoundCharacterTokenizer compoundCharacterTokenizer;
+
+    public GlyphArraySplitterRegexImpl(Set<List<Integer>> matchers)
+    {
+        compoundCharacterTokenizer = new CompoundCharacterTokenizer(getMatchersAsStrings(matchers));
+    }
+
+    @Override
+    public List<List<Integer>> split(List<Integer> glyphIds)
+    {
+        String originalGlyphsAsText = convertGlyphIdsToString(glyphIds);
+        List<String> tokens = compoundCharacterTokenizer.tokenize(originalGlyphsAsText);
+
+        List<List<Integer>> modifiedGlyphs = new ArrayList<>();
+
+        for (String token : tokens)
+        {
+            modifiedGlyphs.add(convertGlyphIdsToList(token));
+        }
+
+        return modifiedGlyphs;
+    }
+
+    private Set<String> getMatchersAsStrings(Set<List<Integer>> matchers)
+    {
+        Set<String> stringMatchers = new HashSet<>(matchers.size());
+        for (List<Integer> glyphIds : matchers)
+        {
+            stringMatchers.add(convertGlyphIdsToString(glyphIds));
+        }
+        return stringMatchers;
+    }
+
+    private String convertGlyphIdsToString(List<Integer> glyphIds)
+    {
+        StringBuilder sb = new StringBuilder(20);
+        sb.append(GLYPH_ID_SEPARATOR);
+        for (Integer glyphId : glyphIds)
+        {
+            sb.append(glyphId).append(GLYPH_ID_SEPARATOR);
+        }
+        return sb.toString();
+    }
+
+    private List<Integer> convertGlyphIdsToList(String glyphIdsAsString)
+    {
+        List<Integer> gsubProcessedGlyphsIds = new ArrayList<>();
+
+        for (String glyphId : glyphIdsAsString.split(GLYPH_ID_SEPARATOR))
+        {
+            if (glyphId.trim().length() == 0)
+            {
+                continue;
+            }
+            gsubProcessedGlyphsIds.add(Integer.valueOf(glyphId));
+        }
+
+        return gsubProcessedGlyphsIds;
+    }
+
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,235 @@
+/*
+ * 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.fontbox.ttf.gsub;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.ttf.table.common.CoverageTable;
+import org.apache.fontbox.ttf.table.common.FeatureListTable;
+import org.apache.fontbox.ttf.table.common.FeatureRecord;
+import org.apache.fontbox.ttf.table.common.LangSysTable;
+import org.apache.fontbox.ttf.table.common.LookupListTable;
+import org.apache.fontbox.ttf.table.common.LookupSubTable;
+import org.apache.fontbox.ttf.table.common.LookupTable;
+import org.apache.fontbox.ttf.table.common.ScriptTable;
+import org.apache.fontbox.ttf.table.gsub.LigatureSetTable;
+import org.apache.fontbox.ttf.table.gsub.LigatureTable;
+import org.apache.fontbox.ttf.table.gsub.LookupTypeLigatureSubstitutionSubstFormat1;
+import org.apache.fontbox.ttf.table.gsub.LookupTypeSingleSubstFormat1;
+import org.apache.fontbox.ttf.table.gsub.LookupTypeSingleSubstFormat2;
+
+/**
+ * This class has utility methods to extract meaningful data from the highly obfuscated GSUB Tables. This data is then
+ * used to determine which combination of Glyphs or words have to be replaced.
+ * 
+ * @author Palash Ray
+ * 
+ */
+public class GlyphSubstitutionDataExtractor
+{
+
+    private static final Log LOG = LogFactory.getLog(GlyphSubstitutionDataExtractor.class);
+
+    private static final String[] SUPPORTED_LANGUAGES = { "bng2", "beng" };
+
+    public Map<String, Map<List<Integer>, Integer>> getGsubData(Map<String, ScriptTable> scriptList,
+            FeatureListTable featureListTable, LookupListTable lookupListTable)
+    {
+
+        ScriptTable scriptTable = getSupportedLanguage(scriptList);
+
+        if (scriptTable == null)
+        {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Map<List<Integer>, Integer>> gsubData = new LinkedHashMap<>();
+        // the starting point is really the scriptTags
+        if (scriptTable.getDefaultLangSysTable() != null)
+        {
+            populateGsubData(gsubData, scriptTable.getDefaultLangSysTable(), featureListTable,
+                    lookupListTable);
+        }
+        for (LangSysTable langSysTable : scriptTable.getLangSysTables().values())
+        {
+            populateGsubData(gsubData, langSysTable, featureListTable, lookupListTable);
+        }
+        return Collections.unmodifiableMap(gsubData);
+    }
+
+    private ScriptTable getSupportedLanguage(Map<String, ScriptTable> scriptList)
+    {
+        for (String supportedLanguage : SUPPORTED_LANGUAGES)
+        {
+            if (scriptList.containsKey(supportedLanguage))
+            {
+                return scriptList.get(supportedLanguage);
+            }
+        }
+        return null;
+    }
+
+    private void populateGsubData(Map<String, Map<List<Integer>, Integer>> gsubData,
+            LangSysTable langSysTable, FeatureListTable featureListTable,
+            LookupListTable lookupListTable)
+    {
+        for (int featureIndex : langSysTable.getFeatureIndices())
+        {
+            FeatureRecord featureRecord = featureListTable.getFeatureRecords()[featureIndex];
+            populateGsubData(gsubData, featureRecord, lookupListTable);
+        }
+    }
+
+    private void populateGsubData(Map<String, Map<List<Integer>, Integer>> gsubData,
+            FeatureRecord featureRecord, LookupListTable lookupListTable)
+    {
+
+        LOG.debug("*********** extracting GSUB data for the feature: "
+                + featureRecord.getFeatureTag());
+
+        Map<List<Integer>, Integer> glyphSubstitutionMap = new LinkedHashMap<>();
+        for (int lookupIndex : featureRecord.getFeatureTable().getLookupListIndices())
+        {
+            LookupTable lookupTable = lookupListTable.getLookups()[lookupIndex];
+            extractData(glyphSubstitutionMap, lookupTable);
+        }
+        gsubData.put(featureRecord.getFeatureTag(),
+                Collections.unmodifiableMap(glyphSubstitutionMap));
+    }
+
+    private void extractData(Map<List<Integer>, Integer> glyphSubstitutionMap,
+            LookupTable lookupTable)
+    {
+
+        for (LookupSubTable lookupSubTable : lookupTable.getSubTables())
+        {
+            if (lookupSubTable instanceof LookupTypeLigatureSubstitutionSubstFormat1)
+            {
+                extractDataFromLigatureSubstitutionSubstFormat1Table(glyphSubstitutionMap,
+                        (LookupTypeLigatureSubstitutionSubstFormat1) lookupSubTable);
+            }
+            else if (lookupSubTable instanceof LookupTypeSingleSubstFormat1)
+            {
+                extractDataFromSingleSubstTableFormat1Table(glyphSubstitutionMap,
+                        (LookupTypeSingleSubstFormat1) lookupSubTable);
+            }
+            else if (lookupSubTable instanceof LookupTypeSingleSubstFormat2)
+            {
+                extractDataFromSingleSubstTableFormat2Table(glyphSubstitutionMap,
+                        (LookupTypeSingleSubstFormat2) lookupSubTable);
+            }
+            else
+            {
+                LOG.warn("The type " + lookupSubTable + " is not yet supported, will be ignored");
+            }
+        }
+
+    }
+
+    private void extractDataFromSingleSubstTableFormat1Table(
+            Map<List<Integer>, Integer> glyphSubstitutionMap,
+            LookupTypeSingleSubstFormat1 singleSubstTableFormat1)
+    {
+        CoverageTable coverageTable = singleSubstTableFormat1.getCoverageTable();
+        for (int i = 0; i < coverageTable.getSize(); i++)
+        {
+            int coverageGlyphId = coverageTable.getGlyphId(i);
+            int substituteGlyphId = coverageGlyphId + singleSubstTableFormat1.getDeltaGlyphID();
+            putNewSubstitutionEntry(glyphSubstitutionMap, substituteGlyphId,
+                    Arrays.asList(coverageGlyphId));
+        }
+    }
+
+    private void extractDataFromSingleSubstTableFormat2Table(
+            Map<List<Integer>, Integer> glyphSubstitutionMap,
+            LookupTypeSingleSubstFormat2 singleSubstTableFormat2)
+    {
+
+        CoverageTable coverageTable = singleSubstTableFormat2.getCoverageTable();
+
+        if (coverageTable.getSize() != singleSubstTableFormat2.getSubstituteGlyphIDs().length)
+        {
+            throw new IllegalArgumentException(
+                    "The no. coverage table entries should be the same as the size of the substituteGlyphIDs");
+        }
+
+        for (int i = 0; i < coverageTable.getSize(); i++)
+        {
+            int coverageGlyphId = coverageTable.getGlyphId(i);
+            int substituteGlyphId = coverageGlyphId
+                    + singleSubstTableFormat2.getSubstituteGlyphIDs()[i];
+            putNewSubstitutionEntry(glyphSubstitutionMap, substituteGlyphId,
+                    Arrays.asList(coverageGlyphId));
+        }
+    }
+
+    private void extractDataFromLigatureSubstitutionSubstFormat1Table(
+            Map<List<Integer>, Integer> glyphSubstitutionMap,
+            LookupTypeLigatureSubstitutionSubstFormat1 ligatureSubstitutionTable)
+    {
+
+        for (LigatureSetTable ligatureSetTable : ligatureSubstitutionTable.getLigatureSetTables())
+        {
+            for (LigatureTable ligatureTable : ligatureSetTable.getLigatureTables())
+            {
+                extractDataFromLigatureTable(glyphSubstitutionMap, ligatureTable);
+            }
+
+        }
+
+    }
+
+    private void extractDataFromLigatureTable(Map<List<Integer>, Integer> glyphSubstitutionMap,
+            LigatureTable ligatureTable)
+    {
+
+        List<Integer> glyphsToBeSubstituted = new ArrayList<>();
+
+        for (int componentGlyphID : ligatureTable.getComponentGlyphIDs())
+        {
+            glyphsToBeSubstituted.add(componentGlyphID);
+        }
+
+        LOG.debug("glyphsToBeSubstituted: " + glyphsToBeSubstituted);
+
+        putNewSubstitutionEntry(glyphSubstitutionMap, ligatureTable.getLigatureGlyph(),
+                glyphsToBeSubstituted);
+
+    }
+
+    private void putNewSubstitutionEntry(Map<List<Integer>, Integer> glyphSubstitutionMap,
+            int newGlyph, List<Integer> glyphsToBeSubstituted)
+    {
+        Integer oldValue = glyphSubstitutionMap.put(glyphsToBeSubstituted, newGlyph);
+
+        if (oldValue != null)
+        {
+            String message = "For the newGlyph: " + newGlyph + ", newValue: "
+                    + glyphsToBeSubstituted + " is trying to override the oldValue: " + oldValue;
+            LOG.warn(message);
+        }
+    }
+
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorker.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorker.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorker.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorker.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,35 @@
+/*
+ * 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.fontbox.ttf.gsub;
+
+import java.util.List;
+
+/**
+ * This class is responsible for replacing GlyphIDs with new ones according to the GSUB tables. Each language should
+ * have an implementation of this.
+ * 
+ * @author Palash Ray
+ * 
+ */
+public interface GsubWorker
+{
+    List<Integer> substituteGlyphs(List<Integer> originalGlyphIds);
+
+    List<Integer> repositionGlyphs(List<Integer> originalGlyphIds);
+
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorker.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForBengali.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForBengali.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForBengali.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForBengali.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,148 @@
+/*
+ * 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.fontbox.ttf.gsub;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.ttf.CmapLookup;
+
+/**
+ * 
+ * Bengali-specific implementation of GSUB system
+ * 
+ * @author Palash Ray
+ *
+ */
+public class GsubWorkerForBengali implements GsubWorker
+{
+
+    private static final Log LOG = LogFactory.getLog(GsubWorkerForBengali.class);
+
+    /**
+     * This sequence is very important. This has been taken from <a href=
+     * "https://docs.microsoft.com/en-us/typography/script-development/bengali">https://docs.microsoft.com/en-us/typography/script-development/bengali</a>
+     */
+    private static final List<String> FEATURES_IN_ORDER = Arrays.asList("locl", "nukt", "akhn",
+            "rphf", "blwf", "half", "pstf", "vatu", "cjct", "init", "pres", "abvs", "blws", "psts",
+            "haln", "calt");
+
+    private static final char[] BEFORE_HALF_CHARS = new char[] { '\u09BF', '\u09C7', '\u09C8' };
+
+    private final Map<String, Map<List<Integer>, Integer>> glyphSubstitutionMap;
+
+    private final List<Integer> beforeHalfGlyphIds;
+
+    public GsubWorkerForBengali(CmapLookup cmapLookup,
+            Map<String, Map<List<Integer>, Integer>> glyphSubstitutionMap)
+    {
+        this.glyphSubstitutionMap = glyphSubstitutionMap;
+        beforeHalfGlyphIds = getBeforeHalfGlyphIds(cmapLookup);
+    }
+
+    @Override
+    public List<Integer> substituteGlyphs(List<Integer> originalGlyphIds)
+    {
+        List<Integer> intermediateGlyphsFromGsub = originalGlyphIds;
+
+        for (String feature : FEATURES_IN_ORDER)
+        {
+            if (!glyphSubstitutionMap.containsKey(feature))
+            {
+                LOG.debug("the feature " + feature + " was not found");
+                continue;
+            }
+
+            LOG.debug("applying the feature " + feature);
+
+            Map<List<Integer>, Integer> featureMap = glyphSubstitutionMap.get(feature);
+
+            intermediateGlyphsFromGsub = applyGsubFeature(featureMap, intermediateGlyphsFromGsub);
+        }
+
+        return intermediateGlyphsFromGsub;
+    }
+
+    @Override
+    public List<Integer> repositionGlyphs(List<Integer> originalGlyphIds)
+    {
+        List<Integer> repositionedGlyphIds = new ArrayList<>(originalGlyphIds);
+
+        for (int index = 1; index < originalGlyphIds.size(); index++)
+        {
+            int glyphId = originalGlyphIds.get(index);
+            if (beforeHalfGlyphIds.contains(glyphId))
+            {
+                int previousGlyphId = originalGlyphIds.get(index - 1);
+                repositionedGlyphIds.set(index, previousGlyphId);
+                repositionedGlyphIds.set(index - 1, glyphId);
+            }
+        }
+        return repositionedGlyphIds;
+    }
+
+    private List<Integer> applyGsubFeature(Map<List<Integer>, Integer> featureMap,
+            List<Integer> originalGlyphs)
+    {
+
+        GlyphArraySplitter glyphArraySplitter = new GlyphArraySplitterRegexImpl(
+                featureMap.keySet());
+
+        List<List<Integer>> tokens = glyphArraySplitter.split(originalGlyphs);
+
+        List<Integer> gsubProcessedGlyphs = new ArrayList<>();
+
+        for (List<Integer> chunk : tokens)
+        {
+            if (featureMap.containsKey(chunk))
+            {
+                // gsub system kicks in, you get the glyphId directly
+                int glyphId = featureMap.get(chunk);
+                gsubProcessedGlyphs.add(glyphId);
+            }
+            else
+            {
+                gsubProcessedGlyphs.addAll(chunk);
+            }
+        }
+
+        LOG.debug("originalGlyphs: " + originalGlyphs + ", gsubProcessedGlyphs: "
+                + gsubProcessedGlyphs);
+
+        return gsubProcessedGlyphs;
+    }
+
+    private static List<Integer> getBeforeHalfGlyphIds(CmapLookup cmapLookup)
+    {
+        List<Integer> beforeHalfGlyphIds = new ArrayList<>();
+
+        for (char beforeHalfChar : BEFORE_HALF_CHARS)
+        {
+            beforeHalfGlyphIds.add(cmapLookup.getGlyphId(beforeHalfChar));
+        }
+
+        return Collections.unmodifiableList(beforeHalfGlyphIds);
+
+    }
+
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForBengali.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/package.html
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/package.html?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/package.html (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/package.html Sat Apr 21 14:17:29 2018
@@ -0,0 +1,25 @@
+<!--
+ ! 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.
+ !-->
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+<body>
+This package contains utility classes which extract meanigful data from the highly obfuscated <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/gsub">GSUB table structures</a>.
+</body>
+</html>

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/package.html
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTable.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTable.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTable.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,48 @@
+/*
+ * 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.fontbox.ttf.table.common;
+
+/**
+ * This class models the
+ * <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table">Coverage Table</a> in the
+ * Open Type layout common tables.
+ * 
+ * @author Palash Ray
+ *
+ */
+public abstract class CoverageTable
+{
+    private final int coverageFormat;
+
+    public CoverageTable(int coverageFormat)
+    {
+        this.coverageFormat = coverageFormat;
+    }
+
+    public abstract int getCoverageIndex(int gid);
+
+    public abstract int getGlyphId(int index);
+
+    public abstract int getSize();
+
+    public int getCoverageFormat()
+    {
+        return coverageFormat;
+    }
+
+}
\ No newline at end of file

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTable.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat1.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat1.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat1.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat1.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,72 @@
+/*
+ * 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.fontbox.ttf.table.common;
+
+import java.util.Arrays;
+
+/**
+ * This class models the
+ * <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-1">Coverage format 1</a>
+ * in the Open Type layout common tables.
+ * 
+ * @author Palash Ray
+ *
+ */
+public class CoverageTableFormat1 extends CoverageTable
+{
+
+    private final int[] glyphArray;
+
+    public CoverageTableFormat1(int coverageFormat, int[] glyphArray)
+    {
+        super(coverageFormat);
+        this.glyphArray = glyphArray;
+    }
+
+    @Override
+    public int getCoverageIndex(int gid)
+    {
+        return Arrays.binarySearch(glyphArray, gid);
+    }
+
+    @Override
+    public int getGlyphId(int index)
+    {
+        return glyphArray[index];
+    }
+
+    @Override
+    public int getSize()
+    {
+        return glyphArray.length;
+    }
+
+    public int[] getGlyphArray()
+    {
+        return glyphArray;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("CoverageTableFormat1[coverageFormat=%d,glyphArray=%s]",
+                getCoverageFormat(), Arrays.toString(glyphArray));
+    }
+
+
+}
\ No newline at end of file

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat1.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat2.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat2.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat2.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat2.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,75 @@
+/*
+ * 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.fontbox.ttf.table.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class models the
+ * <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2">Coverage format 2</a>
+ * in the Open Type layout common tables.
+ * 
+ * @author Palash Ray
+ *
+ */
+public class CoverageTableFormat2 extends CoverageTableFormat1
+{
+    private final RangeRecord[] rangeRecords;
+
+    public CoverageTableFormat2(int coverageFormat, RangeRecord[] rangeRecords)
+    {
+        super(coverageFormat, getRangeRecordsAsArray(rangeRecords));
+        this.rangeRecords = rangeRecords;
+    }
+
+    public RangeRecord[] getRangeRecords()
+    {
+        return rangeRecords;
+    }
+
+    private static int[] getRangeRecordsAsArray(RangeRecord[] rangeRecords)
+    {
+
+        List<Integer> glyphIds = new ArrayList<>();
+
+        for (int i = 0; i < rangeRecords.length; i++)
+        {
+            for (int glyphId = rangeRecords[i].getStartGlyphID(); glyphId <= rangeRecords[i]
+                    .getEndGlyphID(); glyphId++)
+            {
+                glyphIds.add(glyphId);
+            }
+        }
+
+        int[] glyphArray = new int[glyphIds.size()];
+
+        for (int i = 0; i < glyphArray.length; i++)
+        {
+            glyphArray[i] = glyphIds.get(i);
+        }
+
+        return glyphArray;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("CoverageTableFormat2[coverageFormat=%d]", getCoverageFormat());
+    }
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/CoverageTableFormat2.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/FeatureListTable.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/FeatureListTable.java?rev=1829710&view=auto
==============================================================================
--- pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/FeatureListTable.java (added)
+++ pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/FeatureListTable.java Sat Apr 21 14:17:29 2018
@@ -0,0 +1,56 @@
+/*
+ * 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.fontbox.ttf.table.common;
+
+/**
+ * This class models the
+ * <a href="https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#feature-list-table">Feature List
+ * table</a> in the Open Type layout common tables.
+ * 
+ * @author Palash Ray
+ *
+ */
+public class FeatureListTable
+{
+    private final int featureCount;
+    private final FeatureRecord[] featureRecords;
+
+    public FeatureListTable(int featureCount, FeatureRecord[] featureRecords)
+    {
+        this.featureCount = featureCount;
+        this.featureRecords = featureRecords;
+    }
+
+    public int getFeatureCount()
+    {
+        return featureCount;
+    }
+
+    public FeatureRecord[] getFeatureRecords()
+    {
+        return featureRecords;
+    }
+
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s[featureCount=%d]", FeatureListTable.class.getSimpleName(),
+                featureCount);
+    }
+}

Propchange: pdfbox/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/table/common/FeatureListTable.java
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message