123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- /*
- * 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(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(String font, byte[] subsetFont)
- throws IOException {
- decodedSections = new ArrayList<byte[]>();
-
- //Reinitialise the input stream as reset only supports 1000 bytes.
- InputStream 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>();
- 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++);
- 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++);
- 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++);
- 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);
- }
-
- 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>();
- 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));
- }
- operands.clear();
- }
- if (cur >= 32 && cur <= 246) {
- operands.add(new BytesNumber(cur - 139, 1));
- } else if (cur >= 247 && cur <= 250) {
- operands.add(new BytesNumber((cur - 247) * 256 + (data[i + 1] & 0xFF) + 108, 2));
- i++;
- } else if (cur >= 251 && cur <= 254) {
- operands.add(new BytesNumber(-(cur - 251) * 256 - (data[i + 1] & 0xFF) - 108, 2));
- 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));
- 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;
- }
- }
|