Files
pdf/engine/tests/font_encoding_test.cpp
T
2026-06-29 10:51:04 +05:30

352 lines
12 KiB
C++

#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<PredefinedEncoding>(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> <FFFF>\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<uint8_t> buffer(size);
ASSERT_TRUE(file.read(reinterpret_cast<char*>(buffer.data()), size));
auto encoding = std::make_unique<pdfengine::fonts::pdf_fonts::PredefinedEncoding>(
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<PredefinedEncoding>(SimpleEncodingType::WinAnsi);
auto customEnc = std::make_unique<CustomEncoding>(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<ToUnicodeCMap>();
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<FontSubset*>(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<CIDFont*>(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<uint32_t, uint32_t> 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<uint32_t> codes = {65, 66, 67};
EXPECT_EQ(font->decodeStringToUnicode(codes), "ABC");
auto cmap = std::make_unique<ToUnicodeCMap>();
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<uint32_t> cjkCodes = {10, 11, 12};
std::string decodedCjk = fontCMap->decodeStringToUnicode(cjkCodes);
EXPECT_EQ(decodedCjk, "日本語");
}
}