| #include "rive/simple_array.hpp" |
| #include "catch.hpp" |
| #include "rive/text_engine.hpp" |
| #include "rive/text/font_hb.hpp" |
| #include "rive/text/utf.hpp" |
| #include "hb.h" |
| #include "hb-ot.h" |
| #include <string> |
| |
| using namespace rive; |
| |
| static rive::TextRun append(std::vector<rive::Unichar>* unichars, |
| rive::rcp<rive::Font> font, |
| float size, |
| const char text[]) |
| { |
| const uint8_t* ptr = (const uint8_t*)text; |
| uint32_t n = 0; |
| while (*ptr) |
| { |
| unichars->push_back(rive::UTF::NextUTF8(&ptr)); |
| n += 1; |
| } |
| return {std::move(font), size, -1.0f, 0.0f, n, 0}; |
| } |
| |
| static rcp<Font> loadFont(const char* filename) |
| { |
| FILE* fp = fopen(filename, "rb"); |
| REQUIRE(fp != nullptr); |
| |
| fseek(fp, 0, SEEK_END); |
| const size_t length = ftell(fp); |
| fseek(fp, 0, SEEK_SET); |
| std::vector<uint8_t> bytes(length); |
| REQUIRE(fread(bytes.data(), 1, length, fp) == length); |
| fclose(fp); |
| |
| return HBFont::Decode(bytes); |
| } |
| |
| static std::vector<rive::rcp<rive::Font>> fallbackFonts; |
| static rive::rcp<rive::Font> pickFallbackFont(const rive::Unichar missing, |
| const uint32_t fallbackIndex, |
| const rive::Font*) |
| { |
| if (fallbackIndex > 0) |
| { |
| return nullptr; |
| } |
| size_t length = fallbackFonts.size(); |
| for (size_t i = fallbackIndex; i < length; i++) |
| { |
| HBFont* font = static_cast<HBFont*>(fallbackFonts[i].get()); |
| if (font->hasGlyph(missing)) |
| { |
| return fallbackFonts[i]; |
| } |
| } |
| return nullptr; |
| } |
| |
| TEST_CASE("Inspect Font Styles", "[text_styles]") |
| { |
| struct TestCaseData |
| { |
| const char* fontPath; |
| uint16_t expectedWeight; |
| bool expectedItalic; |
| }; |
| |
| std::vector<TestCaseData> testCases = { |
| {"assets/fonts/AdventPro-VariableFont_wdth,wght.ttf", 400, false}, |
| {"assets/fonts/Inter_18pt-Regular.ttf", 400, false}, |
| {"assets/fonts/Inter_28pt-Bold.ttf", 700, false}, |
| {"assets/fonts/OpenSans-Italic.ttf", 400, true}, |
| {"assets/fonts/OpenSans-ExtraBoldItalic.ttf", 800, true}, |
| }; |
| |
| for (const auto& testCase : testCases) |
| { |
| SECTION(testCase.fontPath) |
| { |
| rive::rcp<Font> font = loadFont(testCase.fontPath); |
| HBFont* hbFont = static_cast<HBFont*>(font.get()); |
| |
| REQUIRE(hbFont->getWeight() == testCase.expectedWeight); |
| REQUIRE(hbFont->isItalic() == testCase.expectedItalic); |
| } |
| } |
| } |
| |
| TEST_CASE("fallback glyphs are found", "[text_fallback]") |
| { |
| REQUIRE(fallbackFonts.empty()); |
| auto font = loadFont("assets/RobotoFlex.ttf"); |
| REQUIRE(font != nullptr); |
| auto fallbackFont = loadFont("assets/IBMPlexSansArabic-Regular.ttf"); |
| REQUIRE(fallbackFont != nullptr); |
| fallbackFonts.push_back(fallbackFont); |
| |
| Font::gFallbackProc = pickFallbackFont; |
| |
| std::vector<rive::TextRun> truns; |
| std::vector<rive::Unichar> unichars; |
| truns.push_back(append(&unichars, font, 32.0f, "لمفاتيح ABC DEF")); |
| |
| auto paragraphs = font->shapeText(unichars, truns); |
| REQUIRE(paragraphs.size() == 1); |
| paragraphs = SimpleArray<Paragraph>(); |
| REQUIRE(paragraphs.size() == 0); |
| fallbackFonts.clear(); |
| Font::gFallbackProc = nullptr; |
| } |
| |
| TEST_CASE("variable axis values can be read", "[text]") |
| { |
| REQUIRE(fallbackFonts.empty()); |
| auto font = loadFont("assets/RobotoFlex.ttf"); |
| REQUIRE(font != nullptr); |
| |
| auto count = font->getAxisCount(); |
| |
| bool hasWeight = false; |
| for (uint16_t i = 0; i < count; i++) |
| { |
| auto axis = font->getAxis(i); |
| if (axis.tag == 2003265652) |
| { |
| REQUIRE(axis.def == 400.0f); |
| hasWeight = true; |
| break; |
| } |
| } |
| |
| REQUIRE(hasWeight); |
| |
| float value = font->getAxisValue(2003265652); |
| REQUIRE(value == 400.0f); |
| |
| REQUIRE(font->getAxisValue(2003072104) == 100.0f); |
| |
| rive::Font::Coord coord = {2003265652, 800.0f}; |
| rive::rcp<rive::Font> vfont = |
| font->makeAtCoords(rive::Span<HBFont::Coord>(&coord, 1)); |
| REQUIRE(vfont->getAxisValue(2003265652) == 800.0f); |
| |
| rive::Font::Coord coord2 = {2003072104, 122.0f}; |
| rive::rcp<rive::Font> vfont2 = |
| vfont->makeAtCoords(rive::Span<HBFont::Coord>(&coord2, 1)); |
| REQUIRE(vfont2->getAxisValue(2003072104) == 122.0f); |
| // Should also still have the first axis value we set. |
| REQUIRE(vfont2->getAxisValue(2003265652) == 800.0f); |
| } |
| |
| static std::string tagToString(uint32_t tag) |
| { |
| std::string tag_name; |
| tag_name += ((char)((tag & 0xff000000) >> 24)); |
| tag_name += ((char)((tag & 0x00ff0000) >> 16)); |
| tag_name += ((char)((tag & 0x0000ff00) >> 8)); |
| tag_name += ((char)((tag & 0x000000ff))); |
| return tag_name; |
| } |
| |
| static bool hasTag(std::vector<std::string> featureStrings, std::string tag) |
| { |
| return std::find(std::begin(featureStrings), |
| std::end(featureStrings), |
| tag) != std::end(featureStrings); |
| } |
| |
| TEST_CASE("font features load as expected", "[text]") |
| { |
| REQUIRE(fallbackFonts.empty()); |
| auto font = loadFont("assets/RobotoFlex.ttf"); |
| REQUIRE(font != nullptr); |
| |
| rive::SimpleArray<uint32_t> features = font->features(); |
| std::vector<std::string> featureStrings; |
| for (auto feature : features) |
| { |
| featureStrings.push_back(tagToString(feature)); |
| } |
| REQUIRE(features.size() == 7); |
| |
| REQUIRE(hasTag(featureStrings, "mkmk")); |
| REQUIRE(hasTag(featureStrings, "kern")); |
| REQUIRE(hasTag(featureStrings, "rvrn")); |
| REQUIRE(hasTag(featureStrings, "mark")); |
| REQUIRE(hasTag(featureStrings, "locl")); |
| REQUIRE(hasTag(featureStrings, "pnum")); |
| REQUIRE(hasTag(featureStrings, "liga")); |
| } |