// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Tests for glyph rendering.

use crate::renderer::Renderer;
#[cfg(target_os = "macos")]
use crate::util::layout_glyphs_apple_color_emoji;
use crate::util::{
    layout_glyphs_noto_cbtf, layout_glyphs_noto_colr, layout_glyphs_roboto,
    stops_blue_green_red_yellow,
};
use glifo::{FontEmbolden, Glyph};
use std::f64::consts::FRAC_PI_4;
use std::iter;
use std::sync::Arc;
use vello_common::color::Srgb;
use vello_common::color::palette::css::{BLACK, BLUE, GREEN, REBECCA_PURPLE};
use vello_common::kurbo::{Affine, Diagonal2, Point, Stroke};
use vello_common::paint::{Image, PaintType, PremulColor};
use vello_common::peniko::{
    Blob, Extend, FontData, Gradient, ImageQuality, ImageSampler, LinearGradientPosition,
};
use vello_common::pixmap::Pixmap;
use vello_dev_macros::vello_test;

fn render_transform_composition_rows(
    ctx: &mut impl Renderer,
    enable_caching: bool,
    hint: bool,
    reverse_x_shift: f64,
    paint: impl Into<PaintType>,
    layout: impl Fn(f32) -> (FontData, Vec<Glyph>),
) {
    let rows = [
        (Affine::IDENTITY, 20.0_f32, Affine::IDENTITY),
        (Affine::scale(20.0), 1.0_f32, Affine::IDENTITY),
        (Affine::IDENTITY, 10.0_f32, Affine::scale(2.0)),
        (Affine::scale(2.0), 5.0_f32, Affine::scale(2.0)),
        (
            Affine::translate((-4.0, 0.0)),
            20.0_f32,
            Affine::translate((4.0, 0.0)),
        ),
        (
            Affine::translate((-4.0, 0.0)) * Affine::scale(4.0),
            5.0_f32,
            Affine::translate((1.0, 0.0)),
        ),
        (
            Affine::translate((-1.0, 0.0)),
            40.0_f32,
            Affine::scale(0.5) * Affine::translate((2.0, 0.0)),
        ),
        (
            Affine::IDENTITY,
            20.0_f32,
            Affine::translate((10.0, -10.0))
                * Affine::rotate(FRAC_PI_4)
                * Affine::translate((-10.0, 10.0)),
        ),
        (
            Affine::IDENTITY,
            20.0_f32,
            Affine::translate((10.0, -10.0))
                * Affine::skew(0.35, 0.0)
                * Affine::translate((-10.0, 10.0)),
        ),
        (
            Affine::IDENTITY,
            20.0_f32,
            Affine::translate((10.0, -10.0))
                * Affine::skew(0.0, 0.2)
                * Affine::translate((-10.0, 10.0)),
        ),
        (
            Affine::scale_non_uniform(1.0, -1.0) * Affine::translate((0.0, 20.0)),
            20.0_f32,
            Affine::IDENTITY,
        ),
        (
            Affine::scale_non_uniform(-1.0, 1.0) * Affine::translate((-reverse_x_shift, 0.0)),
            20.0_f32,
            Affine::IDENTITY,
        ),
        (
            Affine::scale_non_uniform(-1.0, -1.0) * Affine::translate((-reverse_x_shift, 20.0)),
            20.0_f32,
            Affine::IDENTITY,
        ),
    ];

    ctx.set_paint(paint);

    let mut y = 28.35;
    for (run_scale_transform, font_size, glyph_transform) in rows {
        let (font, glyphs) = layout(font_size);
        ctx.set_transform(Affine::translate((16.0, y)) * run_scale_transform);
        ctx.glyph_run(&font)
            .font_size(font_size)
            .atlas_cache(enable_caching)
            .glyph_transform(glyph_transform)
            .hint(hint)
            .fill_glyphs(glyphs.into_iter());
        y += 30.0;
    }
}

#[vello_test(width = 300, height = 70, glyph)]
fn glyphs_filled(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(true)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 300, height = 70, glyph)]
fn glyphs_filled_unhinted(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(false)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 760, height = 140)]
fn glyphs_emboldened(ctx: &mut impl Renderer) {
    let font_size: f32 = 44_f32;
    let (font, glyphs) = layout_glyphs_roboto("this is regular and emboldened text", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .hint(true)
        .fill_glyphs(glyphs.into_iter());

    let (font, glyphs) = layout_glyphs_roboto("this is regular and emboldened text", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size) + 58.0)));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .font_embolden(FontEmbolden::new(Diagonal2::new(1.0, 1.0)))
        .hint(true)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 300, height = 70, glyph)]
fn glyphs_stroked(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(true)
        .stroke_glyphs(glyphs.into_iter());
}

#[vello_test(width = 300, height = 70, glyph)]
fn glyphs_stroked_unhinted(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(false)
        .stroke_glyphs(glyphs.into_iter());
}

#[vello_test(width = 300, height = 70, glyph)]
fn glyphs_stroked_scaled_up(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 5_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))).then_scale(10.0));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.set_stroke(Stroke {
        width: 0.3,
        ..Stroke::default()
    });
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(false)
        .stroke_glyphs(glyphs.into_iter());
}

#[vello_test(width = 300, height = 70, glyph)]
fn glyphs_large_stroke_width(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.set_stroke(Stroke {
        width: 3.0,
        ..Stroke::default()
    });
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .stroke_glyphs(glyphs.into_iter());
}

#[vello_test(width = 300, height = 120)]
fn glyphs_stroked_then_filled(ctx: &mut impl Renderer) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    render_roboto_with_mode(
        ctx,
        &font,
        font_size,
        glyphs.iter().copied(),
        Affine::translate((0., f64::from(font_size))),
        DrawMode::Stroke,
    );
    render_roboto_with_mode(
        ctx,
        &font,
        font_size,
        glyphs.into_iter(),
        Affine::translate((0., f64::from(font_size * 2.0))),
        DrawMode::Fill,
    );
}

#[vello_test(width = 300, height = 120)]
fn glyphs_filled_then_stroked(ctx: &mut impl Renderer) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    render_roboto_with_mode(
        ctx,
        &font,
        font_size,
        glyphs.iter().copied(),
        Affine::translate((0., f64::from(font_size))),
        DrawMode::Fill,
    );
    render_roboto_with_mode(
        ctx,
        &font,
        font_size,
        glyphs.into_iter(),
        Affine::translate((0., f64::from(font_size * 2.0))),
        DrawMode::Stroke,
    );
}

#[vello_test(width = 300, height = 70, glyph)]
fn glyphs_skewed(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .glyph_transform(Affine::skew(-20_f64.to_radians().tan(), 0.))
        .hint(true)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 300, height = 70, glyph)]
fn glyphs_skewed_unhinted(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .glyph_transform(Affine::skew(-20_f64.to_radians().tan(), 0.))
        .hint(false)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 250, height = 75, glyph)]
fn glyphs_skewed_long(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 20_f32;
    let (font, glyphs) = layout_glyphs_roboto(
        "Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit.\nSed ornare arcu lectus.",
        font_size,
    );

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE);
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .glyph_transform(Affine::skew(-10_f64.to_radians().tan(), 0.))
        .hint(true)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 250, height = 75, glyph)]
fn glyphs_skewed_long_unhinted(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 20_f32;
    let (font, glyphs) = layout_glyphs_roboto(
        "Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit.\nSed ornare arcu lectus.",
        font_size,
    );

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE);
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .glyph_transform(Affine::skew(-10_f64.to_radians().tan(), 0.))
        .hint(false)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 150, height = 125, glyph)]
fn glyphs_skewed_unskewed(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello,\nworld!", font_size);

    ctx.set_transform(
        Affine::translate((0., f64::from(font_size)))
            * Affine::skew(-20_f64.to_radians().tan(), 0.),
    );
    ctx.set_paint(REBECCA_PURPLE);
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .glyph_transform(Affine::skew(20_f64.to_radians().tan(), 0.))
        .hint(true)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 150, height = 125, glyph)]
fn glyphs_skewed_unskewed_unhinted(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello,\nworld!", font_size);

    ctx.set_transform(
        Affine::translate((0., f64::from(font_size)))
            * Affine::skew(-20_f64.to_radians().tan(), 0.),
    );
    ctx.set_paint(REBECCA_PURPLE);
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .glyph_transform(Affine::skew(20_f64.to_radians().tan(), 0.))
        .hint(false)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 150, height = 125, glyph)]
fn glyphs_scaled(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 25_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello,\nworld!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))).then_scale(2.0));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(true)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 150, height = 125, glyph)]
fn glyphs_scaled_unhinted(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 25_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello,\nworld!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))).then_scale(2.0));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(false)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 150, height = 125, glyph)]
fn glyphs_glyph_transform(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 25_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello,\nworld!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))).then_scale(2.0));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .glyph_transform(Affine::translate((10., 10.)))
        .hint(true)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 150, height = 125, glyph)]
fn glyphs_glyph_transform_unhinted(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 25_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello,\nworld!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))).then_scale(2.0));
    ctx.set_paint(REBECCA_PURPLE.with_alpha(0.5));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .glyph_transform(Affine::translate((10., 10.)))
        .hint(false)
        .fill_glyphs(glyphs.into_iter());
}

enum GlyphPaint {
    Gradient,
    Image,
}

fn glyphs_with_transformed_paint_inner(
    ctx: &mut impl Renderer,
    enable_caching: bool,
    mode: DrawMode,
    glyph_paint: GlyphPaint,
) {
    const SCENE_WIDTH: f64 = 300.0;
    const GRADIENT_WIDTH: f64 = SCENE_WIDTH / 2.0;
    const IMAGE_WIDTH: f64 = 4.0;
    const BASELINE_DELTA: f64 = 40.0;

    let font_size = 36.0_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello World", font_size);
    let text_width = glyphs
        .last()
        .map_or(0.0, |glyph| f64::from(glyph.x) + f64::from(font_size) * 0.6);
    let centered_x = ((SCENE_WIDTH - text_width) / 2.0) as f32;
    let paint = match glyph_paint {
        GlyphPaint::Gradient => PaintType::from(Gradient {
            kind: LinearGradientPosition {
                start: Point::new(0.0, 0.0),
                end: Point::new(GRADIENT_WIDTH, 0.0),
            }
            .into(),
            stops: stops_blue_green_red_yellow(),
            ..Default::default()
        }),
        GlyphPaint::Image => {
            let color_strip = stops_blue_green_red_yellow();
            let pixels = color_strip
                .0
                .iter()
                .map(|stop| {
                    PremulColor::from_alpha_color(stop.color.to_alpha_color::<Srgb>())
                        .as_premul_rgba8()
                })
                .collect();
            let image = ctx.get_image_source(Arc::new(Pixmap::from_parts(pixels, 4, 1)));
            PaintType::from(Image {
                image,
                sampler: ImageSampler {
                    x_extend: Extend::Pad,
                    y_extend: Extend::Pad,
                    // TODO: There seems to be a mismatch when using `Medium` here between Hybrid and CPU,
                    // so let's use `Low` for now.
                    quality: ImageQuality::Low,
                    alpha: 1.0,
                },
            })
        }
    };

    ctx.set_paint(paint);
    ctx.set_stroke(Stroke {
        width: 1.5,
        ..Stroke::default()
    });

    let mut baseline_y = 42.0;
    // 1) Visible gradient starts at green since we only translate the
    // glyph X, which shouldn't affect the paint transform.
    // 2) Visible gradient starts at 2, since paint is transformed along with
    // the path transform.
    // 3) Same as 2), just that the source is the paint transform instead of the
    // scene transform.
    // 4) Gradient should be shifted to the right, since it's affected by
    // scene transform + paint transform.
    for (use_render_transform, shift_gradient) in
        [(false, false), (true, false), (false, true), (true, true)]
    {
        let (transform, glyph_x) = if use_render_transform {
            (Affine::translate((centered_x as f64, baseline_y)), 0.0)
        } else {
            (Affine::translate((0.0, baseline_y)), centered_x)
        };
        let paint_transform = match glyph_paint {
            GlyphPaint::Gradient => {
                if shift_gradient {
                    Affine::translate((centered_x as f64, 0.0))
                } else {
                    Affine::IDENTITY
                }
            }
            GlyphPaint::Image => {
                let image_transform = Affine::scale_non_uniform(GRADIENT_WIDTH / IMAGE_WIDTH, 1.0);
                if shift_gradient {
                    Affine::translate((centered_x as f64, 0.0)) * image_transform
                } else {
                    image_transform
                }
            }
        };
        let row_glyphs = glyphs.iter().map(|glyph| Glyph {
            id: glyph.id,
            x: glyph.x + glyph_x,
            y: glyph.y,
        });

        ctx.set_transform(transform);
        ctx.set_paint_transform(paint_transform);
        let builder = ctx
            .glyph_run(&font)
            .font_size(font_size)
            .atlas_cache(enable_caching)
            .hint(false);

        match mode {
            DrawMode::Fill => builder.fill_glyphs(row_glyphs),
            DrawMode::Stroke => builder.stroke_glyphs(row_glyphs),
        }

        baseline_y += BASELINE_DELTA;
    }
}

#[vello_test(width = 300, height = 180, glyph)]
fn glyphs_with_gradient(ctx: &mut impl Renderer, enable_caching: bool) {
    glyphs_with_transformed_paint_inner(ctx, enable_caching, DrawMode::Fill, GlyphPaint::Gradient);
}

#[vello_test(width = 300, height = 180, glyph)]
fn glyphs_with_gradient_stroked(ctx: &mut impl Renderer, enable_caching: bool) {
    glyphs_with_transformed_paint_inner(
        ctx,
        enable_caching,
        DrawMode::Stroke,
        GlyphPaint::Gradient,
    );
}

#[vello_test(width = 300, height = 180, glyph)]
fn glyphs_with_image(ctx: &mut impl Renderer, enable_caching: bool) {
    glyphs_with_transformed_paint_inner(ctx, enable_caching, DrawMode::Fill, GlyphPaint::Image);
}

#[vello_test(width = 110, height = 410, glyph, hybrid_tolerance = 1)]
fn glyphs_transform_composition_rows_outline(ctx: &mut impl Renderer, enable_caching: bool) {
    render_transform_composition_rows(
        ctx,
        enable_caching,
        false,
        47.0,
        REBECCA_PURPLE.with_alpha(0.5),
        |font_size| layout_glyphs_roboto("Hello", font_size),
    );
}

#[vello_test(width = 110, height = 410, glyph, hybrid_tolerance = 1)]
fn glyphs_transform_composition_rows_outline_hinted(ctx: &mut impl Renderer, enable_caching: bool) {
    render_transform_composition_rows(
        ctx,
        enable_caching,
        true,
        47.0,
        REBECCA_PURPLE.with_alpha(0.5),
        |font_size| layout_glyphs_roboto("Hello", font_size),
    );
}

// Next two tests require high tolerance on CPU likely due to having to use bicubic interpolation, since
// we downscale a lot.

#[vello_test(width = 210, height = 410, skip_hybrid, glyph, cpu_u8_tolerance = 3)]
fn glyphs_transform_composition_rows_bitmap(ctx: &mut impl Renderer, enable_caching: bool) {
    render_transform_composition_rows(ctx, enable_caching, false, 100.0, BLACK, |font_size| {
        layout_glyphs_noto_cbtf("✅👀🎉🤠", font_size)
    });
}

#[vello_test(width = 210, height = 410, skip_hybrid, glyph, cpu_u8_tolerance = 3)]
fn glyphs_transform_composition_rows_bitmap_hinted(ctx: &mut impl Renderer, enable_caching: bool) {
    render_transform_composition_rows(ctx, enable_caching, true, 100.0, BLACK, |font_size| {
        layout_glyphs_noto_cbtf("✅👀🎉🤠", font_size)
    });
}

// TODO: The cached versions of COLR glyphs seem to have a slight shift, investigate.

#[vello_test(
    width = 210,
    height = 410,
    cpu_u8_tolerance = 3,
    hybrid_tolerance = 3,
    glyph
)]
fn glyphs_transform_composition_rows_colr(ctx: &mut impl Renderer, enable_caching: bool) {
    render_transform_composition_rows(ctx, enable_caching, false, 100.0, BLACK, |font_size| {
        layout_glyphs_noto_colr("✅👀🎉🤠", font_size)
    });
}

#[vello_test(
    width = 210,
    height = 410,
    cpu_u8_tolerance = 3,
    hybrid_tolerance = 3,
    glyph
)]
fn glyphs_transform_composition_rows_colr_hinted(ctx: &mut impl Renderer, enable_caching: bool) {
    render_transform_composition_rows(ctx, enable_caching, true, 100.0, BLACK, |font_size| {
        layout_glyphs_noto_colr("✅👀🎉🤠", font_size)
    });
}

#[vello_test(width = 60, height = 12, glyph)]
fn glyphs_small(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 10_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE);
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(true)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 60, height = 12, glyph)]
fn glyphs_small_unhinted(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 10_f32;
    let (font, glyphs) = layout_glyphs_roboto("Hello, world!", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.set_paint(REBECCA_PURPLE);
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(false)
        .fill_glyphs(glyphs.into_iter());
}

#[vello_test(width = 250, height = 70, skip_hybrid, glyph)]
fn glyphs_bitmap_noto(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_noto_cbtf("✅👀🎉🤠", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .fill_glyphs(glyphs.into_iter());
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DrawMode {
    Fill,
    Stroke,
}

fn render_roboto_with_mode(
    ctx: &mut impl Renderer,
    font: &FontData,
    font_size: f32,
    glyphs: impl Iterator<Item = Glyph> + Clone,
    transform: Affine,
    mode: DrawMode,
) {
    ctx.set_transform(transform);
    ctx.set_paint(REBECCA_PURPLE);
    ctx.set_stroke(Stroke {
        width: 3.0,
        ..Stroke::default()
    });
    let builder = ctx.glyph_run(font).font_size(font_size);

    match mode {
        DrawMode::Fill => {
            builder.fill_glyphs(glyphs);
        }
        DrawMode::Stroke => {
            builder.stroke_glyphs(glyphs);
        }
    }
}

#[vello_test(
    width = 250,
    height = 70,
    cpu_u8_tolerance = 1,
    hybrid_tolerance = 2,
    glyph
)]
fn glyphs_colr_noto(ctx: &mut impl Renderer, enable_caching: bool) {
    render_colr_noto_with_transform(
        ctx,
        Affine::translate((0., 50.)),
        enable_caching,
        DrawMode::Fill,
    );
}

#[vello_test(
    width = 250,
    height = 70,
    cpu_u8_tolerance = 1,
    hybrid_tolerance = 2,
    glyph
)]
fn glyphs_colr_noto_stroked(ctx: &mut impl Renderer, enable_caching: bool) {
    render_colr_noto_with_transform(
        ctx,
        Affine::translate((0., 50.)),
        enable_caching,
        DrawMode::Fill,
    );
}

#[vello_test(width = 100, height = 100, glyph)]
fn glyphs_colr_noto_overflow_centered(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size = 150.0;
    let (font, mut glyphs) = layout_glyphs_noto_colr("✅", font_size);
    let glyph = glyphs.pop().unwrap();

    let centered_glyph = Glyph {
        id: glyph.id,
        x: -25.0,
        y: 125.0,
    };

    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(false)
        .fill_glyphs(iter::once(centered_glyph));
}

#[vello_test(
    width = 500,
    height = 140,
    cpu_u8_tolerance = 1,
    hybrid_tolerance = 2,
    glyph
)]
fn glyphs_colr_noto_scaled_2x(ctx: &mut impl Renderer, enable_caching: bool) {
    render_colr_noto_with_transform(
        ctx,
        Affine::translate((0., 50.)).then_scale(2.0),
        enable_caching,
        DrawMode::Fill,
    );
}

#[vello_test(
    width = 125,
    height = 35,
    cpu_u8_tolerance = 1,
    hybrid_tolerance = 2,
    glyph
)]
fn glyphs_colr_noto_scaled_half(ctx: &mut impl Renderer, enable_caching: bool) {
    render_colr_noto_with_transform(
        ctx,
        Affine::translate((0., 50.)).then_scale(0.5),
        enable_caching,
        DrawMode::Fill,
    );
}

#[vello_test(
    width = 350,
    height = 350,
    cpu_u8_tolerance = 3,
    hybrid_tolerance = 2,
    glyph
)]
fn glyphs_colr_noto_rotated(ctx: &mut impl Renderer, enable_caching: bool) {
    render_colr_noto_with_transform(
        ctx,
        Affine::translate((175., 100.)) * Affine::rotate(FRAC_PI_4),
        enable_caching,
        DrawMode::Fill,
    );
}

#[vello_test(
    width = 600,
    height = 600,
    cpu_u8_tolerance = 2,
    hybrid_tolerance = 2,
    glyph
)]
fn glyphs_colr_noto_rotated_scaled(ctx: &mut impl Renderer, enable_caching: bool) {
    render_colr_noto_with_transform(
        ctx,
        Affine::translate((300., 150.)) * Affine::rotate(FRAC_PI_4) * Affine::scale(2.0),
        enable_caching,
        DrawMode::Fill,
    );
}

#[vello_test(
    width = 250,
    height = 140,
    cpu_u8_tolerance = 1,
    hybrid_tolerance = 2,
    glyph
)]
fn glyphs_colr_noto_scaled_non_uniform(ctx: &mut impl Renderer, enable_caching: bool) {
    render_colr_noto_with_transform(
        ctx,
        Affine::translate((0., 50.)) * Affine::scale_non_uniform(1.0, 2.0),
        enable_caching,
        DrawMode::Fill,
    );
}

#[vello_test(
    width = 300,
    height = 300,
    cpu_u8_tolerance = 2,
    hybrid_tolerance = 1,
    glyph
)]
fn glyphs_colr_noto_rotated_scaled_non_uniform(ctx: &mut impl Renderer, enable_caching: bool) {
    render_colr_noto_with_transform(
        ctx,
        Affine::translate((150., 150.))
            * Affine::rotate(FRAC_PI_4)
            * Affine::scale_non_uniform(1.0, 2.0),
        enable_caching,
        DrawMode::Fill,
    );
}

#[vello_test(width = 250, height = 70, skip_hybrid)]
fn glyphs_bitmap_noto_stroked(ctx: &mut impl Renderer) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_noto_cbtf("✅👀🎉🤠", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .stroke_glyphs(glyphs.into_iter());
}

#[cfg(target_os = "macos")]
#[vello_test(width = 200, height = 70, skip_hybrid, cpu_u8_tolerance = 2, glyph)]
fn glyphs_bitmap_apple(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_apple_color_emoji("✅👀🎉🤠", font_size);

    ctx.set_transform(Affine::translate((0., f64::from(font_size))));
    ctx.glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .fill_glyphs(glyphs.into_iter());
}

// In case anything changes here, compare to https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/web_tests/platform/linux/virtual/text-antialias/colrv1-expected.png
#[vello_test(
    width = 400,
    height = 960,
    hybrid_tolerance = 1,
    diff_pixels = 55,
    glyph
)]
fn glyphs_colr_test_glyphs(ctx: &mut impl Renderer, enable_caching: bool) {
    const TEST_FONT: &[u8] =
        include_bytes!("../../../examples/assets/colr_test_glyphs/test_glyphs-glyf_colr_1.ttf");
    let font = FontData::new(Blob::new(Arc::new(TEST_FONT)), 0);
    let num_glyphs = 221;

    let font_size = 40_f64;
    let cols = 10.0;

    let width = (font_size * cols) as u16;

    ctx.set_paint(BLACK);

    let mut cur_x = 0.0;
    let mut cur_y = font_size;

    let draw_glyphs = (0..=num_glyphs).filter(|n| match n {
        // Those are not COLR glyphs.
        0..8 => false,
        161..=165 => false,
        170..=176 => false,
        _ => true,
    });

    for id in draw_glyphs {
        if cur_x >= width as f64 {
            cur_x = 0.0;
            cur_y += font_size;
        }

        let glyph_iter = iter::once(Glyph { id, x: 0.0, y: 0.0 });

        ctx.set_transform(Affine::translate((cur_x, cur_y)));
        ctx.glyph_run(&font)
            .font_size(font_size as f32)
            .atlas_cache(enable_caching)
            .fill_glyphs(glyph_iter);

        cur_x += font_size;
    }

    cur_y += font_size;

    for color in [BLUE, GREEN] {
        cur_x = 0.0;
        cur_y += font_size;

        ctx.set_paint(color);

        for g in 148..=153 {
            let glyph_iter = iter::once(Glyph {
                id: g,
                x: 0.0,
                y: 0.0,
            });

            ctx.set_transform(Affine::translate((cur_x, cur_y)));
            ctx.glyph_run(&font)
                .font_size(font_size as f32)
                .atlas_cache(enable_caching)
                .fill_glyphs(glyph_iter);

            cur_x += font_size;
        }
    }
}

/// Hinting is disabled to preserve transforms passed to `prepare_colr_glyph`.
fn render_colr_noto_with_transform(
    ctx: &mut impl Renderer,
    transform: Affine,
    enable_caching: bool,
    mode: DrawMode,
) {
    let font_size: f32 = 50_f32;
    let (font, glyphs) = layout_glyphs_noto_colr("✅👀🎉🤠", font_size);

    ctx.set_transform(transform);
    let run = ctx
        .glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(false);

    if mode == DrawMode::Stroke {
        run.stroke_glyphs(glyphs.into_iter());
    } else {
        run.fill_glyphs(glyphs.into_iter());
    }
}

fn render_decorated_text(
    ctx: &mut impl Renderer,
    text: &str,
    font_size: f32,
    enable_caching: bool,
    glyph_transform: Option<Affine>,
    offset: f32,
    size: f32,
    buffer: f32,
) {
    let (font, glyphs) = layout_glyphs_roboto(text, font_size);

    ctx.set_paint(REBECCA_PURPLE);

    let mut fill_builder = ctx
        .glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(false);
    if let Some(gt) = glyph_transform {
        fill_builder = fill_builder.glyph_transform(gt);
    }
    fill_builder.fill_glyphs(glyphs.iter().copied());

    let x_end = glyphs.last().map_or(0.0, |g| g.x + font_size * 0.6);
    let mut deco_builder = ctx
        .glyph_run(&font)
        .font_size(font_size)
        .atlas_cache(enable_caching)
        .hint(glyph_transform.is_none());
    if let Some(gt) = glyph_transform {
        deco_builder = deco_builder.glyph_transform(gt);
    }
    deco_builder.render_decoration(glyphs.into_iter(), 0.0..=x_end, 0.0, offset, size, buffer);
}

#[vello_test(width = 300, height = 180, glyph)]
fn glyphs_decoration_offset_values(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size = 30.0_f32;
    for (i, offset) in [-6.0_f32, -2.0, 0.0, 8.0, 15.0].iter().enumerate() {
        let y = 30.0 + (i as f64) * 32.0;
        ctx.set_transform(Affine::translate((0., y)));
        render_decorated_text(
            ctx,
            "Happy joyful",
            font_size,
            enable_caching,
            None,
            *offset,
            1.5,
            1.5,
        );
    }
}

#[vello_test(width = 180, height = 180, glyph)]
fn glyphs_decoration_size_values(ctx: &mut impl Renderer, enable_caching: bool) {
    let font_size = 30.0_f32;
    for (i, size) in [0.5_f32, 1.0, 2.0, 4.0].iter().enumerate() {
        let y = 30.0 + (i as f64) * 38.0;
        ctx.set_transform(Affine::translate((0., y)));
        render_decorated_text(
            ctx,
            "Happy joyful",
            font_size,
            enable_caching,
            None,
            -2.0,
            *size,
            1.5,
        );
    }
}

#[vello_test(width = 180, height = 70, glyph)]
fn glyphs_decoration_no_descenders(ctx: &mut impl Renderer, enable_caching: bool) {
    ctx.set_transform(Affine::translate((0., 50.)));
    render_decorated_text(ctx, "HELLO", 50.0, enable_caching, None, -2.0, 2.0, 1.5);
}

#[vello_test(width = 100, height = 150, glyph, hybrid_tolerance = 1)]
fn glyphs_decoration_transformed(ctx: &mut impl Renderer, enable_caching: bool) {
    let text = "Happy";
    let rows: [(Affine, f32, Option<Affine>, f64); 4] = [
        // Run-level scale absorbs into font_size and underline scaled.
        (Affine::scale(2.0), 12.0, None, 30.0),
        // Glyph-level scale (affects skip-ink outline transform)
        (Affine::IDENTITY, 10.0, Some(Affine::scale(1.2)), 40.0),
        // Y-flip (decoration should appear above the flipped text)
        (
            Affine::scale_non_uniform(1.0, -1.0) * Affine::translate((0.0, 20.0)),
            20.0,
            None,
            10.0,
        ),
        // Rotated text
        (Affine::rotate(FRAC_PI_4), 12.0, None, 40.0),
    ];

    let mut y = 30.0;
    for (run_transform, font_size, glyph_transform, buffer) in rows {
        ctx.set_transform(Affine::translate((16.0, y)) * run_transform);
        render_decorated_text(
            ctx,
            text,
            font_size,
            enable_caching,
            glyph_transform,
            -1.0,
            1.0,
            1.0,
        );
        y += buffer;
    }
}
