PdfFontFactoryTest.java

/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2025 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.itextpdf.kernel.font;

import com.itextpdf.io.font.FontNames;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.font.TrueTypeFont;
import com.itextpdf.io.font.Type1Font;
import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.font.PdfFontFactory.EmbeddingStrategy;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.test.ExtendedITextTest;

import java.io.IOException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;

@Tag("UnitTest")
public class PdfFontFactoryTest extends ExtendedITextTest {

    public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/font/";

    @Test
    public void standardFontForceEmbeddedTest() throws IOException {
        Type1Font fontProgram = (Type1Font) FontProgramFactory.createFont(StandardFonts.HELVETICA);

        Exception e = Assertions.assertThrows(PdfException.class,
                () -> PdfFontFactory.createFont(fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.FORCE_EMBEDDED)
        );
        Assertions.assertEquals(KernelExceptionMessageConstant.CANNOT_EMBED_STANDARD_FONT, e.getMessage());
    }

    @Test
    public void standardFontPreferEmbeddedTest() throws IOException {
        Type1Font fontProgram = (Type1Font) FontProgramFactory.createFont(StandardFonts.HELVETICA);

        PdfType1Font font = (PdfType1Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.PREFER_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void standardFontPreferNotEmbeddedTest() throws IOException {
        Type1Font fontProgram = (Type1Font) FontProgramFactory.createFont(StandardFonts.HELVETICA);

        PdfType1Font font = (PdfType1Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.PREFER_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void standardFontForceNotEmbeddedTest() throws IOException {
        Type1Font fontProgram = (Type1Font) FontProgramFactory.createFont(StandardFonts.HELVETICA);

        PdfType1Font font = (PdfType1Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.FORCE_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void customType1FontForceEmbeddedTest() throws IOException {
        Type1Font fontProgram = new CustomType1FontProgram();

        PdfType1Font font = (PdfType1Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.FORCE_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertTrue(font.isEmbedded());
    }

    @Test
    public void customType1FontPreferEmbeddedTest() throws IOException {
        Type1Font fontProgram = new CustomType1FontProgram();

        PdfType1Font font = (PdfType1Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.PREFER_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertTrue(font.isEmbedded());
    }

    @Test
    public void customType1FontPreferNotEmbeddedTest() throws IOException {
        Type1Font fontProgram = new CustomType1FontProgram();

        PdfType1Font font = (PdfType1Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.PREFER_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void customType1FontForceNotEmbeddedTest() throws IOException {
        Type1Font fontProgram = new CustomType1FontProgram();

        PdfType1Font font = (PdfType1Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.FORCE_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramUTF8AllowEmbeddingEncodingForceEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(true);

        PdfTrueTypeFont font = (PdfTrueTypeFont) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.FORCE_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertTrue(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramUTF8AllowEmbeddingEncodingPreferEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(true);

        PdfTrueTypeFont font = (PdfTrueTypeFont) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.PREFER_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertTrue(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramUTF8AllowEmbeddingEncodingPreferNotEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(true);

        PdfTrueTypeFont font = (PdfTrueTypeFont) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.PREFER_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramUTF8AllowEmbeddingEncodingForceNotEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(true);

        PdfTrueTypeFont font = (PdfTrueTypeFont) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.FORCE_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramUTF8NotAllowEmbeddingEncodingForceEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(false);

        Exception e = Assertions.assertThrows(PdfException.class,
                () -> PdfFontFactory.createFont(fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.FORCE_EMBEDDED)
        );
        Assertions.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.CANNOT_BE_EMBEDDED_DUE_TO_LICENSING_RESTRICTIONS, "CustomNameCustomStyle"),
                e.getMessage());
    }

    @Test
    public void trueTypeFontProgramUTF8NotAllowEmbeddingEncodingPreferEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(false);

        PdfTrueTypeFont font = (PdfTrueTypeFont) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.PREFER_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramUTF8NotAllowEmbeddingEncodingPreferNotEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(false);

        PdfTrueTypeFont font = (PdfTrueTypeFont) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.PREFER_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramUTF8NotAllowEmbeddingEncodingForceNotEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(false);

        PdfTrueTypeFont font = (PdfTrueTypeFont) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.UTF8, EmbeddingStrategy.FORCE_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramIdentityHAllowEmbeddingEncodingForceEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(true);

        PdfType0Font font = (PdfType0Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.IDENTITY_H, EmbeddingStrategy.FORCE_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertTrue(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramIdentityHAllowEmbeddingEncodingPreferEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(true);

        PdfType0Font font = (PdfType0Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.IDENTITY_H, EmbeddingStrategy.PREFER_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertTrue(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramIdentityHAllowEmbeddingEncodingPreferNotEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(true);

        PdfType0Font font = (PdfType0Font) PdfFontFactory.createFont(
                fontProgram, PdfEncodings.IDENTITY_H, EmbeddingStrategy.PREFER_NOT_EMBEDDED);
        Assertions.assertNotNull(font);
        Assertions.assertTrue(font.isEmbedded());
    }

    @Test
    public void trueTypeFontProgramIdentityHAllowEmbeddingEncodingForceNotEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(true);

        Exception e = Assertions.assertThrows(PdfException.class,
                () -> PdfFontFactory.createFont(fontProgram, PdfEncodings.IDENTITY_H, EmbeddingStrategy.FORCE_NOT_EMBEDDED)
        );
        Assertions.assertEquals(KernelExceptionMessageConstant.CANNOT_CREATE_TYPE_0_FONT_WITH_TRUE_TYPE_FONT_PROGRAM_WITHOUT_EMBEDDING_IT, e.getMessage());
    }

    @Test
    public void trueTypeFontProgramIdentityHNotAllowEmbeddingEncodingForceEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(false);

        Exception e = Assertions.assertThrows(PdfException.class,
                () -> PdfFontFactory.createFont(fontProgram, PdfEncodings.IDENTITY_H, EmbeddingStrategy.FORCE_EMBEDDED)
        );
        Assertions.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.CANNOT_BE_EMBEDDED_DUE_TO_LICENSING_RESTRICTIONS,
                "CustomNameCustomStyle"), e.getMessage());
    }

    @Test
    public void trueTypeFontProgramIdentityHNotAllowEmbeddingEncodingPreferEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(false);

        Exception e = Assertions.assertThrows(PdfException.class,
                () -> PdfFontFactory.createFont(fontProgram, PdfEncodings.IDENTITY_H, EmbeddingStrategy.PREFER_EMBEDDED)
        );
        Assertions.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.CANNOT_BE_EMBEDDED_DUE_TO_LICENSING_RESTRICTIONS,
                "CustomNameCustomStyle"), e.getMessage());
    }

    @Test
    public void trueTypeFontProgramIdentityHNotAllowEmbeddingEncodingPreferNotEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(false);

        Exception e = Assertions.assertThrows(PdfException.class,
                () -> PdfFontFactory.createFont(fontProgram, PdfEncodings.IDENTITY_H, EmbeddingStrategy.PREFER_NOT_EMBEDDED)
        );
        Assertions.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.CANNOT_BE_EMBEDDED_DUE_TO_LICENSING_RESTRICTIONS,
                "CustomNameCustomStyle"), e.getMessage());
    }

    @Test
    public void trueTypeFontProgramIdentityHNotAllowEmbeddingEncodingForceNotEmbeddedTest() {
        TrueTypeFont fontProgram = new CustomTrueTypeFontProgram(false);

        Exception e = Assertions.assertThrows(PdfException.class,
                () -> PdfFontFactory.createFont(fontProgram, PdfEncodings.IDENTITY_H, EmbeddingStrategy.FORCE_NOT_EMBEDDED)
        );
        Assertions.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.CANNOT_BE_EMBEDDED_DUE_TO_LICENSING_RESTRICTIONS,
                "CustomNameCustomStyle"), e.getMessage());
    }

    @Test
    public void standardFontCachedWithoutDocumentTest() throws IOException {
        // this test ensures that method which allows caching into the document does not fail
        // if the document is null
        PdfDocument cacheTo = null;
        PdfType1Font font = (PdfType1Font) PdfFontFactory.createFont(
                StandardFonts.HELVETICA, PdfEncodings.UTF8, cacheTo);
        Assertions.assertNotNull(font);
        Assertions.assertFalse(font.isEmbedded());
    }

    @Test
    public void createFontFromNullDictionaryTest() {
        PdfDictionary dictionary = null;

        Exception e = Assertions.assertThrows(PdfException.class, () -> PdfFontFactory.createFont(dictionary));
        Assertions.assertEquals(KernelExceptionMessageConstant.CANNOT_CREATE_FONT_FROM_NULL_PDF_DICTIONARY, e.getMessage());
    }

    @Test
    public void createFontFromEmptyDictionaryTest() {
        PdfDictionary dictionary = new PdfDictionary();

        Exception e = Assertions.assertThrows(PdfException.class, () -> PdfFontFactory.createFont(dictionary));
        Assertions.assertEquals(KernelExceptionMessageConstant.DICTIONARY_DOES_NOT_HAVE_SUPPORTED_FONT_DATA, e.getMessage());
    }

    private static class CustomType1FontProgram extends Type1Font {
        @Override
        public boolean isBuiltInFont() {
            return false;
        }
    }

    private static class CustomTrueTypeFontProgram extends TrueTypeFont {
        public CustomTrueTypeFontProgram(boolean allowEmbedding) {
            this.fontNames = new CustomFontNames(allowEmbedding);
        }
    }

    private static class CustomFontNames extends FontNames {
        public CustomFontNames(boolean allowEmbedding) {
            this.setAllowEmbedding(allowEmbedding);
            this.setFontName("CustomName");
            this.setStyle("CustomStyle");
        }
    }
}