/src/gpac/src/utils/path2d_stroker.c
Line | Count | Source |
1 | | /***************************************************************************/ |
2 | | /* */ |
3 | | /* ftstroke.c */ |
4 | | /* */ |
5 | | /* FreeType path stroker (body). */ |
6 | | /* */ |
7 | | /* Copyright 2002, 2003, 2004 by */ |
8 | | /* David Turner, Robert Wilhelm, and Werner Lemberg. */ |
9 | | /* */ |
10 | | /* This file is part of the FreeType project, and may only be used, */ |
11 | | /* modified, and distributed under the terms of the FreeType project */ |
12 | | /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ |
13 | | /* this file you indicate that you have read the license and */ |
14 | | /* understand and accept it fully. */ |
15 | | /* */ |
16 | | /***************************************************************************/ |
17 | | |
18 | | |
19 | | #include <gpac/path2d.h> |
20 | | |
21 | | #ifndef GPAC_DISABLE_EVG |
22 | | |
23 | | /***************************************************************************/ |
24 | | /***************************************************************************/ |
25 | | /***** *****/ |
26 | | /***** BEZIER COMPUTATIONS *****/ |
27 | | /***** *****/ |
28 | | /***************************************************************************/ |
29 | | /***************************************************************************/ |
30 | | |
31 | | #define FT_SMALL_CONIC_THRESHOLD ( GF_PI / 6 ) |
32 | | #define FT_SMALL_CUBIC_THRESHOLD ( GF_PI / 6 ) |
33 | | |
34 | | #define FT_IS_SMALL( x ) ( (x) > -FIX_EPSILON && (x) < FIX_EPSILON ) |
35 | | |
36 | | #if 0 //unused |
37 | | |
38 | | static void ft_conic_split(GF_Point2D* base ) |
39 | | { |
40 | | Fixed a, b; |
41 | | |
42 | | base[4].x = base[2].x; |
43 | | b = base[1].x; |
44 | | a = base[3].x = ( base[2].x + b ) / 2; |
45 | | b = base[1].x = ( base[0].x + b ) / 2; |
46 | | base[2].x = ( a + b ) / 2; |
47 | | |
48 | | base[4].y = base[2].y; |
49 | | b = base[1].y; |
50 | | a = base[3].y = ( base[2].y + b ) / 2; |
51 | | b = base[1].y = ( base[0].y + b ) / 2; |
52 | | base[2].y = ( a + b ) / 2; |
53 | | } |
54 | | |
55 | | |
56 | | |
57 | | static Bool ft_conic_is_small_enough( GF_Point2D* base, Fixed *angle_in, Fixed *angle_out) |
58 | | { |
59 | | GF_Point2D d1, d2; |
60 | | Fixed theta; |
61 | | s32 close1, close2; |
62 | | d1.x = base[1].x - base[2].x; |
63 | | d1.y = base[1].y - base[2].y; |
64 | | d2.x = base[0].x - base[1].x; |
65 | | d2.y = base[0].y - base[1].y; |
66 | | close1 = FT_IS_SMALL( d1.x ) && FT_IS_SMALL( d1.y ); |
67 | | close2 = FT_IS_SMALL( d2.x ) && FT_IS_SMALL( d2.y ); |
68 | | |
69 | | if ( close1 ) { |
70 | | if ( close2 ) |
71 | | *angle_in = *angle_out = 0; |
72 | | else |
73 | | *angle_in = *angle_out = gf_atan2(d2.y, d2.x); |
74 | | } |
75 | | else if ( close2 ) { |
76 | | *angle_in = *angle_out = gf_atan2(d1.y, d1.x); |
77 | | } else { |
78 | | *angle_in = gf_atan2(d1.y, d1.x); |
79 | | *angle_out = gf_atan2(d2.y, d2.x); |
80 | | } |
81 | | theta = ABS( gf_angle_diff(*angle_in, *angle_out)); |
82 | | return ( theta < FT_SMALL_CONIC_THRESHOLD ) ? GF_TRUE : GF_FALSE; |
83 | | } |
84 | | |
85 | | static void ft_cubic_split( GF_Point2D* base ) |
86 | | { |
87 | | Fixed a, b, c, d; |
88 | | base[6].x = base[3].x; |
89 | | c = base[1].x; |
90 | | d = base[2].x; |
91 | | base[1].x = a = ( base[0].x + c ) / 2; |
92 | | base[5].x = b = ( base[3].x + d ) / 2; |
93 | | c = ( c + d ) / 2; |
94 | | base[2].x = a = ( a + c ) / 2; |
95 | | base[4].x = b = ( b + c ) / 2; |
96 | | base[3].x = ( a + b ) / 2; |
97 | | |
98 | | base[6].y = base[3].y; |
99 | | c = base[1].y; |
100 | | d = base[2].y; |
101 | | base[1].y = a = ( base[0].y + c ) / 2; |
102 | | base[5].y = b = ( base[3].y + d ) / 2; |
103 | | c = ( c + d ) / 2; |
104 | | base[2].y = a = ( a + c ) / 2; |
105 | | base[4].y = b = ( b + c ) / 2; |
106 | | base[3].y = ( a + b ) / 2; |
107 | | } |
108 | | |
109 | | |
110 | | static Bool ft_cubic_is_small_enough(GF_Point2D *base, Fixed *angle_in, Fixed *angle_mid, Fixed *angle_out) |
111 | | { |
112 | | GF_Point2D d1, d2, d3; |
113 | | Fixed theta1, theta2; |
114 | | s32 close1, close2, close3; |
115 | | d1.x = base[2].x - base[3].x; |
116 | | d1.y = base[2].y - base[3].y; |
117 | | d2.x = base[1].x - base[2].x; |
118 | | d2.y = base[1].y - base[2].y; |
119 | | d3.x = base[0].x - base[1].x; |
120 | | d3.y = base[0].y - base[1].y; |
121 | | |
122 | | close1 = FT_IS_SMALL( d1.x ) && FT_IS_SMALL( d1.y ); |
123 | | close2 = FT_IS_SMALL( d2.x ) && FT_IS_SMALL( d2.y ); |
124 | | close3 = FT_IS_SMALL( d3.x ) && FT_IS_SMALL( d3.y ); |
125 | | |
126 | | if ( close1 || close3 ) { |
127 | | if ( close2 ) { |
128 | | /* basically a point */ |
129 | | *angle_in = *angle_out = *angle_mid = 0; |
130 | | } else if ( close1 ) { |
131 | | *angle_in = *angle_mid = gf_atan2( d2.y, d2.x); |
132 | | *angle_out = gf_atan2( d3.y, d3.x); |
133 | | } |
134 | | /* close2 */ |
135 | | else { |
136 | | *angle_in = gf_atan2(d1.y, d1.x); |
137 | | *angle_mid = *angle_out = gf_atan2(d2.y, d2.x); |
138 | | } |
139 | | } |
140 | | else if ( close2 ) { |
141 | | *angle_in = *angle_mid = gf_atan2(d1.y, d1.x); |
142 | | *angle_out = gf_atan2(d3.y, d3.x); |
143 | | } else { |
144 | | *angle_in = gf_atan2(d1.y, d1.x); |
145 | | *angle_mid = gf_atan2(d2.y, d2.x); |
146 | | *angle_out = gf_atan2(d3.y, d3.x); |
147 | | } |
148 | | theta1 = ABS( gf_angle_diff( *angle_in, *angle_mid ) ); |
149 | | theta2 = ABS( gf_angle_diff( *angle_mid, *angle_out ) ); |
150 | | return ((theta1 < FT_SMALL_CUBIC_THRESHOLD) && (theta2 < FT_SMALL_CUBIC_THRESHOLD )) ? GF_TRUE : GF_FALSE; |
151 | | } |
152 | | |
153 | | #endif |
154 | | |
155 | | /***************************************************************************/ |
156 | | /***************************************************************************/ |
157 | | /***** *****/ |
158 | | /***** STROKE BORDERS *****/ |
159 | | /***** *****/ |
160 | | /***************************************************************************/ |
161 | | /***************************************************************************/ |
162 | | |
163 | | typedef enum |
164 | | { |
165 | | FT_STROKE_TAG_ON = 1, /* on-curve point */ |
166 | | FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ |
167 | | FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ |
168 | | FT_STROKE_TAG_END = 8 /* sub-path end */ |
169 | | } FT_StrokeTags; |
170 | | |
171 | | |
172 | | typedef struct FT_StrokeBorderRec_ |
173 | | { |
174 | | u32 num_points; |
175 | | u32 max_points; |
176 | | GF_Point2D* points; |
177 | | u8 *tags; |
178 | | Bool movable; |
179 | | /* index of current sub-path start point */ |
180 | | s32 start; |
181 | | Bool valid; |
182 | | } FT_StrokeBorderRec, *FT_StrokeBorder; |
183 | | |
184 | | |
185 | | static s32 ft_stroke_border_grow(FT_StrokeBorder border, u32 new_points) |
186 | 0 | { |
187 | 0 | u32 new_max = border->num_points + new_points; |
188 | 0 | if (new_max > border->max_points) { |
189 | 0 | u32 cur_max = new_max*2; |
190 | 0 | border->points = (GF_Point2D *) gf_realloc(border->points, sizeof(GF_Point2D)*cur_max); |
191 | 0 | border->tags = (u8 *) gf_realloc(border->tags, sizeof(u8)*cur_max); |
192 | 0 | if (!border->points || !border->tags) return -1; |
193 | 0 | border->max_points = cur_max; |
194 | 0 | } |
195 | 0 | return 0; |
196 | 0 | } |
197 | | |
198 | | static void ft_stroke_border_close( FT_StrokeBorder border ) |
199 | 0 | { |
200 | | /* don't record empty paths! */ |
201 | 0 | if ((border->start <0) || !border->num_points ) return; |
202 | 0 | if ( border->num_points > (u32)border->start ) { |
203 | 0 | border->tags[border->start] |= FT_STROKE_TAG_BEGIN; |
204 | 0 | border->tags[border->num_points - 1] |= FT_STROKE_TAG_END; |
205 | 0 | } |
206 | 0 | border->start = -1; |
207 | 0 | border->movable = GF_FALSE; |
208 | 0 | } |
209 | | |
210 | | static s32 ft_stroke_border_lineto( FT_StrokeBorder border, GF_Point2D* to, Bool movable ) |
211 | 0 | { |
212 | 0 | gf_assert(border->start >= 0); |
213 | |
|
214 | 0 | if ( border->movable ) { |
215 | | /* move last point */ |
216 | 0 | border->points[border->num_points - 1] = *to; |
217 | 0 | } else { |
218 | | /* add one point */ |
219 | 0 | if (ft_stroke_border_grow( border, 1 )==0) { |
220 | 0 | GF_Point2D* vec = border->points + border->num_points; |
221 | 0 | u8 *tag = border->tags + border->num_points; |
222 | |
|
223 | 0 | vec[0] = *to; |
224 | 0 | tag[0] = FT_STROKE_TAG_ON; |
225 | 0 | border->num_points += 1; |
226 | 0 | } else { |
227 | 0 | return -1; |
228 | 0 | } |
229 | 0 | } |
230 | 0 | border->movable = movable; |
231 | 0 | return 0; |
232 | 0 | } |
233 | | |
234 | | #if 0 //unused |
235 | | static s32 ft_stroke_border_conicto( FT_StrokeBorder border, GF_Point2D* control, GF_Point2D* to ) |
236 | | { |
237 | | gf_assert( border->start >= 0 ); |
238 | | if (ft_stroke_border_grow( border, 2 )==0) { |
239 | | GF_Point2D* vec = border->points + border->num_points; |
240 | | u8 *tag = border->tags + border->num_points; |
241 | | |
242 | | vec[0] = *control; |
243 | | vec[1] = *to; |
244 | | |
245 | | tag[0] = 0; |
246 | | tag[1] = FT_STROKE_TAG_ON; |
247 | | |
248 | | border->num_points += 2; |
249 | | } else { |
250 | | return -1; |
251 | | } |
252 | | border->movable = GF_FALSE; |
253 | | return 0; |
254 | | } |
255 | | #endif |
256 | | |
257 | | static s32 ft_stroke_border_cubicto( FT_StrokeBorder border, |
258 | | GF_Point2D* control1, |
259 | | GF_Point2D* control2, |
260 | | GF_Point2D* to ) |
261 | 0 | { |
262 | 0 | gf_assert( border->start >= 0 ); |
263 | |
|
264 | 0 | if (!ft_stroke_border_grow( border, 3 )) { |
265 | 0 | GF_Point2D* vec = border->points + border->num_points; |
266 | 0 | u8* tag = border->tags + border->num_points; |
267 | 0 | vec[0] = *control1; |
268 | 0 | vec[1] = *control2; |
269 | 0 | vec[2] = *to; |
270 | |
|
271 | 0 | tag[0] = FT_STROKE_TAG_CUBIC; |
272 | 0 | tag[1] = FT_STROKE_TAG_CUBIC; |
273 | 0 | tag[2] = FT_STROKE_TAG_ON; |
274 | |
|
275 | 0 | border->num_points += 3; |
276 | 0 | } else { |
277 | 0 | return -1; |
278 | 0 | } |
279 | 0 | border->movable = GF_FALSE; |
280 | 0 | return 0; |
281 | 0 | } |
282 | | |
283 | 0 | #define FT_ARC_CUBIC_ANGLE ( GF_PI / 2 ) |
284 | | |
285 | | |
286 | | static s32 ft_stroke_border_arcto( FT_StrokeBorder border, |
287 | | GF_Point2D* center, |
288 | | Fixed radius, |
289 | | Fixed angle_start, |
290 | | Fixed angle_diff ) |
291 | 0 | { |
292 | 0 | Fixed total, angle, step, rotate, next, theta; |
293 | 0 | GF_Point2D a, b, a2, b2; |
294 | 0 | Fixed length; |
295 | | /* compute start point */ |
296 | 0 | a = gf_v2d_from_polar(radius, angle_start ); |
297 | 0 | a.x += center->x; |
298 | 0 | a.y += center->y; |
299 | |
|
300 | 0 | total = angle_diff; |
301 | 0 | angle = angle_start; |
302 | 0 | rotate = ( angle_diff >= 0 ) ? GF_PI2 : -GF_PI2; |
303 | |
|
304 | 0 | while ( total != 0 ) { |
305 | 0 | step = total; |
306 | 0 | if ( step > FT_ARC_CUBIC_ANGLE ) |
307 | 0 | step = FT_ARC_CUBIC_ANGLE; |
308 | 0 | else if ( step < -FT_ARC_CUBIC_ANGLE ) |
309 | 0 | step = -FT_ARC_CUBIC_ANGLE; |
310 | |
|
311 | 0 | next = angle + step; |
312 | 0 | theta = step; |
313 | 0 | if ( theta < 0 ) |
314 | 0 | theta = -theta; |
315 | |
|
316 | | #ifdef GPAC_FIXED_POINT |
317 | | theta >>= 1; |
318 | | #else |
319 | 0 | theta /= 2; |
320 | 0 | #endif |
321 | | |
322 | | /* compute end point */ |
323 | 0 | b = gf_v2d_from_polar(radius, next ); |
324 | 0 | b.x += center->x; |
325 | 0 | b.y += center->y; |
326 | | |
327 | | /* compute first and second control points */ |
328 | 0 | length = gf_muldiv( radius, gf_sin( theta ) * 4, ( FIX_ONE + gf_cos( theta ) ) * 3 ); |
329 | |
|
330 | 0 | a2 = gf_v2d_from_polar(length, angle + rotate ); |
331 | 0 | a2.x += a.x; |
332 | 0 | a2.y += a.y; |
333 | |
|
334 | 0 | b2 = gf_v2d_from_polar(length, next - rotate ); |
335 | 0 | b2.x += b.x; |
336 | 0 | b2.y += b.y; |
337 | | |
338 | | /* add cubic arc */ |
339 | 0 | if (ft_stroke_border_cubicto( border, &a2, &b2, &b ) != 0) return -1; |
340 | | |
341 | | /* process the rest of the arc ?? */ |
342 | 0 | a = b; |
343 | 0 | total -= step; |
344 | 0 | angle = next; |
345 | 0 | } |
346 | 0 | return 0; |
347 | 0 | } |
348 | | |
349 | | |
350 | | static s32 ft_stroke_border_moveto(FT_StrokeBorder border, GF_Point2D* to ) |
351 | 0 | { |
352 | | /* close current open path if any ? */ |
353 | 0 | if ( border->start >= 0 ) |
354 | 0 | ft_stroke_border_close( border ); |
355 | |
|
356 | 0 | border->start = border->num_points; |
357 | 0 | border->movable = GF_FALSE; |
358 | 0 | return ft_stroke_border_lineto(border, to, GF_FALSE); |
359 | 0 | } |
360 | | |
361 | | |
362 | | static s32 ft_stroke_border_get_counts(FT_StrokeBorder border, |
363 | | u32 *anum_points, |
364 | | u32 *anum_contours ) |
365 | 0 | { |
366 | 0 | s32 error = 0; |
367 | 0 | u32 num_points = 0; |
368 | 0 | u32 num_contours = 0; |
369 | 0 | u32 count = border->num_points; |
370 | 0 | GF_Point2D *point = border->points; |
371 | 0 | u8 *tags = border->tags; |
372 | 0 | s32 in_contour = 0; |
373 | |
|
374 | 0 | for ( ; count > 0; count--, num_points++, point++, tags++ ) { |
375 | 0 | if ( tags[0] & FT_STROKE_TAG_BEGIN ) { |
376 | 0 | if ( in_contour != 0 ) goto Fail; |
377 | | |
378 | 0 | in_contour = 1; |
379 | 0 | } else if ( in_contour == 0 ) |
380 | 0 | goto Fail; |
381 | | |
382 | 0 | if ( tags[0] & FT_STROKE_TAG_END ) { |
383 | 0 | if ( in_contour == 0 ) |
384 | 0 | goto Fail; |
385 | 0 | in_contour = 0; |
386 | 0 | num_contours++; |
387 | 0 | } |
388 | 0 | } |
389 | | |
390 | 0 | if ( in_contour != 0 ) |
391 | 0 | goto Fail; |
392 | | |
393 | 0 | border->valid = GF_TRUE; |
394 | |
|
395 | 0 | Exit: |
396 | 0 | *anum_points = num_points; |
397 | 0 | *anum_contours = num_contours; |
398 | 0 | return error; |
399 | | |
400 | 0 | Fail: |
401 | 0 | num_points = 0; |
402 | 0 | num_contours = 0; |
403 | 0 | error = -1; |
404 | 0 | goto Exit; |
405 | 0 | } |
406 | | |
407 | | |
408 | | static void ft_stroke_border_export( FT_StrokeBorder border, GF_Path* outline ) |
409 | 0 | { |
410 | 0 | if (!border->num_points) return; |
411 | | |
412 | | /* copy point locations */ |
413 | 0 | memcpy(outline->points + outline->n_points, border->points, sizeof(GF_Point2D)*border->num_points); |
414 | | |
415 | | /* copy tags */ |
416 | 0 | { |
417 | 0 | u32 count = border->num_points; |
418 | 0 | u8* read = border->tags; |
419 | 0 | u8* write = (u8*)outline->tags + outline->n_points; |
420 | |
|
421 | 0 | for ( ; count > 0; count--, read++, write++ ) { |
422 | 0 | if ( *read & FT_STROKE_TAG_ON ) |
423 | 0 | *write = GF_PATH_CURVE_ON; |
424 | 0 | else if ( *read & FT_STROKE_TAG_CUBIC ) |
425 | 0 | *write = GF_PATH_CURVE_CUBIC; |
426 | 0 | else |
427 | 0 | *write = GF_PATH_CURVE_CONIC; |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | | /* copy contours */ |
432 | 0 | { |
433 | 0 | u32 count = border->num_points; |
434 | 0 | u8 *tags = border->tags; |
435 | 0 | u32 *write = outline->contours + outline->n_contours; |
436 | 0 | u32 idx = outline->n_points; |
437 | |
|
438 | 0 | for ( ; count > 0; count--, tags++, idx++ ) { |
439 | 0 | if ( *tags & FT_STROKE_TAG_END ) { |
440 | 0 | *write++ = idx; |
441 | 0 | outline->n_contours++; |
442 | 0 | } |
443 | 0 | } |
444 | 0 | } |
445 | 0 | outline->n_points = outline->n_points + border->num_points; |
446 | 0 | } |
447 | | |
448 | | |
449 | | /***************************************************************************/ |
450 | | /***************************************************************************/ |
451 | | /***** *****/ |
452 | | /***** STROKER *****/ |
453 | | /***** *****/ |
454 | | /***************************************************************************/ |
455 | | /***************************************************************************/ |
456 | | |
457 | 0 | #define FT_SIDE_TO_ROTATE( s ) ( GF_PI2 - (s) * GF_PI ) |
458 | | |
459 | | typedef struct FT_StrokerRec_ |
460 | | { |
461 | | Fixed angle_in; |
462 | | Fixed angle_out; |
463 | | GF_Point2D center; |
464 | | Bool first_point; |
465 | | Fixed subpath_angle; |
466 | | GF_Point2D subpath_start; |
467 | | |
468 | | u32 line_cap; |
469 | | u32 line_join; |
470 | | Fixed miter_limit; |
471 | | Fixed radius; |
472 | | Bool valid; |
473 | | Bool closing; |
474 | | FT_StrokeBorderRec borders[2]; |
475 | | } FT_StrokerRec, FT_Stroker; |
476 | | |
477 | | |
478 | | /* creates a circular arc at a corner or cap */ |
479 | | static s32 ft_stroker_arcto( FT_Stroker *stroker, s32 side ) |
480 | 0 | { |
481 | 0 | Fixed total, rotate; |
482 | 0 | Fixed radius = stroker->radius; |
483 | 0 | s32 error = 0; |
484 | 0 | FT_StrokeBorder border = stroker->borders + side; |
485 | 0 | rotate = FT_SIDE_TO_ROTATE( side ); |
486 | 0 | total = gf_angle_diff( stroker->angle_in, stroker->angle_out); |
487 | 0 | if ( total == GF_PI ) total = -rotate * 2; |
488 | 0 | error = ft_stroke_border_arcto( border, |
489 | 0 | &stroker->center, |
490 | 0 | radius, |
491 | 0 | stroker->angle_in + rotate, |
492 | 0 | total ); |
493 | 0 | border->movable = GF_FALSE; |
494 | 0 | return error; |
495 | 0 | } |
496 | | |
497 | | /* adds a cap at the end of an opened path */ |
498 | | static s32 ft_stroker_cap(FT_Stroker *stroker, Fixed angle, s32 side) |
499 | 0 | { |
500 | 0 | s32 error = 0; |
501 | 0 | if ( stroker->line_cap == GF_LINE_CAP_ROUND) { |
502 | | /* OK we cheat a bit here compared to FT original code, and use a rough cubic cap instead of |
503 | | a circle to deal with arbitrary orientation of regular paths where arc cap is not always properly oriented. |
504 | | Rather than computing orientation we simply approximate to conic - btw this takes less memory than |
505 | | exact circle cap since cubics are natively supported - we don't use conic since result is not so good looking*/ |
506 | 0 | GF_Point2D delta, delta2, ctl1, ctl2, end; |
507 | 0 | Fixed rotate = FT_SIDE_TO_ROTATE( side ); |
508 | 0 | Fixed radius = stroker->radius; |
509 | 0 | FT_StrokeBorder border = stroker->borders + side; |
510 | | |
511 | |
|
512 | 0 | delta = gf_v2d_from_polar(radius, angle); |
513 | 0 | delta.x = 4*delta.x/3; |
514 | 0 | delta.y = 4*delta.y/3; |
515 | |
|
516 | 0 | delta2 = gf_v2d_from_polar(radius, angle + rotate); |
517 | 0 | ctl1.x = delta.x + stroker->center.x + delta2.x; |
518 | 0 | ctl1.y = delta.y + stroker->center.y + delta2.y; |
519 | |
|
520 | 0 | delta2 = gf_v2d_from_polar(radius, angle - rotate); |
521 | 0 | ctl2.x = delta.x + delta2.x + stroker->center.x; |
522 | 0 | ctl2.y = delta.y + delta2.y + stroker->center.y; |
523 | |
|
524 | 0 | end.x = delta2.x + stroker->center.x; |
525 | 0 | end.y = delta2.y + stroker->center.y; |
526 | |
|
527 | 0 | error = ft_stroke_border_cubicto( border, &ctl1, &ctl2, &end); |
528 | 0 | } else if ( stroker->line_cap == GF_LINE_CAP_SQUARE) { |
529 | | /* add a square cap */ |
530 | 0 | GF_Point2D delta, delta2; |
531 | 0 | Fixed rotate = FT_SIDE_TO_ROTATE( side ); |
532 | 0 | Fixed radius = stroker->radius; |
533 | 0 | FT_StrokeBorder border = stroker->borders + side; |
534 | | |
535 | |
|
536 | 0 | delta2 = gf_v2d_from_polar(radius, angle + rotate); |
537 | 0 | delta = gf_v2d_from_polar(radius, angle); |
538 | |
|
539 | 0 | delta.x += stroker->center.x + delta2.x; |
540 | 0 | delta.y += stroker->center.y + delta2.y; |
541 | |
|
542 | 0 | error = ft_stroke_border_lineto(border, &delta, GF_FALSE); |
543 | 0 | if ( error ) |
544 | 0 | goto Exit; |
545 | | |
546 | 0 | delta2 = gf_v2d_from_polar(radius, angle - rotate); |
547 | 0 | delta = gf_v2d_from_polar(radius, angle); |
548 | |
|
549 | 0 | delta.x += delta2.x + stroker->center.x; |
550 | 0 | delta.y += delta2.y + stroker->center.y; |
551 | |
|
552 | 0 | error = ft_stroke_border_lineto(border, &delta, GF_FALSE); |
553 | 0 | } else if ( stroker->line_cap == GF_LINE_CAP_TRIANGLE) { |
554 | | /* add a triangle cap */ |
555 | 0 | GF_Point2D delta; |
556 | 0 | Fixed radius = stroker->radius; |
557 | 0 | FT_StrokeBorder border = stroker->borders + side; |
558 | 0 | border->movable = GF_FALSE; |
559 | 0 | delta = gf_v2d_from_polar(radius, angle); |
560 | 0 | delta.x += stroker->center.x; |
561 | 0 | delta.y += stroker->center.y; |
562 | 0 | error = ft_stroke_border_lineto(border, &delta, GF_FALSE); |
563 | 0 | } |
564 | | |
565 | 0 | Exit: |
566 | 0 | return error; |
567 | 0 | } |
568 | | |
569 | | |
570 | | /* process an inside corner, i.e. compute intersection */ |
571 | | static s32 ft_stroker_inside(FT_Stroker *stroker, s32 side) |
572 | 0 | { |
573 | 0 | FT_StrokeBorder border = stroker->borders + side; |
574 | 0 | Fixed phi, theta, rotate; |
575 | 0 | Fixed length, thcos, sigma; |
576 | 0 | GF_Point2D delta; |
577 | 0 | s32 error = 0; |
578 | |
|
579 | 0 | rotate = FT_SIDE_TO_ROTATE( side ); |
580 | | |
581 | | /* compute median angle */ |
582 | 0 | theta = gf_angle_diff( stroker->angle_in, stroker->angle_out ); |
583 | 0 | if ( theta == GF_PI ) |
584 | 0 | theta = rotate; |
585 | 0 | else |
586 | 0 | theta = theta / 2; |
587 | |
|
588 | 0 | phi = stroker->angle_in + theta; |
589 | |
|
590 | 0 | thcos = gf_cos( theta ); |
591 | 0 | sigma = gf_mulfix( stroker->miter_limit, thcos ); |
592 | |
|
593 | 0 | if ( sigma < FIX_ONE ) { |
594 | 0 | delta = gf_v2d_from_polar(stroker->radius, stroker->angle_out + rotate ); |
595 | 0 | delta.x += stroker->center.x; |
596 | 0 | delta.y += stroker->center.y; |
597 | 0 | if (!stroker->closing) border->movable = GF_FALSE; |
598 | 0 | } else { |
599 | 0 | length = gf_divfix( stroker->radius, thcos ); |
600 | 0 | delta = gf_v2d_from_polar(length, phi + rotate ); |
601 | 0 | delta.x += stroker->center.x; |
602 | 0 | delta.y += stroker->center.y; |
603 | 0 | } |
604 | 0 | error = ft_stroke_border_lineto(border, &delta, GF_FALSE); |
605 | 0 | return error; |
606 | 0 | } |
607 | | |
608 | | |
609 | | /* process an outside corner, i.e. compute bevel/miter/round */ |
610 | | static s32 ft_stroker_outside( FT_Stroker *stroker, s32 side ) |
611 | 0 | { |
612 | 0 | FT_StrokeBorder border = stroker->borders + side; |
613 | 0 | s32 error; |
614 | 0 | Fixed rotate; |
615 | 0 | u32 join = stroker->line_join; |
616 | |
|
617 | 0 | if ( join == GF_LINE_JOIN_MITER_SVG ) { |
618 | 0 | Fixed sin_theta, inv_sin_theta; |
619 | 0 | join = GF_LINE_JOIN_MITER; |
620 | 0 | sin_theta = gf_sin(gf_angle_diff( stroker->angle_out - GF_PI, stroker->angle_in) / 2 ); |
621 | 0 | if (sin_theta) { |
622 | 0 | inv_sin_theta = gf_invfix(sin_theta); |
623 | 0 | if (inv_sin_theta > stroker->miter_limit) join = GF_LINE_JOIN_BEVEL; |
624 | 0 | } else { |
625 | 0 | join = GF_LINE_JOIN_BEVEL; |
626 | 0 | } |
627 | 0 | } |
628 | |
|
629 | 0 | if ( join == GF_LINE_JOIN_ROUND ) { |
630 | 0 | error = ft_stroker_arcto( stroker, side ); |
631 | 0 | } else if (join == GF_LINE_JOIN_BEVEL) { |
632 | 0 | GF_Point2D delta; |
633 | 0 | rotate = FT_SIDE_TO_ROTATE( side ); |
634 | 0 | delta = gf_v2d_from_polar(stroker->radius, stroker->angle_out + rotate ); |
635 | 0 | delta.x += stroker->center.x; |
636 | 0 | delta.y += stroker->center.y; |
637 | | /*prevent moving current point*/ |
638 | 0 | border->movable = GF_FALSE; |
639 | | /*and add un-movable end point*/ |
640 | 0 | error = ft_stroke_border_lineto( border, &delta, GF_FALSE); |
641 | 0 | } else { |
642 | | /* this is a mitered or beveled corner */ |
643 | 0 | Fixed sigma, radius = stroker->radius; |
644 | 0 | Fixed theta, phi; |
645 | 0 | Fixed thcos; |
646 | 0 | Bool miter = GF_TRUE; |
647 | |
|
648 | 0 | rotate = FT_SIDE_TO_ROTATE( side ); |
649 | |
|
650 | 0 | theta = gf_angle_diff( stroker->angle_in, stroker->angle_out ); |
651 | 0 | if ( theta == GF_PI ) { |
652 | 0 | theta = rotate; |
653 | 0 | phi = stroker->angle_in; |
654 | 0 | } else { |
655 | 0 | theta = theta / 2; |
656 | 0 | phi = stroker->angle_in + theta + rotate; |
657 | 0 | } |
658 | |
|
659 | 0 | thcos = gf_cos( theta ); |
660 | 0 | sigma = gf_mulfix( stroker->miter_limit, thcos ); |
661 | |
|
662 | 0 | if ( sigma >= FIX_ONE ) { |
663 | 0 | miter = GF_FALSE; |
664 | 0 | } |
665 | | |
666 | | /* this is a miter (broken angle) */ |
667 | 0 | if ( miter ) { |
668 | 0 | GF_Point2D middle, delta; |
669 | 0 | Fixed length; |
670 | | |
671 | | /* compute middle point */ |
672 | 0 | middle = gf_v2d_from_polar(gf_mulfix(radius, stroker->miter_limit), phi); |
673 | 0 | middle.x += stroker->center.x; |
674 | 0 | middle.y += stroker->center.y; |
675 | | |
676 | | /* compute first angle point */ |
677 | 0 | length = gf_mulfix(radius, gf_divfix( FIX_ONE - sigma, ABS( gf_sin( theta ) ) ) ); |
678 | |
|
679 | 0 | delta = gf_v2d_from_polar(length, phi + rotate ); |
680 | 0 | delta.x += middle.x; |
681 | 0 | delta.y += middle.y; |
682 | |
|
683 | 0 | error = ft_stroke_border_lineto( border, &delta, GF_FALSE ); |
684 | 0 | if ( error ) |
685 | 0 | goto Exit; |
686 | | |
687 | | /* compute second angle point */ |
688 | 0 | delta = gf_v2d_from_polar(length, phi - rotate); |
689 | 0 | delta.x += middle.x; |
690 | 0 | delta.y += middle.y; |
691 | |
|
692 | 0 | error = ft_stroke_border_lineto( border, &delta, GF_FALSE ); |
693 | 0 | if ( error ) |
694 | 0 | goto Exit; |
695 | | |
696 | | /* finally, add a movable end point */ |
697 | 0 | delta = gf_v2d_from_polar(radius, stroker->angle_out + rotate ); |
698 | 0 | delta.x += stroker->center.x; |
699 | 0 | delta.y += stroker->center.y; |
700 | |
|
701 | 0 | error = ft_stroke_border_lineto( border, &delta, GF_TRUE); |
702 | 0 | } |
703 | | /* this is a bevel (intersection) */ |
704 | 0 | else { |
705 | 0 | Fixed length; |
706 | 0 | GF_Point2D delta; |
707 | | |
708 | |
|
709 | 0 | length = gf_divfix( stroker->radius, thcos ); |
710 | |
|
711 | 0 | delta = gf_v2d_from_polar(length, phi ); |
712 | 0 | delta.x += stroker->center.x; |
713 | 0 | delta.y += stroker->center.y; |
714 | |
|
715 | 0 | error = ft_stroke_border_lineto( border, &delta, GF_FALSE ); |
716 | 0 | if (error) goto Exit; |
717 | | |
718 | | /* now add end point */ |
719 | 0 | delta = gf_v2d_from_polar(stroker->radius, stroker->angle_out + rotate ); |
720 | 0 | delta.x += stroker->center.x; |
721 | 0 | delta.y += stroker->center.y; |
722 | |
|
723 | 0 | error = ft_stroke_border_lineto( border, &delta, GF_TRUE ); |
724 | 0 | } |
725 | 0 | } |
726 | 0 | Exit: |
727 | 0 | return error; |
728 | 0 | } |
729 | | |
730 | | |
731 | | |
732 | | static s32 ft_stroker_process_corner(FT_Stroker *stroker ) |
733 | 0 | { |
734 | 0 | s32 error = 0; |
735 | 0 | Fixed turn; |
736 | 0 | s32 inside_side; |
737 | 0 | turn = gf_angle_diff( stroker->angle_in, stroker->angle_out ); |
738 | | |
739 | | /* no specific corner processing is required if the turn is 0 */ |
740 | 0 | if ( turn == 0 ) |
741 | 0 | goto Exit; |
742 | | |
743 | | /* when we turn to the right, the inside side is 0 */ |
744 | 0 | inside_side = 0; |
745 | | /* otherwise, the inside side is 1 */ |
746 | 0 | if (turn < 0 ) |
747 | 0 | inside_side = 1; |
748 | | |
749 | | /* process the inside side */ |
750 | 0 | error = ft_stroker_inside( stroker, inside_side ); |
751 | 0 | if ( error ) goto Exit; |
752 | | |
753 | | /* process the outside side */ |
754 | 0 | error = ft_stroker_outside( stroker, 1 - inside_side ); |
755 | |
|
756 | 0 | Exit: |
757 | 0 | return error; |
758 | 0 | } |
759 | | |
760 | | |
761 | | /* add two points to the left and right borders corresponding to the */ |
762 | | /* start of the subpath.. */ |
763 | | static s32 ft_stroker_subpath_start( FT_Stroker *stroker, Fixed start_angle ) |
764 | 0 | { |
765 | 0 | GF_Point2D delta; |
766 | 0 | GF_Point2D point; |
767 | 0 | s32 error; |
768 | 0 | FT_StrokeBorder border; |
769 | |
|
770 | 0 | delta = gf_v2d_from_polar(stroker->radius, start_angle + GF_PI2 ); |
771 | |
|
772 | 0 | point.x = stroker->center.x + delta.x; |
773 | 0 | point.y = stroker->center.y + delta.y; |
774 | |
|
775 | 0 | border = stroker->borders; |
776 | 0 | error = ft_stroke_border_moveto( border, &point ); |
777 | 0 | if ( error ) |
778 | 0 | goto Exit; |
779 | | |
780 | 0 | point.x = stroker->center.x - delta.x; |
781 | 0 | point.y = stroker->center.y - delta.y; |
782 | |
|
783 | 0 | border++; |
784 | 0 | error = ft_stroke_border_moveto( border, &point ); |
785 | | |
786 | | /* save angle for last cap */ |
787 | 0 | stroker->subpath_angle = start_angle; |
788 | 0 | stroker->first_point = GF_FALSE; |
789 | |
|
790 | 0 | Exit: |
791 | 0 | return error; |
792 | 0 | } |
793 | | |
794 | | |
795 | | static s32 FT_Stroker_LineTo( FT_Stroker *stroker, GF_Point2D* to, Bool is_last) |
796 | 0 | { |
797 | 0 | s32 error = 0; |
798 | 0 | FT_StrokeBorder border; |
799 | 0 | GF_Point2D delta; |
800 | 0 | Fixed angle; |
801 | 0 | s32 side; |
802 | |
|
803 | 0 | delta.x = to->x - stroker->center.x; |
804 | 0 | delta.y = to->y - stroker->center.y; |
805 | 0 | if (!is_last && !delta.x && !delta.y) return 0; |
806 | | |
807 | 0 | angle = gf_atan2( delta.y, delta.x); |
808 | 0 | delta = gf_v2d_from_polar(stroker->radius, angle + GF_PI2 ); |
809 | | |
810 | | /* process corner if necessary */ |
811 | 0 | if ( stroker->first_point ) { |
812 | | /* This is the first segment of a subpath. We need to */ |
813 | | /* add a point to each border at their respective starting */ |
814 | | /* point locations. */ |
815 | 0 | error = ft_stroker_subpath_start( stroker, angle ); |
816 | 0 | if ( error ) |
817 | 0 | goto Exit; |
818 | 0 | } else { |
819 | | /* process the current corner */ |
820 | 0 | stroker->angle_out = angle; |
821 | 0 | error = ft_stroker_process_corner( stroker ); |
822 | 0 | if ( error ) |
823 | 0 | goto Exit; |
824 | 0 | } |
825 | | |
826 | | /* now add a line segment to both the "inside" and "outside" paths */ |
827 | 0 | for ( border = stroker->borders, side = 1; side >= 0; side--, border++ ) { |
828 | 0 | GF_Point2D point; |
829 | 0 | point.x = to->x + delta.x; |
830 | 0 | point.y = to->y + delta.y; |
831 | |
|
832 | 0 | error = ft_stroke_border_lineto( border, &point, GF_TRUE ); |
833 | 0 | if ( error ) |
834 | 0 | goto Exit; |
835 | | |
836 | 0 | delta.x = -delta.x; |
837 | 0 | delta.y = -delta.y; |
838 | 0 | } |
839 | 0 | stroker->angle_in = angle; |
840 | 0 | stroker->center = *to; |
841 | |
|
842 | 0 | Exit: |
843 | 0 | return error; |
844 | 0 | } |
845 | | |
846 | | #if 0 //unused |
847 | | static s32 FT_Stroker_ConicTo(FT_Stroker *stroker, GF_Point2D* control, GF_Point2D * to) |
848 | | { |
849 | | s32 error = 0; |
850 | | GF_Point2D bez_stack[34]; |
851 | | GF_Point2D* arc; |
852 | | GF_Point2D* limit = bez_stack + 30; |
853 | | Fixed start_angle; |
854 | | Bool first_arc = GF_TRUE; |
855 | | |
856 | | |
857 | | arc = bez_stack; |
858 | | arc[0] = *to; |
859 | | arc[1] = *control; |
860 | | arc[2] = stroker->center; |
861 | | |
862 | | while ( arc >= bez_stack ) { |
863 | | Fixed angle_in, angle_out; |
864 | | angle_in = angle_out = 0; /* remove compiler warnings */ |
865 | | |
866 | | if ( arc < limit && |
867 | | !ft_conic_is_small_enough( arc, &angle_in, &angle_out ) ) |
868 | | { |
869 | | ft_conic_split( arc ); |
870 | | arc += 2; |
871 | | continue; |
872 | | } |
873 | | |
874 | | if ( first_arc ) { |
875 | | first_arc = GF_FALSE; |
876 | | |
877 | | start_angle = angle_in; |
878 | | |
879 | | /* process corner if necessary */ |
880 | | if ( stroker->first_point ) |
881 | | error = ft_stroker_subpath_start( stroker, start_angle ); |
882 | | else { |
883 | | stroker->angle_out = start_angle; |
884 | | error = ft_stroker_process_corner( stroker ); |
885 | | } |
886 | | } |
887 | | |
888 | | /* the arc's angle is small enough; we can add it directly to each */ |
889 | | /* border */ |
890 | | { |
891 | | GF_Point2D ctrl, end; |
892 | | Fixed theta, phi, rotate; |
893 | | Fixed length; |
894 | | s32 side; |
895 | | |
896 | | theta = gf_angle_diff( angle_in, angle_out ) / 2; |
897 | | phi = angle_in + theta; |
898 | | length = gf_divfix( stroker->radius, gf_cos( theta ) ); |
899 | | |
900 | | for ( side = 0; side <= 1; side++ ) { |
901 | | rotate = FT_SIDE_TO_ROTATE( side ); |
902 | | |
903 | | /* compute control point */ |
904 | | ctrl = gf_v2d_from_polar(length, phi + rotate ); |
905 | | ctrl.x += arc[1].x; |
906 | | ctrl.y += arc[1].y; |
907 | | |
908 | | /* compute end point */ |
909 | | end = gf_v2d_from_polar(stroker->radius, angle_out + rotate ); |
910 | | end.x += arc[0].x; |
911 | | end.y += arc[0].y; |
912 | | |
913 | | error = ft_stroke_border_conicto( stroker->borders + side, &ctrl, &end ); |
914 | | if ( error ) |
915 | | goto Exit; |
916 | | } |
917 | | } |
918 | | |
919 | | arc -= 2; |
920 | | |
921 | | if ( arc < bez_stack ) |
922 | | stroker->angle_in = angle_out; |
923 | | } |
924 | | |
925 | | stroker->center = *to; |
926 | | Exit: |
927 | | return error; |
928 | | } |
929 | | |
930 | | |
931 | | static s32 FT_Stroker_CubicTo(FT_Stroker *stroker, |
932 | | GF_Point2D* control1, |
933 | | GF_Point2D* control2, |
934 | | GF_Point2D* to ) |
935 | | { |
936 | | s32 error = 0; |
937 | | GF_Point2D bez_stack[37]; |
938 | | GF_Point2D* arc; |
939 | | GF_Point2D* limit = bez_stack + 32; |
940 | | Fixed start_angle; |
941 | | Bool first_arc = GF_TRUE; |
942 | | |
943 | | arc = bez_stack; |
944 | | arc[0] = *to; |
945 | | arc[1] = *control2; |
946 | | arc[2] = *control1; |
947 | | arc[3] = stroker->center; |
948 | | |
949 | | while ( arc >= bez_stack ) { |
950 | | Fixed angle_in, angle_mid, angle_out; |
951 | | /* remove compiler warnings */ |
952 | | angle_in = angle_out = angle_mid = 0; |
953 | | |
954 | | if (arc < limit && |
955 | | !ft_cubic_is_small_enough( arc, &angle_in, &angle_mid, &angle_out ) ) |
956 | | { |
957 | | ft_cubic_split( arc ); |
958 | | arc += 3; |
959 | | continue; |
960 | | } |
961 | | |
962 | | if ( first_arc ) { |
963 | | first_arc = GF_FALSE; |
964 | | |
965 | | /* process corner if necessary */ |
966 | | start_angle = angle_in; |
967 | | |
968 | | if ( stroker->first_point ) |
969 | | error = ft_stroker_subpath_start( stroker, start_angle ); |
970 | | else { |
971 | | stroker->angle_out = start_angle; |
972 | | error = ft_stroker_process_corner( stroker ); |
973 | | } |
974 | | if ( error ) |
975 | | goto Exit; |
976 | | } |
977 | | |
978 | | /* the arc's angle is small enough; we can add it directly to each */ |
979 | | /* border */ |
980 | | { |
981 | | GF_Point2D ctrl1, ctrl2, end; |
982 | | Fixed theta1, phi1, theta2, phi2, rotate; |
983 | | Fixed length1, length2; |
984 | | s32 side; |
985 | | |
986 | | |
987 | | theta1 = ABS( angle_mid - angle_in ) / 2; |
988 | | theta2 = ABS( angle_out - angle_mid ) / 2; |
989 | | phi1 = (angle_mid + angle_in ) / 2; |
990 | | phi2 = (angle_mid + angle_out ) / 2; |
991 | | length1 = gf_divfix( stroker->radius, gf_cos( theta1 ) ); |
992 | | length2 = gf_divfix( stroker->radius, gf_cos(theta2) ); |
993 | | |
994 | | for ( side = 0; side <= 1; side++ ) { |
995 | | rotate = FT_SIDE_TO_ROTATE( side ); |
996 | | |
997 | | /* compute control points */ |
998 | | ctrl1 = gf_v2d_from_polar(length1, phi1 + rotate ); |
999 | | ctrl1.x += arc[2].x; |
1000 | | ctrl1.y += arc[2].y; |
1001 | | |
1002 | | ctrl2 = gf_v2d_from_polar(length2, phi2 + rotate ); |
1003 | | ctrl2.x += arc[1].x; |
1004 | | ctrl2.y += arc[1].y; |
1005 | | |
1006 | | /* compute end point */ |
1007 | | end = gf_v2d_from_polar(stroker->radius, angle_out + rotate ); |
1008 | | end.x += arc[0].x; |
1009 | | end.y += arc[0].y; |
1010 | | |
1011 | | error = ft_stroke_border_cubicto( stroker->borders + side, |
1012 | | &ctrl1, &ctrl2, &end ); |
1013 | | if ( error ) |
1014 | | goto Exit; |
1015 | | } |
1016 | | } |
1017 | | |
1018 | | arc -= 3; |
1019 | | if ( arc < bez_stack ) |
1020 | | stroker->angle_in = angle_out; |
1021 | | } |
1022 | | |
1023 | | stroker->center = *to; |
1024 | | |
1025 | | Exit: |
1026 | | return error; |
1027 | | } |
1028 | | #endif |
1029 | | |
1030 | | |
1031 | | |
1032 | | static s32 FT_Stroker_BeginSubPath(FT_Stroker *stroker, GF_Point2D* to) |
1033 | 0 | { |
1034 | | /* We cannot process the first point, because there is not enough */ |
1035 | | /* information regarding its corner/cap. The latter will be processed */ |
1036 | | /* in the "end_subpath" routine. */ |
1037 | | /* */ |
1038 | 0 | stroker->first_point = GF_TRUE; |
1039 | 0 | stroker->center = *to; |
1040 | | |
1041 | | /* record the subpath start point index for each border */ |
1042 | 0 | stroker->subpath_start = *to; |
1043 | 0 | return 0; |
1044 | 0 | } |
1045 | | |
1046 | | static s32 ft_stroker_add_reverse_left( FT_Stroker *stroker, Bool open ) |
1047 | 0 | { |
1048 | 0 | FT_StrokeBorder right = stroker->borders + 0; |
1049 | 0 | FT_StrokeBorder left = stroker->borders + 1; |
1050 | 0 | s32 new_points; |
1051 | 0 | s32 error = 0; |
1052 | |
|
1053 | 0 | if (!left->num_points) return 0; |
1054 | | |
1055 | 0 | gf_assert( left->start >= 0 ); |
1056 | 0 | new_points = left->num_points - left->start; |
1057 | 0 | if ( new_points > 0 ) { |
1058 | 0 | error = ft_stroke_border_grow( right, (u32)new_points ); |
1059 | 0 | if ( error ) |
1060 | 0 | goto Exit; |
1061 | | |
1062 | 0 | { |
1063 | 0 | GF_Point2D* dst_point = right->points + right->num_points; |
1064 | 0 | u8* dst_tag = right->tags + right->num_points; |
1065 | 0 | GF_Point2D* src_point = left->points + left->num_points - 1; |
1066 | 0 | u8* src_tag = left->tags + left->num_points - 1; |
1067 | |
|
1068 | 0 | while ( src_point >= left->points + left->start ) { |
1069 | 0 | *dst_point = *src_point; |
1070 | 0 | *dst_tag = *src_tag; |
1071 | |
|
1072 | 0 | if ( open ) |
1073 | 0 | dst_tag[0] &= ~( FT_STROKE_TAG_BEGIN | FT_STROKE_TAG_END ); |
1074 | 0 | else { |
1075 | | /* switch begin/end tags if necessary.. */ |
1076 | 0 | if ( dst_tag[0] & ( FT_STROKE_TAG_BEGIN | FT_STROKE_TAG_END ) ) |
1077 | 0 | dst_tag[0] ^= ( FT_STROKE_TAG_BEGIN | FT_STROKE_TAG_END ); |
1078 | 0 | } |
1079 | |
|
1080 | 0 | src_point--; |
1081 | 0 | src_tag--; |
1082 | 0 | dst_point++; |
1083 | 0 | dst_tag++; |
1084 | 0 | } |
1085 | 0 | } |
1086 | |
|
1087 | 0 | left->num_points = left->start; |
1088 | 0 | right->num_points += new_points; |
1089 | |
|
1090 | 0 | right->movable = GF_FALSE; |
1091 | 0 | left->movable = GF_FALSE; |
1092 | 0 | } |
1093 | | |
1094 | 0 | Exit: |
1095 | 0 | return error; |
1096 | 0 | } |
1097 | | |
1098 | | /* there's a lot of magic in this function! */ |
1099 | | static s32 FT_Stroker_EndSubPath( FT_Stroker *stroker, Bool do_close) |
1100 | 0 | { |
1101 | 0 | s32 error = 0; |
1102 | 0 | FT_StrokeBorder right = stroker->borders; |
1103 | 0 | if (do_close) { |
1104 | 0 | Fixed turn; |
1105 | 0 | s32 inside_side; |
1106 | | |
1107 | | /* process the corner */ |
1108 | 0 | stroker->angle_out = stroker->subpath_angle; |
1109 | 0 | turn = gf_angle_diff(stroker->angle_in, stroker->angle_out ); |
1110 | | |
1111 | | /* no specific corner processing is required if the turn is 0 */ |
1112 | 0 | if ( turn != 0 ) { |
1113 | | /* when we turn to the right, the inside side is 0 */ |
1114 | 0 | inside_side = 0; |
1115 | | |
1116 | | /* otherwise, the inside side is 1 */ |
1117 | 0 | if ( turn < 0 ) inside_side = 1; |
1118 | | |
1119 | | /* IMPORTANT: WE DO NOT PROCESS THE INSIDE BORDER HERE! */ |
1120 | | /* process the inside side */ |
1121 | | /* error = ft_stroker_inside( stroker, inside_side ); */ |
1122 | | /* if ( error ) */ |
1123 | | /* goto Exit; */ |
1124 | | |
1125 | | /* process the outside side */ |
1126 | 0 | error = ft_stroker_outside( stroker, 1 - inside_side ); |
1127 | 0 | if ( error ) |
1128 | 0 | goto Exit; |
1129 | 0 | } |
1130 | | |
1131 | 0 | ft_stroker_add_reverse_left(stroker, GF_FALSE); |
1132 | | /* then end our two subpaths */ |
1133 | 0 | ft_stroke_border_close( stroker->borders + 0 ); |
1134 | 0 | ft_stroke_border_close( stroker->borders + 1 ); |
1135 | 0 | } else { |
1136 | | /* All right, this is an opened path, we need to add a cap between */ |
1137 | | /* right & left, add the reverse of left, then add a final cap */ |
1138 | | /* between left & right. */ |
1139 | 0 | error = ft_stroker_cap( stroker, stroker->angle_in, 0 ); |
1140 | 0 | if ( error ) goto Exit; |
1141 | | |
1142 | | /* add reversed points from "left" to "right" */ |
1143 | 0 | error = ft_stroker_add_reverse_left( stroker, GF_TRUE ); |
1144 | 0 | if ( error ) goto Exit; |
1145 | | |
1146 | | /* now add the final cap */ |
1147 | 0 | stroker->center = stroker->subpath_start; |
1148 | 0 | error = ft_stroker_cap( stroker, |
1149 | 0 | stroker->subpath_angle + GF_PI, 0 ); |
1150 | 0 | if ( error ) |
1151 | 0 | goto Exit; |
1152 | | |
1153 | | /* Now end the right subpath accordingly. The left one is */ |
1154 | | /* rewind and doesn't need further processing. */ |
1155 | 0 | ft_stroke_border_close( right ); |
1156 | 0 | } |
1157 | | |
1158 | 0 | Exit: |
1159 | 0 | return error; |
1160 | 0 | } |
1161 | | |
1162 | | |
1163 | | static s32 FT_Stroker_GetCounts( FT_Stroker *stroker, u32 *anum_points, u32 *anum_contours ) |
1164 | 0 | { |
1165 | 0 | u32 count1, count2, num_points = 0; |
1166 | 0 | u32 count3, count4, num_contours = 0; |
1167 | 0 | s32 error; |
1168 | |
|
1169 | 0 | error = ft_stroke_border_get_counts( stroker->borders + 0, &count1, &count2 ); |
1170 | 0 | if ( error ) goto Exit; |
1171 | 0 | error = ft_stroke_border_get_counts( stroker->borders + 1, &count3, &count4 ); |
1172 | 0 | if ( error ) goto Exit; |
1173 | 0 | num_points = count1 + count3; |
1174 | 0 | num_contours = count2 + count4; |
1175 | |
|
1176 | 0 | Exit: |
1177 | 0 | *anum_points = num_points; |
1178 | 0 | *anum_contours = num_contours; |
1179 | 0 | return error; |
1180 | 0 | } |
1181 | | |
1182 | | /* |
1183 | | * The following is very similar to FT_Outline_Decompose, except |
1184 | | * that we do support opened paths, and do not scale the outline. |
1185 | | */ |
1186 | | static s32 FT_Stroker_ParseOutline(FT_Stroker *stroker, GF_Path* outline) |
1187 | 0 | { |
1188 | 0 | GF_Point2D v_last; |
1189 | | #if 0 //unused |
1190 | | GF_Point2D v_control; |
1191 | | #endif |
1192 | 0 | GF_Point2D v_start; |
1193 | 0 | GF_Point2D* point; |
1194 | 0 | GF_Point2D* limit; |
1195 | 0 | u8 *tags; |
1196 | 0 | s32 error; |
1197 | 0 | u32 n; /* index of contour in outline */ |
1198 | 0 | u32 first; /* index of first point in contour */ |
1199 | 0 | s32 tag; /* current point's state */ |
1200 | |
|
1201 | 0 | if ( !outline || !stroker ) |
1202 | 0 | return -1; |
1203 | | |
1204 | 0 | first = 0; |
1205 | |
|
1206 | 0 | for ( n = 0; n < outline->n_contours; n++ ) { |
1207 | 0 | s32 closed_subpath; |
1208 | 0 | s32 last; /* index of last point in contour */ |
1209 | |
|
1210 | 0 | last = outline->contours[n]; |
1211 | 0 | limit = outline->points + last; |
1212 | |
|
1213 | 0 | v_start = outline->points[first]; |
1214 | 0 | v_last = outline->points[last]; |
1215 | |
|
1216 | | #if 0 //unused |
1217 | | v_control = v_start; |
1218 | | #endif |
1219 | |
|
1220 | 0 | point = outline->points + first; |
1221 | 0 | tags = outline->tags + first; |
1222 | 0 | tag = tags[0]; |
1223 | | |
1224 | | /* A contour cannot start with a cubic control point! */ |
1225 | 0 | if ( tag == GF_PATH_CURVE_CUBIC ) |
1226 | 0 | goto Invalid_Outline; |
1227 | | |
1228 | | /* check first point to determine origin */ |
1229 | 0 | if ( tag == GF_PATH_CURVE_CONIC ) { |
1230 | | /* First point is conic control. Yes, this happens. */ |
1231 | 0 | if ( outline->tags[last] & GF_PATH_CURVE_ON ) { |
1232 | | /* start at last point if it is on the curve */ |
1233 | 0 | v_start = v_last; |
1234 | 0 | limit--; |
1235 | 0 | } else { |
1236 | | /* if both first and last points are conic, */ |
1237 | | /* start at their middle and record its position */ |
1238 | | /* for closure */ |
1239 | 0 | v_start.x = ( v_start.x + v_last.x ) / 2; |
1240 | 0 | v_start.y = ( v_start.y + v_last.y ) / 2; |
1241 | 0 | } |
1242 | 0 | point--; |
1243 | 0 | tags--; |
1244 | 0 | } |
1245 | 0 | closed_subpath = (outline->tags[outline->contours[n]]==GF_PATH_CLOSE) ? 1 : 0; |
1246 | |
|
1247 | 0 | error = FT_Stroker_BeginSubPath(stroker, &v_start); |
1248 | 0 | if ( error ) |
1249 | 0 | goto Exit; |
1250 | | |
1251 | | /*subpath is a single point, force a lineTo to start for the stroker to compute lineCap*/ |
1252 | 0 | if (point==limit) { |
1253 | 0 | error = FT_Stroker_LineTo(stroker, &v_start, GF_TRUE); |
1254 | 0 | closed_subpath = 0; |
1255 | 0 | goto Close; |
1256 | 0 | } |
1257 | | |
1258 | 0 | while ( point < limit ) { |
1259 | 0 | point++; |
1260 | 0 | tags++; |
1261 | |
|
1262 | 0 | tag = tags[0]; |
1263 | 0 | switch ( tag ) { |
1264 | 0 | case GF_PATH_CURVE_ON: /* emit a single line_to */ |
1265 | 0 | case GF_PATH_CLOSE: /* emit a single line_to */ |
1266 | 0 | { |
1267 | 0 | GF_Point2D vec; |
1268 | 0 | vec.x = point->x; |
1269 | 0 | vec.y = point->y; |
1270 | |
|
1271 | 0 | error = FT_Stroker_LineTo( stroker, &vec, (point == limit) ? GF_TRUE : GF_FALSE ); |
1272 | 0 | if ( error ) |
1273 | 0 | goto Exit; |
1274 | 0 | continue; |
1275 | 0 | } |
1276 | | |
1277 | | |
1278 | | #if 0 //unused |
1279 | | case GF_PATH_CURVE_CONIC: /* consume conic arcs */ |
1280 | | v_control.x = point->x; |
1281 | | v_control.y = point->y; |
1282 | | |
1283 | | Do_Conic: |
1284 | | if ( point < limit ) { |
1285 | | GF_Point2D vec; |
1286 | | GF_Point2D v_middle; |
1287 | | |
1288 | | |
1289 | | point++; |
1290 | | tags++; |
1291 | | tag = tags[0]; |
1292 | | |
1293 | | vec = point[0]; |
1294 | | |
1295 | | if ( tag & GF_PATH_CURVE_ON) { |
1296 | | |
1297 | | error = FT_Stroker_ConicTo( stroker, &v_control, &vec ); |
1298 | | if ( error ) |
1299 | | goto Exit; |
1300 | | continue; |
1301 | | } |
1302 | | |
1303 | | if ( tag != GF_PATH_CURVE_CONIC ) |
1304 | | goto Invalid_Outline; |
1305 | | |
1306 | | v_middle.x = ( v_control.x + vec.x ) / 2; |
1307 | | v_middle.y = ( v_control.y + vec.y ) / 2; |
1308 | | |
1309 | | error = FT_Stroker_ConicTo( stroker, &v_control, &v_middle ); |
1310 | | if ( error ) |
1311 | | goto Exit; |
1312 | | |
1313 | | v_control = vec; |
1314 | | goto Do_Conic; |
1315 | | } |
1316 | | error = FT_Stroker_ConicTo( stroker, &v_control, &v_start ); |
1317 | | goto Close; |
1318 | | |
1319 | | default: /* GF_PATH_CURVE_CUBIC */ |
1320 | | { |
1321 | | GF_Point2D vec1, vec2; |
1322 | | |
1323 | | if ( point + 1 > limit || |
1324 | | tags[1] != GF_PATH_CURVE_CUBIC ) |
1325 | | goto Invalid_Outline; |
1326 | | |
1327 | | point += 2; |
1328 | | tags += 2; |
1329 | | |
1330 | | vec1 = point[-2]; |
1331 | | vec2 = point[-1]; |
1332 | | |
1333 | | if ( point <= limit ) { |
1334 | | GF_Point2D vec; |
1335 | | vec = point[0]; |
1336 | | |
1337 | | error = FT_Stroker_CubicTo( stroker, &vec1, &vec2, &vec ); |
1338 | | if ( error ) |
1339 | | goto Exit; |
1340 | | continue; |
1341 | | } |
1342 | | error = FT_Stroker_CubicTo( stroker, &vec1, &vec2, &v_start ); |
1343 | | goto Close; |
1344 | | } |
1345 | | break; |
1346 | | #else |
1347 | 0 | default: /* GF_PATH_CURVE_CUBIC */ |
1348 | 0 | GF_LOG(GF_LOG_ERROR, GF_LOG_CORE, ("[Path2DStroke] Path is not flatten, cannot strike cubic and quadratic !\n")); |
1349 | 0 | #endif |
1350 | |
|
1351 | 0 | } |
1352 | 0 | } |
1353 | | |
1354 | 0 | Close: |
1355 | 0 | if ( error ) goto Exit; |
1356 | | |
1357 | 0 | error = FT_Stroker_EndSubPath(stroker, closed_subpath); |
1358 | 0 | if ( error ) |
1359 | 0 | goto Exit; |
1360 | | |
1361 | 0 | first = last + 1; |
1362 | 0 | } |
1363 | 0 | return 0; |
1364 | | |
1365 | 0 | Exit: |
1366 | 0 | return error; |
1367 | | |
1368 | 0 | Invalid_Outline: |
1369 | 0 | return -1; |
1370 | 0 | } |
1371 | | |
1372 | | |
1373 | | |
1374 | | |
1375 | | |
1376 | | |
1377 | 0 | #define GF_PATH_DOT_LEN 1 |
1378 | 0 | #define GF_PATH_DOT_SPACE 2 |
1379 | 0 | #define GF_PATH_DASH_LEN 3 |
1380 | | |
1381 | | static Fixed gf_path_get_dash(GF_PenSettings *pen, u32 dash_slot, u32 *next_slot) |
1382 | 0 | { |
1383 | 0 | Fixed ret = 0; |
1384 | 0 | switch (pen->dash) { |
1385 | 0 | case GF_DASH_STYLE_DOT: |
1386 | 0 | if (dash_slot==0) ret = GF_PATH_DOT_LEN; |
1387 | 0 | else if (dash_slot==1) ret = GF_PATH_DOT_SPACE; |
1388 | 0 | *next_slot = (dash_slot + 1) % 2; |
1389 | 0 | return ret * pen->width; |
1390 | 0 | case GF_DASH_STYLE_DASH: |
1391 | 0 | if (dash_slot==0) ret = GF_PATH_DASH_LEN; |
1392 | 0 | else if (dash_slot==1) ret = GF_PATH_DOT_SPACE; |
1393 | 0 | *next_slot = (dash_slot + 1) % 2; |
1394 | 0 | return ret * pen->width; |
1395 | 0 | case GF_DASH_STYLE_DASH_DOT: |
1396 | 0 | if (dash_slot==0) ret = GF_PATH_DASH_LEN; |
1397 | 0 | else if (dash_slot==1) ret = GF_PATH_DOT_SPACE; |
1398 | 0 | else if (dash_slot==2) ret = GF_PATH_DOT_LEN; |
1399 | 0 | else if (dash_slot==3) ret = GF_PATH_DOT_SPACE; |
1400 | 0 | *next_slot = (dash_slot + 1) % 4; |
1401 | 0 | return ret * pen->width; |
1402 | 0 | case GF_DASH_STYLE_DASH_DASH_DOT: |
1403 | 0 | if (dash_slot==0) ret = GF_PATH_DASH_LEN; |
1404 | 0 | else if (dash_slot==1) ret = GF_PATH_DOT_SPACE; |
1405 | 0 | else if (dash_slot==2) ret = GF_PATH_DASH_LEN; |
1406 | 0 | else if (dash_slot==3) ret = GF_PATH_DOT_SPACE; |
1407 | 0 | else if (dash_slot==4) ret = GF_PATH_DOT_LEN; |
1408 | 0 | else if (dash_slot==5) ret = GF_PATH_DOT_SPACE; |
1409 | 0 | *next_slot = (dash_slot + 1) % 6; |
1410 | 0 | return ret * pen->width; |
1411 | 0 | case GF_DASH_STYLE_DASH_DOT_DOT: |
1412 | 0 | if (dash_slot==0) ret = GF_PATH_DASH_LEN; |
1413 | 0 | else if (dash_slot==1) ret = GF_PATH_DOT_SPACE; |
1414 | 0 | else if (dash_slot==2) ret = GF_PATH_DOT_LEN; |
1415 | 0 | else if (dash_slot==3) ret = GF_PATH_DOT_SPACE; |
1416 | 0 | else if (dash_slot==4) ret = GF_PATH_DOT_LEN; |
1417 | 0 | else if (dash_slot==5) ret = GF_PATH_DOT_SPACE; |
1418 | 0 | *next_slot = (dash_slot + 1) % 6; |
1419 | 0 | return ret * pen->width; |
1420 | 0 | case GF_DASH_STYLE_CUSTOM: |
1421 | 0 | case GF_DASH_STYLE_SVG: |
1422 | 0 | if (!pen->dash_set || !pen->dash_set->num_dash) return 0; |
1423 | 0 | if (dash_slot>=pen->dash_set->num_dash) dash_slot = 0; |
1424 | 0 | ret = pen->dash_set->dashes[dash_slot]; |
1425 | 0 | *next_slot = (1 + dash_slot) % pen->dash_set->num_dash; |
1426 | 0 | if (pen->dash==GF_DASH_STYLE_SVG) return ret; |
1427 | | /*custom dashes are of type Fixed !!*/ |
1428 | 0 | return gf_mulfix(ret, pen->width); |
1429 | | |
1430 | 0 | default: |
1431 | 0 | case GF_DASH_STYLE_PLAIN: |
1432 | 0 | *next_slot = 0; |
1433 | 0 | return 0; |
1434 | 0 | } |
1435 | 0 | } |
1436 | | |
1437 | | |
1438 | | /* Credits go to Raph Levien for libart / art_vpath_dash */ |
1439 | | |
1440 | | /* FIXEME - NOT DONE - Merge first and last subpaths when first and last dash segment are joined a closepath. */ |
1441 | | static GF_Err gf_path_mergedashes(GF_Path *gp, u32 start_contour_index) |
1442 | 0 | { |
1443 | 0 | u32 i, dash_first_pt, dash_nb_pts; |
1444 | 0 | if (start_contour_index) { |
1445 | 0 | dash_nb_pts = gp->contours[start_contour_index] - gp->contours[start_contour_index-1]; |
1446 | 0 | dash_first_pt = gp->contours[start_contour_index-1]+1; |
1447 | 0 | } else { |
1448 | 0 | dash_nb_pts = gp->contours[start_contour_index]+1; |
1449 | 0 | dash_first_pt = 0; |
1450 | 0 | } |
1451 | | /*skip first point of first dash in subpath (same as last point of last dash)*/ |
1452 | 0 | for (i=1; i<dash_nb_pts; i++) { |
1453 | 0 | GF_Err e = gf_path_add_line_to_vec(gp, &gp->points[dash_first_pt + i]); |
1454 | 0 | if (e) return e; |
1455 | 0 | } |
1456 | | /*remove initial dash*/ |
1457 | 0 | gp->n_points -= dash_nb_pts; |
1458 | 0 | memmove(gp->points + dash_first_pt, gp->points + dash_first_pt + dash_nb_pts, sizeof(GF_Point2D)*(gp->n_points - dash_first_pt)); |
1459 | 0 | memmove(gp->tags + dash_first_pt, gp->tags + dash_first_pt + dash_nb_pts, sizeof(u8)*(gp->n_points - dash_first_pt)); |
1460 | |
|
1461 | 0 | for (i=start_contour_index; i<gp->n_contours-1; i++) { |
1462 | 0 | gp->contours[i] = gp->contours[i+1] - dash_nb_pts; |
1463 | 0 | } |
1464 | 0 | gp->n_contours--; |
1465 | 0 | gp->contours = (u32 *)gf_realloc(gp->contours, sizeof(u32)*gp->n_contours); |
1466 | | |
1467 | | /* |
1468 | | gp->points = gf_realloc(gp->points, sizeof(GF_Point2D)*gp->n_points); |
1469 | | gp->tags = gf_realloc(gp->tags, sizeof(u8)*gp->n_points); |
1470 | | gp->n_alloc_points = gp->n_points; |
1471 | | */ |
1472 | 0 | return GF_OK; |
1473 | 0 | } |
1474 | | |
1475 | | static GF_Err evg_dash_subpath(GF_Path *dashed, GF_Point2D *pts, u32 nb_pts, GF_PenSettings *pen, Fixed length_scale) |
1476 | 0 | { |
1477 | 0 | Fixed *dists; |
1478 | 0 | Fixed totaldist; |
1479 | 0 | Fixed dash; |
1480 | 0 | Fixed dist; |
1481 | 0 | Fixed dash_dist; |
1482 | 0 | s32 offsetinit; |
1483 | 0 | u32 next_offset=0; |
1484 | 0 | s32 toggleinit; |
1485 | 0 | s32 firstindex; |
1486 | 0 | Bool toggle_check; |
1487 | 0 | GF_Err e; |
1488 | 0 | u32 i, start_ind; |
1489 | 0 | Fixed phase; |
1490 | 0 | s32 offset, toggle; |
1491 | |
|
1492 | 0 | dists = (Fixed *)gf_malloc(sizeof (Fixed) * nb_pts); |
1493 | 0 | if (dists == NULL) return GF_OUT_OF_MEM; |
1494 | | |
1495 | | /* initial values */ |
1496 | 0 | toggleinit = 1; |
1497 | 0 | offsetinit = 0; |
1498 | 0 | dash_dist = 0; |
1499 | |
|
1500 | 0 | dash = gf_path_get_dash(pen, offsetinit, &next_offset); |
1501 | 0 | if (length_scale) dash = gf_mulfix(dash, length_scale); |
1502 | 0 | firstindex = -1; |
1503 | 0 | toggle_check = GF_FALSE; |
1504 | |
|
1505 | 0 | start_ind = 0; |
1506 | 0 | dist = 0; |
1507 | | |
1508 | | /*SVG dashing is different from BIFS one*/ |
1509 | 0 | if (pen->dash==GF_DASH_STYLE_SVG) { |
1510 | 0 | while (pen->dash_offset>0) { |
1511 | 0 | if (pen->dash_offset - dash < 0) { |
1512 | 0 | dash -= pen->dash_offset; |
1513 | 0 | pen->dash_offset = 0; |
1514 | 0 | break; |
1515 | 0 | } |
1516 | 0 | pen->dash_offset -= dash; |
1517 | 0 | offsetinit = next_offset; |
1518 | 0 | toggleinit = !toggleinit; |
1519 | 0 | dash = gf_path_get_dash(pen, offsetinit, &next_offset); |
1520 | 0 | if (length_scale) dash = gf_mulfix(dash, length_scale); |
1521 | 0 | } |
1522 | 0 | if ((pen->dash_offset<0) && pen->dash_set && pen->dash_set->num_dash) { |
1523 | 0 | offsetinit = pen->dash_set->num_dash-1; |
1524 | 0 | dash = gf_path_get_dash(pen, offsetinit, &next_offset); |
1525 | 0 | if (length_scale) dash = gf_mulfix(dash, length_scale); |
1526 | 0 | while (pen->dash_offset<0) { |
1527 | 0 | toggleinit = !toggleinit; |
1528 | 0 | if (pen->dash_offset + dash > 0) { |
1529 | 0 | dash_dist = dash+pen->dash_offset; |
1530 | 0 | pen->dash_offset = 0; |
1531 | 0 | break; |
1532 | 0 | } |
1533 | 0 | pen->dash_offset += dash; |
1534 | 0 | if (offsetinit) offsetinit --; |
1535 | 0 | else offsetinit = pen->dash_set->num_dash-1; |
1536 | 0 | dash = gf_path_get_dash(pen, offsetinit, &next_offset); |
1537 | 0 | if (length_scale) dash = gf_mulfix(dash, length_scale); |
1538 | 0 | } |
1539 | 0 | } |
1540 | 0 | } |
1541 | | |
1542 | | /* calculate line lengths and update offset*/ |
1543 | 0 | totaldist = 0; |
1544 | 0 | for (i = 0; i < nb_pts - 1; i++) { |
1545 | 0 | GF_Point2D diff; |
1546 | 0 | diff.x = pts[i+1].x - pts[i].x; |
1547 | 0 | diff.y = pts[i+1].y - pts[i].y; |
1548 | 0 | dists[i] = gf_v2d_len(&diff); |
1549 | |
|
1550 | 0 | if (pen->dash_offset > dists[i]) { |
1551 | 0 | pen->dash_offset -= dists[i]; |
1552 | 0 | dists[i] = 0; |
1553 | 0 | } |
1554 | 0 | else if (pen->dash_offset) { |
1555 | 0 | Fixed a, x, y, dx, dy; |
1556 | |
|
1557 | 0 | a = gf_divfix(pen->dash_offset, dists[i]); |
1558 | 0 | dx = pts[i + 1].x - pts[i].x; |
1559 | 0 | dy = pts[i + 1].y - pts[i].y; |
1560 | 0 | x = pts[i].x + gf_mulfix(a, dx); |
1561 | 0 | y = pts[i].y + gf_mulfix(a, dy); |
1562 | 0 | e = gf_path_add_move_to(dashed, x, y); |
1563 | 0 | if (e) goto err_exit; |
1564 | 0 | totaldist += dists[i]; |
1565 | 0 | dist = pen->dash_offset; |
1566 | 0 | pen->dash_offset = 0; |
1567 | 0 | start_ind = i; |
1568 | 0 | } else { |
1569 | 0 | totaldist += dists[i]; |
1570 | 0 | } |
1571 | 0 | } |
1572 | 0 | dash -= dash_dist; |
1573 | | |
1574 | | /* subpath fits within first dash and no offset*/ |
1575 | 0 | if (!dist && totaldist <= dash) { |
1576 | 0 | if (toggleinit) { |
1577 | 0 | gf_path_add_move_to_vec(dashed, &pts[0]); |
1578 | 0 | for (i=1; i<nb_pts; i++) { |
1579 | 0 | gf_path_add_line_to_vec(dashed, &pts[i]); |
1580 | 0 | } |
1581 | 0 | } |
1582 | 0 | gf_free(dists); |
1583 | 0 | return GF_OK; |
1584 | 0 | } |
1585 | | |
1586 | | /* subpath is composed of at least one dash */ |
1587 | 0 | phase = 0; |
1588 | 0 | toggle = toggleinit; |
1589 | 0 | i = start_ind; |
1590 | |
|
1591 | 0 | if (toggle && !dist) { |
1592 | 0 | e = gf_path_add_move_to_vec(dashed, &pts[i]); |
1593 | 0 | if (e) goto err_exit; |
1594 | 0 | firstindex = dashed->n_contours - 1; |
1595 | 0 | } |
1596 | | |
1597 | 0 | while (i < nb_pts - 1) { |
1598 | | /* dash boundary is next */ |
1599 | 0 | if (dists[i] - dist > dash - phase) { |
1600 | 0 | Fixed a, x, y, dx, dy; |
1601 | 0 | dist += dash - phase; |
1602 | 0 | a = gf_divfix(dist, dists[i]); |
1603 | 0 | dx = pts[i + 1].x - pts[i].x; |
1604 | 0 | dy = pts[i + 1].y - pts[i].y; |
1605 | 0 | x = pts[i].x + gf_mulfix(a, dx); |
1606 | 0 | y = pts[i].y + gf_mulfix(a, dy); |
1607 | |
|
1608 | 0 | if (!toggle_check || ((x != pts[i].x) || (y != pts[i].y))) { |
1609 | 0 | if (toggle) { |
1610 | 0 | e = gf_path_add_line_to(dashed, x, y); |
1611 | 0 | if (e) goto err_exit; |
1612 | 0 | } |
1613 | 0 | else { |
1614 | 0 | e = gf_path_add_move_to(dashed, x, y); |
1615 | 0 | if (e) goto err_exit; |
1616 | 0 | } |
1617 | 0 | } |
1618 | | |
1619 | | /* advance to next dash */ |
1620 | 0 | toggle = !toggle; |
1621 | 0 | phase = 0; |
1622 | 0 | offset = next_offset; |
1623 | 0 | dash = gf_path_get_dash(pen, offset, &next_offset); |
1624 | 0 | if (length_scale) dash = gf_mulfix(dash, length_scale); |
1625 | 0 | } |
1626 | | /* end of line in subpath is next */ |
1627 | 0 | else { |
1628 | 0 | phase += dists[i] - dist; |
1629 | 0 | i ++; |
1630 | 0 | toggle_check = GF_FALSE; |
1631 | 0 | dist = 0; |
1632 | 0 | if (toggle) { |
1633 | 0 | e = gf_path_add_line_to_vec(dashed, &pts[i]); |
1634 | 0 | if (e) goto err_exit; |
1635 | 0 | toggle_check = GF_TRUE; |
1636 | |
|
1637 | 0 | if ( (firstindex>=0) && (i == (nb_pts - 1) && ((firstindex + 1) != (s32) start_ind ) )) { |
1638 | | /*merge if closed path*/ |
1639 | 0 | if ((pts[0].x==pts[nb_pts-1].x) && (pts[0].y==pts[nb_pts-1].y)) { |
1640 | 0 | e = gf_path_mergedashes(dashed, firstindex); |
1641 | 0 | if (e) goto err_exit; |
1642 | 0 | } |
1643 | 0 | } |
1644 | 0 | } |
1645 | 0 | } |
1646 | 0 | } |
1647 | | |
1648 | 0 | err_exit: |
1649 | | // pen->dash_offset = dist; |
1650 | 0 | gf_free(dists); |
1651 | 0 | return GF_OK; |
1652 | 0 | } |
1653 | | |
1654 | | static GF_Path *gf_path_dash(GF_Path *path, GF_PenSettings *pen) |
1655 | 0 | { |
1656 | 0 | u32 i, j, nb_pts; |
1657 | 0 | GF_Point2D *pts; |
1658 | 0 | Fixed length_scale = 0; |
1659 | 0 | Fixed dash_off = pen->dash_offset; |
1660 | 0 | GF_Path *dashed = gf_path_new(); |
1661 | | |
1662 | | |
1663 | | /* calculate line lengths and update offset*/ |
1664 | 0 | if (pen->path_length>0) { |
1665 | 0 | Fixed totaldist = 0; |
1666 | 0 | nb_pts = 0; |
1667 | 0 | for (i=0; i<path->n_contours; i++) { |
1668 | 0 | pts = &path->points[nb_pts]; |
1669 | 0 | nb_pts = 1+path->contours[i] - nb_pts; |
1670 | |
|
1671 | 0 | for (j=0; j<nb_pts-1; j++) { |
1672 | 0 | GF_Point2D diff; |
1673 | 0 | diff.x = pts[j+1].x - pts[j].x; |
1674 | 0 | diff.y = pts[j+1].y - pts[j].y; |
1675 | 0 | totaldist += gf_v2d_len(&diff); |
1676 | 0 | } |
1677 | 0 | nb_pts = 1+path->contours[i]; |
1678 | 0 | } |
1679 | 0 | length_scale = gf_divfix(totaldist, pen->path_length); |
1680 | 0 | pen->dash_offset = gf_mulfix(pen->dash_offset, length_scale); |
1681 | 0 | } |
1682 | |
|
1683 | 0 | nb_pts = 0; |
1684 | 0 | for (i=0; i<path->n_contours; i++) { |
1685 | 0 | pts = &path->points[nb_pts]; |
1686 | 0 | nb_pts = 1+path->contours[i] - nb_pts; |
1687 | 0 | evg_dash_subpath(dashed, pts, nb_pts, pen, length_scale); |
1688 | 0 | nb_pts = 1+path->contours[i]; |
1689 | | // if (length_scale) pen->dash_offset = gf_mulfix(pen->dash_offset, length_scale); |
1690 | 0 | } |
1691 | 0 | pen->dash_offset = dash_off; |
1692 | 0 | dashed->flags |= GF_PATH_FILL_ZERO_NONZERO; |
1693 | 0 | return dashed; |
1694 | 0 | } |
1695 | | |
1696 | | GF_EXPORT |
1697 | | GF_Path *gf_path_get_outline(GF_Path *path, GF_PenSettings pen) |
1698 | 0 | { |
1699 | 0 | s32 error; |
1700 | 0 | GF_Path *outline; |
1701 | 0 | GF_Path *dashed; |
1702 | 0 | GF_Path *scaled; |
1703 | 0 | FT_Stroker stroker; |
1704 | 0 | if (!path || !pen.width) return NULL; |
1705 | | |
1706 | 0 | memset(&stroker, 0, sizeof(stroker)); |
1707 | 0 | stroker.borders[0].start = -1; |
1708 | 0 | stroker.borders[1].start = -1; |
1709 | 0 | stroker.line_cap = pen.cap; |
1710 | 0 | stroker.line_join = pen.join; |
1711 | 0 | stroker.miter_limit = pen.miterLimit; |
1712 | 0 | stroker.radius = pen.width/2; |
1713 | |
|
1714 | 0 | gf_path_flatten(path); |
1715 | |
|
1716 | 0 | scaled = NULL; |
1717 | | /*if not centered, simply scale path...*/ |
1718 | 0 | if (pen.align) { |
1719 | 0 | Fixed sx, sy; |
1720 | 0 | GF_Rect bounds; |
1721 | 0 | gf_path_get_bounds(path, &bounds); |
1722 | 0 | if (pen.align==GF_PATH_LINE_OUTSIDE) { |
1723 | 0 | sx = gf_divfix(bounds.width+pen.width, bounds.width); |
1724 | 0 | sy = gf_divfix(bounds.height+pen.width, bounds.height); |
1725 | 0 | } else { |
1726 | | /*note: this may result in negative scaling, not our pb but the author's one*/ |
1727 | 0 | sx = gf_divfix(bounds.width-pen.width, bounds.width); |
1728 | 0 | sy = gf_divfix(bounds.height-pen.width, bounds.height); |
1729 | 0 | } |
1730 | 0 | if (sx && sy) { |
1731 | 0 | u32 i; |
1732 | 0 | scaled = gf_path_clone(path); |
1733 | 0 | for (i=0; i<scaled->n_points; i++) { |
1734 | 0 | scaled->points[i].x = gf_mulfix(scaled->points[i].x, sx); |
1735 | 0 | scaled->points[i].y = gf_mulfix(scaled->points[i].y, sy); |
1736 | 0 | } |
1737 | 0 | path = scaled; |
1738 | 0 | } |
1739 | 0 | } |
1740 | | |
1741 | | /*if dashing, first flatten path then dash all segments*/ |
1742 | 0 | dashed = NULL; |
1743 | | /*security, seen in some SVG files*/ |
1744 | 0 | if (pen.dash_set && (pen.dash_set->num_dash==1) && (pen.dash_set->dashes[0]==0)) pen.dash = GF_DASH_STYLE_PLAIN; |
1745 | 0 | if (pen.dash) { |
1746 | 0 | GF_Path *flat; |
1747 | 0 | flat = gf_path_get_flatten(path); |
1748 | 0 | if (!flat) return NULL; |
1749 | 0 | dashed = gf_path_dash(flat, &pen); |
1750 | 0 | gf_path_del(flat); |
1751 | 0 | if (!dashed) return NULL; |
1752 | 0 | path = dashed; |
1753 | 0 | } |
1754 | | |
1755 | 0 | outline = NULL; |
1756 | 0 | error = FT_Stroker_ParseOutline(&stroker, path); |
1757 | 0 | if (!error) { |
1758 | 0 | u32 nb_pt, nb_cnt; |
1759 | 0 | error = FT_Stroker_GetCounts(&stroker, &nb_pt, &nb_cnt); |
1760 | 0 | if (!error) { |
1761 | 0 | outline = gf_path_new(); |
1762 | 0 | if (nb_pt) { |
1763 | 0 | FT_StrokeBorder sborder; |
1764 | 0 | outline->points = (GF_Point2D *) gf_malloc(sizeof(GF_Point2D)*nb_pt); |
1765 | 0 | outline->tags = (u8 *) gf_malloc(sizeof(u8)*nb_pt); |
1766 | 0 | outline->contours = (u32 *) gf_malloc(sizeof(u32)*nb_cnt); |
1767 | 0 | outline->n_alloc_points = nb_pt; |
1768 | 0 | sborder = &stroker.borders[0]; |
1769 | 0 | if (sborder->valid ) ft_stroke_border_export(sborder, outline); |
1770 | 0 | sborder = &stroker.borders[1]; |
1771 | | /*if left border is valid this is a closed path, used odd/even rule - we will have issues at recovering |
1772 | | segments...*/ |
1773 | 0 | if (sborder->valid && sborder->num_points) { |
1774 | 0 | ft_stroke_border_export(sborder, outline); |
1775 | 0 | } |
1776 | | /*otherwise this is an open path, use zero/non-zero*/ |
1777 | 0 | else { |
1778 | 0 | outline->flags |= GF_PATH_FILL_ZERO_NONZERO; |
1779 | 0 | } |
1780 | 0 | } |
1781 | 0 | outline->flags |= GF_PATH_BBOX_DIRTY; |
1782 | | |
1783 | | /*our caps are cubic bezier!!*/ |
1784 | 0 | if ( (path->flags & GF_PATH_FLATTENED) && (pen.cap!=GF_LINE_CAP_ROUND) && (pen.join!=GF_LINE_JOIN_ROUND) ) |
1785 | 0 | outline->flags |= GF_PATH_FLATTENED; |
1786 | 0 | } |
1787 | 0 | } |
1788 | |
|
1789 | 0 | if (stroker.borders[0].points) gf_free(stroker.borders[0].points); |
1790 | 0 | if (stroker.borders[0].tags) gf_free(stroker.borders[0].tags); |
1791 | 0 | if (stroker.borders[1].points) gf_free(stroker.borders[1].points); |
1792 | 0 | if (stroker.borders[1].tags) gf_free(stroker.borders[1].tags); |
1793 | |
|
1794 | 0 | if (dashed) gf_path_del(dashed); |
1795 | 0 | if (scaled) gf_path_del(scaled); |
1796 | |
|
1797 | 0 | return outline; |
1798 | 0 | } |
1799 | | |
1800 | | #endif //GPAC_DISABLE_EVG |