CFFParserTest.java
/*
* Copyright 2017 The Apache Software Foundation.
*
* Licensed 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.
*/
package org.apache.fontbox.cff;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.fontbox.util.BoundingBox;
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
/**
*
* @author Petr Slaby
*/
class CFFParserTest
{
private static CFFType1Font testCFFType1Font;
@BeforeAll
static void loadCFFFont() throws IOException
{
List<CFFFont> fonts = readFont("target/fonts/SourceSansProBold.otf");
testCFFType1Font = (CFFType1Font) fonts.get(0);
}
@Test
void testFontname()
{
assertEquals("SourceSansPro-Bold", testCFFType1Font.getName());
}
@Test
void testFontBBox() throws IOException
{
BoundingBox fontBBox = testCFFType1Font.getFontBBox();
assertNotNull(fontBBox, "FontBBox must not be null");
assertEquals(-231.0f, fontBBox.getLowerLeftX());
assertEquals(-384.0f, fontBBox.getLowerLeftY());
assertEquals(1223.0f, fontBBox.getUpperRightX());
assertEquals(974.0f, fontBBox.getUpperRightY());
}
@Test
void testFontMatrix()
{
List<Number> fontMatrix = testCFFType1Font.getFontMatrix();
assertNotNull(fontMatrix, "FontMatrix must not be null");
assertNumberList("FontMatrix values are different than expected" + fontMatrix.toString(),
new float[] { 0.001f, 0.0f, 0.0f, 0.001f, 0.0f, 0.0f }, fontMatrix);
}
@Test
void testCharset()
{
CFFCharset charset = testCFFType1Font.getCharset();
assertNotNull(charset, "Charset must not be null");
assertFalse(charset.isCIDFont(), "isCIDFont has to be false");
assertEquals("Format1Charset", charset.getClass().getSimpleName(),
"Charset is not an instance of Format1Charset");
// check some randomly chosen mappings
// gid2name
assertEquals(".notdef", charset.getNameForGID(0), "Unexpected value for gid2name mapping");
assertEquals("space", charset.getNameForGID(1), "Unexpected value for gid2name mapping");
assertEquals("F", charset.getNameForGID(7), "Unexpected value for gid2name mapping");
assertEquals("jcircumflex", charset.getNameForGID(300),
"Unexpected value for gid2name mapping");
assertEquals("infinity", charset.getNameForGID(700),
"Unexpected value for gid2name mapping");
// gid2sid
assertEquals(0, charset.getSIDForGID(0), "Unexpected value for gid2sid mapping");
assertEquals(1, charset.getSIDForGID(1), "Unexpected value for gid2sid mapping");
assertEquals(39, charset.getSIDForGID(7), "Unexpected value for gid2sid mapping");
assertEquals(585, charset.getSIDForGID(300), "Unexpected value for gid2sid mapping");
assertEquals(872, charset.getSIDForGID(700), "Unexpected value for gid2sid mapping");
// name2sid
assertEquals(0, charset.getSID(".notdef"), "Unexpected value for name2sid mapping");
assertEquals(1, charset.getSID("space"), "Unexpected value for name2sid mapping");
assertEquals(39, charset.getSID("F"), "Unexpected value for name2sid mapping");
assertEquals(585, charset.getSID("jcircumflex"), "Unexpected value for name2sid mapping");
assertEquals(872, charset.getSID("infinity"), "Unexpected value for name2sid mapping");
}
@Test
void voidEncoding()
{
CFFEncoding encoding = testCFFType1Font.getEncoding();
assertNotNull(encoding, "Encoding must not be null");
assertTrue(encoding instanceof CFFStandardEncoding,
"Encoding is not an instance of CFFStandardEncoding");
}
@Test
void testCharStringBytess()
{
List<byte[]> charStringBytes = testCFFType1Font.getCharStringBytes();
assertFalse(charStringBytes.isEmpty());
assertEquals(824, testCFFType1Font.getNumCharStrings());
// check some randomly chosen values
assertTrue(Arrays.equals(new byte[] { -4, 15, 14 }, charStringBytes.get(1)), //
"Other char strings byte values than expected");
assertTrue(
Arrays.equals(new byte[] { 72, 29, -13, 29, -9, -74, -9, 43, 3, 33, 29, 14 },
charStringBytes.get(16)), //
"Other char strings byte values than expected");
assertTrue(
Arrays.equals(new byte[] { -41, 88, 29, -47, -9, 12, 1, -123, 10, 3, 35, 29, -9,
-50, -9, 62, -9, 3, 10, 85, -56, 61, 10 }, charStringBytes.get(195)), //
"Other char strings byte values than expected");
assertTrue(
Arrays.equals(new byte[] { -5, -69, -61, -8, 28, 1, -9, 57, -39, -65, 29, 14 },
charStringBytes.get(525)), //
"Other char strings byte values than expected");
assertTrue(
Arrays.equals(new byte[] { 107, -48, 10, -9, 20, -9, 123, 3, -9, -112, -8, -46, 21,
-10, 115, 10 }, charStringBytes.get(738)), //
"Other char strings byte values than expected");
}
@Test
void testGlobalSubrIndex()
{
List<byte[]> globalSubrIndex = testCFFType1Font.getGlobalSubrIndex();
assertFalse(globalSubrIndex.isEmpty());
assertEquals(278, globalSubrIndex.size());
// check some randomly chosen values
assertTrue(
Arrays.equals(new byte[] { 21, -70, -83, -85, -72, -72, 105, -85, 92, 91, 105, 107,
10, -83, -9, 62, 10 }, globalSubrIndex.get(12)), //
"Other global subr index values than expected");
assertTrue(
Arrays.equals(new byte[] { 58, 122, 29, -5, 48, 6, 11 }, globalSubrIndex.get(120)), //
"Other global subr index values than expected");
assertTrue(
Arrays.equals(new byte[] { 68, 80, 29, -45, -9, 16, -8, -92, 119, 11 },
globalSubrIndex.get(253)), //
"Other global subr index values than expected");
}
/**
* PDFBOX-4038: Test whether BlueValues and other delta encoded lists are read correctly. The test file is from
* FOP-2432.
*
* @throws IOException
*/
@Test
void testDeltaLists() throws IOException
{
@SuppressWarnings("unchecked")
List<Number> blues = (List<Number>) testCFFType1Font.getPrivateDict().get("BlueValues");
// Expected values found for this font
assertNumberList("Blue values are different than expected: " + blues.toString(),
new int[] { -12, 0, 496, 508, 578, 590, 635, 647, 652, 664, 701, 713 }, blues);
@SuppressWarnings("unchecked")
List<Number> otherBlues = (List<Number>) testCFFType1Font.getPrivateDict()
.get("OtherBlues");
assertNumberList("Other blues are different than expected: " + otherBlues.toString(),
new int[] { -196, -184 }, otherBlues);
@SuppressWarnings("unchecked")
List<Number> familyBlues = (List<Number>) testCFFType1Font.getPrivateDict()
.get("FamilyBlues");
assertNumberList("Other blues are different than expected: " + familyBlues.toString(),
new int[] { -12, 0, 486, 498, 574, 586, 638, 650, 656, 668, 712, 724 },
familyBlues);
@SuppressWarnings("unchecked")
List<Number> familyOtherBlues = (List<Number>) testCFFType1Font.getPrivateDict()
.get("FamilyOtherBlues");
assertNumberList("Other blues are different than expected: " + familyOtherBlues.toString(),
new int[] { -217, -205 }, familyOtherBlues);
@SuppressWarnings("unchecked")
List<Number> stemSnapH = (List<Number>) testCFFType1Font.getPrivateDict().get("StemSnapH");
assertNumberList("StemSnapH values are different than expected: " + stemSnapH.toString(),
new int[] { 115 }, stemSnapH);
@SuppressWarnings("unchecked")
List<Number> stemSnapV = (List<Number>) testCFFType1Font.getPrivateDict().get("StemSnapV");
assertNumberList("StemSnapV values are different than expected: " + stemSnapV.toString(),
new int[] { 146, 150 }, stemSnapV);
}
/**
* PDFBOX-5819: ensure thread safety of Type2CharStringParser when parsing the path of a glyph.
*
* @throws InterruptedException
*/
@Test
void testMultiThreadParse() throws InterruptedException
{
CountDownLatch latch = new CountDownLatch(2);
PathRunner pathRunner1 = new PathRunner(latch);
PathRunner pathRunner2 = new PathRunner(latch);
AtomicBoolean wasCalled = new AtomicBoolean(false);
Thread.UncaughtExceptionHandler handler = (t, e) -> wasCalled.set(true);
Thread thread1 = new Thread(pathRunner1);
thread1.setUncaughtExceptionHandler(handler);
Thread thread2 = new Thread(pathRunner2);
thread2.setUncaughtExceptionHandler(handler);
thread1.start();
thread2.start();
latch.await();
assertFalse(wasCalled.get());
}
private class PathRunner implements Runnable
{
private final CountDownLatch latch;
PathRunner(CountDownLatch latch)
{
this.latch = latch;
}
@Override
public void run()
{
try
{
for (int i = 33; i < 126; i++)
{
testCFFType1Font.getPath(Character.toString(i));
}
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
finally
{
latch.countDown();
}
}
}
private static List<CFFFont> readFont(String filename) throws IOException
{
RandomAccessReadBufferedFile randomAccessRead = new RandomAccessReadBufferedFile(filename);
CFFParser parser = new CFFParser();
return parser.parse(randomAccessRead);
}
private void assertNumberList(String message, int[] expected, List<Number> found)
{
assertEquals(expected.length, found.size(), message);
for (int i = 0; i < expected.length; i++)
{
assertEquals(expected[i], found.get(i).intValue(), message);
}
}
private void assertNumberList(String message, float[] expected, List<Number> found)
{
assertEquals(expected.length, found.size(), message);
for (int i = 0; i < expected.length; i++)
{
assertEquals(expected[i], found.get(i).floatValue(), message);
}
}
}