1/****************************************************************************
2 *
3 * ftstroke.c
4 *
5 * FreeType path stroker (body).
6 *
7 * Copyright (C) 2002-2023 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 <freetype/ftstroke.h>
20#include <freetype/fttrigon.h>
21#include <freetype/ftoutln.h>
22#include <freetype/internal/ftmemory.h>
23#include <freetype/internal/ftdebug.h>
24#include <freetype/internal/ftobjs.h>
25
26
27 /* declare an extern to access `ft_outline_glyph_class' globally */
28 /* allocated in `ftglyph.c' */
29 FT_CALLBACK_TABLE const FT_Glyph_Class ft_outline_glyph_class;
30
31
32 /* documentation is in ftstroke.h */
33
34 FT_EXPORT_DEF( FT_StrokerBorder )
35 FT_Outline_GetInsideBorder( FT_Outline* outline )
36 {
37 FT_Orientation o = FT_Outline_Get_Orientation( outline );
38
39
40 return o == FT_ORIENTATION_TRUETYPE ? FT_STROKER_BORDER_RIGHT
41 : FT_STROKER_BORDER_LEFT;
42 }
43
44
45 /* documentation is in ftstroke.h */
46
47 FT_EXPORT_DEF( FT_StrokerBorder )
48 FT_Outline_GetOutsideBorder( FT_Outline* outline )
49 {
50 FT_Orientation o = FT_Outline_Get_Orientation( outline );
51
52
53 return o == FT_ORIENTATION_TRUETYPE ? FT_STROKER_BORDER_LEFT
54 : FT_STROKER_BORDER_RIGHT;
55 }
56
57
58 /*************************************************************************/
59 /*************************************************************************/
60 /***** *****/
61 /***** BEZIER COMPUTATIONS *****/
62 /***** *****/
63 /*************************************************************************/
64 /*************************************************************************/
65
66#define FT_SMALL_CONIC_THRESHOLD ( FT_ANGLE_PI / 6 )
67#define FT_SMALL_CUBIC_THRESHOLD ( FT_ANGLE_PI / 8 )
68
69#define FT_EPSILON 2
70
71#define FT_IS_SMALL( x ) ( (x) > -FT_EPSILON && (x) < FT_EPSILON )
72
73
74 static FT_Pos
75 ft_pos_abs( FT_Pos x )
76 {
77 return x >= 0 ? x : -x;
78 }
79
80
81 static void
82 ft_conic_split( FT_Vector* base )
83 {
84 FT_Pos a, b;
85
86
87 base[4].x = base[2].x;
88 a = base[0].x + base[1].x;
89 b = base[1].x + base[2].x;
90 base[3].x = b >> 1;
91 base[2].x = ( a + b ) >> 2;
92 base[1].x = a >> 1;
93
94 base[4].y = base[2].y;
95 a = base[0].y + base[1].y;
96 b = base[1].y + base[2].y;
97 base[3].y = b >> 1;
98 base[2].y = ( a + b ) >> 2;
99 base[1].y = a >> 1;
100 }
101
102
103 static FT_Bool
104 ft_conic_is_small_enough( FT_Vector* base,
105 FT_Angle *angle_in,
106 FT_Angle *angle_out )
107 {
108 FT_Vector d1, d2;
109 FT_Angle theta;
110 FT_Int close1, close2;
111
112
113 d1.x = base[1].x - base[2].x;
114 d1.y = base[1].y - base[2].y;
115 d2.x = base[0].x - base[1].x;
116 d2.y = base[0].y - base[1].y;
117
118 close1 = FT_IS_SMALL( d1.x ) && FT_IS_SMALL( d1.y );
119 close2 = FT_IS_SMALL( d2.x ) && FT_IS_SMALL( d2.y );
120
121 if ( close1 )
122 {
123 if ( close2 )
124 {
125 /* basically a point; */
126 /* do nothing to retain original direction */
127 }
128 else
129 {
130 *angle_in =
131 *angle_out = FT_Atan2( d2.x, d2.y );
132 }
133 }
134 else /* !close1 */
135 {
136 if ( close2 )
137 {
138 *angle_in =
139 *angle_out = FT_Atan2( d1.x, d1.y );
140 }
141 else
142 {
143 *angle_in = FT_Atan2( d1.x, d1.y );
144 *angle_out = FT_Atan2( d2.x, d2.y );
145 }
146 }
147
148 theta = ft_pos_abs( FT_Angle_Diff( *angle_in, *angle_out ) );
149
150 return FT_BOOL( theta < FT_SMALL_CONIC_THRESHOLD );
151 }
152
153
154 static void
155 ft_cubic_split( FT_Vector* base )
156 {
157 FT_Pos a, b, c;
158
159
160 base[6].x = base[3].x;
161 a = base[0].x + base[1].x;
162 b = base[1].x + base[2].x;
163 c = base[2].x + base[3].x;
164 base[5].x = c >> 1;
165 c += b;
166 base[4].x = c >> 2;
167 base[1].x = a >> 1;
168 a += b;
169 base[2].x = a >> 2;
170 base[3].x = ( a + c ) >> 3;
171
172 base[6].y = base[3].y;
173 a = base[0].y + base[1].y;
174 b = base[1].y + base[2].y;
175 c = base[2].y + base[3].y;
176 base[5].y = c >> 1;
177 c += b;
178 base[4].y = c >> 2;
179 base[1].y = a >> 1;
180 a += b;
181 base[2].y = a >> 2;
182 base[3].y = ( a + c ) >> 3;
183 }
184
185
186 /* Return the average of `angle1' and `angle2'. */
187 /* This gives correct result even if `angle1' and `angle2' */
188 /* have opposite signs. */
189 static FT_Angle
190 ft_angle_mean( FT_Angle angle1,
191 FT_Angle angle2 )
192 {
193 return angle1 + FT_Angle_Diff( angle1, angle2 ) / 2;
194 }
195
196
197 static FT_Bool
198 ft_cubic_is_small_enough( FT_Vector* base,
199 FT_Angle *angle_in,
200 FT_Angle *angle_mid,
201 FT_Angle *angle_out )
202 {
203 FT_Vector d1, d2, d3;
204 FT_Angle theta1, theta2;
205 FT_Int close1, close2, close3;
206
207
208 d1.x = base[2].x - base[3].x;
209 d1.y = base[2].y - base[3].y;
210 d2.x = base[1].x - base[2].x;
211 d2.y = base[1].y - base[2].y;
212 d3.x = base[0].x - base[1].x;
213 d3.y = base[0].y - base[1].y;
214
215 close1 = FT_IS_SMALL( d1.x ) && FT_IS_SMALL( d1.y );
216 close2 = FT_IS_SMALL( d2.x ) && FT_IS_SMALL( d2.y );
217 close3 = FT_IS_SMALL( d3.x ) && FT_IS_SMALL( d3.y );
218
219 if ( close1 )
220 {
221 if ( close2 )
222 {
223 if ( close3 )
224 {
225 /* basically a point; */
226 /* do nothing to retain original direction */
227 }
228 else /* !close3 */
229 {
230 *angle_in =
231 *angle_mid =
232 *angle_out = FT_Atan2( d3.x, d3.y );
233 }
234 }
235 else /* !close2 */
236 {
237 if ( close3 )
238 {
239 *angle_in =
240 *angle_mid =
241 *angle_out = FT_Atan2( d2.x, d2.y );
242 }
243 else /* !close3 */
244 {
245 *angle_in =
246 *angle_mid = FT_Atan2( d2.x, d2.y );
247 *angle_out = FT_Atan2( d3.x, d3.y );
248 }
249 }
250 }
251 else /* !close1 */
252 {
253 if ( close2 )
254 {
255 if ( close3 )
256 {
257 *angle_in =
258 *angle_mid =
259 *angle_out = FT_Atan2( d1.x, d1.y );
260 }
261 else /* !close3 */
262 {
263 *angle_in = FT_Atan2( d1.x, d1.y );
264 *angle_out = FT_Atan2( d3.x, d3.y );
265 *angle_mid = ft_angle_mean( *angle_in, *angle_out );
266 }
267 }
268 else /* !close2 */
269 {
270 if ( close3 )
271 {
272 *angle_in = FT_Atan2( d1.x, d1.y );
273 *angle_mid =
274 *angle_out = FT_Atan2( d2.x, d2.y );
275 }
276 else /* !close3 */
277 {
278 *angle_in = FT_Atan2( d1.x, d1.y );
279 *angle_mid = FT_Atan2( d2.x, d2.y );
280 *angle_out = FT_Atan2( d3.x, d3.y );
281 }
282 }
283 }
284
285 theta1 = ft_pos_abs( FT_Angle_Diff( *angle_in, *angle_mid ) );
286 theta2 = ft_pos_abs( FT_Angle_Diff( *angle_mid, *angle_out ) );
287
288 return FT_BOOL( theta1 < FT_SMALL_CUBIC_THRESHOLD &&
289 theta2 < FT_SMALL_CUBIC_THRESHOLD );
290 }
291
292
293 /*************************************************************************/
294 /*************************************************************************/
295 /***** *****/
296 /***** STROKE BORDERS *****/
297 /***** *****/
298 /*************************************************************************/
299 /*************************************************************************/
300
301 typedef enum FT_StrokeTags_
302 {
303 FT_STROKE_TAG_ON = 1, /* on-curve point */
304 FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */
305 FT_STROKE_TAG_BEGIN = 4, /* sub-path start */
306 FT_STROKE_TAG_END = 8 /* sub-path end */
307
308 } FT_StrokeTags;
309
310#define FT_STROKE_TAG_BEGIN_END ( FT_STROKE_TAG_BEGIN | FT_STROKE_TAG_END )
311
312 typedef struct FT_StrokeBorderRec_
313 {
314 FT_UInt num_points;
315 FT_UInt max_points;
316 FT_Vector* points;
317 FT_Byte* tags;
318 FT_Bool movable; /* TRUE for ends of lineto borders */
319 FT_Int start; /* index of current sub-path start point */
320 FT_Memory memory;
321 FT_Bool valid;
322
323 } FT_StrokeBorderRec, *FT_StrokeBorder;
324
325
326 static FT_Error
327 ft_stroke_border_grow( FT_StrokeBorder border,
328 FT_UInt new_points )
329 {
330 FT_UInt old_max = border->max_points;
331 FT_UInt new_max = border->num_points + new_points;
332 FT_Error error = FT_Err_Ok;
333
334
335 if ( new_max > old_max )
336 {
337 FT_UInt cur_max = old_max;
338 FT_Memory memory = border->memory;
339
340
341 while ( cur_max < new_max )
342 cur_max += ( cur_max >> 1 ) + 16;
343
344 if ( FT_RENEW_ARRAY( border->points, old_max, cur_max ) ||
345 FT_RENEW_ARRAY( border->tags, old_max, cur_max ) )
346 goto Exit;
347
348 border->max_points = cur_max;
349 }
350
351 Exit:
352 return error;
353 }
354
355
356 static void
357 ft_stroke_border_close( FT_StrokeBorder border,
358 FT_Bool reverse )
359 {
360 FT_UInt start = (FT_UInt)border->start;
361 FT_UInt count = border->num_points;
362
363
364 FT_ASSERT( border->start >= 0 );
365
366 /* don't record empty paths! */
367 if ( count <= start + 1U )
368 border->num_points = start;
369 else
370 {
371 /* copy the last point to the start of this sub-path, since */
372 /* it contains the `adjusted' starting coordinates */
373 border->num_points = --count;
374 border->points[start] = border->points[count];
375 border->tags[start] = border->tags[count];
376
377 if ( reverse )
378 {
379 /* reverse the points */
380 {
381 FT_Vector* vec1 = border->points + start + 1;
382 FT_Vector* vec2 = border->points + count - 1;
383
384
385 for ( ; vec1 < vec2; vec1++, vec2-- )
386 {
387 FT_Vector tmp;
388
389
390 tmp = *vec1;
391 *vec1 = *vec2;
392 *vec2 = tmp;
393 }
394 }
395
396 /* then the tags */
397 {
398 FT_Byte* tag1 = border->tags + start + 1;
399 FT_Byte* tag2 = border->tags + count - 1;
400
401
402 for ( ; tag1 < tag2; tag1++, tag2-- )
403 {
404 FT_Byte tmp;
405
406
407 tmp = *tag1;
408 *tag1 = *tag2;
409 *tag2 = tmp;
410 }
411 }
412 }
413
414 border->tags[start ] |= FT_STROKE_TAG_BEGIN;
415 border->tags[count - 1] |= FT_STROKE_TAG_END;
416 }
417
418 border->start = -1;
419 border->movable = FALSE;
420 }
421
422
423 static FT_Error
424 ft_stroke_border_lineto( FT_StrokeBorder border,
425 FT_Vector* to,
426 FT_Bool movable )
427 {
428 FT_Error error = FT_Err_Ok;
429
430
431 FT_ASSERT( border->start >= 0 );
432
433 if ( border->movable )
434 {
435 /* move last point */
436 border->points[border->num_points - 1] = *to;
437 }
438 else
439 {
440 /* don't add zero-length lineto, but always add moveto */
441 if ( border->num_points > (FT_UInt)border->start &&
442 FT_IS_SMALL( border->points[border->num_points - 1].x - to->x ) &&
443 FT_IS_SMALL( border->points[border->num_points - 1].y - to->y ) )
444 return error;
445
446 /* add one point */
447 error = ft_stroke_border_grow( border, 1 );
448 if ( !error )
449 {
450 FT_Vector* vec = border->points + border->num_points;
451 FT_Byte* tag = border->tags + border->num_points;
452
453
454 vec[0] = *to;
455 tag[0] = FT_STROKE_TAG_ON;
456
457 border->num_points += 1;
458 }
459 }
460 border->movable = movable;
461 return error;
462 }
463
464
465 static FT_Error
466 ft_stroke_border_conicto( FT_StrokeBorder border,
467 FT_Vector* control,
468 FT_Vector* to )
469 {
470 FT_Error error;
471
472
473 FT_ASSERT( border->start >= 0 );
474
475 error = ft_stroke_border_grow( border, 2 );
476 if ( !error )
477 {
478 FT_Vector* vec = border->points + border->num_points;
479 FT_Byte* tag = border->tags + border->num_points;
480
481
482 vec[0] = *control;
483 vec[1] = *to;
484
485 tag[0] = 0;
486 tag[1] = FT_STROKE_TAG_ON;
487
488 border->num_points += 2;
489 }
490
491 border->movable = FALSE;
492
493 return error;
494 }
495
496
497 static FT_Error
498 ft_stroke_border_cubicto( FT_StrokeBorder border,
499 FT_Vector* control1,
500 FT_Vector* control2,
501 FT_Vector* to )
502 {
503 FT_Error error;
504
505
506 FT_ASSERT( border->start >= 0 );
507
508 error = ft_stroke_border_grow( border, 3 );
509 if ( !error )
510 {
511 FT_Vector* vec = border->points + border->num_points;
512 FT_Byte* tag = border->tags + border->num_points;
513
514
515 vec[0] = *control1;
516 vec[1] = *control2;
517 vec[2] = *to;
518
519 tag[0] = FT_STROKE_TAG_CUBIC;
520 tag[1] = FT_STROKE_TAG_CUBIC;
521 tag[2] = FT_STROKE_TAG_ON;
522
523 border->num_points += 3;
524 }
525
526 border->movable = FALSE;
527
528 return error;
529 }
530
531
532#define FT_ARC_CUBIC_ANGLE ( FT_ANGLE_PI / 2 )
533
534
535 static FT_Error
536 ft_stroke_border_arcto( FT_StrokeBorder border,
537 FT_Vector* center,
538 FT_Fixed radius,
539 FT_Angle angle_start,
540 FT_Angle angle_diff )
541 {
542 FT_Fixed coef;
543 FT_Vector a0, a1, a2, a3;
544 FT_Int i, arcs = 1;
545 FT_Error error = FT_Err_Ok;
546
547
548 /* number of cubic arcs to draw */
549 while ( angle_diff > FT_ARC_CUBIC_ANGLE * arcs ||
550 -angle_diff > FT_ARC_CUBIC_ANGLE * arcs )
551 arcs++;
552
553 /* control tangents */
554 coef = FT_Tan( angle_diff / ( 4 * arcs ) );
555 coef += coef / 3;
556
557 /* compute start and first control point */
558 FT_Vector_From_Polar( &a0, radius, angle_start );
559 a1.x = FT_MulFix( -a0.y, coef );
560 a1.y = FT_MulFix( a0.x, coef );
561
562 a0.x += center->x;
563 a0.y += center->y;
564 a1.x += a0.x;
565 a1.y += a0.y;
566
567 for ( i = 1; i <= arcs; i++ )
568 {
569 /* compute end and second control point */
570 FT_Vector_From_Polar( &a3, radius,
571 angle_start + i * angle_diff / arcs );
572 a2.x = FT_MulFix( a3.y, coef );
573 a2.y = FT_MulFix( -a3.x, coef );
574
575 a3.x += center->x;
576 a3.y += center->y;
577 a2.x += a3.x;
578 a2.y += a3.y;
579
580 /* add cubic arc */
581 error = ft_stroke_border_cubicto( border, &a1, &a2, &a3 );
582 if ( error )
583 break;
584
585 /* a0 = a3; */
586 a1.x = a3.x - a2.x + a3.x;
587 a1.y = a3.y - a2.y + a3.y;
588 }
589
590 return error;
591 }
592
593
594 static FT_Error
595 ft_stroke_border_moveto( FT_StrokeBorder border,
596 FT_Vector* to )
597 {
598 /* close current open path if any ? */
599 if ( border->start >= 0 )
600 ft_stroke_border_close( border, FALSE );
601
602 border->start = (FT_Int)border->num_points;
603 border->movable = FALSE;
604
605 return ft_stroke_border_lineto( border, to, FALSE );
606 }
607
608
609 static void
610 ft_stroke_border_init( FT_StrokeBorder border,
611 FT_Memory memory )
612 {
613 border->memory = memory;
614 border->points = NULL;
615 border->tags = NULL;
616
617 border->num_points = 0;
618 border->max_points = 0;
619 border->start = -1;
620 border->valid = FALSE;
621 }
622
623
624 static void
625 ft_stroke_border_reset( FT_StrokeBorder border )
626 {
627 border->num_points = 0;
628 border->start = -1;
629 border->valid = FALSE;
630 }
631
632
633 static void
634 ft_stroke_border_done( FT_StrokeBorder border )
635 {
636 FT_Memory memory = border->memory;
637
638
639 FT_FREE( border->points );
640 FT_FREE( border->tags );
641
642 border->num_points = 0;
643 border->max_points = 0;
644 border->start = -1;
645 border->valid = FALSE;
646 }
647
648
649 static FT_Error
650 ft_stroke_border_get_counts( FT_StrokeBorder border,
651 FT_UInt *anum_points,
652 FT_UInt *anum_contours )
653 {
654 FT_Error error = FT_Err_Ok;
655 FT_UInt num_points = 0;
656 FT_UInt num_contours = 0;
657
658 FT_UInt count = border->num_points;
659 FT_Vector* point = border->points;
660 FT_Byte* tags = border->tags;
661 FT_Int in_contour = 0;
662
663
664 for ( ; count > 0; count--, num_points++, point++, tags++ )
665 {
666 if ( tags[0] & FT_STROKE_TAG_BEGIN )
667 {
668 if ( in_contour != 0 )
669 goto Fail;
670
671 in_contour = 1;
672 }
673 else if ( in_contour == 0 )
674 goto Fail;
675
676 if ( tags[0] & FT_STROKE_TAG_END )
677 {
678 in_contour = 0;
679 num_contours++;
680 }
681 }
682
683 if ( in_contour != 0 )
684 goto Fail;
685
686 border->valid = TRUE;
687
688 Exit:
689 *anum_points = num_points;
690 *anum_contours = num_contours;
691 return error;
692
693 Fail:
694 num_points = 0;
695 num_contours = 0;
696 goto Exit;
697 }
698
699
700 static void
701 ft_stroke_border_export( FT_StrokeBorder border,
702 FT_Outline* outline )
703 {
704 /* copy point locations */
705 if ( border->num_points )
706 FT_ARRAY_COPY( outline->points + outline->n_points,
707 border->points,
708 border->num_points );
709
710 /* copy tags */
711 {
712 FT_UInt count = border->num_points;
713 FT_Byte* read = border->tags;
714 FT_Byte* write = (FT_Byte*)outline->tags + outline->n_points;
715
716
717 for ( ; count > 0; count--, read++, write++ )
718 {
719 if ( *read & FT_STROKE_TAG_ON )
720 *write = FT_CURVE_TAG_ON;
721 else if ( *read & FT_STROKE_TAG_CUBIC )
722 *write = FT_CURVE_TAG_CUBIC;
723 else
724 *write = FT_CURVE_TAG_CONIC;
725 }
726 }
727
728 /* copy contours */
729 {
730 FT_UInt count = border->num_points;
731 FT_Byte* tags = border->tags;
732 FT_Short* write = outline->contours + outline->n_contours;
733 FT_Short idx = (FT_Short)outline->n_points;
734
735
736 for ( ; count > 0; count--, tags++, idx++ )
737 {
738 if ( *tags & FT_STROKE_TAG_END )
739 {
740 *write++ = idx;
741 outline->n_contours++;
742 }
743 }
744 }
745
746 outline->n_points += (short)border->num_points;
747
748 FT_ASSERT( FT_Outline_Check( outline ) == 0 );
749 }
750
751
752 /*************************************************************************/
753 /*************************************************************************/
754 /***** *****/
755 /***** STROKER *****/
756 /***** *****/
757 /*************************************************************************/
758 /*************************************************************************/
759
760#define FT_SIDE_TO_ROTATE( s ) ( FT_ANGLE_PI2 - (s) * FT_ANGLE_PI )
761
762 typedef struct FT_StrokerRec_
763 {
764 FT_Angle angle_in; /* direction into curr join */
765 FT_Angle angle_out; /* direction out of join */
766 FT_Vector center; /* current position */
767 FT_Fixed line_length; /* length of last lineto */
768 FT_Bool first_point; /* is this the start? */
769 FT_Bool subpath_open; /* is the subpath open? */
770 FT_Angle subpath_angle; /* subpath start direction */
771 FT_Vector subpath_start; /* subpath start position */
772 FT_Fixed subpath_line_length; /* subpath start lineto len */
773 FT_Bool handle_wide_strokes; /* use wide strokes logic? */
774
775 FT_Stroker_LineCap line_cap;
776 FT_Stroker_LineJoin line_join;
777 FT_Stroker_LineJoin line_join_saved;
778 FT_Fixed miter_limit;
779 FT_Fixed radius;
780
781 FT_StrokeBorderRec borders[2];
782 FT_Library library;
783
784 } FT_StrokerRec;
785
786
787 /* documentation is in ftstroke.h */
788
789 FT_EXPORT_DEF( FT_Error )
790 FT_Stroker_New( FT_Library library,
791 FT_Stroker *astroker )
792 {
793 FT_Error error; /* assigned in FT_NEW */
794 FT_Memory memory;
795 FT_Stroker stroker = NULL;
796
797
798 if ( !library )
799 return FT_THROW( Invalid_Library_Handle );
800
801 if ( !astroker )
802 return FT_THROW( Invalid_Argument );
803
804 memory = library->memory;
805
806 if ( !FT_NEW( stroker ) )
807 {
808 stroker->library = library;
809
810 ft_stroke_border_init( &stroker->borders[0], memory );
811 ft_stroke_border_init( &stroker->borders[1], memory );
812 }
813
814 *astroker = stroker;
815
816 return error;
817 }
818
819
820 /* documentation is in ftstroke.h */
821
822 FT_EXPORT_DEF( void )
823 FT_Stroker_Set( FT_Stroker stroker,
824 FT_Fixed radius,
825 FT_Stroker_LineCap line_cap,
826 FT_Stroker_LineJoin line_join,
827 FT_Fixed miter_limit )
828 {
829 if ( !stroker )
830 return;
831
832 stroker->radius = radius;
833 stroker->line_cap = line_cap;
834 stroker->line_join = line_join;
835 stroker->miter_limit = miter_limit;
836
837 /* ensure miter limit has sensible value */
838 if ( stroker->miter_limit < 0x10000L )
839 stroker->miter_limit = 0x10000L;
840
841 /* save line join style: */
842 /* line join style can be temporarily changed when stroking curves */
843 stroker->line_join_saved = line_join;
844
845 FT_Stroker_Rewind( stroker );
846 }
847
848
849 /* documentation is in ftstroke.h */
850
851 FT_EXPORT_DEF( void )
852 FT_Stroker_Rewind( FT_Stroker stroker )
853 {
854 if ( stroker )
855 {
856 ft_stroke_border_reset( &stroker->borders[0] );
857 ft_stroke_border_reset( &stroker->borders[1] );
858 }
859 }
860
861
862 /* documentation is in ftstroke.h */
863
864 FT_EXPORT_DEF( void )
865 FT_Stroker_Done( FT_Stroker stroker )
866 {
867 if ( stroker )
868 {
869 FT_Memory memory = stroker->library->memory;
870
871
872 ft_stroke_border_done( &stroker->borders[0] );
873 ft_stroke_border_done( &stroker->borders[1] );
874
875 stroker->library = NULL;
876 FT_FREE( stroker );
877 }
878 }
879
880
881 /* create a circular arc at a corner or cap */
882 static FT_Error
883 ft_stroker_arcto( FT_Stroker stroker,
884 FT_Int side )
885 {
886 FT_Angle total, rotate;
887 FT_Fixed radius = stroker->radius;
888 FT_Error error = FT_Err_Ok;
889 FT_StrokeBorder border = stroker->borders + side;
890
891
892 rotate = FT_SIDE_TO_ROTATE( side );
893
894 total = FT_Angle_Diff( stroker->angle_in, stroker->angle_out );
895 if ( total == FT_ANGLE_PI )
896 total = -rotate * 2;
897
898 error = ft_stroke_border_arcto( border,
899 &stroker->center,
900 radius,
901 stroker->angle_in + rotate,
902 total );
903 border->movable = FALSE;
904 return error;
905 }
906
907
908 /* add a cap at the end of an opened path */
909 static FT_Error
910 ft_stroker_cap( FT_Stroker stroker,
911 FT_Angle angle,
912 FT_Int side )
913 {
914 FT_Error error = FT_Err_Ok;
915
916
917 if ( stroker->line_cap == FT_STROKER_LINECAP_ROUND )
918 {
919 /* add a round cap */
920 stroker->angle_in = angle;
921 stroker->angle_out = angle + FT_ANGLE_PI;
922
923 error = ft_stroker_arcto( stroker, side );
924 }
925 else
926 {
927 /* add a square or butt cap */
928 FT_Vector middle, delta;
929 FT_Fixed radius = stroker->radius;
930 FT_StrokeBorder border = stroker->borders + side;
931
932
933 /* compute middle point and first angle point */
934 FT_Vector_From_Polar( &middle, radius, angle );
935 delta.x = side ? middle.y : -middle.y;
936 delta.y = side ? -middle.x : middle.x;
937
938 if ( stroker->line_cap == FT_STROKER_LINECAP_SQUARE )
939 {
940 middle.x += stroker->center.x;
941 middle.y += stroker->center.y;
942 }
943 else /* FT_STROKER_LINECAP_BUTT */
944 {
945 middle.x = stroker->center.x;
946 middle.y = stroker->center.y;
947 }
948
949 delta.x += middle.x;
950 delta.y += middle.y;
951
952 error = ft_stroke_border_lineto( border, &delta, FALSE );
953 if ( error )
954 goto Exit;
955
956 /* compute second angle point */
957 delta.x = middle.x - delta.x + middle.x;
958 delta.y = middle.y - delta.y + middle.y;
959
960 error = ft_stroke_border_lineto( border, &delta, FALSE );
961 }
962
963 Exit:
964 return error;
965 }
966
967
968 /* process an inside corner, i.e. compute intersection */
969 static FT_Error
970 ft_stroker_inside( FT_Stroker stroker,
971 FT_Int side,
972 FT_Fixed line_length )
973 {
974 FT_StrokeBorder border = stroker->borders + side;
975 FT_Angle phi, theta, rotate;
976 FT_Fixed length;
977 FT_Vector sigma = { 0, 0 };
978 FT_Vector delta;
979 FT_Error error = FT_Err_Ok;
980 FT_Bool intersect; /* use intersection of lines? */
981
982
983 rotate = FT_SIDE_TO_ROTATE( side );
984
985 theta = FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2;
986
987 /* Only intersect borders if between two lineto's and both */
988 /* lines are long enough (line_length is zero for curves). */
989 /* Also avoid U-turns of nearly 180 degree. */
990 if ( !border->movable || line_length == 0 ||
991 theta > 0x59C000 || theta < -0x59C000 )
992 intersect = FALSE;
993 else
994 {
995 /* compute minimum required length of lines */
996 FT_Fixed min_length;
997
998
999 FT_Vector_Unit( &sigma, theta );
1000 min_length =
1001 ft_pos_abs( FT_MulDiv( stroker->radius, sigma.y, sigma.x ) );
1002
1003 intersect = FT_BOOL( min_length &&
1004 stroker->line_length >= min_length &&
1005 line_length >= min_length );
1006 }
1007
1008 if ( !intersect )
1009 {
1010 FT_Vector_From_Polar( &delta, stroker->radius,
1011 stroker->angle_out + rotate );
1012 delta.x += stroker->center.x;
1013 delta.y += stroker->center.y;
1014
1015 border->movable = FALSE;
1016 }
1017 else
1018 {
1019 /* compute median angle */
1020 phi = stroker->angle_in + theta + rotate;
1021
1022 length = FT_DivFix( stroker->radius, sigma.x );
1023
1024 FT_Vector_From_Polar( &delta, length, phi );
1025 delta.x += stroker->center.x;
1026 delta.y += stroker->center.y;
1027 }
1028
1029 error = ft_stroke_border_lineto( border, &delta, FALSE );
1030
1031 return error;
1032 }
1033
1034
1035 /* process an outside corner, i.e. compute bevel/miter/round */
1036 static FT_Error
1037 ft_stroker_outside( FT_Stroker stroker,
1038 FT_Int side,
1039 FT_Fixed line_length )
1040 {
1041 FT_StrokeBorder border = stroker->borders + side;
1042 FT_Error error;
1043 FT_Angle rotate;
1044
1045
1046 if ( stroker->line_join == FT_STROKER_LINEJOIN_ROUND )
1047 error = ft_stroker_arcto( stroker, side );
1048 else
1049 {
1050 /* this is a mitered (pointed) or beveled (truncated) corner */
1051 FT_Fixed radius = stroker->radius;
1052 FT_Vector sigma = { 0, 0 };
1053 FT_Angle theta = 0, phi = 0;
1054 FT_Bool bevel, fixed_bevel;
1055
1056
1057 rotate = FT_SIDE_TO_ROTATE( side );
1058
1059 bevel =
1060 FT_BOOL( stroker->line_join == FT_STROKER_LINEJOIN_BEVEL );
1061
1062 fixed_bevel =
1063 FT_BOOL( stroker->line_join != FT_STROKER_LINEJOIN_MITER_VARIABLE );
1064
1065 /* check miter limit first */
1066 if ( !bevel )
1067 {
1068 theta = FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2;
1069
1070 if ( theta == FT_ANGLE_PI2 )
1071 theta = -rotate;
1072
1073 phi = stroker->angle_in + theta + rotate;
1074
1075 FT_Vector_From_Polar( &sigma, stroker->miter_limit, theta );
1076
1077 /* is miter limit exceeded? */
1078 if ( sigma.x < 0x10000L )
1079 {
1080 /* don't create variable bevels for very small deviations; */
1081 /* FT_Sin(x) = 0 for x <= 57 */
1082 if ( fixed_bevel || ft_pos_abs( theta ) > 57 )
1083 bevel = TRUE;
1084 }
1085 }
1086
1087 if ( bevel ) /* this is a bevel (broken angle) */
1088 {
1089 if ( fixed_bevel )
1090 {
1091 /* the outer corners are simply joined together */
1092 FT_Vector delta;
1093
1094
1095 /* add bevel */
1096 FT_Vector_From_Polar( &delta,
1097 radius,
1098 stroker->angle_out + rotate );
1099 delta.x += stroker->center.x;
1100 delta.y += stroker->center.y;
1101
1102 border->movable = FALSE;
1103 error = ft_stroke_border_lineto( border, &delta, FALSE );
1104 }
1105 else /* variable bevel or clipped miter */
1106 {
1107 /* the miter is truncated */
1108 FT_Vector middle, delta;
1109 FT_Fixed coef;
1110
1111
1112 /* compute middle point and first angle point */
1113 FT_Vector_From_Polar( &middle,
1114 FT_MulFix( radius, stroker->miter_limit ),
1115 phi );
1116
1117 coef = FT_DivFix( 0x10000L - sigma.x, sigma.y );
1118 delta.x = FT_MulFix( middle.y, coef );
1119 delta.y = FT_MulFix( -middle.x, coef );
1120
1121 middle.x += stroker->center.x;
1122 middle.y += stroker->center.y;
1123 delta.x += middle.x;
1124 delta.y += middle.y;
1125
1126 error = ft_stroke_border_lineto( border, &delta, FALSE );
1127 if ( error )
1128 goto Exit;
1129
1130 /* compute second angle point */
1131 delta.x = middle.x - delta.x + middle.x;
1132 delta.y = middle.y - delta.y + middle.y;
1133
1134 error = ft_stroke_border_lineto( border, &delta, FALSE );
1135 if ( error )
1136 goto Exit;
1137
1138 /* finally, add an end point; only needed if not lineto */
1139 /* (line_length is zero for curves) */
1140 if ( line_length == 0 )
1141 {
1142 FT_Vector_From_Polar( &delta,
1143 radius,
1144 stroker->angle_out + rotate );
1145
1146 delta.x += stroker->center.x;
1147 delta.y += stroker->center.y;
1148
1149 error = ft_stroke_border_lineto( border, &delta, FALSE );
1150 }
1151 }
1152 }
1153 else /* this is a miter (intersection) */
1154 {
1155 FT_Fixed length;
1156 FT_Vector delta;
1157
1158
1159 length = FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x );
1160
1161 FT_Vector_From_Polar( &delta, length, phi );
1162 delta.x += stroker->center.x;
1163 delta.y += stroker->center.y;
1164
1165 error = ft_stroke_border_lineto( border, &delta, FALSE );
1166 if ( error )
1167 goto Exit;
1168
1169 /* now add an end point; only needed if not lineto */
1170 /* (line_length is zero for curves) */
1171 if ( line_length == 0 )
1172 {
1173 FT_Vector_From_Polar( &delta,
1174 stroker->radius,
1175 stroker->angle_out + rotate );
1176 delta.x += stroker->center.x;
1177 delta.y += stroker->center.y;
1178
1179 error = ft_stroke_border_lineto( border, &delta, FALSE );
1180 }
1181 }
1182 }
1183
1184 Exit:
1185 return error;
1186 }
1187
1188
1189 static FT_Error
1190 ft_stroker_process_corner( FT_Stroker stroker,
1191 FT_Fixed line_length )
1192 {
1193 FT_Error error = FT_Err_Ok;
1194 FT_Angle turn;
1195 FT_Int inside_side;
1196
1197
1198 turn = FT_Angle_Diff( stroker->angle_in, stroker->angle_out );
1199
1200 /* no specific corner processing is required if the turn is 0 */
1201 if ( turn == 0 )
1202 goto Exit;
1203
1204 /* when we turn to the right, the inside side is 0 */
1205 /* otherwise, the inside side is 1 */
1206 inside_side = ( turn < 0 );
1207
1208 /* process the inside side */
1209 error = ft_stroker_inside( stroker, inside_side, line_length );
1210 if ( error )
1211 goto Exit;
1212
1213 /* process the outside side */
1214 error = ft_stroker_outside( stroker, !inside_side, line_length );
1215
1216 Exit:
1217 return error;
1218 }
1219
1220
1221 /* add two points to the left and right borders corresponding to the */
1222 /* start of the subpath */
1223 static FT_Error
1224 ft_stroker_subpath_start( FT_Stroker stroker,
1225 FT_Angle start_angle,
1226 FT_Fixed line_length )
1227 {
1228 FT_Vector delta;
1229 FT_Vector point;
1230 FT_Error error;
1231 FT_StrokeBorder border;
1232
1233
1234 FT_Vector_From_Polar( &delta, stroker->radius,
1235 start_angle + FT_ANGLE_PI2 );
1236
1237 point.x = stroker->center.x + delta.x;
1238 point.y = stroker->center.y + delta.y;
1239
1240 border = stroker->borders;
1241 error = ft_stroke_border_moveto( border, &point );
1242 if ( error )
1243 goto Exit;
1244
1245 point.x = stroker->center.x - delta.x;
1246 point.y = stroker->center.y - delta.y;
1247
1248 border++;
1249 error = ft_stroke_border_moveto( border, &point );
1250
1251 /* save angle, position, and line length for last join */
1252 /* (line_length is zero for curves) */
1253 stroker->subpath_angle = start_angle;
1254 stroker->first_point = FALSE;
1255 stroker->subpath_line_length = line_length;
1256
1257 Exit:
1258 return error;
1259 }
1260
1261
1262 /* documentation is in ftstroke.h */
1263
1264 FT_EXPORT_DEF( FT_Error )
1265 FT_Stroker_LineTo( FT_Stroker stroker,
1266 FT_Vector* to )
1267 {
1268 FT_Error error = FT_Err_Ok;
1269 FT_StrokeBorder border;
1270 FT_Vector delta;
1271 FT_Angle angle;
1272 FT_Int side;
1273 FT_Fixed line_length;
1274
1275
1276 if ( !stroker || !to )
1277 return FT_THROW( Invalid_Argument );
1278
1279 delta.x = to->x - stroker->center.x;
1280 delta.y = to->y - stroker->center.y;
1281
1282 /* a zero-length lineto is a no-op; avoid creating a spurious corner */
1283 if ( delta.x == 0 && delta.y == 0 )
1284 goto Exit;
1285
1286 /* compute length of line */
1287 line_length = FT_Vector_Length( &delta );
1288
1289 angle = FT_Atan2( delta.x, delta.y );
1290 FT_Vector_From_Polar( &delta, stroker->radius, angle + FT_ANGLE_PI2 );
1291
1292 /* process corner if necessary */
1293 if ( stroker->first_point )
1294 {
1295 /* This is the first segment of a subpath. We need to */
1296 /* add a point to each border at their respective starting */
1297 /* point locations. */
1298 error = ft_stroker_subpath_start( stroker, angle, line_length );
1299 if ( error )
1300 goto Exit;
1301 }
1302 else
1303 {
1304 /* process the current corner */
1305 stroker->angle_out = angle;
1306 error = ft_stroker_process_corner( stroker, line_length );
1307 if ( error )
1308 goto Exit;
1309 }
1310
1311 /* now add a line segment to both the `inside' and `outside' paths */
1312 for ( border = stroker->borders, side = 1; side >= 0; side--, border++ )
1313 {
1314 FT_Vector point;
1315
1316
1317 point.x = to->x + delta.x;
1318 point.y = to->y + delta.y;
1319
1320 /* the ends of lineto borders are movable */
1321 error = ft_stroke_border_lineto( border, &point, TRUE );
1322 if ( error )
1323 goto Exit;
1324
1325 delta.x = -delta.x;
1326 delta.y = -delta.y;
1327 }
1328
1329 stroker->angle_in = angle;
1330 stroker->center = *to;
1331 stroker->line_length = line_length;
1332
1333 Exit:
1334 return error;
1335 }
1336
1337
1338 /* documentation is in ftstroke.h */
1339
1340 FT_EXPORT_DEF( FT_Error )
1341 FT_Stroker_ConicTo( FT_Stroker stroker,
1342 FT_Vector* control,
1343 FT_Vector* to )
1344 {
1345 FT_Error error = FT_Err_Ok;
1346 FT_Vector bez_stack[34];
1347 FT_Vector* arc;
1348 FT_Vector* limit = bez_stack + 30;
1349 FT_Bool first_arc = TRUE;
1350
1351
1352 if ( !stroker || !control || !to )
1353 {
1354 error = FT_THROW( Invalid_Argument );
1355 goto Exit;
1356 }
1357
1358 /* if all control points are coincident, this is a no-op; */
1359 /* avoid creating a spurious corner */
1360 if ( FT_IS_SMALL( stroker->center.x - control->x ) &&
1361 FT_IS_SMALL( stroker->center.y - control->y ) &&
1362 FT_IS_SMALL( control->x - to->x ) &&
1363 FT_IS_SMALL( control->y - to->y ) )
1364 {
1365 stroker->center = *to;
1366 goto Exit;
1367 }
1368
1369 arc = bez_stack;
1370 arc[0] = *to;
1371 arc[1] = *control;
1372 arc[2] = stroker->center;
1373
1374 while ( arc >= bez_stack )
1375 {
1376 FT_Angle angle_in, angle_out;
1377
1378
1379 /* initialize with current direction */
1380 angle_in = angle_out = stroker->angle_in;
1381
1382 if ( arc < limit &&
1383 !ft_conic_is_small_enough( arc, &angle_in, &angle_out ) )
1384 {
1385 if ( stroker->first_point )
1386 stroker->angle_in = angle_in;
1387
1388 ft_conic_split( arc );
1389 arc += 2;
1390 continue;
1391 }
1392
1393 if ( first_arc )
1394 {
1395 first_arc = FALSE;
1396
1397 /* process corner if necessary */
1398 if ( stroker->first_point )
1399 error = ft_stroker_subpath_start( stroker, angle_in, 0 );
1400 else
1401 {
1402 stroker->angle_out = angle_in;
1403 error = ft_stroker_process_corner( stroker, 0 );
1404 }
1405 }
1406 else if ( ft_pos_abs( FT_Angle_Diff( stroker->angle_in, angle_in ) ) >
1407 FT_SMALL_CONIC_THRESHOLD / 4 )
1408 {
1409 /* if the deviation from one arc to the next is too great, */
1410 /* add a round corner */
1411 stroker->center = arc[2];
1412 stroker->angle_out = angle_in;
1413 stroker->line_join = FT_STROKER_LINEJOIN_ROUND;
1414
1415 error = ft_stroker_process_corner( stroker, 0 );
1416
1417 /* reinstate line join style */
1418 stroker->line_join = stroker->line_join_saved;
1419 }
1420
1421 if ( error )
1422 goto Exit;
1423
1424 /* the arc's angle is small enough; we can add it directly to each */
1425 /* border */
1426 {
1427 FT_Vector ctrl, end;
1428 FT_Angle theta, phi, rotate, alpha0 = 0;
1429 FT_Fixed length;
1430 FT_StrokeBorder border;
1431 FT_Int side;
1432
1433
1434 theta = FT_Angle_Diff( angle_in, angle_out ) / 2;
1435 phi = angle_in + theta;
1436 length = FT_DivFix( stroker->radius, FT_Cos( theta ) );
1437
1438 /* compute direction of original arc */
1439 if ( stroker->handle_wide_strokes )
1440 alpha0 = FT_Atan2( arc[0].x - arc[2].x, arc[0].y - arc[2].y );
1441
1442 for ( border = stroker->borders, side = 0;
1443 side <= 1;
1444 side++, border++ )
1445 {
1446 rotate = FT_SIDE_TO_ROTATE( side );
1447
1448 /* compute control point */
1449 FT_Vector_From_Polar( &ctrl, length, phi + rotate );
1450 ctrl.x += arc[1].x;
1451 ctrl.y += arc[1].y;
1452
1453 /* compute end point */
1454 FT_Vector_From_Polar( &end, stroker->radius, angle_out + rotate );
1455 end.x += arc[0].x;
1456 end.y += arc[0].y;
1457
1458 if ( stroker->handle_wide_strokes )
1459 {
1460 FT_Vector start;
1461 FT_Angle alpha1;
1462
1463
1464 /* determine whether the border radius is greater than the */
1465 /* radius of curvature of the original arc */
1466 start = border->points[border->num_points - 1];
1467
1468 alpha1 = FT_Atan2( end.x - start.x, end.y - start.y );
1469
1470 /* is the direction of the border arc opposite to */
1471 /* that of the original arc? */
1472 if ( ft_pos_abs( FT_Angle_Diff( alpha0, alpha1 ) ) >
1473 FT_ANGLE_PI / 2 )
1474 {
1475 FT_Angle beta, gamma;
1476 FT_Vector bvec, delta;
1477 FT_Fixed blen, sinA, sinB, alen;
1478
1479
1480 /* use the sine rule to find the intersection point */
1481 beta = FT_Atan2( arc[2].x - start.x, arc[2].y - start.y );
1482 gamma = FT_Atan2( arc[0].x - end.x, arc[0].y - end.y );
1483
1484 bvec.x = end.x - start.x;
1485 bvec.y = end.y - start.y;
1486
1487 blen = FT_Vector_Length( &bvec );
1488
1489 sinA = ft_pos_abs( FT_Sin( alpha1 - gamma ) );
1490 sinB = ft_pos_abs( FT_Sin( beta - gamma ) );
1491
1492 alen = FT_MulDiv( blen, sinA, sinB );
1493
1494 FT_Vector_From_Polar( &delta, alen, beta );
1495 delta.x += start.x;
1496 delta.y += start.y;
1497
1498 /* circumnavigate the negative sector backwards */
1499 border->movable = FALSE;
1500 error = ft_stroke_border_lineto( border, &delta, FALSE );
1501 if ( error )
1502 goto Exit;
1503 error = ft_stroke_border_lineto( border, &end, FALSE );
1504 if ( error )
1505 goto Exit;
1506 error = ft_stroke_border_conicto( border, &ctrl, &start );
1507 if ( error )
1508 goto Exit;
1509 /* and then move to the endpoint */
1510 error = ft_stroke_border_lineto( border, &end, FALSE );
1511 if ( error )
1512 goto Exit;
1513
1514 continue;
1515 }
1516
1517 /* else fall through */
1518 }
1519
1520 /* simply add an arc */
1521 error = ft_stroke_border_conicto( border, &ctrl, &end );
1522 if ( error )
1523 goto Exit;
1524 }
1525 }
1526
1527 arc -= 2;
1528
1529 stroker->angle_in = angle_out;
1530 }
1531
1532 stroker->center = *to;
1533 stroker->line_length = 0;
1534
1535 Exit:
1536 return error;
1537 }
1538
1539
1540 /* documentation is in ftstroke.h */
1541
1542 FT_EXPORT_DEF( FT_Error )
1543 FT_Stroker_CubicTo( FT_Stroker stroker,
1544 FT_Vector* control1,
1545 FT_Vector* control2,
1546 FT_Vector* to )
1547 {
1548 FT_Error error = FT_Err_Ok;
1549 FT_Vector bez_stack[37];
1550 FT_Vector* arc;
1551 FT_Vector* limit = bez_stack + 32;
1552 FT_Bool first_arc = TRUE;
1553
1554
1555 if ( !stroker || !control1 || !control2 || !to )
1556 {
1557 error = FT_THROW( Invalid_Argument );
1558 goto Exit;
1559 }
1560
1561 /* if all control points are coincident, this is a no-op; */
1562 /* avoid creating a spurious corner */
1563 if ( FT_IS_SMALL( stroker->center.x - control1->x ) &&
1564 FT_IS_SMALL( stroker->center.y - control1->y ) &&
1565 FT_IS_SMALL( control1->x - control2->x ) &&
1566 FT_IS_SMALL( control1->y - control2->y ) &&
1567 FT_IS_SMALL( control2->x - to->x ) &&
1568 FT_IS_SMALL( control2->y - to->y ) )
1569 {
1570 stroker->center = *to;
1571 goto Exit;
1572 }
1573
1574 arc = bez_stack;
1575 arc[0] = *to;
1576 arc[1] = *control2;
1577 arc[2] = *control1;
1578 arc[3] = stroker->center;
1579
1580 while ( arc >= bez_stack )
1581 {
1582 FT_Angle angle_in, angle_mid, angle_out;
1583
1584
1585 /* initialize with current direction */
1586 angle_in = angle_out = angle_mid = stroker->angle_in;
1587
1588 if ( arc < limit &&
1589 !ft_cubic_is_small_enough( arc, &angle_in,
1590 &angle_mid, &angle_out ) )
1591 {
1592 if ( stroker->first_point )
1593 stroker->angle_in = angle_in;
1594
1595 ft_cubic_split( arc );
1596 arc += 3;
1597 continue;
1598 }
1599
1600 if ( first_arc )
1601 {
1602 first_arc = FALSE;
1603
1604 /* process corner if necessary */
1605 if ( stroker->first_point )
1606 error = ft_stroker_subpath_start( stroker, angle_in, 0 );
1607 else
1608 {
1609 stroker->angle_out = angle_in;
1610 error = ft_stroker_process_corner( stroker, 0 );
1611 }
1612 }
1613 else if ( ft_pos_abs( FT_Angle_Diff( stroker->angle_in, angle_in ) ) >
1614 FT_SMALL_CUBIC_THRESHOLD / 4 )
1615 {
1616 /* if the deviation from one arc to the next is too great, */
1617 /* add a round corner */
1618 stroker->center = arc[3];
1619 stroker->angle_out = angle_in;
1620 stroker->line_join = FT_STROKER_LINEJOIN_ROUND;
1621
1622 error = ft_stroker_process_corner( stroker, 0 );
1623
1624 /* reinstate line join style */
1625 stroker->line_join = stroker->line_join_saved;
1626 }
1627
1628 if ( error )
1629 goto Exit;
1630
1631 /* the arc's angle is small enough; we can add it directly to each */
1632 /* border */
1633 {
1634 FT_Vector ctrl1, ctrl2, end;
1635 FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0;
1636 FT_Fixed length1, length2;
1637 FT_StrokeBorder border;
1638 FT_Int side;
1639
1640
1641 theta1 = FT_Angle_Diff( angle_in, angle_mid ) / 2;
1642 theta2 = FT_Angle_Diff( angle_mid, angle_out ) / 2;
1643 phi1 = ft_angle_mean( angle_in, angle_mid );
1644 phi2 = ft_angle_mean( angle_mid, angle_out );
1645 length1 = FT_DivFix( stroker->radius, FT_Cos( theta1 ) );
1646 length2 = FT_DivFix( stroker->radius, FT_Cos( theta2 ) );
1647
1648 /* compute direction of original arc */
1649 if ( stroker->handle_wide_strokes )
1650 alpha0 = FT_Atan2( arc[0].x - arc[3].x, arc[0].y - arc[3].y );
1651
1652 for ( border = stroker->borders, side = 0;
1653 side <= 1;
1654 side++, border++ )
1655 {
1656 rotate = FT_SIDE_TO_ROTATE( side );
1657
1658 /* compute control points */
1659 FT_Vector_From_Polar( &ctrl1, length1, phi1 + rotate );
1660 ctrl1.x += arc[2].x;
1661 ctrl1.y += arc[2].y;
1662
1663 FT_Vector_From_Polar( &ctrl2, length2, phi2 + rotate );
1664 ctrl2.x += arc[1].x;
1665 ctrl2.y += arc[1].y;
1666
1667 /* compute end point */
1668 FT_Vector_From_Polar( &end, stroker->radius, angle_out + rotate );
1669 end.x += arc[0].x;
1670 end.y += arc[0].y;
1671
1672 if ( stroker->handle_wide_strokes )
1673 {
1674 FT_Vector start;
1675 FT_Angle alpha1;
1676
1677
1678 /* determine whether the border radius is greater than the */
1679 /* radius of curvature of the original arc */
1680 start = border->points[border->num_points - 1];
1681
1682 alpha1 = FT_Atan2( end.x - start.x, end.y - start.y );
1683
1684 /* is the direction of the border arc opposite to */
1685 /* that of the original arc? */
1686 if ( ft_pos_abs( FT_Angle_Diff( alpha0, alpha1 ) ) >
1687 FT_ANGLE_PI / 2 )
1688 {
1689 FT_Angle beta, gamma;
1690 FT_Vector bvec, delta;
1691 FT_Fixed blen, sinA, sinB, alen;
1692
1693
1694 /* use the sine rule to find the intersection point */
1695 beta = FT_Atan2( arc[3].x - start.x, arc[3].y - start.y );
1696 gamma = FT_Atan2( arc[0].x - end.x, arc[0].y - end.y );
1697
1698 bvec.x = end.x - start.x;
1699 bvec.y = end.y - start.y;
1700
1701 blen = FT_Vector_Length( &bvec );
1702
1703 sinA = ft_pos_abs( FT_Sin( alpha1 - gamma ) );
1704 sinB = ft_pos_abs( FT_Sin( beta - gamma ) );
1705
1706 alen = FT_MulDiv( blen, sinA, sinB );
1707
1708 FT_Vector_From_Polar( &delta, alen, beta );
1709 delta.x += start.x;
1710 delta.y += start.y;
1711
1712 /* circumnavigate the negative sector backwards */
1713 border->movable = FALSE;
1714 error = ft_stroke_border_lineto( border, &delta, FALSE );
1715 if ( error )
1716 goto Exit;
1717 error = ft_stroke_border_lineto( border, &end, FALSE );
1718 if ( error )
1719 goto Exit;
1720 error = ft_stroke_border_cubicto( border,
1721 &ctrl2,
1722 &ctrl1,
1723 &start );
1724 if ( error )
1725 goto Exit;
1726 /* and then move to the endpoint */
1727 error = ft_stroke_border_lineto( border, &end, FALSE );
1728 if ( error )
1729 goto Exit;
1730
1731 continue;
1732 }
1733
1734 /* else fall through */
1735 }
1736
1737 /* simply add an arc */
1738 error = ft_stroke_border_cubicto( border, &ctrl1, &ctrl2, &end );
1739 if ( error )
1740 goto Exit;
1741 }
1742 }
1743
1744 arc -= 3;
1745
1746 stroker->angle_in = angle_out;
1747 }
1748
1749 stroker->center = *to;
1750 stroker->line_length = 0;
1751
1752 Exit:
1753 return error;
1754 }
1755
1756
1757 /* documentation is in ftstroke.h */
1758
1759 FT_EXPORT_DEF( FT_Error )
1760 FT_Stroker_BeginSubPath( FT_Stroker stroker,
1761 FT_Vector* to,
1762 FT_Bool open )
1763 {
1764 if ( !stroker || !to )
1765 return FT_THROW( Invalid_Argument );
1766
1767 /* We cannot process the first point, because there is not enough */
1768 /* information regarding its corner/cap. The latter will be processed */
1769 /* in the `FT_Stroker_EndSubPath' routine. */
1770 /* */
1771 stroker->first_point = TRUE;
1772 stroker->center = *to;
1773 stroker->subpath_open = open;
1774
1775 /* Determine if we need to check whether the border radius is greater */
1776 /* than the radius of curvature of a curve, to handle this case */
1777 /* specially. This is only required if bevel joins or butt caps may */
1778 /* be created, because round & miter joins and round & square caps */
1779 /* cover the negative sector created with wide strokes. */
1780 stroker->handle_wide_strokes =
1781 FT_BOOL( stroker->line_join != FT_STROKER_LINEJOIN_ROUND ||
1782 ( stroker->subpath_open &&
1783 stroker->line_cap == FT_STROKER_LINECAP_BUTT ) );
1784
1785 /* record the subpath start point for each border */
1786 stroker->subpath_start = *to;
1787
1788 stroker->angle_in = 0;
1789
1790 return FT_Err_Ok;
1791 }
1792
1793
1794 static FT_Error
1795 ft_stroker_add_reverse_left( FT_Stroker stroker,
1796 FT_Bool open )
1797 {
1798 FT_StrokeBorder right = stroker->borders + 0;
1799 FT_StrokeBorder left = stroker->borders + 1;
1800 FT_Int new_points;
1801 FT_Error error = FT_Err_Ok;
1802
1803
1804 FT_ASSERT( left->start >= 0 );
1805
1806 new_points = (FT_Int)left->num_points - left->start;
1807 if ( new_points > 0 )
1808 {
1809 error = ft_stroke_border_grow( right, (FT_UInt)new_points );
1810 if ( error )
1811 goto Exit;
1812
1813 {
1814 FT_Vector* dst_point = right->points + right->num_points;
1815 FT_Byte* dst_tag = right->tags + right->num_points;
1816 FT_Vector* src_point = left->points + left->num_points - 1;
1817 FT_Byte* src_tag = left->tags + left->num_points - 1;
1818
1819
1820 while ( src_point >= left->points + left->start )
1821 {
1822 *dst_point = *src_point;
1823 *dst_tag = *src_tag;
1824
1825 if ( open )
1826 dst_tag[0] &= ~FT_STROKE_TAG_BEGIN_END;
1827 else
1828 {
1829 FT_Byte ttag =
1830 (FT_Byte)( dst_tag[0] & FT_STROKE_TAG_BEGIN_END );
1831
1832
1833 /* switch begin/end tags if necessary */
1834 if ( ttag == FT_STROKE_TAG_BEGIN ||
1835 ttag == FT_STROKE_TAG_END )
1836 dst_tag[0] ^= FT_STROKE_TAG_BEGIN_END;
1837 }
1838
1839 src_point--;
1840 src_tag--;
1841 dst_point++;
1842 dst_tag++;
1843 }
1844 }
1845
1846 left->num_points = (FT_UInt)left->start;
1847 right->num_points += (FT_UInt)new_points;
1848
1849 right->movable = FALSE;
1850 left->movable = FALSE;
1851 }
1852
1853 Exit:
1854 return error;
1855 }
1856
1857
1858 /* documentation is in ftstroke.h */
1859
1860 /* there's a lot of magic in this function! */
1861 FT_EXPORT_DEF( FT_Error )
1862 FT_Stroker_EndSubPath( FT_Stroker stroker )
1863 {
1864 FT_Error error = FT_Err_Ok;
1865
1866
1867 if ( !stroker )
1868 {
1869 error = FT_THROW( Invalid_Argument );
1870 goto Exit;
1871 }
1872
1873 if ( stroker->subpath_open )
1874 {
1875 FT_StrokeBorder right = stroker->borders;
1876
1877
1878 /* All right, this is an opened path, we need to add a cap between */
1879 /* right & left, add the reverse of left, then add a final cap */
1880 /* between left & right. */
1881 error = ft_stroker_cap( stroker, stroker->angle_in, 0 );
1882 if ( error )
1883 goto Exit;
1884
1885 /* add reversed points from `left' to `right' */
1886 error = ft_stroker_add_reverse_left( stroker, TRUE );
1887 if ( error )
1888 goto Exit;
1889
1890 /* now add the final cap */
1891 stroker->center = stroker->subpath_start;
1892 error = ft_stroker_cap( stroker,
1893 stroker->subpath_angle + FT_ANGLE_PI, 0 );
1894 if ( error )
1895 goto Exit;
1896
1897 /* Now end the right subpath accordingly. The left one is */
1898 /* rewind and doesn't need further processing. */
1899 ft_stroke_border_close( right, FALSE );
1900 }
1901 else
1902 {
1903 /* close the path if needed */
1904 if ( !FT_IS_SMALL( stroker->center.x - stroker->subpath_start.x ) ||
1905 !FT_IS_SMALL( stroker->center.y - stroker->subpath_start.y ) )
1906 {
1907 error = FT_Stroker_LineTo( stroker, &stroker->subpath_start );
1908 if ( error )
1909 goto Exit;
1910 }
1911
1912 /* process the corner */
1913 stroker->angle_out = stroker->subpath_angle;
1914
1915 error = ft_stroker_process_corner( stroker,
1916 stroker->subpath_line_length );
1917 if ( error )
1918 goto Exit;
1919
1920 /* then end our two subpaths */
1921 ft_stroke_border_close( stroker->borders + 0, FALSE );
1922 ft_stroke_border_close( stroker->borders + 1, TRUE );
1923 }
1924
1925 Exit:
1926 return error;
1927 }
1928
1929
1930 /* documentation is in ftstroke.h */
1931
1932 FT_EXPORT_DEF( FT_Error )
1933 FT_Stroker_GetBorderCounts( FT_Stroker stroker,
1934 FT_StrokerBorder border,
1935 FT_UInt *anum_points,
1936 FT_UInt *anum_contours )
1937 {
1938 FT_UInt num_points = 0, num_contours = 0;
1939 FT_Error error;
1940
1941
1942 if ( !stroker || border > 1 )
1943 {
1944 error = FT_THROW( Invalid_Argument );
1945 goto Exit;
1946 }
1947
1948 error = ft_stroke_border_get_counts( stroker->borders + border,
1949 &num_points, &num_contours );
1950 Exit:
1951 if ( anum_points )
1952 *anum_points = num_points;
1953
1954 if ( anum_contours )
1955 *anum_contours = num_contours;
1956
1957 return error;
1958 }
1959
1960
1961 /* documentation is in ftstroke.h */
1962
1963 FT_EXPORT_DEF( FT_Error )
1964 FT_Stroker_GetCounts( FT_Stroker stroker,
1965 FT_UInt *anum_points,
1966 FT_UInt *anum_contours )
1967 {
1968 FT_UInt count1, count2, num_points = 0;
1969 FT_UInt count3, count4, num_contours = 0;
1970 FT_Error error;
1971
1972
1973 if ( !stroker )
1974 {
1975 error = FT_THROW( Invalid_Argument );
1976 goto Exit;
1977 }
1978
1979 error = ft_stroke_border_get_counts( stroker->borders + 0,
1980 &count1, &count2 );
1981 if ( error )
1982 goto Exit;
1983
1984 error = ft_stroke_border_get_counts( stroker->borders + 1,
1985 &count3, &count4 );
1986 if ( error )
1987 goto Exit;
1988
1989 num_points = count1 + count3;
1990 num_contours = count2 + count4;
1991
1992 Exit:
1993 if ( anum_points )
1994 *anum_points = num_points;
1995
1996 if ( anum_contours )
1997 *anum_contours = num_contours;
1998
1999 return error;
2000 }
2001
2002
2003 /* documentation is in ftstroke.h */
2004
2005 FT_EXPORT_DEF( void )
2006 FT_Stroker_ExportBorder( FT_Stroker stroker,
2007 FT_StrokerBorder border,
2008 FT_Outline* outline )
2009 {
2010 if ( !stroker || !outline )
2011 return;
2012
2013 if ( border == FT_STROKER_BORDER_LEFT ||
2014 border == FT_STROKER_BORDER_RIGHT )
2015 {
2016 FT_StrokeBorder sborder = & stroker->borders[border];
2017
2018
2019 if ( sborder->valid )
2020 ft_stroke_border_export( sborder, outline );
2021 }
2022 }
2023
2024
2025 /* documentation is in ftstroke.h */
2026
2027 FT_EXPORT_DEF( void )
2028 FT_Stroker_Export( FT_Stroker stroker,
2029 FT_Outline* outline )
2030 {
2031 FT_Stroker_ExportBorder( stroker, FT_STROKER_BORDER_LEFT, outline );
2032 FT_Stroker_ExportBorder( stroker, FT_STROKER_BORDER_RIGHT, outline );
2033 }
2034
2035
2036 /* documentation is in ftstroke.h */
2037
2038 /*
2039 * The following is very similar to FT_Outline_Decompose, except
2040 * that we do support opened paths, and do not scale the outline.
2041 */
2042 FT_EXPORT_DEF( FT_Error )
2043 FT_Stroker_ParseOutline( FT_Stroker stroker,
2044 FT_Outline* outline,
2045 FT_Bool opened )
2046 {
2047 FT_Vector v_last;
2048 FT_Vector v_control;
2049 FT_Vector v_start;
2050
2051 FT_Vector* point;
2052 FT_Vector* limit;
2053 char* tags;
2054
2055 FT_Error error;
2056
2057 FT_Int n; /* index of contour in outline */
2058 FT_Int first; /* index of first point in contour */
2059 FT_Int last; /* index of last point in contour */
2060
2061 FT_Int tag; /* current point's state */
2062
2063
2064 if ( !outline )
2065 return FT_THROW( Invalid_Outline );
2066
2067 if ( !stroker )
2068 return FT_THROW( Invalid_Argument );
2069
2070 FT_Stroker_Rewind( stroker );
2071
2072 last = -1;
2073 for ( n = 0; n < outline->n_contours; n++ )
2074 {
2075 first = last + 1;
2076 last = outline->contours[n];
2077
2078 /* skip empty points; we don't stroke these */
2079 if ( last <= first )
2080 continue;
2081
2082 limit = outline->points + last;
2083
2084 v_start = outline->points[first];
2085 v_last = outline->points[last];
2086
2087 v_control = v_start;
2088
2089 point = outline->points + first;
2090 tags = outline->tags + first;
2091 tag = FT_CURVE_TAG( tags[0] );
2092
2093 /* A contour cannot start with a cubic control point! */
2094 if ( tag == FT_CURVE_TAG_CUBIC )
2095 goto Invalid_Outline;
2096
2097 /* check first point to determine origin */
2098 if ( tag == FT_CURVE_TAG_CONIC )
2099 {
2100 /* First point is conic control. Yes, this happens. */
2101 if ( FT_CURVE_TAG( outline->tags[last] ) == FT_CURVE_TAG_ON )
2102 {
2103 /* start at last point if it is on the curve */
2104 v_start = v_last;
2105 limit--;
2106 }
2107 else
2108 {
2109 /* if both first and last points are conic, */
2110 /* start at their middle */
2111 v_start.x = ( v_start.x + v_last.x ) / 2;
2112 v_start.y = ( v_start.y + v_last.y ) / 2;
2113 }
2114 point--;
2115 tags--;
2116 }
2117
2118 error = FT_Stroker_BeginSubPath( stroker, &v_start, opened );
2119 if ( error )
2120 goto Exit;
2121
2122 while ( point < limit )
2123 {
2124 point++;
2125 tags++;
2126
2127 tag = FT_CURVE_TAG( tags[0] );
2128 switch ( tag )
2129 {
2130 case FT_CURVE_TAG_ON: /* emit a single line_to */
2131 {
2132 FT_Vector vec;
2133
2134
2135 vec.x = point->x;
2136 vec.y = point->y;
2137
2138 error = FT_Stroker_LineTo( stroker, &vec );
2139 if ( error )
2140 goto Exit;
2141 continue;
2142 }
2143
2144 case FT_CURVE_TAG_CONIC: /* consume conic arcs */
2145 v_control.x = point->x;
2146 v_control.y = point->y;
2147
2148 Do_Conic:
2149 if ( point < limit )
2150 {
2151 FT_Vector vec;
2152 FT_Vector v_middle;
2153
2154
2155 point++;
2156 tags++;
2157 tag = FT_CURVE_TAG( tags[0] );
2158
2159 vec = point[0];
2160
2161 if ( tag == FT_CURVE_TAG_ON )
2162 {
2163 error = FT_Stroker_ConicTo( stroker, &v_control, &vec );
2164 if ( error )
2165 goto Exit;
2166 continue;
2167 }
2168
2169 if ( tag != FT_CURVE_TAG_CONIC )
2170 goto Invalid_Outline;
2171
2172 v_middle.x = ( v_control.x + vec.x ) / 2;
2173 v_middle.y = ( v_control.y + vec.y ) / 2;
2174
2175 error = FT_Stroker_ConicTo( stroker, &v_control, &v_middle );
2176 if ( error )
2177 goto Exit;
2178
2179 v_control = vec;
2180 goto Do_Conic;
2181 }
2182
2183 error = FT_Stroker_ConicTo( stroker, &v_control, &v_start );
2184 goto Close;
2185
2186 default: /* FT_CURVE_TAG_CUBIC */
2187 {
2188 FT_Vector vec1, vec2;
2189
2190
2191 if ( point + 1 > limit ||
2192 FT_CURVE_TAG( tags[1] ) != FT_CURVE_TAG_CUBIC )
2193 goto Invalid_Outline;
2194
2195 point += 2;
2196 tags += 2;
2197
2198 vec1 = point[-2];
2199 vec2 = point[-1];
2200
2201 if ( point <= limit )
2202 {
2203 FT_Vector vec;
2204
2205
2206 vec = point[0];
2207
2208 error = FT_Stroker_CubicTo( stroker, &vec1, &vec2, &vec );
2209 if ( error )
2210 goto Exit;
2211 continue;
2212 }
2213
2214 error = FT_Stroker_CubicTo( stroker, &vec1, &vec2, &v_start );
2215 goto Close;
2216 }
2217 }
2218 }
2219
2220 Close:
2221 if ( error )
2222 goto Exit;
2223
2224 /* don't try to end the path if no segments have been generated */
2225 if ( !stroker->first_point )
2226 {
2227 error = FT_Stroker_EndSubPath( stroker );
2228 if ( error )
2229 goto Exit;
2230 }
2231 }
2232
2233 return FT_Err_Ok;
2234
2235 Exit:
2236 return error;
2237
2238 Invalid_Outline:
2239 return FT_THROW( Invalid_Outline );
2240 }
2241
2242
2243 /* documentation is in ftstroke.h */
2244
2245 FT_EXPORT_DEF( FT_Error )
2246 FT_Glyph_Stroke( FT_Glyph *pglyph,
2247 FT_Stroker stroker,
2248 FT_Bool destroy )
2249 {
2250 FT_Error error = FT_ERR( Invalid_Argument );
2251 FT_Glyph glyph = NULL;
2252
2253
2254 if ( !pglyph )
2255 goto Exit;
2256
2257 glyph = *pglyph;
2258 if ( !glyph || glyph->clazz != &ft_outline_glyph_class )
2259 goto Exit;
2260
2261 {
2262 FT_Glyph copy;
2263
2264
2265 error = FT_Glyph_Copy( glyph, &copy );
2266 if ( error )
2267 goto Exit;
2268
2269 glyph = copy;
2270 }
2271
2272 {
2273 FT_OutlineGlyph oglyph = (FT_OutlineGlyph)glyph;
2274 FT_Outline* outline = &oglyph->outline;
2275 FT_UInt num_points, num_contours;
2276
2277
2278 error = FT_Stroker_ParseOutline( stroker, outline, FALSE );
2279 if ( error )
2280 goto Fail;
2281
2282 FT_Stroker_GetCounts( stroker, &num_points, &num_contours );
2283
2284 FT_Outline_Done( glyph->library, outline );
2285
2286 error = FT_Outline_New( glyph->library,
2287 num_points,
2288 (FT_Int)num_contours,
2289 outline );
2290 if ( error )
2291 goto Fail;
2292
2293 outline->n_points = 0;
2294 outline->n_contours = 0;
2295
2296 FT_Stroker_Export( stroker, outline );
2297 }
2298
2299 if ( destroy )
2300 FT_Done_Glyph( *pglyph );
2301
2302 *pglyph = glyph;
2303 goto Exit;
2304
2305 Fail:
2306 FT_Done_Glyph( glyph );
2307 glyph = NULL;
2308
2309 if ( !destroy )
2310 *pglyph = NULL;
2311
2312 Exit:
2313 return error;
2314 }
2315
2316
2317 /* documentation is in ftstroke.h */
2318
2319 FT_EXPORT_DEF( FT_Error )
2320 FT_Glyph_StrokeBorder( FT_Glyph *pglyph,
2321 FT_Stroker stroker,
2322 FT_Bool inside,
2323 FT_Bool destroy )
2324 {
2325 FT_Error error = FT_ERR( Invalid_Argument );
2326 FT_Glyph glyph = NULL;
2327
2328
2329 if ( !pglyph )
2330 goto Exit;
2331
2332 glyph = *pglyph;
2333 if ( !glyph || glyph->clazz != &ft_outline_glyph_class )
2334 goto Exit;
2335
2336 {
2337 FT_Glyph copy;
2338
2339
2340 error = FT_Glyph_Copy( glyph, &copy );
2341 if ( error )
2342 goto Exit;
2343
2344 glyph = copy;
2345 }
2346
2347 {
2348 FT_OutlineGlyph oglyph = (FT_OutlineGlyph)glyph;
2349 FT_StrokerBorder border;
2350 FT_Outline* outline = &oglyph->outline;
2351 FT_UInt num_points, num_contours;
2352
2353
2354 border = FT_Outline_GetOutsideBorder( outline );
2355 if ( inside )
2356 {
2357 if ( border == FT_STROKER_BORDER_LEFT )
2358 border = FT_STROKER_BORDER_RIGHT;
2359 else
2360 border = FT_STROKER_BORDER_LEFT;
2361 }
2362
2363 error = FT_Stroker_ParseOutline( stroker, outline, FALSE );
2364 if ( error )
2365 goto Fail;
2366
2367 FT_Stroker_GetBorderCounts( stroker, border,
2368 &num_points, &num_contours );
2369
2370 FT_Outline_Done( glyph->library, outline );
2371
2372 error = FT_Outline_New( glyph->library,
2373 num_points,
2374 (FT_Int)num_contours,
2375 outline );
2376 if ( error )
2377 goto Fail;
2378
2379 outline->n_points = 0;
2380 outline->n_contours = 0;
2381
2382 FT_Stroker_ExportBorder( stroker, border, outline );
2383 }
2384
2385 if ( destroy )
2386 FT_Done_Glyph( *pglyph );
2387
2388 *pglyph = glyph;
2389 goto Exit;
2390
2391 Fail:
2392 FT_Done_Glyph( glyph );
2393 glyph = NULL;
2394
2395 if ( !destroy )
2396 *pglyph = NULL;
2397
2398 Exit:
2399 return error;
2400 }
2401
2402
2403/* END */
2404