Imported Upstream version 8.2.2
[platform/upstream/harfbuzz.git] / src / hb-cairo-utils.cc
1 /*
2  * Copyright © 2022  Red Hat, Inc
3  * Copyright © 2021, 2022  Black Foundry
4  *
5  *  This is part of HarfBuzz, a text shaping library.
6  *
7  * Permission is hereby granted, without written agreement and without
8  * license or royalty fees, to use, copy, modify, and distribute this
9  * software and its documentation for any purpose, provided that the
10  * above copyright notice and the following two paragraphs appear in
11  * all copies of this software.
12  *
13  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
14  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
15  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
16  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
17  * DAMAGE.
18  *
19  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
20  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
22  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
23  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
24  *
25  * Google Author(s): Matthias Clasen
26  */
27
28 #include "hb.hh"
29
30 #ifdef HAVE_CAIRO
31
32 #include "hb-cairo-utils.hh"
33
34 #include <cairo.h>
35
36 /* Some routines in this file were ported from BlackRenderer by Black Foundry.
37  * Used by permission to relicense to HarfBuzz license.
38  *
39  * https://github.com/BlackFoundryCom/black-renderer
40  */
41
42 #define PREALLOCATED_COLOR_STOPS 16
43
44 typedef struct {
45   float r, g, b, a;
46 } hb_cairo_color_t;
47
48 static inline cairo_extend_t
49 hb_cairo_extend (hb_paint_extend_t extend)
50 {
51   switch (extend)
52     {
53     case HB_PAINT_EXTEND_PAD: return CAIRO_EXTEND_PAD;
54     case HB_PAINT_EXTEND_REPEAT: return CAIRO_EXTEND_REPEAT;
55     case HB_PAINT_EXTEND_REFLECT: return CAIRO_EXTEND_REFLECT;
56     default: break;
57     }
58
59   return CAIRO_EXTEND_PAD;
60 }
61
62 #ifdef CAIRO_HAS_PNG_FUNCTIONS
63 typedef struct
64 {
65   hb_blob_t *blob;
66   unsigned int offset;
67 } hb_cairo_read_blob_data_t;
68
69 static cairo_status_t
70 hb_cairo_read_blob (void *closure,
71                     unsigned char *data,
72                     unsigned int length)
73 {
74   hb_cairo_read_blob_data_t *r = (hb_cairo_read_blob_data_t *) closure;
75   const char *d;
76   unsigned int size;
77
78   d = hb_blob_get_data (r->blob, &size);
79
80   if (r->offset + length > size)
81     return CAIRO_STATUS_READ_ERROR;
82
83   hb_memcpy (data, d + r->offset, length);
84   r->offset += length;
85
86   return CAIRO_STATUS_SUCCESS;
87 }
88 #endif
89
90 static const cairo_user_data_key_t *_hb_cairo_surface_blob_user_data_key = {0};
91
92 static void
93 _hb_cairo_destroy_blob (void *p)
94 {
95   hb_blob_destroy ((hb_blob_t *) p);
96 }
97
98 hb_bool_t
99 _hb_cairo_paint_glyph_image (hb_cairo_context_t *c,
100                              hb_blob_t *blob,
101                              unsigned width,
102                              unsigned height,
103                              hb_tag_t format,
104                              float slant,
105                              hb_glyph_extents_t *extents)
106 {
107   cairo_t *cr = c->cr;
108
109   if (!extents) /* SVG currently. */
110     return false;
111
112   cairo_surface_t *surface = nullptr;
113
114 #ifdef CAIRO_HAS_PNG_FUNCTIONS
115   if (format == HB_PAINT_IMAGE_FORMAT_PNG)
116   {
117     hb_cairo_read_blob_data_t r;
118     r.blob = blob;
119     r.offset = 0;
120     surface = cairo_image_surface_create_from_png_stream (hb_cairo_read_blob, &r);
121
122     /* For PNG, width,height can be unreliable, as is the case for NotoColorEmoji :(.
123      * Just pull them out of the surface. */
124     width = cairo_image_surface_get_width (surface);
125     height = cairo_image_surface_get_width (surface);
126   }
127   else
128 #endif
129   if (format == HB_PAINT_IMAGE_FORMAT_BGRA)
130   {
131     /* Byte-endian conversion. */
132     unsigned data_size = hb_blob_get_length (blob);
133     if (data_size < width * height * 4)
134       return false;
135
136     unsigned char *data;
137 #ifdef __BYTE_ORDER
138     if (__BYTE_ORDER == __BIG_ENDIAN)
139     {
140       data = (unsigned char *) hb_blob_get_data_writable (blob, nullptr);
141       if (!data)
142         return false;
143
144       unsigned count = width * height * 4;
145       for (unsigned i = 0; i < count; i += 4)
146       {
147         unsigned char b;
148         b = data[i];
149         data[i] = data[i+3];
150         data[i+3] = b;
151         b = data[i+1];
152         data[i+1] = data[i+2];
153         data[i+2] = b;
154       }
155     }
156     else
157 #endif
158       data = (unsigned char *) hb_blob_get_data (blob, nullptr);
159
160     surface = cairo_image_surface_create_for_data (data,
161                                                    CAIRO_FORMAT_ARGB32,
162                                                    width, height,
163                                                    width * 4);
164
165     cairo_surface_set_user_data (surface,
166                                  _hb_cairo_surface_blob_user_data_key,
167                                  hb_blob_reference (blob),
168                                  _hb_cairo_destroy_blob);
169   }
170
171   if (!surface)
172     return false;
173
174   cairo_save (cr);
175   /* this clip is here to work around recording surface limitations */
176   cairo_rectangle (cr,
177                    extents->x_bearing,
178                    extents->y_bearing,
179                    extents->width,
180                    extents->height);
181   cairo_clip (cr);
182
183   cairo_pattern_t *pattern = cairo_pattern_create_for_surface (surface);
184   cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
185
186   cairo_matrix_t matrix = {(double) width, 0, 0, (double) height, 0, 0};
187   cairo_pattern_set_matrix (pattern, &matrix);
188
189   /* Undo slant in the extents and apply it in the context. */
190   extents->width -= extents->height * slant;
191   extents->x_bearing -= extents->y_bearing * slant;
192   cairo_matrix_t cairo_matrix = {1., 0., (double) slant, 1., 0., 0.};
193   cairo_transform (cr, &cairo_matrix);
194
195   cairo_translate (cr, extents->x_bearing, extents->y_bearing);
196   cairo_scale (cr, extents->width, extents->height);
197   cairo_set_source (cr, pattern);
198
199   cairo_paint (cr);
200
201   cairo_pattern_destroy (pattern);
202   cairo_surface_destroy (surface);
203
204   cairo_restore (cr);
205
206   return true;
207 }
208
209 static void
210 _hb_cairo_reduce_anchors (float x0, float y0,
211                           float x1, float y1,
212                           float x2, float y2,
213                           float *xx0, float *yy0,
214                           float *xx1, float *yy1)
215 {
216   float q1x, q1y, q2x, q2y;
217   float s;
218   float k;
219
220   q2x = x2 - x0;
221   q2y = y2 - y0;
222   q1x = x1 - x0;
223   q1y = y1 - y0;
224
225   s = q2x * q2x + q2y * q2y;
226   if (s < 0.000001f)
227     {
228       *xx0 = x0; *yy0 = y0;
229       *xx1 = x1; *yy1 = y1;
230       return;
231     }
232
233   k = (q2x * q1x + q2y * q1y) / s;
234   *xx0 = x0;
235   *yy0 = y0;
236   *xx1 = x1 - k * q2x;
237   *yy1 = y1 - k * q2y;
238 }
239
240 static int
241 _hb_cairo_cmp_color_stop (const void *p1,
242                           const void *p2)
243 {
244   const hb_color_stop_t *c1 = (const hb_color_stop_t *) p1;
245   const hb_color_stop_t *c2 = (const hb_color_stop_t *) p2;
246
247   if (c1->offset < c2->offset)
248     return -1;
249   else if (c1->offset > c2->offset)
250     return 1;
251   else
252     return 0;
253 }
254
255 static void
256 _hb_cairo_normalize_color_line (hb_color_stop_t *stops,
257                                 unsigned int len,
258                                 float *omin,
259                                 float *omax)
260 {
261   float min, max;
262
263   hb_qsort (stops, len, sizeof (hb_color_stop_t), _hb_cairo_cmp_color_stop);
264
265   min = max = stops[0].offset;
266   for (unsigned int i = 0; i < len; i++)
267     {
268       min = hb_min (min, stops[i].offset);
269       max = hb_max (max, stops[i].offset);
270     }
271
272   if (min != max)
273     {
274       for (unsigned int i = 0; i < len; i++)
275         stops[i].offset = (stops[i].offset - min) / (max - min);
276     }
277
278   *omin = min;
279   *omax = max;
280 }
281
282 static bool
283 _hb_cairo_get_color_stops (hb_cairo_context_t *c,
284                            hb_color_line_t *color_line,
285                            unsigned *count,
286                            hb_color_stop_t **stops)
287 {
288   unsigned len = hb_color_line_get_color_stops (color_line, 0, nullptr, nullptr);
289   if (len > *count)
290   {
291     *stops = (hb_color_stop_t *) hb_malloc (len * sizeof (hb_color_stop_t));
292     if (unlikely (!stops))
293       return false;
294   }
295   hb_color_line_get_color_stops (color_line, 0, &len, *stops);
296   for (unsigned i = 0; i < len; i++)
297     if ((*stops)[i].is_foreground)
298     {
299 #ifdef HAVE_CAIRO_USER_SCALED_FONT_GET_FOREGROUND_SOURCE
300       double r, g, b, a;
301       cairo_pattern_t *foreground = cairo_user_scaled_font_get_foreground_source (c->scaled_font);
302       if (cairo_pattern_get_rgba (foreground, &r, &g, &b, &a) == CAIRO_STATUS_SUCCESS)
303         (*stops)[i].color = HB_COLOR (round (b * 255.), round (g * 255.), round (r * 255.),
304                                       round (a * hb_color_get_alpha ((*stops)[i].color)));
305       else
306 #endif
307         (*stops)[i].color = HB_COLOR (0, 0, 0, hb_color_get_alpha ((*stops)[i].color));
308     }
309
310   *count = len;
311   return true;
312 }
313
314 void
315 _hb_cairo_paint_linear_gradient (hb_cairo_context_t *c,
316                                  hb_color_line_t *color_line,
317                                  float x0, float y0,
318                                  float x1, float y1,
319                                  float x2, float y2)
320 {
321   cairo_t *cr = c->cr;
322
323   unsigned int len = PREALLOCATED_COLOR_STOPS;
324   hb_color_stop_t stops_[PREALLOCATED_COLOR_STOPS];
325   hb_color_stop_t *stops = stops_;
326   float xx0, yy0, xx1, yy1;
327   float xxx0, yyy0, xxx1, yyy1;
328   float min, max;
329   cairo_pattern_t *pattern;
330
331   if (unlikely (!_hb_cairo_get_color_stops (c, color_line, &len, &stops)))
332     return;
333   _hb_cairo_normalize_color_line (stops, len, &min, &max);
334
335   _hb_cairo_reduce_anchors (x0, y0, x1, y1, x2, y2, &xx0, &yy0, &xx1, &yy1);
336
337   xxx0 = xx0 + min * (xx1 - xx0);
338   yyy0 = yy0 + min * (yy1 - yy0);
339   xxx1 = xx0 + max * (xx1 - xx0);
340   yyy1 = yy0 + max * (yy1 - yy0);
341
342   pattern = cairo_pattern_create_linear ((double) xxx0, (double) yyy0, (double) xxx1, (double) yyy1);
343   cairo_pattern_set_extend (pattern, hb_cairo_extend (hb_color_line_get_extend (color_line)));
344   for (unsigned int i = 0; i < len; i++)
345     {
346       double r, g, b, a;
347       r = hb_color_get_red (stops[i].color) / 255.;
348       g = hb_color_get_green (stops[i].color) / 255.;
349       b = hb_color_get_blue (stops[i].color) / 255.;
350       a = hb_color_get_alpha (stops[i].color) / 255.;
351       cairo_pattern_add_color_stop_rgba (pattern, (double) stops[i].offset, r, g, b, a);
352     }
353
354   cairo_set_source (cr, pattern);
355   cairo_paint (cr);
356
357   cairo_pattern_destroy (pattern);
358
359   if (stops != stops_)
360     hb_free (stops);
361 }
362
363 void
364 _hb_cairo_paint_radial_gradient (hb_cairo_context_t *c,
365                                  hb_color_line_t *color_line,
366                                  float x0, float y0, float r0,
367                                  float x1, float y1, float r1)
368 {
369   cairo_t *cr = c->cr;
370
371   unsigned int len = PREALLOCATED_COLOR_STOPS;
372   hb_color_stop_t stops_[PREALLOCATED_COLOR_STOPS];
373   hb_color_stop_t *stops = stops_;
374   float min, max;
375   float xx0, yy0, xx1, yy1;
376   float rr0, rr1;
377   cairo_pattern_t *pattern;
378
379   if (unlikely (!_hb_cairo_get_color_stops (c, color_line, &len, &stops)))
380     return;
381   _hb_cairo_normalize_color_line (stops, len, &min, &max);
382
383   xx0 = x0 + min * (x1 - x0);
384   yy0 = y0 + min * (y1 - y0);
385   xx1 = x0 + max * (x1 - x0);
386   yy1 = y0 + max * (y1 - y0);
387   rr0 = r0 + min * (r1 - r0);
388   rr1 = r0 + max * (r1 - r0);
389
390   pattern = cairo_pattern_create_radial ((double) xx0, (double) yy0, (double) rr0, (double) xx1, (double) yy1, (double) rr1);
391   cairo_pattern_set_extend (pattern, hb_cairo_extend (hb_color_line_get_extend (color_line)));
392
393   for (unsigned int i = 0; i < len; i++)
394     {
395       double r, g, b, a;
396       r = hb_color_get_red (stops[i].color) / 255.;
397       g = hb_color_get_green (stops[i].color) / 255.;
398       b = hb_color_get_blue (stops[i].color) / 255.;
399       a = hb_color_get_alpha (stops[i].color) / 255.;
400       cairo_pattern_add_color_stop_rgba (pattern, (double) stops[i].offset, r, g, b, a);
401     }
402
403   cairo_set_source (cr, pattern);
404   cairo_paint (cr);
405
406   cairo_pattern_destroy (pattern);
407
408   if (stops != stops_)
409     hb_free (stops);
410 }
411
412 typedef struct {
413   float x, y;
414 } hb_cairo_point_t;
415
416 static inline float
417 _hb_cairo_interpolate (float f0, float f1, float f)
418 {
419   return f0 + f * (f1 - f0);
420 }
421
422 static inline void
423 _hb_cairo_premultiply (hb_cairo_color_t *c)
424 {
425   c->r *= c->a;
426   c->g *= c->a;
427   c->b *= c->a;
428 }
429
430 static inline void
431 _hb_cairo_unpremultiply (hb_cairo_color_t *c)
432 {
433   if (c->a != 0.f)
434   {
435      c->r /= c->a;
436      c->g /= c->a;
437      c->b /= c->a;
438   }
439 }
440
441 static void
442 _hb_cairo_interpolate_colors (hb_cairo_color_t *c0, hb_cairo_color_t *c1, float k, hb_cairo_color_t *c)
443 {
444   // According to the COLR specification, gradients
445   // should be interpolated in premultiplied form
446   _hb_cairo_premultiply (c0);
447   _hb_cairo_premultiply (c1);
448   c->r = c0->r + k * (c1->r - c0->r);
449   c->g = c0->g + k * (c1->g - c0->g);
450   c->b = c0->b + k * (c1->b - c0->b);
451   c->a = c0->a + k * (c1->a - c0->a);
452   _hb_cairo_unpremultiply (c);
453 }
454
455 static inline float
456 _hb_cairo_dot (hb_cairo_point_t p, hb_cairo_point_t q)
457 {
458   return p.x * q.x + p.y * q.y;
459 }
460
461 static inline hb_cairo_point_t
462 _hb_cairo_normalize (hb_cairo_point_t p)
463 {
464   float len = sqrtf (_hb_cairo_dot (p, p));
465
466   return hb_cairo_point_t { p.x / len, p.y / len };
467 }
468
469 static inline hb_cairo_point_t
470 _hb_cairo_sum (hb_cairo_point_t p, hb_cairo_point_t q)
471 {
472   return hb_cairo_point_t { p.x + q.x, p.y + q.y };
473 }
474
475 static inline hb_cairo_point_t
476 _hb_cairo_difference (hb_cairo_point_t p, hb_cairo_point_t q)
477 {
478   return hb_cairo_point_t { p.x - q.x, p.y - q.y };
479 }
480
481 static inline hb_cairo_point_t
482 _hb_cairo_scale (hb_cairo_point_t p, float f)
483 {
484   return hb_cairo_point_t { p.x * f, p.y * f };
485 }
486
487 typedef struct {
488   hb_cairo_point_t center, p0, c0, c1, p1;
489   hb_cairo_color_t color0, color1;
490 } hb_cairo_patch_t;
491
492 static void
493 _hb_cairo_add_patch (cairo_pattern_t *pattern, hb_cairo_point_t *center, hb_cairo_patch_t *p)
494 {
495   cairo_mesh_pattern_begin_patch (pattern);
496   cairo_mesh_pattern_move_to (pattern, (double) center->x, (double) center->y);
497   cairo_mesh_pattern_line_to (pattern, (double) p->p0.x, (double) p->p0.y);
498   cairo_mesh_pattern_curve_to (pattern,
499                                (double) p->c0.x, (double) p->c0.y,
500                                (double) p->c1.x, (double) p->c1.y,
501                                (double) p->p1.x, (double) p->p1.y);
502   cairo_mesh_pattern_line_to (pattern, (double) center->x, (double) center->y);
503   cairo_mesh_pattern_set_corner_color_rgba (pattern, 0,
504                                             (double) p->color0.r,
505                                             (double) p->color0.g,
506                                             (double) p->color0.b,
507                                             (double) p->color0.a);
508   cairo_mesh_pattern_set_corner_color_rgba (pattern, 1,
509                                             (double) p->color0.r,
510                                             (double) p->color0.g,
511                                             (double) p->color0.b,
512                                             (double) p->color0.a);
513   cairo_mesh_pattern_set_corner_color_rgba (pattern, 2,
514                                             (double) p->color1.r,
515                                             (double) p->color1.g,
516                                             (double) p->color1.b,
517                                             (double) p->color1.a);
518   cairo_mesh_pattern_set_corner_color_rgba (pattern, 3,
519                                             (double) p->color1.r,
520                                             (double) p->color1.g,
521                                             (double) p->color1.b,
522                                             (double) p->color1.a);
523   cairo_mesh_pattern_end_patch (pattern);
524 }
525
526 #define MAX_ANGLE (HB_PI / 8.f)
527
528 static void
529 _hb_cairo_add_sweep_gradient_patches1 (float cx, float cy, float radius,
530                                        float a0, hb_cairo_color_t *c0,
531                                        float a1, hb_cairo_color_t *c1,
532                                        cairo_pattern_t *pattern)
533 {
534   hb_cairo_point_t center = hb_cairo_point_t { cx, cy };
535   int num_splits;
536   hb_cairo_point_t p0;
537   hb_cairo_color_t color0, color1;
538
539   num_splits = ceilf (fabsf (a1 - a0) / MAX_ANGLE);
540   p0 = hb_cairo_point_t { cosf (a0), sinf (a0) };
541   color0 = *c0;
542
543   for (int a = 0; a < num_splits; a++)
544     {
545       float k = (a + 1.) / num_splits;
546       float angle1;
547       hb_cairo_point_t p1;
548       hb_cairo_point_t A, U;
549       hb_cairo_point_t C0, C1;
550       hb_cairo_patch_t patch;
551
552       angle1 = _hb_cairo_interpolate (a0, a1, k);
553       _hb_cairo_interpolate_colors (c0, c1, k, &color1);
554
555       patch.color0 = color0;
556       patch.color1 = color1;
557
558       p1 = hb_cairo_point_t { cosf (angle1), sinf (angle1) };
559       patch.p0 = _hb_cairo_sum (center, _hb_cairo_scale (p0, radius));
560       patch.p1 = _hb_cairo_sum (center, _hb_cairo_scale (p1, radius));
561
562       A = _hb_cairo_normalize (_hb_cairo_sum (p0, p1));
563       U = hb_cairo_point_t { -A.y, A.x };
564       C0 = _hb_cairo_sum (A, _hb_cairo_scale (U, _hb_cairo_dot (_hb_cairo_difference (p0, A), p0) / _hb_cairo_dot (U, p0)));
565       C1 = _hb_cairo_sum (A, _hb_cairo_scale (U, _hb_cairo_dot (_hb_cairo_difference (p1, A), p1) / _hb_cairo_dot (U, p1)));
566
567       patch.c0 = _hb_cairo_sum (center, _hb_cairo_scale (_hb_cairo_sum (C0, _hb_cairo_scale (_hb_cairo_difference (C0, p0), 0.33333f)), radius));
568       patch.c1 = _hb_cairo_sum (center, _hb_cairo_scale (_hb_cairo_sum (C1, _hb_cairo_scale (_hb_cairo_difference (C1, p1), 0.33333f)), radius));
569
570       _hb_cairo_add_patch (pattern, &center, &patch);
571
572       p0 = p1;
573       color0 = color1;
574     }
575 }
576
577 static void
578 _hb_cairo_add_sweep_gradient_patches (hb_color_stop_t *stops,
579                                       unsigned int n_stops,
580                                       cairo_extend_t extend,
581                                       float cx, float cy,
582                                       float radius,
583                                       float start_angle,
584                                       float end_angle,
585                                       cairo_pattern_t *pattern)
586 {
587   float angles_[PREALLOCATED_COLOR_STOPS];
588   float *angles = angles_;
589   hb_cairo_color_t colors_[PREALLOCATED_COLOR_STOPS];
590   hb_cairo_color_t *colors = colors_;
591   hb_cairo_color_t color0, color1;
592
593   if (start_angle == end_angle)
594   {
595     if (extend == CAIRO_EXTEND_PAD)
596     {
597       hb_cairo_color_t c;
598       if (start_angle > 0)
599       {
600         c.r = hb_color_get_red (stops[0].color) / 255.;
601         c.g = hb_color_get_green (stops[0].color) / 255.;
602         c.b = hb_color_get_blue (stops[0].color) / 255.;
603         c.a = hb_color_get_alpha (stops[0].color) / 255.;
604         _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
605                                                0.,          &c,
606                                                start_angle, &c,
607                                                pattern);
608       }
609       if (end_angle < HB_2_PI)
610       {
611         c.r = hb_color_get_red (stops[n_stops - 1].color) / 255.;
612         c.g = hb_color_get_green (stops[n_stops - 1].color) / 255.;
613         c.b = hb_color_get_blue (stops[n_stops - 1].color) / 255.;
614         c.a = hb_color_get_alpha (stops[n_stops - 1].color) / 255.;
615         _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
616                                                end_angle, &c,
617                                                HB_2_PI,  &c,
618                                                pattern);
619       }
620     }
621     return;
622   }
623
624   assert (start_angle != end_angle);
625
626   /* handle directions */
627   if (end_angle < start_angle)
628   {
629     hb_swap (start_angle, end_angle);
630
631     for (unsigned i = 0; i < n_stops - 1 - i; i++)
632       hb_swap (stops[i], stops[n_stops - 1 - i]);
633     for (unsigned i = 0; i < n_stops; i++)
634       stops[i].offset = 1 - stops[i].offset;
635   }
636
637   if (n_stops > PREALLOCATED_COLOR_STOPS)
638   {
639     angles = (float *) hb_malloc (sizeof (float) * n_stops);
640     colors = (hb_cairo_color_t *) hb_malloc (sizeof (hb_cairo_color_t) * n_stops);
641     if (unlikely (!angles || !colors))
642     {
643       hb_free (angles);
644       hb_free (colors);
645       return;
646     }
647   }
648
649   for (unsigned i = 0; i < n_stops; i++)
650   {
651     angles[i] = start_angle + stops[i].offset * (end_angle - start_angle);
652     colors[i].r = hb_color_get_red (stops[i].color) / 255.;
653     colors[i].g = hb_color_get_green (stops[i].color) / 255.;
654     colors[i].b = hb_color_get_blue (stops[i].color) / 255.;
655     colors[i].a = hb_color_get_alpha (stops[i].color) / 255.;
656   }
657
658   if (extend == CAIRO_EXTEND_PAD)
659   {
660     unsigned pos;
661
662     color0 = colors[0];
663     for (pos = 0; pos < n_stops; pos++)
664     {
665       if (angles[pos] >= 0)
666       {
667         if (pos > 0)
668         {
669           float k = (0 - angles[pos - 1]) / (angles[pos] - angles[pos - 1]);
670           _hb_cairo_interpolate_colors (&colors[pos-1], &colors[pos], k, &color0);
671         }
672         break;
673       }
674     }
675     if (pos == n_stops)
676     {
677       /* everything is below 0 */
678       color0 = colors[n_stops-1];
679       _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
680                                              0.,       &color0,
681                                              HB_2_PI, &color0,
682                                              pattern);
683       goto done;
684     }
685
686     _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
687                                            0.,          &color0,
688                                            angles[pos], &colors[pos],
689                                            pattern);
690
691     for (pos++; pos < n_stops; pos++)
692     {
693       if (angles[pos] <= HB_2_PI)
694       {
695         _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
696                                                angles[pos - 1], &colors[pos-1],
697                                                angles[pos],     &colors[pos],
698                                                pattern);
699       }
700       else
701       {
702         float k = (HB_2_PI - angles[pos - 1]) / (angles[pos] - angles[pos - 1]);
703         _hb_cairo_interpolate_colors (&colors[pos - 1], &colors[pos], k, &color1);
704         _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
705                                                angles[pos - 1], &colors[pos - 1],
706                                                HB_2_PI,        &color1,
707                                                pattern);
708         break;
709       }
710     }
711
712     if (pos == n_stops)
713     {
714       /* everything is below 2*M_PI */
715       color0 = colors[n_stops - 1];
716       _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
717                                              angles[n_stops - 1], &color0,
718                                              HB_2_PI,            &color0,
719                                              pattern);
720       goto done;
721     }
722   }
723   else
724   {
725     int k;
726     float span;
727
728     span = angles[n_stops - 1] - angles[0];
729     k = 0;
730     if (angles[0] >= 0)
731     {
732       float ss = angles[0];
733       while (ss > 0)
734       {
735         if (span > 0)
736         {
737           ss -= span;
738           k--;
739         }
740         else
741         {
742           ss += span;
743           k++;
744         }
745       }
746     }
747     else if (angles[0] < 0)
748     {
749       float ee = angles[n_stops - 1];
750       while (ee < 0)
751       {
752         if (span > 0)
753         {
754           ee += span;
755           k++;
756         }
757         else
758         {
759           ee -= span;
760           k--;
761         }
762       }
763     }
764
765     //assert (angles[0] + k * span <= 0 && 0 < angles[n_stops - 1] + k * span);
766     span = fabsf (span);
767
768     for (signed l = k; l < 1000; l++)
769     {
770       for (unsigned i = 1; i < n_stops; i++)
771       {
772         float a0, a1;
773         hb_cairo_color_t *c0, *c1;
774
775         if ((l % 2 != 0) && (extend == CAIRO_EXTEND_REFLECT))
776         {
777           a0 = angles[0] + angles[n_stops - 1] - angles[n_stops - 1 - (i-1)] + l * span;
778           a1 = angles[0] + angles[n_stops - 1] - angles[n_stops - 1 - i] + l * span;
779           c0 = &colors[n_stops - 1 - (i - 1)];
780           c1 = &colors[n_stops - 1 - i];
781         }
782         else
783         {
784           a0 = angles[i-1] + l * span;
785           a1 = angles[i] + l * span;
786           c0 = &colors[i-1];
787           c1 = &colors[i];
788         }
789
790         if (a1 < 0)
791           continue;
792         if (a0 < 0)
793         {
794           hb_cairo_color_t color;
795           float f = (0 - a0)/(a1 - a0);
796           _hb_cairo_interpolate_colors (c0, c1, f, &color);
797           _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
798                                                  0,  &color,
799                                                  a1, c1,
800                                                  pattern);
801         }
802         else if (a1 >= HB_2_PI)
803         {
804           hb_cairo_color_t color;
805           float f = (HB_2_PI - a0)/(a1 - a0);
806           _hb_cairo_interpolate_colors (c0, c1, f, &color);
807           _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
808                                                  a0,       c0,
809                                                  HB_2_PI, &color,
810                                                  pattern);
811           goto done;
812         }
813         else
814         {
815           _hb_cairo_add_sweep_gradient_patches1 (cx, cy, radius,
816                                                  a0, c0,
817                                                  a1, c1,
818                                                  pattern);
819         }
820       }
821     }
822   }
823
824 done:
825
826   if (angles != angles_)
827     hb_free (angles);
828   if (colors != colors_)
829     hb_free (colors);
830 }
831
832 void
833 _hb_cairo_paint_sweep_gradient (hb_cairo_context_t *c,
834                                 hb_color_line_t *color_line,
835                                 float cx, float cy,
836                                 float start_angle,
837                                 float end_angle)
838 {
839   cairo_t *cr = c->cr;
840
841   unsigned int len = PREALLOCATED_COLOR_STOPS;
842   hb_color_stop_t stops_[PREALLOCATED_COLOR_STOPS];
843   hb_color_stop_t *stops = stops_;
844   cairo_extend_t extend;
845   double x1, y1, x2, y2;
846   float max_x, max_y, radius;
847   cairo_pattern_t *pattern;
848
849   if (unlikely (!_hb_cairo_get_color_stops (c, color_line, &len, &stops)))
850     return;
851
852   hb_qsort (stops, len, sizeof (hb_color_stop_t), _hb_cairo_cmp_color_stop);
853
854   cairo_clip_extents (cr, &x1, &y1, &x2, &y2);
855   max_x = (float) hb_max ((x1 - (double) cx) * (x1 - (double) cx), (x2 - (double) cx) * (x2 - (double) cx));
856   max_y = (float) hb_max ((y1 - (double) cy) * (y1 - (double) cy), (y2 - (double) cy) * (y2 - (double) cy));
857   radius = sqrtf (max_x + max_y);
858
859   extend = hb_cairo_extend (hb_color_line_get_extend (color_line));
860   pattern = cairo_pattern_create_mesh ();
861
862   _hb_cairo_add_sweep_gradient_patches (stops, len, extend, cx, cy,
863                                         radius, start_angle, end_angle, pattern);
864
865   cairo_set_source (cr, pattern);
866   cairo_paint (cr);
867
868   cairo_pattern_destroy (pattern);
869
870   if (stops != stops_)
871     hb_free (stops);
872 }
873
874 #endif