352 lines
12 KiB
C++
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, "日本語");
|
||
|
|
}
|
||
|
|
}
|