%%% Package   : scaletextbullet -- Resize the \textbullet without changing its
%%% vertical center
%%% Copyright : 2024-2025 (c) Oliver Beery <beeryoliver@gmail.com>
%%% CTAN      : https://ctan.org/pkg/scaletextbullet
%%% Repository: https://github.com/beeryoliver/scaletextbullet
%%% License   : The LaTeX Project Public License 1.3c

%%> \section{Loading the package}

\NeedsTeXFormat{LaTeX2e}[2023-11-01]
\ProvidesExplPackage{scaletextbullet}{2025-10-14}{2.0.3}
  {Resize the \noexpand\textbullet without changing its vertical center.}

\msg_new:nnn { scaletextbullet } { l3kernel }
  {
    The~ scaletextbullet~ package~ could~ not~ load. \\
    This~ package~ requires~
    L3~ programming~ layer~ version~ 2023-11-01~ or~ later.
  }
\IfExplAtLeastTF { 2023-11-01 } { }
  { \msg_critical:nn { scaletextbullet } { l3kernel } }

%%> \section{Messages}

\msg_new:nnn { scaletextbullet } { math-mode-invalid }
  { '\iow_char:N \\ #1'~ invalid~ in~ math~ mode~ \msg_line_context:. }
\msg_new:nnn { scaletextbullet } { factor-invalid }
  { Invalid~ \iow_char:N \\ textbullet~ factor~ '#1'~ \msg_line_context:. }
\msg_new:nnn { scaletextbullet } { scale-invalid }
  { Invalid~ scale~ factor~ '#1'~ \msg_line_context:. }
\msg_new:nnn { scaletextbullet } { count-invalid }
  {
    Invalid~ number~ of~ \iow_char:N \\ textbullet~ s~ '#1'~
    \msg_line_context:.
  }
\msg_new:nnn { scaletextbullet } { scale-zero }
  {
    The~ new~ \iow_char:N \\ textbullet~ would~ have~ zero~ dimensions~
    \msg_line_context:.
  }
\msg_new:nnn { scaletextbullet } { count-zero }
  { No~ \iow_char:N \\ textbullet~ s~ were~ printed~ \msg_line_context:. }

%%> \section{Some variables}

\fp_new:N \l__scaletextbullet_factor_fp
\fp_set:Nn \l__scaletextbullet_factor_fp { 0.4 }
\int_new:N \l__scaletextbullet_count_int
\fp_new:N \l__scaletextbullet_scale_fp
\box_new:N \l__scaletextbullet_textbullet_box

% Used only to speed up floating point computations.
\fp_const:Nn \c__scaletextbullet_count_two_fp   { 0.7071 0678 1186 5475 }
\fp_const:Nn \c__scaletextbullet_count_three_fp { 0.5773 5026 9189 6258 }
\fp_const:Nn \c__scaletextbullet_count_four_fp  { 0.5 }

%%> \section{Some functions}

% Lowers the \textbullet to the baseline, scales it by a factor of #2, and
% then raises it back to the vertical center.
% I have referenced code by the user egreg:
% https://tex.stackexchange.com/questions/620507/how-to-resize-textbullet-without-the-bullet-moving-down
% the bottom and center of \textbullet
\dim_new:N \l__scaletextbullet_bottom_dim
\dim_new:N \l__scaletextbullet_center_dim
\cs_new_protected:Npn \__scaletextbullet_box_scale:Nn #1#2
  {
    \mode_leave_vertical:
    \hbox_set:Nn #1 { \textbullet }
    \dim_set:Nn \l__scaletextbullet_bottom_dim
      {
        \box_ht:N #1 -
        \fp_to_dim:n
          { \l__scaletextbullet_factor_fp * \dim_to_fp:n { \box_wd:N #1 } }
      }
    \dim_set:Nn \l__scaletextbullet_center_dim
      { ( \box_ht:N #1 + \l__scaletextbullet_bottom_dim ) / 2 }
    \hbox_set:Nn #1
      {
        \box_move_down:nn { \l__scaletextbullet_bottom_dim } { \box_use:N #1 }
      }
    \box_scale:Nnn #1 {#2} {#2}
    \hbox_set:Nn #1
      {
        \box_move_up:nn { \l__scaletextbullet_center_dim - \box_ht:N #1 / 2 }
          { \box_use:N #1 }
      }
  }

\cs_new:Npn \__scaletextbullet_box_if_zero:NTF #1
  {
    \bool_lazy_or:nnTF
      { \dim_compare_p:nNn { \box_wd:N #1 } = \c_zero_dim }
      { \dim_compare_p:nNn { \box_ht_plus_dp:N #1 } = \c_zero_dim }
  }

%%> \section{Document commands}

\NewDocumentCommand \settextbulletfactor { m }
  {
    \mode_if_math:TF
      {
        \msg_error:nnn { scaletextbullet } { math-mode-invalid }
          { settextbulletfactor }
      }
      { \__scaletextbullet_set_factor:n {#1} }
  }
\cs_new_protected:Npn \__scaletextbullet_set_factor:n #1
  {
    \fp_set:Nn \l__scaletextbullet_factor_fp {#1}
    \fp_compare:nF { \c_zero_fp < \l__scaletextbullet_factor_fp <= \c_one_fp }
      {
        \msg_error:nne { scaletextbullet } { factor-invalid }
          { \fp_to_tl:N \l__scaletextbullet_factor_fp }
      }
  }

\NewDocumentCommand \scaletextbullet { m }
  {
    \mode_if_math:TF
      {
        \msg_error:nnn { scaletextbullet } { math-mode-invalid }
          { scaletextbullet }
      }
      { \__scaletextbullet_scale_textbullet:n {#1} }
  }
\cs_new_protected:Npn \__scaletextbullet_scale_textbullet:n #1
  {
    \fp_set:Nn \l__scaletextbullet_scale_fp {#1}
    \fp_compare:nNnTF \l__scaletextbullet_scale_fp < \c_zero_fp
      {
        \msg_error:nne { scaletextbullet } { scale-invalid }
          { \fp_to_tl:N \l__scaletextbullet_scale_fp }
      }
      {
        \__scaletextbullet_box_scale:Nn \l__scaletextbullet_textbullet_box
          { \l__scaletextbullet_scale_fp }
        \__scaletextbullet_box_if_zero:NTF \l__scaletextbullet_textbullet_box
          { \msg_warning:nn { scaletextbullet } { scale-zero } }
          { \box_use:N \l__scaletextbullet_textbullet_box }
      }
  }

\NewDocumentCommand \scaletextbullets { o m }
  {
    \mode_if_math:TF
      {
        \msg_error:nnn { scaletextbullet } { math-mode-invalid }
          { scaletextbullets }
      }
      {
        \IfNoValueTF {#1}
          { \__scaletextbullet_scale_textbullets:n {#2} }
          { \__scaletextbullet_scale_textbullets:nn {#1} {#2} }
      }
  }
\cs_new_protected:Npn \__scaletextbullet_scale_textbullets:n #1
  {
    \int_set:Nn \l__scaletextbullet_count_int {#1}
    \int_compare:nNnTF \l__scaletextbullet_count_int > 0
      {
        \int_case:nnF { \l__scaletextbullet_count_int }
          {
            { 1 } { \fp_set_eq:NN \l__scaletextbullet_scale_fp \c_one_fp }
            { 2 }
            {
              \fp_set_eq:NN \l__scaletextbullet_scale_fp
                \c__scaletextbullet_count_two_fp
            }
            { 3 }
            {
              \fp_set_eq:NN \l__scaletextbullet_scale_fp
                \c__scaletextbullet_count_three_fp
            }
            { 4 }
            {
              \fp_set_eq:NN \l__scaletextbullet_scale_fp
                \c__scaletextbullet_count_four_fp
            }
          }
          {
            \fp_set:Nn \l__scaletextbullet_scale_fp
              { \int_use:N \l__scaletextbullet_count_int ^ -0.5 }
          }
        \__scaletextbullet_box_scale:Nn \l__scaletextbullet_textbullet_box
          { \l__scaletextbullet_scale_fp }
        \prg_replicate:nn { \l__scaletextbullet_count_int }
          { \box_use:N \l__scaletextbullet_textbullet_box }
      }
      {
        \int_if_zero:nTF { \l__scaletextbullet_count_int }
          { \msg_warning:nn { scaletextbullet } { count-zero } }
          {
            \msg_error:nnV { scaletextbullet } { count-invalid }
              \l__scaletextbullet_count_int
          }
      }
  }
\cs_new_protected:Npn \__scaletextbullet_scale_textbullets:nn #1#2
  {
    \fp_set:Nn \l__scaletextbullet_scale_fp {#1}
    \int_set:Nn \l__scaletextbullet_count_int {#2}
    \fp_compare:nNnTF \l__scaletextbullet_scale_fp < \c_zero_fp
      {
        \msg_error:nne { scaletextbullet } { scale-invalid }
          { \fp_to_tl:N \l__scaletextbullet_scale_fp }
      }
      {
        \int_compare:nNnTF \l__scaletextbullet_count_int > 0
          {
            \__scaletextbullet_box_scale:Nn \l__scaletextbullet_textbullet_box
              { \l__scaletextbullet_scale_fp }
            \__scaletextbullet_box_if_zero:NTF
              \l__scaletextbullet_textbullet_box
              { \msg_warning:nn { scaletextbullet } { scale-zero } }
              {
                \prg_replicate:nn { \l__scaletextbullet_count_int }
                  { \box_use:N \l__scaletextbullet_textbullet_box }
              }
          }
          {
            \int_if_zero:nTF { \l__scaletextbullet_count_int }
              { \msg_warning:nn { scaletextbullet } { count-zero } }
              {
                \msg_error:nnV { scaletextbullet } { count-invalid }
                  \l__scaletextbullet_count_int
              }
          }
      }
  }

\NewDocumentCommand \scaletextbulletdebug { }
  {
    \mode_if_math:TF
      {
        \msg_error:nnn { scaletextbullet } { math-mode-invalid }
          { scaletextbulletdebug }
      }
      { \__scaletextbullet_debug: }
  }
% I have referenced code by the user egreg:
% https://tex.stackexchange.com/questions/620507/how-to-resize-textbullet-without-the-bullet-moving-down
\cs_new_protected:Npn \__scaletextbullet_debug:
  {
    \int_step_inline:nn { 15 }
      {
        \fp_set:Nn \l__scaletextbullet_scale_fp { ##1 ^ -0.5 }
        \__scaletextbullet_box_scale:Nn \l__scaletextbullet_textbullet_box
          { \l__scaletextbullet_scale_fp }
        \box_use:N \l__scaletextbullet_textbullet_box
      }
    \,
    \group_begin:
      \setlength \fboxrule { 0.1pt }
      \setlength \fboxsep { 0pt }
      \framebox
        [
          \fp_to_dim:n
            { \l__scaletextbullet_factor_fp * \dim_to_fp:n { \width } }
        ]
        { \textbullet }
    \group_end:
  }