%% autotype.sty
%% Copyright 2020-2024 Stephan Hennig and Keno Wehr
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3
% of this license or (at your option) any later version.
% The latest version of this license is in
% http://www.latex-project.org/lppl.txt
% and version 1.3 or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
\ProvidesExplPackage {autotype} {2024-01-05} {0.5} {automatic language-specific typography}

% Variables
\str_new:N \l_autotype_lang_str % for storing a language name
\str_new:N \l_autotype_font_str % font name given by the user

% Error messages
\msg_new:nnn {autotype} {bad-engine}
  {
    LuaTeX~engine~required.~You~could~try~with~the~'lualatex'~command.
  }
\msg_new:nnn {autotype} {undefined-language} {Language~'#1'~is~not~loaded.}

\msg_new:nnn {autotype} {strange-noligbreak}
  {
    The~ligbreak~option~is~not~active~for~\languagename.~
    The~\noligbreak~command~is~useless~\msg_line_context: .
  }

% Test for the LuaTeX engine
\sys_if_engine_luatex:F { \msg_fatal:nn {autotype} {bad-engine} }

% Load autotype's Lua module
\directlua { autotype = require('autotype') }

% To be able to switch the node list manipulation on and off even within a
% paragraph, we define a LuaTeX attribute for every language and autotype
% option requested be the user. E.g., for breaking up ligatures in language
% 'ngerman' we create an attribute \autotype_ngerman_ligbreak_attr.

% Check if a LuaTeX attribute exists
% #1: language name, #2: autotype option
\prg_new_conditional:Npnn \autotype_attribute_if_exist:nn #1#2 {T,F,TF}
  {
    \cs_if_exist:cTF {autotype_#1_#2_attr} {\prg_return_true:} {\prg_return_false:}
  }

\cs_generate_variant:Nn \autotype_attribute_if_exist:nnT {V}
\cs_generate_variant:Nn \autotype_attribute_if_exist:nnF {V}

% Command for creating LuaTeX attributes
% #1: language name, #2: autotype option
\cs_new:Npn \autotype_new_attribute:nn #1#2
  {
    \exp_after:wN \newattribute \cs:w autotype_#1_#2_attr \cs_end:
  }

\cs_generate_variant:Nn \autotype_new_attribute:nn {Vn}

% Command for setting LuaTeX attributes
% #1: language name, #2: autotype option
\cs_new:Npn \autotype_set_attribute:nn #1#2
  {
    \exp_after:wN \setattribute \cs:w autotype_#1_#2_attr \cs_end: {1}
  }

\cs_generate_variant:Nn \autotype_set_attribute:nn {Vn}

% Command for unsetting LuaTeX attributes
% #1: language name, #2: autotype option
\cs_new:Npn \autotype_unset_attribute:nn #1#2
  {
    \exp_after:wN \unsetattribute \cs:w autotype_#1_#2_attr \cs_end:
  }

\cs_generate_variant:Nn \autotype_unset_attribute:nn {Vn}

% Check if a LuaTeX attribute is set
% #1: language name, #2: autotype option
\prg_new_conditional:Npnn \autotype_attribute_if_set:nn #1#2 {TF}
  {
    \int_compare:nNnTF { \cs:w autotype_#1_#2_attr \cs_end: } = {1}
      {
        \prg_return_true:
      }
      {
        \prg_return_false:
      }
  }

% #1: language name, #2: Lua function name
\cs_new:Npn \autotype_activate_lang_option:nn #1#2
  {
    \@ifpackageloaded {polyglossia}
      {
        \begin {#1} % invoke polyglossia language environment
        \directlua { autotype.#2 ('#1', tex.language) }
        \end {#1}
      }
      {
        \directlua { autotype.#2 ('#1') }
      }
  }

\cs_generate_variant:Nn \autotype_activate_lang_option:nn {Vn}

% #1: attribute name
\cs_new:Npn \autotype_activate_lang_option_with_attribute:n #1
  {
    % Create attribute if not already existing
    \autotype_attribute_if_exist:VnF \l_autotype_lang_str {#1}
      {
        \autotype_new_attribute:Vn \l_autotype_lang_str {#1}
        % Register callback
        \autotype_activate_lang_option:Vn \l_autotype_lang_str {#1}
      }
    % Set attribute
    \autotype_set_attribute:Vn \l_autotype_lang_str {#1}
  }

% #1: attribute name
\cs_new:Npn \autotype_deactivate_lang_option_with_attribute:n #1
  {
    % Unset attribute if existing
    \autotype_attribute_if_exist:VnT \l_autotype_lang_str {#1}
      {
        \autotype_unset_attribute:Vn \l_autotype_lang_str {#1}
      }
  }

% Command for executing language options
% #1: language name, #2: option
\cs_new:Npn \autotype_execute_lang_option:nn #1#2
  {
    \str_set:Nn \l_autotype_lang_str {#1}
    \keys_set:nn {autotype-lang} {#2}
  }

\keys_define:nn {autotype-lang}
  {
    hyphenation .choices:nn = {default, primary, weighted}
      {
        \str_case:nn {#1}
          {
            {default} { \autotype_activate_lang_option:Vn \l_autotype_lang_str {default_hyph} }
            {primary} { \autotype_activate_lang_option:Vn \l_autotype_lang_str {primary_hyph} }
            {weighted} { \autotype_activate_lang_option:Vn \l_autotype_lang_str {weighted_hyph} }
          }
      } ,
    hyphenation .value_required:n = true ,
    mark-hyph. choices:nn = {on, off}
      {
        \str_case:nn {#1}
          {
            {on}  { \autotype_activate_lang_option_with_attribute:n {mark_hyph} }
            {off} { \autotype_deactivate_lang_option_with_attribute:n {mark_hyph} }
          }
      } ,
    mark-hyph .default:n = {on} ,
    ligbreak .choices:nn = {on, off}
      {
        \str_case:nn {#1}
          {
            {on}  { \autotype_activate_lang_option_with_attribute:n {ligbreak} }
            {off} { \autotype_deactivate_lang_option_with_attribute:n {ligbreak} }
          }
      } ,
    ligbreak .default:n = {on} ,
    long-s .choices:nn = {on, off}
      {
        \str_case:nn {#1}
          {
            {on}  { \autotype_activate_lang_option_with_attribute:n {long_s} }
            {off} { \autotype_deactivate_lang_option_with_attribute:n {long_s} }
          }
      } ,
    long-s .default:n = {on}
  }

\keys_define:nn {autotype-font}
  {
    long-s-codepoint .code:n = { \directlua { autotype.set_long_s_codepoint ( "\l_autotype_font_str" , #1 ) } } ,
    round-s-codepoint .code:n = { \directlua { autotype.set_round_s_codepoint ( "\l_autotype_font_str" , #1 ) } } ,
    final-round-s-codepoint .code:n = { \directlua { autotype.set_final_round_s_codepoint ( "\l_autotype_font_str" , #1 ) } }
  }

% Command for executing font options
% #1: font name, #2: option
\cs_new:Npn \autotype_execute_font_option:nn #1#2
  {
    \str_set:Nn \l_autotype_font_str {#1}
    \keys_set:nn {autotype-font} {#2}
  }


%%%%% Document commands

% Command for setting autotype language options
% #1: language name, #2: a comma-separated list of options
\NewDocumentCommand \autotypelangoptions {mm}
  {
    % Test if language #1 is defined and iterate over options.
    \@ifpackageloaded {polyglossia}
      {
        \iflanguageloaded {#1}
          {
            \clist_map_inline:nn {#2} { \autotype_execute_lang_option:nn {#1} {##1} }
          }
          {
            \msg_error:nnn {autotype} {undefined-language} {#1}
          }
      }
      {
        \cs_if_exist:cTF {l@#1}
          {
            \clist_map_inline:nn {#2} { \autotype_execute_lang_option:nn {#1} {##1} }
          }
          {
            \msg_error:nnn {autotype} {undefined-language} {#1}
          }
      }
  }

% Command for setting autotype font options
% #1: font name, #2: a comma-separated list of options
\NewDocumentCommand \autotypefontoptions {mm}
  {
    \clist_map_inline:nn {#2} { \autotype_execute_font_option:nn {#1} {##1} }
  }

% Command for temporarily switching off the breaking up of ligatures
% #1: text for which no ligatures are broken up
% Note: This command may fail, if the language is changed within the argument.
\NewDocumentCommand \noligbreak {m}
  {
    % Check if the current language's ligbreak attribute exists
    \autotype_attribute_if_exist:nnTF {\languagename} {ligbreak}
      {
        % Check if the current language's ligbreak attribute is set
        \autotype_attribute_if_set:nnTF {\languagename} {ligbreak}
          {
            % Store the current language name (just in case it is changed
            % within the command argument)
            \str_set_eq:NN \l_autotype_lang_str \languagename
            % Unset the attribute
            \autotype_unset_attribute:nn {\languagename} {ligbreak}
            % Insert the argument
            #1
            % Reset the attribute
            \autotype_set_attribute:nn {\l_autotype_lang_str} {ligbreak}
          }
          {
            % Issue a warning
            \msg_warning:nn {autotype} {strange-noligbreak}
            % Insert the argument
            #1
          }
      }
      {
        % Issue a warning
        \msg_warning:nn {autotype} {strange-noligbreak}
        % Insert the argument
        #1
      }
  }

% Command for inserting a long s
\NewDocumentCommand \autotypelongs { }
  {
    % Check if the current language's long_s attribute exists
    \autotype_attribute_if_exist:nnTF {\languagename} {long_s}
      {
        % Check if the current language's long_s attribute is set
        \autotype_attribute_if_set:nnTF {\languagename} {long_s}
          {
            % Unset the attribute
            \autotype_unset_attribute:nn {\languagename} {long_s}
            % Insert a long s
            \symbol { \directlua { tex.write(autotype.get_current_long_s_codepoint()) } }
            % Reset the attribute
            \autotype_set_attribute:nn {\languagename} {long_s}
          }
          {
            % Insert a long s
            \symbol { \directlua { tex.write(autotype.get_current_long_s_codepoint()) } }
          }
      }
      {
        % Insert a long s
        \symbol { \directlua { tex.write(autotype.get_current_long_s_codepoint()) } }
      }
  }

% Command for inserting a round s
\NewDocumentCommand \autotyperounds { }
  {
    % Check if the current language's long_s attribute exists
    \autotype_attribute_if_exist:nnTF {\languagename} {long_s}
      {
        % Check if the current language's long_s attribute is set
        \autotype_attribute_if_set:nnTF {\languagename} {long_s}
          {
            % Unset the attribute
            \autotype_unset_attribute:nn {\languagename} {long_s}
            % Insert a round s
            \symbol { \directlua { tex.write(autotype.get_current_round_s_codepoint()) } }
            % Reset the attribute
            \autotype_set_attribute:nn {\languagename} {long_s}
          }
          {
            % Insert a long s
            \symbol { \directlua { tex.write(autotype.get_current_round_s_codepoint()) } }
          }
      }
      {
        % Insert a long s
        \symbol { \directlua { tex.write(autotype.get_current_round_s_codepoint()) } }
      }
  }