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;
/**
* @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.
*

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);
/*!
* \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.
*

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)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;

View file

@ -180,6 +180,7 @@ struct SwStroke
SwPoint ptStartSubPath;
SwFixed subPathLineLength;
SwFixed width;
float miterlimit;
StrokeCap cap;
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)
{
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();

View file

@ -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

View file

@ -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
{

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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;
};

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.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);
}

View file

@ -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);
}

View file

@ -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);