#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(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(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 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(renderedOpt->width * renderedOpt->height)); } auto hitBmp = cache.get(face, sg.glyphIndex, fontSize); ASSERT_TRUE(hitBmp.has_value()); EXPECT_EQ(hitBmp->pixels.size(), static_cast(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(slot->bitmap.width)); EXPECT_EQ(renderedOpt->height, static_cast(slot->bitmap.rows)); EXPECT_EQ(renderedOpt->bearingX, slot->bitmap_left); EXPECT_EQ(renderedOpt->bearingY, slot->bitmap_top); EXPECT_DOUBLE_EQ(renderedOpt->advance, static_cast(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 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()); } }