aboutsummaryrefslogtreecommitdiffstats
path: root/test/java/org/apache
diff options
context:
space:
mode:
authorRobert Meyer <rmeyer@apache.org>2014-05-23 15:05:19 +0000
committerRobert Meyer <rmeyer@apache.org>2014-05-23 15:05:19 +0000
commit764eba0b31076d7220082ac167f18cc75c427e27 (patch)
tree3e209ccb9e5e1a376b94e9e5ea29a811b45e3129 /test/java/org/apache
parentc4e5885007f3c61837a12ab330658b131d978247 (diff)
downloadxmlgraphics-fop-764eba0b31076d7220082ac167f18cc75c427e27.tar.gz
xmlgraphics-fop-764eba0b31076d7220082ac167f18cc75c427e27.zip
FOP-2354: Subset support for Type 1 fonts
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1597112 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'test/java/org/apache')
-rw-r--r--test/java/org/apache/fop/fonts/type1/PostscriptParserTestCase.java93
-rw-r--r--test/java/org/apache/fop/fonts/type1/Type1SubsetFileTestCase.java348
2 files changed, 441 insertions, 0 deletions
diff --git a/test/java/org/apache/fop/fonts/type1/PostscriptParserTestCase.java b/test/java/org/apache/fop/fonts/type1/PostscriptParserTestCase.java
new file mode 100644
index 000000000..8686dc048
--- /dev/null
+++ b/test/java/org/apache/fop/fonts/type1/PostscriptParserTestCase.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fonts.type1;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.fop.fonts.type1.PostscriptParser.PSDictionary;
+import org.apache.fop.fonts.type1.PostscriptParser.PSElement;
+import org.apache.fop.fonts.type1.PostscriptParser.PSFixedArray;
+import org.apache.fop.fonts.type1.PostscriptParser.PSSubroutine;
+import org.apache.fop.fonts.type1.PostscriptParser.PSVariable;
+import org.apache.fop.fonts.type1.PostscriptParser.PSVariableArray;
+
+public class PostscriptParserTestCase {
+ private PostscriptParser parser;
+ private String eol = new String(new byte[] {13});
+ private String postscriptElements =
+ "/myVariable 100 def" + eol
+ + "/-| {def} executeonly def" + eol
+ + "/myFixedArray 6 array" + eol
+ + "0 1 5 {1 index exch /.notdef put } for" + eol
+ + "dup 1 /a put" + eol
+ + "dup 2 /b put" + eol
+ + "dup 3 /c put" + eol
+ + "dup 4 /d put" + eol
+ + "readonly def" + eol
+ + "/myVariableArray [ { this } { is } { a } { test } ] no access def" + eol
+ + "/refVarSubr myValue -|";
+
+ @Before
+ public void setUp() {
+ parser = new PostscriptParser();
+ }
+
+ /**
+ * Tests parsing an example Postscript document and verifying what
+ * has been read.
+ * @throws IOException
+ */
+ @Test
+ public void testPostscriptParsing() throws IOException {
+ List<PSElement> elements = parser.parse(postscriptElements.getBytes());
+ assertEquals(elements.size(), 5);
+ assertTrue(elements.get(0) instanceof PSVariable);
+ assertTrue(elements.get(2) instanceof PSFixedArray);
+ assertTrue(elements.get(3) instanceof PSVariableArray);
+ PSFixedArray fixedArray = (PSFixedArray)elements.get(2);
+ assertEquals(fixedArray.getEntries().size(), 4);
+ assertEquals(fixedArray.getEntries().get(2), "dup 2 /b put ");
+ PSVariableArray variableArray = (PSVariableArray)elements.get(3);
+ assertEquals(variableArray.getEntries().size(), 4);
+ /* Currently only variable arrays containing subroutines are supported, though
+ * this can be modified to support single values and also strip out unnecessary
+ * characters like the { } below. */
+ assertEquals(variableArray.getEntries().get(0).trim(), "{ this }");
+ }
+
+ /**
+ * Tests that the correct element is returned given the operator and element ID provided
+ */
+ @Test
+ public void testCreateElement() {
+ assertTrue(parser.createElement("/custDictionary", "dict", -1) instanceof PSDictionary);
+ assertEquals(parser.createElement("/Private", "dict", -1), null);
+ assertTrue(parser.createElement("/aFixedArray", "array", -1) instanceof PSFixedArray);
+ assertTrue(parser.createElement("/aVariableArray", "[", -1) instanceof PSVariableArray);
+ assertTrue(parser.createElement("/aSubroutine", "{", -1) instanceof PSSubroutine);
+ }
+}
diff --git a/test/java/org/apache/fop/fonts/type1/Type1SubsetFileTestCase.java b/test/java/org/apache/fop/fonts/type1/Type1SubsetFileTestCase.java
new file mode 100644
index 000000000..e18173a6b
--- /dev/null
+++ b/test/java/org/apache/fop/fonts/type1/Type1SubsetFileTestCase.java
@@ -0,0 +1,348 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.fonts.type1;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.xmlgraphics.fonts.Glyphs;
+
+import org.apache.fop.fonts.SingleByteFont;
+import org.apache.fop.fonts.type1.PostscriptParser.PSDictionary;
+import org.apache.fop.fonts.type1.PostscriptParser.PSElement;
+import org.apache.fop.fonts.type1.PostscriptParser.PSFixedArray;
+import org.apache.fop.fonts.type1.Type1SubsetFile.BinaryCoder;
+import org.apache.fop.fonts.type1.Type1SubsetFile.BytesNumber;
+
+public class Type1SubsetFileTestCase {
+
+ private List<byte[]> decodedSections;
+ private static final String TEST_FONT_A = "./test/resources/fonts/type1/c0419bt_.pfb";
+
+ @Test
+ public void test() throws IOException {
+ InputStream in = new FileInputStream(TEST_FONT_A);
+ compareCharStringData(in, TEST_FONT_A, createFontASubset(in, TEST_FONT_A));
+ }
+
+ @Test
+ public void testStitchFont() throws IOException {
+ ByteArrayOutputStream baosHeader = new ByteArrayOutputStream();
+ ByteArrayOutputStream baosMain = new ByteArrayOutputStream();
+ ByteArrayOutputStream baosTrailer = new ByteArrayOutputStream();
+
+ //Header
+ for (int i = 0; i < 10; i++) {
+ baosHeader.write(123);
+ baosMain.write(123);
+ }
+ for (int i = 0; i < 10; i++) {
+ baosTrailer.write(0);
+ }
+
+ Type1SubsetFile subset = new Type1SubsetFile();
+ byte[] result = subset.stitchFont(baosHeader, baosMain, baosTrailer);
+ ByteArrayInputStream bais = new ByteArrayInputStream(result);
+ assertEquals(result.length, 50);
+ PFBParser parser = new PFBParser();
+ parser.parsePFB(bais);
+ }
+
+ @Test
+ public void testUpdateSectionSize() throws IOException {
+ Type1SubsetFile subset = new Type1SubsetFile();
+ ByteArrayOutputStream baos = subset.updateSectionSize(456);
+ byte[] lowOrderSize = baos.toByteArray();
+ assertEquals(lowOrderSize[0], -56);
+ assertEquals(lowOrderSize[1], 1);
+ }
+
+ @Test
+ public void testVariableContents() {
+ Type1SubsetFile subset = new Type1SubsetFile();
+ String result = subset.readVariableContents("/myvariable {some variable contents}");
+ assertEquals(result, "some variable contents");
+ result = subset.readVariableContents("/myvariable {hello {some more text {test} and some more}test}");
+ //Should only reads one level deep
+ assertEquals(result, "hello test");
+ }
+
+ @Test
+ public void getOpPositionAndLength() {
+ Type1SubsetFile subset = new Type1SubsetFile();
+ ArrayList<BytesNumber> ops = new ArrayList<BytesNumber>();
+ ops.add(new BytesNumber(10, 1));
+ ops.add(new BytesNumber(255, 2));
+ ops.add(new BytesNumber(100, 1));
+ ops.add(new BytesNumber(97, 1));
+ ops.add(new BytesNumber(856, 2));
+ assertEquals(subset.getOpPosition(4, ops), 4);
+ assertEquals(subset.getOperandsLength(ops), 7);
+ }
+
+ @Test
+ public void testConcatArrays() {
+ byte[] arrayA = {(byte)1, (byte)2, (byte)3, (byte)4, (byte)5};
+ byte[] arrayB = {(byte)6, (byte)7, (byte)8, (byte)9, (byte)10};
+ Type1SubsetFile subset = new Type1SubsetFile();
+ byte[] concatArray = subset.concatArray(arrayA, arrayB);
+ assertEquals(concatArray.length, 10);
+ assertEquals(concatArray[5], 6);
+ assertEquals(concatArray[3], 4);
+ }
+
+ @Test
+ public void testGetBinaryEntry() {
+ byte[] decoded = {(byte)34, (byte)23, (byte)78, (byte)55, (byte)12,
+ (byte)2, (byte)65, (byte)49, (byte)90, (byte)10};
+ int[] section = {3, 7};
+ Type1SubsetFile subset = new Type1SubsetFile();
+ byte[] segment = subset.getBinaryEntry(section, decoded);
+ assertEquals(segment.length, 4);
+ assertEquals(segment[0], 55);
+ assertEquals(segment[3], 65);
+ }
+
+ private void compareCharStringData(InputStream in, String font, byte[] subsetFont)
+ throws IOException {
+ decodedSections = new ArrayList<byte[]>();
+
+ //Reinitialise the input stream as reset only supports 1000 bytes.
+ in = new FileInputStream(font);
+ List<PSElement> origElements = parseElements(in);
+ List<PSElement> subsetElements = parseElements(new ByteArrayInputStream(subsetFont));
+
+ PSFixedArray origSubs = (PSFixedArray)findElement(origElements, "/Subrs");
+ PSFixedArray subsetSubs = (PSFixedArray)findElement(subsetElements, "/Subrs");
+ PSDictionary origCharStrings = (PSDictionary)findElement(origElements, "/CharStrings");
+ PSDictionary subsetCharStrings = (PSDictionary)findElement(subsetElements, "/CharStrings");
+ for (String element : subsetCharStrings.getEntries().keySet()) {
+ if (element.equals("/.notdef")) {
+ continue;
+ }
+ int[] origBinaryCharLocation = origCharStrings.getBinaryEntries().get(element);
+ int[] subsetBinaryCharLocation = subsetCharStrings.getBinaryEntries().get(element);
+
+ int origLength = origBinaryCharLocation[1] - origBinaryCharLocation[0];
+ int subsetLength = subsetBinaryCharLocation[1] - subsetBinaryCharLocation[0];
+ byte[] origCharData = new byte[origLength];
+ byte[] subsetCharData = new byte[subsetLength];
+ System.arraycopy(decodedSections.get(0), origBinaryCharLocation[0], origCharData, 0, origLength);
+ System.arraycopy(decodedSections.get(1), subsetBinaryCharLocation[0], subsetCharData, 0, subsetLength);
+ origCharData = BinaryCoder.decodeBytes(origCharData, 4330, 4);
+ subsetCharData = BinaryCoder.decodeBytes(subsetCharData, 4330, 4);
+ byte[] origFullCharData = readFullCharString(decodedSections.get(0), origCharData, origSubs);
+ byte[] subsetFullCharData = readFullCharString(decodedSections.get(1), subsetCharData, subsetSubs);
+ assertArrayEquals(origFullCharData, subsetFullCharData);
+ }
+ }
+
+ private byte[] createFontASubset(InputStream in, String font) throws IOException {
+ SingleByteFont sbfont = mock(SingleByteFont.class);
+ //Glyph index & selector
+ Map<Integer, Integer> glyphs = new HashMap<Integer, Integer>();
+ //Selector & unicode
+ Map<Integer, Character> usedCharsIndex = new HashMap<Integer, Character>();
+ Map<Integer, String> usedCharNames = new HashMap<Integer, String>();
+ int count = 0;
+ for (int i = 32; i < 127; i++) {
+ glyphs.put(i, count++);
+ when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i);
+ usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i)));
+ when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i));
+ }
+ for (int i = 161; i < 204; i++) {
+ glyphs.put(i, count++);
+ usedCharsIndex.put(count, (char)i);
+ when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i);
+ usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i)));
+ when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i));
+ }
+ int[] randomGlyphs = {205, 206, 207, 208, 225, 227, 232, 233, 234, 235, 241, 245,
+ 248, 249, 250, 251
+ };
+ for (int i = 0; i < randomGlyphs.length; i++) {
+ glyphs.put(randomGlyphs[i], count++);
+ usedCharsIndex.put(count, (char)randomGlyphs[i]);
+ when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)randomGlyphs[i]);
+ usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i)));
+ when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i));
+ }
+ for (int i = 256; i < 335; i++) {
+ glyphs.put(i, count++);
+ usedCharsIndex.put(count, (char)i);
+ when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i);
+ usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i)));
+ when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i));
+ }
+ when(sbfont.getUsedGlyphNames()).thenReturn(usedCharNames);
+ when(sbfont.getUsedGlyphs()).thenReturn(glyphs);
+ when(sbfont.getEmbedFileURI()).thenReturn(URI.create(font));
+ Type1SubsetFile subset = new Type1SubsetFile();
+ return subset.createSubset(in, sbfont, "AAAAAA");
+ }
+
+ private List<PSElement> parseElements(InputStream in)
+ throws IOException {
+ PFBParser pfbParser = new PFBParser();
+ PFBData origData = pfbParser.parsePFB(in);
+ PostscriptParser parser = new PostscriptParser();
+ byte[] decoded = BinaryCoder.decodeBytes(origData.getEncryptedSegment(), 55665, 4);
+ decodedSections.add(decoded);
+ return parser.parse(decoded);
+ }
+
+ private PSElement findElement(List<PSElement> elements, String operator) {
+ for (PSElement element : elements) {
+ if (element.getOperator().equals(operator)) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ private byte[] readFullCharString(byte[] decoded, byte[] data, PSFixedArray subroutines) {
+ List<BytesNumber> operands = new ArrayList<BytesNumber>();
+ List<BytesNumber> fullList = new ArrayList<BytesNumber>();
+ for (int i = 0; i < data.length; i++) {
+ int cur = data[i] & 0xFF;
+ if (cur >= 0 && cur <= 31) {
+ //Found subroutine. Read subroutine, recursively scan and update references
+ if (cur == 10) {
+ if (operands.size() == 0) {
+ continue;
+ }
+ int[] subrData = subroutines.getBinaryEntryByIndex(operands.get(0).getNumber());
+ byte[] subroutine = getBinaryEntry(subrData, decoded);
+ subroutine = BinaryCoder.decodeBytes(subroutine, 4330, 4);
+ subroutine = readFullCharString(decoded, subroutine, subroutines);
+ data = replaceReference(data, subroutine, i - 1 + operands.get(0).getNumBytes(), i);
+ } else {
+ int next = -1;
+ if (cur == 12) {
+ next = data[++i] & 0xFF;
+ }
+ BytesNumber operand = new BytesNumber(cur, i);
+ operand.setName(getName(cur, next));
+ fullList.add(operand);
+ }
+ operands.clear();
+ }
+ if (cur >= 32 && cur <= 246) {
+ operands.add(new BytesNumber(cur - 139, 1));
+ fullList.add(operands.get(operands.size() - 1));
+ } else if (cur >= 247 && cur <= 250) {
+ operands.add(new BytesNumber((cur - 247) * 256 + (data[i + 1] & 0xFF) + 108, 2));
+ fullList.add(operands.get(operands.size() - 1));
+ i++;
+ } else if (cur >= 251 && cur <= 254) {
+ operands.add(new BytesNumber(-(cur - 251) * 256 - (data[i + 1] & 0xFF) - 108, 2));
+ fullList.add(operands.get(operands.size() - 1));
+ i++;
+ } else if (cur == 255) {
+ int b1 = data[i + 1] & 0xFF;
+ int b2 = data[i + 2] & 0xFF;
+ int b3 = data[i + 3] & 0xFF;
+ int b4 = data[i + 4] & 0xFF;
+ int value = b1 << 24 | b2 << 16 | b3 << 8 | b4;
+ operands.add(new BytesNumber(value, 5));
+ fullList.add(operands.get(operands.size() - 1));
+ i += 4;
+ }
+ }
+ return data;
+ }
+
+ private String getName(int operator, int next) {
+ switch (operator) {
+ case 14: return "endchar";
+ case 13: return "hsbw";
+ case 12:
+ switch (next) {
+ case 0: return "dotsection";
+ case 1: return "vstem3";
+ case 2: return "hstem3";
+ case 6: return "seac";
+ case 7: return "sbw";
+ case 16: return "callothersubr";
+ case 17: return "pop";
+ case 33: return "setcurrentpoint";
+ default: return "unknown";
+ }
+ case 9: return "closepath";
+ case 6: return "hlineto";
+ case 22: return "hmoveto";
+ case 31: return "hvcurveto";
+ case 5: return "rlineto";
+ case 21: return "rmoveto";
+ case 8: return "rrcurveto";
+ case 30: return "vhcurveto";
+ case 7: return "vlineto";
+ case 4: return "vmoveto";
+ case 1: return "hstem";
+ case 3: return "vstem";
+ case 10: return "callsubr";
+ case 11: return "return";
+ default: return "unknown";
+ }
+ }
+
+ private byte[] replaceReference(byte[] data, byte[] subroutine, int startRef, int endRef) {
+ byte[] preBytes = new byte[startRef - 1];
+ System.arraycopy(data, 0, preBytes, 0, startRef - 1);
+ byte[] postBytes = new byte[data.length - endRef - 1];
+ System.arraycopy(data, endRef + 1, postBytes, 0, data.length - endRef - 1);
+ data = concatArray(preBytes, subroutine, 1);
+ data = concatArray(data, postBytes, 0);
+ return data;
+ }
+
+ private byte[] getBinaryEntry(int[] position, byte[] decoded) {
+ int start = position[0];
+ int finish = position[1];
+ byte[] line = new byte[finish - start];
+ System.arraycopy(decoded, start, line, 0, finish - start);
+ return line;
+ }
+
+ private byte[] concatArray(byte[] a, byte[] b, int subtract) {
+ int aLen = a.length;
+ int bLen = b.length - subtract;
+ byte[] c = new byte[aLen + bLen];
+ System.arraycopy(a, 0, c, 0, aLen);
+ System.arraycopy(b, 0, c, aLen, bLen);
+ return c;
+ }
+}