| #include "rive/shapes/paint/dash_path.hpp" |
| #include "rive/shapes/paint/dash.hpp" |
| #include "rive/shapes/paint/stroke.hpp" |
| #include "rive/factory.hpp" |
| |
| using namespace rive; |
| |
| void PathDasher::invalidateSourcePath() |
| { |
| m_contours.clear(); |
| invalidateDash(); |
| } |
| |
| void PathDasher::invalidateDash() { m_renderPath = nullptr; } |
| |
| RenderPath* PathDasher::dash(const RawPath& source, |
| Factory* factory, |
| Dash* offset, |
| Span<Dash*> dashes) |
| { |
| if (m_renderPath != nullptr) |
| { |
| // Previous result hasn't been invalidated, it's still good. |
| return m_renderPath; |
| } |
| |
| m_rawPath.rewind(); |
| if (m_contours.empty()) |
| { |
| // 0.5f / 8.0f is a value that seems to look good on dashes with small |
| // gaps and scaled |
| ContourMeasureIter iter(&source, 0.0625f); |
| while (auto meas = iter.next()) |
| { |
| m_contours.push_back(meas); |
| } |
| } |
| |
| // Make sure dashes have some length. |
| bool hasValidDash = false; |
| for (const rcp<ContourMeasure>& contour : m_contours) |
| { |
| for (auto dash : dashes) |
| { |
| if (dash->normalizedLength(contour->length()) > 0.0f) |
| { |
| hasValidDash = true; |
| break; |
| } |
| } |
| if (hasValidDash) |
| { |
| break; |
| } |
| } |
| if (hasValidDash) |
| { |
| int dashIndex = 0; |
| for (const rcp<ContourMeasure>& contour : m_contours) |
| { |
| float dashed = 0.0f; |
| float distance = offset->normalizedLength(contour->length()); |
| bool draw = true; |
| while (dashed < contour->length()) |
| { |
| const Dash* dash = dashes[dashIndex++ % dashes.size()]; |
| float dashLength = dash->normalizedLength(contour->length()); |
| if (dashLength > contour->length()) |
| { |
| dashLength = contour->length(); |
| } |
| float endLength = distance + dashLength; |
| if (endLength > contour->length()) |
| { |
| endLength -= contour->length(); |
| if (draw) |
| { |
| if (distance < contour->length()) |
| { |
| contour->getSegment(distance, |
| contour->length(), |
| &m_rawPath, |
| true); |
| contour->getSegment(0.0f, |
| endLength, |
| &m_rawPath, |
| !contour->isClosed()); |
| } |
| else |
| { |
| contour->getSegment(0.0f, |
| endLength, |
| &m_rawPath, |
| true); |
| } |
| } |
| |
| // Setup next step. |
| distance = endLength - dashLength; |
| } |
| else if (draw) |
| { |
| contour->getSegment(distance, endLength, &m_rawPath, true); |
| } |
| distance += dashLength; |
| dashed += dashLength; |
| draw = !draw; |
| } |
| } |
| } |
| |
| if (!m_dashedPath) |
| { |
| m_dashedPath = factory->makeEmptyRenderPath(); |
| } |
| else |
| { |
| m_dashedPath->rewind(); |
| } |
| |
| m_renderPath = m_dashedPath.get(); |
| m_rawPath.addTo(m_renderPath); |
| |
| return m_renderPath; |
| } |
| |
| float PathDasher::pathLength() const |
| { |
| float totalLength = 0.0f; |
| for (auto contour : m_contours) |
| { |
| totalLength += contour->length(); |
| } |
| return totalLength; |
| } |
| |
| StatusCode DashPath::onAddedClean(CoreContext* context) |
| { |
| if (!parent()->is<Stroke>()) |
| { |
| return StatusCode::InvalidObject; |
| } |
| parent()->as<Stroke>()->addStrokeEffect(this); |
| |
| m_dashes.clear(); |
| for (auto child : children()) |
| { |
| if (child->is<Dash>()) |
| { |
| m_dashes.push_back(child->as<Dash>()); |
| } |
| } |
| return StatusCode::Ok; |
| } |
| |
| RenderPath* DashPath::effectPath(const RawPath& source, Factory* factory) |
| { |
| Dash dashOffset(offset(), offsetIsPercentage()); |
| return dash(source, factory, &dashOffset, m_dashes); |
| } |
| |
| void DashPath::invalidateEffect() { invalidateSourcePath(); } |
| |
| void DashPath::offsetChanged() { invalidateDash(); } |
| void DashPath::offsetIsPercentageChanged() { invalidateDash(); } |