/*
 * Copyright (c) 2025, Nico Weber <thakis@chromium.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/BitStream.h>
#include <AK/Enumerate.h>
#include <AK/GenericShorthands.h>
#include <LibGfx/EdgeFlagPathRasterizer.h>
#include <LibGfx/Painter.h>
#include <LibGfx/Vector2.h>
#include <LibPDF/ColorSpace.h>
#include <LibPDF/CommonNames.h>
#include <LibPDF/Document.h>
#include <LibPDF/Shading.h>

namespace PDF {

namespace {

// TABLE 4.28 Entries common to all shading dictionaries
struct CommonEntries {
    // "(Required) The color space in which color values are expressed. This may be
    //  any device, CIE-based, or special color space except a Pattern space."
    AK::NonnullRefPtr<ColorSpaceWithFloatArgs> color_space;

    // "(Optional) An array of color components appropriate to the color space,
    //  specifying a single background color value. If present, this color is used, be-
    //  fore any painting operation involving the shading, to fill those portions of the
    //  area to be painted that lie outside the bounds of the shading object
    //  Note: The background color is applied only when the shading is used as part of
    //  a shading pattern, not when it is painted directly with the sh operator."
    Optional<Vector<float>> background {};

    // "(Optional) An array of four numbers giving the left, bottom, right, and top
    //  coordinates, respectively, of the shading’s bounding box. The coordinates are
    //  interpreted in the shading’s target coordinate space. If present, this bounding
    //  box is applied as a temporary clipping boundary when the shading is painted,
    //  in addition to the current clipping path and any other clipping boundaries in
    //  effect at that time."
    Optional<Gfx::FloatRect> b_box {};

    // "(Optional) A flag indicating whether to filter the shading function to prevent
    //  aliasing artifacts. [...] Anti-aliasing
    //  may not be implemented on some output devices, in which case this flag is
    //  ignored. Default value: false."
    // We currently ignore this.
    bool anti_alias { false };

    PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const
    {
        if (!background.has_value())
            return Optional<Gfx::Color> {};
        return TRY(color_space->color(background.value()));
    }
};

PDFErrorOr<CommonEntries> read_common_entries(Document* document, DictObject const& shading_dict, Renderer& renderer)
{
    // "(Required) The color space in which color values are expressed. This may be
    //  any device, CIE-based, or special color space except a Pattern space. See
    //  “Color Space: Special Considerations” on page 306 for further information."
    auto color_space_object = TRY(shading_dict.get_object(document, CommonNames::ColorSpace));
    auto color_space = TRY(ColorSpace::create(document, move(color_space_object), renderer));
    if (color_space->family() == ColorSpaceFamily::Pattern)
        return Error::malformed_error("Shading color space must not be pattern");

    CommonEntries common_entries { .color_space = verify_cast<ColorSpaceWithFloatArgs>(*color_space) };

    if (shading_dict.contains(CommonNames::Background)) {
        auto background_array = TRY(shading_dict.get_array(document, CommonNames::Background));
        Vector<float> background;
        for (auto const& value : background_array->elements())
            background.append(value.to_float());
        common_entries.background = move(background);
    }

    if (shading_dict.contains(CommonNames::BBox)) {
        auto bbox_array = TRY(shading_dict.get_array(document, CommonNames::BBox));
        if (bbox_array->size() != 4)
            return Error::malformed_error("BBox must have 4 elements");
        auto bbox = Gfx::FloatRect::from_two_points(
            { bbox_array->at(0).to_float(), bbox_array->at(1).to_float() },
            { bbox_array->at(2).to_float(), bbox_array->at(3).to_float() });
        common_entries.b_box = bbox;
    }

    if (shading_dict.contains(CommonNames::AntiAlias))
        common_entries.anti_alias = TRY(document->resolve(shading_dict.get_value(CommonNames::AntiAlias))).get<bool>();

    return common_entries;
}

using ShadingFunctionsType = Variant<Empty, NonnullRefPtr<Function>, Vector<NonnullRefPtr<Function>>>;
using NonemptyShadingFunctionsType = Variant<NonnullRefPtr<Function>, Vector<NonnullRefPtr<Function>>>;

static PDFErrorOr<NonemptyShadingFunctionsType> read_shading_functions(Document* document, NonnullRefPtr<DictObject> shading_dict, NonnullRefPtr<ColorSpace> color_space, ReadonlySpan<float> function_input)
{
    if (color_space->family() == ColorSpaceFamily::Indexed)
        return Error::malformed_error("Function cannot be used with Indexed color space");

    auto function_object = TRY(shading_dict->get_object(document, CommonNames::Function));
    if (function_object->is<ArrayObject>()) {
        auto function_array = function_object->cast<ArrayObject>();
        Vector<NonnullRefPtr<Function>> functions_vector;
        if (function_array->size() != static_cast<size_t>(color_space->number_of_components()))
            return Error::malformed_error("Function array must have as many elements as color space has components");
        for (size_t i = 0; i < function_array->size(); ++i) {
            auto function = TRY(Function::create(document, TRY(document->resolve_to<Object>(function_array->at(i)))));
            if (TRY(function->evaluate(function_input)).size() != 1)
                return Error::malformed_error("Function must have 1 output component");
            TRY(functions_vector.try_append(move(function)));
        }
        return functions_vector;
    }
    auto function = TRY(Function::create(document, function_object));
    if (TRY(function->evaluate(function_input)).size() != static_cast<size_t>(color_space->number_of_components()))
        return Error::malformed_error("Function must have as many output components as color space");
    return function;
}

static PDFErrorOr<NonemptyShadingFunctionsType> read_shading_functions(Document* document, NonnullRefPtr<DictObject> shading_dict, NonnullRefPtr<ColorSpace> color_space, float function_input)
{
    return read_shading_functions(document, shading_dict, color_space, Array { function_input });
}

class FunctionBasedShading final : public Shading {
public:
    static PDFErrorOr<NonnullRefPtr<FunctionBasedShading>> create(Document*, NonnullRefPtr<DictObject>, CommonEntries);

    virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
    virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
    virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;

private:
    FunctionBasedShading(CommonEntries common_entries, Gfx::FloatRect domain, Gfx::AffineTransform matrix, NonemptyShadingFunctionsType functions)
        : m_common_entries(move(common_entries))
        , m_domain(domain)
        , m_matrix(matrix)
        , m_functions(move(functions))
    {
    }

    CommonEntries m_common_entries;
    Gfx::FloatRect m_domain;
    Gfx::AffineTransform m_matrix;
    NonemptyShadingFunctionsType m_functions;
};

PDFErrorOr<NonnullRefPtr<FunctionBasedShading>> FunctionBasedShading::create(Document* document, NonnullRefPtr<DictObject> shading_dict, CommonEntries common_entries)
{
    // TABLE 4.29 Additional entries specific to a type 1 shading dictionary

    // "(Optional) An array of four numbers [ xmin xmax ymin ymax ] specifying the
    //  rectangular domain of coordinates over which the color function(s) are defined.
    //  Default value: [ 0.0 1.0 0.0 1.0 ]."
    Gfx::FloatRect domain { 0.0f, 0.0f, 1.0f, 1.0f };
    if (shading_dict->contains(CommonNames::Domain)) {
        auto domain_array = TRY(shading_dict->get_array(document, CommonNames::Domain));
        if (domain_array->size() != 4)
            return Error::malformed_error("Domain must have 4 elements");
        float xmin = domain_array->at(0).to_float();
        float xmax = domain_array->at(1).to_float();
        float ymin = domain_array->at(2).to_float();
        float ymax = domain_array->at(3).to_float();
        domain = Gfx::FloatRect::from_two_points({ xmin, ymin }, { xmax, ymax });
    }

    // "(Optional) An array of six numbers specifying a transformation matrix mapping
    //  the coordinate space specified by the Domain entry into the shading’s target co-
    //  ordinate space. For example, to map the domain rectangle [ 0.0 1.0 0.0 1.0 ] to a
    //  1-inch square with lower-left corner at coordinates (100, 100) in default user
    //  space, the Matrix value would be [ 72 0 0 72 100 100 ]. Default value: the iden-
    //  tity matrix [ 1 0 0 1 0 0 ]."
    Gfx::AffineTransform matrix;
    if (shading_dict->contains(CommonNames::Matrix)) {
        auto matrix_array = TRY(shading_dict->get_array(document, CommonNames::Matrix));
        if (matrix_array->size() != 6)
            return Error::malformed_error("Matrix must have 6 elements");
        matrix = Gfx::AffineTransform {
            matrix_array->at(0).to_float(),
            matrix_array->at(1).to_float(),
            matrix_array->at(2).to_float(),
            matrix_array->at(3).to_float(),
            matrix_array->at(4).to_float(),
            matrix_array->at(5).to_float(),
        };
    }

    // "(Required) A 2-in, n-out function or an array of n 2-in, 1-out functions (where n
    //  is the number of color components in the shading dictionary’s color space). Each
    //  function’s domain must be a superset of that of the shading dictionary. If the val-
    //  ue returned by the function for a given color component is out of range, it is ad-
    //  justed to the nearest valid value."
    auto functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, Array { domain.x(), domain.y() }));

    return adopt_ref(*new FunctionBasedShading(move(common_entries), domain, matrix, move(functions)));
}

PDFErrorOr<void> FunctionBasedShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
{
    auto maybe_inverse_ctm = ctm.inverse();
    if (!maybe_inverse_ctm.has_value())
        return {};
    auto inverse_ctm = maybe_inverse_ctm.value();

    auto& bitmap = painter.target();

    auto scale = painter.scale();
    auto clip_rect = painter.clip_rect() * scale;

    Vector<float, 4> color_components;
    color_components.resize(m_common_entries.color_space->number_of_components());

    auto maybe_to_domain = m_matrix.inverse();
    if (!maybe_to_domain.has_value())
        return Error::malformed_error("Matrix is not invertible");
    auto to_domain = maybe_to_domain.value();

    for (int y = clip_rect.top(); y < clip_rect.bottom(); ++y) {
        for (int x = clip_rect.left(); x < clip_rect.right(); ++x) {
            Gfx::FloatPoint shading_point = inverse_ctm.map(Gfx::FloatPoint { x, y } / scale);
            auto domain_point = to_domain.map(shading_point);
            if (!m_domain.contains(domain_point))
                continue;

            TRY(m_functions.visit(
                [&](Function const& function) -> PDFErrorOr<void> {
                    auto result = TRY(function.evaluate(to_array({ domain_point.x(), domain_point.y() })));
                    result.copy_to(color_components);
                    return {};
                },
                [&](Vector<NonnullRefPtr<Function>> const& functions) -> PDFErrorOr<void> {
                    for (size_t i = 0; i < functions.size(); ++i) {
                        auto result = TRY(functions[i]->evaluate(to_array({ domain_point.x(), domain_point.y() })));
                        color_components[i] = result[0];
                    }
                    return {};
                }));

            auto color = TRY(m_common_entries.color_space->color(color_components));
            bitmap.scanline(y)[x] = color.value();
        }
    }

    return {};
}

class AxialShading final : public Shading {
public:
    static PDFErrorOr<NonnullRefPtr<AxialShading>> create(Document*, NonnullRefPtr<DictObject>, CommonEntries);

    virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
    virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
    virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;

private:
    AxialShading(CommonEntries common_entries, Gfx::FloatPoint start, Gfx::FloatPoint end, float t0, float t1, NonemptyShadingFunctionsType functions, bool extend_start, bool extend_end)
        : m_common_entries(move(common_entries))
        , m_start(start)
        , m_end(end)
        , m_t0(t0)
        , m_t1(t1)
        , m_functions(move(functions))
        , m_extend_start(extend_start)
        , m_extend_end(extend_end)
    {
    }

    CommonEntries m_common_entries;
    Gfx::FloatPoint m_start;
    Gfx::FloatPoint m_end;
    float m_t0 { 0.0f };
    float m_t1 { 1.0f };
    NonemptyShadingFunctionsType m_functions;
    bool m_extend_start { false };
    bool m_extend_end { false };
};

PDFErrorOr<NonnullRefPtr<AxialShading>> AxialShading::create(Document* document, NonnullRefPtr<DictObject> shading_dict, CommonEntries common_entries)
{
    // TABLE 4.30 Additional entries specific to a type 2 shading dictionary
    // "(Required) An array of four numbers [ x0 y0 x1 y1 ] specifying the starting and
    //  ending coordinates of the axis, expressed in the shading’s target coordinate
    //  space."
    auto coords = TRY(shading_dict->get_array(document, CommonNames::Coords));
    if (coords->size() != 4)
        return Error::malformed_error("Coords must have 4 elements");
    Gfx::FloatPoint start { coords->at(0).to_float(), coords->at(1).to_float() };
    Gfx::FloatPoint end { coords->at(2).to_float(), coords->at(3).to_float() };

    // "(Optional) An array of two numbers [ t0 t1 ] specifying the limiting values of a
    //  parametric variable t. The variable is considered to vary linearly between these
    //  two values as the color gradient varies between the starting and ending points of
    //  the axis. The variable t becomes the input argument to the color function(s). De-
    //  fault value: [ 0.0 1.0 ]."
    float t0 = 0.0f;
    float t1 = 1.0f;
    if (shading_dict->contains(CommonNames::Domain)) {
        auto domain_array = TRY(shading_dict->get_array(document, CommonNames::Domain));
        if (domain_array->size() != 2)
            return Error::malformed_error("Domain must have 2 elements");
        t0 = domain_array->at(0).to_float();
        t1 = domain_array->at(1).to_float();
    }

    // "(Required) A 1-in, n-out function or an array of n 1-in, 1-out functions (where n
    //  is the number of color components in the shading dictionary’s color space). The
    //  function(s) are called with values of the parametric variable t in the domain de-
    //  fined by the Domain entry. Each function’s domain must be a superset of that of
    //  the shading dictionary. If the value returned by the function for a given color
    //  component is out of range, it is adjusted to the nearest valid value."
    auto functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, t0));

    // "(Optional) An array of two boolean values specifying whether to extend the
    //  shading beyond the starting and ending points of the axis, respectively. Default
    //  value: [ false false ]."
    bool extend_start = false;
    bool extend_end = false;
    if (shading_dict->contains(CommonNames::Extend)) {
        auto extend_array = TRY(shading_dict->get_array(document, CommonNames::Extend));
        if (extend_array->size() != 2)
            return Error::malformed_error("Extend must have 2 elements");
        extend_start = extend_array->at(0).get<bool>();
        extend_end = extend_array->at(1).get<bool>();
    }

    return adopt_ref(*new AxialShading(move(common_entries), start, end, t0, t1, move(functions), extend_start, extend_end));
}

PDFErrorOr<void> AxialShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
{
    auto maybe_inverse_ctm = ctm.inverse();
    if (!maybe_inverse_ctm.has_value())
        return {};
    auto inverse_ctm = maybe_inverse_ctm.value();

    auto& bitmap = painter.target();

    auto scale = painter.scale();
    auto clip_rect = painter.clip_rect() * scale;

    Vector<float, 4> color_components;
    color_components.resize(m_common_entries.color_space->number_of_components());

    for (int y = clip_rect.top(); y < clip_rect.bottom(); ++y) {
        for (int x = clip_rect.left(); x < clip_rect.right(); ++x) {
            Gfx::FloatPoint pdf = inverse_ctm.map(Gfx::FloatPoint { x, y } / scale);

            // FIXME: Normalize m_end to have unit length from m_start.
            Gfx::FloatVector2 to_point { pdf.x() - m_start.x(), pdf.y() - m_start.y() };
            Gfx::FloatVector2 to_end { m_end.x() - m_start.x(), m_end.y() - m_start.y() };
            float x_prime = to_point.dot(to_end) / to_end.dot(to_end);

            float t;
            if (0 <= x_prime && x_prime <= 1)
                t = m_t0 + (m_t1 - m_t0) * x_prime;
            else if (x_prime < 0) {
                if (!m_extend_start)
                    continue;
                t = m_t0;
            } else {
                if (!m_extend_end)
                    continue;
                t = m_t1;
            }

            TRY(m_functions.visit(
                [&](Function const& function) -> PDFErrorOr<void> {
                    auto result = TRY(function.evaluate(to_array({ t })));
                    result.copy_to(color_components);
                    return {};
                },
                [&](Vector<NonnullRefPtr<Function>> const& functions) -> PDFErrorOr<void> {
                    for (size_t i = 0; i < functions.size(); ++i) {
                        auto result = TRY(functions[i]->evaluate(to_array({ t })));
                        color_components[i] = result[0];
                    }
                    return {};
                }));

            auto color = TRY(m_common_entries.color_space->color(color_components));
            bitmap.scanline(y)[x] = color.value();
        }
    }

    return {};
}

class RadialShading final : public Shading {
public:
    static PDFErrorOr<NonnullRefPtr<RadialShading>> create(Document*, NonnullRefPtr<DictObject>, CommonEntries);

    virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
    virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
    virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;

private:
    RadialShading(CommonEntries common_entries, Gfx::FloatPoint start, float start_radius, Gfx::FloatPoint end, float end_radius, float t0, float t1, NonemptyShadingFunctionsType functions, bool extend_start, bool extend_end)
        : m_common_entries(move(common_entries))
        , m_start(start)
        , m_start_radius(start_radius)
        , m_end(end)
        , m_end_radius(end_radius)
        , m_t0(t0)
        , m_t1(t1)
        , m_functions(move(functions))
        , m_extend_start(extend_start)
        , m_extend_end(extend_end)
    {
    }

    CommonEntries m_common_entries;
    Gfx::FloatPoint m_start;
    float m_start_radius { 0.0f };
    Gfx::FloatPoint m_end;
    float m_end_radius { 0.0f };
    float m_t0 { 0.0f };
    float m_t1 { 1.0f };
    NonemptyShadingFunctionsType m_functions;
    bool m_extend_start { false };
    bool m_extend_end { false };
};

PDFErrorOr<NonnullRefPtr<RadialShading>> RadialShading::create(Document* document, NonnullRefPtr<DictObject> shading_dict, CommonEntries common_entries)
{
    // TABLE 4.31 Additional entries specific to a type 3 shading dictionary
    // "(Required) An array of six numbers [ x0 y0 r0 x1 y1 r1 ] specifying the centers and
    //  radii of the starting and ending circles, expressed in the shading’s target coor-
    //  dinate space. The radii r0 and r1 must both be greater than or equal to 0. If one
    //  radius is 0, the corresponding circle is treated as a point; if both are 0, nothing is
    //  painted."
    auto coords = TRY(shading_dict->get_array(document, CommonNames::Coords));
    if (coords->size() != 6)
        return Error::malformed_error("Coords must have 6 elements");
    Gfx::FloatPoint start { coords->at(0).to_float(), coords->at(1).to_float() };
    float start_radius = coords->at(2).to_float();
    Gfx::FloatPoint end { coords->at(3).to_float(), coords->at(4).to_float() };
    float end_radius = coords->at(5).to_float();

    // "(Optional) An array of two numbers [ t0 t1 ] specifying the limiting values of a
    //  parametric variable t. The variable is considered to vary linearly between these
    //  two values as the color gradient varies between the starting and ending circles.
    //  The variable t becomes the input argument to the color function(s). Default
    //  value: [ 0.0 1.0 ]."
    float t0 = 0.0f;
    float t1 = 1.0f;
    if (shading_dict->contains(CommonNames::Domain)) {
        auto domain_array = TRY(shading_dict->get_array(document, CommonNames::Domain));
        if (domain_array->size() != 2)
            return Error::malformed_error("Domain must have 2 elements");
        t0 = domain_array->at(0).to_float();
        t1 = domain_array->at(1).to_float();
    }

    // "(Required) A 1-in, n-out function or an array of n 1-in, 1-out functions (where n
    //  is the number of color components in the shading dictionary’s color space). The
    //  function(s) are called with values of the parametric variable t in the domain de-
    //  fined by the shading dictionary’s Domain entry. Each function’s domain must be
    //  a superset of that of the shading dictionary. If the value returned by the function
    //  for a given color component is out of range, it is adjusted to the nearest valid val-
    //  ue."
    auto functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, t0));

    // "(Optional) An array of two boolean values specifying whether to extend the
    //  shading beyond the starting and ending circles, respectively. Default value:
    //  [ false false ]."
    bool extend_start = false;
    bool extend_end = false;
    if (shading_dict->contains(CommonNames::Extend)) {
        auto extend_array = TRY(shading_dict->get_array(document, CommonNames::Extend));
        if (extend_array->size() != 2)
            return Error::malformed_error("Extend must have 2 elements");
        extend_start = extend_array->at(0).get<bool>();
        extend_end = extend_array->at(1).get<bool>();
    }

    return adopt_ref(*new RadialShading(move(common_entries), start, start_radius, end, end_radius, t0, t1, move(functions), extend_start, extend_end));
}

PDFErrorOr<void> RadialShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
{
    auto maybe_inverse_ctm = ctm.inverse();
    if (!maybe_inverse_ctm.has_value())
        return {};
    auto inverse_ctm = maybe_inverse_ctm.value();

    auto& bitmap = painter.target();

    auto scale = painter.scale();
    auto clip_rect = painter.clip_rect() * scale;

    Vector<float, 4> color_components;
    color_components.resize(m_common_entries.color_space->number_of_components());

    // FIXME: Use smaller box if the circles are nested and the outer circle is not extended.

    for (int y = clip_rect.top(); y < clip_rect.bottom(); ++y) {
        for (int x = clip_rect.left(); x < clip_rect.right(); ++x) {
            Gfx::FloatPoint point = inverse_ctm.map(Gfx::FloatPoint { x, y } / scale);

            // The spec explains how to get a point given s. We want to solve the inverse problem:
            // The current pixel is at p. We want to find the s where (c(s) - p)^2 = r(s)^2 (eq 1).
            // Per spec, the circle depending on s has its center at
            //
            //     c(s) = c0 + s * (c1 - c0)
            //
            // and a radius of
            //
            //     r(s) = r0 + s * (r1 - r0)
            //
            // Putting that into (eq 1):
            //
            //     (c0 + s * (c1 - c0) - p)^2 = (r0 + s * (r1 - r0))^2
            //
            // Rearranging terms, we get a quadratic equation in s:
            //
            //     A * s^2 + B * s + C = 0
            //
            // with:
            //
            //     A = (c1 - c0)^2 - (r1 - r0)^2
            //     B = -2 * ((c1 - c0) * (p - c0) + (r1 - r0) * r0)
            //     C = (c0 - p)^2 - r0^2
            //
            // When both circles touch in one point, A = 0 and we get a linear equation instead.

            // FIXME: Normalize m_end to have unit length from m_start.
            Gfx::FloatVector2 to_point { point.x() - m_start.x(), point.y() - m_start.y() };
            Gfx::FloatVector2 to_end { m_end.x() - m_start.x(), m_end.y() - m_start.y() };
            float dr = m_end_radius - m_start_radius;

            float A = to_end.dot(to_end) - dr * dr;
            float B = -2 * (to_end.dot(to_point) + dr * m_start_radius);
            float C = to_point.dot(to_point) - m_start_radius * m_start_radius;
            float s_0;
            float s_1;
            if (A != 0) {
                float discriminant = B * B - 4 * A * C;
                if (discriminant < 0)
                    continue;

                s_0 = (-B + sqrt(discriminant)) / (2 * A);
                s_1 = (-B - sqrt(discriminant)) / (2 * A);
                if (A < 0)
                    swap(s_0, s_1);
            } else {
                // Linear case: B * s + C = 0
                s_0 = -C / B;
                s_1 = s_0;
            }

            float s;
            if (to_end.length() < max(m_start_radius, m_end_radius) - min(m_start_radius, m_end_radius)) {
                // One circle is inside the other one.
                // One of s_0, s_1 will be 0..1 in the main gradient part, and the other one will be negative in the whole circle.
                s = m_start_radius < m_end_radius ? s_0 : s_1;
                if (s < 0) {
                    if (!m_extend_start)
                        continue;
                    s = 0;
                } else if (s > 1) {
                    if (!m_extend_end)
                        continue;
                    s = 1;
                }
            } else {
                // Two disjoint or overlapping circles. Assuming the start circle is to the left of the end circle,
                // s_0 is the value of s when the left side of the circle touches the current point, while s_1 is the value
                // of s when the right side of the circle touches the current point. The forward formulation in the spec
                // says we're drawing the circles in increasing order of s, so the s_0 value is the one that draws on
                // top for the points drawn by both edges.
                // s_0 is in [0..1] in the start circle up to outside of the end circle (where it's > 1).
                // s_1 is in [0..1] in the end circle up to outside of the start circle (where it's < 0).
                s = s_0 >= 0 && s_0 <= 1 ? s_0 : s_1;

                if (m_extend_start) {
                    if (m_start_radius <= m_end_radius && s < -m_start_radius / dr)
                        continue;
                    if (s < 0)
                        s = 0;
                } else {
                    if (s < 0 && !(m_extend_end && s_0 > 0))
                        continue;
                }

                if (m_extend_end) {
                    if (m_start_radius > m_end_radius && s > -m_start_radius / dr)
                        continue;
                    if (s_0 > 1)
                        s = 1;
                } else {
                    if (s > 1)
                        continue;
                }
            }

            float t = m_t0 + s * (m_t1 - m_t0);

            TRY(m_functions.visit(
                [&](Function const& function) -> PDFErrorOr<void> {
                    auto result = TRY(function.evaluate(to_array({ t })));
                    result.copy_to(color_components);
                    return {};
                },
                [&](Vector<NonnullRefPtr<Function>> const& functions) -> PDFErrorOr<void> {
                    for (size_t i = 0; i < functions.size(); ++i) {
                        auto result = TRY(functions[i]->evaluate(to_array({ t })));
                        color_components[i] = result[0];
                    }
                    return {};
                }));

            auto color = TRY(m_common_entries.color_space->color(color_components));
            bitmap.scanline(y)[x] = color.value();
        }
    }

    return {};
}

using GouraudColor = Vector<float, 4>;

struct GouraudBounds {
    GouraudColor min;
    GouraudColor max;
};

static GouraudBounds bounds_from_decode_array(ReadonlySpan<float> decode_array)
{
    VERIFY(decode_array.size() % 2 == 0);
    size_t number_of_components = decode_array.size() / 2;
    GouraudBounds bounds;
    bounds.min.resize(number_of_components);
    bounds.max.resize(number_of_components);
    for (size_t i = 0; i < number_of_components; ++i) {
        bounds.min[i] = decode_array[i * 2];
        bounds.max[i] = decode_array[i * 2 + 1];
    }
    return bounds;
}

class GouraudPaintStyle final : public Gfx::PaintStyle {
public:
    static NonnullRefPtr<GouraudPaintStyle> create(NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, Array<Gfx::FloatPoint, 3> points, Array<GouraudColor, 3> colors, GouraudBounds bounds)
    {
        return adopt_ref(*new GouraudPaintStyle(move(color_space), move(functions), move(points), move(colors), move(bounds)));
    }

    // We can't override sample_color() because it doesn't receive a useful origin.
    // Instead, override `paint()` and pass the origin to similar function.
    // FIXME: Try changing the signature of sample_color() to receive the actual origin.
    virtual void paint(Gfx::IntRect physical_bounding_box, PaintFunction paint) const override
    {
        paint([this, physical_bounding_box](Gfx::IntPoint point) { return sample_color_in_bbox(physical_bounding_box.location() + point); });
    }

private:
    GouraudPaintStyle(NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, Array<Gfx::FloatPoint, 3> points, Array<GouraudColor, 3> colors, GouraudBounds bounds)
        : m_functions(move(functions))
        , m_color_space(move(color_space))
        , m_points(move(points))
        , m_colors(move(colors))
        , m_bounds(move(bounds))
    {
    }

    Gfx::Color sample_color_in_bbox(Gfx::IntPoint) const;

    ShadingFunctionsType m_functions;
    NonnullRefPtr<ColorSpaceWithFloatArgs> m_color_space;
    Array<Gfx::FloatPoint, 3> m_points;
    Array<GouraudColor, 3> m_colors;
    GouraudBounds m_bounds;
};

Gfx::Color GouraudPaintStyle::sample_color_in_bbox(Gfx::IntPoint point_in_bbox) const
{
    auto signed_area = [](Gfx::FloatPoint a, Gfx::FloatPoint b, Gfx::FloatPoint c) {
        return (a.x() - c.x()) * (b.y() - c.y()) - (b.x() - c.x()) * (a.y() - c.y());
    };

    auto point = Gfx::FloatPoint { point_in_bbox };

    float area = signed_area(m_points[0], m_points[1], m_points[2]);
    VERIFY(area != 0);
    float alpha = signed_area(point, m_points[1], m_points[2]) / area;
    float beta = signed_area(m_points[0], point, m_points[2]) / area;
    float gamma = signed_area(m_points[0], m_points[1], point) / area;

    GouraudColor color;
    color.resize(m_color_space->number_of_components());

    m_functions.visit(
        [&](Empty) {
            for (int i = 0; i < m_color_space->number_of_components(); ++i)
                color[i] = clamp(alpha * m_colors[0][i] + beta * m_colors[1][i] + gamma * m_colors[2][i], m_bounds.min[i], m_bounds.max[i]);
        },
        [&](Function const& function) {
            float input = clamp(alpha * m_colors[0][0] + beta * m_colors[1][0] + gamma * m_colors[2][0], m_bounds.min[0], m_bounds.max[0]);
            auto result = MUST(function.evaluate(to_array({ input })));
            result.copy_to(color);
        },
        [&](Vector<NonnullRefPtr<Function>> const& functions) {
            float input = clamp(alpha * m_colors[0][0] + beta * m_colors[1][0] + gamma * m_colors[2][0], m_bounds.min[0], m_bounds.max[0]);
            for (size_t i = 0; i < functions.size(); ++i) {
                auto result = MUST(functions[i]->evaluate(to_array({ input })));
                color[i] = result[0];
            }
        });

    return MUST(m_color_space->color(color));
}

void draw_gouraud_triangle(Gfx::Painter& painter, NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, Array<Gfx::FloatPoint, 3> points, Array<GouraudColor, 3> colors, GouraudBounds const& bounds)
{
    static_assert(points.size() == 3);
    static_assert(colors.size() == 3);

    Gfx::Path triangle_path;
    triangle_path.move_to(points[0]);
    triangle_path.line_to(points[1]);
    triangle_path.line_to(points[2]);
    triangle_path.close();

    auto paint_style = GouraudPaintStyle::create(move(color_space), move(functions), move(points), move(colors), bounds);

    // To hide triangle edges. (Setting this to <Gfx::SampleAA> is useful for debugging; it makes triangle edges visible.)
    painter.fill_path<Gfx::SampleNoAA>(triangle_path, paint_style);
}

struct Triangle {
    u32 a;
    u32 b;
    u32 c;
};

PDFErrorOr<void> draw_gouraud_triangles(Gfx::Painter& painter, Gfx::AffineTransform const& ctm, NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType const& functions, Vector<Triangle> const& triangles, Vector<float> const& vertex_data, GouraudBounds bounds)
{
    size_t const number_of_components = !functions.has<Empty>() ? 1 : color_space->number_of_components();
    bool is_indexed = color_space->family() == ColorSpaceFamily::Indexed;
    RefPtr<IndexedColorSpace> indexed_color_space;
    if (is_indexed) {
        indexed_color_space = static_ptr_cast<IndexedColorSpace>(color_space);
        color_space = indexed_color_space->base_color_space();
        bounds = bounds_from_decode_array(color_space->default_decode());
    }

    int const n = 2 + number_of_components;
    for (auto const& triangle : triangles) {
        // FIXME: early-out for triangles completely outside clip
        auto a = Gfx::FloatPoint { vertex_data[triangle.a * n], vertex_data[triangle.a * n + 1] };
        auto b = Gfx::FloatPoint { vertex_data[triangle.b * n], vertex_data[triangle.b * n + 1] };
        auto c = Gfx::FloatPoint { vertex_data[triangle.c * n], vertex_data[triangle.c * n + 1] };

        a = ctm.map(a);
        b = ctm.map(b);
        c = ctm.map(c);

        Array<GouraudColor, 3> colors;
        for (auto [i, triangle_index] : enumerate(to_array<u32>({ triangle.a, triangle.b, triangle.c }))) {
            GouraudColor color;
            if (is_indexed) {
                // "If ColorSpace is an Indexed color space, all color values specified in the shading
                //  are immediately converted to the base color space. [...] Interpolation never occurs
                //  in an Indexed color space, which is quantized and therefore inappropriate for calculations
                //  that assume a continuous range of colors."
                color.extend(TRY(indexed_color_space->base_components(vertex_data[triangle_index * n + 2])));
            } else {
                color.resize(number_of_components);
                for (size_t j = 0; j < number_of_components; ++j)
                    color[j] = vertex_data[triangle_index * n + 2 + j];
            }
            colors[i] = color;
        }
        draw_gouraud_triangle(painter, color_space, functions, { a, b, c }, move(colors), move(bounds));
    }

    return {};
}

static void draw_gouraud_quad(Gfx::Painter& painter, NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, Array<Gfx::FloatPoint, 4> points, Array<GouraudColor, 4> colors, GouraudBounds const& bounds)
{
    // FIXME: https://gpuopen.com/learn/bilinear-interpolation-quadrilateral-barycentric-coordinates/ / https://jcgt.org/published/0011/03/04/paper.pdf instead.
    draw_gouraud_triangle(painter, color_space, functions, { points[0], points[1], points[3] }, { colors[0], colors[1], colors[3] }, bounds);
    draw_gouraud_triangle(painter, color_space, functions, { points[0], points[2], points[3] }, { colors[0], colors[2], colors[3] }, bounds);
}

struct GouraudBezierPatch {
    Array<Gfx::FloatPoint, 16> points {};
    Array<GouraudColor, 4> colors {};
};

static void draw_gouraud_bezier_patch(Gfx::Painter& painter, NonnullRefPtr<ColorSpaceWithFloatArgs> color_space, ShadingFunctionsType functions, GouraudBezierPatch const& patch, GouraudBounds const& bounds, int depth = 0)
{
    auto const& points = patch.points;
    auto const& colors = patch.colors;

    // FIXME: This is very naive. Instead, compute error from linear patch and adaptively subdivide based on that error.
    //        Figure out a way to deal with T-junctions.
    if (depth == 5) {
        draw_gouraud_quad(painter, color_space, functions, { points[0], points[3], points[12], points[15] }, colors, bounds);
        return;
    }

    auto lerp = [](GouraudColor a, GouraudColor b, float t) {
        GouraudColor c;
        c.resize(a.size());
        for (size_t i = 0; i < a.size(); ++i)
            c[i] = mix(a[i], b[i], t);
        return c;
    };

    GouraudBezierPatch new_patch;
    auto& new_points = new_patch.points;
    auto& new_colors = new_patch.colors;

    // FIXME: Use separable De Casteljau's to do fewer additions and multiplications.

    // Lower left.
    // clang-format off
    new_points[0]  =  points[0];
    new_points[1]  = (points[0] + points[1]) / 2.0f;
    new_points[2]  = (points[0] + points[1] * 2 + points[2]) / 4.0f;
    new_points[3]  = (points[0] + points[1] * 3 + points[2] * 3 + points[3]) / 8.0f;

    new_points[4]  = (points[0] + points[4]) / 2.0f;
    new_points[5]  = (points[0] + points[4] +  points[1] + points[5]) / 4.0f;
    new_points[6]  = (points[0] + points[4] + (points[1] + points[5]) * 2 +  points[2] + points[6]) / 8.0f;
    new_points[7]  = (points[0] + points[4] + (points[1] + points[5]) * 3 + (points[2] + points[6]) * 3 + points[3] + points[7]) / 16.0f;

    new_points[8]  = (points[0] + points[4] * 2 + points[8]) / 4.0f;
    new_points[9]  = (points[0] + points[4] * 2 + points[8] +  points[1] + points[5] * 2 + points[9]) / 8.0f;
    new_points[10] = (points[0] + points[4] * 2 + points[8] + (points[1] + points[5] * 2 + points[9]) * 2 +  points[2] + points[6] * 2 + points[10]) / 16.0f;
    new_points[11] = (points[0] + points[4] * 2 + points[8] + (points[1] + points[5] * 2 + points[9]) * 3 + (points[2] + points[6] * 2 + points[10]) * 3 + points[3] + points[7] * 2 + points[11]) / 32.0f;

    new_points[12] = (points[0] + points[4] * 3 + points[8] * 3 + points[12]) / 8.0f;
    new_points[13] = (points[0] + points[4] * 3 + points[8] * 3 + points[12] +  points[1] + points[5] * 3 + points[9] * 3 + points[13]) / 16.0f;
    new_points[14] = (points[0] + points[4] * 3 + points[8] * 3 + points[12] + (points[1] + points[5] * 3 + points[9] * 3 + points[13]) * 2 +  points[2] + points[6] * 3 + points[10] * 3 + points[14]) / 32.0f;
    new_points[15] = (points[0] + points[4] * 3 + points[8] * 3 + points[12] + (points[1] + points[5] * 3 + points[9] * 3 + points[13]) * 3 + (points[2] + points[6] * 3 + points[10] * 3 + points[14]) * 3 + points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 64.0f;
    // clang-format on

    new_colors[0] = colors[0];
    new_colors[1] = lerp(colors[0], colors[1], 0.5f);
    new_colors[2] = lerp(colors[0], colors[2], 0.5f);
    new_colors[3] = lerp(lerp(colors[0], colors[1], 0.5f), lerp(colors[2], colors[3], 0.5f), 0.5f);

    draw_gouraud_bezier_patch(painter, color_space, functions, new_patch, bounds, depth + 1);

    // Lower right.
    // clang-format off
    new_points[0]  = (points[0] + points[1] * 3 + points[2] * 3 + points[3]) / 8.0f;
    new_points[1]  = (points[1] + points[2] * 2 + points[3]) / 4.0f;
    new_points[2]  = (points[2] + points[3]) / 2.0f;
    new_points[3]  =  points[3];

    new_points[4]  = (points[0] + points[4] + (points[1] + points[5]) * 3 + (points[2] + points[6]) * 3 + points[3] + points[7]) / 16.0f;
    new_points[5]  = (points[1] + points[5] + (points[2] + points[6]) * 2 +  points[3] + points[7]) / 8.0f;
    new_points[6]  = (points[2] + points[6] + points[3] + points[7]) / 4.0f;
    new_points[7]  = (points[3] + points[7]) / 2.0f;

    new_points[8]  = (points[0] + points[4] * 2 + points[ 8] + (points[1] + points[5] * 2 + points[ 9]) * 3 + (points[2] + points[6] * 2 + points[10]) * 3 + points[3] + points[7] * 2 + points[11]) / 32.0f;
    new_points[9]  = (points[1] + points[5] * 2 + points[ 9] + (points[2] + points[6] * 2 + points[10]) * 2 +  points[3] + points[7] * 2 + points[11]) / 16.0f;
    new_points[10] = (points[2] + points[6] * 2 + points[10] +  points[3] + points[7] * 2 + points[11]) / 8.0f;
    new_points[11] = (points[3] + points[7] * 2 + points[11]) / 4.0f;

    new_points[12] = (points[0] + points[4] * 3 + points[ 8] * 3 + points[12] + (points[1] + points[5] * 3 + points[9] * 3 + points[13]) * 3 + (points[2] + points[6] * 3 + points[10] * 3 + points[14]) * 3 + points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 64.0f;
    new_points[13] = (points[1] + points[5] * 3 + points[ 9] * 3 + points[13] + (points[2] + points[6] * 3 + points[10] * 3 + points[14]) * 2 + points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 32.0f;
    new_points[14] = (points[2] + points[6] * 3 + points[10] * 3 + points[14] + points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 16.0f;
    new_points[15] = (points[3] + points[7] * 3 + points[11] * 3 + points[15]) / 8.0f;
    // clang-format on

    new_colors[0] = lerp(colors[0], colors[1], 0.5f);
    new_colors[1] = colors[1];
    new_colors[2] = lerp(lerp(colors[0], colors[1], 0.5f), lerp(colors[2], colors[3], 0.5f), 0.5f);
    new_colors[3] = lerp(colors[1], colors[3], 0.5f);

    draw_gouraud_bezier_patch(painter, color_space, functions, new_patch, bounds, depth + 1);

    // Upper left.
    // clang-format off
    new_points[12] =  points[12];
    new_points[13] = (points[12] + points[13]) / 2.0f;
    new_points[14] = (points[12] + points[13] * 2 + points[14]) / 4.0f;
    new_points[15] = (points[12] + points[13] * 3 + points[14] * 3 + points[15]) / 8.0f;

    new_points[8]  = (points[12] + points[8]) / 2.0f;
    new_points[9]  = (points[12] + points[8] +  points[13] + points[9]) / 4.0f;
    new_points[10] = (points[12] + points[8] + (points[13] + points[9]) * 2 + points[14] + points[10]) / 8.0f;
    new_points[11] = (points[12] + points[8] + (points[13] + points[9]) * 3 + (points[14] + points[10]) * 3 + points[15] + points[11]) / 16.0f;

    new_points[4]  = (points[12] + points[8] * 2 + points[4]) / 4.0f;
    new_points[5]  = (points[12] + points[8] * 2 + points[4] +  points[13] + points[9] * 2 + points[5]) / 8.0f;
    new_points[6]  = (points[12] + points[8] * 2 + points[4] + (points[13] + points[9] * 2 + points[5]) * 2 +  points[14] + points[10] * 2 + points[6]) / 16.0f;
    new_points[7]  = (points[12] + points[8] * 2 + points[4] + (points[13] + points[9] * 2 + points[5]) * 3 + (points[14] + points[10] * 2 + points[6]) * 3 + points[15] + points[11] * 2 + points[7]) / 32.0f;

    new_points[0]  = (points[12] + points[8] * 3 + points[4] * 3 + points[0]) / 8.0f;
    new_points[1]  = (points[12] + points[8] * 3 + points[4] * 3 + points[0] +  points[13] + points[9] * 3 + points[5] * 3 + points[1]) / 16.0f;
    new_points[2]  = (points[12] + points[8] * 3 + points[4] * 3 + points[0] + (points[13] + points[9] * 3 + points[5] * 3 + points[1]) * 2 +  points[14] + points[10] * 3 + points[6] * 3 + points[2]) / 32.0f;
    new_points[3]  = (points[12] + points[8] * 3 + points[4] * 3 + points[0] + (points[13] + points[9] * 3 + points[5] * 3 + points[1]) * 3 + (points[14] + points[10] * 3 + points[6] * 3 + points[2]) * 3 + points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 64.0f;
    // clang-format on

    new_colors[0] = lerp(colors[0], colors[2], 0.5f);
    new_colors[1] = lerp(lerp(colors[0], colors[1], 0.5f), lerp(colors[2], colors[3], 0.5f), 0.5f);
    new_colors[2] = colors[2];
    new_colors[3] = lerp(colors[2], colors[3], 0.5f);

    draw_gouraud_bezier_patch(painter, color_space, functions, new_patch, bounds, depth + 1);

    // Upper right.
    // clang-format off
    new_points[12] = (points[12] + points[13] * 3 + points[14] * 3 + points[15]) / 8.0f;
    new_points[13] = (points[13] + points[14] * 2 + points[15]) / 4.0f;
    new_points[14] = (points[14] + points[15]) / 2.0f;
    new_points[15] =  points[15];

    new_points[8]  = (points[12] + points[ 8] + (points[13] + points[ 9]) * 3 + (points[14] + points[10]) * 3 + points[15] + points[11]) / 16.0f;
    new_points[9]  = (points[13] + points[ 9] + (points[14] + points[10]) * 2 +  points[15] + points[11]) / 8.0f;
    new_points[10] = (points[14] + points[10] +  points[15] + points[11]) / 4.0f;
    new_points[11] = (points[15] + points[11]) / 2.0f;

    new_points[4]  = (points[12] + points[ 8] * 2 + points[4] + (points[13] + points[ 9] * 2 + points[5]) * 3 + (points[14] + points[10] * 2 + points[6]) * 3 + points[15] + points[11] * 2 + points[7]) / 32.0f;
    new_points[5]  = (points[13] + points[ 9] * 2 + points[5] + (points[14] + points[10] * 2 + points[6]) * 2 +  points[15] + points[11] * 2 + points[7]) / 16.0f;
    new_points[6]  = (points[14] + points[10] * 2 + points[6] +  points[15] + points[11] * 2 + points[7]) / 8.0f;
    new_points[7]  = (points[15] + points[11] * 2 + points[7]) / 4.0f;

    new_points[0]  = (points[12] + points[ 8] * 3 + points[4] * 3 + points[0] + (points[13] + points[ 9] * 3 + points[5] * 3 + points[1]) * 3 + (points[14] + points[10] * 3 + points[6] * 3 + points[2]) * 3 + points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 64.0f;
    new_points[1]  = (points[13] + points[ 9] * 3 + points[5] * 3 + points[1] + (points[14] + points[10] * 3 + points[6] * 3 + points[2]) * 2 +  points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 32.0f;
    new_points[2]  = (points[14] + points[10] * 3 + points[6] * 3 + points[2] +  points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 16.0f;
    new_points[3]  = (points[15] + points[11] * 3 + points[7] * 3 + points[3]) / 8.0f;
    // clang-format on

    new_colors[0] = lerp(lerp(colors[0], colors[1], 0.5f), lerp(colors[2], colors[3], 0.5f), 0.5f);
    new_colors[1] = lerp(colors[1], colors[3], 0.5f);
    new_colors[2] = lerp(colors[2], colors[3], 0.5f);
    new_colors[3] = colors[3];

    draw_gouraud_bezier_patch(painter, color_space, functions, new_patch, bounds, depth + 1);
}

class FreeFormGouraudShading final : public Shading {
public:
    static PDFErrorOr<NonnullRefPtr<FreeFormGouraudShading>> create(Document*, NonnullRefPtr<StreamObject>, CommonEntries);

    virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
    virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
    virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;

private:
    FreeFormGouraudShading(CommonEntries common_entries, Vector<float> vertex_data, Vector<Triangle> triangles, ShadingFunctionsType functions, GouraudBounds bounds)
        : m_common_entries(move(common_entries))
        , m_vertex_data(move(vertex_data))
        , m_triangles(move(triangles))
        , m_functions(move(functions))
        , m_bounds(move(bounds))
    {
    }

    CommonEntries m_common_entries;

    // Interleaved x, y, c0, c1, c2, ...
    Vector<float> m_vertex_data;
    Vector<Triangle> m_triangles;
    ShadingFunctionsType m_functions;
    GouraudBounds m_bounds;
};

PDFErrorOr<NonnullRefPtr<FreeFormGouraudShading>> FreeFormGouraudShading::create(Document* document, NonnullRefPtr<StreamObject> shading_stream, CommonEntries common_entries)
{
    auto shading_dict = shading_stream->dict();

    // TABLE 4.32 Additional entries specific to a type 4 shading dictionary
    // "(Required) The number of bits used to represent each vertex coordinate.
    //  Valid values are 1, 2, 4, 8, 12, 16, 24, and 32."
    int bits_per_coordinate = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerCoordinate))).to_int();
    if (!first_is_one_of(bits_per_coordinate, 1, 2, 4, 8, 12, 16, 24, 32))
        return Error::malformed_error("BitsPerCoordinate invalid");

    // "(Required) The number of bits used to represent each color component.
    //  Valid values are 1, 2, 4, 8, 12, and 16."
    int bits_per_component = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerComponent))).to_int();
    if (!first_is_one_of(bits_per_component, 1, 2, 4, 8, 12, 16))
        return Error::malformed_error("BitsPerComponent invalid");

    // "(Required) The number of bits used to represent the edge flag for each ver-
    //  tex (see below). Valid values of BitsPerFlag are 2, 4, and 8, but only the
    //  least significant 2 bits in each flag value are used. Valid values for the edge
    //  flag are 0, 1, and 2."
    int bits_per_flag = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerFlag))).to_int();
    if (!first_is_one_of(bits_per_flag, 2, 4, 8))
        return Error::malformed_error("BitsPerFlag invalid");

    // "(Required) An array of numbers specifying how to map vertex coordinates
    //  and color components into the appropriate ranges of values. The decoding
    //  method is similar to that used in image dictionaries (see “Decode Arrays”
    //  on page 344). The ranges are specified as follows:
    //
    //      [ xmin xmax ymin ymax c1,min c1,max … cn,min cn,max ]
    //
    //  Note that only one pair of c values should be specified if a Function entry
    //  is present."
    auto decode_array = TRY(shading_dict->get_array(document, CommonNames::Decode));
    size_t number_of_components = static_cast<size_t>(shading_dict->contains(CommonNames::Function) ? 1 : common_entries.color_space->number_of_components());
    if (decode_array->size() != 4 + 2 * number_of_components)
        return Error::malformed_error("Decode array must have 4 + 2 * number of components elements");
    Vector<float> decode;
    decode.resize(decode_array->size());
    for (size_t i = 0; i < decode_array->size(); ++i)
        decode[i] = decode_array->at(i).to_float();

    // "(Optional) A 1-in, n-out function or an array of n 1-in, 1-out functions
    //  (where n is the number of color components in the shading dictionary’s
    //  color space). If this entry is present, the color data for each vertex must be
    //  specified by a single parametric variable rather than by n separate color
    //  components. The designated function(s) are called with each interpolated
    //  value of the parametric variable to determine the actual color at each
    //  point. Each input value is forced into the range interval specified for the
    //  corresponding color component in the shading dictionary’s Decode array.
    //  Each function’s domain must be a superset of that interval. If the value re-
    //  turned by the function for a given color component is out of range, it is
    //  adjusted to the nearest valid value.
    //  This entry may not be used with an Indexed color space."
    ShadingFunctionsType functions;
    if (shading_dict->contains(CommonNames::Function))
        functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, decode[4]));

    // See "Type 4 Shadings (Free-Form Gouraud-Shaded Triangle Meshes)" in the PDF 1.7 spec for a description of the stream contents.
    auto stream = FixedMemoryStream { shading_stream->bytes() };
    BigEndianInputBitStream bitstream { MaybeOwned { stream } };

    Vector<u8> flags;
    Vector<float> vertex_data;
    while (!bitstream.is_eof()) {
        u8 flag = TRY(bitstream.read_bits<u8>(bits_per_flag));
        if (flag > 2)
            return Error::malformed_error("Invalid edge flag");
        TRY(flags.try_append(flag));

        u32 x = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
        u32 y = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
        TRY(vertex_data.try_append(mix(decode[0], decode[1], x / (powf(2.0f, bits_per_coordinate) - 1))));
        TRY(vertex_data.try_append(mix(decode[2], decode[3], y / (powf(2.0f, bits_per_coordinate) - 1))));
        for (size_t i = 0; i < number_of_components; ++i) {
            u16 color = TRY(bitstream.read_bits<u16>(bits_per_component));
            TRY(vertex_data.try_append(mix(decode[4 + 2 * i], decode[4 + 2 * i + 1], color / (powf(2.0f, bits_per_component) - 1))));
        }
        bitstream.align_to_byte_boundary();
    }

    Vector<Triangle> triangles;
    for (u32 i = 0; i < flags.size(); ++i) {
        if (flags[i] == 0) {
            if (i + 2 >= flags.size())
                return Error::malformed_error("Invalid triangle");
            triangles.append({ i, i + 1, i + 2 });
            i += 2;
        } else if (flags[i] == 1) {
            if (triangles.is_empty())
                return Error::malformed_error("Invalid triangle strip");
            triangles.append({ triangles.last().b, triangles.last().c, i });
        } else if (flags[i] == 2) {
            if (triangles.is_empty())
                return Error::malformed_error("Invalid triangle fan");
            triangles.append({ triangles.last().a, triangles.last().c, i });
        }
    }

    GouraudBounds bounds = bounds_from_decode_array(decode.span().slice(4));
    return adopt_ref(*new FreeFormGouraudShading(move(common_entries), move(vertex_data), move(triangles), move(functions), move(bounds)));
}

PDFErrorOr<void> FreeFormGouraudShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
{
    return draw_gouraud_triangles(painter, ctm, m_common_entries.color_space, m_functions, m_triangles, m_vertex_data, m_bounds);
}

class LatticeFormGouraudShading final : public Shading {
public:
    static PDFErrorOr<NonnullRefPtr<LatticeFormGouraudShading>> create(Document*, NonnullRefPtr<StreamObject>, CommonEntries);

    virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
    virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
    virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;

private:
    LatticeFormGouraudShading(CommonEntries common_entries, Vector<float> vertex_data, Vector<Triangle> triangles, ShadingFunctionsType functions, GouraudBounds bounds)
        : m_common_entries(move(common_entries))
        , m_vertex_data(move(vertex_data))
        , m_triangles(move(triangles))
        , m_functions(move(functions))
        , m_bounds(move(bounds))
    {
    }

    CommonEntries m_common_entries;

    // Interleaved x, y, c0, c1, c2, ...
    Vector<float> m_vertex_data;
    Vector<Triangle> m_triangles;
    ShadingFunctionsType m_functions;
    GouraudBounds m_bounds;
};

PDFErrorOr<NonnullRefPtr<LatticeFormGouraudShading>> LatticeFormGouraudShading::create(Document* document, NonnullRefPtr<StreamObject> shading_stream, CommonEntries common_entries)
{
    auto shading_dict = shading_stream->dict();

    // TABLE 4.33 Additional entries specific to a type 5 shading dictionary
    // "(Required) The number of bits used to represent each vertex coordinate.
    //  Valid values are 1, 2, 4, 8, 12, 16, 24, and 32."
    int bits_per_coordinate = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerCoordinate))).to_int();
    if (!first_is_one_of(bits_per_coordinate, 1, 2, 4, 8, 12, 16, 24, 32))
        return Error::malformed_error("BitsPerCoordinate invalid");

    // "(Required) The number of bits used to represent each color component.
    //  Valid values are 1, 2, 4, 8, 12, and 16."
    int bits_per_component = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerComponent))).to_int();
    if (!first_is_one_of(bits_per_component, 1, 2, 4, 8, 12, 16))
        return Error::malformed_error("BitsPerComponent invalid");

    // "(Required) The number of vertices in each row of the lattice; the value
    //  must be greater than or equal to 2. The number of rows need not be
    //  specified."
    int vertices_per_row = TRY(document->resolve(shading_dict->get_value(CommonNames::VerticesPerRow))).to_int();
    if (vertices_per_row < 2)
        return Error::malformed_error("VerticesPerRow invalid");

    // "(Required) An array of numbers specifying how to map vertex coordinates
    //  and color components into the appropriate ranges of values. The decoding
    //  method is similar to that used in image dictionaries (see “Decode Arrays”
    //  on page 344). The ranges are specified as follows:
    //
    //      [ xmin xmax ymin ymax c1,min c1,max … cn,min cn,max ]
    //
    //  Note that only one pair of c values should be specified if a Function entry
    //  is present."
    auto decode_array = TRY(shading_dict->get_array(document, CommonNames::Decode));
    size_t number_of_components = static_cast<size_t>(shading_dict->contains(CommonNames::Function) ? 1 : common_entries.color_space->number_of_components());
    if (decode_array->size() != 4 + 2 * number_of_components)
        return Error::malformed_error("Decode array must have 4 + 2 * number of components elements");
    Vector<float> decode;
    decode.resize(decode_array->size());
    for (size_t i = 0; i < decode_array->size(); ++i)
        decode[i] = decode_array->at(i).to_float();

    // "(Optional) A 1-in, n-out function or an array of n 1-in, 1-out functions
    //  (where n is the number of color components in the shading dictionary’s
    //  color space). If this entry is present, the color data for each vertex must be
    //  specified by a single parametric variable rather than by n separate color
    //  components. The designated function(s) are called with each interpolated
    //  value of the parametric variable to determine the actual color at each
    //  point. Each input value is forced into the range interval specified for the
    //  corresponding color component in the shading dictionary’s Decode array.
    //  Each function’s domain must be a superset of that interval. If the value re-
    //  turned by the function for a given color component is out of range, it is
    //  adjusted to the nearest valid value.
    //  This entry may not be used with an Indexed color space."
    ShadingFunctionsType functions;
    if (shading_dict->contains(CommonNames::Function))
        functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, decode[4]));

    // See "Type 5 Shadings (Lattice-Form Gouraud-Shaded Triangle Meshes)" in the PDF 1.7 spec for a description of the stream contents.
    auto stream = FixedMemoryStream { shading_stream->bytes() };
    BigEndianInputBitStream bitstream { MaybeOwned { stream } };

    Vector<float> vertex_data;
    while (!bitstream.is_eof()) {
        u32 x = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
        u32 y = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
        TRY(vertex_data.try_append(mix(decode[0], decode[1], x / (powf(2.0f, bits_per_coordinate) - 1))));
        TRY(vertex_data.try_append(mix(decode[2], decode[3], y / (powf(2.0f, bits_per_coordinate) - 1))));
        for (size_t i = 0; i < number_of_components; ++i) {
            u16 color = TRY(bitstream.read_bits<u16>(bits_per_component));
            TRY(vertex_data.try_append(mix(decode[4 + 2 * i], decode[4 + 2 * i + 1], color / (powf(2.0f, bits_per_component) - 1))));
        }
        bitstream.align_to_byte_boundary();
    }

    size_t number_of_vertices = vertex_data.size() / (2 + number_of_components);
    if (number_of_vertices % vertices_per_row != 0)
        return Error::malformed_error("Number of vertices must be a multiple of vertices per row");
    size_t number_of_rows = number_of_vertices / vertices_per_row;
    if (number_of_rows < 2)
        return Error::malformed_error("Number of rows must be at least 2");

    Vector<Triangle> triangles;
    for (u32 i = 0; i <= number_of_rows - 2; ++i) {
        for (u32 j = 0; j <= static_cast<u32>(vertices_per_row - 2); ++j) {
            triangles.append({ i * vertices_per_row + j, i * vertices_per_row + j + 1, (i + 1) * vertices_per_row + j });
            triangles.append({ i * vertices_per_row + j + 1, (i + 1) * vertices_per_row + j, (i + 1) * vertices_per_row + j + 1 });
        }
    }

    GouraudBounds bounds = bounds_from_decode_array(decode.span().slice(4));
    return adopt_ref(*new LatticeFormGouraudShading(move(common_entries), move(vertex_data), move(triangles), move(functions), move(bounds)));
}

PDFErrorOr<void> LatticeFormGouraudShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
{
    return draw_gouraud_triangles(painter, ctm, m_common_entries.color_space, m_functions, m_triangles, m_vertex_data, m_bounds);
}

class CoonsPatchShading final : public Shading {
public:
    static PDFErrorOr<NonnullRefPtr<CoonsPatchShading>> create(Document*, NonnullRefPtr<StreamObject>, CommonEntries);

    virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
    virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
    virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;

private:
    // Indexes into m_patch_data.
    struct CoonsPatch {
        u32 control_points[12];
        u32 colors[4];
    };

    CoonsPatchShading(CommonEntries common_entries, Vector<float> patch_data, Vector<CoonsPatch> patches, ShadingFunctionsType functions, GouraudBounds bounds)
        : m_common_entries(move(common_entries))
        , m_patch_data(move(patch_data))
        , m_patches(move(patches))
        , m_functions(move(functions))
        , m_bounds(move(bounds))
    {
    }

    CommonEntries m_common_entries;

    // Interleaved x0, y0, x1, y1, ..., x11, y11, c0, c1, c2, c3, ...
    // (For flags 1-3, only 8 coordinates and 2 colors.)
    Vector<float> m_patch_data;
    Vector<CoonsPatch> m_patches;
    ShadingFunctionsType m_functions;
    GouraudBounds m_bounds;
};

PDFErrorOr<NonnullRefPtr<CoonsPatchShading>> CoonsPatchShading::create(Document* document, NonnullRefPtr<StreamObject> shading_stream, CommonEntries common_entries)
{
    auto shading_dict = shading_stream->dict();

    // TABLE 4.34 Additional entries specific to a type 6 shading dictionary
    // "(Required) The number of bits used to represent each geometric coordi-
    //  nate. Valid values are 1, 2, 4, 8, 12, 16, 24, and 32."
    int bits_per_coordinate = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerCoordinate))).to_int();
    if (!first_is_one_of(bits_per_coordinate, 1, 2, 4, 8, 12, 16, 24, 32))
        return Error::malformed_error("BitsPerCoordinate invalid");

    // "(Required) The number of bits used to represent each color component.
    //  Valid values are 1, 2, 4, 8, 12, and 16."
    int bits_per_component = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerComponent))).to_int();
    if (!first_is_one_of(bits_per_component, 1, 2, 4, 8, 12, 16))
        return Error::malformed_error("BitsPerComponent invalid");

    // "(Required) The number of bits used to represent the edge flag for each
    //  patch (see below). Valid values of BitsPerFlag are 2, 4, and 8, but only the
    //  least significant 2 bits in each flag value are used. Valid values for the edge
    //  flag are 0, 1, 2, and 3."
    int bits_per_flag = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerFlag))).to_int();
    if (!first_is_one_of(bits_per_flag, 2, 4, 8))
        return Error::malformed_error("BitsPerFlag invalid");

    // "(Required) An array of numbers specifying how to map vertex coordinates
    //  and color components into the appropriate ranges of values. The decoding
    //  method is similar to that used in image dictionaries (see “Decode Arrays”
    //  on page 344). The ranges are specified as follows:
    //
    //      [ xmin xmax ymin ymax c1,min c1,max … cn,min cn,max ]
    //
    //  Note that only one pair of c values should be specified if a Function entry
    //  is present."
    auto decode_array = TRY(shading_dict->get_array(document, CommonNames::Decode));
    size_t number_of_components = static_cast<size_t>(shading_dict->contains(CommonNames::Function) ? 1 : common_entries.color_space->number_of_components());
    if (decode_array->size() != 4 + 2 * number_of_components)
        return Error::malformed_error("Decode array must have 4 + 2 * number of components elements");
    Vector<float> decode;
    decode.resize(decode_array->size());
    for (size_t i = 0; i < decode_array->size(); ++i)
        decode[i] = decode_array->at(i).to_float();

    // "(Optional) A 1-in, n-out function or an array of n 1-in, 1-out functions
    //  (where n is the number of color components in the shading dictionary’s
    //  color space). If this entry is present, the color data for each vertex must be
    //  specified by a single parametric variable rather than by n separate color
    //  components. The designated function(s) are called with each interpolated
    //  value of the parametric variable to determine the actual color at each
    //  point. Each input value is forced into the range interval specified for the
    //  corresponding color component in the shading dictionary’s Decode array.
    //  Each function’s domain must be a superset of that interval. If the value re-
    //  turned by the function for a given color component is out of range, it is
    //  adjusted to the nearest valid value.
    //  This entry may not be used with an Indexed color space."
    ShadingFunctionsType functions;
    if (shading_dict->contains(CommonNames::Function))
        functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, decode[4]));

    // See "Type 6 Shadings (Coons Patch Meshes)" in the PDF 1.7 spec for a description of the stream contents.
    auto stream = FixedMemoryStream { shading_stream->bytes() };
    BigEndianInputBitStream bitstream { MaybeOwned { stream } };

    Vector<float> patch_data;
    Vector<CoonsPatch> patches;

    auto read_point = [&]() -> ErrorOr<void> {
        u32 x = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
        u32 y = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
        TRY(patch_data.try_append(mix(decode[0], decode[1], x / (powf(2.0f, bits_per_coordinate) - 1))));
        TRY(patch_data.try_append(mix(decode[2], decode[3], y / (powf(2.0f, bits_per_coordinate) - 1))));
        return {};
    };

    auto read_points = [&](u32 n) -> ErrorOr<void> {
        for (u32 i = 0; i < n; ++i)
            TRY(read_point());
        return {};
    };

    auto read_color = [&]() -> ErrorOr<void> {
        for (size_t i = 0; i < number_of_components; ++i) {
            u16 color = TRY(bitstream.read_bits<u16>(bits_per_component));
            TRY(patch_data.try_append(mix(decode[4 + 2 * i], decode[4 + 2 * i + 1], color / (powf(2.0f, bits_per_component) - 1))));
        }
        return {};
    };

    auto read_colors = [&](u32 n) -> ErrorOr<void> {
        for (u32 i = 0; i < n; ++i)
            TRY(read_color());
        return {};
    };

    while (!bitstream.is_eof()) {
        u8 flag = TRY(bitstream.read_bits<u8>(bits_per_flag));

        int n = patch_data.size();
        CoonsPatch patch;

        // "TABLE 4.35 Data values in a Coons patch mesh"
        switch (flag) {
        case 0:
            // "x1 y1 x2 y2 x3 y3 x4 y4 x5 y5 x6 y6 x7 y7 x8 y8 x9 y9 x10 y10 x11 y11 x12 y12 c1 c2 c3 c4
            //  New patch; no implicit values"
            TRY(patch_data.try_ensure_capacity(patch_data.size() + 12 * 2 + 4 + number_of_components));
            TRY(read_points(12));
            TRY(read_colors(4));
            for (int i = 0; i < 12; ++i)
                patch.control_points[i] = n + 2 * i;
            for (int i = 0; i < 4; ++i)
                patch.colors[i] = n + 24 + number_of_components * i;
            break;
        case 1:
            if (patches.is_empty())
                return Error::malformed_error("Edge flag 1 without preceding patch");
            // "x5 y5 x6 y6 x7 y7 x8 y8 x9 y9 x10 y10 x11 y11 x12 y12 c3 c4
            //  Implicit values:
            //  (x1, y1) = (x4, y4) previous
            //  (x2, y2) = (x5, y5) previous
            //  (x3, y3) = (x6, y6) previous
            //  (x4, y4) = (x7, y7) previous
            //  c1 = c2 previous
            //  c2 = c3 previous"
            TRY(patch_data.try_ensure_capacity(patch_data.size() + 8 * 2 + 2 + number_of_components));
            TRY(read_points(8));
            TRY(read_colors(2));
            patch.control_points[0] = patches.last().control_points[3];
            patch.control_points[1] = patches.last().control_points[4];
            patch.control_points[2] = patches.last().control_points[5];
            patch.control_points[3] = patches.last().control_points[6];
            for (int i = 0; i < 8; ++i)
                patch.control_points[i + 4] = n + 2 * i;
            patch.colors[0] = patches.last().colors[1];
            patch.colors[1] = patches.last().colors[2];
            for (int i = 0; i < 2; ++i)
                patch.colors[i + 2] = n + 16 + number_of_components * i;
            break;
        case 2:
            if (patches.is_empty())
                return Error::malformed_error("Edge flag 2 without preceding patch");
            // "x5 y5 x6 y6 x7 y7 x8 y8 x9 y9 x10 y10 x11 y11 x12 y12 c3 c4
            //  Implicit values:
            //  (x1, y1) = (x7, y7) previous
            //  (x2, y2) = (x8, y8) previous
            //  (x3, y3) = (x9, y9) previous
            //  (x4, y4) = (x10, y10) previous
            //  c1 = c3 previous
            //  c2 = c4 previous"
            TRY(patch_data.try_ensure_capacity(patch_data.size() + 8 * 2 + 2 + number_of_components));
            TRY(read_points(8));
            TRY(read_colors(2));
            patch.control_points[0] = patches.last().control_points[6];
            patch.control_points[1] = patches.last().control_points[7];
            patch.control_points[2] = patches.last().control_points[8];
            patch.control_points[3] = patches.last().control_points[9];
            for (int i = 0; i < 8; ++i)
                patch.control_points[i + 4] = n + 2 * i;
            patch.colors[0] = patches.last().colors[2];
            patch.colors[1] = patches.last().colors[3];
            for (int i = 0; i < 2; ++i)
                patch.colors[i + 2] = n + 16 + number_of_components * i;
            break;
        case 3:
            if (patches.is_empty())
                return Error::malformed_error("Edge flag 3 without preceding patch");
            // "x5 y5 x6 y6 x7 y7 x8 y8 x9 y9 x10 y10 x11 y11 x12 y12 c3 c4
            //  Implicit values:
            //  (x1, y1) = (x10, y10) previous
            //  (x2, y2) = (x11, y11) previous
            //  (x3, y3) = (x12, y12) previous
            //  (x4, y4) = (x1, y1) previous
            //  c1 = c4 previous
            //  c2 = c1 previous"
            TRY(patch_data.try_ensure_capacity(patch_data.size() + 8 * 2 + 2 + number_of_components));
            TRY(read_points(8));
            TRY(read_colors(2));
            patch.control_points[0] = patches.last().control_points[9];
            patch.control_points[1] = patches.last().control_points[10];
            patch.control_points[2] = patches.last().control_points[11];
            patch.control_points[3] = patches.last().control_points[0];
            for (int i = 0; i < 8; ++i)
                patch.control_points[i + 4] = n + 2 * i;
            patch.colors[0] = patches.last().colors[3];
            patch.colors[1] = patches.last().colors[0];
            for (int i = 0; i < 2; ++i)
                patch.colors[i + 2] = n + 16 + number_of_components * i;
            break;
        default:
            return Error::malformed_error("Invalid edge flag");
        }

        TRY(patches.try_append(patch));
        bitstream.align_to_byte_boundary();
    }

    GouraudBounds bounds = bounds_from_decode_array(decode.span().slice(4));
    return adopt_ref(*new CoonsPatchShading(move(common_entries), move(patch_data), move(patches), move(functions), move(bounds)));
}

PDFErrorOr<void> CoonsPatchShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
{
    NonnullRefPtr<ColorSpaceWithFloatArgs> color_space = m_common_entries.color_space;
    size_t const number_of_components = !m_functions.has<Empty>() ? 1 : color_space->number_of_components();

    bool is_indexed = color_space->family() == ColorSpaceFamily::Indexed;
    RefPtr<IndexedColorSpace> indexed_color_space;
    auto bounds = m_bounds;
    if (is_indexed) {
        indexed_color_space = static_ptr_cast<IndexedColorSpace>(color_space);
        color_space = indexed_color_space->base_color_space();
        bounds = bounds_from_decode_array(color_space->default_decode());
    }

    for (auto& patch : m_patches) {
        GouraudBezierPatch bezier_patch;
        auto& control_points = bezier_patch.points;

        for (size_t i = 0; i < 4; ++i)
            control_points[i] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[i]], m_patch_data[patch.control_points[i] + 1] });

        for (size_t i = 0; i < 3; ++i)
            control_points[7 + i * 4] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[4 + i]], m_patch_data[patch.control_points[4 + i] + 1] });
        for (size_t i = 0; i < 3; ++i)
            control_points[14 - i] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[7 + i]], m_patch_data[patch.control_points[7 + i] + 1] });

        control_points[8] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[10]], m_patch_data[patch.control_points[10] + 1] });
        control_points[4] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[11]], m_patch_data[patch.control_points[11] + 1] });

        // "The Coons patch (type 6) is actually a special case of the tensor-product patch
        //  (type 7) in which the four internal control points (p11 , p12 , p21 , p22 ) are implicitly
        //  defined by the boundary curves. The values of the internal control points are giv-
        //  en by these equations:"
        auto p = [&](int c, int r) -> Gfx::FloatPoint& { return bezier_patch.points[c + 4 * r]; };

        p(1, 1) = (1.0f / 9.0f)
            * (-4.0f * p(0, 0)
                + 6.0f * (p(0, 1) + p(1, 0))
                - 2.0f * (p(0, 3) + p(3, 0))
                + 3.0f * (p(3, 1) + p(1, 3))
                - 1.0f * p(3, 3));

        p(1, 2) = (1.0f / 9.0f)
            * (-4.0f * p(0, 3)
                + 6.0f * (p(0, 2) + p(1, 3))
                - 2.0f * (p(0, 0) + p(3, 3))
                + 3.0f * (p(3, 2) + p(1, 0))
                - 1.0f * p(3, 0));

        p(2, 1) = (1.0f / 9.0f)
            * (-4.0f * p(3, 0)
                + 6.0f * (p(3, 1) + p(2, 0))
                - 2.0f * (p(3, 3) + p(0, 0))
                + 3.0f * (p(0, 1) + p(2, 3))
                - 1.0f * p(0, 3));

        p(2, 2) = (1.0f / 9.0f)
            * (-4.0f * p(3, 3)
                + 6.0f * (p(3, 2) + p(2, 3))
                - 2.0f * (p(3, 0) + p(0, 3))
                + 3.0f * (p(0, 2) + p(2, 0))
                - 1.0f * p(0, 0));

        for (size_t i = 0; i < 4; ++i) {
            GouraudColor color;

            if (is_indexed) {
                // "If ColorSpace is an Indexed color space, all color values specified in the shading
                //  are immediately converted to the base color space. [...] Interpolation never occurs
                //  in an Indexed color space, which is quantized and therefore inappropriate for calculations
                //  that assume a continuous range of colors."
                color.extend(TRY(indexed_color_space->base_components(m_patch_data[patch.colors[i]])));
            } else {
                color.resize(number_of_components);
                for (size_t j = 0; j < number_of_components; ++j)
                    color[j] = m_patch_data[patch.colors[i] + j];
            }

            bezier_patch.colors[i] = color;
        }

        swap(bezier_patch.colors[2], bezier_patch.colors[3]); // Coons order goes counter-clockwise, bezier patch in scanline order.
        draw_gouraud_bezier_patch(painter, color_space, m_functions, bezier_patch, bounds);
    }
    return {};
}

class TensorProductPatchShading final : public Shading {
public:
    static PDFErrorOr<NonnullRefPtr<TensorProductPatchShading>> create(Document*, NonnullRefPtr<StreamObject>, CommonEntries);

    virtual Optional<Gfx::FloatRect> bounding_box() const override { return m_common_entries.b_box; }
    virtual PDFErrorOr<Optional<Gfx::Color>> background_for_pattern() const override { return m_common_entries.background_for_pattern(); }
    virtual PDFErrorOr<void> draw(Gfx::Painter&, Gfx::AffineTransform const&) override;

private:
    // Indexes into m_patch_data.
    struct TensorProductPatch {
        // Pij (col i, row j) is at index:
        // p03 p13 p23 p33       12 13 14 15
        // p02 p12 p22 p32  <=>   8  9 10 11
        // p01 p11 p21 p31        4  5  6  7
        // p00 p10 p20 p30        0  1  2  3
        u32 control_points[16];

        // cij (col i, row j) is at index:
        // c03 c33       2 3
        // c00 c30  <=>  0 1
        u32 colors[4];
    };

    TensorProductPatchShading(CommonEntries common_entries, Vector<float> patch_data, Vector<TensorProductPatch> patches, ShadingFunctionsType functions, GouraudBounds bounds)
        : m_common_entries(move(common_entries))
        , m_patch_data(move(patch_data))
        , m_patches(move(patches))
        , m_functions(move(functions))
        , m_bounds(move(bounds))
    {
    }

    CommonEntries m_common_entries;

    // Interleaved x0, y0, x1, y1, ..., x15, y15, c0, c1, c2, c3, ...
    // (For flags 1-3, only 12 coordinates and 2 colors.)
    Vector<float> m_patch_data;
    Vector<TensorProductPatch> m_patches;
    ShadingFunctionsType m_functions;
    GouraudBounds m_bounds;
};

PDFErrorOr<NonnullRefPtr<TensorProductPatchShading>> TensorProductPatchShading::create(Document* document, NonnullRefPtr<StreamObject> shading_stream, CommonEntries common_entries)
{
    auto shading_dict = shading_stream->dict();

    // "Type 7 shadings (tensor-product patch meshes) are identical to type 6, except that
    //  they are based on a bicubic tensor-product patch defined by 16 control points in-
    //  stead of the 12 control points that define a Coons patch. The shading dictionaries
    //  representing the two patch types differ only in the value of the ShadingType entry
    //  and in the number of control points specified for each patch in the data stream."

    // FIXME: Extract some common code once we have implemented painting and can make sure
    //        that refactoring doesn't break things.

    // TABLE 4.34 Additional entries specific to a type 6 shading dictionary
    // "(Required) The number of bits used to represent each geometric coordi-
    //  nate. Valid values are 1, 2, 4, 8, 12, 16, 24, and 32."
    int bits_per_coordinate = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerCoordinate))).to_int();
    if (!first_is_one_of(bits_per_coordinate, 1, 2, 4, 8, 12, 16, 24, 32))
        return Error::malformed_error("BitsPerCoordinate invalid");

    // "(Required) The number of bits used to represent each color component.
    //  Valid values are 1, 2, 4, 8, 12, and 16."
    int bits_per_component = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerComponent))).to_int();
    if (!first_is_one_of(bits_per_component, 1, 2, 4, 8, 12, 16))
        return Error::malformed_error("BitsPerComponent invalid");

    // "(Required) The number of bits used to represent the edge flag for each
    //  patch (see below). Valid values of BitsPerFlag are 2, 4, and 8, but only the
    //  least significant 2 bits in each flag value are used. Valid values for the edge
    //  flag are 0, 1, 2, and 3."
    int bits_per_flag = TRY(document->resolve(shading_dict->get_value(CommonNames::BitsPerFlag))).to_int();
    if (!first_is_one_of(bits_per_flag, 2, 4, 8))
        return Error::malformed_error("BitsPerFlag invalid");

    // "(Required) An array of numbers specifying how to map vertex coordinates
    //  and color components into the appropriate ranges of values. The decoding
    //  method is similar to that used in image dictionaries (see “Decode Arrays”
    //  on page 344). The ranges are specified as follows:
    //
    //      [ xmin xmax ymin ymax c1,min c1,max … cn,min cn,max ]
    //
    //  Note that only one pair of c values should be specified if a Function entry
    //  is present."
    auto decode_array = TRY(shading_dict->get_array(document, CommonNames::Decode));
    size_t number_of_components = static_cast<size_t>(shading_dict->contains(CommonNames::Function) ? 1 : common_entries.color_space->number_of_components());
    if (decode_array->size() != 4 + 2 * number_of_components)
        return Error::malformed_error("Decode array must have 4 + 2 * number of components elements");
    Vector<float> decode;
    decode.resize(decode_array->size());
    for (size_t i = 0; i < decode_array->size(); ++i)
        decode[i] = decode_array->at(i).to_float();

    // "(Optional) A 1-in, n-out function or an array of n 1-in, 1-out functions
    //  (where n is the number of color components in the shading dictionary’s
    //  color space). If this entry is present, the color data for each vertex must be
    //  specified by a single parametric variable rather than by n separate color
    //  components. The designated function(s) are called with each interpolated
    //  value of the parametric variable to determine the actual color at each
    //  point. Each input value is forced into the range interval specified for the
    //  corresponding color component in the shading dictionary’s Decode array.
    //  Each function’s domain must be a superset of that interval. If the value re-
    //  turned by the function for a given color component is out of range, it is
    //  adjusted to the nearest valid value.
    //  This entry may not be used with an Indexed color space."
    ShadingFunctionsType functions;
    if (shading_dict->contains(CommonNames::Function))
        functions = TRY(read_shading_functions(document, shading_dict, common_entries.color_space, decode[4]));

    // See "Type 6 Shadings (Coons Patch Meshes)" in the PDF 1.7 spec for a description of the stream contents.
    auto stream = FixedMemoryStream { shading_stream->bytes() };
    BigEndianInputBitStream bitstream { MaybeOwned { stream } };

    Vector<float> patch_data;
    Vector<TensorProductPatch> patches;

    auto read_point = [&]() -> ErrorOr<void> {
        u32 x = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
        u32 y = TRY(bitstream.read_bits<u32>(bits_per_coordinate));
        TRY(patch_data.try_append(mix(decode[0], decode[1], x / (powf(2.0f, bits_per_coordinate) - 1))));
        TRY(patch_data.try_append(mix(decode[2], decode[3], y / (powf(2.0f, bits_per_coordinate) - 1))));
        return {};
    };

    auto read_points = [&](u32 n) -> ErrorOr<void> {
        for (u32 i = 0; i < n; ++i)
            TRY(read_point());
        return {};
    };

    auto read_color = [&]() -> ErrorOr<void> {
        for (size_t i = 0; i < number_of_components; ++i) {
            u16 color = TRY(bitstream.read_bits<u16>(bits_per_component));
            TRY(patch_data.try_append(mix(decode[4 + 2 * i], decode[4 + 2 * i + 1], color / (powf(2.0f, bits_per_component) - 1))));
        }
        return {};
    };

    auto read_colors = [&](u32 n) -> ErrorOr<void> {
        for (u32 i = 0; i < n; ++i)
            TRY(read_color());
        return {};
    };

    // "The coordinates of the control points in a tensor-product patch are actually spec-
    //  ified in the shading’s data stream in the following order:
    //  4 5 6 7
    //  3 14 15 8
    //  2 13 16 9
    //  1 12 11 10"
    // We need to invert this to map data stream index to control point index.
    u32 const patch_index[] = {
        // clang-format off
        0, 4, 8, 12,
        13, 14, 15,
        11, 7, 3,
        2, 1,
        5, 9, 10, 6,
        // clang-format on
    };
    u32 const color_index[] = { 0, 2, 3, 1 };

    // "The 16 control points can be arranged in a
    //  4-by-4 array indexed by row and column, as follows (see Figure 4.24):
    //  p03 p13 p23 p33
    //  p02 p12 p22 p32
    //  p01 p11 p21 p31
    //  p00 p10 p20 p30"

    while (!bitstream.is_eof()) {
        u8 flag = TRY(bitstream.read_bits<u8>(bits_per_flag));

        int n = patch_data.size();
        TensorProductPatch patch;

        // "TABLE 4.36 Data values in a tensor-product patch mesh"
        switch (flag) {
        case 0:
            // "x00 y00 x01 y01 x02 y02 x03 y03 x13 y13 x23 y23 x33 y33 x32 y32
            //  x31 y31 x30 y30 x20 y20 x10 y10 x11 y11 x12 y12 x22 y22 x21 y21
            //  c00 c03 c33 c30
            //  New patch; no implicit values"
            TRY(patch_data.try_ensure_capacity(patch_data.size() + 16 * 2 + 4 + number_of_components));
            TRY(read_points(16));
            TRY(read_colors(4));
            for (int i = 0; i < 16; ++i)
                patch.control_points[patch_index[i]] = n + 2 * i;
            for (int i = 0; i < 4; ++i)
                patch.colors[color_index[i]] = n + 32 + number_of_components * i;
            break;
        case 1:
            if (patches.is_empty())
                return Error::malformed_error("Edge flag 1 without preceding patch");
            // "x13 y13 x23 y23 x33 y33 x32 y32 x31 y31 x30 y30
            //  x20 y20 x10 y10 x11 y11 x12 y12 x22 y22 x21 y21
            //  c33 c30
            //  Implicit values:
            //  (x00, y00) = (x03, y03) previous
            //  (x01, y01) = (x13, y13) previous
            //  (x02, y02) = (x23, y23) previous
            //  (x03, y03) = (x33, y33) previous
            //  c00 = c03 previous
            //  c03 = c33 previous"
            TRY(patch_data.try_ensure_capacity(patch_data.size() + 12 * 2 + 2 + number_of_components));
            TRY(read_points(12));
            TRY(read_colors(2));
            patch.control_points[patch_index[0]] = patches.last().control_points[12];
            patch.control_points[patch_index[1]] = patches.last().control_points[13];
            patch.control_points[patch_index[2]] = patches.last().control_points[14];
            patch.control_points[patch_index[3]] = patches.last().control_points[15];
            for (int i = 0; i < 12; ++i)
                patch.control_points[patch_index[i + 4]] = n + 2 * i;
            patch.colors[color_index[0]] = patches.last().colors[2];
            patch.colors[color_index[1]] = patches.last().colors[3];
            for (int i = 0; i < 2; ++i)
                patch.colors[color_index[i + 2]] = n + 24 + number_of_components * i;
            break;
        case 2:
            if (patches.is_empty())
                return Error::malformed_error("Edge flag 2 without preceding patch");
            // "x13 y13 x23 y23 x33 y33 x32 y32 x31 y31 x30 y30
            //  x20 y20 x10 y10 x11 y11 x12 y12 x22 y22 x21 y21
            //  c33 c30
            //  Implicit values:
            //  (x00, y00) = (x33, y33) previous
            //  (x01, y01) = (x32, y32) previous
            //  (x02, y02) = (x31, y31) previous
            //  (x03, y03) = (x30, y30) previous
            //  c00 = c33 previous
            //  c03 = c30 previous"
            TRY(patch_data.try_ensure_capacity(patch_data.size() + 12 * 2 + 2 + number_of_components));
            TRY(read_points(12));
            TRY(read_colors(2));
            patch.control_points[patch_index[0]] = patches.last().control_points[15];
            patch.control_points[patch_index[1]] = patches.last().control_points[11];
            patch.control_points[patch_index[2]] = patches.last().control_points[7];
            patch.control_points[patch_index[3]] = patches.last().control_points[3];
            for (int i = 0; i < 12; ++i)
                patch.control_points[patch_index[i + 4]] = n + 2 * i;
            patch.colors[color_index[0]] = patches.last().colors[3];
            patch.colors[color_index[1]] = patches.last().colors[1];
            for (int i = 0; i < 2; ++i)
                patch.colors[color_index[i + 2]] = n + 24 + number_of_components * i;
            break;
        case 3:
            if (patches.is_empty())
                return Error::malformed_error("Edge flag 3 without preceding patch");
            // "x13 y13 x23 y23 x33 y33 x32 y32 x31 y31 x30 y30
            //  x20 y20 x10 y10 x11 y11 x12 y12 x22 y22 x21 y21
            //  c33 c30
            //  Implicit values:
            //  (x00, y00) = (x30, y30) previous
            //  (x01, y01) = (x20, y20) previous
            //  (x02, y02) = (x10, y10) previous
            //  (x03, y03) = (x00, y00) previous
            //  c00 = c30 previous
            //  c03 = c00 previous"
            TRY(patch_data.try_ensure_capacity(patch_data.size() + 12 * 2 + 2 + number_of_components));
            TRY(read_points(12));
            TRY(read_colors(2));
            patch.control_points[patch_index[0]] = patches.last().control_points[3];
            patch.control_points[patch_index[1]] = patches.last().control_points[2];
            patch.control_points[patch_index[2]] = patches.last().control_points[1];
            patch.control_points[patch_index[3]] = patches.last().control_points[0];
            for (int i = 0; i < 12; ++i)
                patch.control_points[patch_index[i + 4]] = n + 2 * i;
            patch.colors[color_index[0]] = patches.last().colors[1];
            patch.colors[color_index[1]] = patches.last().colors[0];
            for (int i = 0; i < 2; ++i)
                patch.colors[color_index[i + 2]] = n + 24 + number_of_components * i;
            break;
        default:
            return Error::malformed_error("Invalid edge flag");
        }

        TRY(patches.try_append(patch));
        bitstream.align_to_byte_boundary();
    }

    GouraudBounds bounds = bounds_from_decode_array(decode.span().slice(4));
    return adopt_ref(*new TensorProductPatchShading(move(common_entries), move(patch_data), move(patches), move(functions), move(bounds)));
}

PDFErrorOr<void> TensorProductPatchShading::draw(Gfx::Painter& painter, Gfx::AffineTransform const& ctm)
{
    NonnullRefPtr<ColorSpaceWithFloatArgs> color_space = m_common_entries.color_space;
    size_t const number_of_components = !m_functions.has<Empty>() ? 1 : color_space->number_of_components();
    bool is_indexed = color_space->family() == ColorSpaceFamily::Indexed;
    RefPtr<IndexedColorSpace> indexed_color_space;
    auto bounds = m_bounds;
    if (is_indexed) {
        indexed_color_space = static_ptr_cast<IndexedColorSpace>(color_space);
        color_space = indexed_color_space->base_color_space();
        bounds = bounds_from_decode_array(color_space->default_decode());
    }

    for (auto& patch : m_patches) {
        GouraudBezierPatch bezier_patch;

        for (size_t i = 0; i < 16; ++i)
            bezier_patch.points[i] = ctm.map(Gfx::FloatPoint { m_patch_data[patch.control_points[i]], m_patch_data[patch.control_points[i] + 1] });

        for (size_t i = 0; i < 4; ++i) {
            GouraudColor color;

            if (is_indexed) {
                // "If ColorSpace is an Indexed color space, all color values specified in the shading
                //  are immediately converted to the base color space. [...] Interpolation never occurs
                //  in an Indexed color space, which is quantized and therefore inappropriate for calculations
                //  that assume a continuous range of colors."
                color.extend(TRY(indexed_color_space->base_components(m_patch_data[patch.colors[i]])));
            } else {
                color.resize(number_of_components);
                for (size_t j = 0; j < number_of_components; ++j)
                    color[j] = m_patch_data[patch.colors[i] + j];
            }

            bezier_patch.colors[i] = color;
        }

        draw_gouraud_bezier_patch(painter, color_space, m_functions, bezier_patch, bounds);
    }
    return {};
}

}

PDFErrorOr<NonnullRefPtr<Shading>> Shading::create(Document* document, NonnullRefPtr<Object> shading_dict_or_stream, Renderer& renderer)
{
    // "Shading types 4 to 7 are defined by a stream containing descriptive data charac-
    //  terizing the shading’s gradient fill. In these cases, the shading dictionary is also a
    //  stream dictionary and can contain any of the standard entries common to all
    //  streams"
    auto shading_dict = TRY([&]() -> PDFErrorOr<NonnullRefPtr<DictObject>> {
        if (shading_dict_or_stream->is<DictObject>())
            return shading_dict_or_stream->cast<DictObject>();
        if (shading_dict_or_stream->is<StreamObject>())
            return shading_dict_or_stream->cast<StreamObject>()->dict();
        return Error::malformed_error("Shading must be a dictionary or stream");
    }());

    int shading_type = TRY(document->resolve(shading_dict->get_value(CommonNames::ShadingType))).to_int();
    auto common_entries = TRY(read_common_entries(document, *shading_dict, renderer));

    switch (shading_type) {
    case 1:
        if (!shading_dict_or_stream->is<DictObject>())
            return Error::malformed_error("Function-based shading dictionary has wrong type");
        return FunctionBasedShading::create(document, shading_dict, move(common_entries));
    case 2:
        if (!shading_dict_or_stream->is<DictObject>())
            return Error::malformed_error("Axial shading dictionary has wrong type");
        return AxialShading::create(document, shading_dict, move(common_entries));
    case 3:
        if (!shading_dict_or_stream->is<DictObject>())
            return Error::malformed_error("Radial shading dictionary has wrong type");
        return RadialShading::create(document, shading_dict, move(common_entries));
    case 4:
        if (!shading_dict_or_stream->is<StreamObject>())
            return Error::malformed_error("Free-form Gouraud-shaded triangle mesh stream has wrong type");
        return FreeFormGouraudShading::create(document, shading_dict_or_stream->cast<StreamObject>(), move(common_entries));
    case 5:
        if (!shading_dict_or_stream->is<StreamObject>())
            return Error::malformed_error("Lattice-form Gouraud-shaded triangle mesh stream has wrong type");
        return LatticeFormGouraudShading::create(document, shading_dict_or_stream->cast<StreamObject>(), move(common_entries));
    case 6:
        if (!shading_dict_or_stream->is<StreamObject>())
            return Error::malformed_error("Coons patch mesh stream has wrong type");
        return CoonsPatchShading::create(document, shading_dict_or_stream->cast<StreamObject>(), move(common_entries));
    case 7:
        if (!shading_dict_or_stream->is<StreamObject>())
            return Error::malformed_error("Tensor-product patch mesh stream has wrong type");
        return TensorProductPatchShading::create(document, shading_dict_or_stream->cast<StreamObject>(), move(common_entries));
    }
    dbgln("Shading type {}", shading_type);
    return Error::malformed_error("Invalid shading type");
}

}
