From 478e45f9f3d330aea65e839b58ba05f4be3a02a8 Mon Sep 17 00:00:00 2001 From: Mira Grudzinska Date: Fri, 18 Aug 2023 23:19:30 +0200 Subject: [PATCH] common: stroke dash offset support added The feature is supported also in the svg loader. @Issue: https://github.com/thorvg/thorvg/issues/1591 --- inc/thorvg.h | 6 ++++-- src/lib/sw_engine/tvgSwRenderer.cpp | 2 +- src/lib/sw_engine/tvgSwShape.cpp | 28 +++++++++++++++++++++----- src/lib/tvgRender.h | 4 +++- src/lib/tvgShape.cpp | 8 ++++---- src/lib/tvgShapeImpl.h | 4 +++- src/loaders/svg/tvgSvgLoader.cpp | 14 ++++++++++++- src/loaders/svg/tvgSvgLoaderCommon.h | 8 +++++--- src/loaders/svg/tvgSvgSceneBuilder.cpp | 2 +- 9 files changed, 57 insertions(+), 19 deletions(-) diff --git a/inc/thorvg.h b/inc/thorvg.h index 086c8377..3f094638 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -1002,6 +1002,7 @@ public: * * @param[in] dashPattern The array of consecutive pair values of the dash length and the gap length. * @param[in] cnt The length of the @p dashPattern array. + * @param[in] offset The shift of the starting point within the repeating dash pattern from which the path's dashing begins. * * @retval Result::Success When succeed. * @retval Result::FailedAllocation An internal error with a memory allocation for an object to be dashed. @@ -1010,7 +1011,7 @@ public: * @note To reset the stroke dash pattern, pass @c nullptr to @p dashPattern and zero to @p cnt. * @warning @p cnt must be greater than 1 if the dash pattern is valid. */ - Result stroke(const float* dashPattern, uint32_t cnt) noexcept; + Result stroke(const float* dashPattern, uint32_t cnt, float offset = 0.0f) noexcept; /** * @brief Sets the cap style of the stroke in the open sub-paths. @@ -1170,10 +1171,11 @@ public: * @brief Gets the dash pattern of the stroke. * * @param[out] dashPattern The pointer to the memory, where the dash pattern array is stored. + * @param[out] offset The shift of the starting point within the repeating dash pattern. * * @return The length of the @p dashPattern array. */ - uint32_t strokeDash(const float** dashPattern) const noexcept; + uint32_t strokeDash(const float** dashPattern, float* offset = nullptr) const noexcept; /** * @brief Gets the cap style used for stroking the path. diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index 1115a41d..959970cf 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -132,7 +132,7 @@ struct SwShapeTask : SwTask shape outline below stroke could be full covered by stroke drawing. Thus it turns off antialising in that condition. Also, it shouldn't be dash style. */ - auto antiAlias = strokeAlpha < 255 || rshape->strokeWidth() <= 2 || rshape->strokeDash(nullptr) > 0 || (rshape->stroke && rshape->stroke->strokeFirst); + auto antiAlias = strokeAlpha < 255 || rshape->strokeWidth() <= 2 || rshape->strokeDash(nullptr, nullptr) > 0 || (rshape->stroke && rshape->stroke->strokeFirst); if (!shapeGenRle(&shape, rshape, antiAlias)) goto err; } diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp index 14dd68b9..0ba16d3d 100644 --- a/src/lib/sw_engine/tvgSwShape.cpp +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -222,9 +222,27 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans dash.curOpGap = false; const float* pattern; - dash.cnt = rshape->strokeDash(&pattern); + float offset; + dash.cnt = rshape->strokeDash(&pattern, &offset); if (dash.cnt == 0) return nullptr; + auto patternLength = 0.0f; + uint32_t offIdx = 0; + if (fabsf(offset) > FLT_EPSILON) { + for (auto i = 0; i < dash.cnt; ++i) patternLength += pattern[i]; + bool isOdd = dash.cnt % 2; + if (isOdd) patternLength *= 2; + + if (offset < 0) offset = patternLength + fmod(offset, patternLength); + else offset = fmod(offset, patternLength); + + for (auto i = 0; i < dash.cnt * (1 + isOdd); ++i, ++offIdx) { + auto curPattern = pattern[i % dash.cnt]; + if (offset < curPattern) break; + offset -= curPattern; + } + } + //OPTMIZE ME: Use mempool??? dash.pattern = const_cast(pattern); dash.outline = static_cast(calloc(1, sizeof(SwOutline))); @@ -271,9 +289,9 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans } case PathCommand::MoveTo: { //reset the dash - dash.curIdx = 0; - dash.curLen = *dash.pattern; - dash.curOpGap = false; + dash.curIdx = offIdx % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx] - offset; + dash.curOpGap = offIdx % 2; dash.ptStart = dash.ptCur = *pts; ++pts; break; @@ -515,7 +533,7 @@ bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* bool ret = true; //Dash Style Stroke - if (rshape->strokeDash(nullptr) > 0) { + if (rshape->strokeDash(nullptr, nullptr) > 0) { shapeOutline = _genDashOutline(rshape, transform); if (!shapeOutline) return false; freeOutline = true; diff --git a/src/lib/tvgRender.h b/src/lib/tvgRender.h index 19d5eee1..0a20e5a3 100644 --- a/src/lib/tvgRender.h +++ b/src/lib/tvgRender.h @@ -137,6 +137,7 @@ struct RenderStroke Fill *fill = nullptr; float* dashPattern = nullptr; uint32_t dashCnt = 0; + float dashOffset = 0.0f; StrokeCap cap = StrokeCap::Square; StrokeJoin join = StrokeJoin::Bevel; float miterlimit = 4.0f; @@ -200,10 +201,11 @@ struct RenderShape return stroke->fill; } - uint32_t strokeDash(const float** dashPattern) const + uint32_t strokeDash(const float** dashPattern, float* offset) const { if (!stroke) return 0; if (dashPattern) *dashPattern = stroke->dashPattern; + if (offset) *offset = stroke->dashOffset; return stroke->dashCnt; } diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp index d1691d02..6cbca556 100644 --- a/src/lib/tvgShape.cpp +++ b/src/lib/tvgShape.cpp @@ -340,7 +340,7 @@ const Fill* Shape::strokeFill() const noexcept } -Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept +Result Shape::stroke(const float* dashPattern, uint32_t cnt, float offset) noexcept { if ((cnt == 1) || (!dashPattern && cnt > 0) || (dashPattern && cnt == 0)) { return Result::InvalidArguments; @@ -349,15 +349,15 @@ Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept for (uint32_t i = 0; i < cnt; i++) if (dashPattern[i] < FLT_EPSILON) return Result::InvalidArguments; - if (!pImpl->strokeDash(dashPattern, cnt)) return Result::FailedAllocation; + if (!pImpl->strokeDash(dashPattern, cnt, offset)) return Result::FailedAllocation; return Result::Success; } -uint32_t Shape::strokeDash(const float** dashPattern) const noexcept +uint32_t Shape::strokeDash(const float** dashPattern, float* offset) const noexcept { - return pImpl->rs.strokeDash(dashPattern); + return pImpl->rs.strokeDash(dashPattern, offset); } diff --git a/src/lib/tvgShapeImpl.h b/src/lib/tvgShapeImpl.h index edc4f365..7c4d5fff 100644 --- a/src/lib/tvgShapeImpl.h +++ b/src/lib/tvgShapeImpl.h @@ -269,7 +269,7 @@ struct Shape::Impl return Result::Success; } - bool strokeDash(const float* pattern, uint32_t cnt) + bool strokeDash(const float* pattern, uint32_t cnt, float offset) { //Reset dash if (!pattern && cnt == 0) { @@ -290,6 +290,7 @@ struct Shape::Impl } } rs.stroke->dashCnt = cnt; + rs.stroke->dashOffset = offset; flag |= RenderUpdateFlag::Stroke; return true; @@ -338,6 +339,7 @@ struct Shape::Impl dup->rs.stroke = new RenderStroke(); dup->rs.stroke->width = rs.stroke->width; dup->rs.stroke->dashCnt = rs.stroke->dashCnt; + dup->rs.stroke->dashOffset = rs.stroke->dashOffset; dup->rs.stroke->cap = rs.stroke->cap; dup->rs.stroke->join = rs.stroke->join; dup->rs.stroke->strokeFirst = rs.stroke->strokeFirst; diff --git a/src/loaders/svg/tvgSvgLoader.cpp b/src/loaders/svg/tvgSvgLoader.cpp index bf7080e5..f5cc0dfe 100644 --- a/src/loaders/svg/tvgSvgLoader.cpp +++ b/src/loaders/svg/tvgSvgLoader.cpp @@ -952,6 +952,12 @@ static void _handleStrokeDashArrayAttr(SvgLoaderData* loader, SvgNode* node, con _parseDashArray(loader, value, &node->style->stroke.dash); } +static void _handleStrokeDashOffsetAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::DashOffset); + node->style->stroke.dash.offset = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); +} + static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value) { node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::Width); @@ -1108,6 +1114,7 @@ static constexpr struct STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap), STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity), STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray), + STYLE_DEF(stroke-dashoffset, StrokeDashOffset, SvgStyleFlags::StrokeDashOffset), STYLE_DEF(transform, Transform, SvgStyleFlags::Transform), STYLE_DEF(clip-path, ClipPath, SvgStyleFlags::ClipPath), STYLE_DEF(mask, Mask, SvgStyleFlags::Mask), @@ -2773,6 +2780,9 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren } } } + if (!(child->stroke.flags & SvgStrokeFlags::DashOffset)) { + child->stroke.dash.offset = parent->stroke.dash.offset; + } if (!(child->stroke.flags & SvgStrokeFlags::Cap)) { child->stroke.cap = parent->stroke.cap; } @@ -2839,13 +2849,15 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from) } } } + if (from->stroke.flags & SvgStrokeFlags::DashOffset) { + to->stroke.dash.offset = from->stroke.dash.offset; + } if (from->stroke.flags & SvgStrokeFlags::Cap) { to->stroke.cap = from->stroke.cap; } if (from->stroke.flags & SvgStrokeFlags::Join) { to->stroke.join = from->stroke.join; } - if (from->stroke.flags & SvgStrokeFlags::Miterlimit) { to->stroke.miterlimit = from->stroke.miterlimit; } diff --git a/src/loaders/svg/tvgSvgLoaderCommon.h b/src/loaders/svg/tvgSvgLoaderCommon.h index f657eb30..a6074fdd 100644 --- a/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/src/loaders/svg/tvgSvgLoaderCommon.h @@ -100,7 +100,8 @@ enum class SvgStrokeFlags Cap = 0x20, Join = 0x40, Dash = 0x80, - Miterlimit = 0x100 + Miterlimit = 0x100, + DashOffset = 0x200 }; constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b) @@ -139,7 +140,8 @@ enum class SvgStyleFlags MaskType = 0x4000, Display = 0x8000, PaintOrder = 0x10000, - StrokeMiterlimit = 0x20000 + StrokeMiterlimit = 0x20000, + StrokeDashOffset = 0x40000, }; constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b) @@ -423,6 +425,7 @@ struct SvgPaint struct SvgDash { Array array; + float offset; }; struct SvgStyleGradient @@ -469,7 +472,6 @@ struct SvgStyleStroke StrokeJoin join; float miterlimit; SvgDash dash; - int dashCount; }; struct SvgStyleProperty diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 076b3fa4..b8b3b903 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -349,7 +349,7 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg, vg->stroke(style->stroke.join); vg->strokeMiterlimit(style->stroke.miterlimit); if (style->stroke.dash.array.count > 0) { - vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count); + vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count, style->stroke.dash.offset); } //If stroke property is nullptr then do nothing