diff options
-rw-r--r-- | .gitlab-ci.yml | 2 | ||||
-rw-r--r-- | meson.build | 5 | ||||
-rw-r--r-- | src/cairo-colr-glyph-render.c | 1235 | ||||
-rw-r--r-- | src/cairo-ft-font.c | 251 | ||||
-rw-r--r-- | src/cairo-ft-private.h | 15 | ||||
-rw-r--r-- | src/cairo-pattern.c | 4 | ||||
-rw-r--r-- | src/cairo.c | 2 | ||||
-rw-r--r-- | src/cairoint.h | 5 | ||||
-rw-r--r-- | src/meson.build | 1 |
9 files changed, 1479 insertions, 41 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d18644277..699fdbf1f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -240,7 +240,7 @@ fedora meson build: - touch builddir/src/.libs/libfoo.so # Run all the tests, except for the big test executable which # gets run separately - - meson test -C builddir --no-suite=slow + - meson test -C builddir --no-suite=slow --print-errorlogs # TODO: These aren't set up as Meson tests yet - (cd doc/public && bash "check-doc-syntax.sh") diff --git a/meson.build b/meson.build index 02f74856c..4dedeadee 100644 --- a/meson.build +++ b/meson.build @@ -345,6 +345,11 @@ if freetype_dep.found() conf.set('CAIRO_CAN_TEST_TTX_FONT', 1) endif endif + # When FT COLRv1 API is no longer experimental we can change this to a + # version check for the stable COLRv1 API. + if cc.get_define('DEBUG_ENABLE_COLR_V1') != '' + conf.set('HAVE_FT_COLR_V1', 1) + endif check_funcs += ft_check_funcs deps += [freetype_dep] endif diff --git a/src/cairo-colr-glyph-render.c b/src/cairo-colr-glyph-render.c new file mode 100644 index 000000000..2388d9e02 --- /dev/null +++ b/src/cairo-colr-glyph-render.c @@ -0,0 +1,1235 @@ +/* -*- 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 <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; + cairo_pattern_t *foreground_color; + FT_Color *palette; + unsigned int num_palette_entries; + 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, + 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; + 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 = double_from_2_14 (ci->alpha); + if (foreground) + *is_foreground_color = TRUE; +} + +static cairo_status_t +draw_paint_solid (cairo_colr_glyph_render_t *render, + FT_PaintSolid *solid, + cairo_t *cr) +{ + cairo_color_t color; + cairo_bool_t is_foreground_color; + +#if DEBUG_COLR + printf ("%*sDraw PaintSolid\n", 2 * render->level, ""); +#endif + + get_palette_color (render, &solid->color, &color, &is_foreground_color); + if (is_foreground_color) + cairo_set_source (cr, render->foreground_color); + else + cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.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; + + 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, NULL); + 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); + + cairo_push_group (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); + cairo_pop_group_to_source (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + 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_UShort palette_index, + cairo_t *cr) +{ + cairo_status_t status = CAIRO_STATUS_SUCCESS; + cairo_colr_glyph_render_t *colr_render = NULL; + FT_Color *palette = NULL; + FT_Palette_Data palette_data; + +#if DEBUG_COLR + printf ("_cairo_render_colr_glyph glyph index: %ld\n", glyph); +#endif + + colr_render = _cairo_malloc (sizeof (cairo_colr_glyph_render_t)); + if (unlikely (colr_render == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + goto cleanup; + } + + if (FT_Palette_Data_Get (face, &palette_data) == 0 && palette_data.num_palettes > 0) { + if (palette_index >= palette_data.num_palettes) + palette_index = CAIRO_COLOR_PALETTE_DEFAULT; + + if (FT_Palette_Select (face, palette_index, &palette) != 0) + palette = NULL; + } + + colr_render->face = face; + colr_render->palette = palette; + colr_render->num_palette_entries = palette_data.num_palette_entries; + colr_render->foreground_color = cairo_pattern_reference (cairo_get_source (cr)); + colr_render->level = 0; + + status = draw_colr_glyph (colr_render, + glyph, + FT_COLOR_INCLUDE_ROOT_TRANSFORM, + cr); + + cleanup: + cairo_pattern_destroy (colr_render->foreground_color); + + if (colr_render) + free (colr_render); + + return status; +} + +#endif /* HAVE_FT_COLR_V1 */ diff --git a/src/cairo-ft-font.c b/src/cairo-ft-font.c index 823898c23..6db833c83 100644 --- a/src/cairo-ft-font.c +++ b/src/cairo-ft-font.c @@ -248,17 +248,19 @@ _cairo_ft_resolve_pattern (FcPattern *pattern, #endif -static cairo_status_t -_ft_to_cairo_error (FT_Error error) +cairo_status_t +_cairo_ft_to_cairo_error (FT_Error error) { /* Currently we don't get many (any?) useful statuses here. * Populate as needed. */ switch (error) { - case FT_Err_Out_Of_Memory: - return CAIRO_STATUS_NO_MEMORY; - default: - return CAIRO_STATUS_FREETYPE_ERROR; + case FT_Err_Ok: + return CAIRO_STATUS_SUCCESS; + case FT_Err_Out_Of_Memory: + return CAIRO_STATUS_NO_MEMORY; + default: + return CAIRO_STATUS_FREETYPE_ERROR; } } @@ -758,7 +760,7 @@ _cairo_ft_unscaled_font_lock_face (cairo_ft_unscaled_font_t *unscaled) { unscaled->lock_count--; CAIRO_MUTEX_UNLOCK (unscaled->mutex); - _cairo_error_throw (_ft_to_cairo_error (error)); + _cairo_error_throw (_cairo_ft_to_cairo_error (error)); return NULL; } @@ -915,7 +917,7 @@ _cairo_ft_unscaled_font_set_scale (cairo_ft_unscaled_font_t *unscaled, sf.y_scale * 64.0 + .5, 0, 0); if (error) - return _cairo_error (_ft_to_cairo_error (error)); + return _cairo_error (_cairo_ft_to_cairo_error (error)); return CAIRO_STATUS_SUCCESS; } @@ -1357,7 +1359,7 @@ _get_bitmap_surface (FT_Bitmap *bitmap, error = FT_Bitmap_Convert( library, bitmap, &tmp, align ); if (error) - return _cairo_error (_ft_to_cairo_error (error)); + return _cairo_error (_cairo_ft_to_cairo_error (error)); FT_Bitmap_Done( library, bitmap ); *bitmap = tmp; @@ -1565,7 +1567,7 @@ _render_glyph_outline (FT_Face face, #endif if (error) - return _cairo_error (_ft_to_cairo_error (error)); + return _cairo_error (_cairo_ft_to_cairo_error (error)); bitmap_size = _compute_xrender_bitmap_size (&bitmap, face->glyph, @@ -2268,10 +2270,9 @@ _cubic_to (FT_Vector *control1, FT_Vector *control2, return 0; } -static cairo_status_t -_decompose_glyph_outline (FT_Face face, - cairo_font_options_t *options, - cairo_path_fixed_t **pathp) +cairo_status_t +_cairo_ft_face_decompose_glyph_outline (FT_Face face, + cairo_path_fixed_t **pathp) { static const FT_Outline_Funcs outline_funcs = { (FT_Outline_MoveToFunc)_move_to, @@ -2498,6 +2499,7 @@ typedef enum { CAIRO_FT_GLYPH_TYPE_OUTLINE, CAIRO_FT_GLYPH_TYPE_SVG, CAIRO_FT_GLYPH_TYPE_COLR_V0, + CAIRO_FT_GLYPH_TYPE_COLR_V1, } cairo_ft_glyph_format_t; typedef struct { @@ -2600,6 +2602,7 @@ _cairo_ft_scaled_glyph_init_surface (cairo_ft_scaled_font_t *scaled_font, cairo_status_t status; cairo_image_surface_t *surface; cairo_bool_t uses_foreground_color = FALSE; + cairo_ft_glyph_private_t *glyph_priv = scaled_glyph->dev_private; /* Only one info type at a time handled in this function */ assert (info == CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE || info == CAIRO_SCALED_GLYPH_INFO_SURFACE); @@ -2643,7 +2646,9 @@ _cairo_ft_scaled_glyph_init_surface (cairo_ft_scaled_font_t *scaled_font, glyph = face->glyph; - if (glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + if (glyph_priv->format == CAIRO_FT_GLYPH_TYPE_COLR_V0 || + glyph_priv->format == CAIRO_FT_GLYPH_TYPE_OUTLINE) { + status = _render_glyph_outline (face, &scaled_font->ft_options.base, &surface); } else { @@ -2761,7 +2766,7 @@ _cairo_ft_scaled_glyph_init_record_colr_v0_glyph (cairo_ft_scaled_font_t *scaled goto cleanup; } - status = _decompose_glyph_outline (face, &scaled_font->ft_options.base, &path_fixed); + status = _cairo_ft_face_decompose_glyph_outline (face, &path_fixed); if (unlikely (status)) return status; @@ -2787,6 +2792,141 @@ _cairo_ft_scaled_glyph_init_record_colr_v0_glyph (cairo_ft_scaled_font_t *scaled } #endif +#if HAVE_FT_COLR_V1 +static cairo_int_status_t +_cairo_ft_scaled_glyph_init_record_colr_v1_glyph (cairo_ft_scaled_font_t *scaled_font, + cairo_scaled_glyph_t *scaled_glyph, + FT_Face face, + cairo_text_extents_t *extents) +{ + cairo_status_t status = CAIRO_STATUS_SUCCESS; + cairo_surface_t *recording_surface; + cairo_t *cr; + cairo_pattern_t *pattern; + FT_Color *palette; + unsigned int num_palette_entries; + + recording_surface = + cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL); + + cairo_surface_set_device_scale (recording_surface, 1, -1); + + cr = cairo_create (recording_surface); + + cairo_set_font_size (cr, 1.0); + cairo_set_font_options (cr, &scaled_font->base.options); + + pattern = cairo_pattern_create_rgb (0, 0, 0); + pattern->is_userfont_foreground = TRUE; + cairo_set_source (cr, pattern); + cairo_pattern_destroy (pattern); + + extents->x_bearing = DOUBLE_FROM_26_6(face->bbox.xMin); + extents->y_bearing = DOUBLE_FROM_26_6(face->bbox.yMin); + extents->width = DOUBLE_FROM_26_6(face->bbox.xMax) - extents->x_bearing; + extents->height = DOUBLE_FROM_26_6(face->bbox.yMax) - extents->y_bearing; + + _cairo_ft_scaled_glyph_set_palette (scaled_font, face, &num_palette_entries, &palette); + + if (!_cairo_matrix_is_scale_0 (&scaled_font->base.scale)) { + status = _cairo_render_colr_v1_glyph (face, + _cairo_scaled_glyph_index (scaled_glyph), + scaled_font->base.options.palette_index, + cr); + if (status == CAIRO_STATUS_USER_FONT_NOT_IMPLEMENTED) + status = CAIRO_INT_STATUS_UNSUPPORTED; + + if (status == CAIRO_STATUS_SUCCESS) + status = cairo_status (cr); + } + + cairo_destroy (cr); + + if (status) { + cairo_surface_destroy (recording_surface); + scaled_glyph->color_glyph = FALSE; + scaled_glyph->color_glyph_set = TRUE; + return status; + } + + _cairo_scaled_glyph_set_recording_surface (scaled_glyph, + &scaled_font->base, + recording_surface); + + scaled_glyph->color_glyph = TRUE; + scaled_glyph->color_glyph_set = TRUE; + + /* get metrics */ + + /* Copied from cairo-user-font.c */ + cairo_matrix_t extent_scale; + double extent_x_scale; + double extent_y_scale; + double snap_x_scale; + double snap_y_scale; + double fixed_scale, x_scale, y_scale; + + extent_scale = scaled_font->base.scale_inverse; + snap_x_scale = 1.0; + snap_y_scale = 1.0; + status = _cairo_matrix_compute_basis_scale_factors (&extent_scale, + &x_scale, &y_scale, + 1); + if (status == CAIRO_STATUS_SUCCESS) { + if (x_scale == 0) + x_scale = 1; + if (y_scale == 0) + y_scale = 1; + + snap_x_scale = x_scale; + snap_y_scale = y_scale; + + /* since glyphs are pretty much 1.0x1.0, we can reduce error by + * scaling to a larger square. say, 1024.x1024. */ + fixed_scale = 1024; + x_scale /= fixed_scale; + y_scale /= fixed_scale; + + cairo_matrix_scale (&extent_scale, 1.0 / x_scale, 1.0 / y_scale); + + extent_x_scale = x_scale; + extent_y_scale = y_scale; + } + + { + /* compute width / height */ + cairo_box_t bbox; + double x1, y1, x2, y2; + double x_scale, y_scale; + + /* Compute extents.x/y/width/height from recording_surface, + * in font space. + */ + status = _cairo_recording_surface_get_bbox ((cairo_recording_surface_t *) recording_surface, + &bbox, + &extent_scale); + if (unlikely (status)) + return status; + + _cairo_box_to_doubles (&bbox, &x1, &y1, &x2, &y2); + + x_scale = extent_x_scale; + y_scale = extent_y_scale; + extents->x_bearing = x1 * x_scale; + extents->y_bearing = y1 * y_scale; + extents->width = (x2 - x1) * x_scale; + extents->height = (y2 - y1) * y_scale; + } + + if (scaled_font->base.options.hint_metrics != CAIRO_HINT_METRICS_OFF) { + extents->x_advance = _cairo_lround (extents->x_advance / snap_x_scale) * snap_x_scale; + extents->y_advance = _cairo_lround (extents->y_advance / snap_y_scale) * snap_y_scale; + } + + return status; +} +#endif + #if HAVE_FT_SVG_DOCUMENT static cairo_int_status_t _cairo_ft_scaled_glyph_init_record_svg_glyph (cairo_ft_scaled_font_t *scaled_font, @@ -2938,9 +3078,9 @@ _cairo_ft_scaled_glyph_init_record_svg_glyph (cairo_ft_scaled_font_t *scaled_fon #endif static cairo_int_status_t -_cairo_ft_scaled_glyph_init_surface_svg_glyph (cairo_ft_scaled_font_t *scaled_font, - cairo_scaled_glyph_t *scaled_glyph, - const cairo_color_t *foreground_color) +_cairo_ft_scaled_glyph_init_surface_for_recording_surface (cairo_ft_scaled_font_t *scaled_font, + cairo_scaled_glyph_t *scaled_glyph, + const cairo_color_t *foreground_color) { cairo_surface_t *surface; int width, height; @@ -3084,9 +3224,8 @@ _cairo_ft_scaled_glyph_get_metrics (cairo_ft_scaled_font_t *scaled_font, static cairo_bool_t _cairo_ft_scaled_glyph_is_colr_v0 (cairo_ft_scaled_font_t *scaled_font, - cairo_scaled_glyph_t *scaled_glyph, + cairo_scaled_glyph_t *scaled_glyph, FT_Face face) - { #ifdef HAVE_FT_PALETTE_SELECT FT_LayerIterator iterator; @@ -3095,15 +3234,33 @@ _cairo_ft_scaled_glyph_is_colr_v0 (cairo_ft_scaled_font_t *scaled_font, iterator.p = NULL; if (FT_Get_Color_Glyph_Layer(face, - _cairo_scaled_glyph_index (scaled_glyph), - &layer_glyph_index, - &layer_color_index, - &iterator)) + _cairo_scaled_glyph_index (scaled_glyph), + &layer_glyph_index, + &layer_color_index, + &iterator) == 1) { return TRUE; } #endif + return FALSE; +} + +static cairo_bool_t +_cairo_ft_scaled_glyph_is_colr_v1 (cairo_ft_scaled_font_t *scaled_font, + cairo_scaled_glyph_t *scaled_glyph, + FT_Face face) +{ +#if HAVE_FT_COLR_V1 + FT_OpaquePaint paint = { NULL, 0 }; + if (FT_Get_Color_Glyph_Paint (face, + _cairo_scaled_glyph_index (scaled_glyph), + FT_COLOR_INCLUDE_ROOT_TRANSFORM, + &paint) == 1) + { + return TRUE; + } +#endif return FALSE; } @@ -3160,7 +3317,9 @@ _cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font, if (is_svg_format) { glyph_priv->format = CAIRO_FT_GLYPH_TYPE_SVG; } else if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { - if (_cairo_ft_scaled_glyph_is_colr_v0 (scaled_font, scaled_glyph, face)) + if (_cairo_ft_scaled_glyph_is_colr_v1 (scaled_font, scaled_glyph, face)) + glyph_priv->format = CAIRO_FT_GLYPH_TYPE_COLR_V1; + else if (_cairo_ft_scaled_glyph_is_colr_v0 (scaled_font, scaled_glyph, face)) glyph_priv->format = CAIRO_FT_GLYPH_TYPE_COLR_V0; else glyph_priv->format = CAIRO_FT_GLYPH_TYPE_OUTLINE; @@ -3175,14 +3334,16 @@ _cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font, load_flags, &fs_metrics); + +/* SVG and COLR v1 glyphs require the bounding box to be obtained from + * the ink extents of the rendering. We need to render glyph to a + * recording surface to obtain these extents. But we also need the + * advance from _cairo_ft_scaled_glyph_get_metrics() before calling + * this function. + */ + #if HAVE_FT_SVG_DOCUMENT if (glyph_priv->format == CAIRO_FT_GLYPH_TYPE_SVG) { - /* SVG glyphs require the bounding box to be obtained from the - * ink extents of the SVG rendering. We need to render the SVG - * to a recording surface to obtain these extents. But we also - * need the advance from _cairo_ft_scaled_glyph_get_metrics() - * before calling this function. - */ status = (cairo_int_status_t)_cairo_ft_scaled_glyph_init_record_svg_glyph (scaled_font, scaled_glyph, face, @@ -3190,6 +3351,15 @@ _cairo_ft_scaled_glyph_init_metrics (cairo_ft_scaled_font_t *scaled_font, } #endif +#if HAVE_FT_COLR_V1 + if (glyph_priv->format == CAIRO_FT_GLYPH_TYPE_COLR_V1) { + status = (cairo_int_status_t)_cairo_ft_scaled_glyph_init_record_colr_v1_glyph (scaled_font, + scaled_glyph, + face, + &fs_metrics); + } +#endif + _cairo_scaled_glyph_set_metrics (scaled_glyph, &scaled_font->base, &fs_metrics); @@ -3252,10 +3422,12 @@ _cairo_ft_scaled_glyph_init (void *abstract_font, switch (glyph_priv->format) { case CAIRO_FT_GLYPH_TYPE_BITMAP: case CAIRO_FT_GLYPH_TYPE_OUTLINE: - status = CAIRO_INT_STATUS_UNSUPPORTED; break; case CAIRO_FT_GLYPH_TYPE_SVG: - /* The SVG recording surface is initialized in _cairo_ft_scaled_glyph_init_metrics() */ + case CAIRO_FT_GLYPH_TYPE_COLR_V1: + /* The SVG and COLR v1 recording surfaces are + * initialized in _cairo_ft_scaled_glyph_init_metrics() + */ status = CAIRO_STATUS_SUCCESS; break; case CAIRO_FT_GLYPH_TYPE_COLR_V0: @@ -3273,8 +3445,10 @@ _cairo_ft_scaled_glyph_init (void *abstract_font, } if (info & CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE) { - if (glyph_priv->format == CAIRO_FT_GLYPH_TYPE_SVG) { - status = _cairo_ft_scaled_glyph_init_surface_svg_glyph (scaled_font, + if (glyph_priv->format == CAIRO_FT_GLYPH_TYPE_SVG || + glyph_priv->format == CAIRO_FT_GLYPH_TYPE_COLR_V1) + { + status = _cairo_ft_scaled_glyph_init_surface_for_recording_surface (scaled_font, scaled_glyph, foreground_color); } else { @@ -3340,8 +3514,7 @@ _cairo_ft_scaled_glyph_init (void *abstract_font, } if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { - status = _decompose_glyph_outline (face, &scaled_font->ft_options.base, - &path); + status = _cairo_ft_face_decompose_glyph_outline (face, &path); } else { status = CAIRO_INT_STATUS_UNSUPPORTED; } @@ -3483,7 +3656,7 @@ _cairo_ft_is_synthetic (void *abstract_font, error = FT_Get_MM_Var (face, &mm_var); if (error) { - status = _cairo_error (_ft_to_cairo_error (error)); + status = _cairo_error (_cairo_ft_to_cairo_error (error)); goto cleanup; } diff --git a/src/cairo-ft-private.h b/src/cairo-ft-private.h index 75d65b4db..4775d8135 100644 --- a/src/cairo-ft-private.h +++ b/src/cairo-ft-private.h @@ -55,6 +55,13 @@ _cairo_scaled_font_is_ft (cairo_scaled_font_t *scaled_font); cairo_private unsigned int _cairo_ft_scaled_font_get_load_flags (cairo_scaled_font_t *scaled_font); +cairo_private cairo_status_t +_cairo_ft_to_cairo_error (FT_Error error); + +cairo_private cairo_status_t +_cairo_ft_face_decompose_glyph_outline (FT_Face face, + cairo_path_fixed_t **pathp); + #if HAVE_FT_SVG_DOCUMENT typedef struct FT_Color_ FT_Color; @@ -70,6 +77,14 @@ _cairo_render_svg_glyph (const char *svg_document, cairo_t *cr); #endif +#if HAVE_FT_COLR_V1 +cairo_private cairo_status_t +_cairo_render_colr_v1_glyph (FT_Face face, + unsigned long glyph, + FT_UShort palette_index, + cairo_t *cr); +#endif + CAIRO_END_DECLS #endif /* CAIRO_HAS_FT_FONT */ diff --git a/src/cairo-pattern.c b/src/cairo-pattern.c index 2cbd52b2c..64db0a336 100644 --- a/src/cairo-pattern.c +++ b/src/cairo-pattern.c @@ -1048,6 +1048,7 @@ cairo_pattern_create_mesh (void) return &pattern->base; } +slim_hidden_def (cairo_pattern_create_mesh); /** * cairo_pattern_reference: @@ -1283,7 +1284,7 @@ cairo_mesh_pattern_begin_patch (cairo_pattern_t *pattern) for (i = 0; i < 4; i++) mesh->has_color[i] = FALSE; } - +slim_hidden_def (cairo_mesh_pattern_begin_patch); static void _calc_control_point (cairo_mesh_patch_t *patch, int control_point) @@ -1400,6 +1401,7 @@ cairo_mesh_pattern_end_patch (cairo_pattern_t *pattern) mesh->current_patch = NULL; } +slim_hidden_def (cairo_mesh_pattern_end_patch); /** * cairo_mesh_pattern_curve_to: diff --git a/src/cairo.c b/src/cairo.c index 7bfc6a143..35696ab89 100644 --- a/src/cairo.c +++ b/src/cairo.c @@ -1595,6 +1595,7 @@ cairo_identity_matrix (cairo_t *cr) if (unlikely (status)) _cairo_set_error (cr, status); } +slim_hidden_def (cairo_identity_matrix); /** * cairo_user_to_device: @@ -2874,6 +2875,7 @@ cairo_clip_extents (cairo_t *cr, if (unlikely (status)) _cairo_set_error (cr, status); } +slim_hidden_def (cairo_clip_extents); /** * cairo_in_clip: diff --git a/src/cairoint.h b/src/cairoint.h index 976162e6f..032d42145 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -1950,6 +1950,7 @@ slim_hidden_proto (cairo_append_path); slim_hidden_proto (cairo_arc); slim_hidden_proto (cairo_arc_negative); slim_hidden_proto (cairo_clip); +slim_hidden_proto (cairo_clip_extents); slim_hidden_proto (cairo_clip_preserve); slim_hidden_proto (cairo_close_path); slim_hidden_proto (cairo_copy_path); @@ -1983,6 +1984,7 @@ slim_hidden_proto (cairo_get_tolerance); slim_hidden_proto (cairo_glyph_allocate); slim_hidden_proto (cairo_glyph_free); slim_hidden_proto (cairo_has_current_point); +slim_hidden_proto (cairo_identity_matrix); slim_hidden_proto (cairo_image_surface_create); slim_hidden_proto (cairo_image_surface_create_for_data); slim_hidden_proto (cairo_image_surface_get_data); @@ -2004,7 +2006,9 @@ slim_hidden_proto (cairo_matrix_scale); slim_hidden_proto (cairo_matrix_transform_distance); slim_hidden_proto (cairo_matrix_transform_point); slim_hidden_proto (cairo_matrix_translate); +slim_hidden_proto (cairo_mesh_pattern_begin_patch); slim_hidden_proto (cairo_mesh_pattern_curve_to); +slim_hidden_proto (cairo_mesh_pattern_end_patch); slim_hidden_proto (cairo_mesh_pattern_get_control_point); slim_hidden_proto (cairo_mesh_pattern_get_corner_color_rgba); slim_hidden_proto (cairo_mesh_pattern_get_patch_count); @@ -2020,6 +2024,7 @@ slim_hidden_proto_no_warn (cairo_path_destroy); slim_hidden_proto (cairo_pattern_add_color_stop_rgba); slim_hidden_proto (cairo_pattern_create_for_surface); slim_hidden_proto (cairo_pattern_create_linear); +slim_hidden_proto (cairo_pattern_create_mesh); slim_hidden_proto (cairo_pattern_create_radial); slim_hidden_proto (cairo_pattern_create_rgb); slim_hidden_proto (cairo_pattern_create_rgba); diff --git a/src/meson.build b/src/meson.build index abb04e1ff..30de39f35 100644 --- a/src/meson.build +++ b/src/meson.build @@ -130,6 +130,7 @@ cairo_feature_sources = { ], 'cairo-ft': [ 'cairo-ft-font.c', + 'cairo-colr-glyph-render.c', 'cairo-svg-glyph-render.c' ], |