1/*!
2 * \file path_dash_effect.cpp
3 * \brief file path_dash_effect.cpp
4 *
5 * Copyright 2019 by Intel.
6 *
7 * Contact: kevin.rogovin@gmail.com
8 *
9 * This Source Code Form is subject to the
10 * terms of the Mozilla Public License, v. 2.0.
11 * If a copy of the MPL was not distributed with
12 * this file, You can obtain one at
13 * http://mozilla.org/MPL/2.0/.
14 *
15 * \author Kevin Rogovin <kevin.rogovin@gmail.com>
16 *
17 */
18
19#include <vector>
20#include <algorithm>
21#include <fastuidraw/path_dash_effect.hpp>
22#include <private/util_private.hpp>
23#include <cmath>
24
25namespace
26{
27 class DashElement
28 {
29 public:
30 float m_start_distance, m_end_distance;
31 float m_draw_length, m_skip_length;
32 };
33
34 class DashElementProxy
35 {
36 public:
37 DashElementProxy(int idx,
38 int cycle,
39 const DashElement &d,
40 float increase_by):
41 m_idx(idx),
42 m_cycle(cycle),
43 m_start_distance(increase_by + d.m_start_distance),
44 m_end_distance(increase_by + d.m_end_distance),
45 m_draw_length(d.m_draw_length),
46 m_skip_length(d.m_skip_length)
47 {}
48
49 float
50 end_draw(void) const
51 {
52 return m_start_distance + m_draw_length;
53 }
54
55 int m_idx, m_cycle;
56 float m_start_distance, m_end_distance;
57 float m_draw_length, m_skip_length;
58 };
59
60 inline
61 bool
62 compare_dash_element_against_distance(const DashElement &lhs, float rhs)
63 {
64 return lhs.m_end_distance < rhs;
65 }
66
67 void
68 add_cap(const fastuidraw::TessellatedPath::segment &S,
69 float length_from_start_S,
70 bool is_start_cap,
71 fastuidraw::PathEffect::Storage &dst)
72 {
73 fastuidraw::TessellatedPath::cap C;
74 float s, t, n;
75
76 t = length_from_start_S / S.m_length;
77 s = 1.0f - t;
78 n = (is_start_cap) ? -1.0f : +1.0f;
79 if (S.m_type == fastuidraw::TessellatedPath::line_segment)
80 {
81 C.m_position = s * S.m_start_pt + t * S.m_end_pt;
82 C.m_unit_vector = n * S.m_enter_segment_unit_vector;
83 }
84 else
85 {
86 float angle(s * S.m_arc_angle.m_begin + t * S.m_arc_angle.m_end);
87 fastuidraw::vec2 cs(fastuidraw::t_cos(angle), fastuidraw::t_sin(angle));
88
89 if (S.m_arc_angle.m_begin > S.m_arc_angle.m_end)
90 {
91 n = -n;
92 }
93 C.m_position = S.m_center + S.m_radius * cs;
94 C.m_unit_vector = n * fastuidraw::vec2(-cs.y(), cs.x());
95 }
96 C.m_contour_length = S.m_contour_length;
97 C.m_edge_length = S.m_edge_length;
98 C.m_distance_from_contour_start = S.m_distance_from_contour_start + t * S.m_length;
99 C.m_distance_from_edge_start = S.m_distance_from_edge_start + t * S.m_length;
100 C.m_is_starting_cap = is_start_cap;
101 C.m_contour_id = S.m_contour_id;
102 dst.add_cap(C);
103 }
104
105 class PathDashEffectPrivate
106 {
107 public:
108 PathDashEffectPrivate(void):
109 m_dash_offset(0.0f)
110 {}
111
112 float
113 current_length(void) const
114 {
115 return (m_lengths.empty()) ?
116 0.0f:
117 m_lengths.back().m_end_distance;
118 }
119
120 void
121 process_segment(const fastuidraw::TessellatedPath::segment &S,
122 DashElementProxy &P,
123 fastuidraw::PathEffect::Storage &dst);
124
125 DashElementProxy
126 proxy(int I) const;
127
128 /* TODO: add parameters to PathDashEffect to select if distance
129 * value starts at contour, since the last join or a distance
130 * since the last join.
131 */
132 float
133 distance(const fastuidraw::TessellatedPath::segment &S)
134 {
135 return m_dash_offset + S.m_distance_from_contour_start;
136 }
137
138 float
139 distance(const fastuidraw::TessellatedPath::join &J)
140 {
141 return m_dash_offset + J.m_distance_from_contour_start;
142 }
143
144 float
145 distance(const fastuidraw::TessellatedPath::cap &C)
146 {
147 return (C.m_is_starting_cap) ?
148 m_dash_offset:
149 m_dash_offset + C.m_contour_length;
150 }
151
152 int
153 find_index(float f) const;
154
155 std::vector<DashElement>::const_iterator
156 find_iterator(float &f, int &N) const;
157
158 bool
159 point_inside_draw_interval(float f) const;
160
161 float m_dash_offset;
162 std::vector<DashElement> m_lengths;
163 };
164}
165
166////////////////////////////////
167// PathDashEffectPrivate methods
168DashElementProxy
169PathDashEffectPrivate::
170proxy(int I) const
171{
172 unsigned int N(I / m_lengths.size());
173 unsigned int srcI(I - N * m_lengths.size());
174 const DashElement &src(m_lengths[srcI]);
175 float fN(N);
176 DashElementProxy return_value(I, N, src, fN * current_length());
177
178 return return_value;
179}
180
181std::vector<DashElement>::const_iterator
182PathDashEffectPrivate::
183find_iterator(float &f, int &N) const
184{
185 float c(current_length());
186 std::vector<DashElement>::const_iterator iter;
187
188 if (f >= c || f < 0.0f)
189 {
190 /* Compute r, fN so that
191 * 1) f = r + fN * c
192 * 2) 0.0 <= r < c
193 */
194 float ff(f / c), fN, r;
195
196 r = std::modf(ff, &fN);
197 if (r < 0.0f)
198 {
199 r += 1.0f;
200 fN -= 1.0f;
201 }
202 N = fN;
203 f = r * c;
204 }
205 else
206 {
207 N = 0;
208 }
209
210 /* iter points to the first element with iter->m_end_distance >= f */
211 iter = std::lower_bound(m_lengths.begin(), m_lengths.end(),
212 f, compare_dash_element_against_distance);
213
214 return iter;
215}
216
217int
218PathDashEffectPrivate::
219find_index(float f) const
220{
221 std::vector<DashElement>::const_iterator iter;
222 int N;
223
224 iter = find_iterator(f, N);
225 return std::distance(m_lengths.begin(), iter) + m_lengths.size() * N;
226}
227
228bool
229PathDashEffectPrivate::
230point_inside_draw_interval(float f) const
231{
232 /* We shall interpret an empty-dash pattern as no dashing at all */
233 if (m_lengths.empty())
234 {
235 return true;
236 }
237
238 std::vector<DashElement>::const_iterator iter;
239 int N;
240
241 iter = find_iterator(f, N);
242 return iter == m_lengths.end()
243 || iter->m_start_distance + iter->m_draw_length >= f;
244}
245
246void
247PathDashEffectPrivate::
248process_segment(const fastuidraw::TessellatedPath::segment &pS,
249 DashElementProxy &P,
250 fastuidraw::PathEffect::Storage &dst)
251{
252 fastuidraw::TessellatedPath::segment S(pS);
253 float t, startS(distance(S)), endS(startS + S.m_length);
254 bool need_to_begin_chain(true);
255 fastuidraw::TessellatedPath::segment A, B;
256
257 for (;;)
258 {
259 FASTUIDRAWassert(startS >= P.m_start_distance);
260 if (P.end_draw() >= startS && P.end_draw() <= endS)
261 {
262 /* Draw interval of P ends within S, so add an ending cap. */
263 add_cap(S, P.end_draw() - startS, false, dst);
264 S.m_last_segment_of_edge = true;
265 }
266
267 if (P.end_draw() >= startS && P.m_draw_length > 0.0f)
268 {
269 if (P.end_draw() < endS)
270 {
271 t = (P.end_draw() - startS) / S.m_length;
272
273 /* add the draw element of S on [0, t] */
274 S.split_segment(t, &A, &B);
275 A.m_last_segment_of_edge = true;
276 dst.add_segment(A);
277
278 /* continue with the S on [t, 1], note that we
279 * need to start a new chain
280 */
281 S = B;
282 S.m_continuation_with_predecessor = false;
283 S.m_first_segment_of_edge = true;
284 startS = distance(S);
285 need_to_begin_chain = true;
286 }
287 else
288 {
289 /* The draw interval extends past the ending of S,
290 * so we are done.
291 */
292 dst.add_segment(S);
293 return;
294 }
295 }
296
297 if (P.m_end_distance <= endS)
298 {
299 /* The skip interval ends within S, add a starting cap for
300 * the next draw interval.
301 */
302 add_cap(S, P.m_end_distance - startS, true, dst);
303 }
304 else
305 {
306 /* the skip interval extends past S, so we are done with S */
307 return;
308 }
309
310 /* S extends past the skip interval, take the portion of S that does */
311 t = (P.m_end_distance - startS) / S.m_length;
312 S.split_segment(t, &A, &B);
313 S = B;
314 S.m_first_segment_of_edge = true;
315 S.m_continuation_with_predecessor = false;
316 startS = distance(S);
317
318 P = proxy(P.m_idx + 1);
319 if (need_to_begin_chain)
320 {
321 dst.begin_chain(nullptr);
322 need_to_begin_chain = false;
323 }
324 }
325}
326
327//////////////////////////////////////
328// fastuidraw::PathDashEffect methods
329fastuidraw::PathDashEffect::
330PathDashEffect(void)
331{
332 m_d = FASTUIDRAWnew PathDashEffectPrivate();
333}
334
335fastuidraw::PathDashEffect::
336~PathDashEffect(void)
337{
338 PathDashEffectPrivate *d;
339 d = static_cast<PathDashEffectPrivate*>(m_d);
340 FASTUIDRAWdelete(d);
341}
342
343fastuidraw::PathDashEffect&
344fastuidraw::PathDashEffect::
345dash_offset(float v)
346{
347 PathDashEffectPrivate *d;
348 d = static_cast<PathDashEffectPrivate*>(m_d);
349 d->m_dash_offset = v;
350 return *this;
351}
352
353fastuidraw::PathDashEffect&
354fastuidraw::PathDashEffect::
355clear(void)
356{
357 PathDashEffectPrivate *d;
358 d = static_cast<PathDashEffectPrivate*>(m_d);
359 d->m_lengths.clear();
360 return *this;
361}
362
363fastuidraw::PathDashEffect&
364fastuidraw::PathDashEffect::
365add_dash(float draw, float skip)
366{
367 PathDashEffectPrivate *d;
368 d = static_cast<PathDashEffectPrivate*>(m_d);
369
370 if (draw < 0.0f || skip < 0.0f)
371 {
372 FASTUIDRAWmessaged_assert(draw >= 0.0f, "Bad draw length passed to add_dash");
373 FASTUIDRAWmessaged_assert(skip >= 0.0f, "Bad skip length passed to add_dash");
374 return *this;
375 }
376
377 if (!d->m_lengths.empty() && d->m_lengths.back().m_skip_length == 0.0f)
378 {
379 d->m_lengths.back().m_draw_length += draw;
380 d->m_lengths.back().m_skip_length += skip;
381 d->m_lengths.back().m_end_distance += draw + skip;
382 return *this;
383 }
384
385 DashElement E;
386
387 E.m_draw_length = draw;
388 E.m_skip_length = skip;
389 E.m_start_distance = d->current_length();
390 E.m_end_distance = E.m_start_distance + draw + skip;
391
392 d->m_lengths.push_back(E);
393 return *this;
394}
395
396void
397fastuidraw::PathDashEffect::
398process_join(const TessellatedPath::join &join, Storage &dst) const
399{
400 PathDashEffectPrivate *d;
401 d = static_cast<PathDashEffectPrivate*>(m_d);
402
403 float dist(d->distance(join));
404 if (d->point_inside_draw_interval(dist))
405 {
406 dst.add_join(join);
407 }
408}
409
410void
411fastuidraw::PathDashEffect::
412process_cap(const TessellatedPath::cap &cap, Storage &dst) const
413{
414 PathDashEffectPrivate *d;
415 d = static_cast<PathDashEffectPrivate*>(m_d);
416
417 float dist(d->distance(cap));
418 if (d->point_inside_draw_interval(dist))
419 {
420 dst.add_cap(cap);
421 }
422}
423
424void
425fastuidraw::PathDashEffect::
426process_chain(const segment_chain &chain, Storage &dst) const
427{
428 PathDashEffectPrivate *d;
429 d = static_cast<PathDashEffectPrivate*>(m_d);
430
431 if (chain.m_segments.empty())
432 {
433 return;
434 }
435
436 if (d->m_lengths.empty())
437 {
438 dst.begin_chain(chain.m_prev_to_start);
439 for (const auto &S : chain.m_segments)
440 {
441 dst.add_segment(S);
442 }
443 return;
444 }
445
446 const TessellatedPath::segment *prev_segment(chain.m_prev_to_start);
447 const TessellatedPath::segment &front(chain.m_segments.front());
448 float dist(d->distance(front));
449 DashElementProxy P(d->proxy(d->find_index(dist)));
450
451 dst.begin_chain(prev_segment);
452
453 /* check if we need to give a starting cap to the
454 * first segment of the chain; this is only needed
455 * for the first segment of the first edge.
456 */
457 if (front.m_edge_id == 0 && front.m_first_segment_of_edge)
458 {
459 add_cap(front, 0.0f, true, dst);
460 }
461
462 for (const auto &S : chain.m_segments)
463 {
464 d->process_segment(S, P, dst);
465 }
466}
467