% Package   : fontscale -- A flexible interface for setting font sizes
% Copyright : 2024-2026 (c) Oliver Beery <beeryoliver@gmail.com>
% CTAN      : https://ctan.org/pkg/fontscale
% Repository: https://github.com/beeryoliver/fontscale
% License   : The LaTeX Project Public License 1.3c

% Loading the package

\NeedsTeXFormat{LaTeX2e}[2023-11-01]
\ProvidesExplPackage{fontscale}{2026-02-17}{5.0.0}
  {A flexible interface for setting font sizes}

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

% Some variables

% Declares and initializes the font size and font baselineskip of each font
% size command from \tiny to \Huge.

\dim_new:N \l_fontscale_tiny_size_dim
\dim_new:N \l_fontscale_scriptsize_size_dim
\dim_new:N \l_fontscale_footnotesize_size_dim
\dim_new:N \l_fontscale_small_size_dim
\dim_new:N \l_fontscale_normalsize_size_dim
\dim_new:N \l_fontscale_large_size_dim
\dim_new:N \l_fontscale_Large_size_dim
\dim_new:N \l_fontscale_LARGE_size_dim
\dim_new:N \l_fontscale_huge_size_dim
\dim_new:N \l_fontscale_Huge_size_dim

\skip_new:N \l_fontscale_tiny_baselineskip_skip
\skip_new:N \l_fontscale_scriptsize_baselineskip_skip
\skip_new:N \l_fontscale_footnotesize_baselineskip_skip
\skip_new:N \l_fontscale_small_baselineskip_skip
\skip_new:N \l_fontscale_normalsize_baselineskip_skip
\skip_new:N \l_fontscale_large_baselineskip_skip
\skip_new:N \l_fontscale_Large_baselineskip_skip
\skip_new:N \l_fontscale_LARGE_baselineskip_skip
\skip_new:N \l_fontscale_huge_baselineskip_skip
\skip_new:N \l_fontscale_Huge_baselineskip_skip

\dim_set:Nn \l_fontscale_tiny_size_dim         {  6pt }
\dim_set:Nn \l_fontscale_scriptsize_size_dim   {  7pt }
\dim_set:Nn \l_fontscale_footnotesize_size_dim {  8pt }
\dim_set:Nn \l_fontscale_small_size_dim        {  9pt }
\dim_set:Nn \l_fontscale_normalsize_size_dim   { 10pt }
\dim_set:Nn \l_fontscale_large_size_dim        { 11pt }
\dim_set:Nn \l_fontscale_Large_size_dim        { 12pt }
\dim_set:Nn \l_fontscale_LARGE_size_dim        { 14pt }
\dim_set:Nn \l_fontscale_huge_size_dim         { 16pt }
\dim_set:Nn \l_fontscale_Huge_size_dim         { 18pt }

\skip_set:Nn \l_fontscale_tiny_baselineskip_skip         {  7.2pt }
\skip_set:Nn \l_fontscale_scriptsize_baselineskip_skip   {  8.4pt }
\skip_set:Nn \l_fontscale_footnotesize_baselineskip_skip {  9.6pt }
\skip_set:Nn \l_fontscale_small_baselineskip_skip        { 10.8pt }
\skip_set:Nn \l_fontscale_normalsize_baselineskip_skip   { 12  pt }
\skip_set:Nn \l_fontscale_large_baselineskip_skip        { 13.2pt }
\skip_set:Nn \l_fontscale_Large_baselineskip_skip        { 14.4pt }
\skip_set:Nn \l_fontscale_LARGE_baselineskip_skip        { 16.8pt }
\skip_set:Nn \l_fontscale_huge_baselineskip_skip         { 19.2pt }
\skip_set:Nn \l_fontscale_Huge_baselineskip_skip         { 21.6pt }

% Stores the current font size and font baselineskip. These variables will be
% initialized when \normalsize is used.
\dim_new:N \l_fontscale_size_dim
\skip_new:N \l_fontscale_baselineskip_skip
\hook_gput_code:nnn { selectfont } { fontscale }
  {
    \dim_set:Nn \l_fontscale_size_dim { \f@size pt }
    \skip_set:Nn \l_fontscale_baselineskip_skip { \f@baselineskip }
  }

% Used only to speed up floating point computations.
\fp_new:N \l__fontscale_musical_base_fp
\fp_new:N \l__fontscale_musical_notes_fp

% Scratch variable
\fp_new:N \l__fontscale_tmp_fp

% Define keys

\str_new:N \l__fontscale_typographic_scale_str
\str_new:N \l__fontscale_classic_base_str
\str_new:N \l__fontscale_classic_point_str

\keys_define:nn { fontscale }
  {
      baselineskip-size-ratio .fp_set:N =
        \l__fontscale_baselineskip_size_ratio_fp
    , baselineskip-size-ratio .value_required:n = true

    , typographic-scale .choices:nn = { classic , musical }
        { \str_set:Nn \l__fontscale_typographic_scale_str {#1} }
    , typographic-scale .value_required:n = true

    , classic .meta:n = { typographic-scale = classic }
    , classic .value_forbidden:n = true

    , musical .meta:n = { typographic-scale = musical }
    , musical .value_forbidden:n = true
  }
\keys_define:nn { fontscale / classic }
  {
      base .choices:nn = { 10 , 11 , 12 }
        { \str_set:Nn \l__fontscale_classic_base_str {#1} }
    , base .value_required:n = true

    , point .choices:nn = { pt , bp , dd , nd }
        { \str_set:Nn \l__fontscale_classic_point_str {#1} }
    , point .value_required:n = true
  }
\keys_define:nn { fontscale / musical }
  {
      base .dim_set:N = \l__fontscale_musical_base_dim
    , base .value_required:n = true

    , ratio .fp_set:N = \l__fontscale_musical_ratio_fp
    , ratio .value_required:n = true

    , notes .int_set:N = \l__fontscale_musical_notes_int
    , notes .value_required:n = true
  }
\keys_define:nn { fontscale / normalsize }
  {
      size .tl_set:N = \l__fontscale_normalsize_size_tl
    , size .value_required:n = true

    , baselineskip-size-ratio .tl_set:N =
        \l__fontscale_normalsize_baselineskip_size_ratio_tl
    , baselineskip-size-ratio .value_required:n = true

    , baselineskip .tl_set:N = \l__fontscale_normalsize_baselineskip_tl
    , baselineskip .value_required:n = true
  }
\tl_map_inline:nn
  {
    {tiny} {scriptsize} {footnotesize} {small}
    {large} {Large} {LARGE} {huge} {Huge}
  }
  {
    \keys_define:nn { fontscale / #1 }
      {
          size-normalsize-ratio .tl_set:c =
            { l__fontscale_#1_size_normalsize_ratio_tl }
        , size-normalsize-ratio .value_required:n = true

        , size .tl_set:c = { l__fontscale_#1_size_tl }
        , size .value_required:n = true

        , baselineskip-size-ratio .tl_set:c =
            { l__fontscale_#1_baselineskip_size_ratio_tl }
        , baselineskip-size-ratio .value_required:n = true

        , baselineskip .tl_set:c = { l__fontscale_#1_baselineskip_tl }
        , baselineskip .value_required:n = true
      }
  }
% As an optimization, the keys below are defined using .tl_set:N instead of
% .meta:n.
\keys_define:nn { fontscale }
  {
      tiny .tl_set:N = \l__fontscale_tiny_size_tl
    , tiny .value_required:n = true

    , scriptsize .tl_set:N = \l__fontscale_scriptsize_size_tl
    , scriptsize .value_required:n = true

    , footnotesize .tl_set:N = \l__fontscale_footnotesize_size_tl
    , footnotesize .value_required:n = true

    , small .tl_set:N = \l__fontscale_small_size_tl
    , small .value_required:n = true

    , normalsize .tl_set:N = \l__fontscale_normalsize_size_tl
    , normalsize .value_required:n = true

    , large .tl_set:N = \l__fontscale_large_size_tl
    , large .value_required:n = true

    , Large .tl_set:N = \l__fontscale_Large_size_tl
    , Large .value_required:n = true

    , LARGE .tl_set:N = \l__fontscale_LARGE_size_tl
    , LARGE .value_required:n = true

    , huge .tl_set:N = \l__fontscale_huge_size_tl
    , huge .value_required:n = true

    , Huge .tl_set:N = \l__fontscale_Huge_size_tl
    , Huge .value_required:n = true
  }

% Sets the keys to their initial values. This is significantly faster than
% using .initial:n and \keys_precompile:nnN. The special value of \q_no_value
% is used to test if the key has been set by the user.
\cs_new_protected:Npn \__fontscale_keys_set_initial:
  {
    \fp_set:Nn  \l__fontscale_baselineskip_size_ratio_fp { 1.2 }
    \str_set:Nn \l__fontscale_typographic_scale_str { classic }
    \str_set:Nn \l__fontscale_classic_base_str  { 10 }
    \str_set:Nn \l__fontscale_classic_point_str { pt }
    \dim_set:Nn \l__fontscale_musical_base_dim  { 10pt }
    \fp_set:Nn  \l__fontscale_musical_ratio_fp  { 2 }
    \int_set:Nn \l__fontscale_musical_notes_int { 5 }

    \tl_map_inline:nn
      {
        \l__fontscale_tiny_size_normalsize_ratio_tl
        \l__fontscale_scriptsize_size_normalsize_ratio_tl
        \l__fontscale_footnotesize_size_normalsize_ratio_tl
        \l__fontscale_small_size_normalsize_ratio_tl
        \l__fontscale_large_size_normalsize_ratio_tl
        \l__fontscale_Large_size_normalsize_ratio_tl
        \l__fontscale_LARGE_size_normalsize_ratio_tl
        \l__fontscale_huge_size_normalsize_ratio_tl
        \l__fontscale_Huge_size_normalsize_ratio_tl

        \l__fontscale_tiny_size_tl
        \l__fontscale_scriptsize_size_tl
        \l__fontscale_footnotesize_size_tl
        \l__fontscale_small_size_tl
        \l__fontscale_normalsize_size_tl
        \l__fontscale_large_size_tl
        \l__fontscale_Large_size_tl
        \l__fontscale_LARGE_size_tl
        \l__fontscale_huge_size_tl
        \l__fontscale_Huge_size_tl

        \l__fontscale_tiny_baselineskip_size_ratio_tl
        \l__fontscale_scriptsize_baselineskip_size_ratio_tl
        \l__fontscale_footnotesize_baselineskip_size_ratio_tl
        \l__fontscale_small_baselineskip_size_ratio_tl
        \l__fontscale_normalsize_baselineskip_size_ratio_tl
        \l__fontscale_large_baselineskip_size_ratio_tl
        \l__fontscale_Large_baselineskip_size_ratio_tl
        \l__fontscale_LARGE_baselineskip_size_ratio_tl
        \l__fontscale_huge_baselineskip_size_ratio_tl
        \l__fontscale_Huge_baselineskip_size_ratio_tl

        \l__fontscale_tiny_baselineskip_tl
        \l__fontscale_scriptsize_baselineskip_tl
        \l__fontscale_footnotesize_baselineskip_tl
        \l__fontscale_small_baselineskip_tl
        \l__fontscale_normalsize_baselineskip_tl
        \l__fontscale_large_baselineskip_tl
        \l__fontscale_Large_baselineskip_tl
        \l__fontscale_LARGE_baselineskip_tl
        \l__fontscale_huge_baselineskip_tl
        \l__fontscale_Huge_baselineskip_tl
      }
      { \tl_set:Nn ##1 { \q_no_value } }
  }
\__fontscale_keys_set_initial:

% Set and process keys

\NewDocumentCommand \fontscalesetup { s m }
  {
    \mode_if_math:TF
      { \msg_error:nnn { fontscale } { math-mode-invalid } { fontscalesetup } }
      {
        \IfBooleanT #1 { \__fontscale_keys_set_initial: }
        \keys_set:nn { fontscale } {#2}
        \__fontscale_keys_process:
        \normalsize
      }
  }
\cs_new_protected:Npn \__fontscale_keys_process:
  {
    \__fontscale_keys_process_start:
    \__fontscale_keys_process_sizes_normalsize:
    \__fontscale_keys_process_sizes:
    \__fontscale_keys_process_baselineskips:
    \__fontscale_keys_process_end:
    \prg_break_point:
  }
\cs_new_protected:Npn \__fontscale_keys_process_start:
  {
    \dim_compare:nF { \c_zero_dim < \l__fontscale_musical_base_dim < 2048pt }
      {
        \msg_error:nn { fontscale } { musical-base-invalid }
        \prg_break:
      }
    \fp_compare:nNnF \l__fontscale_musical_ratio_fp > \c_one_fp
      {
        \msg_error:nn { fontscale } { musical-ratio-invalid }
        \prg_break:
      }
    \int_compare:nNnF \l__fontscale_musical_notes_int > 0
      {
        \msg_error:nn { fontscale } { musical-notes-invalid }
        \prg_break:
      }
  }
\cs_new_protected:Npn \__fontscale_keys_process_sizes_normalsize:
  {
    \quark_if_no_value:NTF \l__fontscale_normalsize_size_tl
      {
        \dim_set:Nn \l_fontscale_normalsize_size_dim
          {
            \str_case:Vn \l__fontscale_typographic_scale_str
              {
                { classic }
                {
                  \l__fontscale_classic_base_str
                  \l__fontscale_classic_point_str
                }
                { musical } { \l__fontscale_musical_base_dim }
              }
          }
      }
      {
        \dim_set:Nn \l_fontscale_normalsize_size_dim
          { \l__fontscale_normalsize_size_tl }
        \tl_set:NV \l__fontscale_normalsize_size_tl
          \l_fontscale_normalsize_size_dim
      }
    \dim_compare:nF { \c_zero_dim < \l_fontscale_normalsize_size_dim < 2048pt }
      {
        \msg_error:nnn { fontscale } { name-font-size-invalid } { normalsize }
        \prg_break:
      }
  }
\cs_new_protected:Npn \__fontscale_keys_process_sizes:
  {
    \str_case:Vn \l__fontscale_typographic_scale_str
      {
        { classic }
        {
          \cs_set_eq:Nc \__fontscale_keys_process_sizes_aux:n
            {
              __fontscale_keys_process_sizes_classic_
              \l__fontscale_classic_base_str :n
            }
        }
        { musical }
        {
          \cs_set_eq:NN \__fontscale_keys_process_sizes_aux:n
            \__fontscale_keys_process_sizes_musical:n
          \fp_set:Nn \l__fontscale_musical_base_fp
            { \dim_to_fp:n { \l__fontscale_musical_base_dim } }
          \fp_set:Nn \l__fontscale_musical_notes_fp
            { \int_use:N \l__fontscale_musical_notes_int }
        }
      }
    \tl_map_function:nN
      {
        {tiny} {scriptsize} {footnotesize} {small}
        {large} {Large} {LARGE} {huge} {Huge}
      }
      \__fontscale_keys_process_sizes:n
  }
\cs_new_protected:cpn { __fontscale_keys_process_sizes_classic_10:n } #1
  {
    \dim_set:cn { l_fontscale_#1_size_dim }
      {
        \str_case:nn {#1}
          {
            { tiny         } {  6 }
            { scriptsize   } {  7 }
            { footnotesize } {  8 }
            { small        } {  9 }
            { large        } { 11 }
            { Large        } { 12 }
            { LARGE        } { 14 }
            { huge         } { 16 }
            { Huge         } { 18 }
          }
        \l__fontscale_classic_point_str
      }
  }
\cs_new_protected:cpn { __fontscale_keys_process_sizes_classic_11:n } #1
  {
    \dim_set:cn { l_fontscale_#1_size_dim }
      {
        \str_case:nn {#1}
          {
            { tiny         } {  7 }
            { scriptsize   } {  8 }
            { footnotesize } {  9 }
            { small        } { 10 }
            { large        } { 12 }
            { Large        } { 14 }
            { LARGE        } { 16 }
            { huge         } { 18 }
            { Huge         } { 21 }
          }
        \l__fontscale_classic_point_str
      }
  }
\cs_new_protected:cpn { __fontscale_keys_process_sizes_classic_12:n } #1
  {
    \dim_set:cn { l_fontscale_#1_size_dim }
      {
        \str_case:nn {#1}
          {
            { tiny         } {  8 }
            { scriptsize   } {  9 }
            { footnotesize } { 10 }
            { small        } { 11 }
            { large        } { 14 }
            { Large        } { 16 }
            { LARGE        } { 18 }
            { huge         } { 21 }
            { Huge         } { 24 }
          }
        \l__fontscale_classic_point_str
      }
  }
\cs_new_protected:Npn \__fontscale_keys_process_sizes_musical:n #1
  {
    \dim_set:cn { l_fontscale_#1_size_dim }
      {
        \fp_to_dim:n
          {
            \l__fontscale_musical_base_fp * \l__fontscale_musical_ratio_fp ^
            (
              \str_case:nn {#1}
                {
                  { tiny         } { -4 }
                  { scriptsize   } { -3 }
                  { footnotesize } { -2 }
                  { small        } { -1 }
                  { large        } {  1 }
                  { Large        } {  2 }
                  { LARGE        } {  3 }
                  { huge         } {  4 }
                  { Huge         } {  5 }
                }
              / \l__fontscale_musical_notes_fp
            )
          }
      }
  }
\cs_new_protected:Npn \__fontscale_keys_process_sizes:n #1
  {
    \quark_if_no_value:cF { l__fontscale_#1_size_normalsize_ratio_tl }
      {
        \fp_set:Nn \l__fontscale_tmp_fp
          { \use:c { l__fontscale_#1_size_normalsize_ratio_tl } }
        \tl_set:cV { l__fontscale_#1_size_normalsize_ratio_tl }
          \l__fontscale_tmp_fp
        \dim_set:cn { l_fontscale_#1_size_dim }
          {
            \fp_to_dim:n
              {
                \l__fontscale_tmp_fp *
                \dim_to_fp:n { \l_fontscale_normalsize_size_dim }
              }
          }
        \quark_if_no_value:cT { l__fontscale_#1_size_tl } { \prg_break: }
      }
    \quark_if_no_value:cF { l__fontscale_#1_size_tl }
      {
        \dim_set:cn { l_fontscale_#1_size_dim }
          { \use:c { l__fontscale_#1_size_tl } }
        \tl_set:cv { l__fontscale_#1_size_tl } { l_fontscale_#1_size_dim }
        \quark_if_no_value:cF { l__fontscale_#1_size_normalsize_ratio_tl }
          {
            \msg_warning:nnn { fontscale }
              { size-normalsize-ratio-overwritten } {#1}
          }
        \prg_break:
      }
    \__fontscale_keys_process_sizes_aux:n {#1}
    \prg_break_point:
    \dim_compare:nF
      { \c_zero_dim < \use:c { l_fontscale_#1_size_dim } < 2048pt }
      {
        \tl_map_break:n
          {
            \msg_error:nnn { fontscale } { name-font-size-invalid } {#1}
            \prg_break:
          }
      }
  }
\cs_new_protected:Npn \__fontscale_keys_process_baselineskips:
  {
    \tl_map_function:nN
      {
        {tiny} {scriptsize} {footnotesize} {small} {normalsize}
        {large} {Large} {LARGE} {huge} {Huge}
      }
      \__fontscale_keys_process_baselineskips:n
  }
\cs_new_protected:Npn \__fontscale_keys_process_baselineskips:n #1
  {
    \quark_if_no_value:cF { l__fontscale_#1_baselineskip_size_ratio_tl }
      {
        \fp_set:Nn \l__fontscale_tmp_fp
          { \use:c { l__fontscale_#1_baselineskip_size_ratio_tl } }
        \tl_set:cV { l__fontscale_#1_baselineskip_size_ratio_tl }
          \l__fontscale_tmp_fp
        \skip_set:cn { l_fontscale_#1_baselineskip_skip }
          {
            \fp_to_dim:n
              {
                \l__fontscale_tmp_fp *
                \dim_to_fp:n { \use:c { l_fontscale_#1_size_dim } }
              }
          }
        \quark_if_no_value:cT { l__fontscale_#1_baselineskip_tl }
          { \prg_break: }
      }
    \quark_if_no_value:cF { l__fontscale_#1_baselineskip_tl }
      {
        \skip_set:cn { l_fontscale_#1_baselineskip_skip }
          { \use:c { l__fontscale_#1_baselineskip_tl } }
        \tl_set:cv { l__fontscale_#1_baselineskip_tl }
          { l_fontscale_#1_baselineskip_skip }
        \quark_if_no_value:cF { l__fontscale_#1_baselineskip_size_ratio_tl }
          {
            \msg_warning:nnn { fontscale }
              { baselineskip-size-ratio-overwritten } {#1}
          }
        \prg_break:
      }
    \skip_set:cn { l_fontscale_#1_baselineskip_skip }
      {
        \fp_to_dim:n
          {
            \dim_to_fp:n { \use:c { l_fontscale_#1_size_dim } } *
            \l__fontscale_baselineskip_size_ratio_fp
          }
      }
    \prg_break_point:
  }
\cs_new_protected:Npn \__fontscale_keys_process_end:
  {
    \dim_compare:nF
      {
          \l_fontscale_tiny_size_dim
        < \l_fontscale_scriptsize_size_dim
        < \l_fontscale_footnotesize_size_dim
        < \l_fontscale_small_size_dim
        < \l_fontscale_normalsize_size_dim
        < \l_fontscale_large_size_dim
        < \l_fontscale_Large_size_dim
        < \l_fontscale_LARGE_size_dim
        < \l_fontscale_huge_size_dim
        < \l_fontscale_Huge_size_dim
      }
      { \msg_warning:nn { fontscale } { font-sizes-wrong-order } }
  }

% Document commands

% Defines the font size commands from \tiny to \Huge and initializes to
% \normalsize. Their corresponding internal functions are not used elsewhere in
% this package for compatibility with hooks.
\tl_map_inline:nn
  {
    {tiny} {scriptsize} {footnotesize} {small} {normalsize}
    {large} {Large} {LARGE} {huge} {Huge}
  }
  {
    \use:e
      {
        \DeclareDocumentCommand \exp_not:c {#1} { }
          {
            \exp_not:N \mode_if_math:TF
              { \msg_error:nnn { fontscale } { math-mode-invalid } {#1} }
              { \exp_not:c { __fontscale_#1: } }
          }
      }
    \cs_new_protected:cpe { __fontscale_#1: }
      {
        \cs_set_eq:NN \exp_not:N \@currsize \exp_not:c {#1}
        \exp_not:N \fontsize
          { \exp_not:N \dim_use:N \exp_not:c { l_fontscale_#1_size_dim } }
          {
            \exp_not:N \skip_use:N
            \exp_not:c { l_fontscale_#1_baselineskip_skip }
          }
        \exp_not:N \selectfont
      }
  }
\normalsize

% Defines a few user-level lengths by abusing \cs_new_eq:NN.
\cs_new_eq:NN \currentfontsize \l_fontscale_size_dim
\cs_new_eq:NN \currentfontbaselineskip \l_fontscale_baselineskip_skip
\cs_new_eq:NN \currentnormalsize \l_fontscale_normalsize_size_dim

\NewDocumentCommand \setfontsize { o m }
  {
    \mode_if_math:TF
      { \msg_error:nnn { fontscale } { math-mode-invalid } { setfontsize } }
      {
        \IfNoValueTF {#1}
          { \__fontscale_set_font_size:n {#2} }
          { \__fontscale_set_font_size:nn {#2} {#1} }
      }
  }
% Similar to \fontsize + \selectfont, except that it:
% (1) Takes dimen and skip expressions as arguments without appending a default
% unit of 'pt'.
% (2) Avoids the issue where \f@size is set to the new font size before the
% second argument is expanded.
% (3) Issues a user-friendly error message if the font size is invalid.
\cs_new_protected:Npn \__fontscale_set_font_size:nn #1#2
  {
    \use:e
      {
        \__fontscale_set_font_size_aux:nn { \dim_eval:n {#1} }
          { \skip_eval:n {#2} }
      }
  }
\cs_new_protected:Npn \__fontscale_set_font_size_aux:nn #1#2
  {
    \dim_compare:nTF { \c_zero_dim < #1 < 2048pt }
      { \fontsize {#1} {#2} \selectfont }
      { \msg_error:nnn { fontscale } { font-size-invalid } {#1} }
  }
% Similar to \__fontscale_set_font_size:nn, except that it sets the font
% baselineskip equal to the new font size times
% \l__fontscale_baselineskip_size_ratio_fp.
\cs_new_protected:Npn \__fontscale_set_font_size:n #1
  { \exp_args:Ne \__fontscale_set_font_size_aux:n { \dim_eval:n {#1} } }
\cs_new_protected:Npn \__fontscale_set_font_size_aux:n #1
  {
    \dim_compare:nTF { \c_zero_dim < #1 < 2048pt }
      {
        \fontsize {#1}
          {
            \fp_to_dim:n
              { \dim_to_fp:n {#1} * \l__fontscale_baselineskip_size_ratio_fp }
          }
        \selectfont
      }
      { \msg_error:nnn { fontscale } { font-size-invalid } {#1} }
  }

\NewDocumentCommand \stepfontsize { m }
  {
    \mode_if_math:TF
      { \msg_error:nnn { fontscale } { math-mode-invalid } { stepfontsize } }
      { \__fontscale_step_font_size:n {#1} }
  }
\cs_new_protected:Npn \__fontscale_step_font_size:n #1
  {
    \use:e
      {
        \__fontscale_step_font_size:nn
          {
            \dim_case:nn { \l_fontscale_size_dim }
              {
                { \l_fontscale_tiny_size_dim         } { 0 }
                { \l_fontscale_scriptsize_size_dim   } { 1 }
                { \l_fontscale_footnotesize_size_dim } { 2 }
                { \l_fontscale_small_size_dim        } { 3 }
                { \l_fontscale_normalsize_size_dim   } { 4 }
                { \l_fontscale_large_size_dim        } { 5 }
                { \l_fontscale_Large_size_dim        } { 6 }
                { \l_fontscale_LARGE_size_dim        } { 7 }
                { \l_fontscale_huge_size_dim         } { 8 }
                { \l_fontscale_Huge_size_dim         } { 9 }
              }
          }
          { \int_eval:n {#1} }
      }
  }
\cs_new_protected:Npn \__fontscale_step_font_size:nn #1#2
  {
    \tl_if_empty:nTF {#1}
      { \msg_warning:nn { fontscale } { current-font-step-invalid } }
      {
        \int_compare:nNnTF { \int_abs:n {#2} } > 9
          { \msg_error:nnn { fontscale } { change-in-font-step-invalid } {#2} }
          {
            \int_case:nnF { #1 + #2 }
              {
                { 0 } { \tiny         }
                { 1 } { \scriptsize   }
                { 2 } { \footnotesize }
                { 3 } { \small        }
                { 4 } { \normalsize   }
                { 5 } { \large        }
                { 6 } { \Large        }
                { 7 } { \LARGE        }
                { 8 } { \huge         }
                { 9 } { \Huge         }
              }
              {
                \int_compare:nNnTF { #1 + #2 } > 9
                  {
                    \msg_warning:nnn { fontscale } { font-step-too-large } {#2}
                    \Huge
                  }
                  {
                    \msg_warning:nnn { fontscale } { font-step-too-small } {#2}
                    \tiny
                  }
              }
          }
      }
  }

\NewDocumentCommand \setfontbaselineskip { m }
  {
    \mode_if_math:TF
      {
        \msg_error:nnn { fontscale } { math-mode-invalid }
          { setfontbaselineskip }
      }
      { \__fontscale_set_font_baselineskip:n {#1} }
  }
\cs_new_protected:Npn \__fontscale_set_font_baselineskip:n
  { \__fontscale_set_font_size:nn { \l_fontscale_size_dim } }

\NewDocumentCommand \printsampletext { s +m }
  {
    \mode_if_math:TF
      {
        \msg_error:nnn { fontscale } { math-mode-invalid } { printsampletext }
      }
      {
        \IfBooleanTF #1
          { \__fontscale_print_sample_text_descending_order:n {#2} }
          { \__fontscale_print_sample_text_ascending_order:n {#2} }
      }
  }
\cs_new_protected:Npn \__fontscale_print_sample_text_ascending_order:n #1
  {
    \tl_map_inline:nn
      {
        \tiny \scriptsize \footnotesize \small \normalsize
        \large \Large \LARGE \huge \Huge
      }
      { \group_begin: ##1 #1 \par \group_end: }
  }
\cs_new_protected:Npn \__fontscale_print_sample_text_descending_order:n #1
  {
    \tl_map_inline:nn
      {
        \Huge \huge \LARGE \Large \large
        \normalsize \small \footnotesize \scriptsize \tiny
      }
      { \group_begin: ##1 #1 \par \group_end: }
  }

\NewDocumentCommand \printfontsizecommand { }
  {
    \mode_if_math:TF
      {
        \msg_error:nnn { fontscale } { math-mode-invalid }
          { printfontsizecommand }
      }
      { \__fontscale_print_name: }
  }
\cs_new_protected:Npn \__fontscale_print_name:
  {
    \texttt
      {
        \c_backslash_str
        \dim_case:nnF { \l_fontscale_size_dim }
          {
            { \l_fontscale_tiny_size_dim         } { tiny         }
            { \l_fontscale_scriptsize_size_dim   } { scriptsize   }
            { \l_fontscale_footnotesize_size_dim } { footnotesize }
            { \l_fontscale_small_size_dim        } { small        }
            { \l_fontscale_normalsize_size_dim   } { normalsize   }
            { \l_fontscale_large_size_dim        } { large        }
            { \l_fontscale_Large_size_dim        } { Large        }
            { \l_fontscale_LARGE_size_dim        } { LARGE \@     }
            { \l_fontscale_huge_size_dim         } { huge         }
            { \l_fontscale_Huge_size_dim         } { Huge         }
          }
          { undefined }
      }
  }

% Purifying text

\NewExpandableDocumentCommand \__fontscale_use_none_sm:w { s m } { }
\NewExpandableDocumentCommand \__fontscale_use_none_om:w { o m } { }

\text_declare_purify_equivalent:Nn \fontscalesetup
  { \__fontscale_use_none_sm:w }
\text_declare_purify_equivalent:Nn \setfontsize { \__fontscale_use_none_om:w }
\text_declare_purify_equivalent:Nn \stepfontsize { \use_none:n }
\text_declare_purify_equivalent:Nn \setfontbaselineskip { \use_none:n }

% Messages

\msg_new:nnn { fontscale } { math-mode-invalid }
  { '\iow_char:N \\ #1'~ invalid~ in~ math~ mode~ \msg_line_context:. }
\msg_new:nnn { fontscale } { font-size-invalid }
  {
    Invalid~ font~ size~ '#1'~ \msg_line_context:. \\
    The~ font~ size~ must~ be~ a~ positive~ length~ and~ less~ than~ 2048pt.
  }
% Messages used in \fontscalesetup.
\msg_new:nnn { fontscale } { musical-base-invalid }
  {
    Invalid~ value~ '\dim_use:N \l__fontscale_musical_base_dim'~
    \msg_line_context:. \\
    The~ value~ of~ the~ key~ 'musical / base'~ must~ be~ a~ positive~ length~
    and~ less~ than~ 2048pt.
  }
\msg_new:nnn { fontscale } { musical-ratio-invalid }
  {
    Invalid~ value~ '\fp_to_tl:N \l__fontscale_musical_ratio_fp'~
    \msg_line_context:. \\
    The~ value~ of~ the~ key~ 'musical / ratio'~ must~ be~ greater~ than~ 1.
  }
\msg_new:nnn { fontscale } { musical-notes-invalid }
  {
    Invalid~ value~ '\int_use:N \l__fontscale_musical_notes_int'~
    \msg_line_context:. \\
    The~ value~ of~ the~ key~ 'musical / notes'~ must~ be~ a~ positive~
    integer.
  }
\msg_new:nnn { fontscale } { name-font-size-invalid }
  {
    Invalid~ font~ size~ of~ \iow_char:N \\ #1~
    '\dim_use:c { l_fontscale_#1_size_dim }'~ \msg_line_context:. \\
    The~ font~ size~ must~ be~ a~ positive~ length~ and~ less~ than~ 2048pt.
  }
\msg_new:nnn { fontscale } { size-normalsize-ratio-overwritten }
  {
    The~ font~ size~ set~ by~ the~ key~ '#1 / size-normalsize-ratio'~
    has~ been~ overwritten~ by~ the~ key~ '#1 / size'~ \msg_line_context:.
  }
\msg_new:nnn { fontscale } { baselineskip-size-ratio-overwritten }
  {
    The~ font~ baselineskip~ set~ by~ the~ key~ '#1 / baselineskip-size-ratio'~
    has~ been~ overwritten~ by~ the~ key~ '#1 / baselineskip'~
    \msg_line_context:.
  }
\msg_new:nnn { fontscale } { font-sizes-wrong-order }
  {
    Some~ package~ features~ may~ not~ work~ correctly~ because~ the~ lengths~
    of~ the~ font~ sizes~ are~ not~ correctly~ ordered~ from~
    \iow_char:N \\ tiny~ to~ \iow_char:N \\ Huge~ \msg_line_context:. \\
    The~ lengths~ of~ the~ font~ sizes~ are: \\ \\
    \iow_indent:n
      {
        \iow_char:N \\ tiny:~ \dim_use:N \l_fontscale_tiny_size_dim \\
        \iow_char:N \\ scriptsize:~
          \dim_use:N \l_fontscale_scriptsize_size_dim \\
        \iow_char:N \\ footnotesize:~
          \dim_use:N \l_fontscale_footnotesize_size_dim \\
        \iow_char:N \\ small:~ \dim_use:N \l_fontscale_small_size_dim \\
        \iow_char:N \\ normalsize:~
          \dim_use:N \l_fontscale_normalsize_size_dim \\
        \iow_char:N \\ large:~ \dim_use:N \l_fontscale_large_size_dim \\
        \iow_char:N \\ Large:~ \dim_use:N \l_fontscale_Large_size_dim \\
        \iow_char:N \\ LARGE:~ \dim_use:N \l_fontscale_LARGE_size_dim \\
        \iow_char:N \\ huge:~ \dim_use:N \l_fontscale_huge_size_dim \\
        \iow_char:N \\ Huge:~ \dim_use:N \l_fontscale_Huge_size_dim
      }
  }
% Messages used in \stepfontsize.
\msg_new:nnn { fontscale } { change-in-font-step-invalid }
  {
    Invalid~ change~ in~ font~ step~ '#1'~ \msg_line_context:. \\
    The~ change~ in~ font~ step~ must~ be~ an~ integer~ from~ -9~ to~ 9.
  }
\msg_new:nnn { fontscale } { current-font-step-invalid }
  {
    The~ font~ size~ could~ not~ be~ stepped~ \msg_line_context:. \\
    The~ current~ font~ size~ '\dim_use:N \l_fontscale_size_dim'~
    does~ not~ equal~ the~ font~ size~ of~ any~ font~ size~ command~
    from~ \iow_char:N \\ tiny~ to~ \iow_char:N \\ Huge.
  }
\cs_set_protected:Npn \__fontscale_tmp:nn #1#2
  {
    \msg_new:nnn { fontscale } {#1}
      {
        Using~ \iow_char:N \\ #2~ \msg_line_context:. \\
        The~ font~ size~ '\dim_use:N \l_fontscale_size_dim \c_space_tl
        ( \iow_char:N \\
        \dim_case:nn { \l_fontscale_size_dim }
          {
            { \l_fontscale_tiny_size_dim         } { tiny         }
            { \l_fontscale_scriptsize_size_dim   } { scriptsize   }
            { \l_fontscale_footnotesize_size_dim } { footnotesize }
            { \l_fontscale_small_size_dim        } { small        }
            { \l_fontscale_normalsize_size_dim   } { normalsize   }
            { \l_fontscale_large_size_dim        } { large        }
            { \l_fontscale_Large_size_dim        } { Large        }
            { \l_fontscale_LARGE_size_dim        } { LARGE        }
            { \l_fontscale_huge_size_dim         } { huge         }
            { \l_fontscale_Huge_size_dim         } { Huge         }
          }
        )'~ cannot~ be~ changed~ by~ '##1'~ step
        \int_compare:nNnF { \int_abs:n {##1} } = 1 { s } .
      }
  }
\__fontscale_tmp:nn { font-step-too-small } { tiny }
\__fontscale_tmp:nn { font-step-too-large } { Huge }
