#include "fonts_test_helpers.hpp" namespace pdfengine::fonts { TEST(EncodingTest, PredefinedEncodingTest) { using namespace pdfengine::fonts::pdf_fonts; PredefinedEncoding winAnsi(SimpleEncodingType::WinAnsi); EXPECT_EQ(winAnsi.getType(), SimpleEncodingType::WinAnsi); EXPECT_EQ(winAnsi.decode(65), 65); EXPECT_EQ(winAnsi.decode(128), 0x20AC); EXPECT_EQ(winAnsi.decode(169), 169); EXPECT_EQ(winAnsi.decode(300), 0); PredefinedEncoding macRoman(SimpleEncodingType::MacRoman); EXPECT_EQ(macRoman.getType(), SimpleEncodingType::MacRoman); EXPECT_EQ(macRoman.decode(65), 65); EXPECT_EQ(macRoman.decode(128), 0x00C4); EXPECT_EQ(macRoman.decode(300), 0); PredefinedEncoding identity(SimpleEncodingType::Identity); EXPECT_EQ(identity.getType(), SimpleEncodingType::Identity); EXPECT_EQ(identity.decode(65), 65); EXPECT_EQ(identity.decode(128), 128); EXPECT_EQ(identity.decode(1000), 1000); } TEST(EncodingTest, CustomEncodingWithDifferences) { using namespace pdfengine::fonts::pdf_fonts; auto baseEncoding = std::make_unique(SimpleEncodingType::WinAnsi); CustomEncoding custom(std::move(baseEncoding)); EXPECT_EQ(custom.decode(65), 65); custom.addDifference(120, "quotesingle"); EXPECT_EQ(custom.decode(120), 0x0027); custom.addDifference(121, "uni0041"); EXPECT_EQ(custom.decode(121), 0x0041); custom.addDifference(122, "u0042"); EXPECT_EQ(custom.decode(122), 0x0042); custom.addDifference(123, "nonexistentglyphname123"); EXPECT_EQ(custom.decode(123), 123); } TEST(EncodingTest, ToUnicodeCMapbfchar) { using namespace pdfengine::fonts::pdf_fonts; ToUnicodeCMap cmap; std::string cmapStream = "/CIDInit /ProcSet findresource begin\n" "12 dict begin\n" "begincmap\n" "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n" "/CMapName /Custom-ToUnicode def\n" "1 begincodespacerange\n" "<0000> \n" "endcodespacerange\n" "2 beginbfchar\n" "<0001> <0041>\n" "<0002> <0042>\n" "endbfchar\n" "endcmap\n" "CMapName currentdict /CMap defineresource pop\n" "end\n" "end\n"; ASSERT_TRUE(cmap.parseCMapStream(cmapStream)); EXPECT_EQ(cmap.decode(1), 0x0041); EXPECT_EQ(cmap.decode(2), 0x0042); EXPECT_EQ(cmap.decode(3), 0); } TEST(EncodingTest, ToUnicodeCMapbfrange) { using namespace pdfengine::fonts::pdf_fonts; ToUnicodeCMap cmap; std::string cmapStream = "begincmap\n" "2 beginbfrange\n" "<0001> <0005> <0041>\n" "<0010> <0012> [<0061> <0062> <0063>]\n" "endbfrange\n" "endcmap\n"; ASSERT_TRUE(cmap.parseCMapStream(cmapStream)); EXPECT_EQ(cmap.decode(1), 0x0041); EXPECT_EQ(cmap.decode(3), 0x0043); EXPECT_EQ(cmap.decode(5), 0x0045); EXPECT_EQ(cmap.decode(0x10), 0x0061); EXPECT_EQ(cmap.decode(0x11), 0x0062); EXPECT_EQ(cmap.decode(0x12), 0x0063); } TEST(EncodingTest, ToUnicodeMalformedCMap) { using namespace pdfengine::fonts::pdf_fonts; ToUnicodeCMap cmap; std::string garbageStream = "This is a garbage string with no valid CMap elements"; EXPECT_FALSE(cmap.parseCMapStream(garbageStream)); std::string partialStream = "begincmap\n" "beginbfchar\n" "<0001> <0041>\n" "<0002> /invalid\n" "<0003> <0043>\n" "endbfchar\n" "endcmap\n"; EXPECT_TRUE(cmap.parseCMapStream(partialStream)); EXPECT_EQ(cmap.decode(1), 0x0041); EXPECT_EQ(cmap.decode(2), 0); EXPECT_EQ(cmap.decode(3), 0x0043); } TEST(EncodingTest, FontLoaderWithEncoding) { std::string fontPath = getSystemFontPath(); if (fontPath.empty()) { GTEST_SKIP() << "No system font found to run PDF font loader with encoding test."; } std::ifstream file(fontPath, std::ios::binary | std::ios::ate); ASSERT_TRUE(file.is_open()); std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); std::vector buffer(size); ASSERT_TRUE(file.read(reinterpret_cast(buffer.data()), size)); auto encoding = std::make_unique( pdfengine::fonts::pdf_fonts::SimpleEncodingType::WinAnsi ); auto pdfFont = pdfengine::fonts::pdf_fonts::FontLoader::loadTrueTypeFromMemory( "Arial-With-Encoding", buffer, nullptr, std::move(encoding) ); ASSERT_NE(pdfFont, nullptr); EXPECT_EQ(pdfFont->getBaseFont(), "Arial-With-Encoding"); const auto* retrievedEncoding = pdfFont->getEncoding(); ASSERT_NE(retrievedEncoding, nullptr); EXPECT_EQ(retrievedEncoding->decode(128), 0x20AC); } TEST(FontSubsetTest, SubsetTagParsingAndStripping) { using namespace pdfengine::fonts::pdf_fonts; std::string subsetName = "KTJHQO+Arial"; EXPECT_TRUE(FontSubset::hasSubsetPrefix(subsetName)); EXPECT_EQ(FontSubset::getSubsetPrefix(subsetName), "KTJHQO"); EXPECT_EQ(FontSubset::stripSubsetPrefix(subsetName), "Arial"); std::string normalName = "Arial"; EXPECT_FALSE(FontSubset::hasSubsetPrefix(normalName)); EXPECT_EQ(FontSubset::getSubsetPrefix(normalName), ""); EXPECT_EQ(FontSubset::stripSubsetPrefix(normalName), "Arial"); } TEST(FontSubsetTest, PrefixFormatValidation) { using namespace pdfengine::fonts::pdf_fonts; EXPECT_TRUE(FontSubset::hasSubsetPrefix("ABCDEF+Helvetica")); EXPECT_FALSE(FontSubset::hasSubsetPrefix("abcDEF+Helvetica")); EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCdef+Helvetica")); EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABC123+Helvetica")); EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDE_+Helvetica")); EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDE+Helvetica")); EXPECT_FALSE(FontSubset::hasSubsetPrefix("ABCDEFG+Helvetica")); } TEST(FontSubsetTest, GIDRemappingTranslations) { using namespace pdfengine::fonts::pdf_fonts; FontSubset subset("KTJHQO+Arial"); EXPECT_TRUE(subset.isSubset()); EXPECT_EQ(subset.getFullFontName(), "KTJHQO+Arial"); EXPECT_EQ(subset.getBaseFontName(), "Arial"); EXPECT_EQ(subset.getPrefix(), "KTJHQO"); EXPECT_EQ(subset.mapSubsetToOriginal(5), 5); EXPECT_FALSE(subset.hasGlyphMapping(5)); subset.addGlyphMapping(1, 41); subset.addGlyphMapping(2, 42); subset.addGlyphMapping(3, 43); EXPECT_EQ(subset.getMappingCount(), 3u); EXPECT_TRUE(subset.hasGlyphMapping(1)); EXPECT_TRUE(subset.hasGlyphMapping(2)); EXPECT_EQ(subset.mapSubsetToOriginal(1), 41); EXPECT_EQ(subset.mapSubsetToOriginal(2), 42); EXPECT_EQ(subset.mapSubsetToOriginal(3), 43); EXPECT_EQ(subset.mapSubsetToOriginal(4), 4); } TEST(FontSubsetTest, CoreFontSubsettingIntegration) { using namespace pdfengine::fonts::pdf_fonts; TrueTypeFont fontTT("KTJHQO+Arial", false); const auto* ttSubset = fontTT.getSubsetInfo(); ASSERT_NE(ttSubset, nullptr); EXPECT_TRUE(ttSubset->isSubset()); EXPECT_EQ(ttSubset->getBaseFontName(), "Arial"); EXPECT_EQ(ttSubset->getPrefix(), "KTJHQO"); Type1Font fontT1("SUBSET+Courier", false); const auto* t1Subset = fontT1.getSubsetInfo(); ASSERT_NE(t1Subset, nullptr); EXPECT_TRUE(t1Subset->isSubset()); EXPECT_EQ(t1Subset->getBaseFontName(), "Courier"); EXPECT_EQ(t1Subset->getPrefix(), "SUBSET"); CIDFont fontCID("CJKTAG+SimSun", FontType::CIDFontType2, false); const auto* cidSubset = fontCID.getSubsetInfo(); ASSERT_NE(cidSubset, nullptr); EXPECT_TRUE(cidSubset->isSubset()); EXPECT_EQ(cidSubset->getBaseFontName(), "SimSun"); EXPECT_EQ(cidSubset->getPrefix(), "CJKTAG"); } TEST(TextExtractionLayerTest, SimpleCharacterDecoding) { using namespace pdfengine::fonts::pdf_fonts; auto font = FontLoader::loadType1SystemFallback("Helvetica"); ASSERT_NE(font, nullptr); EXPECT_EQ(font->decodeToUnicode(65), 65u); EXPECT_EQ(font->decodeToUnicode(97), 97u); EXPECT_EQ(font->decodeToUnicode(48), 48u); } TEST(TextExtractionLayerTest, EncodingAndToUnicodeDecoding) { using namespace pdfengine::fonts::pdf_fonts; auto baseEncoding = std::make_unique(SimpleEncodingType::WinAnsi); auto customEnc = std::make_unique(std::move(baseEncoding)); customEnc->addDifference(128, "euro"); auto font = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(customEnc)); ASSERT_NE(font, nullptr); EXPECT_EQ(font->decodeToUnicode(128), 0x20ACu); auto cmap = std::make_unique(); cmap->addMapping(1, 0x0041); cmap->addMapping(2, 0x0042); cmap->addMapping(3, 0x20AC); auto fontCMap = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(cmap)); ASSERT_NE(fontCMap, nullptr); EXPECT_EQ(fontCMap->decodeToUnicode(1), 0x0041u); EXPECT_EQ(fontCMap->decodeToUnicode(2), 0x0042u); EXPECT_EQ(fontCMap->decodeToUnicode(3), 0x20ACu); } TEST(TextExtractionLayerTest, SubsetFontTextExtraction) { using namespace pdfengine::fonts::pdf_fonts; auto font = FontLoader::loadType1SystemFallback("KTJHQO+Helvetica"); ASSERT_NE(font, nullptr); const auto* subsetConst = font->getSubsetInfo(); ASSERT_NE(subsetConst, nullptr); auto* subset = const_cast(subsetConst); FT_Face face = font->getFontFace().getFace(); ASSERT_NE(face, nullptr); FT_UInt originalGid = FT_Get_Char_Index(face, 'A'); ASSERT_GT(originalGid, 0u); subset->addGlyphMapping(5, originalGid); EXPECT_EQ(font->decodeToUnicode(5), 65u); } TEST(TextExtractionLayerTest, CIDFontTextExtraction) { using namespace pdfengine::fonts::pdf_fonts; auto font = FontLoader::loadCIDFontSystemFallback("SimSun", FontType::CIDFontType2); ASSERT_NE(font, nullptr); CIDFont* cidFont = dynamic_cast(font.get()); ASSERT_NE(cidFont, nullptr); FT_Face face = cidFont->getFontFace().getFace(); ASSERT_NE(face, nullptr); FT_UInt gid65 = 65; FT_ULong expectedChar65 = 0; FT_UInt gindex; FT_ULong charcode = FT_Get_First_Char(face, &gindex); while (gindex != 0) { if (gindex == gid65) { expectedChar65 = charcode; break; } charcode = FT_Get_Next_Char(face, charcode, &gindex); } if (expectedChar65 != 0) { EXPECT_EQ(cidFont->decodeToUnicode(65), expectedChar65); } else { EXPECT_EQ(cidFont->decodeToUnicode(65), 65u); } FT_UInt gidA = FT_Get_Char_Index(face, 'A'); if (gidA > 0) { std::unordered_map cidToGid = { {500, gidA} }; cidFont->setCIDToGIDMap(cidToGid); EXPECT_EQ(cidFont->decodeToUnicode(500), 65u); } } TEST(TextExtractionLayerTest, StringUtf8Conversion) { using namespace pdfengine::fonts::pdf_fonts; auto font = FontLoader::loadType1SystemFallback("Helvetica"); ASSERT_NE(font, nullptr); std::vector codes = {65, 66, 67}; EXPECT_EQ(font->decodeStringToUnicode(codes), "ABC"); auto cmap = std::make_unique(); cmap->addMapping(10, 0x65E5); cmap->addMapping(11, 0x672C); cmap->addMapping(12, 0x8A9E); auto fontCMap = FontLoader::loadType1SystemFallback("Helvetica", nullptr, std::move(cmap)); ASSERT_NE(fontCMap, nullptr); std::vector cjkCodes = {10, 11, 12}; std::string decodedCjk = fontCMap->decodeStringToUnicode(cjkCodes); EXPECT_EQ(decodedCjk, "日本語"); } }