1/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "include/core/SkPathMeasure.h"
9#include "include/effects/SkTrimPathEffect.h"
10#include "src/core/SkReadBuffer.h"
11#include "src/core/SkWriteBuffer.h"
12#include "src/effects/SkTrimPE.h"
13
14namespace {
15
16// Returns the number of contours iterated to satisfy the request.
17static size_t add_segments(const SkPath& src, SkScalar start, SkScalar stop, SkPath* dst,
18 bool requires_moveto = true) {
19 SkASSERT(start < stop);
20
21 SkPathMeasure measure(src, false);
22
23 SkScalar current_segment_offset = 0;
24 size_t contour_count = 1;
25
26 do {
27 const auto next_offset = current_segment_offset + measure.getLength();
28
29 if (start < next_offset) {
30 measure.getSegment(start - current_segment_offset,
31 stop - current_segment_offset,
32 dst, requires_moveto);
33
34 if (stop <= next_offset)
35 break;
36 }
37
38 contour_count++;
39 current_segment_offset = next_offset;
40 } while (measure.nextContour());
41
42 return contour_count;
43}
44
45} // namespace
46
47SkTrimPE::SkTrimPE(SkScalar startT, SkScalar stopT, SkTrimPathEffect::Mode mode)
48 : fStartT(startT), fStopT(stopT), fMode(mode) {}
49
50bool SkTrimPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*) const {
51 if (fStartT >= fStopT) {
52 SkASSERT(fMode == SkTrimPathEffect::Mode::kNormal);
53 return true;
54 }
55
56 // First pass: compute the total len.
57 SkScalar len = 0;
58 SkPathMeasure meas(src, false);
59 do {
60 len += meas.getLength();
61 } while (meas.nextContour());
62
63 const auto arcStart = len * fStartT,
64 arcStop = len * fStopT;
65
66 // Second pass: actually add segments.
67 if (fMode == SkTrimPathEffect::Mode::kNormal) {
68 // Normal mode -> one span.
69 if (arcStart < arcStop) {
70 add_segments(src, arcStart, arcStop, dst);
71 }
72 } else {
73 // Inverted mode -> one logical span which wraps around at the end -> two actual spans.
74 // In order to preserve closed path continuity:
75 //
76 // 1) add the second/tail span first
77 //
78 // 2) skip the head span move-to for single-closed-contour paths
79
80 bool requires_moveto = true;
81 if (arcStop < len) {
82 // since we're adding the "tail" first, this is the total number of contours
83 const auto contour_count = add_segments(src, arcStop, len, dst);
84
85 // if the path consists of a single closed contour, we don't want to disconnect
86 // the two parts with a moveto.
87 if (contour_count == 1 && src.isLastContourClosed()) {
88 requires_moveto = false;
89 }
90 }
91 if (0 < arcStart) {
92 add_segments(src, 0, arcStart, dst, requires_moveto);
93 }
94 }
95
96 return true;
97}
98
99void SkTrimPE::flatten(SkWriteBuffer& buffer) const {
100 buffer.writeScalar(fStartT);
101 buffer.writeScalar(fStopT);
102 buffer.writeUInt(static_cast<uint32_t>(fMode));
103}
104
105sk_sp<SkFlattenable> SkTrimPE::CreateProc(SkReadBuffer& buffer) {
106 const auto start = buffer.readScalar(),
107 stop = buffer.readScalar();
108 const auto mode = buffer.readUInt();
109
110 return SkTrimPathEffect::Make(start, stop,
111 (mode & 1) ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal);
112}
113
114//////////////////////////////////////////////////////////////////////////////////////////////////
115
116sk_sp<SkPathEffect> SkTrimPathEffect::Make(SkScalar startT, SkScalar stopT, Mode mode) {
117 if (!SkScalarsAreFinite(startT, stopT)) {
118 return nullptr;
119 }
120
121 if (startT <= 0 && stopT >= 1 && mode == Mode::kNormal) {
122 return nullptr;
123 }
124
125 startT = SkTPin(startT, 0.f, 1.f);
126 stopT = SkTPin(stopT, 0.f, 1.f);
127
128 if (startT >= stopT && mode == Mode::kInverted) {
129 return nullptr;
130 }
131
132 return sk_sp<SkPathEffect>(new SkTrimPE(startT, stopT, mode));
133}
134