diff options
Diffstat (limited to 'gfx/cairo/cairo/src/cairo-colr-glyph-render.c')
-rw-r--r-- | gfx/cairo/cairo/src/cairo-colr-glyph-render.c | 1248 |
1 files changed, 1248 insertions, 0 deletions
diff --git a/gfx/cairo/cairo/src/cairo-colr-glyph-render.c b/gfx/cairo/cairo/src/cairo-colr-glyph-render.c new file mode 100644 index 0000000000..28254fd51a --- /dev/null +++ b/gfx/cairo/cairo/src/cairo-colr-glyph-render.c @@ -0,0 +1,1248 @@ +/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */ +/* cairo - a vector graphics library with display and print output + * + * Copyright © 2022 Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * Contributor(s): + * Matthias Clasen <mclasen@redhat.com> + */ + +#include "cairoint.h" +#include "cairo-array-private.h" +#include "cairo-ft-private.h" +#include "cairo-path-private.h" +#include "cairo-pattern-private.h" + +#include <assert.h> +#include <math.h> +#include <stdio.h> +#include <string.h> + +#if HAVE_FT_COLR_V1 + +#include <ft2build.h> +#include FT_CONFIG_OPTIONS_H +#include FT_COLOR_H +#include FT_GLYPH_H +#include FT_OUTLINE_H +#include FT_SIZES_H + +/* #define DEBUG_COLR 1 */ + +typedef struct _cairo_colr_glyph_render { + FT_Face face; + FT_Color *palette; + unsigned int num_palette_entries; + cairo_pattern_t *foreground_marker; + cairo_pattern_t *foreground_source; + cairo_bool_t foreground_source_used; + int level; +} cairo_colr_glyph_render_t; + +static cairo_status_t +draw_paint (cairo_colr_glyph_render_t *render, + FT_OpaquePaint *paint, + cairo_t *cr); + + +static inline double +double_from_16_16 (FT_Fixed f) +{ + return f / (double) (1 << 16); +} + +static inline double +double_from_26_6 (FT_F26Dot6 f) +{ + return f / (double) (1 << 6); +} + +static inline double +double_from_2_14 (FT_F2Dot14 f) +{ + return f / (double) (1 << 14); +} + +static inline double +interpolate (double f0, double f1, double f) +{ + return f0 + f * (f1 - f0); +} + +static inline void +interpolate_points (cairo_point_double_t *p0, + cairo_point_double_t *p1, + double f, + cairo_point_double_t *out) +{ + out->x = interpolate (p0->x, p1->x, f); + out->y = interpolate (p0->y, p1->y, f); +} + +static inline void +interpolate_colors (cairo_color_t *c0, + cairo_color_t *c1, + double f, + cairo_color_t *out) +{ + out->red = interpolate (c0->red, c1->red, f); + out->green = interpolate (c0->green, c1->green, f); + out->blue = interpolate (c0->blue, c1->blue, f); + out->alpha = interpolate (c0->alpha, c1->alpha, f); +} + +static inline double +dot (cairo_point_double_t p, cairo_point_double_t q) +{ + return p.x * q.x + p.y * q.y; +} + +static inline cairo_point_double_t +normalize (cairo_point_double_t p) +{ + double len = sqrt (dot (p, p)); + + return (cairo_point_double_t) { p.x / len, p.y / len }; +} + +static inline cairo_point_double_t +sum (cairo_point_double_t p, cairo_point_double_t q) +{ + return (cairo_point_double_t) { p.x + q.x, p.y + q.y }; +} + +static inline cairo_point_double_t +difference (cairo_point_double_t p, cairo_point_double_t q) +{ + return (cairo_point_double_t) { p.x - q.x, p.y - q.y }; +} + +static inline cairo_point_double_t +scale (cairo_point_double_t p, double f) +{ + return (cairo_point_double_t) { p.x * f, p.y * f }; +} + +static cairo_operator_t +cairo_operator_from_ft_composite_mode (FT_Composite_Mode mode) +{ + switch (mode) + { + case FT_COLR_COMPOSITE_CLEAR: return CAIRO_OPERATOR_CLEAR; + case FT_COLR_COMPOSITE_SRC: return CAIRO_OPERATOR_SOURCE; + case FT_COLR_COMPOSITE_DEST: return CAIRO_OPERATOR_DEST; + case FT_COLR_COMPOSITE_SRC_OVER: return CAIRO_OPERATOR_OVER; + case FT_COLR_COMPOSITE_DEST_OVER: return CAIRO_OPERATOR_DEST_OVER; + case FT_COLR_COMPOSITE_SRC_IN: return CAIRO_OPERATOR_IN; + case FT_COLR_COMPOSITE_DEST_IN: return CAIRO_OPERATOR_DEST_IN; + case FT_COLR_COMPOSITE_SRC_OUT: return CAIRO_OPERATOR_OUT; + case FT_COLR_COMPOSITE_DEST_OUT: return CAIRO_OPERATOR_DEST_OUT; + case FT_COLR_COMPOSITE_SRC_ATOP: return CAIRO_OPERATOR_ATOP; + case FT_COLR_COMPOSITE_DEST_ATOP: return CAIRO_OPERATOR_DEST_ATOP; + case FT_COLR_COMPOSITE_XOR: return CAIRO_OPERATOR_XOR; + case FT_COLR_COMPOSITE_PLUS: return CAIRO_OPERATOR_ADD; + case FT_COLR_COMPOSITE_SCREEN: return CAIRO_OPERATOR_SCREEN; + case FT_COLR_COMPOSITE_OVERLAY: return CAIRO_OPERATOR_OVERLAY; + case FT_COLR_COMPOSITE_DARKEN: return CAIRO_OPERATOR_DARKEN; + case FT_COLR_COMPOSITE_LIGHTEN: return CAIRO_OPERATOR_LIGHTEN; + case FT_COLR_COMPOSITE_COLOR_DODGE: return CAIRO_OPERATOR_COLOR_DODGE; + case FT_COLR_COMPOSITE_COLOR_BURN: return CAIRO_OPERATOR_COLOR_BURN; + case FT_COLR_COMPOSITE_HARD_LIGHT: return CAIRO_OPERATOR_HARD_LIGHT; + case FT_COLR_COMPOSITE_SOFT_LIGHT: return CAIRO_OPERATOR_SOFT_LIGHT; + case FT_COLR_COMPOSITE_DIFFERENCE: return CAIRO_OPERATOR_DIFFERENCE; + case FT_COLR_COMPOSITE_EXCLUSION: return CAIRO_OPERATOR_EXCLUSION; + case FT_COLR_COMPOSITE_MULTIPLY: return CAIRO_OPERATOR_MULTIPLY; + case FT_COLR_COMPOSITE_HSL_HUE: return CAIRO_OPERATOR_HSL_HUE; + case FT_COLR_COMPOSITE_HSL_SATURATION: return CAIRO_OPERATOR_HSL_SATURATION; + case FT_COLR_COMPOSITE_HSL_COLOR: return CAIRO_OPERATOR_HSL_COLOR; + case FT_COLR_COMPOSITE_HSL_LUMINOSITY: return CAIRO_OPERATOR_HSL_LUMINOSITY; + case FT_COLR_COMPOSITE_MAX: + default: + ASSERT_NOT_REACHED; + } +} + +static cairo_extend_t +cairo_extend_from_ft_paint_extend (FT_PaintExtend extend) +{ + switch (extend) + { + case FT_COLR_PAINT_EXTEND_PAD: return CAIRO_EXTEND_PAD; + case FT_COLR_PAINT_EXTEND_REPEAT: return CAIRO_EXTEND_REPEAT; + case FT_COLR_PAINT_EXTEND_REFLECT: return CAIRO_EXTEND_REFLECT; + default: + ASSERT_NOT_REACHED; + } +} + +static cairo_status_t +draw_paint_colr_layers (cairo_colr_glyph_render_t *render, + FT_PaintColrLayers *colr_layers, + cairo_t *cr) +{ + FT_OpaquePaint paint; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + +#if DEBUG_COLR + printf ("%*sDraw PaintColrLayers\n", 2 * render->level, ""); +#endif + + while (FT_Get_Paint_Layers (render->face, &colr_layers->layer_iterator, &paint)) { + cairo_push_group (cr); + status = draw_paint (render, &paint, cr); + cairo_pop_group_to_source (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_paint (cr); + + if (unlikely (status)) + break; + } + + return status; +} + +static void +get_palette_color (cairo_colr_glyph_render_t *render, + FT_ColorIndex *ci, + cairo_color_t *color, + double *colr_alpha, + cairo_bool_t *is_foreground_color) +{ + cairo_bool_t foreground = FALSE; + + if (ci->palette_index == 0xffff || ci->palette_index >= render->num_palette_entries) { + color->red = 0; + color->green = 0; + color->blue = 0; + color->alpha = 1; + foreground = TRUE; + } else { + FT_Color c = render->palette[ci->palette_index]; + color->red = c.red / 255.0; + color->green = c.green / 255.0; + color->blue = c.blue / 255.0; + color->alpha = c.alpha / 255.0; + } + + *colr_alpha = double_from_2_14 (ci->alpha); + *is_foreground_color = foreground; +} + +static cairo_status_t +draw_paint_solid (cairo_colr_glyph_render_t *render, + FT_PaintSolid *solid, + cairo_t *cr) +{ + cairo_color_t color; + double colr_alpha; + cairo_bool_t is_foreground_color; + +#if DEBUG_COLR + printf ("%*sDraw PaintSolid\n", 2 * render->level, ""); +#endif + + get_palette_color (render, &solid->color, &color, &colr_alpha, &is_foreground_color); + if (is_foreground_color) { + cairo_set_source (cr, render->foreground_marker); + cairo_paint_with_alpha (cr, colr_alpha); + } else { + cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha * colr_alpha); + cairo_paint (cr); + } + + return CAIRO_STATUS_SUCCESS; +} + +typedef struct _cairo_colr_color_stop { + cairo_color_t color; + double position; +} cairo_colr_color_stop_t; + +typedef struct _cairo_colr_color_line { + int n_stops; + cairo_colr_color_stop_t *stops; +} cairo_colr_color_line_t; + +static void +free_colorline (cairo_colr_color_line_t *cl) +{ + free (cl->stops); + free (cl); +} + +static int +_compare_stops (const void *p1, const void *p2) +{ + const cairo_colr_color_stop_t *c1 = p1; + const cairo_colr_color_stop_t *c2 = p2; + + if (c1->position < c2->position) + return -1; + else if (c1->position > c2->position) + return 1; + else + return 0; +} + +static cairo_colr_color_line_t * +read_colorline (cairo_colr_glyph_render_t *render, + FT_ColorLine *colorline) +{ + cairo_colr_color_line_t *cl; + FT_ColorStop stop; + int i; + double colr_alpha; + cairo_bool_t is_foreground_color; + + cl = calloc (1, sizeof (cairo_colr_color_line_t)); + if (unlikely (cl == NULL)) + return NULL; + + cl->n_stops = colorline->color_stop_iterator.num_color_stops; + cl->stops = calloc (cl->n_stops, sizeof (cairo_colr_color_stop_t)); + if (unlikely (cl->stops == NULL)) { + free (cl); + return NULL; + } + + i = 0; + while (FT_Get_Colorline_Stops (render->face, &stop, &colorline->color_stop_iterator)) { + cl->stops[i].position = double_from_16_16 (stop.stop_offset); + get_palette_color (render, &stop.color, &cl->stops[i].color, &colr_alpha, &is_foreground_color); + if (is_foreground_color) { + double red, green, blue, alpha; + if (cairo_pattern_get_rgba (render->foreground_source, + &red, &green, &blue, &alpha) == CAIRO_STATUS_SUCCESS) + { + cl->stops[i].color.red = red; + cl->stops[i].color.green = green; + cl->stops[i].color.blue = blue; + cl->stops[i].color.alpha = alpha * colr_alpha; + render->foreground_source_used = TRUE; + } + else + { + cl->stops[i].color.red = 0; + cl->stops[i].color.green = 0; + cl->stops[i].color.blue = 0; + cl->stops[i].color.alpha = colr_alpha; + } + } else { + cl->stops[i].color.alpha *= colr_alpha; + } + i++; + } + + qsort (cl->stops, cl->n_stops, sizeof (cairo_colr_color_stop_t), _compare_stops); + + return cl; +} + +static void +reduce_anchors (FT_PaintLinearGradient *gradient, + cairo_point_double_t *pp0, + cairo_point_double_t *pp1) +{ + cairo_point_double_t p0, p1, p2; + cairo_point_double_t q1, q2; + double s; + double k; + + p0.x = double_from_16_16 (gradient->p0.x); + p0.y = double_from_16_16 (gradient->p0.y); + p1.x = double_from_16_16 (gradient->p1.x); + p1.y = double_from_16_16 (gradient->p1.y); + p2.x = double_from_16_16 (gradient->p2.x); + p2.y = double_from_16_16 (gradient->p2.y); + + q2.x = p2.x - p0.x; + q2.y = p2.y - p0.y; + q1.x = p1.x - p0.x; + q1.y = p1.y - p0.y; + + s = q2.x * q2.x + q2.y * q2.y; + if (s < 0.000001) + { + pp0->x = p0.x; pp0->y = p0.y; + pp1->x = p1.x; pp1->y = p1.y; + return; + } + + k = (q2.x * q1.x + q2.y * q1.y) / s; + pp0->x = p0.x; + pp0->y = p0.y; + pp1->x = p1.x - k * q2.x; + pp1->y = p1.y - k * q2.y; +} + +static void +normalize_colorline (cairo_colr_color_line_t *cl, + double *out_min, + double *out_max) +{ + double min, max; + + *out_min = 0.; + *out_max = 1.; + + min = max = cl->stops[0].position; + for (int i = 0; i < cl->n_stops; i++) { + cairo_colr_color_stop_t *stop = &cl->stops[i]; + min = MIN (min, stop->position); + max = MAX (max, stop->position); + } + + if (min != max) { + for (int i = 0; i < cl->n_stops; i++) { + cairo_colr_color_stop_t *stop = &cl->stops[i]; + stop->position = (stop->position - min) / (max - min); + } + *out_min = min; + *out_max = max; + } +} + +static cairo_status_t +draw_paint_linear_gradient (cairo_colr_glyph_render_t *render, + FT_PaintLinearGradient *gradient, + cairo_t *cr) +{ + cairo_colr_color_line_t *cl; + cairo_point_double_t p0, p1; + cairo_point_double_t pp0, pp1; + cairo_pattern_t *pattern; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + double min, max; + +#if DEBUG_COLR + printf ("%*sDraw PaintLinearGradient\n", 2 * render->level, ""); +#endif + + cl = read_colorline (render, &gradient->colorline); + if (unlikely (cl == NULL)) + return CAIRO_STATUS_NO_MEMORY; + + /* cairo only allows stop positions between 0 and 1 */ + normalize_colorline (cl, &min, &max); + reduce_anchors (gradient, &p0, &p1); + interpolate_points (&p0, &p1, min, &pp0); + interpolate_points (&p0, &p1, max, &pp1); + + pattern = cairo_pattern_create_linear (pp0.x, pp0.y, pp1.x, pp1.y); + + cairo_pattern_set_extend (pattern, cairo_extend_from_ft_paint_extend (gradient->colorline.extend)); + + for (int i = 0; i < cl->n_stops; i++) { + cairo_colr_color_stop_t *stop = &cl->stops[i]; + cairo_pattern_add_color_stop_rgba (pattern, stop->position, + stop->color.red, stop->color.green, stop->color.blue, stop->color.alpha); + } + + cairo_set_source (cr, pattern); + cairo_paint (cr); + + cairo_pattern_destroy (pattern); + + free_colorline (cl); + + return status; +} + +static cairo_status_t +draw_paint_radial_gradient (cairo_colr_glyph_render_t *render, + FT_PaintRadialGradient *gradient, + cairo_t *cr) +{ + cairo_colr_color_line_t *cl; + cairo_point_double_t start, end; + cairo_point_double_t start1, end1; + double start_radius, end_radius; + double start_radius1, end_radius1; + double min, max; + cairo_pattern_t *pattern; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + +#if DEBUG_COLR + printf ("%*sDraw PaintRadialGradient\n", 2 * render->level, ""); +#endif + + cl = read_colorline (render, &gradient->colorline); + if (unlikely (cl == NULL)) + return CAIRO_STATUS_NO_MEMORY; + + start.x = double_from_16_16 (gradient->c0.x); + start.y = double_from_16_16 (gradient->c0.y); + end.x = double_from_16_16 (gradient->c1.x); + end.y = double_from_16_16 (gradient->c1.y); + + start_radius = double_from_16_16 (gradient->r0); + end_radius = double_from_16_16 (gradient->r1); + + /* cairo only allows stop positions between 0 and 1 */ + normalize_colorline (cl, &min, &max); + interpolate_points (&start, &end, min, &start1); + interpolate_points (&start, &end, max, &end1); + start_radius1 = interpolate (start_radius, end_radius, min); + end_radius1 = interpolate (start_radius, end_radius, max); + + pattern = cairo_pattern_create_radial (start1.x, start1.y, start_radius1, + end1.x, end1.y, end_radius1); + + cairo_pattern_set_extend (pattern, cairo_extend_from_ft_paint_extend (gradient->colorline.extend)); + + for (int i = 0; i < cl->n_stops; i++) { + cairo_colr_color_stop_t *stop = &cl->stops[i]; + cairo_pattern_add_color_stop_rgba (pattern, stop->position, + stop->color.red, stop->color.green, stop->color.blue, stop->color.alpha); + } + + cairo_set_source (cr, pattern); + cairo_paint (cr); + + cairo_pattern_destroy (pattern); + + free_colorline (cl); + + return status; +} + +typedef struct { + cairo_point_double_t center, p0, c0, c1, p1; + cairo_color_t color0, color1; +} cairo_colr_gradient_patch_t; + +static void +add_patch (cairo_pattern_t *pattern, + cairo_point_double_t *center, + cairo_colr_gradient_patch_t *p) +{ + cairo_mesh_pattern_begin_patch (pattern); + cairo_mesh_pattern_move_to (pattern, center->x, center->y); + cairo_mesh_pattern_line_to (pattern, p->p0.x, p->p0.y); + cairo_mesh_pattern_curve_to (pattern, + p->c0.x, p->c0.y, + p->c1.x, p->c1.y, + p->p1.x, p->p1.y); + cairo_mesh_pattern_line_to (pattern, center->x, center->y); + cairo_mesh_pattern_set_corner_color_rgba (pattern, 0, + p->color0.red, + p->color0.green, + p->color0.blue, + p->color0.alpha); + cairo_mesh_pattern_set_corner_color_rgba (pattern, 1, + p->color0.red, + p->color0.green, + p->color0.blue, + p->color0.alpha); + cairo_mesh_pattern_set_corner_color_rgba (pattern, 2, + p->color1.red, + p->color1.green, + p->color1.blue, + p->color1.alpha); + cairo_mesh_pattern_set_corner_color_rgba (pattern, 3, + p->color1.red, + p->color1.green, + p->color1.blue, + p->color1.alpha); + cairo_mesh_pattern_end_patch (pattern); +} + +#define MAX_ANGLE (M_PI / 8.) + +static void +add_sweep_gradient_patches1 (cairo_point_double_t *center, + double radius, + double a0, + cairo_color_t *c0, + double a1, + cairo_color_t *c1, + cairo_pattern_t *pattern) +{ + + int num_splits; + cairo_point_double_t p0; + cairo_color_t color0, color1; + + num_splits = ceilf (fabs (a1 - a0) / MAX_ANGLE); + p0 = (cairo_point_double_t) { cosf (a0), sinf (a0) }; + color0 = *c0; + + for (int a = 0; a < num_splits; a++) { + double k = (a + 1.) / num_splits; + double angle1; + cairo_point_double_t p1; + cairo_point_double_t A, U; + cairo_point_double_t C0, C1; + cairo_colr_gradient_patch_t patch; + + angle1 = interpolate (a0, a1, k); + interpolate_colors (c0, c1, k, &color1); + + patch.color0 = color0; + patch.color1 = color1; + + p1 = (cairo_point_double_t) { cosf (angle1), sinf (angle1) }; + patch.p0 = sum (*center, scale (p0, radius)); + patch.p1 = sum (*center, scale (p1, radius)); + + A = normalize (sum (p0, p1)); + U = (cairo_point_double_t) { -A.y, A.x }; + C0 = sum (A, scale (U, dot (difference (p0, A), p0) / dot (U, p0))); + C1 = sum (A, scale (U, dot (difference (p1, A), p1) / dot (U, p1))); + patch.c0 = sum (*center, scale (sum (C0, scale (difference (C0, p0), 0.33333)), radius)); + patch.c1 = sum (*center, scale (sum (C1, scale (difference (C1, p1), 0.33333)), radius)); + + add_patch (pattern, center, &patch); + + p0 = p1; + color0 = color1; + } +} + +static void +add_sweep_gradient_patches (cairo_colr_color_line_t *cl, + cairo_extend_t extend, + cairo_point_double_t *center, + double radius, + double start_angle, + double end_angle, + cairo_pattern_t *pattern) +{ + double *angles; + cairo_color_t color0, color1; + + if (start_angle == end_angle) { + if (extend == CAIRO_EXTEND_PAD) { + if (start_angle > 0) + add_sweep_gradient_patches1 (center, radius, + 0., &cl->stops[0].color, + start_angle, &cl->stops[0].color, + pattern); + if (end_angle < 2 * M_PI) + add_sweep_gradient_patches1 (center, radius, + end_angle, &cl->stops[cl->n_stops - 1].color, + 2 * M_PI, &cl->stops[cl->n_stops - 1].color, + pattern); + } + return; + } + + assert (start_angle != end_angle); + + angles = alloca (sizeof (double) * cl->n_stops); + + for (int i = 0; i < cl->n_stops; i++) + angles[i] = start_angle + cl->stops[i].position * (end_angle - start_angle); + + /* handle directions */ + if (end_angle < start_angle) { + for (int i = 0; i < cl->n_stops - 1 - i; i++) { + cairo_colr_color_stop_t stop = cl->stops[i]; + double a = angles[i]; + cl->stops[i] = cl->stops[cl->n_stops - 1 - i]; + cl->stops[cl->n_stops - 1 - i] = stop; + angles[i] = angles[cl->n_stops - 1 - i]; + angles[cl->n_stops - 1 - i] = a; + } + } + + if (extend == CAIRO_EXTEND_PAD) + { + int pos; + + color0 = cl->stops[0].color; + for (pos = 0; pos < cl->n_stops; pos++) { + if (angles[pos] >= 0) { + if (pos > 0) { + double k = (0 - angles[pos - 1]) / (angles[pos] - angles[pos - 1]); + interpolate_colors (&cl->stops[pos - 1].color, &cl->stops[pos].color, k, &color0); + } + break; + } + } + if (pos == cl->n_stops) { + /* everything is below 0 */ + color0 = cl->stops[cl->n_stops - 1].color; + add_sweep_gradient_patches1 (center, radius, + 0., &color0, + 2 * M_PI, &color0, + pattern); + return; + } + + add_sweep_gradient_patches1 (center, radius, + 0., &color0, + angles[pos], &cl->stops[pos].color, + pattern); + + for (pos++; pos < cl->n_stops; pos++) { + if (angles[pos] <= 2 * M_PI) { + add_sweep_gradient_patches1 (center, radius, + angles[pos - 1], &cl->stops[pos - 1].color, + angles[pos], &cl->stops[pos].color, + pattern); + } else { + double k = (2 * M_PI - angles[pos - 1]) / (angles[pos] - angles[pos - 1]); + interpolate_colors (&cl->stops[pos - 1].color, &cl->stops[pos].color, k, &color1); + add_sweep_gradient_patches1 (center, radius, + angles[pos - 1], &cl->stops[pos - 1].color, + 2 * M_PI, &color1, + pattern); + break; + } + } + + if (pos == cl->n_stops) { + /* everything is below 2*M_PI */ + color0 = cl->stops[cl->n_stops - 1].color; + add_sweep_gradient_patches1 (center, radius, + angles[cl->n_stops - 1], &color0, + 2 * M_PI, &color0, + pattern); + return; + } + } else { + int k; + double span; + + span = angles[cl->n_stops - 1] - angles[0]; + k = 0; + if (angles[0] >= 0) { + double ss = angles[0]; + while (ss > 0) { + if (span > 0) { + ss -= span; + k--; + } else { + ss += span; + k++; + } + } + } + else if (angles[0] < 0) + { + double ee = angles[cl->n_stops - 1]; + while (ee < 0) { + if (span > 0) { + ee += span; + k++; + } else { + ee -= span; + k--; + } + } + } + + //assert (angles[0] + k * span <= 0 && 0 < angles[cl->n_stops - 1] + k * span); + + for (int l = k; TRUE; l++) { + for (int i = 1; i < cl->n_stops; i++) { + double a0, a1; + cairo_color_t *c0, *c1; + + if ((l % 2 != 0) && (extend == CAIRO_EXTEND_REFLECT)) { + a0 = angles[0] + angles[cl->n_stops - 1] - angles[cl->n_stops - 1 - (i-1)] + l * span; + a1 = angles[0] + angles[cl->n_stops - 1] - angles[cl->n_stops - 1 - i] + l * span; + c0 = &cl->stops[cl->n_stops - 1 - (i-1)].color; + c1 = &cl->stops[cl->n_stops - 1 - i].color; + } else { + a0 = angles[i-1] + l * span; + a1 = angles[i] + l * span; + c0 = &cl->stops[i-1].color; + c1 = &cl->stops[i].color; + } + + if (a1 < 0) + continue; + + if (a0 < 0) { + cairo_color_t color; + double f = (0 - a0)/(a1 - a0); + interpolate_colors (c0, c1, f, &color); + add_sweep_gradient_patches1 (center, radius, + 0, &color, + a1, c1, + pattern); + } else if (a1 >= 2 * M_PI) { + cairo_color_t color; + double f = (2 * M_PI - a0)/(a1 - a0); + interpolate_colors (c0, c1, f, &color); + add_sweep_gradient_patches1 (center, radius, + a0, c0, + 2 * M_PI, &color, + pattern); + return; + } else { + add_sweep_gradient_patches1 (center, radius, + a0, c0, + a1, c1, + pattern); + } + } + } + } +} + +static cairo_status_t +draw_paint_sweep_gradient (cairo_colr_glyph_render_t *render, + FT_PaintSweepGradient *gradient, + cairo_t *cr) +{ + cairo_colr_color_line_t *cl; + cairo_point_double_t center; + double start_angle, end_angle; + double x1, y1, x2, y2; + double max_x, max_y, R; + cairo_pattern_t *pattern; + cairo_extend_t extend; + +#if DEBUG_COLR + printf ("%*sDraw PaintSweepGradient\n", 2 * render->level, ""); +#endif + + cl = read_colorline (render, &gradient->colorline); + if (unlikely (cl == NULL)) + return CAIRO_STATUS_NO_MEMORY; + + center.x = double_from_16_16 (gradient->center.x); + center.y = double_from_16_16 (gradient->center.y); + start_angle = (double_from_16_16 (gradient->start_angle) + 1) * M_PI; + end_angle = (double_from_16_16 (gradient->end_angle) + 1) * M_PI; + + pattern = cairo_pattern_create_mesh (); + + cairo_clip_extents (cr, &x1, &y1, &x2, &y2); + max_x = MAX ((x1 - center.x) * (x1 - center.x), (x2 - center.x) * (x2 - center.x)); + max_y = MAX ((y1 - center.y) * (y1 - center.y), (y2 - center.y) * (y2 - center.y)); + R = sqrt (max_x + max_y); + + extend = cairo_extend_from_ft_paint_extend (gradient->colorline.extend); + + add_sweep_gradient_patches (cl, extend, ¢er, R, start_angle, end_angle, pattern); + + cairo_set_source (cr, pattern); + cairo_paint (cr); + + cairo_pattern_destroy (pattern); + + free_colorline (cl); + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +draw_paint_glyph (cairo_colr_glyph_render_t *render, + FT_PaintGlyph *glyph, + cairo_t *cr) +{ + cairo_path_fixed_t *path_fixed; + cairo_path_t *path; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + FT_Error error; + +#if DEBUG_COLR + printf ("%*sDraw PaintGlyph\n", 2 * render->level, ""); +#endif + + error = FT_Load_Glyph (render->face, glyph->glyphID, FT_LOAD_DEFAULT); + status = _cairo_ft_to_cairo_error (error); + if (unlikely (status)) + return status; + + status = _cairo_ft_face_decompose_glyph_outline (render->face, &path_fixed); + if (unlikely (status)) + return status; + + cairo_save (cr); + cairo_identity_matrix (cr); + path = _cairo_path_create (path_fixed, cr); + _cairo_path_fixed_destroy (path_fixed); + cairo_restore (cr); + + cairo_save (cr); + + cairo_new_path (cr); + cairo_append_path (cr, path); + cairo_path_destroy (path); + cairo_clip (cr); + + status = draw_paint (render, &glyph->paint, cr); + + cairo_restore (cr); + + return status; +} + +static cairo_status_t draw_colr_glyph (cairo_colr_glyph_render_t *render, + unsigned long glyph, + FT_Color_Root_Transform root, + cairo_t *cr); + +static cairo_status_t +draw_paint_colr_glyph (cairo_colr_glyph_render_t *render, + FT_PaintColrGlyph *colr_glyph, + cairo_t *cr) +{ +#if DEBUG_COLR + printf ("%*sDraw PaintColrGlyph\n", 2 * render->level, ""); +#endif + + return draw_colr_glyph (render, colr_glyph->glyphID, FT_COLOR_NO_ROOT_TRANSFORM, cr); +} + +static cairo_status_t +draw_paint_transform (cairo_colr_glyph_render_t *render, + FT_PaintTransform *transform, + cairo_t *cr) +{ + cairo_matrix_t t; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + +#if DEBUG_COLR + printf ("%*sDraw PaintTransform\n", 2 * render->level, ""); +#endif + + cairo_matrix_init (&t, + double_from_16_16 (transform->affine.xx), + double_from_16_16 (transform->affine.yx), + double_from_16_16 (transform->affine.xy), + double_from_16_16 (transform->affine.yy), + double_from_16_16 (transform->affine.dx), + double_from_16_16 (transform->affine.dy)); + + cairo_save (cr); + + cairo_transform (cr, &t); + status = draw_paint (render, &transform->paint, cr); + + cairo_restore (cr); + + return status; +} + +static cairo_status_t +draw_paint_translate (cairo_colr_glyph_render_t *render, + FT_PaintTranslate *translate, + cairo_t *cr) +{ + cairo_status_t status = CAIRO_STATUS_SUCCESS; + +#if DEBUG_COLR + printf ("%*sDraw PaintTranslate\n", 2 * render->level, ""); +#endif + + cairo_save (cr); + + cairo_translate (cr, double_from_16_16 (translate->dx), double_from_16_16 (translate->dy)); + status = draw_paint (render, &translate->paint, cr); + + cairo_restore (cr); + + return status; +} + +static cairo_status_t +draw_paint_rotate (cairo_colr_glyph_render_t *render, + FT_PaintRotate *rotate, + cairo_t *cr) +{ + cairo_status_t status = CAIRO_STATUS_SUCCESS; + +#if DEBUG_COLR + printf ("%*sDraw PaintRotate\n", 2 * render->level, ""); +#endif + + cairo_save (cr); + + cairo_translate (cr, double_from_16_16 (rotate->center_x), double_from_16_16 (rotate->center_y)); + cairo_rotate (cr, double_from_16_16 (rotate->angle) * M_PI); + cairo_translate (cr, - double_from_16_16 (rotate->center_x), - double_from_16_16 (rotate->center_y)); + status = draw_paint (render, &rotate->paint, cr); + + cairo_restore (cr); + + return status; +} + +static cairo_status_t +draw_paint_scale (cairo_colr_glyph_render_t *render, + FT_PaintScale *scale, + cairo_t *cr) +{ + cairo_status_t status = CAIRO_STATUS_SUCCESS; + +#if DEBUG_COLR + printf ("%*sDraw PaintScale\n", 2 * render->level, ""); +#endif + + cairo_save (cr); + + cairo_translate (cr, double_from_16_16 (scale->center_x), double_from_16_16 (scale->center_y)); + cairo_scale (cr, double_from_16_16 (scale->scale_x), double_from_16_16 (scale->scale_y)); + cairo_translate (cr, - double_from_16_16 (scale->center_x), - double_from_16_16 (scale->center_y)); + status = draw_paint (render, &scale->paint, cr); + + cairo_restore (cr); + + return status; +} + +static cairo_status_t +draw_paint_skew (cairo_colr_glyph_render_t *render, + FT_PaintSkew *skew, + cairo_t *cr) +{ + cairo_matrix_t s; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + +#if DEBUG_COLR + printf ("%*sDraw PaintSkew\n", 2 * render->level, ""); +#endif + + cairo_save (cr); + + cairo_translate (cr, double_from_16_16 (skew->center_x), double_from_16_16 (skew->center_y)); + cairo_matrix_init (&s, 1., tan (double_from_16_16 (skew->y_skew_angle) * M_PI), - tan (double_from_16_16 (skew->x_skew_angle) * M_PI), 1., 0., 0.); + cairo_transform (cr, &s); + cairo_translate (cr, - double_from_16_16 (skew->center_x), - double_from_16_16 (skew->center_y)); + status = draw_paint (render, &skew->paint, cr); + + cairo_restore (cr); + + return status; +} + +static cairo_status_t +draw_paint_composite (cairo_colr_glyph_render_t *render, + FT_PaintComposite *composite, + cairo_t *cr) +{ + cairo_status_t status = CAIRO_STATUS_SUCCESS; + + +#if DEBUG_COLR + printf ("%*sDraw PaintComposite\n", 2 * render->level, ""); +#endif + + cairo_save (cr); + + status = draw_paint (render, &composite->backdrop_paint, cr); + if (unlikely (status)) { + cairo_pattern_destroy (cairo_pop_group (cr)); + goto cleanup; + } + + cairo_push_group (cr); + status = draw_paint (render, &composite->source_paint, cr); + if (unlikely (status)) { + cairo_pattern_destroy (cairo_pop_group (cr)); + cairo_pattern_destroy (cairo_pop_group (cr)); + goto cleanup; + } + + cairo_pop_group_to_source (cr); + cairo_set_operator (cr, cairo_operator_from_ft_composite_mode (composite->composite_mode)); + cairo_paint (cr); + + cleanup: + cairo_restore (cr); + + return status; +} + +static cairo_status_t +draw_paint (cairo_colr_glyph_render_t *render, + FT_OpaquePaint *paint, + cairo_t *cr) +{ + FT_COLR_Paint p; + FT_Size orig_size; + FT_Size unscaled_size; + FT_Matrix orig_transform; + FT_Vector orig_delta; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + + assert (cairo_status (cr) == CAIRO_STATUS_SUCCESS); + + if (!FT_Get_Paint (render->face, *paint, &p)) + return CAIRO_STATUS_NO_MEMORY; + + if (render->level == 0) { + /* Now that the FT_Get_Paint call has applied the root transform, + * make the face unscaled and untransformed, so we can load glyph + * contours. + */ + + FT_Matrix transform; + FT_Vector delta; + + orig_size = render->face->size; + FT_New_Size (render->face, &unscaled_size); + FT_Activate_Size (unscaled_size); + FT_Set_Char_Size (render->face, render->face->units_per_EM << 6, 0, 0, 0); + + transform.xx = transform.yy = 1 << 16; + transform.xy = transform.yx = 0; + delta.x = delta.y = 0; + + FT_Get_Transform (render->face, &orig_transform, &orig_delta); + FT_Set_Transform (render->face, &transform, &delta); + } + + render->level++; + + switch (p.format) { + case FT_COLR_PAINTFORMAT_COLR_LAYERS: + status = draw_paint_colr_layers (render, &p.u.colr_layers, cr); + break; + case FT_COLR_PAINTFORMAT_SOLID: + status = draw_paint_solid (render, &p.u.solid, cr); + break; + case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT: + status = draw_paint_linear_gradient (render, &p.u.linear_gradient, cr); + break; + case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT: + status = draw_paint_radial_gradient (render, &p.u.radial_gradient, cr); + break; + case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT: + status = draw_paint_sweep_gradient (render, &p.u.sweep_gradient, cr); + break; + case FT_COLR_PAINTFORMAT_GLYPH: + status = draw_paint_glyph (render, &p.u.glyph, cr); + break; + case FT_COLR_PAINTFORMAT_COLR_GLYPH: + status = draw_paint_colr_glyph (render, &p.u.colr_glyph, cr); + break; + case FT_COLR_PAINTFORMAT_TRANSFORM: + status = draw_paint_transform (render, &p.u.transform, cr); + break; + case FT_COLR_PAINTFORMAT_TRANSLATE: + status = draw_paint_translate (render, &p.u.translate, cr); + break; + case FT_COLR_PAINTFORMAT_ROTATE: + status = draw_paint_rotate (render, &p.u.rotate, cr); + break; + case FT_COLR_PAINTFORMAT_SCALE: + status = draw_paint_scale (render, &p.u.scale, cr); + break; + case FT_COLR_PAINTFORMAT_SKEW: + status = draw_paint_skew (render, &p.u.skew, cr); + break; + case FT_COLR_PAINTFORMAT_COMPOSITE: + status = draw_paint_composite (render, &p.u.composite, cr); + break; + case FT_COLR_PAINT_FORMAT_MAX: + case FT_COLR_PAINTFORMAT_UNSUPPORTED: + default: + ASSERT_NOT_REACHED; + } + + render->level--; + + if (render->level == 0) { + FT_Set_Transform (render->face, &orig_transform, &orig_delta); + FT_Activate_Size (orig_size); + FT_Done_Size (unscaled_size); + } + + return status; +} + +static cairo_status_t +draw_colr_glyph (cairo_colr_glyph_render_t *render, + unsigned long glyph, + FT_Color_Root_Transform root, + cairo_t *cr) +{ + FT_OpaquePaint paint = { NULL, 0 }; + FT_ClipBox box; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + + cairo_save (cr); + + if (FT_Get_Color_Glyph_ClipBox (render->face, glyph, &box)) { + double xmin, ymin, xmax, ymax; + + xmin = double_from_26_6 (box.bottom_left.x); + ymin = double_from_26_6 (box.bottom_left.y); + xmax = double_from_26_6 (box.top_right.x); + ymax = double_from_26_6 (box.top_right.y); + + cairo_new_path (cr); + cairo_rectangle (cr, xmin, ymin, xmax - xmin, ymax - ymin); + cairo_clip (cr); + } + + if (FT_Get_Color_Glyph_Paint (render->face, glyph, root, &paint)) + status = draw_paint (render, &paint, cr); + + cairo_restore (cr); + + return status; +} + +/* Create an image surface and render the glyph onto it, + * using the given colors. + */ +cairo_status_t +_cairo_render_colr_v1_glyph (FT_Face face, + unsigned long glyph, + FT_Color *palette, + int num_palette_entries, + cairo_t *cr, + cairo_pattern_t *foreground_source, + cairo_bool_t *foreground_source_used) +{ + cairo_status_t status = CAIRO_STATUS_SUCCESS; + cairo_colr_glyph_render_t colr_render; + +#if DEBUG_COLR + printf ("_cairo_render_colr_glyph glyph index: %ld\n", glyph); +#endif + + colr_render.face = face; + colr_render.palette = palette; + colr_render.num_palette_entries = num_palette_entries; + colr_render.foreground_marker = _cairo_pattern_create_foreground_marker (); + colr_render.foreground_source = cairo_pattern_reference (foreground_source);; + colr_render.foreground_source_used = FALSE; + colr_render.level = 0; + + status = draw_colr_glyph (&colr_render, + glyph, + FT_COLOR_INCLUDE_ROOT_TRANSFORM, + cr); + + cairo_pattern_destroy (colr_render.foreground_marker); + cairo_pattern_destroy (colr_render.foreground_source); + *foreground_source_used = colr_render.foreground_source_used; + + return status; +} + +#endif /* HAVE_FT_COLR_V1 */ |