GCC Code Coverage Report


Directory: ./
File: src/2geom/path.cpp
Date: 2024-03-18 17:01:34
Exec Total Coverage
Lines: 408 659 61.9%
Functions: 39 58 67.2%
Branches: 530 1322 40.1%

Line Branch Exec Source
1 /** @file
2 * @brief Path - a sequence of contiguous curves (implementation file)
3 *//*
4 * Authors:
5 * MenTaLguY <mental@rydia.net>
6 * Marco Cecchetti <mrcekets at gmail.com>
7 * Krzysztof Kosiński <tweenk.pl@gmail.com>
8 *
9 * Copyright 2007-2014 authors
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it either under the terms of the GNU Lesser General Public
13 * License version 2.1 as published by the Free Software Foundation
14 * (the "LGPL") or, at your option, under the terms of the Mozilla
15 * Public License Version 1.1 (the "MPL"). If you do not alter this
16 * notice, a recipient may use your version of this file under either
17 * the MPL or the LGPL.
18 *
19 * You should have received a copy of the LGPL along with this library
20 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * You should have received a copy of the MPL along with this library
23 * in the file COPYING-MPL-1.1
24 *
25 * The contents of this file are subject to the Mozilla Public License
26 * Version 1.1 (the "License"); you may not use this file except in
27 * compliance with the License. You may obtain a copy of the License at
28 * http://www.mozilla.org/MPL/
29 *
30 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
31 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
32 * the specific language governing rights and limitations.
33 */
34
35 #include <2geom/path.h>
36 #include <2geom/pathvector.h>
37 #include <2geom/transforms.h>
38 #include <2geom/circle.h>
39 #include <2geom/ellipse.h>
40 #include <2geom/convex-hull.h>
41 #include <2geom/svg-path-writer.h>
42 #include <2geom/sweeper.h>
43 #include <algorithm>
44 #include <limits>
45 #include <optional>
46
47 using std::swap;
48 using namespace Geom::PathInternal;
49
50 namespace Geom {
51
52 // this represents an empty interval
53 116 PathInterval::PathInterval()
54 116 : _from(0, 0.0)
55 116 , _to(0, 0.0)
56 116 , _path_size(1)
57 116 , _cross_start(false)
58 116 , _reverse(false)
59 116 {}
60
61 50 PathInterval::PathInterval(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size)
62 50 : _from(from)
63 50 , _to(to)
64 50 , _path_size(path_size)
65 50 , _cross_start(cross_start)
66 50 , _reverse((to < from) ^ cross_start)
67 {
68
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 31 times.
50 if (_reverse) {
69 19 _to.normalizeForward(_path_size);
70
6/6
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 10 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 18 times.
19 if (cross_start && _to < to) {
71 // Normalization made us cross start (closed path),
72 // so we don't need to cross the start anymore.
73 1 _cross_start = false;
74 }
75
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 if (_from != _to) {
76 19 _from.normalizeBackward(_path_size);
77
6/6
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 10 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 18 times.
19 if (cross_start && _from > from) {
78 // Normalization backwards made us logically cross
79 // the start – we shouldn't cross the start again.
80 1 _cross_start = false;
81 }
82 }
83 } else {
84 31 _from.normalizeForward(_path_size);
85
6/6
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 22 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 30 times.
31 if (cross_start && _from < from) {
86 1 _cross_start = false;
87 }
88
1/2
✓ Branch 1 taken 31 times.
✗ Branch 2 not taken.
31 if (_from != _to) {
89 31 _to.normalizeBackward(_path_size);
90
6/6
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 22 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 30 times.
31 if (cross_start && _to > to) {
91 1 _cross_start = false;
92 }
93 }
94 }
95
96
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
50 if (_from == _to) {
97 _reverse = false;
98 _cross_start = false;
99 }
100 50 }
101
102 184 bool PathInterval::contains(PathTime const &pos) const {
103
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 94 times.
184 if (_cross_start) {
104
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 45 times.
90 if (_reverse) {
105
4/4
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 15 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 12 times.
45 return pos >= _to || _from >= pos;
106 } else {
107
4/4
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 15 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 12 times.
45 return pos >= _from || _to >= pos;
108 }
109 } else {
110
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 47 times.
94 if (_reverse) {
111
4/4
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 14 times.
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 11 times.
47 return _to <= pos && pos <= _from;
112 } else {
113
4/4
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 14 times.
✓ Branch 4 taken 22 times.
✓ Branch 5 taken 11 times.
47 return _from <= pos && pos <= _to;
114 }
115 }
116 }
117
118 20 PathInterval::size_type PathInterval::curveCount() const
119 {
120
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
20 if (isDegenerate()) return 0;
121
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
20 if (_cross_start) {
122
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 if (_reverse) {
123 5 return _path_size - _to.curve_index + _from.curve_index + 1;
124 } else {
125 5 return _path_size - _from.curve_index + _to.curve_index + 1;
126 }
127 } else {
128
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 if (_reverse) {
129 5 return _from.curve_index - _to.curve_index + 1;
130 } else {
131 5 return _to.curve_index - _from.curve_index + 1;
132 }
133 }
134 }
135
136 64 PathTime PathInterval::inside(Coord min_dist) const
137 {
138 // If there is some node further than min_dist (in time coord) from the ends,
139 // return that node. Otherwise, return the middle.
140 64 PathTime result(0, 0.0);
141
142
4/4
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 28 times.
64 if (!_cross_start && _from.curve_index == _to.curve_index) {
143 18 PathTime result(_from.curve_index, lerp(0.5, _from.t, _to.t));
144 18 return result;
145 }
146 // If _cross_start, then we can be sure that at least one node is in the domain.
147 // If dcurve == 0, it actually means that all curves are included in the domain
148
149
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 37 times.
46 if (_reverse) {
150 9 size_type dcurve = (_path_size + _from.curve_index - _to.curve_index) % _path_size;
151 9 bool from_close = _from.t < min_dist;
152 9 bool to_close = _to.t > 1 - min_dist;
153
154
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 if (dcurve == 0) {
155 1 dcurve = _path_size;
156 }
157
158
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if (dcurve == 1) {
159
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
3 if (from_close || to_close) {
160 2 result.curve_index = _from.curve_index;
161 2 Coord tmid = _from.t - ((1 - _to.t) + _from.t) * 0.5;
162
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (tmid < 0) {
163 result.curve_index = (_path_size + result.curve_index - 1) % _path_size;
164 tmid += 1;
165 }
166 2 result.t = tmid;
167 2 return result;
168 }
169
170 1 result.curve_index = _from.curve_index;
171 1 return result;
172 }
173
174 6 result.curve_index = (_to.curve_index + 1) % _path_size;
175
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
6 if (to_close) {
176
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (dcurve == 2) {
177 1 result.t = 0.5;
178 } else {
179 1 result.curve_index = (result.curve_index + 1) % _path_size;
180 }
181 }
182 6 return result;
183 } else {
184 37 size_type dcurve = (_path_size + _to.curve_index - _from.curve_index) % _path_size;
185 37 bool from_close = _from.t > 1 - min_dist;
186 37 bool to_close = _to.t < min_dist;
187
188
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 34 times.
37 if (dcurve == 0) {
189 3 dcurve = _path_size;
190 }
191
192
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 19 times.
37 if (dcurve == 1) {
193
3/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
18 if (from_close || to_close) {
194 2 result.curve_index = _from.curve_index;
195 2 Coord tmid = ((1 - _from.t) + _to.t) * 0.5 + _from.t;
196
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (tmid >= 1) {
197 2 result.curve_index = (result.curve_index + 1) % _path_size;
198 2 tmid -= 1;
199 }
200 2 result.t = tmid;
201 2 return result;
202 }
203
204 16 result.curve_index = _to.curve_index;
205 16 return result;
206 }
207
208 19 result.curve_index = (_from.curve_index + 1) % _path_size;
209
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 17 times.
19 if (from_close) {
210
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (dcurve == 2) {
211 1 result.t = 0.5;
212 } else {
213 1 result.curve_index = (result.curve_index + 1) % _path_size;
214 }
215 }
216 19 return result;
217 }
218
219 result.curve_index = _reverse ? _from.curve_index : _to.curve_index;
220 return result;
221 }
222
223 96 PathInterval PathInterval::from_direction(PathTime const &from, PathTime const &to, bool reversed, size_type path_size)
224 {
225 96 PathInterval result;
226 96 result._from = from;
227 96 result._to = to;
228 96 result._path_size = path_size;
229
230
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 66 times.
96 if (reversed) {
231 30 result._to.normalizeForward(path_size);
232
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 if (result._from != result._to) {
233 30 result._from.normalizeBackward(path_size);
234 }
235 } else {
236 66 result._from.normalizeForward(path_size);
237
1/2
✓ Branch 1 taken 66 times.
✗ Branch 2 not taken.
66 if (result._from != result._to) {
238 66 result._to.normalizeBackward(path_size);
239 }
240 }
241
242
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 96 times.
96 if (result._from == result._to) {
243 result._reverse = false;
244 result._cross_start = false;
245 } else {
246 96 result._reverse = reversed;
247
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 66 times.
96 if (reversed) {
248 30 result._cross_start = from < to;
249 } else {
250 66 result._cross_start = to < from;
251 }
252 }
253 96 return result;
254 }
255
256
257 1 Path::Path(Rect const &r)
258
2/6
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
1 : _data(new PathData())
259
2/6
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
1 , _closing_seg(new ClosingSegment(r.corner(3), r.corner(0)))
260 1 , _closed(true)
261 1 , _exception_on_stitch(true)
262 {
263
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 for (unsigned i = 0; i < 3; ++i) {
264
3/8
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
3 _data->curves.push_back(new LineSegment(r.corner(i), r.corner(i+1)));
265 }
266
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 _data->curves.push_back(_closing_seg);
267 1 }
268
269 Path::Path(ConvexHull const &ch)
270 : _data(new PathData())
271 , _closing_seg(new ClosingSegment(Point(), Point()))
272 , _closed(true)
273 , _exception_on_stitch(true)
274 {
275 if (ch.empty()) {
276 _data->curves.push_back(_closing_seg);
277 return;
278 }
279
280 _closing_seg->setInitial(ch.back());
281 _closing_seg->setFinal(ch.front());
282
283 Point last = ch.front();
284
285 for (std::size_t i = 1; i < ch.size(); ++i) {
286 _data->curves.push_back(new LineSegment(last, ch[i]));
287 last = ch[i];
288 }
289
290 _data->curves.push_back(_closing_seg);
291 _closed = true;
292 }
293
294 Path::Path(Circle const &c)
295 : _data(new PathData())
296 , _closing_seg(NULL)
297 , _closed(true)
298 , _exception_on_stitch(true)
299 {
300 Point p1 = c.pointAt(0);
301 Point p2 = c.pointAt(M_PI);
302 _data->curves.push_back(new EllipticalArc(p1, c.radius(), c.radius(), 0, false, true, p2));
303 _data->curves.push_back(new EllipticalArc(p2, c.radius(), c.radius(), 0, false, true, p1));
304 _closing_seg = new ClosingSegment(p1, p1);
305 _data->curves.push_back(_closing_seg);
306 }
307
308 Path::Path(Ellipse const &e)
309 : _data(new PathData())
310 , _closing_seg(NULL)
311 , _closed(true)
312 , _exception_on_stitch(true)
313 {
314 Point p1 = e.pointAt(0);
315 Point p2 = e.pointAt(M_PI);
316 _data->curves.push_back(new EllipticalArc(p1, e.rays(), e.rotationAngle(), false, true, p2));
317 _data->curves.push_back(new EllipticalArc(p2, e.rays(), e.rotationAngle(), false, true, p1));
318 _closing_seg = new ClosingSegment(p1, p1);
319 _data->curves.push_back(_closing_seg);
320 }
321
322 416 void Path::close(bool c)
323 {
324
2/2
✓ Branch 0 taken 46 times.
✓ Branch 1 taken 370 times.
416 if (c == _closed) return;
325
5/6
✓ Branch 0 taken 370 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 369 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 369 times.
✓ Branch 7 taken 1 times.
370 if (c && _data->curves.size() >= 2) {
326 // when closing, if last segment is linear and ends at initial point,
327 // replace it with the closing segment
328
2/4
✓ Branch 3 taken 369 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 369 times.
✗ Branch 7 not taken.
369 Sequence::iterator last = _data->curves.end() - 2;
329
13/20
✓ Branch 2 taken 369 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 212 times.
✓ Branch 5 taken 157 times.
✓ Branch 8 taken 212 times.
✗ Branch 9 not taken.
✓ Branch 13 taken 212 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 54 times.
✓ Branch 17 taken 158 times.
✓ Branch 18 taken 212 times.
✓ Branch 19 taken 157 times.
✓ Branch 21 taken 212 times.
✓ Branch 22 taken 157 times.
✓ Branch 24 taken 54 times.
✓ Branch 25 taken 315 times.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
369 if (last->isLineSegment() && last->finalPoint() == initialPoint()) {
330
2/4
✓ Branch 3 taken 54 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 54 times.
✗ Branch 7 not taken.
54 _closing_seg->setInitial(last->initialPoint());
331
1/2
✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
54 _data->curves.erase(last);
332 }
333 }
334 370 _closed = c;
335 }
336
337 1406 void Path::clear()
338 {
339 1406 _unshare();
340
2/4
✓ Branch 3 taken 1406 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1406 times.
✗ Branch 7 not taken.
1406 _data->curves.pop_back().release();
341 1406 _data->curves.clear();
342
1/2
✓ Branch 3 taken 1406 times.
✗ Branch 4 not taken.
1406 _closing_seg->setInitial(Point(0, 0));
343
1/2
✓ Branch 3 taken 1406 times.
✗ Branch 4 not taken.
1406 _closing_seg->setFinal(Point(0, 0));
344 1406 _data->curves.push_back(_closing_seg);
345 1406 _closed = false;
346 1406 }
347
348 285130 OptRect Path::boundsFast() const
349 {
350 285130 OptRect bounds;
351
2/4
✓ Branch 1 taken 285130 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 285130 times.
285130 if (empty()) {
352 return bounds;
353 }
354 // if the path is not empty, we look for cached bounds
355
2/2
✓ Branch 2 taken 285081 times.
✓ Branch 3 taken 49 times.
285130 if (_data->fast_bounds) {
356 285081 return _data->fast_bounds;
357 }
358
359
3/6
✓ Branch 3 taken 49 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 49 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 49 times.
✗ Branch 10 not taken.
49 bounds = front().boundsFast();
360
1/2
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 const_iterator iter = begin();
361 // the closing path segment can be ignored, because it will always
362 // lie within the bbox of the rest of the path
363
3/6
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 49 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 49 times.
✗ Branch 9 not taken.
49 if (iter != end()) {
364
4/6
✓ Branch 4 taken 246 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 246 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 197 times.
✓ Branch 11 taken 49 times.
246 for (++iter; iter != end(); ++iter) {
365
3/6
✓ Branch 2 taken 197 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 197 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 197 times.
✗ Branch 9 not taken.
197 bounds.unionWith(iter->boundsFast());
366 }
367 }
368 49 _data->fast_bounds = bounds;
369 49 return bounds;
370 }
371
372 OptRect Path::boundsExact() const
373 {
374 OptRect bounds;
375 if (empty())
376 return bounds;
377 bounds = front().boundsExact();
378 const_iterator iter = begin();
379 // the closing path segment can be ignored, because it will always lie within the bbox of the rest of the path
380 if (iter != end()) {
381 for (++iter; iter != end(); ++iter) {
382 bounds.unionWith(iter->boundsExact());
383 }
384 }
385 return bounds;
386 }
387
388 27 Piecewise<D2<SBasis> > Path::toPwSb() const
389 {
390 27 Piecewise<D2<SBasis> > ret;
391
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
27 ret.push_cut(0);
392 27 unsigned i = 1;
393 27 bool degenerate = true;
394 // pw<d2<>> is always open. so if path is closed, add closing segment as well to pwd2.
395
5/8
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 309 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 309 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 282 times.
✓ Branch 14 taken 27 times.
309 for (const_iterator it = begin(); it != end_default(); ++it) {
396
3/6
✓ Branch 1 taken 282 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 282 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 282 times.
✗ Branch 7 not taken.
282 if (!it->isDegenerate()) {
397
3/6
✓ Branch 2 taken 282 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 282 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 282 times.
✗ Branch 9 not taken.
282 ret.push(it->toSBasis(), i++);
398 282 degenerate = false;
399 }
400 }
401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 if (degenerate) {
402 // if path only contains degenerate curves, no second cut is added
403 // so we need to create at least one segment manually
404 ret = Piecewise<D2<SBasis> >(initialPoint());
405 }
406 27 return ret;
407 }
408
409 template <typename iter>
410 iter inc(iter const &x, unsigned n) {
411 iter ret = x;
412 for (unsigned i = 0; i < n; i++)
413 ret++;
414 return ret;
415 }
416
417 72 bool Path::operator==(Path const &other) const
418 {
419
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 72 times.
72 if (this == &other)
420 return true;
421
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 72 times.
72 if (_closed != other._closed)
422 return false;
423 72 return _data->curves == other._data->curves;
424 }
425
426 13407 void Path::start(Point const &p) {
427
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 13407 times.
13407 if (_data->curves.size() > 1) {
428 clear();
429 }
430 13407 _closing_seg->setInitial(p);
431 13407 _closing_seg->setFinal(p);
432 13407 }
433
434 60 Interval Path::timeRange() const
435 {
436
2/4
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 60 times.
✗ Branch 6 not taken.
60 Interval ret(0, size_default());
437 60 return ret;
438 }
439
440 21 Curve const &Path::curveAt(Coord t, Coord *rest) const
441 {
442 21 PathTime pos = _factorTime(t);
443
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 if (rest) {
444 *rest = pos.t;
445 }
446 21 return at(pos.curve_index);
447 }
448
449 148 Point Path::pointAt(Coord t) const
450 {
451
2/4
✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 148 times.
✗ Branch 6 not taken.
296 return pointAt(_factorTime(t));
452 }
453
454 Coord Path::valueAt(Coord t, Dim2 d) const
455 {
456 return valueAt(_factorTime(t), d);
457 }
458
459 Curve const &Path::curveAt(PathTime const &pos) const
460 {
461 return at(pos.curve_index);
462 }
463 226 Point Path::pointAt(PathTime const &pos) const
464 {
465 226 return at(pos.curve_index).pointAt(pos.t);
466 }
467 1 Coord Path::valueAt(PathTime const &pos, Dim2 d) const
468 {
469 1 return at(pos.curve_index).valueAt(pos.t, d);
470 }
471
472 2 std::vector<PathTime> Path::roots(Coord v, Dim2 d) const
473 {
474 2 std::vector<PathTime> res;
475
3/4
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 2 times.
7 for (unsigned i = 0; i < size(); i++) {
476
2/4
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
5 std::vector<Coord> temp = (*this)[i].roots(v, d);
477
2/2
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 5 times.
8 for (double j : temp)
478
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 res.emplace_back(i, j);
479 5 }
480 2 return res;
481 }
482
483
484 // The class below implements sweepline optimization for curve intersection in paths.
485 // Instead of O(N^2), this takes O(N + X), where X is the number of overlaps
486 // between the bounding boxes of curves.
487
488 struct CurveIntersectionSweepSet
489 {
490 public:
491 struct CurveRecord {
492 boost::intrusive::list_member_hook<> _hook;
493 Curve const *curve;
494 Rect bounds;
495 std::size_t index;
496 unsigned which;
497
498 76 CurveRecord(Curve const *pc, std::size_t idx, unsigned w)
499 152 : curve(pc)
500
1/2
✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
76 , bounds(curve->boundsFast())
501 76 , index(idx)
502 76 , which(w)
503 76 {}
504 };
505
506 typedef std::vector<CurveRecord>::const_iterator ItemIterator;
507
508 7 CurveIntersectionSweepSet(std::vector<PathIntersection> &result,
509 Path const &a, Path const &b, Coord precision)
510 14 : _result(result)
511 7 , _precision(precision)
512
2/8
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
21 , _sweep_dir(X)
513 {
514
2/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 std::size_t asz = a.size(), bsz = b.size();
515
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 _records.reserve(asz + bsz);
516
517
2/2
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 7 times.
44 for (std::size_t i = 0; i < asz; ++i) {
518
2/4
✓ Branch 3 taken 37 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 37 times.
✗ Branch 7 not taken.
37 _records.emplace_back(&a[i], i, 0);
519 }
520
2/2
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 7 times.
46 for (std::size_t i = 0; i < bsz; ++i) {
521
2/4
✓ Branch 3 taken 39 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 39 times.
✗ Branch 7 not taken.
39 _records.emplace_back(&b[i], i, 1);
522 }
523
524
3/6
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 7 times.
✗ Branch 11 not taken.
7 OptRect abb = a.boundsFast() | b.boundsFast();
525
5/6
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 7 taken 5 times.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 5 times.
✓ Branch 10 taken 2 times.
7 if (abb && abb->height() > abb->width()) {
526 5 _sweep_dir = Y;
527 }
528
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
7 }
529
530 35 std::vector<CurveRecord> const &items() { return _records; }
531 76 Interval itemBounds(ItemIterator ii) {
532 76 return ii->bounds[_sweep_dir];
533 }
534
535 76 void addActiveItem(ItemIterator ii) {
536 76 unsigned w = ii->which;
537 76 unsigned ow = (w+1) % 2;
538
539 76 _active[w].push_back(const_cast<CurveRecord&>(*ii));
540
541
4/6
✓ Branch 2 taken 76 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 76 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 106 times.
✓ Branch 9 taken 76 times.
258 for (auto & i : _active[ow]) {
542
3/4
✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 68 times.
✓ Branch 5 taken 38 times.
106 if (!ii->bounds.intersects(i.bounds)) continue;
543
1/2
✓ Branch 3 taken 38 times.
✗ Branch 4 not taken.
38 std::vector<CurveIntersection> cx = ii->curve->intersect(*i.curve, _precision);
544
2/2
✓ Branch 7 taken 38 times.
✓ Branch 8 taken 38 times.
76 for (auto & k : cx) {
545 38 PathTime tw(ii->index, k.first), tow(i.index, k.second);
546
5/6
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 19 times.
✓ Branch 4 taken 19 times.
✓ Branch 6 taken 38 times.
✗ Branch 7 not taken.
76 _result.emplace_back(
547 w == 0 ? tw : tow,
548 w == 0 ? tow : tw,
549 76 k.point());
550 }
551 38 }
552 76 }
553 76 void removeActiveItem(ItemIterator ii) {
554 76 ActiveCurveList &acl = _active[ii->which];
555
2/4
✓ Branch 4 taken 76 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 76 times.
✗ Branch 8 not taken.
76 acl.erase(acl.iterator_to(*ii));
556 76 }
557
558 private:
559 typedef boost::intrusive::list
560 < CurveRecord
561 , boost::intrusive::member_hook
562 < CurveRecord
563 , boost::intrusive::list_member_hook<>
564 , &CurveRecord::_hook
565 >
566 > ActiveCurveList;
567
568 std::vector<CurveRecord> _records;
569 std::vector<PathIntersection> &_result;
570 ActiveCurveList _active[2];
571 Coord _precision;
572 Dim2 _sweep_dir;
573 };
574
575 7 std::vector<PathIntersection> Path::intersect(Path const &other, Coord precision) const
576 {
577 7 std::vector<PathIntersection> result;
578
579
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 CurveIntersectionSweepSet cisset(result, *this, other, precision);
580
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 Sweeper<CurveIntersectionSweepSet> sweeper(cisset);
581
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 sweeper.process();
582
583 // preprocessing to remove duplicate intersections at endpoints
584
2/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 std::size_t asz = size(), bsz = other.size();
585
2/2
✓ Branch 7 taken 38 times.
✓ Branch 8 taken 7 times.
45 for (auto & i : result) {
586 38 i.first.normalizeForward(asz);
587 38 i.second.normalizeForward(bsz);
588 }
589
1/2
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
7 std::sort(result.begin(), result.end());
590
2/4
✓ Branch 9 taken 7 times.
✗ Branch 10 not taken.
✓ Branch 13 taken 7 times.
✗ Branch 14 not taken.
7 result.erase(std::unique(result.begin(), result.end()), result.end());
591
592 14 return result;
593 7 }
594
595 155304 int Path::winding(Point const &p) const {
596 155304 int wind = 0;
597
598 /* To handle all the edge cases, we consider the maximum Y edge of the bounding box
599 * as not included in box. This way paths that contain linear horizontal
600 * segments will be treated correctly. */
601
5/8
✓ Branch 2 taken 155304 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 1063527 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1063527 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 908223 times.
✓ Branch 14 taken 155304 times.
1063527 for (const_iterator i = begin(); i != end_closed(); ++i) {
602
2/4
✓ Branch 2 taken 908223 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 908223 times.
✗ Branch 6 not taken.
908223 Rect bounds = i->boundsFast();
603
604
2/2
✓ Branch 1 taken 195015 times.
✓ Branch 2 taken 713208 times.
908223 if (bounds.height() == 0) continue;
605
7/8
✓ Branch 2 taken 427278 times.
✓ Branch 3 taken 285930 times.
✓ Branch 7 taken 427278 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 232525 times.
✓ Branch 10 taken 194753 times.
✓ Branch 11 taken 518455 times.
✓ Branch 12 taken 194753 times.
713208 if (p[X] > bounds.right() || !bounds[Y].lowerContains(p[Y])) {
606 // Ray doesn't intersect bbox, so we ignore this segment
607 518455 continue;
608 }
609
610
2/2
✓ Branch 2 taken 123555 times.
✓ Branch 3 taken 71198 times.
194753 if (p[X] < bounds.left()) {
611 /* Ray intersects the curve's bbox, but the point is outside it.
612 * The winding contribution is exactly the same as that
613 * of a linear segment with the same initial and final points. */
614
2/4
✓ Branch 2 taken 123555 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 123555 times.
✗ Branch 6 not taken.
123555 Point ip = i->initialPoint();
615
2/4
✓ Branch 2 taken 123555 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 123555 times.
✗ Branch 6 not taken.
123555 Point fp = i->finalPoint();
616
1/2
✓ Branch 2 taken 123555 times.
✗ Branch 3 not taken.
123555 Rect eqbox(ip, fp);
617
618
3/4
✓ Branch 3 taken 123555 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 123554 times.
✓ Branch 6 taken 1 times.
123555 if (eqbox[Y].lowerContains(p[Y])) {
619 /* The ray intersects the equivalent linear segment.
620 * Determine winding contribution based on its derivative. */
621
2/2
✓ Branch 2 taken 108881 times.
✓ Branch 3 taken 14673 times.
123554 if (ip[Y] < fp[Y]) {
622 108881 wind += 1;
623
1/2
✓ Branch 2 taken 14673 times.
✗ Branch 3 not taken.
14673 } else if (ip[Y] > fp[Y]) {
624 14673 wind -= 1;
625 } else {
626 // should never happen, because bounds.height() was not zero
627 assert(false);
628 }
629 }
630 } else {
631 // point is inside bbox
632
2/4
✓ Branch 1 taken 71198 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 71198 times.
✗ Branch 5 not taken.
71198 wind += i->winding(p);
633 }
634 }
635 155304 return wind;
636 }
637
638 std::vector<double> Path::allNearestTimes(Point const &_point, double from, double to) const
639 {
640 // TODO from and to are not used anywhere.
641 // rewrite this to simplify.
642 using std::swap;
643
644 if (from > to)
645 swap(from, to);
646 const Path &_path = *this;
647 unsigned int sz = _path.size();
648 if (_path.closed())
649 ++sz;
650 if (from < 0 || to > sz) {
651 THROW_RANGEERROR("[from,to] interval out of bounds");
652 }
653 double sif, st = modf(from, &sif);
654 double eif, et = modf(to, &eif);
655 unsigned int si = static_cast<unsigned int>(sif);
656 unsigned int ei = static_cast<unsigned int>(eif);
657 if (si == sz) {
658 --si;
659 st = 1;
660 }
661 if (ei == sz) {
662 --ei;
663 et = 1;
664 }
665 if (si == ei) {
666 std::vector<double> all_nearest = _path[si].allNearestTimes(_point, st, et);
667 for (double & i : all_nearest) {
668 i = si + i;
669 }
670 return all_nearest;
671 }
672 std::vector<double> all_t;
673 std::vector<std::vector<double> > all_np;
674 all_np.push_back(_path[si].allNearestTimes(_point, st));
675 std::vector<unsigned int> ni;
676 ni.push_back(si);
677 double dsq;
678 double mindistsq = distanceSq(_point, _path[si].pointAt(all_np.front().front()));
679 Rect bb(Geom::Point(0, 0), Geom::Point(0, 0));
680 for (unsigned int i = si + 1; i < ei; ++i) {
681 bb = (_path[i].boundsFast());
682 dsq = distanceSq(_point, bb);
683 if (mindistsq < dsq)
684 continue;
685 all_t = _path[i].allNearestTimes(_point);
686 dsq = distanceSq(_point, _path[i].pointAt(all_t.front()));
687 if (mindistsq > dsq) {
688 all_np.clear();
689 all_np.push_back(all_t);
690 ni.clear();
691 ni.push_back(i);
692 mindistsq = dsq;
693 } else if (mindistsq == dsq) {
694 all_np.push_back(all_t);
695 ni.push_back(i);
696 }
697 }
698 bb = (_path[ei].boundsFast());
699 dsq = distanceSq(_point, bb);
700 if (mindistsq >= dsq) {
701 all_t = _path[ei].allNearestTimes(_point, 0, et);
702 dsq = distanceSq(_point, _path[ei].pointAt(all_t.front()));
703 if (mindistsq > dsq) {
704 for (double & i : all_t) {
705 i = ei + i;
706 }
707 return all_t;
708 } else if (mindistsq == dsq) {
709 all_np.push_back(all_t);
710 ni.push_back(ei);
711 }
712 }
713 std::vector<double> all_nearest;
714 for (unsigned int i = 0; i < all_np.size(); ++i) {
715 for (unsigned int j = 0; j < all_np[i].size(); ++j) {
716 all_nearest.push_back(ni[i] + all_np[i][j]);
717 }
718 }
719 all_nearest.erase(std::unique(all_nearest.begin(), all_nearest.end()), all_nearest.end());
720 return all_nearest;
721 }
722
723 std::vector<Coord> Path::nearestTimePerCurve(Point const &p) const
724 {
725 // return a single nearest time for each curve in this path
726 std::vector<Coord> np;
727 for (const_iterator it = begin(); it != end_default(); ++it) {
728 np.push_back(it->nearestTime(p));
729 }
730 return np;
731 }
732
733 9 PathTime Path::nearestTime(Point const &p, Coord *dist) const
734 {
735 9 Coord mindist = std::numeric_limits<Coord>::max();
736 9 PathTime ret;
737
738
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
9 if (_data->curves.size() == 1) {
739 // naked moveto
740 ret.curve_index = 0;
741 ret.t = 0;
742 if (dist) {
743 *dist = distance(_closing_seg->initialPoint(), p);
744 }
745 return ret;
746 }
747
748
3/4
✓ Branch 1 taken 35 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 9 times.
35 for (size_type i = 0; i < size_default(); ++i) {
749
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 Curve const &c = at(i);
750
4/6
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 26 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 12 times.
✓ Branch 9 taken 14 times.
26 if (distance(p, c.boundsFast()) >= mindist) continue;
751
752
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 Coord t = c.nearestTime(p);
753
2/4
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 14 times.
✗ Branch 6 not taken.
14 Coord d = distance(c.pointAt(t), p);
754
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2 times.
14 if (d < mindist) {
755 12 mindist = d;
756 12 ret.curve_index = i;
757 12 ret.t = t;
758 }
759 }
760
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (dist) {
761 *dist = mindist;
762 }
763
764 9 return ret;
765 }
766
767 std::vector<Point> Path::nodes() const
768 {
769 std::vector<Point> result;
770 size_type path_size = size_closed();
771 for (size_type i = 0; i < path_size; ++i) {
772 result.push_back(_data->curves[i].initialPoint());
773 }
774 return result;
775 }
776
777 void Path::appendPortionTo(Path &ret, double from, double to) const
778 {
779 if (!(from >= 0 && to >= 0)) {
780 THROW_RANGEERROR("from and to must be >=0 in Path::appendPortionTo");
781 }
782 if (to == 0)
783 to = size() + 0.999999;
784 if (from == to) {
785 return;
786 }
787 double fi, ti;
788 double ff = modf(from, &fi), tf = modf(to, &ti);
789 if (tf == 0) {
790 ti--;
791 tf = 1;
792 }
793 const_iterator fromi = inc(begin(), (unsigned)fi);
794 if (fi == ti && from < to) {
795 ret.append(fromi->portion(ff, tf));
796 return;
797 }
798 const_iterator toi = inc(begin(), (unsigned)ti);
799 if (ff != 1.) {
800 // fromv->setInitial(ret.finalPoint());
801 ret.append(fromi->portion(ff, 1.));
802 }
803 if (from >= to) {
804 const_iterator ender = end();
805 if (ender->initialPoint() == ender->finalPoint())
806 ++ender;
807 ret.insert(ret.end(), ++fromi, ender);
808 ret.insert(ret.end(), begin(), toi);
809 } else {
810 ret.insert(ret.end(), ++fromi, toi);
811 }
812 ret.append(toi->portion(0., tf));
813 }
814
815 82 void Path::appendPortionTo(Path &target, PathInterval const &ival,
816 std::optional<Point> const &p_from, std::optional<Point> const &p_to) const
817 {
818
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 82 times.
82 assert(ival.pathSize() == size_closed());
819
820
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 82 times.
82 if (ival.isDegenerate()) {
821 Point stitch_to = p_from ? *p_from : pointAt(ival.from());
822 target.stitchTo(stitch_to);
823 return;
824 }
825
826 82 PathTime const &from = ival.from(), &to = ival.to();
827
828 82 bool reverse = ival.reverse();
829
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 43 times.
82 int di = reverse ? -1 : 1;
830 82 size_type s = size_closed();
831
832
6/6
✓ Branch 1 taken 66 times.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 29 times.
✓ Branch 4 taken 37 times.
✓ Branch 5 taken 29 times.
✓ Branch 6 taken 53 times.
82 if (!ival.crossesStart() && from.curve_index == to.curve_index) {
833 29 Curve *c = (*this)[from.curve_index].portion(from.t, to.t);
834
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 14 times.
29 if (p_from) {
835 15 c->setInitial(*p_from);
836 }
837
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 14 times.
29 if (p_to) {
838 15 c->setFinal(*p_to);
839 }
840 29 target.append(c);
841 } else {
842
2/2
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 20 times.
53 Curve *c_first = (*this)[from.curve_index].portion(from.t, reverse ? 0 : 1);
843
2/2
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 16 times.
53 if (p_from) {
844 37 c_first->setInitial(*p_from);
845 }
846 53 target.append(c_first);
847
848
2/2
✓ Branch 0 taken 37 times.
✓ Branch 1 taken 53 times.
90 for (size_type i = (from.curve_index + s + di) % s; i != to.curve_index;
849 37 i = (i + s + di) % s)
850 {
851
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 5 times.
37 if (reverse) {
852 32 target.append((*this)[i].reverse());
853 } else {
854 5 target.append((*this)[i].duplicate());
855 }
856 }
857
858
2/2
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 20 times.
53 Curve *c_last = (*this)[to.curve_index].portion(reverse ? 1 : 0, to.t);
859
2/2
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 16 times.
53 if (p_to) {
860 37 c_last->setFinal(*p_to);
861 }
862 53 target.append(c_last);
863 }
864 }
865
866 164750 Path Path::reversed() const
867 {
868 typedef std::reverse_iterator<Sequence::const_iterator> RIter;
869
870
2/4
✓ Branch 2 taken 164750 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 164750 times.
✗ Branch 6 not taken.
164750 Path ret(finalPoint());
871
2/4
✓ Branch 1 taken 164750 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 164750 times.
164750 if (empty()) return ret;
872
873
1/2
✓ Branch 3 taken 164750 times.
✗ Branch 4 not taken.
164750 ret._data->curves.pop_back(); // this also deletes the closing segment from ret
874
875
7/12
✓ Branch 4 taken 164750 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 22 times.
✓ Branch 7 taken 164728 times.
✓ Branch 10 taken 22 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 164728 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 164728 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 164750 times.
✗ Branch 21 not taken.
164750 RIter iter(_includesClosingSegment() ? _data->curves.end() : _data->curves.end() - 1);
876
2/4
✓ Branch 5 taken 164750 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 164750 times.
✗ Branch 9 not taken.
164750 RIter rend(_data->curves.begin());
877
878
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 164724 times.
164750 if (_closed) {
879 // when the path is closed, there are two cases:
880
4/6
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 21 times.
✓ Branch 7 taken 5 times.
26 if (front().isLineSegment()) {
881 // 1. initial segment is linear: it becomes the new closing segment.
882
3/6
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 21 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 21 times.
✗ Branch 12 not taken.
21 rend = RIter(_data->curves.begin() + 1);
883
6/14
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 21 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 21 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 21 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 21 times.
✗ Branch 19 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
21 ret._closing_seg = new ClosingSegment(front().finalPoint(), front().initialPoint());
884 } else {
885 // 2. initial segment is not linear: the closing segment becomes degenerate.
886 // However, skip it if it's already degenerate.
887
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 Point fp = finalPoint();
888
2/6
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
5 ret._closing_seg = new ClosingSegment(fp, fp);
889 }
890 } else {
891 // when the path is open, we reverse all real curves, and add a reversed closing segment.
892
1/2
✓ Branch 1 taken 164724 times.
✗ Branch 2 not taken.
164724 ret._closing_seg = static_cast<ClosingSegment *>(_closing_seg->reverse());
893 }
894
895
4/6
✓ Branch 1 taken 329559 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 494309 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 329559 times.
✓ Branch 7 taken 164750 times.
823868 for (; iter != rend; ++iter) {
896
3/6
✓ Branch 2 taken 329559 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 329559 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 329559 times.
✗ Branch 9 not taken.
329559 ret._data->curves.push_back(iter->reverse());
897 }
898
1/2
✓ Branch 2 taken 164750 times.
✗ Branch 3 not taken.
164750 ret._data->curves.push_back(ret._closing_seg);
899 164750 ret._closed = _closed;
900 164750 return ret;
901 }
902
903
904 void Path::insert(iterator pos, Curve const &curve)
905 {
906 _unshare();
907 Sequence::iterator seq_pos(seq_iter(pos));
908 Sequence source;
909 source.push_back(curve.duplicate());
910 do_update(seq_pos, seq_pos, source);
911 }
912
913 1 void Path::erase(iterator pos)
914 {
915
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 _unshare();
916
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 Sequence::iterator seq_pos(seq_iter(pos));
917
918
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 Sequence stitched;
919
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 do_update(seq_pos, seq_pos + 1, stitched);
920 2 }
921
922 void Path::erase(iterator first, iterator last)
923 {
924 _unshare();
925 Sequence::iterator seq_first = seq_iter(first);
926 Sequence::iterator seq_last = seq_iter(last);
927
928 Sequence stitched;
929 do_update(seq_first, seq_last, stitched);
930 }
931
932 299 void Path::stitchTo(Point const &p)
933 {
934
10/14
✓ Branch 1 taken 299 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 181 times.
✓ Branch 4 taken 118 times.
✓ Branch 7 taken 181 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 179 times.
✓ Branch 12 taken 181 times.
✓ Branch 13 taken 118 times.
✓ Branch 15 taken 2 times.
✓ Branch 16 taken 297 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
299 if (!empty() && _closing_seg->initialPoint() != p) {
935
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (_exception_on_stitch) {
936 THROW_CONTINUITYERROR();
937 }
938 2 _unshare();
939
3/8
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
2 do_append(new StitchSegment(_closing_seg->initialPoint(), p));
940 }
941 299 }
942
943 void Path::replace(iterator replaced, Curve const &curve)
944 {
945 replace(replaced, replaced + 1, curve);
946 }
947
948 void Path::replace(iterator first_replaced, iterator last_replaced, Curve const &curve)
949 {
950 _unshare();
951 Sequence::iterator seq_first_replaced(seq_iter(first_replaced));
952 Sequence::iterator seq_last_replaced(seq_iter(last_replaced));
953 Sequence source(1);
954 source.push_back(curve.duplicate());
955
956 do_update(seq_first_replaced, seq_last_replaced, source);
957 }
958
959 void Path::replace(iterator replaced, Path const &path)
960 {
961 replace(replaced, path.begin(), path.end());
962 }
963
964 10 void Path::replace(iterator first, iterator last, Path const &path)
965 {
966 10 replace(first, last, path.begin(), path.end());
967 10 }
968
969 void Path::snapEnds(Coord precision)
970 {
971 if (!_closed) return;
972 if (_data->curves.size() > 1 && are_near(_closing_seg->length(precision), 0, precision)) {
973 _unshare();
974 _closing_seg->setInitial(_closing_seg->finalPoint());
975 (_data->curves.end() - 1)->setFinal(_closing_seg->finalPoint());
976 }
977 }
978
979 9 Path Path::withoutDegenerateCurves() const
980 {
981
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 Sequence cleaned;
982
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
9 cleaned.reserve(size());
983
984
5/8
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 31 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 31 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 22 times.
✓ Branch 14 taken 9 times.
31 for (auto it = begin(); it != end_open(); ++it) {
985
4/6
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 11 times.
22 if (!it->isDegenerate()) {
986
3/6
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 11 times.
✗ Branch 8 not taken.
11 cleaned.push_back(it->duplicate());
987 }
988 }
989
990
1/2
✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
9 Path result;
991 9 result._closed = _closed;
992
3/6
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 9 times.
✗ Branch 10 not taken.
9 result.do_update(result._data->curves.begin(), result._data->curves.end(), cleaned);
993 18 return result;
994 9 }
995
996 // Replace curves between first and last with the contents of source.
997 22 void Path::do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source)
998 {
999 // TODO: handle cases where first > last in closed paths?
1000
2/4
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 22 times.
✗ Branch 7 not taken.
22 bool last_beyond_closing_segment = (last == _data->curves.end());
1001
1002 // special case:
1003 // if do_update replaces the closing segment, we have to regenerate it
1004
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 19 times.
22 if (source.empty()) {
1005
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (first == last) return; // nothing to do
1006
1007 // only removing some segments
1008
18/30
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 1 times.
✓ Branch 17 taken 1 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 1 times.
✗ Branch 21 not taken.
✓ Branch 23 taken 1 times.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✓ Branch 26 taken 1 times.
✓ Branch 27 taken 1 times.
✗ Branch 28 not taken.
✓ Branch 29 taken 1 times.
✓ Branch 30 taken 2 times.
✓ Branch 32 taken 2 times.
✓ Branch 33 taken 1 times.
✗ Branch 35 not taken.
✓ Branch 36 taken 3 times.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
3 if ((!_closed && first == _data->curves.begin()) || (!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) {
1009 // just adjust the closing segment
1010 // do nothing
1011 } else if (first->initialPoint() != (last - 1)->finalPoint()) {
1012 if (_exception_on_stitch) {
1013 THROW_CONTINUITYERROR();
1014 }
1015 source.push_back(new StitchSegment(first->initialPoint(), (last - 1)->finalPoint()));
1016 }
1017 } else {
1018 // replacing
1019
13/22
✓ Branch 3 taken 19 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 19 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 11 times.
✓ Branch 9 taken 8 times.
✓ Branch 13 taken 11 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 11 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 8 times.
✓ Branch 19 taken 3 times.
✓ Branch 20 taken 11 times.
✓ Branch 21 taken 8 times.
✓ Branch 23 taken 19 times.
✗ Branch 24 not taken.
✓ Branch 26 taken 8 times.
✓ Branch 27 taken 11 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
19 if (first == _data->curves.begin() && last == _data->curves.end()) {
1020 // special case: replacing everything should work the same in open and closed curves
1021 8 _data->curves.erase(_data->curves.begin(), _data->curves.end() - 1);
1022
3/6
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
8 _closing_seg->setFinal(source.front().initialPoint());
1023
3/6
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
8 _closing_seg->setInitial(source.back().finalPoint());
1024 8 _data->curves.transfer(_data->curves.begin(), source.begin(), source.end(), source);
1025 8 return;
1026 }
1027
1028 // stitch in front
1029
10/14
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 4 times.
✓ Branch 12 taken 6 times.
✓ Branch 13 taken 5 times.
✓ Branch 15 taken 9 times.
✓ Branch 16 taken 2 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
11 if (!_closed && first == _data->curves.begin()) {
1030 // not necessary to stitch in front
1031
4/8
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
✓ Branch 10 taken 9 times.
✗ Branch 11 not taken.
✓ Branch 15 taken 9 times.
✗ Branch 16 not taken.
9 } else if (first->initialPoint() != source.front().initialPoint()) {
1032
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (_exception_on_stitch) {
1033 THROW_CONTINUITYERROR();
1034 }
1035
6/14
✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✓ Branch 11 taken 9 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 9 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 9 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 9 times.
✗ Branch 21 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
9 source.insert(source.begin(), new StitchSegment(first->initialPoint(), source.front().initialPoint()));
1036 }
1037
1038 // stitch at the end
1039
13/18
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 6 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 4 times.
✓ Branch 14 taken 2 times.
✓ Branch 15 taken 4 times.
✓ Branch 16 taken 5 times.
✓ Branch 17 taken 6 times.
✓ Branch 18 taken 5 times.
✓ Branch 20 taken 5 times.
✓ Branch 21 taken 6 times.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
11 if ((!_closed && last == _data->curves.end() - 1) || last_beyond_closing_segment) {
1040 // repurpose the closing segment as the stitch segment
1041 // do nothing
1042
5/10
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 5 times.
✗ Branch 8 not taken.
✓ Branch 11 taken 5 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 5 times.
✗ Branch 15 not taken.
✓ Branch 20 taken 5 times.
✗ Branch 21 not taken.
5 } else if (source.back().finalPoint() != (last - 1)->finalPoint()) {
1043
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (_exception_on_stitch) {
1044 THROW_CONTINUITYERROR();
1045 }
1046
6/14
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 5 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 5 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 5 times.
✗ Branch 19 not taken.
✓ Branch 21 taken 5 times.
✗ Branch 22 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
5 source.push_back(new StitchSegment(source.back().finalPoint(), (last - 1)->finalPoint()));
1047 }
1048 }
1049
1050 // do not erase the closing segment, adjust it instead
1051
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 8 times.
14 if (last_beyond_closing_segment) {
1052 6 --last;
1053 }
1054 14 _data->curves.erase(first, last);
1055 14 _data->curves.transfer(first, source.begin(), source.end(), source);
1056
1057 // adjust closing segment
1058
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 12 times.
14 if (size_open() == 0) {
1059
2/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
2 _closing_seg->setFinal(_closing_seg->initialPoint());
1060 } else {
1061
3/6
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
12 _closing_seg->setInitial(back_open().finalPoint());
1062
3/6
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
12 _closing_seg->setFinal(front().initialPoint());
1063 }
1064
1065 14 checkContinuity();
1066 }
1067
1068 397309 void Path::do_append(Curve *c)
1069 {
1070
2/2
✓ Branch 2 taken 13494 times.
✓ Branch 3 taken 383815 times.
397309 if (&_data->curves.front() == _closing_seg) {
1071
2/4
✓ Branch 2 taken 13494 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13494 times.
✗ Branch 6 not taken.
13494 _closing_seg->setFinal(c->initialPoint());
1072 } else {
1073 // if we can't freely move the closing segment, we check whether
1074 // the new curve connects with the last non-closing curve
1075
3/6
✓ Branch 2 taken 383815 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 383815 times.
✗ Branch 7 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 383815 times.
383815 if (c->initialPoint() != _closing_seg->initialPoint()) {
1076 THROW_CONTINUITYERROR();
1077 }
1078
6/10
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 383813 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 383815 times.
767636 if (_closed && c->isLineSegment() &&
1079
6/12
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 383813 times.
✓ Branch 10 taken 383813 times.
✓ Branch 11 taken 2 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
383821 c->finalPoint() == _closing_seg->finalPoint())
1080 {
1081 // appending a curve that matches the closing segment has no effect
1082 delete c;
1083 return;
1084 }
1085 }
1086 397309 _data->curves.insert(_data->curves.end() - 1, c);
1087
2/4
✓ Branch 2 taken 397309 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 397309 times.
✗ Branch 6 not taken.
397309 _closing_seg->setInitial(c->finalPoint());
1088 }
1089
1090 39 void Path::checkContinuity() const
1091 {
1092
4/8
✓ Branch 4 taken 39 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 39 times.
✗ Branch 8 not taken.
✓ Branch 14 taken 39 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 39 times.
✗ Branch 18 not taken.
39 Sequence::const_iterator i = _data->curves.begin(), j = _data->curves.begin();
1093 39 ++j;
1094
4/6
✓ Branch 5 taken 176 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 176 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 137 times.
✓ Branch 12 taken 39 times.
176 for (; j != _data->curves.end(); ++i, ++j) {
1095
3/6
✓ Branch 3 taken 137 times.
✗ Branch 4 not taken.
✓ Branch 8 taken 137 times.
✗ Branch 9 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 137 times.
137 if (i->finalPoint() != j->initialPoint()) {
1096 THROW_CONTINUITYERROR();
1097 }
1098 }
1099
5/10
✓ Branch 3 taken 39 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 39 times.
✗ Branch 7 not taken.
✓ Branch 11 taken 39 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 39 times.
✗ Branch 15 not taken.
✗ Branch 19 not taken.
✓ Branch 20 taken 39 times.
39 if (_data->curves.front().initialPoint() != _data->curves.back().finalPoint()) {
1100 THROW_CONTINUITYERROR();
1101 }
1102 39 }
1103
1104 // breaks time value into integral and fractional part
1105 169 PathTime Path::_factorTime(Coord t) const
1106 {
1107
1/2
✓ Branch 1 taken 169 times.
✗ Branch 2 not taken.
169 size_type sz = size_default();
1108
2/4
✓ Branch 0 taken 169 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 169 times.
169 if (t < 0 || t > sz) {
1109 THROW_RANGEERROR("parameter t out of bounds");
1110 }
1111
1112 169 PathTime ret;
1113 169 Coord k;
1114 169 ret.t = modf(t, &k);
1115 169 ret.curve_index = k;
1116
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 110 times.
169 if (ret.curve_index == sz) {
1117 59 --ret.curve_index;
1118 59 ret.t = 1;
1119 }
1120 338 return ret;
1121 }
1122
1123 Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths)
1124 {
1125 Piecewise<D2<SBasis> > ret = paths[0].toPwSb();
1126 for (unsigned i = 1; i < paths.size(); i++) {
1127 ret.concat(paths[i].toPwSb());
1128 }
1129 return ret;
1130 }
1131
1132 4 bool are_near(Path const &a, Path const &b, Coord precision)
1133 {
1134
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3 times.
4 if (a.size() != b.size()) return false;
1135
1136
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 2 times.
14 for (unsigned i = 0; i < a.size(); ++i) {
1137
2/2
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 11 times.
12 if (!a[i].isNear(b[i], precision)) return false;
1138 }
1139 2 return true;
1140 }
1141
1142 std::ostream &operator<<(std::ostream &out, Path const &path)
1143 {
1144 SVGPathWriter pw;
1145 pw.feed(path);
1146 out << pw.str();
1147 return out;
1148 }
1149
1150 } // end namespace Geom
1151
1152 /*
1153 Local Variables:
1154 mode:c++
1155 c-file-style:"stroustrup"
1156 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1157 indent-tabs-mode:nil
1158 fill-column:99
1159 End:
1160 */
1161 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1162