diff --git a/inc/thorvg.h b/inc/thorvg.h index 90e145d3..9a98955a 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -990,6 +990,18 @@ public: */ Result stroke(StrokeJoin join) noexcept; + + /** + * @brief Sets the stroke miterlimit. + * + * @param[in] miterlimit The miterlimit imposes a limit on the extent of the stroke join, when the @c StrokeJoin::Miter join style is set. The default value is 4. + * + * @return Result::Success when succeed, Result::NonSupport unsupported value, Result::FailedAllocation otherwise. + * + * @BETA_API + */ + Result strokeMiterlimit(float miterlimit) noexcept; + /** * @brief Sets the solid color for all of the figures from the path. * @@ -1135,6 +1147,15 @@ public: */ StrokeJoin strokeJoin() const noexcept; + /** + * @brief Gets the stroke miterlimit. + * + * @return The stroke miterlimit value when succeed, 4 if no stroke was set. + * + * @BETA_API + */ + float strokeMiterlimit() const noexcept; + /** * @brief Creates a new Shape object. * diff --git a/src/bindings/capi/thorvg_capi.h b/src/bindings/capi/thorvg_capi.h index 94ef1b55..cf648ede 100644 --- a/src/bindings/capi/thorvg_capi.h +++ b/src/bindings/capi/thorvg_capi.h @@ -1411,6 +1411,34 @@ TVG_API Tvg_Result tvg_shape_set_stroke_join(Tvg_Paint* paint, Tvg_Stroke_Join j TVG_API Tvg_Result tvg_shape_get_stroke_join(const Tvg_Paint* paint, Tvg_Stroke_Join* join); +/*! +* \brief Sets the stroke miterlimit. (BETA_API) +* +* \param[in] paint A Tvg_Paint pointer to the shape object. +* \param[in] miterlimit The miterlimit imposes a limit on the extent of the stroke join when the @c TVG_STROKE_JOIN_MITER join style is set. The default value is 4. +* +* \return Tvg_Result enumeration. +* \retval TVG_RESULT_SUCCESS Succeed. +* \retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Paint pointer. +* \retval TVG_RESULT_NOT_SUPPORTED Unsupported value. +* \retval TVG_RESULT_FAILED_ALLOCATION An internal error with a memory allocation. +*/ +TVG_API Tvg_Result tvg_shape_set_stroke_miterlimit(Tvg_Paint* paint, float miterlimit); + + +/*! +* \brief The function gets the stroke miterlimit. (BETA_API) +* +* \param[in] paint A Tvg_Paint pointer to the shape object. +* \param[out] miterlimit The stroke miterlimit. +* +* \return Tvg_Result enumeration. +* \retval TVG_RESULT_SUCCESS Succeed. +* \retval TVG_RESULT_INVALID_ARGUMENT An invalid pointer passed as an argument. +*/ +TVG_API Tvg_Result tvg_shape_get_stroke_miterlimit(const Tvg_Paint* paint, float* miterlimit); + + /*! * \brief Sets the shape's solid color. * diff --git a/src/bindings/capi/tvgCapi.cpp b/src/bindings/capi/tvgCapi.cpp index 3e9831dc..a1f7ab2b 100644 --- a/src/bindings/capi/tvgCapi.cpp +++ b/src/bindings/capi/tvgCapi.cpp @@ -412,6 +412,22 @@ TVG_API Tvg_Result tvg_shape_get_stroke_join(const Tvg_Paint* paint, Tvg_Stroke_ } +TVG_API Tvg_Result tvg_shape_set_stroke_miterlimit(Tvg_Paint* paint, float ml) +{ + if (ml < 0.0f) return TVG_RESULT_NOT_SUPPORTED; + if (!paint) return TVG_RESULT_INVALID_ARGUMENT; + return (Tvg_Result) reinterpret_cast(paint)->strokeMiterlimit(ml); +} + + +TVG_API Tvg_Result tvg_shape_get_stroke_miterlimit(const Tvg_Paint* paint, float* ml) +{ + if (!paint || !ml) return TVG_RESULT_INVALID_ARGUMENT; + *ml = reinterpret_cast(paint)->strokeMiterlimit(); + return TVG_RESULT_SUCCESS; +} + + TVG_API Tvg_Result tvg_shape_set_fill_color(Tvg_Paint* paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (!paint) return TVG_RESULT_INVALID_ARGUMENT; diff --git a/src/lib/sw_engine/tvgSwCommon.h b/src/lib/sw_engine/tvgSwCommon.h index 3cdaf965..d0be44e2 100644 --- a/src/lib/sw_engine/tvgSwCommon.h +++ b/src/lib/sw_engine/tvgSwCommon.h @@ -180,6 +180,7 @@ struct SwStroke SwPoint ptStartSubPath; SwFixed subPathLineLength; SwFixed width; + float miterlimit; StrokeCap cap; StrokeJoin join; diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp index 2f41d5f0..c1584b5e 100644 --- a/src/lib/sw_engine/tvgSwStroke.cpp +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -233,8 +233,6 @@ static void _arcTo(SwStroke& stroke, int32_t side) static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength) { - constexpr SwFixed MITER_LIMIT = 4 * (1 << 16); - auto border = stroke.borders + side; if (stroke.join == StrokeJoin::Round) { @@ -257,7 +255,7 @@ static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength) } thcos = mathCos(theta); - auto sigma = mathMultiply(MITER_LIMIT, thcos); + auto sigma = mathMultiply(stroke.miterlimit, thcos); //is miter limit exceeded? if (sigma < 0x10000L) bevel = true; @@ -844,6 +842,7 @@ void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix* tran stroke->width = HALF_STROKE(rshape->strokeWidth()); stroke->cap = rshape->strokeCap(); + stroke->miterlimit = rshape->strokeMiterlimit() * (1 << 16); //Save line join: it can be temporarily changed when stroking curves... stroke->joinSaved = stroke->join = rshape->strokeJoin(); diff --git a/src/lib/tvgRender.h b/src/lib/tvgRender.h index 62be27df..62f410f3 100644 --- a/src/lib/tvgRender.h +++ b/src/lib/tvgRender.h @@ -139,6 +139,7 @@ struct RenderStroke uint32_t dashCnt = 0; StrokeCap cap = StrokeCap::Square; StrokeJoin join = StrokeJoin::Bevel; + float miterlimit = 4.0f; bool strokeFirst = false; ~RenderStroke() @@ -225,6 +226,13 @@ struct RenderShape if (!stroke) return StrokeJoin::Bevel; return stroke->join; } + + float strokeMiterlimit() const + { + if (!stroke) return 4.0f; + + return stroke->miterlimit;; + } }; class RenderMethod diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp index 76a94525..251a9d26 100644 --- a/src/lib/tvgShape.cpp +++ b/src/lib/tvgShape.cpp @@ -373,6 +373,17 @@ Result Shape::stroke(StrokeJoin join) noexcept return Result::Success; } +Result Shape::strokeMiterlimit(float miterlimit) noexcept +{ + // https://www.w3.org/TR/SVG2/painting.html#LineJoin + // - A negative value for stroke-miterlimit must be treated as an illegal value. + if (miterlimit < 0.0f) return Result::NonSupport; + // TODO Find out a reasonable max value. + if (!pImpl->strokeMiterlimit(miterlimit)) return Result::FailedAllocation; + + return Result::Success; +} + StrokeCap Shape::strokeCap() const noexcept { @@ -385,6 +396,11 @@ StrokeJoin Shape::strokeJoin() const noexcept return pImpl->rs.strokeJoin(); } +float Shape::strokeMiterlimit() const noexcept +{ + return pImpl->rs.strokeMiterlimit(); +} + Result Shape::fill(FillRule r) noexcept { diff --git a/src/lib/tvgShapeImpl.h b/src/lib/tvgShapeImpl.h index d2ec201e..74bff0c3 100644 --- a/src/lib/tvgShapeImpl.h +++ b/src/lib/tvgShapeImpl.h @@ -245,6 +245,15 @@ struct Shape::Impl return true; } + bool strokeMiterlimit(float miterlimit) + { + if (!rs.stroke) rs.stroke = new RenderStroke(); + rs.stroke->miterlimit = miterlimit; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { if (!rs.stroke) rs.stroke = new RenderStroke(); diff --git a/src/loaders/svg/tvgSvgLoader.cpp b/src/loaders/svg/tvgSvgLoader.cpp index 8a2258a6..08cae2d5 100644 --- a/src/loaders/svg/tvgSvgLoader.cpp +++ b/src/loaders/svg/tvgSvgLoader.cpp @@ -983,6 +983,22 @@ static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node->style->stroke.join = _toLineJoin(value); } +static void _handleStrokeMiterlimitAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + char* end = nullptr; + const float miterlimit = svgUtilStrtof(value, &end); + + // https://www.w3.org/TR/SVG2/painting.html#LineJoin + // - A negative value for stroke-miterlimit must be treated as an illegal value. + if (miterlimit < 0.0f) { + TVGERR("SVG", "A stroke-miterlimit change (%f <- %f) with a negative value is omitted.", + node->style->stroke.miterlimit, miterlimit); + return; + } + + node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::Miterlimit); + node->style->stroke.miterlimit = miterlimit; +} static void _handleFillRuleAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) { @@ -1099,6 +1115,7 @@ static constexpr struct STYLE_DEF(stroke, Stroke, SvgStyleFlags::Stroke), STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth), STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin), + STYLE_DEF(stroke-miterlimit, StrokeMiterlimit, SvgStyleFlags::StrokeMiterlimit), STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap), STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity), STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray), @@ -1307,6 +1324,7 @@ static SvgNode* _createNode(SvgNode* parent, SvgNodeType type) node->style->stroke.cap = StrokeCap::Butt; //Default line join is miter node->style->stroke.join = StrokeJoin::Miter; + node->style->stroke.miterlimit = 4.0f; node->style->stroke.scale = 1.0; node->style->paintOrder = _toPaintOrder("fill stroke"); @@ -2785,6 +2803,9 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren if (!(child->stroke.flags & SvgStrokeFlags::Join)) { child->stroke.join = parent->stroke.join; } + if (!(child->stroke.flags & SvgStrokeFlags::Miterlimit)) { + child->stroke.miterlimit = parent->stroke.miterlimit; + } } @@ -2848,6 +2869,10 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from) 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 dec9fade..b9abe6a5 100644 --- a/src/loaders/svg/tvgSvgLoaderCommon.h +++ b/src/loaders/svg/tvgSvgLoaderCommon.h @@ -100,6 +100,7 @@ enum class SvgStrokeFlags Cap = 0x20, Join = 0x40, Dash = 0x80, + Miterlimit = 0x100 }; constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b) @@ -137,7 +138,8 @@ enum class SvgStyleFlags Mask = 0x2000, MaskType = 0x4000, Display = 0x8000, - PaintOrder = 0x10000 + PaintOrder = 0x10000, + StrokeMiterlimit = 0x20000 }; constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b) @@ -466,6 +468,7 @@ struct SvgStyleStroke float centered; StrokeCap cap; StrokeJoin join; + float miterlimit; SvgDash dash; int dashCount; }; diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 05ed41a9..eeb420f2 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -342,6 +342,7 @@ static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const stri vg->stroke(style->stroke.width); vg->stroke(style->stroke.cap); 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); } diff --git a/test/capi/capiShape.cpp b/test/capi/capiShape.cpp index 493d27af..a9c2c1c6 100644 --- a/test/capi/capiShape.cpp +++ b/test/capi/capiShape.cpp @@ -224,6 +224,7 @@ TEST_CASE("Stroke join", "[capiStrokeJoin]") REQUIRE(paint); Tvg_Stroke_Join join; + float ml = -1.0f; REQUIRE(tvg_shape_set_stroke_join(paint, TVG_STROKE_JOIN_BEVEL) == TVG_RESULT_SUCCESS); REQUIRE(tvg_shape_get_stroke_join(paint, &join) == TVG_RESULT_SUCCESS); @@ -233,6 +234,20 @@ TEST_CASE("Stroke join", "[capiStrokeJoin]") REQUIRE(tvg_shape_get_stroke_join(paint, &join) == TVG_RESULT_SUCCESS); REQUIRE(join == TVG_STROKE_JOIN_MITER); + + REQUIRE(tvg_shape_get_stroke_miterlimit(paint, &ml) == TVG_RESULT_SUCCESS); + REQUIRE(ml == 4.0f); + + REQUIRE(tvg_shape_set_stroke_miterlimit(paint, 1000.0f) == TVG_RESULT_SUCCESS); + REQUIRE(tvg_shape_get_stroke_miterlimit(paint, &ml) == TVG_RESULT_SUCCESS); + REQUIRE(ml == 1000.0f); + + REQUIRE(tvg_shape_set_stroke_miterlimit(paint, -0.001f) == TVG_RESULT_NOT_SUPPORTED); + REQUIRE(tvg_shape_get_stroke_miterlimit(paint, &ml) == TVG_RESULT_SUCCESS); + REQUIRE(ml == 1000.0f); + + + REQUIRE(tvg_paint_del(paint) == TVG_RESULT_SUCCESS); } diff --git a/test/testShape.cpp b/test/testShape.cpp index a2d5d3d0..23b198f3 100644 --- a/test/testShape.cpp +++ b/test/testShape.cpp @@ -191,6 +191,14 @@ TEST_CASE("Stroking", "[tvgShape]") REQUIRE(shape->stroke(StrokeJoin::Round) == Result::Success); REQUIRE(shape->strokeJoin() == StrokeJoin::Round); + //Stroke Miterlimit + REQUIRE(shape->strokeMiterlimit() == 4.0f); + REQUIRE(shape->strokeMiterlimit(0.00001f) == Result::Success); + REQUIRE(shape->strokeMiterlimit() == 0.00001f); + REQUIRE(shape->strokeMiterlimit(1000.0f) == Result::Success); + REQUIRE(shape->strokeMiterlimit() == 1000.0f); + REQUIRE(shape->strokeMiterlimit(-0.001f) == Result::NonSupport); + //Stroke Order After Stroke Setting REQUIRE(shape->order(true) == Result::Success); REQUIRE(shape->order(false) == Result::Success);