com.itextpdf.text.pdf.fonts.otf.GlyphSubstitutionTableReader Maven / Gradle / Ivy
/*
* $Id: 8d63fe34c871ae5d1955b81ac7c5cd450a3ae3d5 $
*
* This file is part of the iText (R) project.
* Copyright (c) 1998-2016 iText Group NV
* Authors: Bruno Lowagie, Paulo Soares, et al.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA, 02110-1301 USA, or download the license from the following URL:
* http://itextpdf.com/terms-of-use/
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* In accordance with Section 7(b) of the GNU Affero General Public License,
* a covered work must retain the producer line in every PDF that is created
* or manipulated using iText.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the iText software without
* disclosing the source code of your own applications.
* These activities include: offering paid services to customers as an ASP,
* serving PDFs on the fly in a web application, shipping iText with a closed
* source product.
*
* For more information, please contact iText Software Corp. at this
* address: [email protected]
*/
package com.itextpdf.text.pdf.fonts.otf;
import java.io.IOException;
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 com.itextpdf.text.pdf.Glyph;
import com.itextpdf.text.pdf.RandomAccessFileOrArray;
/**
*
* Parses an OpenTypeFont file and reads the Glyph Substitution Table. This table governs how two or more Glyphs should be merged
* to a single Glyph. This is especially useful for Asian languages like Bangla, Hindi, etc.
*
*
* This has been written according to the OPenTypeFont specifications. This may be found here.
*
*
* @author Palash Ray
*/
public class GlyphSubstitutionTableReader extends OpenTypeFontTableReader {
private final int[] glyphWidthsByIndex;
private final Map glyphToCharacterMap;
private Map> rawLigatureSubstitutionMap;
public GlyphSubstitutionTableReader(RandomAccessFileOrArray rf, int gsubTableLocation,
Map glyphToCharacterMap, int[] glyphWidthsByIndex) throws IOException {
super(rf, gsubTableLocation);
this.glyphWidthsByIndex = glyphWidthsByIndex;
this.glyphToCharacterMap = glyphToCharacterMap;
}
public void read() throws FontReadingException {
rawLigatureSubstitutionMap = new LinkedHashMap>();
startReadingTable();
}
public Map getGlyphSubstitutionMap() throws FontReadingException {
Map glyphSubstitutionMap = new LinkedHashMap();
for (Integer glyphIdToReplace : rawLigatureSubstitutionMap.keySet()) {
List constituentGlyphs = rawLigatureSubstitutionMap.get(glyphIdToReplace);
StringBuilder chars = new StringBuilder(constituentGlyphs.size());
for (Integer constituentGlyphId : constituentGlyphs) {
chars.append(getTextFromGlyph(constituentGlyphId, glyphToCharacterMap));
}
Glyph glyph = new Glyph(glyphIdToReplace, glyphWidthsByIndex[glyphIdToReplace], chars.toString());
glyphSubstitutionMap.put(glyph.chars, glyph);
}
return Collections.unmodifiableMap(glyphSubstitutionMap);
}
private String getTextFromGlyph(int glyphId, Map glyphToCharacterMap) throws FontReadingException {
StringBuilder chars = new StringBuilder(1);
Character c = glyphToCharacterMap.get(glyphId);
if (c == null) {
// it means this represents a compound glyph
List constituentGlyphs = rawLigatureSubstitutionMap.get(glyphId);
if (constituentGlyphs == null || constituentGlyphs.isEmpty()) {
throw new FontReadingException("No corresponding character or simple glyphs found for GlyphID=" + glyphId);
}
for (int constituentGlyphId : constituentGlyphs) {
chars.append(getTextFromGlyph(constituentGlyphId, glyphToCharacterMap));
}
} else {
chars.append(c.charValue());
}
return chars.toString();
}
@Override
protected void readSubTable(int lookupType, int subTableLocation) throws IOException {
if (lookupType == 1) {
readSingleSubstitutionSubtable(subTableLocation);
} else if (lookupType == 4) {
readLigatureSubstitutionSubtable(subTableLocation);
} else {
System.err.println("LookupType " + lookupType + " is not yet handled for " + GlyphSubstitutionTableReader.class.getSimpleName());
}
}
/**
* LookupType 1: Single Substitution Subtable
*/
private void readSingleSubstitutionSubtable(int subTableLocation) throws IOException {
rf.seek(subTableLocation);
int substFormat = rf.readShort();
LOG.debug("substFormat=" + substFormat);
if (substFormat == 1) {
int coverage = rf.readShort();
LOG.debug("coverage=" + coverage);
int deltaGlyphID = rf.readShort();
LOG.debug("deltaGlyphID=" + deltaGlyphID);
List coverageGlyphIds = readCoverageFormat(subTableLocation + coverage);
for (int coverageGlyphId : coverageGlyphIds) {
int substituteGlyphId = coverageGlyphId + deltaGlyphID;
rawLigatureSubstitutionMap.put(substituteGlyphId, Arrays.asList(coverageGlyphId));
}
} else if (substFormat == 2) {
int coverage = rf.readShort();
LOG.debug("coverage=" + coverage);
int glyphCount = rf.readUnsignedShort();
int[] substitute = new int[glyphCount];
for (int k = 0; k < glyphCount; ++k) {
substitute[k] = rf.readUnsignedShort();
}
List coverageGlyphIds = readCoverageFormat(subTableLocation + coverage);
for (int k = 0; k < glyphCount; ++k) {
rawLigatureSubstitutionMap.put(substitute[k], Arrays.asList(coverageGlyphIds.get(k)));
}
} else {
throw new IllegalArgumentException("Bad substFormat: " + substFormat);
}
}
/**
* LookupType 4: Ligature Substitution Subtable
*/
private void readLigatureSubstitutionSubtable(int ligatureSubstitutionSubtableLocation) throws IOException {
rf.seek(ligatureSubstitutionSubtableLocation);
int substFormat = rf.readShort();
LOG.debug("substFormat=" + substFormat);
if (substFormat != 1) {
throw new IllegalArgumentException("The expected SubstFormat is 1");
}
int coverage = rf.readShort();
LOG.debug("coverage=" + coverage);
int ligSetCount = rf.readShort();
List ligatureOffsets = new ArrayList(ligSetCount);
for (int i = 0; i < ligSetCount; i++) {
int ligatureOffset = rf.readShort();
ligatureOffsets.add(ligatureOffset);
}
List coverageGlyphIds = readCoverageFormat(ligatureSubstitutionSubtableLocation + coverage);
if (ligSetCount != coverageGlyphIds.size()) {
throw new IllegalArgumentException("According to the OpenTypeFont specifications, the coverage count should be equal to the no. of LigatureSetTables");
}
for (int i = 0; i < ligSetCount; i++) {
int coverageGlyphId = coverageGlyphIds.get(i);
int ligatureOffset = ligatureOffsets.get(i);
LOG.debug("ligatureOffset=" + ligatureOffset);
readLigatureSetTable(ligatureSubstitutionSubtableLocation + ligatureOffset, coverageGlyphId);
}
}
private void readLigatureSetTable(int ligatureSetTableLocation, int coverageGlyphId) throws IOException {
rf.seek(ligatureSetTableLocation);
int ligatureCount = rf.readShort();
LOG.debug("ligatureCount=" + ligatureCount);
List ligatureOffsets = new ArrayList(ligatureCount);
for (int i = 0; i < ligatureCount; i++) {
int ligatureOffset = rf.readShort();
ligatureOffsets.add(ligatureOffset);
}
for (int ligatureOffset : ligatureOffsets) {
readLigatureTable(ligatureSetTableLocation + ligatureOffset, coverageGlyphId);
}
}
private void readLigatureTable(int ligatureTableLocation, int coverageGlyphId) throws IOException {
rf.seek(ligatureTableLocation);
int ligGlyph = rf.readShort();
LOG.debug("ligGlyph=" + ligGlyph);
int compCount = rf.readShort();
List glyphIdList = new ArrayList();
glyphIdList.add(coverageGlyphId);
for (int i = 0; i < compCount - 1; i++) {
int glyphId = rf.readShort();
glyphIdList.add(glyphId);
}
LOG.debug("glyphIdList=" + glyphIdList);
List previousValue = rawLigatureSubstitutionMap.put(ligGlyph, glyphIdList);
if (previousValue != null) {
LOG.warn("!!!!!!!!!!glyphId=" + ligGlyph
+ ",\npreviousValue=" + previousValue
+ ",\ncurrentVal=" + glyphIdList);
}
}
}