#include #include #include #include #include "FontCacheManager.h" #include "FontDecompressor.h" #include "SdCardFont.h" namespace { bool countHeapAllocations = false; size_t heapAllocationCount = 0; // TTF fonts are PSRAM-gated and stubbed out on the host (see stubs/TtfEpdFont.h); // every manager gets an empty map for that slot. const std::map kNoTtfFonts; const SdCardFont::PrewarmCall* findCall(const SdCardFont& font, const uint8_t styleMask) { for (int i = 0; i < font.prewarmCallCount; i++) { if (font.prewarmCalls[i].styleMask == styleMask) return &font.prewarmCalls[i]; } return nullptr; } } // namespace void* operator new(const size_t size) { if (countHeapAllocations) heapAllocationCount++; if (void* allocation = std::malloc(size)) return allocation; throw std::bad_alloc(); } void operator delete(void* allocation) noexcept { std::free(allocation); } void operator delete(void* allocation, size_t) noexcept { std::free(allocation); } TEST(FontCacheManagerTest, PrewarmScopeBatchesEachFontAndResolvedStyleSeparately) { SdCardFont readerFont; SdCardFont fallbackFont; const std::map noBuiltinFonts; const std::map sdFonts{{-17, &readerFont}, {23, &fallbackFont}}; FontCacheManager manager(noBuiltinFonts, sdFonts, kNoTtfFonts); auto scope = manager.createPrewarmScope(); manager.recordText("A", -17, EpdFontFamily::REGULAR); manager.recordText("B", -17, EpdFontFamily::BOLD); manager.recordText("C", 23, EpdFontFamily::REGULAR); scope.endScanAndPrewarm(); ASSERT_EQ(2, readerFont.prewarmCallCount); const auto* regularCall = findCall(readerFont, 0x01); ASSERT_NE(nullptr, regularCall); EXPECT_STREQ("A", regularCall->text); EXPECT_FALSE(regularCall->accumulate); const auto* boldCall = findCall(readerFont, 0x02); ASSERT_NE(nullptr, boldCall); EXPECT_STREQ("B", boldCall->text); EXPECT_FALSE(boldCall->accumulate); ASSERT_EQ(1, fallbackFont.prewarmCallCount); EXPECT_STREQ("C", fallbackFont.prewarmCalls[0].text); EXPECT_EQ(0x01, fallbackFont.prewarmCalls[0].styleMask); } TEST(FontCacheManagerTest, PrewarmScopeMergesStylesThatResolveToTheSameSdFontData) { SdCardFont font; font.resolvedStyles[EpdFontFamily::BOLD] = EpdFontFamily::REGULAR; const std::map noBuiltinFonts; const std::map sdFonts{{7, &font}}; FontCacheManager manager(noBuiltinFonts, sdFonts, kNoTtfFonts); auto scope = manager.createPrewarmScope(); manager.recordText("A", 7, EpdFontFamily::REGULAR); manager.recordText("B", 7, EpdFontFamily::BOLD); scope.endScanAndPrewarm(); ASSERT_EQ(1, font.prewarmCallCount); EXPECT_STREQ("AB", font.prewarmCalls[0].text); EXPECT_EQ(0x01, font.prewarmCalls[0].styleMask); } TEST(FontCacheManagerTest, PrewarmScopeMergesBuiltInStylesThatShareFontData) { EpdFontData regularData{reinterpret_cast(1)}; EpdFontData boldData{reinterpret_cast(1)}; const EpdFontFamily family(®ularData, &boldData, ®ularData, nullptr); const std::map builtinFonts{{5, family}}; const std::map noSdFonts; FontDecompressor decompressor; FontCacheManager manager(builtinFonts, noSdFonts, kNoTtfFonts); manager.setFontDecompressor(&decompressor); auto scope = manager.createPrewarmScope(); manager.recordText("A", 5, EpdFontFamily::REGULAR); manager.recordText("B", 5, EpdFontFamily::BOLD); manager.recordText("C", 5, EpdFontFamily::ITALIC); scope.endScanAndPrewarm(); ASSERT_EQ(2, decompressor.prewarmCallCount); const auto& first = decompressor.prewarmCalls[0]; const auto& second = decompressor.prewarmCalls[1]; const auto* regularCall = first.fontData == ®ularData ? &first : &second; const auto* boldCall = first.fontData == &boldData ? &first : &second; EXPECT_STREQ("AC", regularCall->text); EXPECT_STREQ("B", boldCall->text); } TEST(FontCacheManagerTest, PrewarmScopePreservesUniqueMultibyteCodepoints) { SdCardFont font; const std::map noBuiltinFonts; const std::map sdFonts{{7, &font}}; FontCacheManager manager(noBuiltinFonts, sdFonts, kNoTtfFonts); auto scope = manager.createPrewarmScope(); manager.recordText("\xC3\xA9\xE4\xB8\xAD\xF0\x9F\x98\x80\xC3\xA9", 7, EpdFontFamily::REGULAR); scope.endScanAndPrewarm(); ASSERT_EQ(1, font.prewarmCallCount); EXPECT_STREQ("\xC3\xA9\xE4\xB8\xAD\xF0\x9F\x98\x80", font.prewarmCalls[0].text); } TEST(FontCacheManagerTest, PrewarmScanDoesNotAllocateHeapMemory) { SdCardFont font; const std::map noBuiltinFonts; const std::map sdFonts{{7, &font}}; FontCacheManager manager(noBuiltinFonts, sdFonts, kNoTtfFonts); heapAllocationCount = 0; countHeapAllocations = true; auto scope = manager.createPrewarmScope(); manager.recordText("Repeated text: \xC3\xA9 \xE4\xB8\xAD \xF0\x9F\x98\x80", 7, EpdFontFamily::REGULAR); scope.endScanAndPrewarm(); countHeapAllocations = false; EXPECT_EQ(0U, heapAllocationCount); } TEST(FontCacheManagerTest, IncrementalPrewarmRequestsAccumulation) { SdCardFont font; const std::map noBuiltinFonts; const std::map sdFonts{{7, &font}}; FontCacheManager manager(noBuiltinFonts, sdFonts, kNoTtfFonts); manager.prewarmCache(7, "title", 1); ASSERT_EQ(1, font.prewarmCallCount); EXPECT_TRUE(font.prewarmCalls[0].accumulate); }