aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorRobert Meyer <rmeyer@apache.org>2014-02-10 16:21:03 +0000
committerRobert Meyer <rmeyer@apache.org>2014-02-10 16:21:03 +0000
commit785ace252f252ff211330e93635631931d68b890 (patch)
tree9a7924d1e35d867a0606f24f348285b3cd1b83ba /test
parent9dc9aff9a9cef7e5696b42eb8d396944d1c40881 (diff)
downloadxmlgraphics-fop-785ace252f252ff211330e93635631931d68b890.tar.gz
xmlgraphics-fop-785ace252f252ff211330e93635631931d68b890.zip
FOP-2252: OpenType CFF - Remove need for patch to fontbox
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1566674 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'test')
-rw-r--r--test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java291
1 files changed, 262 insertions, 29 deletions
diff --git a/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java b/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java
index c555ebc20..8c92b5f63 100644
--- a/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java
+++ b/test/java/org/apache/fop/fonts/truetype/OTFSubSetFileTestCase.java
@@ -20,6 +20,8 @@
package org.apache.fop.fonts.truetype;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -27,18 +29,15 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import org.apache.fontbox.cff.CFFDataInput;
import org.apache.fontbox.cff.CFFFont;
-import org.apache.fontbox.cff.CFFParser;
-import org.apache.fontbox.cff.IndexData;
-import org.apache.fontbox.cff.Type2CharStringParser;
import org.apache.fop.fonts.cff.CFFDataReader;
import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData;
import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry;
+import org.apache.fop.fonts.truetype.OTFSubSetFile.BytesNumber;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
public class OTFSubSetFileTestCase extends OTFFileTestCase {
@@ -47,10 +46,6 @@ public class OTFSubSetFileTestCase extends OTFFileTestCase {
private byte[] sourceSansData;
CFFDataReader cffReaderHeitiStd;
- public OTFSubSetFileTestCase() throws IOException {
- super();
- }
-
/**
* Initialises the test by creating the font subset. A CFFDataReader is
* also created based on the subset data for use in the tests.
@@ -80,9 +75,14 @@ public class OTFSubSetFileTestCase extends OTFFileTestCase {
public void testCharStringIndex() throws IOException {
assertEquals(256, cffReaderSourceSans.getCharStringIndex().getNumObjects());
assertTrue(checkCorrectOffsets(cffReaderSourceSans.getCharStringIndex()));
- validateCharStrings(cffReaderSourceSans);
+ validateCharStrings(cffReaderSourceSans, sourceSansSubset.getCFFReader());
}
+ /**
+ * Checks the index data to ensure that the offsets are valid
+ * @param indexData The index data to check
+ * @return Returns true if it is found to be valid
+ */
private boolean checkCorrectOffsets(CFFIndexData indexData) {
int last = 0;
for (int i = 0; i < indexData.getOffsets().length; i++) {
@@ -94,35 +94,268 @@ public class OTFSubSetFileTestCase extends OTFFileTestCase {
return true;
}
- private void validateCharStrings(CFFDataReader cffReader) throws IOException {
+ /**
+ * Validates the subset font CharString data by comparing it with the original.
+ * @param subsetCFF The subset CFFDataReader containing the CharString data
+ * @param origCFF The original CFFDataReader containing the CharString data
+ * @throws IOException
+ */
+ private void validateCharStrings(CFFDataReader subsetCFF, CFFDataReader origCFF)
+ throws IOException {
CFFFont sourceSansOriginal = sourceSansProBold.fileFont;
+ CFFIndexData charStrings = subsetCFF.getCharStringIndex();
Map<String, byte[]> origCharStringData = sourceSansOriginal.getCharStringsDict();
- IndexData origGlobalIndex = sourceSansOriginal.getGlobalSubrIndex();
- IndexData origLocalIndex = sourceSansOriginal.getLocalSubrIndex();
+ for (int i = 0; i < charStrings.getNumObjects(); i++) {
+ byte[] origCharData = origCharStringData.get(origCharStringData.keySet().toArray(
+ new String[0])[i]);
+ byte[] charData = charStrings.getValue(i);
+ List<BytesNumber> origOperands = getFullCharString(origCharData, origCFF);
+ List<BytesNumber> subsetOperands = getFullCharString(charData, subsetCFF);
+ for (int j = 0;j < origOperands.size();j++) {
+ assertTrue(origOperands.get(j).equals(subsetOperands.get(j)));
+ }
+ }
+ }
+
+ /**
+ * Recursively reads and constructs the full CharString for comparison
+ * @param data The original byte data of the CharString
+ * @param cffData The CFFDataReader containing the subroutine indexes
+ * @return Returns a list of parsed operands and operators
+ * @throws IOException
+ */
+ private List<BytesNumber> getFullCharString(byte[] data, CFFDataReader cffData) throws IOException {
+ CFFIndexData localIndexSubr = cffData.getLocalIndexSubr();
+ CFFIndexData globalIndexSubr = cffData.getGlobalIndexSubr();
+ boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0;
+ boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0;
+ ArrayList<BytesNumber> operands = new ArrayList<BytesNumber>();
+ for (int dataPos = 0; dataPos < data.length; dataPos++) {
+ int b0 = data[dataPos] & 0xff;
+ if (b0 == 10 && hasLocalSubroutines) {
+ int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(),
+ operands.get(operands.size() - 1).getNumber());
+ byte[] subr = localIndexSubr.getValue(subrNumber);
+ List<BytesNumber> subrOperands = getFullCharString(subr, cffData);
+ operands = mergeOperands(operands, subrOperands);
+ } else if (b0 == 29 && hasGlobalSubroutines) {
+ int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(),
+ operands.get(operands.size() - 1).getNumber());
+ byte[] subr = globalIndexSubr.getValue(subrNumber);
+ ArrayList<BytesNumber> subrOperands = (ArrayList<BytesNumber>)getFullCharString(subr, cffData);
+ operands = mergeOperands(operands, subrOperands);
+ } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) {
+ int size = 1;
+ int b1 = -1;
+ if (b0 == 12) {
+ b1 = data[dataPos++] & 0xff;
+ size = 2;
+ }
+ if (b0 == 19 || b0 == 20) {
+ dataPos += 1;
+ size = 2;
+ }
+ operands.add(new Operator(b0, size, getOperatorName(b0, b1)));
+ } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) {
+ operands.add(readNumber(b0, data, dataPos));
+ dataPos += operands.get(operands.size() - 1).getNumBytes() - 1;
+ }
+ }
+ return operands;
+ }
+
+ /**
+ * Merges two lists of operands. This is typically used to merge the CharString
+ * data with that of a parsed and referenced subroutine.
+ * @param charString The parsed CharString data so far
+ * @param subroutine The parsed elements from a subroutine
+ * @return Returns a merged list of both CharString and subroutine elements.
+ */
+ private ArrayList<BytesNumber> mergeOperands(List<BytesNumber> charString,
+ List<BytesNumber> subroutine) {
+ BytesNumber[] charStringOperands = charString.toArray(new BytesNumber[0]);
+ BytesNumber[] subroutineOperands = subroutine.toArray(new BytesNumber[0]);
+ BytesNumber[] mergeData = new BytesNumber[charStringOperands.length - 1 +
+ subroutineOperands.length - 1];
+ System.arraycopy(charStringOperands, 0, mergeData, 0, charStringOperands.length - 1);
+ System.arraycopy(subroutineOperands, 0, mergeData, charStringOperands.length - 1,
+ subroutineOperands.length - 1);
+ ArrayList<BytesNumber> hello = new ArrayList<BytesNumber>();
+ hello.addAll(Arrays.asList(mergeData));
+ return hello;
+ }
- CFFDataInput globalSubrs = new CFFDataInput(cffReader.getGlobalIndexSubr().getByteData());
- CFFDataInput localSubrs = new CFFDataInput(cffReader.getLocalIndexSubr().getByteData());
+ /**
+ * Parses a number from one or more bytes
+ * @param b0 The first byte to identify how to interpret the number
+ * @param input The original byte data containing the number
+ * @param curPos The current position of the number
+ * @return Returns the number
+ * @throws IOException
+ */
+ private BytesNumber readNumber(int b0, byte[] input, int curPos) throws IOException {
+ if (b0 == 28) {
+ int b1 = input[curPos + 1] & 0xff;
+ int b2 = input[curPos + 2] & 0xff;
+ return new BytesNumber(Integer.valueOf((short) (b1 << 8 | b2)), 3);
+ } else if (b0 >= 32 && b0 <= 246) {
+ return new BytesNumber(Integer.valueOf(b0 - 139), 1);
+ } else if (b0 >= 247 && b0 <= 250) {
+ int b1 = input[curPos + 1] & 0xff;
+ return new BytesNumber(Integer.valueOf((b0 - 247) * 256 + b1 + 108), 2);
+ } else if (b0 >= 251 && b0 <= 254) {
+ int b1 = input[curPos + 1] & 0xff;
+ return new BytesNumber(Integer.valueOf(-(b0 - 251) * 256 - b1 - 108), 2);
+ } else if (b0 == 255) {
+ int b1 = input[curPos + 1] & 0xff;
+ int b2 = input[curPos + 2] & 0xff;
+ return new BytesNumber(Integer.valueOf((short)(b1 << 8 | b2)), 5);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
- IndexData globalIndexData = CFFParser.readIndexData(globalSubrs);
- IndexData localIndexData = CFFParser.readIndexData(localSubrs);
+ /**
+ * Gets the subroutine number according to the number of subroutines
+ * and the provided operand.
+ * @param numSubroutines The number of subroutines used to calculate the
+ * subroutine reference.
+ * @param operand The operand for the subroutine
+ * @return Returns the calculated subroutine number
+ */
+ private int getSubrNumber(int numSubroutines, int operand) {
+ int bias = getBias(numSubroutines);
+ return bias + operand;
+ }
- CFFIndexData charStrings = cffReader.getCharStringIndex();
- for (int i = 0; i < charStrings.getNumObjects(); i++) {
- byte[] charData = charStrings.getValue(i);
- Type2CharStringParser parser = new Type2CharStringParser();
+ /**
+ * Gets the bias give the number of subroutines. This is used in the
+ * calculation to determine a subroutine's number
+ * @param subrCount The number of subroutines for a given index
+ * @return Returns the bias value
+ */
+ private int getBias(int subrCount) {
+ if (subrCount < 1240) {
+ return 107;
+ } else if (subrCount < 33900) {
+ return 1131;
+ } else {
+ return 32768;
+ }
+ }
- byte[] origCharData = origCharStringData.get(origCharStringData.keySet().toArray(
- new String[0])[i]);
- List<Object> origSeq = parser.parse(origCharData, origGlobalIndex, origLocalIndex);
+ /**
+ * A class representing an operator from the CharString data
+ */
+ private class Operator extends BytesNumber {
+ private String opName = "";
- List<Object> subsetSeq = parser.parse(charData, globalIndexData, localIndexData);
+ public Operator(int number, int numBytes, String opName) {
+ super(number, numBytes);
+ this.opName = opName;
+ }
+ public String toString() {
+ return String.format("[%s]", opName);
+ }
+ }
- //Validates the subset glyph render routines against the originals
- assertEquals(origSeq, subsetSeq);
+ /**
+ * Gets the identifying name for the given operator. This is primarily
+ * used for debugging purposes. See the Type 2 CharString Format specification
+ * document (Technical Note #5177) Appendix A (Command Codes).
+ * @param operator The operator code
+ * @param codeb The second byte of the operator
+ * @return Returns the operator name.
+ */
+ private String getOperatorName(int operator, int operatorB) {
+ switch (operator) {
+ case 0: return "Reserved";
+ case 1: return "hstem";
+ case 2: return "Reserved";
+ case 3: return "vstem";
+ case 4: return "vmoveto";
+ case 5: return "rlineto";
+ case 6: return "hlineto";
+ case 7: return "vlineto";
+ case 8: return "rrcurveto";
+ case 9: return "Reserved";
+ case 10: return "callsubr";
+ case 11: return "return";
+ case 12: return getDoubleOpName(operatorB);
+ case 13: return "Reserved";
+ case 14: return "enchar";
+ case 15:
+ case 16:
+ case 17: return "Reserved";
+ case 18: return "hstemhm";
+ case 19: return "hintmask";
+ case 20: return "cntrmask";
+ case 21: return "rmoveto";
+ case 22: return "hmoveto";
+ case 23: return "vstemhm";
+ case 24: return "rcurveline";
+ case 25: return "rlinecurve";
+ case 26: return "vvcurveto";
+ case 27: return "hhcurveto";
+ case 28: return "shortint";
+ case 29: return "callgsubr";
+ case 30: return "vhcurveto";
+ case 31: return "hvcurveto";
+ default: return "Unknown";
}
}
/**
+ * Gets the name of a double byte operator code
+ * @param operator The second byte of the operator
+ * @return Returns the name
+ */
+ private String getDoubleOpName(int operator) {
+ switch (operator) {
+ case 0:
+ case 1:
+ case 2: return "Reserved";
+ case 3: return "and";
+ case 4: return "or";
+ case 5: return "not";
+ case 6:
+ case 7:
+ case 8: return "Reserved";
+ case 9: return "abs";
+ case 10: return "add";
+ case 11: return "sub";
+ case 12: return "div";
+ case 13: return "Reserved";
+ case 14: return "neg";
+ case 15: return "eq";
+ case 16:
+ case 17: return "Reserved";
+ case 18: return "drop";
+ case 19: return "Reserved";
+ case 20: return "put";
+ case 21: return "get";
+ case 22: return "ifelse";
+ case 23: return "random";
+ case 24: return "mul";
+ case 25: return "Reserved";
+ case 26: return "sqrt";
+ case 27: return "dup";
+ case 28: return "exch";
+ case 29: return "index";
+ case 30: return "roll";
+ case 31:
+ case 32:
+ case 33: return "Reserved";
+ case 34: return "hflex";
+ case 35: return "flex";
+ case 36: return "hflex1";
+ case 37: return "flex1";
+ case 38: return "Reserved";
+ default: return "Unknown";
+ }
+ }
+
+ /**
* Validates the String index data and size
* @throws IOException
*/