common: stroke dash offset support added

The feature is supported also in the svg loader.

@Issue: https://github.com/thorvg/thorvg/issues/1591
This commit is contained in:
Mira Grudzinska 2023-08-18 23:19:30 +02:00 committed by Hermet Park
parent c1e4e0808a
commit 478e45f9f3
9 changed files with 57 additions and 19 deletions

View file

@ -1002,6 +1002,7 @@ public:
* *
* @param[in] dashPattern The array of consecutive pair values of the dash length and the gap length. * @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] 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::Success When succeed.
* @retval Result::FailedAllocation An internal error with a memory allocation for an object to be dashed. * @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. * @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. * @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. * @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. * @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] 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. * @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. * @brief Gets the cap style used for stroking the path.

View file

@ -132,7 +132,7 @@ struct SwShapeTask : SwTask
shape outline below stroke could be full covered by stroke drawing. shape outline below stroke could be full covered by stroke drawing.
Thus it turns off antialising in that condition. Thus it turns off antialising in that condition.
Also, it shouldn't be dash style. */ 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; if (!shapeGenRle(&shape, rshape, antiAlias)) goto err;
} }

View file

@ -222,9 +222,27 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
dash.curOpGap = false; dash.curOpGap = false;
const float* pattern; const float* pattern;
dash.cnt = rshape->strokeDash(&pattern); float offset;
dash.cnt = rshape->strokeDash(&pattern, &offset);
if (dash.cnt == 0) return nullptr; 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??? //OPTMIZE ME: Use mempool???
dash.pattern = const_cast<float*>(pattern); dash.pattern = const_cast<float*>(pattern);
dash.outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline))); dash.outline = static_cast<SwOutline*>(calloc(1, sizeof(SwOutline)));
@ -271,9 +289,9 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
} }
case PathCommand::MoveTo: { case PathCommand::MoveTo: {
//reset the dash //reset the dash
dash.curIdx = 0; dash.curIdx = offIdx % dash.cnt;
dash.curLen = *dash.pattern; dash.curLen = dash.pattern[dash.curIdx] - offset;
dash.curOpGap = false; dash.curOpGap = offIdx % 2;
dash.ptStart = dash.ptCur = *pts; dash.ptStart = dash.ptCur = *pts;
++pts; ++pts;
break; break;
@ -515,7 +533,7 @@ bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix*
bool ret = true; bool ret = true;
//Dash Style Stroke //Dash Style Stroke
if (rshape->strokeDash(nullptr) > 0) { if (rshape->strokeDash(nullptr, nullptr) > 0) {
shapeOutline = _genDashOutline(rshape, transform); shapeOutline = _genDashOutline(rshape, transform);
if (!shapeOutline) return false; if (!shapeOutline) return false;
freeOutline = true; freeOutline = true;

View file

@ -137,6 +137,7 @@ struct RenderStroke
Fill *fill = nullptr; Fill *fill = nullptr;
float* dashPattern = nullptr; float* dashPattern = nullptr;
uint32_t dashCnt = 0; uint32_t dashCnt = 0;
float dashOffset = 0.0f;
StrokeCap cap = StrokeCap::Square; StrokeCap cap = StrokeCap::Square;
StrokeJoin join = StrokeJoin::Bevel; StrokeJoin join = StrokeJoin::Bevel;
float miterlimit = 4.0f; float miterlimit = 4.0f;
@ -200,10 +201,11 @@ struct RenderShape
return stroke->fill; return stroke->fill;
} }
uint32_t strokeDash(const float** dashPattern) const uint32_t strokeDash(const float** dashPattern, float* offset) const
{ {
if (!stroke) return 0; if (!stroke) return 0;
if (dashPattern) *dashPattern = stroke->dashPattern; if (dashPattern) *dashPattern = stroke->dashPattern;
if (offset) *offset = stroke->dashOffset;
return stroke->dashCnt; return stroke->dashCnt;
} }

View file

@ -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)) { if ((cnt == 1) || (!dashPattern && cnt > 0) || (dashPattern && cnt == 0)) {
return Result::InvalidArguments; 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++) for (uint32_t i = 0; i < cnt; i++)
if (dashPattern[i] < FLT_EPSILON) return Result::InvalidArguments; 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; 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);
} }

View file

@ -269,7 +269,7 @@ struct Shape::Impl
return Result::Success; return Result::Success;
} }
bool strokeDash(const float* pattern, uint32_t cnt) bool strokeDash(const float* pattern, uint32_t cnt, float offset)
{ {
//Reset dash //Reset dash
if (!pattern && cnt == 0) { if (!pattern && cnt == 0) {
@ -290,6 +290,7 @@ struct Shape::Impl
} }
} }
rs.stroke->dashCnt = cnt; rs.stroke->dashCnt = cnt;
rs.stroke->dashOffset = offset;
flag |= RenderUpdateFlag::Stroke; flag |= RenderUpdateFlag::Stroke;
return true; return true;
@ -338,6 +339,7 @@ struct Shape::Impl
dup->rs.stroke = new RenderStroke(); dup->rs.stroke = new RenderStroke();
dup->rs.stroke->width = rs.stroke->width; dup->rs.stroke->width = rs.stroke->width;
dup->rs.stroke->dashCnt = rs.stroke->dashCnt; dup->rs.stroke->dashCnt = rs.stroke->dashCnt;
dup->rs.stroke->dashOffset = rs.stroke->dashOffset;
dup->rs.stroke->cap = rs.stroke->cap; dup->rs.stroke->cap = rs.stroke->cap;
dup->rs.stroke->join = rs.stroke->join; dup->rs.stroke->join = rs.stroke->join;
dup->rs.stroke->strokeFirst = rs.stroke->strokeFirst; dup->rs.stroke->strokeFirst = rs.stroke->strokeFirst;

View file

@ -952,6 +952,12 @@ static void _handleStrokeDashArrayAttr(SvgLoaderData* loader, SvgNode* node, con
_parseDashArray(loader, value, &node->style->stroke.dash); _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) static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
{ {
node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::Width); 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-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),
STYLE_DEF(stroke-dashoffset, StrokeDashOffset, SvgStyleFlags::StrokeDashOffset),
STYLE_DEF(transform, Transform, SvgStyleFlags::Transform), STYLE_DEF(transform, Transform, SvgStyleFlags::Transform),
STYLE_DEF(clip-path, ClipPath, SvgStyleFlags::ClipPath), STYLE_DEF(clip-path, ClipPath, SvgStyleFlags::ClipPath),
STYLE_DEF(mask, Mask, SvgStyleFlags::Mask), 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)) { if (!(child->stroke.flags & SvgStrokeFlags::Cap)) {
child->stroke.cap = parent->stroke.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) { if (from->stroke.flags & SvgStrokeFlags::Cap) {
to->stroke.cap = from->stroke.cap; to->stroke.cap = from->stroke.cap;
} }
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) { if (from->stroke.flags & SvgStrokeFlags::Miterlimit) {
to->stroke.miterlimit = from->stroke.miterlimit; to->stroke.miterlimit = from->stroke.miterlimit;
} }

View file

@ -100,7 +100,8 @@ enum class SvgStrokeFlags
Cap = 0x20, Cap = 0x20,
Join = 0x40, Join = 0x40,
Dash = 0x80, Dash = 0x80,
Miterlimit = 0x100 Miterlimit = 0x100,
DashOffset = 0x200
}; };
constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b) constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b)
@ -139,7 +140,8 @@ enum class SvgStyleFlags
MaskType = 0x4000, MaskType = 0x4000,
Display = 0x8000, Display = 0x8000,
PaintOrder = 0x10000, PaintOrder = 0x10000,
StrokeMiterlimit = 0x20000 StrokeMiterlimit = 0x20000,
StrokeDashOffset = 0x40000,
}; };
constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b) constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b)
@ -423,6 +425,7 @@ struct SvgPaint
struct SvgDash struct SvgDash
{ {
Array<float> array; Array<float> array;
float offset;
}; };
struct SvgStyleGradient struct SvgStyleGradient
@ -469,7 +472,6 @@ struct SvgStyleStroke
StrokeJoin join; StrokeJoin join;
float miterlimit; float miterlimit;
SvgDash dash; SvgDash dash;
int dashCount;
}; };
struct SvgStyleProperty struct SvgStyleProperty

View file

@ -349,7 +349,7 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg,
vg->stroke(style->stroke.join); vg->stroke(style->stroke.join);
vg->strokeMiterlimit(style->stroke.miterlimit); 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, style->stroke.dash.offset);
} }
//If stroke property is nullptr then do nothing //If stroke property is nullptr then do nothing