123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- /*
- * 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.truetype;
-
- import java.io.ByteArrayInputStream;
- import java.io.DataInputStream;
- import java.io.File;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.junit.Assert;
- import org.junit.Before;
- import org.junit.Test;
-
- import static org.junit.Assert.assertEquals;
- import static org.junit.Assert.assertTrue;
- import static org.mockito.Matchers.any;
- import static org.mockito.Mockito.mock;
- import static org.mockito.Mockito.when;
-
- import org.apache.fontbox.cff.CFFFont;
- import org.apache.fontbox.cff.CFFParser;
- import org.apache.fontbox.cff.CFFType1Font;
- import org.apache.fontbox.cff.CharStringCommand;
- import org.apache.fontbox.cff.Type2CharString;
-
- import org.apache.fop.fonts.MultiByteFont;
- 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;
-
- public class OTFSubSetFileTestCase extends OTFFileTestCase {
- private Map<Integer, Integer> glyphs = new HashMap<Integer, Integer>();
-
- /**
- * Initialises the test by creating the font subset. A CFFDataReader is
- * also created based on the subset data for use in the tests.
- * @throws IOException
- */
- @Before
- public void setUp() throws Exception {
- super.setUp();
- for (int i = 0; i < 256; i++) {
- glyphs.put(i, i);
- }
- }
-
- private CFFDataReader getCFFReaderSourceSans() throws IOException {
- byte[] sourceSansData = getSourceSansSubset().getFontSubset();
- return new CFFDataReader(sourceSansData);
- }
-
- private OTFSubSetFile getSourceSansSubset() throws IOException {
- OTFSubSetFile sourceSansSubset = new OTFSubSetFile();
- sourceSansSubset.readFont(sourceSansReader, "SourceSansProBold", null, glyphs);
- return sourceSansSubset;
- }
-
- /**
- * Validates the CharString data against the original font
- * @throws IOException
- */
- @Test
- public void testCharStringIndex() throws IOException {
- CFFDataReader cffReaderSourceSans = getCFFReaderSourceSans();
- assertEquals(256, cffReaderSourceSans.getCharStringIndex().getNumObjects());
- assertTrue(checkCorrectOffsets(cffReaderSourceSans.getCharStringIndex()));
- validateCharStrings(cffReaderSourceSans, getSourceSansSubset().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++) {
- if (indexData.getOffsets()[i] < last) {
- return false;
- }
- last = indexData.getOffsets()[i];
- }
- return true;
- }
-
- /**
- * 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();
- List<byte[]> origCharStringData = sourceSansOriginal.getCharStringBytes();
- for (int i = 0; i < charStrings.getNumObjects(); i++) {
- byte[] origCharData = origCharStringData.get(i);
- byte[] charData = charStrings.getValue(i);
- List<BytesNumber> origOperands = getFullCharString(new Context(), origCharData, origCFF);
- List<BytesNumber> subsetOperands = getFullCharString(new Context(), charData, subsetCFF);
- for (int j = 0; j < origOperands.size(); j++) {
- assertTrue(origOperands.get(j).equals(subsetOperands.get(j)));
- }
- }
- }
-
- static class Context {
- private ArrayList<BytesNumber> operands = new ArrayList<BytesNumber>();
- private ArrayList<BytesNumber> stack = new ArrayList<BytesNumber>();
- private int hstemCount;
- private int vstemCount;
- private int lastOp = -1;
- private int maskLength = -1;
-
- public void pushOperand(BytesNumber v) {
- operands.add(v);
- if (v instanceof Operator) {
- if (v.getNumber() != 11 && v.getNumber() != 12) {
- lastOp = v.getNumber();
- }
- } else {
- stack.add(v);
- }
- }
-
- public BytesNumber popOperand() {
- operands.remove(operands.size() - 1);
- return stack.remove(stack.size() - 1);
- }
-
- public BytesNumber lastOperand() {
- return operands.get(operands.size() - 1);
- }
-
- public void clearStack() {
- stack.clear();
- }
-
- public int getMaskLength() {
- // The number of data bytes for mask is exactly the number needed, one
- // bit per hint, to reference the number of stem hints declared
- // at the beginning of the charstring program.
- if (maskLength > 0) {
- return maskLength;
- }
- return 1 + (hstemCount + vstemCount - 1) / 8;
- }
-
- public List<BytesNumber> getFullOperandsList() {
- return operands;
- }
-
- public void countHstem() {
- // hstem(hm) operator
- hstemCount += stack.size() / 2;
- clearStack();
- }
-
- public void countVstem() {
- // vstem(hm) operator
- vstemCount += stack.size() / 2;
- clearStack();
- }
-
- public int calcMaskLength() {
- if (lastOp == 1 || lastOp == 18) {
- //If hstem and vstem hints are both declared at the beginning of
- //a charstring, and this sequence is followed directly by the
- //hintmask or cntrmask operators, the vstem hint operator need
- //not be included.
- vstemCount += stack.size() / 2;
- }
- clearStack();
- return getMaskLength();
- }
- }
- /**
- * 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(Context context, 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;
- for (int dataPos = 0; dataPos < data.length; dataPos++) {
- int b0 = data[dataPos] & 0xff;
- if (b0 == 10 && hasLocalSubroutines) {
- int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(),
- context.popOperand().getNumber());
- byte[] subr = localIndexSubr.getValue(subrNumber);
- getFullCharString(context, subr, cffData);
- } else if (b0 == 29 && hasGlobalSubroutines) {
- int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(),
- context.popOperand().getNumber());
- byte[] subr = globalIndexSubr.getValue(subrNumber);
- getFullCharString(context, subr, cffData);
- } 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;
- } else if (b0 == 1 || b0 == 18) {
- context.countHstem();
- } else if (b0 == 3 || b0 == 23) {
- context.countVstem();
- } else if (b0 == 19 || b0 == 20) {
- int length = context.calcMaskLength();
- dataPos += length;
- size = length + 1;
- }
- context.pushOperand(new Operator(b0, size, getOperatorName(b0, b1)));
- } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) {
- context.pushOperand(readNumber(b0, data, dataPos));
- dataPos += context.lastOperand().getNumBytes() - 1;
- }
- }
- return context.getFullOperandsList();
- }
-
- /**
- * 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((int) (short) (b1 << 8 | b2), 3);
- } else if (b0 >= 32 && b0 <= 246) {
- return new BytesNumber(b0 - 139, 1);
- } else if (b0 >= 247 && b0 <= 250) {
- int b1 = input[curPos + 1] & 0xff;
- return new BytesNumber((b0 - 247) * 256 + b1 + 108, 2);
- } else if (b0 >= 251 && b0 <= 254) {
- int b1 = input[curPos + 1] & 0xff;
- return new BytesNumber(-(b0 - 251) * 256 - b1 - 108, 2);
- } else if (b0 == 255) {
- int b1 = input[curPos + 1] & 0xff;
- int b2 = input[curPos + 2] & 0xff;
- int b3 = input[curPos + 3] & 0xff;
- int b4 = input[curPos + 4] & 0xff;
- return new BytesNumber((b1 << 24 | b2 << 16 | b3 << 8 | b4), 5);
- } else {
- throw new IllegalArgumentException();
- }
- }
-
- /**
- * 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;
- }
-
- /**
- * 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;
- }
- }
-
- /**
- * A class representing an operator from the CharString data
- */
- private class Operator extends BytesNumber {
- private String opName = "";
-
- Operator(int number, int numBytes, String opName) {
- super(number, numBytes);
- this.opName = opName;
- }
-
- public String toString() {
- return String.format("[%s]", opName);
- }
- }
-
- /**
- * 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 operatorB 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
- */
- @Test
- public void testStringIndex() throws IOException {
- CFFDataReader cffReaderSourceSans = getCFFReaderSourceSans();
- assertEquals(164, cffReaderSourceSans.getStringIndex().getNumObjects());
- assertTrue(checkCorrectOffsets(cffReaderSourceSans.getStringIndex()));
- assertEquals("Amacron", new String(cffReaderSourceSans.getStringIndex().getValue(5)));
- assertEquals("Edotaccent", new String(cffReaderSourceSans.getStringIndex().getValue(32)));
- assertEquals("uni0122", new String(cffReaderSourceSans.getStringIndex().getValue(45)));
- }
-
- /**
- * Validates the Top Dict data
- * @throws IOException
- */
- @Test
- public void testTopDictData() throws IOException {
- CFFDataReader cffReaderSourceSans = getCFFReaderSourceSans();
- Map<String, DICTEntry> topDictEntries = cffReaderSourceSans.parseDictData(
- cffReaderSourceSans.getTopDictIndex().getData());
- assertEquals(10, topDictEntries.size());
- }
-
- @Test
- public void testFDSelect() throws IOException {
- Assert.assertEquals(getSubset(1).length, 46);
- Assert.assertEquals(getSubset(2).length, 45);
- }
-
- private byte[] getSubset(final int opLen) throws IOException {
- FontFileReader reader = sourceSansReader;
- OTFSubSetFile otfSubSetFile = new MyOTFSubSetFile(opLen);
- otfSubSetFile.readFont(reader, "StandardOpenType", null, new HashMap<Integer, Integer>());
- return otfSubSetFile.getFontSubset();
- }
-
- @Test
- public void testOffsets() throws IOException {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < 2048; i++) {
- sb.append("SourceSansProBold");
- }
- OTFSubSetFile otfSubSetFile = new OTFSubSetFile();
- otfSubSetFile.readFont(sourceSansReader, sb.toString(), null, glyphs);
- new CFFParser().parse(otfSubSetFile.getFontSubset());
- }
-
- @Test
- public void testCharset() throws IOException {
- FontFileReader reader = sourceSansReader;
- MyOTFSubSetFile otfSubSetFile = new MyOTFSubSetFile(1);
- otfSubSetFile.readFont(reader, "StandardOpenType", null, new HashMap<Integer, Integer>());
- ByteArrayInputStream is = new ByteArrayInputStream(otfSubSetFile.getFontSubset());
- is.skip(otfSubSetFile.charsetOffset);
- Assert.assertEquals(is.read(), 2);
- }
-
- class MyOTFSubSetFile extends OTFSubSetFile {
- int charsetOffset;
- int opLen;
- MyOTFSubSetFile(int opLen) throws IOException {
- super();
- this.opLen = opLen;
- }
-
- protected void createCFF() throws IOException {
- cffReader = mock(CFFDataReader.class);
- when(cffReader.getHeader()).thenReturn(new byte[0]);
- when(cffReader.getTopDictIndex()).thenReturn(new CFFDataReader().new CFFIndexData() {
- public byte[] getByteData() throws IOException {
- return new byte[] {0, 0, 1};
- }
- });
-
- LinkedHashMap<String, DICTEntry> map = new LinkedHashMap<String, DICTEntry>();
- DICTEntry dict = new DICTEntry();
- dict.setOperands(Collections.<Number>singletonList(1));
- map.put("charset", dict);
- map.put("CharStrings", dict);
- when((cffReader.getTopDictEntries())).thenReturn(map);
- CFFDataReader.Format3FDSelect fdSelect = new CFFDataReader().new Format3FDSelect();
- fdSelect.setRanges(new HashMap<Integer, Integer>());
- when(cffReader.getFDSelect()).thenReturn(fdSelect);
- cffReader.getTopDictEntries().get("CharStrings").setOperandLength(opLen);
- super.createCFF();
- }
-
- protected void updateFixedOffsets(Map<String, DICTEntry> topDICT, Offsets offsets) throws IOException {
- this.charsetOffset = offsets.charset;
- super.updateFixedOffsets(topDICT, offsets);
- }
- }
-
- @Test
- public void testResizeOfOperand() throws IOException {
- OTFSubSetFile otfSubSetFile = new OTFSubSetFile() {
- protected void writeFDSelect() {
- super.writeFDSelect();
- writeBytes(new byte[1024 * 100]);
- }
- };
- otfSubSetFile.readFont(sourceSansReader, "StandardOpenType", null, glyphs);
- byte[] fontSubset = otfSubSetFile.getFontSubset();
- CFFDataReader cffReader = new CFFDataReader(fontSubset);
- assertEquals(cffReader.getTopDictEntries().get("CharStrings").getOperandLength(), 5);
- assertEquals(cffReader.getTopDictEntries().get("CharStrings").getByteData().length, 6);
- }
-
- @Test
- public void testFDArraySize() throws IOException {
- OTFSubSetFileFDArraySize otfSubSetFileFDArraySize = new OTFSubSetFileFDArraySize();
- otfSubSetFileFDArraySize.readFont(sourceSansReader, "StandardOpenType", null, glyphs);
- byte[] fontSubset = otfSubSetFileFDArraySize.getFontSubset();
- DataInputStream dis = new DataInputStream(new ByteArrayInputStream(fontSubset));
- dis.skipBytes(otfSubSetFileFDArraySize.offset);
- Assert.assertEquals(dis.readUnsignedShort(), otfSubSetFileFDArraySize.fdFontCount);
- Assert.assertEquals(dis.readByte(), 2);
- }
-
- static class OTFSubSetFileFDArraySize extends OTFSubSetFile {
- int offset;
- int fdFontCount = 128;
-
- OTFSubSetFileFDArraySize() throws IOException {
- super();
- }
-
- protected void createCFF() throws IOException {
- super.createCFF();
- writeFDArray(new ArrayList<Integer>(), new ArrayList<Integer>(), new ArrayList<Integer>());
- }
-
- protected int writeFDArray(List<Integer> uniqueNewRefs, List<Integer> privateDictOffsets,
- List<Integer> fontNameSIDs) throws IOException {
- List<CFFDataReader.FontDict> fdFonts = cffReader.getFDFonts();
- CFFDataReader.FontDict fdFont = cffReader.new FontDict() {
- public byte[] getByteData() throws IOException {
- return new byte[128];
- }
- };
- cffReader = makeCFFDataReader();
- when(cffReader.getFDFonts()).thenReturn(fdFonts);
-
- fdFonts.clear();
- uniqueNewRefs.clear();
- privateDictOffsets.clear();
- fontNameSIDs.clear();
- for (int i = 0; i < fdFontCount; i++) {
- fdFonts.add(fdFont);
- uniqueNewRefs.add(i);
- privateDictOffsets.add(i);
- fontNameSIDs.add(i);
- }
- offset = super.writeFDArray(uniqueNewRefs, privateDictOffsets, fontNameSIDs);
- return offset;
- }
- }
-
- @Test
- public void testOrderOfEntries() throws IOException {
- OTFSubSetFileEntryOrder otfSubSetFile = getFont(3, 2);
- assertTrue(otfSubSetFile.offsets.fdArray < otfSubSetFile.offsets.charString);
- assertEquals(otfSubSetFile.cffReader.getTopDictEntries().get("CharStrings").getOperandLength(), 5);
- otfSubSetFile = getFont(2, 3);
- assertTrue(otfSubSetFile.offsets.fdArray < otfSubSetFile.offsets.charString);
- assertEquals(otfSubSetFile.cffReader.getTopDictEntries().get("CharStrings").getOperandLength(), 5);
- }
-
- private OTFSubSetFileEntryOrder getFont(int csLen, int fdLen) throws IOException {
- glyphs.clear();
- OTFSubSetFileEntryOrder otfSubSetFile = new OTFSubSetFileEntryOrder(csLen, fdLen);
- otfSubSetFile.readFont(sourceSansReader, "StandardOpenType", null, glyphs);
- return otfSubSetFile;
- }
-
- static class OTFSubSetFileEntryOrder extends OTFSubSetFile {
- Offsets offsets;
- int csLen;
- int fdLen;
-
- OTFSubSetFileEntryOrder(int csLen, int fdLen) throws IOException {
- super();
- this.csLen = csLen;
- this.fdLen = fdLen;
- }
-
- protected void createCFF() throws IOException {
- cffReader = makeCFFDataReader();
- LinkedHashMap<String, DICTEntry> topDict = new LinkedHashMap<String, DICTEntry>();
- DICTEntry entry = new DICTEntry();
- entry.setOperands(Collections.<Number>singletonList(0));
- topDict.put("charset", entry);
- entry.setOperandLength(csLen);
- topDict.put("CharStrings", entry);
- entry = new DICTEntry();
- entry.setOperandLength(fdLen);
- topDict.put("FDArray", entry);
- when(cffReader.getTopDictEntries()).thenReturn(topDict);
- super.createCFF();
- }
-
- protected void updateCIDOffsets(Offsets offsets) throws IOException {
- super.updateCIDOffsets(offsets);
- this.offsets = offsets;
- }
- }
-
- private static CFFDataReader makeCFFDataReader() throws IOException {
- CFFDataReader cffReader = mock(CFFDataReader.class);
- when(cffReader.getHeader()).thenReturn(new byte[0]);
- when(cffReader.getTopDictIndex()).thenReturn(cffReader.new CFFIndexData() {
- public byte[] getByteData() throws IOException {
- return new byte[]{0, 0, 1};
- }
- });
- CFFDataReader.Format3FDSelect fdSelect = cffReader.new Format3FDSelect();
- fdSelect.setRanges(new HashMap<Integer, Integer>());
- when(cffReader.getFDSelect()).thenReturn(fdSelect);
- CFFDataReader.FontDict fd = mock(CFFDataReader.FontDict.class);
- when(fd.getPrivateDictData()).thenReturn(new byte[0]);
- when(cffReader.getFDFonts()).thenReturn(Collections.singletonList(fd));
-
- LinkedHashMap<String, DICTEntry> map = new LinkedHashMap<String, DICTEntry>();
- DICTEntry e = new DICTEntry();
- e.setOffset(1);
- e.setOperandLengths(Arrays.asList(0, 0));
- e.setOperandLength(2);
- map.put("FontName", e);
- map.put("Private", e);
- map.put("Subrs", e);
- when(cffReader.parseDictData(any(byte[].class))).thenReturn(map);
- return cffReader;
- }
-
- @Test
- public void testWriteCIDDictsAndSubrs() throws IOException {
- OTFSubSetFile subSetFile = new OTFSubSetFile() {
- public void readFont(FontFileReader in, String embeddedName, MultiByteFont mbFont) throws IOException {
- cffReader = makeCFFDataReader();
- fdSubrs = new ArrayList<List<byte[]>>();
- fdSubrs.add(new ArrayList<byte[]>());
- writeCIDDictsAndSubrs(Collections.singletonList(0));
- }
- };
- subSetFile.readFont(null, null, (MultiByteFont) null);
-
- ByteArrayInputStream is = new ByteArrayInputStream(subSetFile.getFontSubset());
- is.skip(1);
- Assert.assertEquals(is.read(), 247);
- Assert.assertEquals(is.read(), 0);
- final int sizeOfPrivateDictByteData = 108;
- is.skip(sizeOfPrivateDictByteData - 3);
- is.skip(2); //start index
- Assert.assertEquals(is.read(), 1);
- }
-
- @Test
- public void testResizeOfOperand2() throws IOException {
- OTFSubSetFile otfSubSetFile = new OTFSubSetFile() {
- void readFont(FontFileReader in, String embeddedName, MultiByteFont mbFont,
- Map<Integer, Integer> usedGlyphs) throws IOException {
- cffReader = makeCFFDataReader();
- LinkedHashMap<String, DICTEntry> topDict = new LinkedHashMap<String, DICTEntry>();
- DICTEntry entry = new DICTEntry();
- entry.setOperandLength(1);
- entry.setOperator(new int[0]);
- entry.setOperands(Collections.<Number>singletonList(0));
- topDict.put("version", entry);
- when(cffReader.getTopDictEntries()).thenReturn(topDict);
- writeTopDICT();
- }
- };
- otfSubSetFile.readFont(sourceSansReader, "StandardOpenType", null, glyphs);
- ByteArrayInputStream fontSubset = new ByteArrayInputStream(otfSubSetFile.getFontSubset());
- fontSubset.skip(5);
- Assert.assertEquals(fontSubset.read(), 248);
- Assert.assertEquals(fontSubset.read(), (byte)(390 - 108));
- }
-
- @Test
- public void testCompositeGlyphMapping() throws IOException {
- glyphs.clear();
- glyphs.put(0, 0);
- OTFSubSetFile sourceSansSubset = new OTFSubSetFile() {
- protected void initializeFont(FontFileReader in) {
- fileFont = new CFFType1Font() {
- List<Object> sequence = Arrays.asList(0, 0, 0, (int)'a', (int)'b', new CharStringCommand(12, 6));
- public Type2CharString getType2CharString(int gid) {
- return new Type2CharString(null, null, null, 0, sequence, 0, 0);
- }
- };
- }
- };
- MultiByteFont multiByteFont = new MultiByteFont(null, null) {
- public void setEmbedResourceName(String name) {
- super.setEmbedResourceName(name);
- addPrivateUseMapping('a', 'a');
- addPrivateUseMapping('b', 'b');
- }
- };
- multiByteFont.setEmbedURI(new File(".").toURI());
- multiByteFont.setEmbedResourceName("");
- sourceSansSubset.readFont(sourceSansReader, "SourceSansProBold", multiByteFont, glyphs);
- Assert.assertEquals(multiByteFont.getUsedGlyphs().toString(), "{0=0, 97=1, 98=2}");
- }
- }
|