PdfType0FontTest.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.CMapEncoding;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.font.TrueTypeFont;
import com.itextpdf.io.font.otf.Glyph;
import com.itextpdf.io.font.otf.GlyphLine;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.pdf.*;
import com.itextpdf.test.ExtendedITextTest;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;

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

    public static final String DESTINATION_FOLDER = "./target/test/resources/com/itextpdf/kernel/font/PdfType0FontTest/";
    public static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/font/PdfType0FontTest/";

    @Test
    public void trueTypeFontAndCmapConstructorTest() throws IOException {
        TrueTypeFont ttf = new TrueTypeFont(SOURCE_FOLDER + "NotoSerif-Regular_v1.7.ttf");

        PdfType0Font type0Font = new PdfType0Font(ttf, PdfEncodings.IDENTITY_H);

        CMapEncoding cmap = type0Font.getCmap();

        Assertions.assertNotNull(cmap);
        Assertions.assertTrue(cmap.isDirect());
        Assertions.assertFalse(cmap.hasUniMap());
        Assertions.assertNull(cmap.getUniMapName());
        Assertions.assertEquals("Adobe", cmap.getRegistry());
        Assertions.assertEquals("Identity", cmap.getOrdering());
        Assertions.assertEquals(0, cmap.getSupplement());
        Assertions.assertEquals(PdfEncodings.IDENTITY_H, cmap.getCmapName());
    }

    @Test
    public void unsupportedCmapTest() throws IOException {
        TrueTypeFont ttf = new TrueTypeFont(SOURCE_FOLDER + "NotoSerif-Regular_v1.7.ttf");

        Exception e = Assertions.assertThrows(PdfException.class,
                () -> new PdfType0Font(ttf, PdfEncodings.WINANSI)
        );
        Assertions.assertEquals(KernelExceptionMessageConstant.ONLY_IDENTITY_CMAPS_SUPPORTS_WITH_TRUETYPE, e.getMessage());
    }

    @Test
    public void dictionaryConstructorTest() throws IOException {
        String filePath = SOURCE_FOLDER + "documentWithType0Noto.pdf";

        PdfDocument pdfDocument = new PdfDocument(new PdfReader(filePath));

        PdfDictionary fontDict = pdfDocument.getPage(1).getResources()
                .getResource(PdfName.Font).getAsDictionary(new PdfName("F1"));

        PdfType0Font type0Font = new PdfType0Font(fontDict);

        CMapEncoding cmap = type0Font.getCmap();

        Assertions.assertNotNull(cmap);
        Assertions.assertTrue(cmap.isDirect());
        Assertions.assertFalse(cmap.hasUniMap());
        Assertions.assertNull(cmap.getUniMapName());
        Assertions.assertEquals("Adobe", cmap.getRegistry());
        Assertions.assertEquals("Identity", cmap.getOrdering());
        Assertions.assertEquals(0, cmap.getSupplement());
        Assertions.assertEquals(PdfEncodings.IDENTITY_H, cmap.getCmapName());
    }

    @Test
    public void appendThreeSurrogatePairsTest() throws IOException {
        // this text contains three successive surrogate pairs, which should result in three glyphs
        String textWithThreeSurrogatePairs = "\uD800\uDF10\uD800\uDF00\uD800\uDF11";
        PdfFont type0Font =
                PdfFontFactory.createFont(SOURCE_FOLDER + "NotoSansOldItalic-Regular.ttf", PdfEncodings.IDENTITY_H);

        List<Glyph> glyphs = new ArrayList<>();
        type0Font.appendGlyphs(textWithThreeSurrogatePairs, 0, textWithThreeSurrogatePairs.length() - 1, glyphs);
        Assertions.assertEquals(3, glyphs.size());
    }

    @Test
    public void getUniMapFromOrderingTest() {
        Assertions.assertEquals("UniCNS-UTF16-H", PdfType0Font.getUniMapFromOrdering("CNS1", true));
        Assertions.assertEquals("UniCNS-UTF16-V", PdfType0Font.getUniMapFromOrdering("CNS1", false));

        Assertions.assertEquals("UniJIS-UTF16-H", PdfType0Font.getUniMapFromOrdering("Japan1", true));
        Assertions.assertEquals("UniJIS-UTF16-V", PdfType0Font.getUniMapFromOrdering("Japan1", false));

        Assertions.assertEquals("UniKS-UTF16-H", PdfType0Font.getUniMapFromOrdering("Korea1", true));
        Assertions.assertEquals("UniKS-UTF16-V", PdfType0Font.getUniMapFromOrdering("Korea1", false));

        Assertions.assertEquals("UniGB-UTF16-H", PdfType0Font.getUniMapFromOrdering("GB1", true));
        Assertions.assertEquals("UniGB-UTF16-V", PdfType0Font.getUniMapFromOrdering("GB1", false));

        Assertions.assertEquals("Identity-H", PdfType0Font.getUniMapFromOrdering("Identity", true));
        Assertions.assertEquals("Identity-V", PdfType0Font.getUniMapFromOrdering("Identity", false));
    }

    @Test
    public void descendantCidFontWithoutOrderingTest() {
        PdfDictionary fontDict = new PdfDictionary();
        PdfArray descendantFonts = new PdfArray();
        PdfDictionary descendantFont = new PdfDictionary();
        descendantFont.put(PdfName.CIDSystemInfo, new PdfDictionary());
        descendantFonts.add(descendantFont);
        fontDict.put(PdfName.DescendantFonts, descendantFonts);


        Exception e = Assertions.assertThrows(PdfException.class, () -> new PdfType0Font(fontDict));
        Assertions.assertEquals(KernelExceptionMessageConstant.ORDERING_SHOULD_BE_DETERMINED, e.getMessage());
    }


    private static List<Glyph> constructGlyphListFromString(String text, PdfFont font) {
        List<Glyph> glyphList = new ArrayList<>(text.length());
        char[] chars = text.toCharArray();
        for (char letter : chars) {
            glyphList.add(font.getGlyph(letter));
        }
        return glyphList;
    }

    @Test
    public void convertToBytesNoEncoderTest() throws IOException {
        byte[] expected = "������������".getBytes(StandardCharsets.UTF_16BE);

        PdfFont font = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
        GlyphLine line = new GlyphLine(constructGlyphListFromString("\u5341\u950a\u57cb\u4f0f", font));
        byte[] result = font.convertToBytes(line);
        Assertions.assertArrayEquals(expected, result);
    }

}