| #include "rive/semantic/semantic_provider.hpp" |
| |
| #include "rive/artboard.hpp" |
| #include "rive/component.hpp" |
| #include "rive/container_component.hpp" |
| #include "rive/math/mat2d.hpp" |
| #include "rive/node.hpp" |
| #include "rive/semantic/semantic_data.hpp" |
| #include "semantic_inference_registry.hpp" |
| |
| using namespace rive; |
| |
| static bool tryNodeWorldBounds(Node* node, AABB& outBounds) |
| { |
| if (node == nullptr) |
| { |
| return false; |
| } |
| |
| const auto localBounds = node->localBounds(); |
| if (localBounds.isEmptyOrNaN()) |
| { |
| return false; |
| } |
| const auto worldTransform = node->worldTransform(); |
| const auto worldBounds = worldTransform.mapBoundingBox(localBounds); |
| if (worldBounds.isEmptyOrNaN()) |
| { |
| return false; |
| } |
| outBounds = worldBounds; |
| return true; |
| } |
| |
| AABB SemanticProvider::rootTransformAABB(Artboard* ab, const AABB& bounds) |
| { |
| AABB out = AABB::forExpansion(); |
| AABB::expandTo(out, ab->rootTransform({bounds.minX, bounds.minY})); |
| AABB::expandTo(out, ab->rootTransform({bounds.maxX, bounds.minY})); |
| AABB::expandTo(out, ab->rootTransform({bounds.maxX, bounds.maxY})); |
| AABB::expandTo(out, ab->rootTransform({bounds.minX, bounds.maxY})); |
| return out; |
| } |
| |
| bool SemanticProvider::canInferSemantics(Component* component) |
| { |
| return supportsInferredSemantics(component); |
| } |
| |
| ResolvedSemanticData SemanticProvider::resolveSemanticData(Component* component) |
| { |
| ResolvedSemanticData out; |
| if (component == nullptr || !component->is<Node>()) |
| { |
| return out; |
| } |
| |
| if (auto* sd = component->as<Node>()->firstChild<SemanticData>()) |
| { |
| out.hasSemantics = true; |
| out.role = sd->role(); |
| out.label = sd->label(); |
| return out; |
| } |
| |
| // Inference fallback (Text -> label) |
| resolveInferredSemantics(component, out); |
| |
| return out; |
| } |
| |
| // Computes world-space bounds for a component. Three strategies: |
| // 1. Drawable nodes: localBounds() → worldTransform → rootTransform |
| // 2. Container nodes: merge bounds of all direct Node children |
| // 3. Fallback: world transform translation (degenerate point bounds) |
| AABB SemanticProvider::semanticBounds(Component* component) |
| { |
| if (component == nullptr || !component->is<Node>()) |
| { |
| return AABB(); |
| } |
| |
| auto* node = component->as<Node>(); |
| |
| AABB worldBounds; |
| if (tryNodeWorldBounds(node, worldBounds)) |
| { |
| auto* ab = node->artboard(); |
| if (ab != nullptr) |
| { |
| return rootTransformAABB(ab, worldBounds); |
| } |
| return worldBounds; |
| } |
| |
| // If this semantic node is a non-drawable container/group, derive bounds |
| // from visual descendant nodes. ContainerComponent::forEachChild walks |
| // the whole subtree (not just direct children): the predicate runs for |
| // each descendant, and returning `true` tells forEachChild to keep |
| // descending into that descendant's children. We always return true so |
| // every Node descendant contributes bounds; non-Node descendants and |
| // Nodes without valid bounds are skipped but their subtrees are still |
| // walked. |
| if (component->is<ContainerComponent>()) |
| { |
| AABB merged = AABB::forExpansion(); |
| bool hasDescendantBounds = false; |
| component->as<ContainerComponent>()->forEachChild( |
| [&](Component* child) -> bool { |
| if (child == nullptr || !child->is<Node>()) |
| { |
| return true; |
| } |
| |
| AABB childBounds; |
| if (!tryNodeWorldBounds(child->as<Node>(), childBounds)) |
| { |
| return true; |
| } |
| |
| merged.expand(childBounds); |
| hasDescendantBounds = true; |
| return true; |
| }); |
| |
| if (hasDescendantBounds) |
| { |
| auto* ab = node->artboard(); |
| if (ab != nullptr) |
| { |
| return rootTransformAABB(ab, merged); |
| } |
| return merged; |
| } |
| } |
| |
| // Fallback for nodes/components without local bounds. |
| const auto worldTransform = node->worldTransform(); |
| const float x = worldTransform[4]; |
| const float y = worldTransform[5]; |
| auto* ab = node->artboard(); |
| if (ab != nullptr) |
| { |
| Vec2D pos = ab->rootTransform(Vec2D(x, y)); |
| return AABB(pos.x, pos.y, pos.x, pos.y); |
| } |
| return AABB(x, y, x, y); |
| } |