| #include "rive/text/text.hpp" |
| #include "rive/text/text_style.hpp" |
| #include "rive/text/text_style_paint.hpp" |
| #include "rive/text/text_value_run.hpp" |
| #include "rive/text/text_modifier_group.hpp" |
| #include "rive/text/text_modifier_range.hpp" |
| #include "rive/shapes/paint/shape_paint.hpp" |
| #include "rive/shapes/paint/feather.hpp" |
| #include "rive/shapes/paint/fill.hpp" |
| #include "rive/animation/linear_animation_instance.hpp" |
| #include "rive_file_reader.hpp" |
| #include "rive_testing.hpp" |
| #include <utils/no_op_renderer.hpp> |
| #include <utils/serializing_factory.hpp> |
| |
| TEST_CASE("text modifiers load correctly", "[text]") |
| { |
| auto file = ReadRiveFile("assets/modifier_test.riv"); |
| auto artboard = file->artboard(); |
| |
| auto textObjects = artboard->find<rive::Text>(); |
| REQUIRE(textObjects.size() == 1); |
| |
| auto text = textObjects[0]; |
| REQUIRE(text->modifierGroups().size() == 1); |
| |
| auto modifierGroup = text->modifierGroups()[0]; |
| REQUIRE(modifierGroup->ranges().size() == 1); |
| REQUIRE(modifierGroup->modifiers().size() == 0); |
| |
| auto range = modifierGroup->ranges()[0]; |
| REQUIRE(range->interpolator() != nullptr); |
| } |
| |
| TEST_CASE("text opacity falloff with feathered and plain fills", "[silver]") |
| { |
| rive::SerializingFactory silver; |
| auto file = ReadRiveFile("assets/text_feather_falloff.riv", &silver); |
| |
| auto artboard = file->artboardDefault(); |
| REQUIRE(artboard != nullptr); |
| |
| // Validate the asset still exercises the repro: at least one text driven |
| // by a modifier range, and at least one style with two fills of which |
| // exactly one is feathered. |
| auto textObjects = artboard->find<rive::Text>(); |
| REQUIRE(textObjects.size() == 3); |
| int textsWithModifiers = 0; |
| for (auto text : textObjects) |
| { |
| if (!text->modifierGroups().empty() && |
| !text->modifierGroups()[0]->ranges().empty()) |
| { |
| textsWithModifiers++; |
| } |
| } |
| REQUIRE(textsWithModifiers == 2); |
| |
| auto styles = artboard->find<rive::TextStylePaint>(); |
| int reproStyles = 0; |
| for (auto style : styles) |
| { |
| auto& shapePaints = style->shapePaints(); |
| if (shapePaints.size() != 2) |
| { |
| continue; |
| } |
| int featheredCount = 0; |
| for (auto shapePaint : shapePaints) |
| { |
| if (shapePaint->feather() != nullptr) |
| { |
| featheredCount++; |
| } |
| } |
| if (featheredCount == 1) |
| { |
| reproStyles++; |
| } |
| } |
| REQUIRE(reproStyles >= 1); |
| |
| silver.frameSize(artboard->width(), artboard->height()); |
| |
| REQUIRE(artboard->animationCount() > 0); |
| auto animation = artboard->animationAt(0); |
| animation->advanceAndApply(0.0f); |
| auto renderer = silver.makeRenderer(); |
| artboard->draw(renderer.get()); |
| |
| // Draw every frame of the animation. |
| uint32_t frames = animation->duration(); |
| float frameDuration = 1.0f / animation->fps(); |
| for (uint32_t i = 0; i < frames; i++) |
| { |
| silver.addFrame(); |
| animation->advanceAndApply(frameDuration); |
| artboard->draw(renderer.get()); |
| } |
| |
| CHECK(silver.matches("text_feather_falloff")); |
| } |
| |
| TEST_CASE("inner feather on text rebuilds as modifiers change the glyphs", |
| "[text]") |
| { |
| auto file = ReadRiveFile("assets/text_feather_falloff.riv"); |
| auto artboard = file->artboardDefault(); |
| REQUIRE(artboard != nullptr); |
| |
| // A feathered fill on a text that also carries a modifier group: the |
| // modifier is what we drive, so both have to hang off the same text. |
| rive::Feather* feather = nullptr; |
| rive::TextModifierGroup* modifierGroup = nullptr; |
| for (auto style : artboard->find<rive::TextStylePaint>()) |
| { |
| auto parent = style->parent(); |
| if (parent == nullptr || !parent->is<rive::Text>() || |
| parent->as<rive::Text>()->modifierGroups().empty()) |
| { |
| continue; |
| } |
| for (auto shapePaint : style->shapePaints()) |
| { |
| if (shapePaint->is<rive::Fill>() && |
| shapePaint->feather() != nullptr) |
| { |
| feather = shapePaint->feather(); |
| modifierGroup = parent->as<rive::Text>()->modifierGroups()[0]; |
| break; |
| } |
| } |
| if (feather != nullptr) |
| { |
| break; |
| } |
| } |
| REQUIRE(feather != nullptr); |
| REQUIRE(modifierGroup != nullptr); |
| |
| // Flip to inner so the feather has to derive its path from the glyphs. |
| feather->inner(true); |
| REQUIRE(feather->isInner()); |
| |
| artboard->advance(0.0f); |
| int baseline = feather->renderCount; |
| REQUIRE(baseline > 0); |
| |
| // The reported case: a modifier transform moves the glyphs. Text only |
| // marks itself dirty for that, so the inner feather has to be dirtied |
| // along with the style's paints or it keeps the old glyph geometry. |
| modifierGroup->x(modifierGroup->x() + 10.0f); |
| artboard->advance(0.0f); |
| |
| CHECK(feather->renderCount > baseline); |
| } |