API, CAPI, sw_engine: add suport for stroke-miterlimit.

This commit is contained in:
Martin Capitanio 2023-06-09 12:50:34 +02:00 committed by Hermet Park
parent 07dc68f655
commit 44a750ee5d
13 changed files with 154 additions and 4 deletions

View file

@ -990,6 +990,18 @@ public:
*/ */
Result stroke(StrokeJoin join) noexcept; 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. * @brief Sets the solid color for all of the figures from the path.
* *
@ -1135,6 +1147,15 @@ public:
*/ */
StrokeJoin strokeJoin() const noexcept; 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. * @brief Creates a new Shape object.
* *

View file

@ -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); 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. * \brief Sets the shape's solid color.
* *

View file

@ -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<Shape*>(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<const Shape*>(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) 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; if (!paint) return TVG_RESULT_INVALID_ARGUMENT;

View file

@ -180,6 +180,7 @@ struct SwStroke
SwPoint ptStartSubPath; SwPoint ptStartSubPath;
SwFixed subPathLineLength; SwFixed subPathLineLength;
SwFixed width; SwFixed width;
float miterlimit;
StrokeCap cap; StrokeCap cap;
StrokeJoin join; StrokeJoin join;

View file

@ -233,8 +233,6 @@ static void _arcTo(SwStroke& stroke, int32_t side)
static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength) static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
{ {
constexpr SwFixed MITER_LIMIT = 4 * (1 << 16);
auto border = stroke.borders + side; auto border = stroke.borders + side;
if (stroke.join == StrokeJoin::Round) { if (stroke.join == StrokeJoin::Round) {
@ -257,7 +255,7 @@ static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
} }
thcos = mathCos(theta); thcos = mathCos(theta);
auto sigma = mathMultiply(MITER_LIMIT, thcos); auto sigma = mathMultiply(stroke.miterlimit, thcos);
//is miter limit exceeded? //is miter limit exceeded?
if (sigma < 0x10000L) bevel = true; 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->width = HALF_STROKE(rshape->strokeWidth());
stroke->cap = rshape->strokeCap(); stroke->cap = rshape->strokeCap();
stroke->miterlimit = rshape->strokeMiterlimit() * (1 << 16);
//Save line join: it can be temporarily changed when stroking curves... //Save line join: it can be temporarily changed when stroking curves...
stroke->joinSaved = stroke->join = rshape->strokeJoin(); stroke->joinSaved = stroke->join = rshape->strokeJoin();

View file

@ -139,6 +139,7 @@ struct RenderStroke
uint32_t dashCnt = 0; uint32_t dashCnt = 0;
StrokeCap cap = StrokeCap::Square; StrokeCap cap = StrokeCap::Square;
StrokeJoin join = StrokeJoin::Bevel; StrokeJoin join = StrokeJoin::Bevel;
float miterlimit = 4.0f;
bool strokeFirst = false; bool strokeFirst = false;
~RenderStroke() ~RenderStroke()
@ -225,6 +226,13 @@ struct RenderShape
if (!stroke) return StrokeJoin::Bevel; if (!stroke) return StrokeJoin::Bevel;
return stroke->join; return stroke->join;
} }
float strokeMiterlimit() const
{
if (!stroke) return 4.0f;
return stroke->miterlimit;;
}
}; };
class RenderMethod class RenderMethod

View file

@ -373,6 +373,17 @@ Result Shape::stroke(StrokeJoin join) noexcept
return Result::Success; 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 StrokeCap Shape::strokeCap() const noexcept
{ {
@ -385,6 +396,11 @@ StrokeJoin Shape::strokeJoin() const noexcept
return pImpl->rs.strokeJoin(); return pImpl->rs.strokeJoin();
} }
float Shape::strokeMiterlimit() const noexcept
{
return pImpl->rs.strokeMiterlimit();
}
Result Shape::fill(FillRule r) noexcept Result Shape::fill(FillRule r) noexcept
{ {

View file

@ -245,6 +245,15 @@ struct Shape::Impl
return true; 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) bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{ {
if (!rs.stroke) rs.stroke = new RenderStroke(); if (!rs.stroke) rs.stroke = new RenderStroke();

View file

@ -983,6 +983,22 @@ static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode*
node->style->stroke.join = _toLineJoin(value); 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) 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, Stroke, SvgStyleFlags::Stroke),
STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth), STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth),
STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin), STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin),
STYLE_DEF(stroke-miterlimit, StrokeMiterlimit, SvgStyleFlags::StrokeMiterlimit),
STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap), STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap),
STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity), STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity),
STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray), STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray),
@ -1307,6 +1324,7 @@ static SvgNode* _createNode(SvgNode* parent, SvgNodeType type)
node->style->stroke.cap = StrokeCap::Butt; node->style->stroke.cap = StrokeCap::Butt;
//Default line join is miter //Default line join is miter
node->style->stroke.join = StrokeJoin::Miter; node->style->stroke.join = StrokeJoin::Miter;
node->style->stroke.miterlimit = 4.0f;
node->style->stroke.scale = 1.0; node->style->stroke.scale = 1.0;
node->style->paintOrder = _toPaintOrder("fill stroke"); node->style->paintOrder = _toPaintOrder("fill stroke");
@ -2785,6 +2803,9 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren
if (!(child->stroke.flags & SvgStrokeFlags::Join)) { if (!(child->stroke.flags & SvgStrokeFlags::Join)) {
child->stroke.join = parent->stroke.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) { if (from->stroke.flags & SvgStrokeFlags::Join) {
to->stroke.join = from->stroke.join; to->stroke.join = from->stroke.join;
} }
if (from->stroke.flags & SvgStrokeFlags::Miterlimit) {
to->stroke.miterlimit = from->stroke.miterlimit;
}
} }

View file

@ -100,6 +100,7 @@ enum class SvgStrokeFlags
Cap = 0x20, Cap = 0x20,
Join = 0x40, Join = 0x40,
Dash = 0x80, Dash = 0x80,
Miterlimit = 0x100
}; };
constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b) constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b)
@ -137,7 +138,8 @@ enum class SvgStyleFlags
Mask = 0x2000, Mask = 0x2000,
MaskType = 0x4000, MaskType = 0x4000,
Display = 0x8000, Display = 0x8000,
PaintOrder = 0x10000 PaintOrder = 0x10000,
StrokeMiterlimit = 0x20000
}; };
constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b) constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b)
@ -466,6 +468,7 @@ struct SvgStyleStroke
float centered; float centered;
StrokeCap cap; StrokeCap cap;
StrokeJoin join; StrokeJoin join;
float miterlimit;
SvgDash dash; SvgDash dash;
int dashCount; int dashCount;
}; };

View file

@ -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.width);
vg->stroke(style->stroke.cap); vg->stroke(style->stroke.cap);
vg->stroke(style->stroke.join); vg->stroke(style->stroke.join);
vg->strokeMiterlimit(style->stroke.miterlimit);
if (style->stroke.dash.array.count > 0) { 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);
} }

View file

@ -224,6 +224,7 @@ TEST_CASE("Stroke join", "[capiStrokeJoin]")
REQUIRE(paint); REQUIRE(paint);
Tvg_Stroke_Join join; 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_set_stroke_join(paint, TVG_STROKE_JOIN_BEVEL) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_shape_get_stroke_join(paint, &join) == 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(tvg_shape_get_stroke_join(paint, &join) == TVG_RESULT_SUCCESS);
REQUIRE(join == TVG_STROKE_JOIN_MITER); 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); REQUIRE(tvg_paint_del(paint) == TVG_RESULT_SUCCESS);
} }

View file

@ -191,6 +191,14 @@ TEST_CASE("Stroking", "[tvgShape]")
REQUIRE(shape->stroke(StrokeJoin::Round) == Result::Success); REQUIRE(shape->stroke(StrokeJoin::Round) == Result::Success);
REQUIRE(shape->strokeJoin() == StrokeJoin::Round); 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 //Stroke Order After Stroke Setting
REQUIRE(shape->order(true) == Result::Success); REQUIRE(shape->order(true) == Result::Success);
REQUIRE(shape->order(false) == Result::Success); REQUIRE(shape->order(false) == Result::Success);