You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Type1SubsetFileTestCase.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.fonts.type1;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.FileInputStream;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.net.URI;
  25. import java.util.ArrayList;
  26. import java.util.HashMap;
  27. import java.util.List;
  28. import java.util.Map;
  29. import org.junit.Test;
  30. import static org.junit.Assert.assertArrayEquals;
  31. import static org.junit.Assert.assertEquals;
  32. import static org.mockito.Mockito.mock;
  33. import static org.mockito.Mockito.when;
  34. import org.apache.xmlgraphics.fonts.Glyphs;
  35. import org.apache.fop.fonts.SingleByteFont;
  36. import org.apache.fop.fonts.type1.PostscriptParser.PSDictionary;
  37. import org.apache.fop.fonts.type1.PostscriptParser.PSElement;
  38. import org.apache.fop.fonts.type1.PostscriptParser.PSFixedArray;
  39. import org.apache.fop.fonts.type1.Type1SubsetFile.BinaryCoder;
  40. import org.apache.fop.fonts.type1.Type1SubsetFile.BytesNumber;
  41. public class Type1SubsetFileTestCase {
  42. private List<byte[]> decodedSections;
  43. private static final String TEST_FONT_A = "./test/resources/fonts/type1/c0419bt_.pfb";
  44. @Test
  45. public void test() throws IOException {
  46. InputStream in = new FileInputStream(TEST_FONT_A);
  47. compareCharStringData(TEST_FONT_A, createFontASubset(in, TEST_FONT_A));
  48. }
  49. @Test
  50. public void testStitchFont() throws IOException {
  51. ByteArrayOutputStream baosHeader = new ByteArrayOutputStream();
  52. ByteArrayOutputStream baosMain = new ByteArrayOutputStream();
  53. ByteArrayOutputStream baosTrailer = new ByteArrayOutputStream();
  54. //Header
  55. for (int i = 0; i < 10; i++) {
  56. baosHeader.write(123);
  57. baosMain.write(123);
  58. }
  59. for (int i = 0; i < 10; i++) {
  60. baosTrailer.write(0);
  61. }
  62. Type1SubsetFile subset = new Type1SubsetFile();
  63. byte[] result = subset.stitchFont(baosHeader, baosMain, baosTrailer);
  64. ByteArrayInputStream bais = new ByteArrayInputStream(result);
  65. assertEquals(result.length, 50);
  66. PFBParser parser = new PFBParser();
  67. parser.parsePFB(bais);
  68. }
  69. @Test
  70. public void testUpdateSectionSize() throws IOException {
  71. Type1SubsetFile subset = new Type1SubsetFile();
  72. ByteArrayOutputStream baos = subset.updateSectionSize(456);
  73. byte[] lowOrderSize = baos.toByteArray();
  74. assertEquals(lowOrderSize[0], -56);
  75. assertEquals(lowOrderSize[1], 1);
  76. }
  77. @Test
  78. public void testVariableContents() {
  79. Type1SubsetFile subset = new Type1SubsetFile();
  80. String result = subset.readVariableContents("/myvariable {some variable contents}");
  81. assertEquals(result, "some variable contents");
  82. result = subset.readVariableContents("/myvariable {hello {some more text {test} and some more}test}");
  83. //Should only reads one level deep
  84. assertEquals(result, "hello test");
  85. }
  86. @Test
  87. public void getOpPositionAndLength() {
  88. Type1SubsetFile subset = new Type1SubsetFile();
  89. ArrayList<BytesNumber> ops = new ArrayList<BytesNumber>();
  90. ops.add(new BytesNumber(10, 1));
  91. ops.add(new BytesNumber(255, 2));
  92. ops.add(new BytesNumber(100, 1));
  93. ops.add(new BytesNumber(97, 1));
  94. ops.add(new BytesNumber(856, 2));
  95. assertEquals(subset.getOpPosition(4, ops), 4);
  96. assertEquals(subset.getOperandsLength(ops), 7);
  97. }
  98. @Test
  99. public void testConcatArrays() {
  100. byte[] arrayA = {(byte)1, (byte)2, (byte)3, (byte)4, (byte)5};
  101. byte[] arrayB = {(byte)6, (byte)7, (byte)8, (byte)9, (byte)10};
  102. Type1SubsetFile subset = new Type1SubsetFile();
  103. byte[] concatArray = subset.concatArray(arrayA, arrayB);
  104. assertEquals(concatArray.length, 10);
  105. assertEquals(concatArray[5], 6);
  106. assertEquals(concatArray[3], 4);
  107. }
  108. @Test
  109. public void testGetBinaryEntry() {
  110. byte[] decoded = {(byte)34, (byte)23, (byte)78, (byte)55, (byte)12,
  111. (byte)2, (byte)65, (byte)49, (byte)90, (byte)10};
  112. int[] section = {3, 7};
  113. Type1SubsetFile subset = new Type1SubsetFile();
  114. byte[] segment = subset.getBinaryEntry(section, decoded);
  115. assertEquals(segment.length, 4);
  116. assertEquals(segment[0], 55);
  117. assertEquals(segment[3], 65);
  118. }
  119. private void compareCharStringData(String font, byte[] subsetFont)
  120. throws IOException {
  121. decodedSections = new ArrayList<byte[]>();
  122. //Reinitialise the input stream as reset only supports 1000 bytes.
  123. InputStream in = new FileInputStream(font);
  124. List<PSElement> origElements = parseElements(in);
  125. List<PSElement> subsetElements = parseElements(new ByteArrayInputStream(subsetFont));
  126. PSFixedArray origSubs = (PSFixedArray)findElement(origElements, "/Subrs");
  127. PSFixedArray subsetSubs = (PSFixedArray)findElement(subsetElements, "/Subrs");
  128. PSDictionary origCharStrings = (PSDictionary)findElement(origElements, "/CharStrings");
  129. PSDictionary subsetCharStrings = (PSDictionary)findElement(subsetElements, "/CharStrings");
  130. for (String element : subsetCharStrings.getEntries().keySet()) {
  131. if (element.equals("/.notdef")) {
  132. continue;
  133. }
  134. int[] origBinaryCharLocation = origCharStrings.getBinaryEntries().get(element);
  135. int[] subsetBinaryCharLocation = subsetCharStrings.getBinaryEntries().get(element);
  136. int origLength = origBinaryCharLocation[1] - origBinaryCharLocation[0];
  137. int subsetLength = subsetBinaryCharLocation[1] - subsetBinaryCharLocation[0];
  138. byte[] origCharData = new byte[origLength];
  139. byte[] subsetCharData = new byte[subsetLength];
  140. System.arraycopy(decodedSections.get(0), origBinaryCharLocation[0], origCharData, 0, origLength);
  141. System.arraycopy(decodedSections.get(1), subsetBinaryCharLocation[0], subsetCharData, 0, subsetLength);
  142. origCharData = BinaryCoder.decodeBytes(origCharData, 4330, 4);
  143. subsetCharData = BinaryCoder.decodeBytes(subsetCharData, 4330, 4);
  144. byte[] origFullCharData = readFullCharString(decodedSections.get(0), origCharData, origSubs);
  145. byte[] subsetFullCharData = readFullCharString(decodedSections.get(1), subsetCharData, subsetSubs);
  146. assertArrayEquals(origFullCharData, subsetFullCharData);
  147. }
  148. }
  149. private byte[] createFontASubset(InputStream in, String font) throws IOException {
  150. SingleByteFont sbfont = mock(SingleByteFont.class);
  151. //Glyph index & selector
  152. Map<Integer, Integer> glyphs = new HashMap<Integer, Integer>();
  153. Map<Integer, String> usedCharNames = new HashMap<Integer, String>();
  154. int count = 0;
  155. for (int i = 32; i < 127; i++) {
  156. glyphs.put(i, count++);
  157. when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i);
  158. usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i)));
  159. when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i));
  160. }
  161. for (int i = 161; i < 204; i++) {
  162. glyphs.put(i, count++);
  163. when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i);
  164. usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i)));
  165. when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i));
  166. }
  167. int[] randomGlyphs = {205, 206, 207, 208, 225, 227, 232, 233, 234, 235, 241, 245,
  168. 248, 249, 250, 251
  169. };
  170. for (int i = 0; i < randomGlyphs.length; i++) {
  171. glyphs.put(randomGlyphs[i], count++);
  172. when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)randomGlyphs[i]);
  173. usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i)));
  174. when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i));
  175. }
  176. for (int i = 256; i < 335; i++) {
  177. glyphs.put(i, count++);
  178. when(sbfont.getUnicodeFromSelector(count)).thenReturn((char)i);
  179. usedCharNames.put(i, String.format("/%s", Glyphs.charToGlyphName((char)i)));
  180. when(sbfont.getGlyphName(i)).thenReturn(AdobeStandardEncoding.getCharFromCodePoint(i));
  181. }
  182. when(sbfont.getUsedGlyphNames()).thenReturn(usedCharNames);
  183. when(sbfont.getUsedGlyphs()).thenReturn(glyphs);
  184. when(sbfont.getEmbedFileURI()).thenReturn(URI.create(font));
  185. Type1SubsetFile subset = new Type1SubsetFile();
  186. return subset.createSubset(in, sbfont);
  187. }
  188. private List<PSElement> parseElements(InputStream in)
  189. throws IOException {
  190. PFBParser pfbParser = new PFBParser();
  191. PFBData origData = pfbParser.parsePFB(in);
  192. PostscriptParser parser = new PostscriptParser();
  193. byte[] decoded = BinaryCoder.decodeBytes(origData.getEncryptedSegment(), 55665, 4);
  194. decodedSections.add(decoded);
  195. return parser.parse(decoded);
  196. }
  197. private PSElement findElement(List<PSElement> elements, String operator) {
  198. for (PSElement element : elements) {
  199. if (element.getOperator().equals(operator)) {
  200. return element;
  201. }
  202. }
  203. return null;
  204. }
  205. private byte[] readFullCharString(byte[] decoded, byte[] data, PSFixedArray subroutines) {
  206. List<BytesNumber> operands = new ArrayList<BytesNumber>();
  207. for (int i = 0; i < data.length; i++) {
  208. int cur = data[i] & 0xFF;
  209. if (cur >= 0 && cur <= 31) {
  210. //Found subroutine. Read subroutine, recursively scan and update references
  211. if (cur == 10) {
  212. if (operands.size() == 0) {
  213. continue;
  214. }
  215. int[] subrData = subroutines.getBinaryEntryByIndex(operands.get(0).getNumber());
  216. byte[] subroutine = getBinaryEntry(subrData, decoded);
  217. subroutine = BinaryCoder.decodeBytes(subroutine, 4330, 4);
  218. subroutine = readFullCharString(decoded, subroutine, subroutines);
  219. data = replaceReference(data, subroutine, i - 1 + operands.get(0).getNumBytes(), i);
  220. } else {
  221. int next = -1;
  222. if (cur == 12) {
  223. next = data[++i] & 0xFF;
  224. }
  225. BytesNumber operand = new BytesNumber(cur, i);
  226. operand.setName(getName(cur, next));
  227. }
  228. operands.clear();
  229. }
  230. if (cur >= 32 && cur <= 246) {
  231. operands.add(new BytesNumber(cur - 139, 1));
  232. } else if (cur >= 247 && cur <= 250) {
  233. operands.add(new BytesNumber((cur - 247) * 256 + (data[i + 1] & 0xFF) + 108, 2));
  234. i++;
  235. } else if (cur >= 251 && cur <= 254) {
  236. operands.add(new BytesNumber(-(cur - 251) * 256 - (data[i + 1] & 0xFF) - 108, 2));
  237. i++;
  238. } else if (cur == 255) {
  239. int b1 = data[i + 1] & 0xFF;
  240. int b2 = data[i + 2] & 0xFF;
  241. int b3 = data[i + 3] & 0xFF;
  242. int b4 = data[i + 4] & 0xFF;
  243. int value = b1 << 24 | b2 << 16 | b3 << 8 | b4;
  244. operands.add(new BytesNumber(value, 5));
  245. i += 4;
  246. }
  247. }
  248. return data;
  249. }
  250. private String getName(int operator, int next) {
  251. switch (operator) {
  252. case 14: return "endchar";
  253. case 13: return "hsbw";
  254. case 12:
  255. switch (next) {
  256. case 0: return "dotsection";
  257. case 1: return "vstem3";
  258. case 2: return "hstem3";
  259. case 6: return "seac";
  260. case 7: return "sbw";
  261. case 16: return "callothersubr";
  262. case 17: return "pop";
  263. case 33: return "setcurrentpoint";
  264. default: return "unknown";
  265. }
  266. case 9: return "closepath";
  267. case 6: return "hlineto";
  268. case 22: return "hmoveto";
  269. case 31: return "hvcurveto";
  270. case 5: return "rlineto";
  271. case 21: return "rmoveto";
  272. case 8: return "rrcurveto";
  273. case 30: return "vhcurveto";
  274. case 7: return "vlineto";
  275. case 4: return "vmoveto";
  276. case 1: return "hstem";
  277. case 3: return "vstem";
  278. case 10: return "callsubr";
  279. case 11: return "return";
  280. default: return "unknown";
  281. }
  282. }
  283. private byte[] replaceReference(byte[] data, byte[] subroutine, int startRef, int endRef) {
  284. byte[] preBytes = new byte[startRef - 1];
  285. System.arraycopy(data, 0, preBytes, 0, startRef - 1);
  286. byte[] postBytes = new byte[data.length - endRef - 1];
  287. System.arraycopy(data, endRef + 1, postBytes, 0, data.length - endRef - 1);
  288. data = concatArray(preBytes, subroutine, 1);
  289. data = concatArray(data, postBytes, 0);
  290. return data;
  291. }
  292. private byte[] getBinaryEntry(int[] position, byte[] decoded) {
  293. int start = position[0];
  294. int finish = position[1];
  295. byte[] line = new byte[finish - start];
  296. System.arraycopy(decoded, start, line, 0, finish - start);
  297. return line;
  298. }
  299. private byte[] concatArray(byte[] a, byte[] b, int subtract) {
  300. int aLen = a.length;
  301. int bLen = b.length - subtract;
  302. byte[] c = new byte[aLen + bLen];
  303. System.arraycopy(a, 0, c, 0, aLen);
  304. System.arraycopy(b, 0, c, aLen, bLen);
  305. return c;
  306. }
  307. }