1 /* CPML - Cairo Path Manipulation Library
2 * Copyright (C) 2008, Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 #include "cpml-segment.h"
22 #include "cpml-pair.h"
23 #include "cpml-macros.h"
24 #include "cpml-alloca.h"
29 static cairo_bool_t
segment_normalize (CpmlSegment
*segment
);
30 static void line_offset (CpmlPair
*p
,
33 static void curve_offset (CpmlPair
*p
,
36 static void join_primitives (cairo_path_data_t
*last_data
,
37 const CpmlVector
*last_vector
,
38 const CpmlPair
*new_point
,
39 const CpmlVector
*new_vector
);
44 * @segment: a #CpmlSegment
45 * @src: the source cairo_path_t
47 * Builds a CpmlSegment from a cairo_path_t structure. This operation
48 * involves stripping the leading %MOVE_TO primitives and setting the
49 * internal segment structure accordling. A pointer to the source
50 * path segment is kept.
52 * Return value: 1 on success, 0 on errors
55 cpml_segment_init(CpmlSegment
*segment
, cairo_path_t
*src
)
57 /* The cairo path should be defined and in perfect state */
58 if (src
== NULL
|| src
->num_data
== 0 ||
59 src
->status
!= CAIRO_STATUS_SUCCESS
)
62 segment
->original
= src
;
63 memcpy(&segment
->path
, src
, sizeof(cairo_path_t
));
65 return segment_normalize(segment
);
70 * @segment: a #CpmlSegment
71 * @src: the source segment to copy
73 * Makes a shallow copy of @src into @segment.
75 * Return value: @segment or %NULL on errors
78 cpml_segment_copy(CpmlSegment
*segment
, const CpmlSegment
*src
)
80 if (segment
== NULL
|| src
== NULL
)
83 return memcpy(segment
, src
, sizeof(CpmlSegment
));
88 * @segment: a #CpmlSegment
90 * Dumps the specified @segment to stdout. Useful for debugging purposes.
93 cpml_segment_dump(const CpmlSegment
*segment
)
95 const cairo_path_t
*path
;
96 cairo_path_data_t
*data
;
99 if (segment
== NULL
) {
100 printf("Trying to dump a NULL segment!\n");
104 path
= &segment
->path
;
105 for (n_data
= 0; n_data
< path
->num_data
; ++n_data
) {
106 data
= path
->data
+ n_data
;
108 switch (data
->header
.type
) {
109 case CAIRO_PATH_MOVE_TO
:
112 case CAIRO_PATH_LINE_TO
:
115 case CAIRO_PATH_CURVE_TO
:
118 case CAIRO_PATH_CLOSE_PATH
:
119 printf("Path close");
122 printf("Unknown entity (%d)", data
->header
.type
);
126 for (n_point
= 1; n_point
< data
->header
.length
; ++n_point
)
127 printf("(%lf, %lf) ", data
[n_point
].point
.x
,
128 data
[n_point
].point
.y
);
130 n_data
+= n_point
- 1;
136 * cpml_segment_reset:
137 * @segment: a #CpmlSegment
139 * Modifies @segment to point to the first segment of the original path.
142 cpml_segment_reset(CpmlSegment
*segment
)
144 memcpy(&segment
->path
, segment
->original
, sizeof(cairo_path_t
));
145 segment_normalize(segment
);
150 * @segment: a #CpmlSegment
152 * Modifies @segment to point to the next segment of the original path.
154 * Return value: 1 on success, 0 if no next segment found or errors
157 cpml_segment_next(CpmlSegment
*segment
)
159 int num_data
= segment
->path
.num_data
;
160 int offset
= segment
->path
.data
- segment
->original
->data
;
162 segment
->path
.num_data
= segment
->original
->num_data
- num_data
- offset
;
163 segment
->path
.data
+= num_data
;
165 return segment_normalize(segment
);
169 * cpml_segment_reverse:
170 * @segment: a #CpmlSegment
172 * Reverses @segment in-place. The resulting rendering will be the same,
173 * but with the primitives generated in reverse order.
176 cpml_segment_reverse(CpmlSegment
*segment
)
178 cairo_path_data_t
*data
, *dst_data
;
181 int num_data
, n_data
;
182 int num_points
, n_point
;
183 const cairo_path_data_t
*src_data
;
185 num_data
= segment
->path
.num_data
;
186 data_size
= sizeof(cairo_path_data_t
) * num_data
;
187 data
= cpml_alloca(data_size
);
188 end_x
= segment
->path
.data
[1].point
.x
;
189 end_y
= segment
->path
.data
[1].point
.y
;
191 for (n_data
= 2; n_data
< num_data
; ++n_data
) {
192 src_data
= segment
->path
.data
+ n_data
;
193 num_points
= src_data
->header
.length
;
195 dst_data
= data
+ num_data
- n_data
- num_points
+ 2;
196 dst_data
->header
.type
= src_data
->header
.type
;
197 dst_data
->header
.length
= num_points
;
199 for (n_point
= 1; n_point
< num_points
; ++n_point
) {
200 dst_data
[num_points
- n_point
].point
.x
= end_x
;
201 dst_data
[num_points
- n_point
].point
.y
= end_y
;
202 end_x
= src_data
[n_point
].point
.x
;
203 end_y
= src_data
[n_point
].point
.y
;
206 n_data
+= n_point
- 1;
209 data
[0].header
.type
= CAIRO_PATH_MOVE_TO
;
210 data
[0].header
.length
= 2;
211 data
[1].point
.x
= end_x
;
212 data
[1].point
.y
= end_y
;
213 memcpy(segment
->path
.data
, data
, data_size
);
217 * cpml_segment_transform:
218 * @segment: a #CpmlSegment
219 * @matrix: the matrix to be applied
221 * Applies @matrix on all the points of @segment.
224 cpml_segment_transform(CpmlSegment
*segment
, const cairo_matrix_t
*matrix
)
226 cairo_path_data_t
*data
;
227 int n_data
, num_data
;
228 int n_point
, num_points
;
230 data
= segment
->path
.data
;
231 num_data
= segment
->path
.num_data
;
233 for (n_data
= 0; n_data
< num_data
; n_data
+= num_points
) {
234 num_points
= data
->header
.length
;
236 for (n_point
= 1; n_point
< num_points
; ++n_point
) {
237 cairo_matrix_transform_point(matrix
, &data
->point
.x
, &data
->point
.y
);
244 * cpml_segment_offset:
245 * @segment: a #CpmlSegment
246 * @offset: the offset distance
248 * Offsets a segment of the specified amount, that is builds a "parallel"
249 * segment at the @offset distance from the original one and returns the
250 * result by replacing the original @segment.
252 * Return value: 1 on success, 0 on errors
254 * @todo: closed path are not yet managed; the solution is not so obvious.
257 cpml_segment_offset(CpmlSegment
*segment
, double offset
)
259 int num_data
, n_data
;
260 int num_points
, n_point
;
261 cairo_path_data_t
*data
;
262 cairo_path_data_t
*last_data
;
263 CpmlVector v_old
, v_new
;
267 num_data
= segment
->path
.num_data
;
272 for (n_data
= 0; n_data
< num_data
; n_data
+= data
->header
.length
) {
273 data
= segment
->path
.data
+ n_data
;
274 num_points
= data
->header
.length
- 1;
276 /* Fill the p[] vector */
277 cpml_pair_copy(&p
[0], &p_old
);
278 for (n_point
= 1; n_point
<= num_points
; ++ n_point
) {
279 p
[n_point
].x
= data
[n_point
].point
.x
;
280 p
[n_point
].y
= data
[n_point
].point
.y
;
283 /* Save the last direction vector in v_old */
284 cpml_pair_copy(&v_old
, &v_new
);
286 switch (data
->header
.type
) {
288 case CAIRO_PATH_MOVE_TO
:
293 case CAIRO_PATH_LINE_TO
:
294 line_offset(p
, &v_new
, offset
);
297 case CAIRO_PATH_CURVE_TO
:
298 curve_offset(p
, &v_new
, offset
);
301 case CAIRO_PATH_CLOSE_PATH
:
305 join_primitives(last_data
, &v_old
, &p
[0], &v_new
);
307 /* Save the end point of the original primitive in p_old */
308 last_data
= data
+ num_points
;
309 p_old
.x
= last_data
->point
.x
;
310 p_old
.y
= last_data
->point
.y
;
312 /* Store the results got from the p[] vector in the cairo path */
313 for (n_point
= 1; n_point
<= num_points
; ++ n_point
) {
314 data
[n_point
].point
.x
= p
[n_point
].x
;
315 data
[n_point
].point
.y
= p
[n_point
].y
;
324 * @segment: a #CpmlSegment
326 * Strips the leading CAIRO_PATH_MOVE_TO primitives, updating the CpmlSegment
327 * structure accordling. One, and only once, MOVE_TO primitive is left.
329 * Return value: 1 on success, 0 on no leading MOVE_TOs or on errors
332 segment_normalize(CpmlSegment
*segment
)
334 cairo_path_data_t
*data
;
336 if (segment
== NULL
|| segment
->path
.num_data
<= 0)
339 data
= segment
->path
.data
;
340 if (data
->header
.type
!= CAIRO_PATH_MOVE_TO
) {
341 segment
->path
.status
= CAIRO_STATUS_INVALID_PATH_DATA
;
345 while (segment
->path
.num_data
>= 0) {
347 if (data
->header
.type
!= CAIRO_PATH_MOVE_TO
)
350 segment
->path
.data
= data
;
351 segment
->path
.num_data
-= 2;
359 * @p: an array of two #CpmlPair structs
360 * @vector: the ending direction vector of the resulting primitive
361 * @offset: distance for the computed parallel line
363 * Given a line segment starting from @p[0] and ending in @p[1],
364 * computes the parallel line distant @offset from the original one
365 * and returns the result in the @p array.
367 * The direction vector of the new line segment is saved in @vector
368 * with a somewhat arbitrary magnitude.
371 line_offset(CpmlPair
*p
, CpmlVector
*vector
, double offset
)
375 cpml_pair_sub(cpml_pair_copy(vector
, &p
[1]), &p
[0]);
377 cpml_vector_from_pair(&normal
, vector
, offset
);
378 cpml_vector_normal(&normal
);
380 cpml_pair_add(&p
[0], &normal
);
381 cpml_pair_add(&p
[1], &normal
);
386 * @p: (inout): an array of four #CpmlPair structs
387 * @vector: (out): the ending direction vector of the resulting primitive
388 * @offset: (in): distance for the computed parallel line
390 * Given a cubic Bézier segment starting from @p[0] and ending
391 * in p[3], with control points in @p[1] and @p[2], this function
392 * finds the approximated Bézier curve parallel to the given one
393 * at distance @offset (an offset curve).
395 * The four points needed to build the new curve are returned
396 * in the @p vector. The angular coefficient of the new curve
397 * in @p[3] is returned as @vector with @offset magnitude.
399 * To solve the offset problem, a custom algorithm is used. First, the
400 * resulting curve MUST have the same slope at the start and end point.
401 * These constraints are not sufficient to resolve the system, so I let
402 * the curve pass thought a given point (pm, known and got from the
403 * original curve) at a given time (m, now hardcoded to 0.5).
405 * Firstly, I define some useful variables:
407 * v0 = unitvector(p[1]-p[0]) * offset;
408 * v3 = unitvector(p[3]-p[2]) * offset;
409 * p0 = p[0] + normal v0;
410 * p3 = p[3] + normal v3.
412 * Now I want the curve to have the specified slopes at the start
413 * and end point. Forcing the same slope at the start point means:
417 * where k1 is an arbitrary factor. Decomposing for x and y components and
420 * p1.x - p0.x = k1 v0.x;
421 * p1.y - p0.y = k1 v0.y.
423 * (p1.x - p0.x) v0.y = (p1.y - p0.y) v0.x.
425 * Doing the same for the end point gives:
428 * (p3.x - p2.x) v3.y = (p3.y - p2.y) v3.x.
430 * Now I interpolate the curve by forcing it to pass throught pm
431 * when "time" is m, where 0 < m < 1. The cubic Bézier function is:
433 * C(t) = (1-t)³p0 + 3t(1-t)²p1 + 3t²(1-t)p2 + t³p3.
435 * and forcing t=m and C(t) = pm:
437 * pm = (1-m)³p0 + 3m(1-m)²p1 + 3m²(1-m)p2 + m³p3.
438 * (1-m) p1 + m p2 = (pm - (1-m)³p0 - m³p3) / (3m (1-m)).
440 * Either p0 and p3 of the curve are known, so I can get rid of
443 * pk = (pm - (1-m)³p0 - m³p3) / (3m (1-m));
444 * (1-m) p1 + m p2 = pk.
446 * So the final system is:
448 * (1-m) p1.x + m p2.x = pk->x;
449 * (1-m) p1.y + m p2.y = pk->y;
450 * (p1.x - p0.x) v0.y = (p1.y - p0.y) v0.x.
451 * (p3.x - p2.x) v3.y = (p3.y - p2.y) v3.x.
453 * Given the above system, I get the following pool of equations:
455 * p1.x = (pk.x - m p2.x)/(1-m);
456 * p1.y = (pk.y - m p2.y)/(1-m).
458 * p2.x = (pk.x - (1-m) p1.x)/m;
459 * p2.y = (pk.y - (1-m) p1.y)/m.
461 * p1.x = p0.x + (p1.y - p0.y) v0.x/v0.y;
462 * p1.y = p0.y + (p1.x - p0.x) v0.y/v0.x.
464 * p2.x = p3.x - (p3.y - p2.y) v3.x/v3.y;
465 * p2.y = p3.y - (p3.x - p2.x) v3.y/v3.x.
467 * The algorithm takes from this pool what is needed in any specific case.
469 * Now the hard part: finding the first solution. Solving this system
470 * is a nightmare because of divisions by 0 exceptions.
472 * After a lot of tedious calculations, it came out when the
473 * v3.x*v0.y == v0.x*v3.y condition is met, that is when v0 and v1
474 * are parallel and in the same direction, this algorithm is not valid:
475 * this condition should be checked before anything else.
477 * If v3.x*v0.y == v0.x*v3.y, an alternative approach is provided:
478 * the control points are found by offseting the control poligon
479 * (as done by Tiller and Hanson) but using a 4/3 factor on the p1-p2
480 * line to compensate the distance between the curve and the p1-p2 line
481 * (the top of the curve is at exactly 3/4 of the poligon height
482 * when v1 and v2 are parallel and in the same direction).
484 * So if v3.x*v0.y == v0.x*v3.y and if m is 0.5 (the mid point of the
485 * curve) so that vm is the vector of @offset magnitude at m, I have:
487 * p1 = p0 + (p[1]-p[0]) + 4/3 vm;
488 * p2 = p3 + (p[2]-p[3]) - 4/3 vm.
490 * In the other cases, I get the first coordinate by looking for a
491 * 0 vector component. If no vector component is 0 any division is allowed
492 * so, taking some equation from the pool, the following system is used:
494 * p1.x = (pk.x - m p2.x)/(1-m);
495 * p1.x = p0.x + (p1.y-p0.y) v0.x/v0.y;
496 * p2.x = p3.x - (p3.y - p2.y) v3.x/v3.y;
497 * p2.y = (pk.y - (1-m) p1.y)/m.
499 * And now I resolve to get the p1.y value:
501 * (pk.x - m p2.x)/(1-m) = p0.x + (p1.y-p0.y) v0.x/v0.y;
502 * p2.x = p3.x + ((pk.y - (1-m) p1.y)/m - p3.y) v3.x/v3.y.
504 * p2.x = pk.x/m - (1-m)/m (p0.x - v0.x/v0.y p0.y)
505 * - p1.y (1-m)/m v0.x/v0.y;
506 * p2.x = p3.x + (pk.y/m - p3.y) v3.x/v3.y
507 * - p1.y (1-m)/m v3.x/v3.y.
509 * pk.x/m - (1-m)/m (p0.x - v0.x/v0.y p0.y)
510 * - p1.y (1-m)/m v0.x/v0.y =
511 * p3.x + (pk.y/m - p3.y) v3.x/v3.y
512 * - p1.y (1-m)/m v3.x/v3.y.
514 * pk.x/m - (1-m)/m (p0.x - p0.y v0.x/v0.y)
515 * - p1.y (1-m)/m v0.x/v0.y =
516 * p3.x + (pk.y/m - p3.y) v3.x/v3.y
517 * - p1.y (1-m)/m v3.x/v3.y.
519 * p1.y (v3.x/v3.y - v0.x/v0.y) = (p0.x - p0.y v0.x/v0.y)
520 * + (m (p3.x + (pk.y/m - p3.y) v3.x/v3.y) - pk.x) / (1-m).
522 * And this is the final equation:
524 * p1.y = ((p0.x - p0.y v0.x/v0.y)
525 * + (m (p3.x + (pk.y/m - p3.y) v3.x/v3.y) - pk.x) / (1-m))
526 * / (v3.x/v3.y - v0.x/v0.y).
529 curve_offset(CpmlPair
*p
, CpmlVector
*vector
, double offset
)
532 CpmlVector v0
, v3
, vm
;
533 CpmlPair p0
, p1
, p2
, p3
, pm
, pk
;
535 /* v0 = vector p[1]-p[0] of @offset magnitude */
536 cpml_pair_sub(cpml_pair_copy(&v0
, &p
[1]), &p
[0]);
537 cpml_vector_from_pair(&v0
, &v0
, offset
);
539 /* v3 = vector p[3]-p[2] of @offset magnitude */
540 cpml_pair_sub(cpml_pair_copy(&v3
, &p
[3]), &p
[2]);
541 cpml_vector_from_pair(&v3
, &v3
, offset
);
543 /* p0 = p[0] + normal of v0 (exact final value of p0) */
544 cpml_vector_normal(cpml_pair_copy(&p0
, &v0
));
545 cpml_pair_add(&p0
, &p
[0]);
547 /* p3 = p[3] + normal of v3 (exact final value of p3) */
548 cpml_vector_normal(cpml_pair_copy(&p3
, &v3
));
549 cpml_pair_add(&p3
, &p
[3]);
551 /* By default, interpolate the new curve by offseting the mid point.
552 * @todo: use a better candidate. */
556 /* pm = point in C(m) offseted the requested @offset distance */
557 cpml_vector_at_curve(&vm
, &p
[0], &p
[1], &p
[2], &p
[3], m
, offset
);
558 cpml_vector_normal(&vm
);
559 cpml_pair_at_curve(&pm
, &p
[0], &p
[1], &p
[2], &p
[3], m
);
560 cpml_pair_add(&pm
, &vm
);
562 /* pk = (pm - (1-m)³p0 - m³p3) / (3m (1-m)) */
563 pk
.x
= (pm
.x
- mm
*mm
*mm
*p0
.x
- m
*m
*m
*p3
.x
) / (m
*3*mm
);
564 pk
.y
= (pm
.y
- mm
*mm
*mm
*p0
.y
- m
*m
*m
*p3
.y
) / (m
*3*mm
);
566 if (v3
.x
*v0
.y
== v0
.x
*v3
.y
) {
567 /* Same slope at the start and end point (parallel p0-p1 and p3-p2) */
568 p1
.x
= p0
.x
+ (p
[1].x
- p
[0].x
) + vm
.x
* 4/3;
569 p1
.y
= p0
.y
+ (p
[1].y
- p
[0].y
) + vm
.y
* 4/3;
570 p2
.x
= p3
.x
+ (p
[2].x
- p
[3].x
) + vm
.x
* 4/3;
571 p2
.y
= p3
.y
+ (p
[2].y
- p
[3].y
) + vm
.y
* 4/3;
573 /* Probably the following can be condensed */
574 } else if (v0
.x
== 0) {
576 p2
.x
= (pk
.x
- mm
*p1
.x
)/m
;
578 p2
.y
= p3
.y
+ (pm
.y
- p3
.y
) * 4/3;
582 p2
.y
= p3
.y
- (p3
.x
- p2
.x
) * v3
.y
/v3
.x
;
583 p1
.y
= (pk
.y
- m
*p2
.y
)/mm
;
584 } else if (v0
.y
== 0) {
586 p2
.y
= (pk
.y
- mm
*p1
.y
)/m
;
588 p2
.x
= p3
.x
+ (pm
.x
- p3
.x
) * 4/3;
592 p2
.x
= p3
.x
- (p3
.y
- p2
.y
) * v3
.x
/v3
.y
;
593 p1
.x
= (pk
.x
- m
*p2
.x
)/mm
;
594 } else if (v3
.x
== 0) {
596 p1
.x
= (pk
.x
- m
*p2
.x
)/mm
;
598 if (v0
.y
!= 0 && v0
.x
!= 0)
599 p1
.y
+= (p1
.x
- p0
.x
) * v0
.y
/v0
.x
;
600 p2
.y
= (pk
.y
- mm
*p1
.y
)/m
;
601 } else if (v3
.y
== 0) {
603 p1
.y
= (pk
.y
- m
*p2
.y
)/mm
;
605 if (v0
.x
!= 0 && v0
.y
!= 0)
606 p1
.x
+= (p1
.y
- p0
.y
) * v0
.x
/v0
.y
;
607 p2
.x
= (pk
.x
- mm
*p1
.x
)/m
;
608 } else if (v3
.x
*v0
.y
== v0
.x
*v3
.y
) {
609 p1
.x
= p1
.x
+ (pm
.x
- p1
.x
) * 4/3;
610 p1
.y
= p1
.y
+ (pm
.y
- p1
.y
) * 4/3;
611 p2
.x
= p3
.x
+ (pm
.x
- p3
.x
) * 4/3;
612 p2
.y
= p3
.y
+ (pm
.y
- p3
.y
) * 4/3;
614 p1
.y
= m
/mm
*v0
.y
*(v3
.y
*(p3
.x
-pk
.x
/m
) + (pk
.y
/m
-p3
.y
)*v3
.x
)
615 / (v3
.x
*v0
.y
- v0
.x
*v3
.y
);
616 p1
.x
= p0
.x
+ (p1
.y
- p0
.y
) * v0
.x
/v0
.y
;
617 p2
.y
= (pk
.y
- mm
*p1
.y
)/m
;
618 p2
.x
= p3
.x
- (p3
.y
- p2
.y
) * v3
.x
/v3
.y
;
621 /* Return the new curve in the original array */
622 cpml_pair_copy(&p
[0], &p0
);
623 cpml_pair_copy(&p
[1], &p1
);
624 cpml_pair_copy(&p
[2], &p2
);
625 cpml_pair_copy(&p
[3], &p3
);
627 /* Return the ending vector */
628 cpml_pair_copy(vector
, &v3
);
633 * @last_data: the previous primitive end data point (if any)
634 * @last_vector: @last_data direction vector
635 * @new_point: the new primitive starting point
636 * @new_vector: @new_point direction vector
638 * Joins two primitive modifying the end point of the first one (stored
639 * as cairo path data in @last_data).
641 * @todo: this approach is quite naive when curves are involved.
644 join_primitives(cairo_path_data_t
*last_data
, const CpmlVector
*last_vector
,
645 const CpmlPair
*new_point
, const CpmlVector
*new_vector
)
649 if (last_data
== NULL
)
652 last_point
.x
= last_data
->point
.x
;
653 last_point
.y
= last_data
->point
.y
;
655 if (cpml_pair_intersection_pv_pv(&last_point
,
656 &last_point
, last_vector
,
657 new_point
, new_vector
)) {
658 last_data
->point
.x
= last_point
.x
;
659 last_data
->point
.y
= last_point
.y
;
661 last_data
->point
.x
= new_point
->x
;
662 last_data
->point
.y
= new_point
->y
;