PDFBOX-5808: add support for GSUB lookup type 3, by Fabrice Calafat
git-svn-id: https://svn.apache.org/repos/asf/pdfbox/trunk@1917433 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java
index 7675b2c..7152c7c 100644
--- a/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphSubstitutionTable.java
@@ -46,8 +46,10 @@
import org.apache.fontbox.ttf.table.common.LookupTable;
import org.apache.fontbox.ttf.table.common.RangeRecord;
import org.apache.fontbox.ttf.table.common.ScriptTable;
+import org.apache.fontbox.ttf.table.gsub.AlternateSetTable;
import org.apache.fontbox.ttf.table.gsub.LigatureSetTable;
import org.apache.fontbox.ttf.table.gsub.LigatureTable;
+import org.apache.fontbox.ttf.table.gsub.LookupTypeAlternateSubstitutionFormat1;
import org.apache.fontbox.ttf.table.gsub.LookupTypeLigatureSubstitutionSubstFormat1;
import org.apache.fontbox.ttf.table.gsub.LookupTypeMultipleSubstitutionFormat1;
import org.apache.fontbox.ttf.table.gsub.LookupTypeSingleSubstFormat1;
@@ -308,6 +310,10 @@
// Multiple Substitution Subtable
// https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#lookuptype-2-multiple-substitution-subtable
return readMultipleSubstitutionSubtable(data, offset);
+ case 3:
+ // Alternate Substitution Subtable
+ // https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#lookuptype-3-alternate-substitution-subtable
+ return readAlternateSubstitutionSubtable(data, offset);
case 4:
// Ligature Substitution Subtable
// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#LS
@@ -365,6 +371,7 @@
{
case 1:
case 2:
+ case 3:
case 4:
for (int i = 0; i < subTableCount; i++)
{
@@ -482,6 +489,49 @@
return new LookupTypeMultipleSubstitutionFormat1(substFormat, coverageTable, sequenceTables);
}
+ private LookupSubTable readAlternateSubstitutionSubtable(TTFDataStream data, long offset) throws IOException
+ {
+ data.seek(offset);
+ int substFormat = data.readUnsignedShort();
+
+ if (substFormat != 1)
+ {
+ throw new IOException(
+ "The expected SubstFormat for AlternateSubstitutionTable is 1");
+ }
+
+ int coverage = data.readUnsignedShort();
+ int altSetCount = data.readUnsignedShort();
+
+ int[] alternateOffsets = new int[altSetCount];
+
+ for (int i = 0; i < altSetCount; i++)
+ {
+ alternateOffsets[i] = data.readUnsignedShort();
+ }
+
+ CoverageTable coverageTable = readCoverageTable(data, offset + coverage);
+
+ if (altSetCount != coverageTable.getSize())
+ {
+ throw new IOException(
+ "According to the OpenTypeFont specifications, the coverage count should be equal to the no. of AlternateSetTable");
+ }
+
+ AlternateSetTable[] alternateSetTables = new AlternateSetTable[altSetCount];
+
+ for (int i = 0; i < altSetCount; i++)
+ {
+ data.seek(offset + alternateOffsets[i]);
+ int glyphCount = data.readUnsignedShort();
+ int[] alternateGlyphIDs = data.readUnsignedShortArray(glyphCount);
+ alternateSetTables[i] = new AlternateSetTable(glyphCount, alternateGlyphIDs);
+ }
+
+ return new LookupTypeAlternateSubstitutionFormat1(substFormat, coverageTable,
+ alternateSetTables);
+ }
+
private LookupSubTable readLigatureSubstitutionSubtable(TTFDataStream data, long offset)
throws IOException
{
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java b/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
index 16609ff..8de1f24 100644
--- a/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/TrueTypeFont.java
@@ -47,6 +47,7 @@
private float version;
private int numberOfGlyphs = -1;
private int unitsPerEm = -1;
+ private boolean enableGsub = true;
protected final Map<String,TTFTable> tables = new HashMap<>();
private final TTFDataStream data;
private volatile Map<String, Integer> postScriptNames;
@@ -74,7 +75,7 @@
/**
* @return Returns the version.
*/
- public float getVersion()
+ public float getVersion()
{
return version;
}
@@ -87,7 +88,24 @@
{
version = versionValue;
}
-
+
+ /**
+ * @return Returns true if the GSUB table can be used for this font
+ */
+ public boolean isEnableGsub()
+ {
+ return enableGsub;
+ }
+
+ /**
+ * Enable or disable the GSUB table for this font.
+ * GSUB table is enabled by default.
+ */
+ public void setEnableGsub(boolean enableGsub)
+ {
+ this.enableGsub = enableGsub;
+ }
+
/**
* Add a table definition. Package-private, used by TTFParser only.
*
@@ -638,13 +656,17 @@
/**
* Returns the GSubData of the GlyphSubstitutionTable if present.
*
- * @return the GSubData of the GlyphSubstitutionTable or {@link GsubData#NO_DATA_FOUND} if either no GSUB data is
- * available or its scripts are not supported
- *
+ * @return the GSubData of the GlyphSubstitutionTable or {@link GsubData#NO_DATA_FOUND} if no GSUB data is
+ * available, its scripts are not supported or it was disabled for that font
* @throws IOException if the font data could not be read
*/
public GsubData getGsubData() throws IOException
{
+ if (!enableGsub)
+ {
+ return GsubData.NO_DATA_FOUND;
+ }
+
GlyphSubstitutionTable table = getGsub();
if (table == null)
{
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java
index 04f4565..2b051f4 100644
--- a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphSubstitutionDataExtractor.java
@@ -34,8 +34,10 @@
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.AlternateSetTable;
import org.apache.fontbox.ttf.table.gsub.LigatureSetTable;
import org.apache.fontbox.ttf.table.gsub.LigatureTable;
+import org.apache.fontbox.ttf.table.gsub.LookupTypeAlternateSubstitutionFormat1;
import org.apache.fontbox.ttf.table.gsub.LookupTypeLigatureSubstitutionSubstFormat1;
import org.apache.fontbox.ttf.table.gsub.LookupTypeMultipleSubstitutionFormat1;
import org.apache.fontbox.ttf.table.gsub.LookupTypeSingleSubstFormat1;
@@ -172,6 +174,11 @@
extractDataFromLigatureSubstitutionSubstFormat1Table(glyphSubstitutionMap,
(LookupTypeLigatureSubstitutionSubstFormat1) lookupSubTable);
}
+ else if (lookupSubTable instanceof LookupTypeAlternateSubstitutionFormat1)
+ {
+ extractDataFromAlternateSubstitutionSubstFormat1Table(glyphSubstitutionMap,
+ (LookupTypeAlternateSubstitutionFormat1) lookupSubTable);
+ }
else if (lookupSubTable instanceof LookupTypeSingleSubstFormat1)
{
extractDataFromSingleSubstTableFormat1Table(glyphSubstitutionMap,
@@ -277,6 +284,47 @@
}
+ /**
+ * Extracts data from the AlternateSubstitutionFormat1 (lookuptype) 3 table and puts it in the glyphSubstitutionMap
+ * This is added for Zola usage
+ *
+ * @param glyphSubstitutionMap the map to store the substitution data
+ * @param alternateSubstitutionFormat1 the alternate substitution format 1 table
+ */
+ private void extractDataFromAlternateSubstitutionSubstFormat1Table(
+ Map<List<Integer>, Integer> glyphSubstitutionMap,
+ LookupTypeAlternateSubstitutionFormat1 alternateSubstitutionFormat1)
+ {
+
+ CoverageTable coverageTable = alternateSubstitutionFormat1.getCoverageTable();
+
+ if (coverageTable.getSize() != alternateSubstitutionFormat1.getAlternateSetTables().length)
+ {
+ LOG.warn("The coverage table size (" + coverageTable.getSize() +
+ ") should be the same as the count of the atlternate set tables (" +
+ alternateSubstitutionFormat1.getAlternateSetTables().length + ")");
+ return;
+ }
+
+ for (int i = 0; i < coverageTable.getSize(); i++)
+ {
+ int coverageGlyphId = coverageTable.getGlyphId(i);
+ AlternateSetTable sequenceTable = alternateSubstitutionFormat1.getAlternateSetTables()[i];
+
+ // Loop through the substitute glyphs and pick the first one that is not the same as the coverage glyph
+ for (int alternateGlyphId : sequenceTable.getAlternateGlyphIDs())
+ {
+ if (alternateGlyphId != coverageGlyphId)
+ {
+ putNewSubstitutionEntry(glyphSubstitutionMap, alternateGlyphId,
+ Collections.singletonList(coverageGlyphId));
+ break;
+ }
+ }
+ }
+
+ }
+
private void extractDataFromLigatureTable(Map<List<Integer>, Integer> glyphSubstitutionMap,
LigatureTable ligatureTable)
{
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/AlternateSetTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/AlternateSetTable.java
new file mode 100644
index 0000000..0c23684
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/AlternateSetTable.java
@@ -0,0 +1,51 @@
+/*
+ * 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.gsub;
+
+import java.util.Arrays;
+
+/**
+ * LookupType 3: Alternate Substitution Subtable
+ * as described in OpenType spec: <a href="https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#31-alternate-substitution-format-1">...</a>
+ */
+public class AlternateSetTable
+{
+ private final int glyphCount;
+ private final int[] alternateGlyphIDs;
+
+ public AlternateSetTable(int glyphCount, int[] alternateGlyphIDs)
+ {
+ this.glyphCount = glyphCount;
+ this.alternateGlyphIDs = alternateGlyphIDs;
+ }
+
+ public int getGlyphCount()
+ {
+ return glyphCount;
+ }
+
+ public int[] getAlternateGlyphIDs()
+ {
+ return alternateGlyphIDs;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "AlternateSetTable{" + "glyphCount=" + glyphCount + ", alternateGlyphIDs=" + Arrays.toString(alternateGlyphIDs) + '}';
+ }
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/LookupTypeAlternateSubstitutionFormat1.java b/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/LookupTypeAlternateSubstitutionFormat1.java
new file mode 100644
index 0000000..d6eae17
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/table/gsub/LookupTypeAlternateSubstitutionFormat1.java
@@ -0,0 +1,43 @@
+/*
+ * 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.gsub;
+
+import org.apache.fontbox.ttf.table.common.CoverageTable;
+import org.apache.fontbox.ttf.table.common.LookupSubTable;
+
+/**
+ * Lookup Type 3: Alternate Substitution Subtable
+ * as described in OpenType spec: <a href="https://learn.microsoft.com/en-us/typography/opentype/spec/gsub#31-alternate-substitution-format-1">...</a>
+ */
+public class LookupTypeAlternateSubstitutionFormat1 extends LookupSubTable {
+ private final AlternateSetTable[] alternateSetTables;
+
+ public LookupTypeAlternateSubstitutionFormat1(
+ int substFormat, CoverageTable coverageTable, AlternateSetTable[] alternateSetTables) {
+ super(substFormat, coverageTable);
+ this.alternateSetTables = alternateSetTables;
+ }
+
+ public AlternateSetTable[] getAlternateSetTables() {
+ return alternateSetTables;
+ }
+
+ @Override
+ public int doSubstitution(int gid, int coverageIndex) {
+ throw new UnsupportedOperationException("not applicable");
+ }
+}