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