/*
 *  $Id: cairo-utils.c 30146 2026-06-25 12:51:18Z yeti-dn $
 *  Copyright (C) 2012-2024 David Nečas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyui/cairo-utils.h"

/**
 * gwy_cairo_set_source_rgba:
 * @cr: A Cairo drawing context.
 * @rgba: An RGBA colour.
 *
 * Sets the source patter of a Cairo context to #GwyRGBA color.
 *
 * This is a convenience function that is exactly equivalent to
 * |[
 * cairo_set_source_rgba(cr, rgba->r, rgba->g, rgba->b, rgba->a);
 * ]|
 **/
void
gwy_cairo_set_source_rgba(cairo_t *cr,
                          const GwyRGBA *rgba)
{
    cairo_set_source_rgba(cr, rgba->r, rgba->g, rgba->b, rgba->a);
}

/**
 * gwy_cairo_pattern_create_gradient:
 * @gradient: A false colour gradient.
 * @towards: Gradient direction in the plane.
 * @use_alpha: %TRUE to use the alpha components and create a possibly semitransparent pattern; %FALSE to render all
 *             colours with full opacity.
 *
 * Creates a linear Cairo pattern corresponding to a false colour gradient.
 *
 * The pattern is always along the interval [0,1] in either horizontal or vertical direction and either towards 1 or
 * towards 0, depending on the value of @towards.
 *
 * Returns: (transfer full):
 *          A newly created linear Cairo pattern.
 **/
cairo_pattern_t*
gwy_cairo_pattern_create_gradient(GwyGradient *gradient,
                                  GtkPositionType towards,
                                  gboolean use_alpha)
{
    g_return_val_if_fail(GWY_IS_GRADIENT(gradient), NULL);

    gdouble xf, yf, xt, yt;
    if (towards == GTK_POS_RIGHT) {
        xf = yf = yt = 0.0;
        xt = 1.0;
    }
    else if (towards == GTK_POS_LEFT) {
        xt = yf = yt = 0.0;
        xf = 1.0;
    }
    else if (towards == GTK_POS_TOP) {
        yt = xf = xt = 0.0;
        yf = 1.0;
    }
    else if (towards == GTK_POS_BOTTOM) {
        yf = xf = xt = 0.0;
        yt = 1.0;
    }
    else {
        g_return_val_if_reached(NULL);
    }

    cairo_pattern_t *pattern = cairo_pattern_create_linear(xf, yf, xt, yt);
    guint npoints;
    const GwyGradientPoint* points = gwy_gradient_get_points(gradient, &npoints);
    for (guint i = 0; i < npoints; i++) {
        const GwyGradientPoint *pt = points + i;
        if (use_alpha)
            cairo_pattern_add_color_stop_rgba(pattern, pt->x, pt->color.r, pt->color.g, pt->color.b, pt->color.a);
        else
            cairo_pattern_add_color_stop_rgb(pattern, pt->x, pt->color.r, pt->color.g, pt->color.b);
    }

    return pattern;
}

/**
 * gwy_cairo_checker:
 * @cr: A Cairo drawing context
 * @even: Even parity colour (occurs in the top left corner).
 * @odd: Odd parity colour.
 * @width: Rectangle width.
 * @height: Rectangle height.
 * @size: Tile size.
 *
 * Fills a rectangle with a checker pattern of two alternating colours.
 *
 * The rectangle starts at (0, 0) and has the specified dimensions. Use a translation to draw it elsewhere.
 **/
void
gwy_cairo_checker_rectangle(cairo_t *cr,
                            const GwyRGBA *even,
                            const GwyRGBA *odd,
                            gint width,
                            gint height,
                            gint size)
{
    gwy_cairo_set_source_rgba(cr, even);
    for (gint row = 0; row < height; row += size) {
        for (gint col = 0; col < width; col += size) {
            if ((row + col)/size % 2 == 0)
                cairo_rectangle(cr, col, row, size, size);
        }
    }
    cairo_fill(cr);

    gwy_cairo_set_source_rgba(cr, odd);
    for (gint row = 0; row < height; row += size) {
        for (gint col = 0; col < width; col += size) {
            if ((row + col)/size % 2 != 0)
                cairo_rectangle(cr, col, row, size, size);
        }
    }
    cairo_fill(cr);
}

/**
 * gwy_cairo_line:
 * @cr: A Cairo drawing context.
 * @xfrom: X-coordinate of start point of the line.
 * @yfrom: Y-coordinate of start point of the line.
 * @xto: X-coordinate of end point of the line.
 * @yto: Y-coordinate of end point of the line.
 *
 * Adds a straight line subpath to a Cairo context.
 *
 * A new subpath is started, but it is not terminated.  Use cairo_new_sub_path() or other subpath-terminating
 * primitive afterwards if necessary.  All parameters are in user coordinates.
 *
 * This function does cairo_move_to() and cairo_line_to() in a single call, simplifying common line drawing a bit.
 **/
void
gwy_cairo_line(cairo_t *cr,
               gdouble xfrom,
               gdouble yfrom,
               gdouble xto,
               gdouble yto)
{
    cairo_move_to(cr, xfrom, yfrom);
    cairo_line_to(cr, xto, yto);
}

/**
 * gwy_cairo_ellipse:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @xr: Horizontal half-axis length.
 * @yr: Vertical half-axis length.
 *
 * Adds a closed elliptical subpath to a Cairo context.
 *
 * A new subpath is started and closed.  The ellipse is approximated using Bezier curves, on the other hand, it does
 * not rely on transformations.  All parameters are in user coordinates.
 **/
void
gwy_cairo_ellipse(cairo_t *cr,
                  gdouble x, gdouble y, gdouble xr, gdouble yr)
{
    const gdouble q = 0.552;

    cairo_move_to(cr, x + xr, y);
    cairo_curve_to(cr, x + xr, y + q*yr, x + q*xr, y + yr, x, y + yr);
    cairo_curve_to(cr, x - q*xr, y + yr, x - xr, y + q*yr, x - xr, y);
    cairo_curve_to(cr, x - xr, y - q*yr, x - q*xr, y - yr, x, y - yr);
    cairo_curve_to(cr, x + q*xr, y - yr, x + xr, y - q*yr, x + xr, y);
    cairo_close_path(cr);
}

/**
 * gwy_cairo_cross:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Length of the cross arm (from centre to end), i.e. half of the side of the smallest square containing
 *            the cross.
 *
 * Adds a cross-shaped subpath to a Cairo context.
 *
 * A new subpath is started and terminated. All parameters are in user coordinates.
 **/
void
gwy_cairo_cross(cairo_t *cr,
                gdouble x, gdouble y, gdouble halfside)
{
    cairo_move_to(cr, x - halfside, y);
    cairo_line_to(cr, x + halfside, y);
    cairo_move_to(cr, x, y - halfside);
    cairo_line_to(cr, x, y + halfside);
    cairo_new_sub_path(cr);
}

/**
 * gwy_cairo_times:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Half of the side of the smallest square containing the cross.
 *
 * Adds a times-shaped subpath to a Cairo context.
 *
 * A new subpath is started and terminated. All parameters are in user coordinates.
 **/
void
gwy_cairo_times(cairo_t *cr,
                gdouble x, gdouble y, gdouble halfside)
{
    cairo_move_to(cr, x - halfside, y - halfside);
    cairo_line_to(cr, x + halfside, y + halfside);
    cairo_move_to(cr, x + halfside, y - halfside);
    cairo_line_to(cr, x - halfside, y + halfside);
    cairo_new_sub_path(cr);
}

/**
 * gwy_cairo_square:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Half of the side of the square.
 *
 * Adds a square-shaped subpath to a Cairo context.
 *
 * A new subpath is started and closed.  All parameters are in user coordinates.
 **/
void
gwy_cairo_square(cairo_t *cr,
                 gdouble x, gdouble y, gdouble halfside)
{
    cairo_move_to(cr, x - halfside, y - halfside);
    cairo_line_to(cr, x + halfside, y - halfside);
    cairo_line_to(cr, x + halfside, y + halfside);
    cairo_line_to(cr, x - halfside, y + halfside);
    cairo_close_path(cr);
}

/**
 * gwy_cairo_diamond:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Half of the side of the containing square.
 *
 * Adds a diamond-shaped subpath to a Cairo context.
 *
 * A new subpath is started and closed.  All parameters are in user coordinates.
 **/
void
gwy_cairo_diamond(cairo_t *cr,
                  gdouble x, gdouble y, gdouble halfside)
{
    cairo_move_to(cr, x, y - halfside);
    cairo_line_to(cr, x + halfside, y);
    cairo_line_to(cr, x, y + halfside);
    cairo_line_to(cr, x - halfside, y);
    cairo_close_path(cr);
}

/**
 * gwy_cairo_triangle_up:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Half of the side of the containing square.
 *
 * Adds an upward pointing triangle-shaped subpath to a Cairo context.
 *
 * A new subpath is started and closed.  All parameters are in user coordinates.
 **/
void
gwy_cairo_triangle_up(cairo_t *cr,
                      gdouble x, gdouble y, gdouble halfside)
{
    cairo_move_to(cr, x, y - halfside);
    cairo_line_to(cr, x - halfside, y + halfside);
    cairo_line_to(cr, x + halfside, y + halfside);
    cairo_close_path(cr);
}

/**
 * gwy_cairo_triangle_down:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Half of the side of the containing square.
 *
 * Adds a downward pointing triangle-shaped subpath to a Cairo context.
 *
 * A new subpath is started and closed.  All parameters are in user coordinates.
 **/
void
gwy_cairo_triangle_down(cairo_t *cr,
                        gdouble x, gdouble y, gdouble halfside)
{
    cairo_move_to(cr, x, y + halfside);
    cairo_line_to(cr, x + halfside, y - halfside);
    cairo_line_to(cr, x - halfside, y - halfside);
    cairo_close_path(cr);
}

/**
 * gwy_cairo_triangle_left:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Half of the side of the containing square.
 *
 * Adds a leftward pointing triangle-shaped subpath to a Cairo context.
 *
 * A new subpath is started and closed.  All parameters are in user coordinates.
 **/
void
gwy_cairo_triangle_left(cairo_t *cr,
                        gdouble x, gdouble y, gdouble halfside)
{
    cairo_move_to(cr, x - halfside, y);
    cairo_line_to(cr, x + halfside, y + halfside);
    cairo_line_to(cr, x + halfside, y - halfside);
    cairo_close_path(cr);
}

/**
 * gwy_cairo_triangle_right:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Half of the side of the containing square.
 *
 * Adds a rightward pointing triangle-shaped subpath to a Cairo context.
 *
 * A new subpath is started and closed.  All parameters are in user coordinates.
 **/
void
gwy_cairo_triangle_right(cairo_t *cr,
                         gdouble x, gdouble y, gdouble halfside)
{
    cairo_move_to(cr, x + halfside, y);
    cairo_line_to(cr, x - halfside, y - halfside);
    cairo_line_to(cr, x - halfside, y + halfside);
    cairo_close_path(cr);
}

/**
 * gwy_cairo_asterisk:
 * @cr: A Cairo drawing context.
 * @x: Centre x-coordinate.
 * @y: Centre y-coordinate.
 * @halfside: Half of the side of the containing square.
 *
 * Adds a rightward pointing triangle-shaped subpath to a Cairo context.
 *
 * A new subpath is started and terminated.  All parameters are in user coordinates.
 **/
void
gwy_cairo_asterisk(cairo_t *cr,
                   gdouble x, gdouble y, gdouble halfside)
{
    gdouble qside = 0.5*halfside, dside = GWY_SQRT3*qside;

    cairo_move_to(cr, x, y - halfside);
    cairo_line_to(cr, x, y + halfside);
    cairo_move_to(cr, x + dside, y + qside);
    cairo_line_to(cr, x - dside, y - qside);
    cairo_move_to(cr, x - dside, y + qside);
    cairo_line_to(cr, x + dside, y - qside);
    cairo_new_sub_path(cr);
}

/**
 * gwy_cairo_region_add_line:
 * @region: Cario region to update.
 * @xfrom: X-coordinate of start point of the line.
 * @yfrom: Y-coordinate of start point of the line.
 * @xto: X-coordinate of end point of the line.
 * @yto: Y-coordinate of end point of the line.
 * @lw: Line width.
 *
 * Updates a Cairo region by adding the area containing an arbitrary straight line.
 **/
void
gwy_cairo_region_add_line(cairo_region_t *region,
                          gdouble xfrom, gdouble yfrom, gdouble xto, gdouble yto,
                          gdouble lw)
{
    cairo_rectangle_int_t rect;
    rect.x = (gint)floor(fmin(xfrom, xto) - 0.6*lw);
    rect.y = (gint)floor(fmin(yfrom, yto) - 0.6*lw);
    rect.width = (gint)ceil(fmax(xfrom, xto) + 0.6*lw) - rect.x;
    rect.height = (gint)ceil(fmax(yfrom, yto) + 0.6*lw) - rect.y;
    cairo_region_union_rectangle(region, &rect);
}

/**
 * gwy_cairo_region_add_crectangle:
 * @region: Cario region to update.
 * @xc: X-coordinate of rectangle centre.
 * @yc: Y-coordinate of rectangle centre.
 * @xr: Half of X-size of the rectangle.
 * @yr: Half of y-size of the rectangle.
 * @lw: Line width.
 *
 * Updates a Cairo region by adding the area containing a rectangle given the centre and half-size.
 **/
void
gwy_cairo_region_add_crectangle(cairo_region_t *region,
                                gdouble xc, gdouble yc, gdouble xr, gdouble yr,
                                gdouble lw)
{
    cairo_rectangle_int_t rect;
    rect.x = (gint)floor(xc - xr - 0.6*lw);
    rect.y = (gint)floor(yc - yr - 0.6*lw);
    rect.width = (gint)ceil(xc + xr + 0.6*lw) - rect.x;
    rect.height = (gint)ceil(yc + yr + 0.6*lw) - rect.y;
    cairo_region_union_rectangle(region, &rect);
}

/**
 * gwy_cairo_region_add_ellipse:
 * @region: Cario region to update.
 * @xc: X-coordinate of ellipse centre.
 * @yc: Y-coordinate of ellipse centre.
 * @xr: X-semiaxis of the ellipse.
 * @yr: Y-semiaxis of the ellipse.
 * @lw: Line width.
 *
 * Updates a Cairo region by adding the area containing an ellipse given the centre and semi-axes.
 **/
void
gwy_cairo_region_add_ellipse(cairo_region_t *region,
                             gdouble xc, gdouble yc, gdouble xr, gdouble yr,
                             gdouble lw)
{
    gwy_cairo_region_add_crectangle(region, xc, yc, xr, yr, lw);
}

/**
 * gwy_cairo_region_add_erectangle:
 * @region: Cario region to update.
 * @x1: X-coordinate of one rectangle corner.
 * @y1: Y-coordinate of one rectangle corner.
 * @x2: X-coordinate of the opposite rectangle corner.
 * @y2: Y-coordinate of the opposite rectangle corner.
 * @lw: Line width.
 *
 * Updates a Cairo region by adding the area containing a rectangle given two opposite corners.
 **/
void
gwy_cairo_region_add_erectangle(cairo_region_t *region,
                                gdouble x1, gdouble y1, gdouble x2, gdouble y2,
                                gdouble lw)
{
    GWY_ORDER(gdouble, x1, x2);
    GWY_ORDER(gdouble, y1, y2);
    cairo_rectangle_int_t rect;

    /* Top. */
    rect.x = (gint)floor(x1 - 0.6*lw);
    rect.y = (gint)floor(y1 - 0.6*lw);
    rect.width = (gint)ceil(x2 + 0.6*lw) - rect.x;
    rect.height = (gint)ceil(y1 + 0.6*lw) - rect.y;
    cairo_region_union_rectangle(region, &rect);

    /* Bottom. */
    rect.y = (gint)floor(y2 - 0.6*lw);
    rect.height = (gint)ceil(y2 + 0.6*lw) - rect.y;
    cairo_region_union_rectangle(region, &rect);

    /* Left. */
    rect.y = (gint)floor(y1 - 0.6*lw);
    rect.width = (gint)ceil(x1 + 0.6*lw) - rect.x;
    rect.height = (gint)ceil(y2 + 0.6*lw) - rect.y;
    cairo_region_union_rectangle(region, &rect);

    /* Right. */
    rect.x = (gint)floor(x2 - 0.6*lw);
    rect.width = (gint)ceil(x2 + 0.6*lw) - rect.x;
    cairo_region_union_rectangle(region, &rect);
}

/**
 * gwy_cairo_draw_or_add_thin_line:
 * @cr: A Cairo drawing context, or %NULL.
 * @region: Cario region to update, or %NULL.
 * @xfrom: X-coordinate of start point of the line.
 * @yfrom: Y-coordinate of start point of the line.
 * @xto: X-coordinate of end point of the line.
 * @yto: Y-coordinate of end point of the line.
 *
 * Draws a line and/or updates a Cairo region to add the line's area.
 *
 * This is a convenience function wrapping gwy_cairo_line() and gwy_cairo_region_add_line(). Either one or both are
 * called, depending on which of @cr and @region are non-NULL. The line is must be thin.
 **/
void
gwy_cairo_draw_or_add_thin_line(cairo_t *cr, cairo_region_t *region,
                                gdouble xfrom, gdouble yfrom, gdouble xto, gdouble yto)
{
    if (cr)
        gwy_cairo_line(cr, xfrom, yfrom, xto, yto);
    if (region)
        gwy_cairo_region_add_line(region, xfrom, yfrom, xto, yto, 1.0);
}

/**
 * gwy_cairo_region_add_layout:
 * @region: Cario region to update.
 * @layout: Pango layout to render at context's current coordinates.
 * @x: Text origin x-coordinate.
 * @y: Text origin y-coordinate.
 *
 * Updates a Cairo region by adding the area containing a Pango layout.
 *
 * The area corresponds to the text being drawn with pango_cairo_show_layout() after moving to (@x, @y).
 **/
void
gwy_cairo_region_add_layout(cairo_region_t *region,
                            PangoLayout *layout,
                            gdouble x, gdouble y)
{
    PangoRectangle prect;
    pango_layout_get_pixel_extents(layout, &prect, NULL);
    // The Pango macro documentation says things like that PANGO_ASCENT is positive when the glyphs extend above the
    // baseline, which they generally do. Yet I always get a negative ascent. Empirically, the following calculation
    // gives the box actually enclosing the text drawn in Cairo coordinates, which is probably good enough.
    cairo_rectangle_int_t crect = {
        .x = (gint)floor(x + prect.x),
        .y = (gint)floor(y + prect.y),
        .width = (gint)ceil(x + prect.x + prect.width) - crect.x,
        .height = (gint)ceil(y + prect.y + prect.height) - crect.y,
    };
    cairo_region_union_rectangle(region, &crect);
}

/**
 * gwy_cairo_draw_or_add_layout:
 * @cr: A Cairo drawing context, or %NULL.
 * @region: Cario region to update, or %NULL.
 * @layout: Pango layout to render at context's current coordinates.
 * @x: Text origin x-coordinate.
 * @y: Text origin y-coordinate.
 *
 * Renders a Pango layout and/or updates a Cairo region to add the text's area.
 *
 * The coordinates have the effect of cairo_move_to() to the specified point. Normally, this function would not be
 * called in the middle of drawing a path. Still, be aware the Cairo context position is modified.
 *
 * This is a convenience function wrapping pango_cairo_show_layout() and gwy_cairo_region_add_layout(). Either one or
 * both are called, depending on which of @cr and @region are non-NULL.
 **/
void
gwy_cairo_draw_or_add_layout(cairo_t *cr,
                             cairo_region_t *region,
                             PangoLayout *layout,
                             gdouble x, gdouble y)
{
    if (cr) {
        cairo_move_to(cr, x, y);
        pango_cairo_show_layout(cr, layout);
    }
    if (region)
        gwy_cairo_region_add_layout(region, layout, x, y);
}

/**
 * SECTION: cairo-utils
 * @title: Cairo drawing utils
 * @short_description: Auxiliary and impedance matching functions for Cairo drawing
 *
 * Drawing primitives for simple geometrical shapes such as gwy_cairo_cross() or gwy_cairo_triangle_up() are namely
 * useful for drawing markes and symbols on graphs because they are visually centered on given coordinates.
 * Nevertheless, the sizes are given as natural parameters. Use gwy_cairo_draw_symbol() to visually uniform sizes.
 **/

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
