AFMParserTest.java

/*
 * 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.
 */

package org.apache.fontbox.afm;

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.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;

import org.apache.fontbox.util.BoundingBox;
import org.junit.jupiter.api.Test;

/**
 *
 * @author Tilman Hausherr
 */
class AFMParserTest
{
    @Test
    void testStartFontMetrics() throws IOException
    {
        try
        {
            new AFMParser(new ByteArrayInputStream("huhu".getBytes(StandardCharsets.US_ASCII)))
                    .parse();
            fail("The AFMParser should have thrown an IOException because of a missing "
                    + AFMParser.START_FONT_METRICS);
        }
        catch (IOException e)
        {
            // expected exception
        }
    }

    @Test
    void testEndFontMetrics() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/NoEndFontMetrics.afm"));
        try
        {
            parser.parse();
            fail("The AFMParser should have thrown an IOException because of a missing "
                    + AFMParser.END_FONT_METRICS);
        }
        catch (IOException e)
        {
            assertTrue(e.getMessage().contains("Unknown AFM key"));
        }
    }

    @Test
    void testMalformedFloat() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/MalformedFloat.afm"));
        try
        {
            parser.parse();
            fail("The AFMParser should have thrown an IOException because of a malformed float value");
        }
        catch (IOException e)
        {
            assertTrue(e.getCause() instanceof NumberFormatException);
            assertTrue(e.getMessage().contains("4,1ab"));
        }
    }

    @Test
    void testMalformedInteger() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/MalformedInteger.afm"));
        try
        {
            parser.parse();
            fail("The AFMParser should have thrown an IOException because of a malformed int value");
        }
        catch (IOException e)
        {
            assertTrue(e.getCause() instanceof NumberFormatException);
            assertTrue(e.getMessage().contains("3.4"));
        }
    }

    @Test
    void testHelveticaFontMetrics() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/Helvetica.afm"));
        checkHelveticaFontMetrics(parser.parse());
    }

    @Test
    void testHelveticaCharMetrics() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/Helvetica.afm"));
        FontMetrics fontMetrics = parser.parse();

        // char metrics
        checkHelveticaCharMetrics(fontMetrics.getCharMetrics());
    }

    @Test
    void testHelveticaKernPairs() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/Helvetica.afm"));
        FontMetrics fontMetrics = parser.parse();

        // KernPairs
        List<KernPair> kernPairs = fontMetrics.getKernPairs();
        assertEquals(2705, kernPairs.size());
        // check "KPX A Ucircumflex -50"
        checkKernPair(kernPairs, "A", "Ucircumflex", -50, 0);
        // check "KPX W agrave -40"
        checkKernPair(kernPairs, "W", "agrave", -40, 0);
        // KernPairs0
        assertTrue(fontMetrics.getKernPairs0().isEmpty());
        // KernPairs1
        assertTrue(fontMetrics.getKernPairs1().isEmpty());
        // composite data
        assertTrue(fontMetrics.getComposites().isEmpty());
    }

    @Test
    void testHelveticaFontMetricsReducedDataset() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/Helvetica.afm"));
        checkHelveticaFontMetrics(parser.parse(true));
    }

    @Test
    void testHelveticaCharMetricsReducedDataset() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/Helvetica.afm"));
        FontMetrics fontMetrics = parser.parse(true);

        // char metrics
        checkHelveticaCharMetrics(fontMetrics.getCharMetrics());
    }

    @Test
    void testHelveticaKernPairsReducedDataset() throws IOException
    {
        AFMParser parser = new AFMParser(
                new FileInputStream("src/test/resources/afm/Helvetica.afm"));
        FontMetrics fontMetrics = parser.parse(true);

        // KernPairs, empty due to reducedDataset == true
        assertTrue(fontMetrics.getKernPairs().isEmpty());
        // KernPairs0
        assertTrue(fontMetrics.getKernPairs0().isEmpty());
        // KernPairs1
        assertTrue(fontMetrics.getKernPairs1().isEmpty());
        // composite data
        assertTrue(fontMetrics.getComposites().isEmpty());
    }

    private void checkHelveticaCharMetrics(List<CharMetric> charMetrics)
    {
        assertEquals(315, charMetrics.size());
        // check "space" metrics
        Optional<CharMetric> space = charMetrics.stream()//
                .filter(c -> "space".equals(c.getName())).findFirst();
        assertTrue(space.isPresent());
        CharMetric spaceCharMetric = space.get();
        assertEquals(278f, spaceCharMetric.getWx(), 0f);
        assertEquals(32, spaceCharMetric.getCharacterCode());
        checkBBox(spaceCharMetric.getBoundingBox(), 0, 0, 0, 0);
        assertTrue(spaceCharMetric.getLigatures().isEmpty());
        assertNull(spaceCharMetric.getW());
        assertNull(spaceCharMetric.getW0());
        assertNull(spaceCharMetric.getW1());
        assertNull(spaceCharMetric.getVv());
        // check "ring" metrics
        Optional<CharMetric> ring = charMetrics.stream()//
                .filter(c -> "ring".equals(c.getName())).findFirst();
        assertTrue(ring.isPresent());
        CharMetric ringCharMetric = ring.get();
        assertEquals(333f, ringCharMetric.getWx(), 0f);
        assertEquals(202, ringCharMetric.getCharacterCode());
        checkBBox(ringCharMetric.getBoundingBox(), 75, 572, 259, 756);
        assertTrue(ringCharMetric.getLigatures().isEmpty());
        assertNull(ringCharMetric.getW());
        assertNull(ringCharMetric.getW0());
        assertNull(ringCharMetric.getW1());
        assertNull(ringCharMetric.getVv());
    }

    private void checkHelveticaFontMetrics(FontMetrics fontMetrics)
    {
        assertEquals(4.1f, fontMetrics.getAFMVersion(), 0f);
        assertEquals("Helvetica", fontMetrics.getFontName());
        assertEquals("Helvetica", fontMetrics.getFullName());
        assertEquals("Helvetica", fontMetrics.getFamilyName());
        assertEquals("Medium", fontMetrics.getWeight());
        checkBBox(fontMetrics.getFontBBox(), -166f, -225f, 1000f, 931f);
        assertEquals("002.000", fontMetrics.getFontVersion());
        assertEquals(
                "Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated.  All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.",
                fontMetrics.getNotice());
        assertEquals("AdobeStandardEncoding", fontMetrics.getEncodingScheme());
        assertEquals(0, fontMetrics.getMappingScheme());
        assertEquals(0, fontMetrics.getEscChar());
        assertEquals("ExtendedRoman", fontMetrics.getCharacterSet());
        assertEquals(0, fontMetrics.getCharacters());
        assertTrue(fontMetrics.getIsBaseFont());
        assertNull(fontMetrics.getVVector());
        assertFalse(fontMetrics.getIsFixedV());
        assertEquals(718f, fontMetrics.getCapHeight(), 0f);
        assertEquals(523f, fontMetrics.getXHeight(), 0f);
        assertEquals(718f, fontMetrics.getAscender(), 0f);
        assertEquals(-207f, fontMetrics.getDescender(), 0f);
        assertEquals(76f, fontMetrics.getStandardHorizontalWidth(), 0f);
        assertEquals(88f, fontMetrics.getStandardVerticalWidth(), 0f);
        List<String> comments = fontMetrics.getComments();
        assertEquals(4, comments.size());
        assertEquals(
                "Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated.  All Rights Reserved.",
                comments.get(0));
        assertEquals("UniqueID 43054", comments.get(2));
        assertEquals(-100f, fontMetrics.getUnderlinePosition(), 0f);
        assertEquals(50f, fontMetrics.getUnderlineThickness(), 0f);
        assertEquals(0f, fontMetrics.getItalicAngle(), 0f);
        assertNull(fontMetrics.getCharWidth());
        assertFalse(fontMetrics.getIsFixedPitch());
    }

    private void checkBBox(BoundingBox bBox, float lowerX, float lowerY, float upperX, float upperY)
    {
        assertNotNull(bBox);
        assertEquals(lowerX, bBox.getLowerLeftX(), 0f);
        assertEquals(lowerY, bBox.getLowerLeftY(), 0f);
        assertEquals(upperX, bBox.getUpperRightX(), 0f);
        assertEquals(upperY, bBox.getUpperRightY(), 0f);
    }

    private void checkKernPair(List<KernPair> kernPairs, String firstKernChar,
            String secondKernChar, float x, float y)
    {
        Optional<KernPair> kernPair = kernPairs.stream() //
                .filter(k -> firstKernChar.equals(k.getFirstKernCharacter())) //
                .filter(k -> secondKernChar.equals(k.getSecondKernCharacter())) //
                .findFirst();
        assertTrue(kernPair.isPresent());
        assertEquals(x, kernPair.get().getX(), 0f);
        assertEquals(y, kernPair.get().getY(), 0f);

    }
}