% File: hawkdraw-shadings.code.tex
% Copyright 2026 Jasper Habicht (mail(at)jasperhabicht.de).
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License version 1.3c,
% available at http://www.latex-project.org/lppl/.
%
% This file is part of the `hawkdraw' package (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% This work has the LPPL maintenance status `maintained'.
%
% BOF

% v0.9.0 2026-09-07

% =====

% Shadings

% ===

% PDF treats shadings as patterns and hence as color that can be used to fill or stroke a path. There are
% various types of shadings, but we only define linear, radial and conic shaings here as type 2, 3 and 6
% shadings respectively.

% Since we want shadings to be reusable, we define them as PDF objects with a size of 100bp times 100bp.
% Type 2 and 3 shadings are defined by functions, type 6 shadings are defined by a binary stream.

% Every shading defined by its type, the used colors and whether or not it has some offset (for radial or
% conic shadings) is defined separatey as PDF Object. In the PDF, two objects are created for every use of
% a shading, one that defines the shading using functions and one as that defines a pattern representing a
% scaled box referring to the shading.

% In order to allow for rotation of shadings, we use the PGF approach of scaling the shadings to twice
% the size of the path they should apply to. Thus, we need to use multiple functions to define linear
% shadings, because the outer part of the shading object will be clipped away by the path. We do this by
% defining the functions in a nested way. The outer function is a type 3 function which defines the inner
% functions. The inner functions depend on the type of the shading and the number of colors used.

% For proper positioning of shadings without the need to retrieve the current position of the path via two
% compilation runs it is necessary to place the shading inside a PDF XObject which creates a proper
% coordinate system.

% ===

\msg_new:nnn { hawkdraw } { shading-unknown } {
    Shading ~ `#1` ~ unknown.
}

\int_new:N \g__hawkdraw_shading_id_int
\int_new:N \g__hawkdraw_shading_obj_int
\box_new:N \l__hawkdraw_shading_box
\clist_new:N \l_hawkdraw_shading_path_use_clist

\clist_new:N \l_hawkdraw_shading_colors_clist
\fp_new:N \l_hawkdraw_shading_rotate_fp
\fp_new:N \l_hawkdraw_shading_offset_fp
\bool_new:N \l_hawkdraw_shading_circular_bool
\fp_new:N \l_hawkdraw_shading_angle_delta_fp

\dim_new:N \l__hawkdraw_shading_path_linewidth_dim

\dim_new:N \l__hawkdraw_shading_bb_xmin_dim
\dim_new:N \l__hawkdraw_shading_bb_xmax_dim
\dim_new:N \l__hawkdraw_shading_bb_ymin_dim
\dim_new:N \l__hawkdraw_shading_bb_ymax_dim

\dim_new:N \l__hawkdraw_shading_bb_xmin_o_dim
\dim_new:N \l__hawkdraw_shading_bb_xmax_o_dim
\dim_new:N \l__hawkdraw_shading_bb_ymin_o_dim
\dim_new:N \l__hawkdraw_shading_bb_ymax_o_dim

\dim_new:N \l__hawkdraw_shading_xoffset_dim
\dim_new:N \l__hawkdraw_shading_yoffset_dim
\fp_new:N \l__hawkdraw_shading_origin_fp

\exp_args_generate:n { NVVVV }

% Storing the path options for later use in the shading XObject
\tl_new:N \l__hawkdraw_shading_path_options_tl
\hook_gput_code:nnn { hawkdraw / options / before } { hawkdraw / shading } {
    \tl_set_eq:NN \l__hawkdraw_shading_path_options_tl \l__hawkdraw_path_options_tl
}

\keys_define:nn { hawkdraw / path } {
    shading                 .code:n         = {
        \int_gincr:N \g__hawkdraw_shading_id_int
        \tl_put_right:Nn \l_hawkdraw_path_preactions_tl {
            \dim_set:Nn \l__hawkdraw_shading_xoffset_dim {
                \dim_min:nn {
                    \hawkdraw_tuple_use_i:n { \g_hawkdraw_path_point_first_fp }
                } {
                    \hawkdraw_path_bb_xmin:
                }
            }
            \dim_set:Nn \l__hawkdraw_shading_yoffset_dim {
                \dim_min:nn {
                    \hawkdraw_tuple_use_ii:n { \g_hawkdraw_path_point_first_fp }
                } {
                    \hawkdraw_path_bb_ymin:
                }
            }
            \pdfxform_new:enn {
                hawkdraw_shading_ \int_use:N \g__hawkdraw_shading_id_int
            } { } {
                \draw_suspend_begin:
                    \draw_begin:
                        \tl_set_eq:NN \l__hawkdraw_path_options_tl \l__hawkdraw_shading_path_options_tl
                        \hawkdraw_path_set_options:
                        \tl_trim_spaces_apply:nN { \g_hawkdraw_softpath_original_tl } \__hawkdraw_path_process:o
                        \draw_transform_shift:n {
                            (
                                - \l__hawkdraw_shading_xoffset_dim
                            ,
                                - \l__hawkdraw_shading_yoffset_dim
                            )
                        }
                        \tl_use:N \g_hawkdraw_softpath_original_tl
                        \dim_set_eq:NN \l__hawkdraw_shading_bb_xmin_dim \g_draw_bb_xmin_dim
                        \dim_set_eq:NN \l__hawkdraw_shading_bb_xmax_dim \g_draw_bb_xmax_dim
                        \dim_set_eq:NN \l__hawkdraw_shading_bb_ymin_dim \g_draw_bb_ymin_dim
                        \dim_set_eq:NN \l__hawkdraw_shading_bb_ymax_dim \g_draw_bb_ymax_dim
                        \hawkdraw_shading_use:n {#1}
                        \draw_path_use_clear:e { \clist_use:N \l_hawkdraw_shading_path_use_clist }
                    \draw_end:
                \draw_suspend_end:
            }
            \hbox_set:Nn \l__hawkdraw_shading_box {
                \pdfxform_use:e {
                    hawkdraw_shading_ \int_use:N \g__hawkdraw_shading_id_int
                }
            }
            \clist_if_in:NnTF \l_hawkdraw_shading_path_use_clist { stroke } {
                \fp_set:Nn \l__hawkdraw_shading_origin_fp {
                    -0.5 * \l_hawkdraw_path_linewidth_dim
                ,
                    \fp_if_nan:nTF { \l_hawkdraw_picture_baseline_fp } {
                        -0.5 * \l_hawkdraw_path_linewidth_dim
                    } {
                        \l_hawkdraw_picture_baseline_fp
                    }
                }
            } {
                \fp_set:Nn \l__hawkdraw_shading_origin_fp { 0pt , 0pt }
            }
            \draw_box_use:Nn \l__hawkdraw_shading_box {
                ( \l__hawkdraw_shading_xoffset_dim , \l__hawkdraw_shading_yoffset_dim ) +
                \l__hawkdraw_shading_origin_fp
            }
            \bool_gset_false:N \g__hawkdraw_path_use_clip_bool
        }
    } ,
    shading ~ colors        .clist_set:N    = \l_hawkdraw_shading_colors_clist ,
    shading ~ rotate        .fp_set:N       = \l_hawkdraw_shading_rotate_fp ,
    shading ~ rotate        .initial:n      = { 0.0 } ,
    shading ~ offset        .fp_set:N       = \l_hawkdraw_shading_offset_fp ,
    shading ~ offset        .initial:n      = { 0.0 } ,
    shading ~ circular      .bool_set:N     = \l_hawkdraw_shading_circular_bool ,
    shading ~ circular      .default:n      = { true } ,
    shading ~ circular      .initial:n      = { false } ,
    shading ~ angle ~ delta .fp_set:N       = \l_hawkdraw_shading_angle_delta_fp ,
    shading ~ angle ~ delta .initial:n      = { 360 } ,
    shading ~ type          .code:n         = {
        \clist_set:Nn \l_hawkdraw_shading_path_use_clist {#1}
    },
    shading ~ type          .initial:n      = { fill } ,
}

\keys_define:nn { hawkdraw / path / node } {
    shading                 .code:n         = {
        \int_gincr:N \g__hawkdraw_shading_id_int
        \tl_put_right:Nn \l_hawkdraw_node_postactions_tl {
            \pdfxform_new:enn {
                hawkdraw_shading_ \int_use:N \g__hawkdraw_shading_id_int
            } { } {
                \draw_suspend_begin:
                    \draw_begin:
                        \tl_set_eq:NN \l__hawkdraw_path_options_tl \l__hawkdraw_shading_path_options_tl
                        \hawkdraw_path_set_options:
                        \draw_transform_shift:n {
                            (
                                0.5 * \l_hawkdraw_node_natural_width_dim
                            ,
                                0.5 * \l_hawkdraw_node_natural_height_dim
                            )
                        }
                        \tl_use:N \l__hawkdraw_node_frame_path_tl
                        \dim_set_eq:NN \l__hawkdraw_shading_bb_xmin_dim \g_draw_bb_xmin_dim
                        \dim_set_eq:NN \l__hawkdraw_shading_bb_xmax_dim \g_draw_bb_xmax_dim
                        \dim_set_eq:NN \l__hawkdraw_shading_bb_ymin_dim \g_draw_bb_ymin_dim
                        \dim_set_eq:NN \l__hawkdraw_shading_bb_ymax_dim \g_draw_bb_ymax_dim
                        \hawkdraw_shading_use:n {#1}
                        \draw_path_use:e { \clist_use:N \l_hawkdraw_shading_path_use_clist }
                    \draw_end:
                \draw_suspend_end:
            }
            \hbox_set:Nn \l__hawkdraw_shading_box {
                \pdfxform_use:e {
                    hawkdraw_shading_ \int_use:N \g__hawkdraw_shading_id_int
                }
                \bool_gset_false:N \g__hawkdraw_path_use_clip_bool
            }
            \draw_box_use:Nn \l__hawkdraw_shading_box {
                (
                    -0.5 * \l_hawkdraw_node_natural_width_dim
                ,
                    -0.5 * \l_hawkdraw_node_natural_height_dim
                )
            }
        }
    } ,
}

\tl_new:N \l__hawkdraw_shading_color_value_tmp_tl
\seq_new:N \l__hawkdraw_shading_color_values_seq

\cs_generate_variant:Nn \color_export:nnN { e }

\cs_new_protected:Npn \hawkdraw_shading_set:nnn #1#2#3 {
    \pdf_object_new:n { hawkdraw_shading_ #1 }
    \tl_if_empty:nTF {#3} {
        \pdf_object_write:nne { hawkdraw_shading_ #1 } { dict } {#2}
    } {
        \hawkdraw_support_pdf_write_stream:nee {
            hawkdraw_shading_ #1
        } {#2} {#3}
    }
    \tl_const:ce {
        c__hawkdraw_shading_ #1 _tl
    } {
        \pdf_object_ref:n { hawkdraw_shading_ #1 }
    }
}
\cs_generate_variant:Nn \hawkdraw_shading_set:nnn { e }

\cs_new_protected:Npn \hawkdraw_shading_use:n #1 {
    \hawkdraw_shading_create:nV {#1} \l_hawkdraw_shading_colors_clist

    % Store original bounding box values for later positioning of pattern
    \dim_set_eq:NN \l__hawkdraw_shading_bb_xmin_o_dim
        \l__hawkdraw_shading_bb_xmin_dim
    \dim_set_eq:NN \l__hawkdraw_shading_bb_xmax_o_dim
        \l__hawkdraw_shading_bb_xmax_dim
    \dim_set_eq:NN \l__hawkdraw_shading_bb_ymin_o_dim
        \l__hawkdraw_shading_bb_ymin_dim
    \dim_set_eq:NN \l__hawkdraw_shading_bb_ymax_o_dim
        \l__hawkdraw_shading_bb_ymax_dim

    \clist_if_in:NnT \l_hawkdraw_shading_path_use_clist { stroke } {
        \dim_set_eq:NN \l__hawkdraw_shading_path_linewidth_dim
            \l_hawkdraw_path_linewidth_dim
    }

    \bool_lazy_and:nnT {
        \l_hawkdraw_shading_circular_bool
    } {
        \str_if_eq_p:nn {#1} { radial }
    } {
        \dim_set:Nn \l__hawkdraw_shading_bb_xmax_dim {
            \dim_max:nn {
                \l__hawkdraw_shading_bb_xmax_dim
            } {
                \l__hawkdraw_shading_bb_ymax_dim
            }
        }
        \dim_set:Nn \l__hawkdraw_shading_bb_xmin_dim {
            \dim_min:nn {
                \l__hawkdraw_shading_bb_xmin_dim
            } {
                \l__hawkdraw_shading_bb_ymin_dim
            }
        }
        \dim_set_eq:NN \l__hawkdraw_shading_bb_ymax_dim
            \l__hawkdraw_shading_bb_xmax_dim
        \dim_set_eq:NN \l__hawkdraw_shading_bb_ymin_dim
            \l__hawkdraw_shading_bb_xmin_dim
    }

    \int_gincr:N \g__hawkdraw_shading_obj_int
    \pdf_object_unnamed_write:ne { dict } {
        /Type ~ /Pattern ~
        /PatternType ~ 2 ~
        /Shading ~ \tl_use:c {
            c__hawkdraw_shading_
            #1
            . \clist_use:N \l_hawkdraw_shading_colors_clist
            \str_if_eq:nnT {#1} { radial } {
                . \fp_to_decimal:n { \l_hawkdraw_shading_offset_fp }
            }
            \str_if_eq:nnT {#1} { conic } {
                . \fp_to_decimal:n { \l_hawkdraw_shading_angle_delta_fp }
            }
            _tl
        } ~
        /Matrix ~ [ ~
            \hawkdraw_pdf_transformation_matrix:nnnnnnn {
                -50bp
            } {
                -50bp
            } {
                (
                    \l__hawkdraw_shading_bb_xmax_dim -
                    \l__hawkdraw_shading_bb_xmin_dim
                ) / 50bp
            } {
                (
                    \l__hawkdraw_shading_bb_ymax_dim -
                    \l__hawkdraw_shading_bb_ymin_dim
                ) / 50bp
            } {
                \l_hawkdraw_shading_rotate_fp
            } {
                (
                    \l__hawkdraw_shading_bb_xmax_o_dim +
                    \l__hawkdraw_shading_bb_xmin_o_dim
                ) / 2 +
                0.5 * \l__hawkdraw_shading_path_linewidth_dim
            } {
                (
                    \l__hawkdraw_shading_bb_ymax_o_dim +
                    \l__hawkdraw_shading_bb_ymin_o_dim
                ) / 2 +
                0.5 * \l__hawkdraw_shading_path_linewidth_dim
                \fp_compare:nNnT { \l_hawkdraw_picture_baseline_fp } < { 0pt } {
                    - \l_hawkdraw_picture_baseline_fp
                }
            } ~
        ] ~
    }
    \pdfmanagement_add:nee {
        Page / Resources / Pattern
    } {
        hawkdraw Shading
        \int_use:N \g__hawkdraw_shading_obj_int
    } {
        \pdf_object_ref_last:
    }

    \clist_if_in:NnT \l_hawkdraw_shading_path_use_clist { fill } {
        \hawkdraw_support_pdfliteral_direct:n {
            /Pattern ~ cs ~ /hawkdraw Shading
            \int_use:N \g__hawkdraw_shading_obj_int
            \c_space_tl scn ~
        }
    }
    \clist_if_in:NnT \l_hawkdraw_shading_path_use_clist { stroke } {
        \hawkdraw_support_pdfliteral_direct:n {
            /Pattern ~ CS ~ /hawkdraw Shading
            \int_use:N \g__hawkdraw_shading_obj_int
            \c_space_tl SCN ~
        }
    }
}

% ===

\cs_new_protected:Npn \__hawkdraw_shading_color_set_value:Nnnn #1#2#3#4 {
    \hawkdraw_case_color_space:nnn {
        \tl_set:Ne \l__hawkdraw_shading_color_value_tmp_tl {#2}
        \seq_put_right:NV #1 \l__hawkdraw_shading_color_value_tmp_tl
    } {
        \color_export:enN {#3} { space-sep-rgb }
            \l__hawkdraw_shading_color_value_tmp_tl
        \seq_put_right:NV #1 \l__hawkdraw_shading_color_value_tmp_tl
    } {
        \color_export:enN {#4} { space-sep-cmyk }
            \l__hawkdraw_shading_color_value_tmp_tl
        \seq_put_right:NV #1 \l__hawkdraw_shading_color_value_tmp_tl
    }
}

\cs_new_protected:Npn \hawkdraw_shading_color_set_values:Nn #1#2 {
    \seq_clear:N #1
    \clist_map_inline:nn {#2} {
        \__hawkdraw_shading_color_set_value:Nnnn #1 {##1} {##1} {##1}
    }
}
\cs_generate_variant:Nn \hawkdraw_shading_color_set_values:Nn { NV }

% ===

\cs_new_protected:Npn \hawkdraw_shading_create:nn #1#2 {
    \cs_if_exist_use:cTF { hawkdraw_shading_ #1 :n } {
        {#2}
    } {
        \msg_warning:nnn { hawkdraw } { shading-unknown } {#1}
    }
}
\cs_generate_variant:Nn \hawkdraw_shading_create:nn { nV }

% ===

\cs_set:Npn \__hawkdraw_shading_linear_bounds:n #1 {
    \fp_format:nn { 25 } { .5 } ~
    \int_step_tokens:nnn { 2 } { \clist_count:n {#1} - 1 } {
        \__hawkdraw_shading_linear_bounds_aux:nn { \clist_count:n {#1} }
    }
    \fp_format:nn { 75 } { .5 } ~
}

\cs_set:Npn \__hawkdraw_shading_linear_bounds_aux:nn #1#2 {
    \fp_format:nn { ( 50 / ( #1 - 1 ) ) * ( #2 - 1 ) + 25 } { .5 } ~
}

\cs_new:Npn \__hawkdraw_shading_linear_functions:n #1 {
    << ~
        /FunctionType ~ 2 ~
        /Domain ~ [ 0.0 ~ 100.0 ] ~
        /C0 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq { 1 }
        ] ~
        /C1 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq { 1 }
        ] ~
        /N ~ 1 ~
    >> ~
    \int_step_function:nnN { 2 } { \seq_count:N \l__hawkdraw_shading_color_values_seq }
        \__hawkdraw_shading_linear_functions_aux:n
    << ~
        /FunctionType ~ 2 ~
        /Domain ~ [ 0.0 ~ 100.0 ] ~
        /C0 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq { -1 }
        ] ~
        /C1 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq { -1 }
        ] ~
        /N ~ 1 ~
    >> ~
}
\cs_generate_variant:Nn \__hawkdraw_shading_linear_functions:n { V }

\cs_new:Npn \__hawkdraw_shading_linear_functions_aux:n #1 {
    << ~
        /FunctionType ~ 2 ~
        /Domain ~ [ 0.0 ~ 100.0 ] ~
        /C0 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq { #1 - 1 }
        ] ~
        /C1 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq {#1}
        ] ~
        /N ~ 1 ~
    >> ~
}

\cs_new_protected:Npn \hawkdraw_shading_linear:n #1 {
    \tl_if_exist:cF {
        c__hawkdraw_shading_
        linear
        . #1
        _tl
    } {
        \hawkdraw_shading_color_set_values:Nn
            \l__hawkdraw_shading_color_values_seq
            {#1}
        \hawkdraw_shading_set:nnn {
            linear
            . #1
        } {
            /ShadingType ~ 2 ~
            \hawkdraw_case_color_space:nnn {
                /ColorSpace ~ /DeviceGray ~
            } {
                /ColorSpace ~ /DeviceRGB ~
            } {
                /ColorSpace ~ /DeviceCMYK ~
            }
            /Domain ~ [ 0.0 ~ 100.0 ] ~
            /Coords ~ [ 0.0 ~ 0.0 ~ 0.0 ~ 100.0 ] ~
            /Extend ~ [ false ~ false ] ~
            /Function ~ << ~
                /FunctionType ~ 3 ~
                /Domain ~ [ 0.0 ~ 100.0 ] ~
                /Functions ~ [ ~
                    \__hawkdraw_shading_linear_functions:V
                        \l__hawkdraw_shading_color_values_seq
                ] ~
                /Bounds ~ [
                    \__hawkdraw_shading_linear_bounds:n {#1}
                ] ~
                /Encode ~ [
                    \prg_replicate:nn { \clist_count:n {#1} + 1 } {
                        0 ~ 1 ~
                    }
                ] ~
            >> ~
        } { }
    }
}

% ===

\cs_new:Npn \__hawkdraw_shading_radial_bounds:n #1 {
    \int_step_tokens:nn { \clist_count:n {#1} - 1 } {
        \__hawkdraw_shading_radial_bounds_aux:nn { \clist_count:n {#1} - 1 }
    }
}

\cs_new:Npn \__hawkdraw_shading_radial_bounds_aux:nn #1#2 {
    \fp_format:nn { ( 25 / ( #1 ) ) * ( #2 ) } { .3 } ~
}

\cs_new:Npn \__hawkdraw_shading_radial_functions:n #1 {
    \int_step_function:nN { \seq_count:N \l__hawkdraw_shading_color_values_seq - 1 }
        \__hawkdraw_shading_radial_functions_aux:n
    << ~
        /FunctionType ~ 2 ~
        /Domain ~ [ 0.0 ~ 50.0 ] ~
        /C0 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq { -1 }
        ] ~
        /C1 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq { -1 }
        ] ~
        /N ~ 1 ~
    >> ~
}
\cs_generate_variant:Nn \__hawkdraw_shading_radial_functions:n { V }

\cs_new:Npn \__hawkdraw_shading_radial_functions_aux:n #1 {
    << ~
        /FunctionType ~ 2 ~
        /Domain ~ [ 0.0 ~ 50.0 ] ~
        /C0 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq {#1}
        ] ~
        /C1 ~ [
            \seq_item:Nn \l__hawkdraw_shading_color_values_seq { #1 + 1 }
        ] ~
        /N ~ 1 ~
    >> ~
}

\cs_new:Npn \hawkdraw_shading_radial_offset:n #1 {
    \fp_format:nn { 50 + ( #1 * 25 ) } { .5 } ~
}

\cs_new_protected:Npn \hawkdraw_shading_radial:n #1 {
    \tl_if_exist:cF {
        c__hawkdraw_shading_
        radial
        . #1
        . \fp_to_decimal:n { \l_hawkdraw_shading_offset_fp }
        _tl
    } {
        \hawkdraw_shading_color_set_values:Nn
            \l__hawkdraw_shading_color_values_seq
            {#1}
        \hawkdraw_shading_set:enn {
            radial
            . #1
            . \fp_to_decimal:n { \l_hawkdraw_shading_offset_fp }
        } {
            /ShadingType ~ 3 ~
            \hawkdraw_case_color_space:nnn {
                /ColorSpace ~ /DeviceGray ~
            } {
                /ColorSpace ~ /DeviceRGB ~
            } {
                /ColorSpace ~ /DeviceCMYK ~
            }
            /Domain ~ [ 0.0 ~ 50.0 ] ~
            /Coords ~ [
                \hawkdraw_shading_radial_offset:n {
                    \l_hawkdraw_shading_offset_fp
                }
                50.0 ~ 0.0 ~ 50.0 ~ 50.0 ~ 50.0
            ] ~
            /Extend ~ [ true ~ false ] ~
            /Function ~ << ~
                /FunctionType ~ 3 ~
                /Domain ~ [ 0.0 ~ 50.0 ] ~
                /Functions ~ [ ~
                    \__hawkdraw_shading_radial_functions:V
                        \l__hawkdraw_shading_color_values_seq
                ] ~
                /Bounds ~ [
                    \__hawkdraw_shading_radial_bounds:n {#1}
                ] ~
                /Encode ~ [
                    \prg_replicate:nn { \clist_count:n {#1} } {
                        0 ~ 1 ~
                    }
                ] ~
            >> ~
        } { }
    }
}

% ===

% We use a Coons patch mesh for conic shadings where multiple circular-segment shaped patches form a circle.
% Two corners of the patch are at the center of the circle, the other two at the outer border forming a curve.
% We use four patches for each color in order to be able to generate a circle (or at least an approximation)
% with only two colors. Therefore, we need to interpolate a few colors so we end up with 2n + 1 colors.

% In theory we can use patches with different flags and reduce the size of the stream, but in practice this
% means that the path direction of every second patch needs to be reversed. Since we don't need a lot of
% patches to create a conic shading, we just use flag-0 patches only.

% We need to output a binary stream. It is theoretically possinle to output a hex stream and set the relevant
% /Filter, but when PDFTeX compresses the stream, it simply appends a /Filter entry to the attributes of the
% PDF object without taking into account an already existing /Filter entry which results in faulty syntax.
% We need different approaches to output such a stream for PDFTeX and LuaTeX. Since dvipdfmx requires a hex
% stream which it converts to a binary stream, we first create a hex stream.

\cs_new_protected:Npn \hawkdraw_shading_conic_stream:N #1 {
    \tl_set:Ne #1 {
        \seq_map_indexed_function:NN
            \l__hawkdraw_shading_color_values_seq
            \__hawkdraw_shading_conic_stream_aux:nn
    }
}

\cs_new:Npn \__hawkdraw_shading_conic_stream_aux:nn #1#2 {
    % skip first entry in seq which is the start color
    \int_compare:nNnF {#1} = { \c_one_int } {
        % flag (4 bits)
        \hawkdraw_hex_to_stream:n { 00 }
        % patch is pie segment going from 0 deg to 360 / ( n * 4 ) deg
        % points (12 points, 2 * 16 bits each)
        \prg_replicate:nn { 2 } {
            \__hawkdraw_shading_conic_stream_point_polar:nn { 0 } {
                \l_hawkdraw_shading_angle_delta_fp /
                ( \seq_count:N \l__hawkdraw_shading_color_values_seq - 1 ) * ( #1 - 2 )
            }
        }
        \prg_replicate:nn { 2 } {
            \__hawkdraw_shading_conic_stream_point_polar:nn { 1 } {
                \l_hawkdraw_shading_angle_delta_fp /
                ( \seq_count:N \l__hawkdraw_shading_color_values_seq - 1 ) * ( #1 - 2 )
            }
        }
        \__hawkdraw_shading_conic_stream_point_polar:nn { 1 } {
            \l_hawkdraw_shading_angle_delta_fp /
            ( \seq_count:N \l__hawkdraw_shading_color_values_seq - 1 ) * ( #1 - 1.75 )
        }
        \__hawkdraw_shading_conic_stream_point_polar:nn { 1 } {
            \l_hawkdraw_shading_angle_delta_fp /
            ( \seq_count:N \l__hawkdraw_shading_color_values_seq - 1 ) * ( #1 - 1.25 )
        }
        \prg_replicate:nn { 2 } {
            \__hawkdraw_shading_conic_stream_point_polar:nn { 1 } {
                \l_hawkdraw_shading_angle_delta_fp /
                ( \seq_count:N \l__hawkdraw_shading_color_values_seq - 1 ) * ( #1 - 1 )
            }
        }
        \prg_replicate:nn { 3 } {
            \__hawkdraw_shading_conic_stream_point_polar:nn { 0 } {
                \l_hawkdraw_shading_angle_delta_fp /
                ( \seq_count:N \l__hawkdraw_shading_color_values_seq - 1 ) * ( #1 - 1 )
            }
        }
        \__hawkdraw_shading_conic_stream_point_polar:nn { 0 } {
            \l_hawkdraw_shading_angle_delta_fp /
            ( \seq_count:N \l__hawkdraw_shading_color_values_seq - 1 ) * ( #1 - 2 )
        }
        % colors (4 colors, 3 * 8 bits each for RGB)
        \hawkdraw_hex_to_stream:e {
            \hawkdraw_color_pdf_to_hex:e {
                \seq_item:Nn \l__hawkdraw_shading_color_values_seq { #1 - 1 }
            }
        }
        \hawkdraw_hex_to_stream:e {
            \hawkdraw_color_pdf_to_hex:e {
                \seq_item:Nn \l__hawkdraw_shading_color_values_seq { #1 - 1 }
            }
        }
        \hawkdraw_hex_to_stream:e {
            \hawkdraw_color_pdf_to_hex:n {#2}
        }
        \hawkdraw_hex_to_stream:e {
            \hawkdraw_color_pdf_to_hex:n {#2}
        }
    }
}

\cs_new:Npn \__hawkdraw_shading_conic_stream_point_polar:nn #1#2 {
    % get coordinates from shifted point polar and normalize to FFFF
    \hawkdraw_hex_to_stream:e {
        \int_format:nn {
            \fp_to_int:n {
                \hawkdraw_tuple_use_i:n {
                    ( 50bp , 50bp ) + (
                        \draw_point_polar:nn { ( #1 ) * 50bp } {#2}
                    )
                } / 100bp * 65535
            }
        } { 0>4X }
    }
    \hawkdraw_hex_to_stream:e {
        \int_format:nn {
            \fp_to_int:n {
                \hawkdraw_tuple_use_ii:n {
                    ( 50bp , 50bp ) + (
                        \draw_point_polar:nn { ( #1 ) * 50bp } {#2}
                    )
                } / 100bp * 65535
            }
        } { 0>4X }
    }
}

\seq_new:N \l__hawkdraw_shading_color_values_tmp_seq

\cs_new_protected:Npn \hawkdraw_shading_conic_color_set_values:Nn #1#2 {
    \seq_clear:N #1
    \seq_set_from_clist:Nn \l__hawkdraw_shading_color_values_tmp_seq {#2}
    \seq_map_indexed_inline:Nn \l__hawkdraw_shading_color_values_tmp_seq {
        \int_compare:nNnTF {##1} = { \c_one_int } {
            % first entry is the start color
            \__hawkdraw_shading_color_set_value:Nnnn #1 {##2} {##2} {##2}
        } {
            \__hawkdraw_shading_color_set_value:Nnnn #1 {
                \fp_to_decimal:n {
                    0.75 * \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 } +
                    0.25 * ##2
                }
            } {
                \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 }
                ! 75 ! ##2
            } {
                \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 }
                ! 75 ! ##2
            }
            \__hawkdraw_shading_color_set_value:Nnnn #1 {
                \fp_to_decimal:n {
                    0.5 * \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 } +
                    0.5 * ##2
                }
            } {
                \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 }
                ! 50 ! ##2
            } {
                \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 }
                ! 50 ! ##2
            }
            \__hawkdraw_shading_color_set_value:Nnnn #1 {
                \fp_to_decimal:n {
                    0.25 * \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 } +
                    0.75 * ##2
                }
            } {
                \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 }
                ! 25 ! ##2
            } {
                \seq_item:Nn \l__hawkdraw_shading_color_values_tmp_seq { ##1 - 1 }
                ! 25 ! ##2
            }
            \__hawkdraw_shading_color_set_value:Nnnn #1 {##2} {##2} {##2}
        }
    }
}

\tl_new:N \l__hawkdraw_shading_conic_stream_tl

\cs_new_protected:Npn \hawkdraw_shading_conic:n #1 {
    \tl_if_exist:cF {
        c__hawkdraw_shading_
        conic
        . #1
        . \fp_to_decimal:n { \l_hawkdraw_shading_angle_delta_fp }
        _tl
    } {
        \hawkdraw_shading_conic_color_set_values:Nn
            \l__hawkdraw_shading_color_values_seq
            {#1}
        \hawkdraw_shading_conic_stream:N \l__hawkdraw_shading_conic_stream_tl
        \hawkdraw_shading_set:enn {
            conic
            . #1
            . \fp_to_decimal:n { \l_hawkdraw_shading_angle_delta_fp }
        } {
            /ShadingType ~ 6 ~
            \hawkdraw_case_color_space:nnn {
                /ColorSpace ~ /DeviceGray ~
            } {
                /ColorSpace ~ /DeviceRGB ~
            } {
                /ColorSpace ~ /DeviceCMYK ~
            }
            /BitsPerCoordinate ~ 16 ~
            /BitsPerComponent ~ 8 ~
            /BitsPerFlag ~ 8 ~
            /Decode ~ [ ~
                0 ~ 100 ~
                0 ~ 100 ~
                \hawkdraw_case_color_space:nnn {
                    0 ~ 1 ~
                } {
                    0 ~ 1 ~
                    0 ~ 1 ~
                    0 ~ 1 ~
                } {
                    0 ~ 1 ~
                    0 ~ 1 ~
                    0 ~ 1 ~
                    0 ~ 1 ~
                }
            ] ~
        } {
            \tl_use:N \l__hawkdraw_shading_conic_stream_tl
        }
    }
}

%EOF