454 lines
14 KiB
C++
454 lines
14 KiB
C++
#include "fonts_test_helpers.hpp"
|
|
|
|
namespace pdfengine::fonts {
|
|
|
|
TEST(FontTest, FontFaceInitialization) {
|
|
FontFace face;
|
|
EXPECT_EQ(face.getFace(), nullptr);
|
|
}
|
|
|
|
TEST(FontTest, FontFaceLoadNonExistentFile) {
|
|
FontFace face;
|
|
EXPECT_FALSE(face.loadFromFile("this_file_does_not_exist_12345.ttf"));
|
|
EXPECT_EQ(face.getFace(), nullptr);
|
|
}
|
|
|
|
TEST(FontTest, FontFaceMoveSemantics) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run move semantics test.";
|
|
}
|
|
|
|
FontFace face1;
|
|
ASSERT_TRUE(face1.loadFromFile(fontPath));
|
|
FT_Face rawFace = face1.getFace();
|
|
ASSERT_NE(rawFace, nullptr);
|
|
|
|
FontFace face2(std::move(face1));
|
|
EXPECT_EQ(face1.getFace(), nullptr);
|
|
EXPECT_EQ(face2.getFace(), rawFace);
|
|
|
|
FontFace face3;
|
|
face3 = std::move(face2);
|
|
EXPECT_EQ(face2.getFace(), nullptr);
|
|
EXPECT_EQ(face3.getFace(), rawFace);
|
|
}
|
|
|
|
TEST(FontTest, HbShaperEmptyInput) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run empty input shaper test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
HbShaper shaper;
|
|
auto glyphs = shaper.shapeRun("", face, 16);
|
|
EXPECT_TRUE(glyphs.empty());
|
|
}
|
|
|
|
TEST(FontTest, HbShaperNullFace) {
|
|
FontFace face;
|
|
HbShaper shaper;
|
|
auto glyphs = shaper.shapeRun("Hello", face, 16);
|
|
EXPECT_TRUE(glyphs.empty());
|
|
}
|
|
|
|
TEST(FontTest, HbShaperShapeTextSuccess) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
std::cout << "[ WARNING ] Skipping shape success test: no system font found." << std::endl;
|
|
GTEST_SKIP() << "No system font found to run text shaping test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
ASSERT_NE(face.getFace(), nullptr);
|
|
|
|
HbShaper shaper;
|
|
std::string testText = "Hello World!";
|
|
auto glyphs = shaper.shapeRun(testText, face, 16);
|
|
|
|
EXPECT_FALSE(glyphs.empty());
|
|
|
|
for (const auto& g : glyphs) {
|
|
EXPECT_GT(g.advanceX, 0.0);
|
|
}
|
|
}
|
|
|
|
TEST(FontTest, FontFaceRenderGlyphNullFace) {
|
|
FontFace face;
|
|
auto glyph = face.renderGlyph(0, 16);
|
|
EXPECT_FALSE(glyph.has_value());
|
|
}
|
|
|
|
TEST(FontTest, FontFaceRenderGlyphSuccess) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run render glyph success test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
ASSERT_NE(face.getFace(), nullptr);
|
|
|
|
unsigned int glyphIndex = FT_Get_Char_Index(face.getFace(), 'A');
|
|
ASSERT_GT(glyphIndex, 0u);
|
|
|
|
auto glyphOpt = face.renderGlyph(glyphIndex, 24);
|
|
ASSERT_TRUE(glyphOpt.has_value());
|
|
|
|
const auto& glyph = *glyphOpt;
|
|
EXPECT_GT(glyph.width, 0);
|
|
EXPECT_GT(glyph.height, 0);
|
|
EXPECT_EQ(glyph.pixels.size(), static_cast<size_t>(glyph.width * glyph.height));
|
|
EXPECT_GT(glyph.advance, 0.0);
|
|
}
|
|
|
|
TEST(FontTest, GlyphCacheBasicGetInsert) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run cache test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
GlyphCache cache(10);
|
|
EXPECT_EQ(cache.capacity(), 10u);
|
|
EXPECT_EQ(cache.size(), 0u);
|
|
|
|
auto miss = cache.get(face, 12, 16);
|
|
EXPECT_FALSE(miss.has_value());
|
|
|
|
GlyphBitmap bitmap;
|
|
bitmap.width = 10;
|
|
bitmap.height = 12;
|
|
bitmap.pixels = std::vector<unsigned char>(120, 255);
|
|
bitmap.advance = 8.5;
|
|
|
|
cache.insert(face, 12, 16, bitmap);
|
|
EXPECT_EQ(cache.size(), 1u);
|
|
|
|
auto hit = cache.get(face, 12, 16);
|
|
ASSERT_TRUE(hit.has_value());
|
|
EXPECT_EQ(hit->width, 10);
|
|
EXPECT_EQ(hit->height, 12);
|
|
EXPECT_EQ(hit->advance, 8.5);
|
|
EXPECT_EQ(hit->pixels.size(), 120u);
|
|
}
|
|
|
|
TEST(FontTest, GlyphCacheEvictionPolicy) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run cache eviction test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
GlyphCache cache(2);
|
|
|
|
GlyphBitmap bmp1{ .width = 1 };
|
|
GlyphBitmap bmp2{ .width = 2 };
|
|
GlyphBitmap bmp3{ .width = 3 };
|
|
|
|
cache.insert(face, 1, 16, bmp1);
|
|
cache.insert(face, 2, 16, bmp2);
|
|
EXPECT_EQ(cache.size(), 2u);
|
|
|
|
cache.insert(face, 3, 16, bmp3);
|
|
EXPECT_EQ(cache.size(), 2u);
|
|
|
|
EXPECT_FALSE(cache.get(face, 1, 16).has_value());
|
|
EXPECT_TRUE(cache.get(face, 2, 16).has_value());
|
|
EXPECT_TRUE(cache.get(face, 3, 16).has_value());
|
|
}
|
|
|
|
TEST(FontTest, GlyphCacheLRUPolicy) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run cache LRU test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
GlyphCache cache(2);
|
|
|
|
GlyphBitmap bmp1{ .width = 1 };
|
|
GlyphBitmap bmp2{ .width = 2 };
|
|
GlyphBitmap bmp3{ .width = 3 };
|
|
|
|
cache.insert(face, 1, 16, bmp1);
|
|
cache.insert(face, 2, 16, bmp2);
|
|
|
|
auto hit = cache.get(face, 1, 16);
|
|
ASSERT_TRUE(hit.has_value());
|
|
|
|
cache.insert(face, 3, 16, bmp3);
|
|
EXPECT_EQ(cache.size(), 2u);
|
|
|
|
EXPECT_TRUE(cache.get(face, 1, 16).has_value());
|
|
EXPECT_FALSE(cache.get(face, 2, 16).has_value());
|
|
EXPECT_TRUE(cache.get(face, 3, 16).has_value());
|
|
}
|
|
|
|
TEST(FontTest, FontPipelineIntegration) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run pipeline integration test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
HbShaper shaper;
|
|
GlyphCache cache(100);
|
|
|
|
std::vector<std::string> testTexts = {"Hello World", "office", "سلام"};
|
|
unsigned int fontSize = 16;
|
|
|
|
for (const auto& text : testTexts) {
|
|
auto shapedGlyphs = shaper.shapeRun(text, face, fontSize);
|
|
EXPECT_FALSE(shapedGlyphs.empty());
|
|
|
|
for (const auto& sg : shapedGlyphs) {
|
|
auto cachedBmp = cache.get(face, sg.glyphIndex, fontSize);
|
|
if (!cachedBmp.has_value()) {
|
|
auto renderedOpt = face.renderGlyph(sg.glyphIndex, fontSize);
|
|
ASSERT_TRUE(renderedOpt.has_value());
|
|
cache.insert(face, sg.glyphIndex, fontSize, *renderedOpt);
|
|
EXPECT_EQ(renderedOpt->pixels.size(), static_cast<size_t>(renderedOpt->width * renderedOpt->height));
|
|
}
|
|
|
|
auto hitBmp = cache.get(face, sg.glyphIndex, fontSize);
|
|
ASSERT_TRUE(hitBmp.has_value());
|
|
EXPECT_EQ(hitBmp->pixels.size(), static_cast<size_t>(hitBmp->width * hitBmp->height));
|
|
EXPECT_GE(hitBmp->advance, 0.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(FontTest, UnicodeAndRtlShaping) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run Unicode and RTL shaping test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
HbShaper shaper;
|
|
unsigned int fontSize = 16;
|
|
|
|
{
|
|
std::string arabicText = "سلام";
|
|
auto glyphs = shaper.shapeRun(arabicText, face, fontSize);
|
|
EXPECT_FALSE(glyphs.empty());
|
|
for (const auto& g : glyphs) {
|
|
EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.offsetX != 0.0 || g.offsetY != 0.0 || g.glyphIndex != 999999u);
|
|
}
|
|
}
|
|
|
|
{
|
|
std::string hindiText = "नमस्ते";
|
|
auto glyphs = shaper.shapeRun(hindiText, face, fontSize);
|
|
EXPECT_FALSE(glyphs.empty());
|
|
for (const auto& g : glyphs) {
|
|
EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.glyphIndex != 999999u);
|
|
}
|
|
}
|
|
|
|
{
|
|
std::string ligatureText = "office";
|
|
auto glyphs = shaper.shapeRun(ligatureText, face, fontSize);
|
|
EXPECT_FALSE(glyphs.empty());
|
|
EXPECT_LE(glyphs.size(), ligatureText.length());
|
|
for (const auto& g : glyphs) {
|
|
EXPECT_TRUE(g.advanceX != 0.0 || g.advanceY != 0.0 || g.glyphIndex != 999999u);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(FontTest, CachePerformanceTest) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run cache performance test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
HbShaper shaper;
|
|
GlyphCache cache(100);
|
|
|
|
std::string text = "Hello Hello Hello Hello";
|
|
unsigned int fontSize = 16;
|
|
|
|
{
|
|
auto glyphs = shaper.shapeRun(text, face, fontSize);
|
|
for (const auto& g : glyphs) {
|
|
auto cached = cache.get(face, g.glyphIndex, fontSize);
|
|
if (!cached.has_value()) {
|
|
auto rendered = face.renderGlyph(g.glyphIndex, fontSize);
|
|
if (rendered.has_value()) {
|
|
cache.insert(face, g.glyphIndex, fontSize, *rendered);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cache.resetStats();
|
|
|
|
for (int i = 0; i < 1000; ++i) {
|
|
auto glyphs = shaper.shapeRun(text, face, fontSize);
|
|
for (const auto& g : glyphs) {
|
|
auto cached = cache.get(face, g.glyphIndex, fontSize);
|
|
if (!cached.has_value()) {
|
|
auto rendered = face.renderGlyph(g.glyphIndex, fontSize);
|
|
if (rendered.has_value()) {
|
|
cache.insert(face, g.glyphIndex, fontSize, *rendered);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
double hitRateVal = cache.hitRate();
|
|
std::cout << "[ INFO ] Cache Hit Rate for repetitive text: " << (hitRateVal * 100.0) << "%" << std::endl;
|
|
EXPECT_GT(hitRateVal, 0.90);
|
|
}
|
|
|
|
TEST(FontTest, EngineStressTest) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run engine stress test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
HbShaper shaper;
|
|
GlyphCache cache(32);
|
|
|
|
std::string base = "The quick brown fox jumps over the lazy dog. 1234567890!@#$%^&*() ";
|
|
std::string longText;
|
|
longText.reserve(10000);
|
|
while (longText.length() < 10000) {
|
|
longText += base;
|
|
}
|
|
|
|
unsigned int fontSize = 16;
|
|
|
|
auto glyphs = shaper.shapeRun(longText, face, fontSize);
|
|
EXPECT_FALSE(glyphs.empty());
|
|
|
|
for (const auto& g : glyphs) {
|
|
auto cached = cache.get(face, g.glyphIndex, fontSize);
|
|
if (!cached.has_value()) {
|
|
auto rendered = face.renderGlyph(g.glyphIndex, fontSize);
|
|
if (rendered.has_value()) {
|
|
cache.insert(face, g.glyphIndex, fontSize, *rendered);
|
|
}
|
|
}
|
|
}
|
|
|
|
EXPECT_LE(cache.size(), cache.capacity());
|
|
EXPECT_GT(cache.size(), 0u);
|
|
}
|
|
|
|
TEST(FontTest, VisualBitmapDebugging) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run visual bitmap debugging test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
unsigned int glyphIndex = FT_Get_Char_Index(face.getFace(), 'A');
|
|
ASSERT_GT(glyphIndex, 0u);
|
|
|
|
auto renderedOpt = face.renderGlyph(glyphIndex, 48);
|
|
ASSERT_TRUE(renderedOpt.has_value());
|
|
|
|
std::string filename = "A.pgm";
|
|
std::filesystem::remove(filename);
|
|
|
|
ASSERT_TRUE(saveGlyphAsPGM(*renderedOpt, filename));
|
|
EXPECT_TRUE(std::filesystem::exists(filename));
|
|
EXPECT_GT(std::filesystem::file_size(filename), 0u);
|
|
}
|
|
|
|
TEST(FontTest, MetricsValidation) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run metrics validation test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
FT_Face ftFace = face.getFace();
|
|
ASSERT_NE(ftFace, nullptr);
|
|
|
|
unsigned int glyphIndex = FT_Get_Char_Index(ftFace, 'B');
|
|
ASSERT_GT(glyphIndex, 0u);
|
|
|
|
unsigned int fontSize = 24;
|
|
auto renderedOpt = face.renderGlyph(glyphIndex, fontSize);
|
|
ASSERT_TRUE(renderedOpt.has_value());
|
|
|
|
ASSERT_EQ(FT_Set_Pixel_Sizes(ftFace, 0, fontSize), 0);
|
|
ASSERT_EQ(FT_Load_Glyph(ftFace, glyphIndex, FT_LOAD_RENDER), 0);
|
|
|
|
FT_GlyphSlot slot = ftFace->glyph;
|
|
EXPECT_EQ(renderedOpt->width, static_cast<int>(slot->bitmap.width));
|
|
EXPECT_EQ(renderedOpt->height, static_cast<int>(slot->bitmap.rows));
|
|
EXPECT_EQ(renderedOpt->bearingX, slot->bitmap_left);
|
|
EXPECT_EQ(renderedOpt->bearingY, slot->bitmap_top);
|
|
EXPECT_DOUBLE_EQ(renderedOpt->advance, static_cast<double>(slot->advance.x) / 64.0);
|
|
}
|
|
|
|
TEST(FontTest, CacheRecencyStress) {
|
|
std::string fontPath = getSystemFontPath();
|
|
if (fontPath.empty()) {
|
|
GTEST_SKIP() << "No system font found to run cache recency stress test.";
|
|
}
|
|
|
|
FontFace face;
|
|
ASSERT_TRUE(face.loadFromFile(fontPath));
|
|
|
|
GlyphCache cache(5);
|
|
|
|
std::vector<GlyphBitmap> bmps;
|
|
for (int i = 0; i < 10; ++i) {
|
|
GlyphBitmap b;
|
|
b.width = i;
|
|
bmps.push_back(b);
|
|
}
|
|
|
|
for (unsigned int i = 0; i < 5; ++i) {
|
|
cache.insert(face, i, 16, bmps[i]);
|
|
}
|
|
EXPECT_EQ(cache.size(), 5u);
|
|
|
|
ASSERT_TRUE(cache.get(face, 0, 16).has_value());
|
|
ASSERT_TRUE(cache.get(face, 2, 16).has_value());
|
|
|
|
cache.insert(face, 5, 16, bmps[5]);
|
|
EXPECT_FALSE(cache.get(face, 1, 16).has_value());
|
|
EXPECT_TRUE(cache.get(face, 5, 16).has_value());
|
|
|
|
ASSERT_TRUE(cache.get(face, 3, 16).has_value());
|
|
|
|
cache.insert(face, 6, 16, bmps[6]);
|
|
EXPECT_FALSE(cache.get(face, 4, 16).has_value());
|
|
|
|
EXPECT_TRUE(cache.get(face, 0, 16).has_value());
|
|
EXPECT_TRUE(cache.get(face, 2, 16).has_value());
|
|
EXPECT_TRUE(cache.get(face, 3, 16).has_value());
|
|
EXPECT_TRUE(cache.get(face, 5, 16).has_value());
|
|
EXPECT_TRUE(cache.get(face, 6, 16).has_value());
|
|
}
|
|
} |